View Javadoc

1   /**
2    * Copyright (c) 2008-2011, http://www.snakeyaml.org
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.yaml.snakeyaml.representer;
18  
19  import java.beans.IntrospectionException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.yaml.snakeyaml.DumperOptions.FlowStyle;
28  import org.yaml.snakeyaml.error.YAMLException;
29  import org.yaml.snakeyaml.introspector.Property;
30  import org.yaml.snakeyaml.nodes.MappingNode;
31  import org.yaml.snakeyaml.nodes.Node;
32  import org.yaml.snakeyaml.nodes.NodeId;
33  import org.yaml.snakeyaml.nodes.NodeTuple;
34  import org.yaml.snakeyaml.nodes.ScalarNode;
35  import org.yaml.snakeyaml.nodes.SequenceNode;
36  import org.yaml.snakeyaml.nodes.Tag;
37  
38  /**
39   * Represent JavaBeans
40   */
41  public class Representer extends SafeRepresenter {
42  
43      public Representer() {
44          this.representers.put(null, new RepresentJavaBean());
45      }
46  
47      protected class RepresentJavaBean implements Represent {
48          public Node representData(Object data) {
49              try {
50                  return representJavaBean(getProperties(data.getClass()), data);
51              } catch (IntrospectionException e) {
52                  throw new YAMLException(e);
53              }
54          }
55      }
56  
57      /**
58       * Tag logic:<br/>
59       * - explicit root tag is set in serializer <br/>
60       * - if there is a predefined class tag it is used<br/>
61       * - a global tag with class name is always used as tag. The JavaBean parent
62       * of the specified JavaBean may set another tag (tag:yaml.org,2002:map)
63       * when the property class is the same as runtime class
64       * 
65       * @param properties
66       *            JavaBean getters
67       * @param javaBean
68       *            instance for Node
69       * @return Node to get serialized
70       */
71      protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
72          List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
73          Tag tag;
74          Tag customTag = classTags.get(javaBean.getClass());
75          tag = customTag != null ? customTag : new Tag(javaBean.getClass());
76          // flow style will be chosen by BaseRepresenter
77          MappingNode node = new MappingNode(tag, value, null);
78          representedObjects.put(javaBean, node);
79          boolean bestStyle = true;
80          for (Property property : properties) {
81              Object memberValue = property.get(javaBean);
82              Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue
83                      .getClass());
84              NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue,
85                      customPropertyTag);
86              if (tuple == null) {
87                  continue;
88              }
89              if (((ScalarNode) tuple.getKeyNode()).getStyle() != null) {
90                  bestStyle = false;
91              }
92              Node nodeValue = tuple.getValueNode();
93              if (!((nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).getStyle() == null))) {
94                  bestStyle = false;
95              }
96              value.add(tuple);
97          }
98          if (defaultFlowStyle != FlowStyle.AUTO) {
99              node.setFlowStyle(defaultFlowStyle.getStyleBoolean());
100         } else {
101             node.setFlowStyle(bestStyle);
102         }
103         return node;
104     }
105 
106     /**
107      * Represent one JavaBean property.
108      * 
109      * @param javaBean
110      *            - the instance to be represented
111      * @param property
112      *            - the property of the instance
113      * @param propertyValue
114      *            - value to be represented
115      * @param customTag
116      *            - user defined Tag
117      * @return NodeTuple to be used in a MappingNode. Return null to skip the
118      *         property
119      */
120     protected NodeTuple representJavaBeanProperty(Object javaBean, Property property,
121             Object propertyValue, Tag customTag) {
122         ScalarNode nodeKey = (ScalarNode) representData(property.getName());
123         // the first occurrence of the node must keep the tag
124         boolean hasAlias = this.representedObjects.containsKey(propertyValue);
125 
126         Node nodeValue = representData(propertyValue);
127 
128         if (propertyValue != null && !hasAlias) {
129             NodeId nodeId = nodeValue.getNodeId();
130             if (customTag == null) {
131                 if (nodeId == NodeId.scalar) {
132                     if (propertyValue instanceof Enum<?>) {
133                         nodeValue.setTag(Tag.STR);
134                     }
135                 } else {
136                     if (nodeId == NodeId.mapping) {
137                         if (property.getType() == propertyValue.getClass()) {
138                             if (!(propertyValue instanceof Map<?, ?>)) {
139                                 if (!nodeValue.getTag().equals(Tag.SET)) {
140                                     nodeValue.setTag(Tag.MAP);
141                                 }
142                             }
143                         }
144                     }
145                     checkGlobalTag(property, nodeValue, propertyValue);
146                 }
147             }
148         }
149 
150         return new NodeTuple(nodeKey, nodeValue);
151     }
152 
153     /**
154      * Remove redundant global tag for a type safe (generic) collection if it is
155      * the same as defined by the JavaBean property
156      * 
157      * @param property
158      *            - JavaBean property
159      * @param node
160      *            - representation of the property
161      * @param object
162      *            - instance represented by the node
163      */
164     @SuppressWarnings("unchecked")
165     protected void checkGlobalTag(Property property, Node node, Object object) {
166         Class<?>[] arguments = property.getActualTypeArguments();
167         if (arguments != null) {
168             if (node.getNodeId() == NodeId.sequence) {
169                 // apply map tag where class is the same
170                 Class<? extends Object> t = arguments[0];
171                 SequenceNode snode = (SequenceNode) node;
172                 Iterable<Object> memberList;
173                 if (object.getClass().isArray()) {
174                     memberList = Arrays.asList((Object[]) object);
175                 } else {
176                     // list
177                     memberList = (Iterable<Object>) object;
178                 }
179                 Iterator<Object> iter = memberList.iterator();
180                 for (Node childNode : snode.getValue()) {
181                     Object member = iter.next();
182                     if (member != null) {
183                         if (t.equals(member.getClass()))
184                             if (childNode.getNodeId() == NodeId.mapping) {
185                                 childNode.setTag(Tag.MAP);
186                             }
187                     }
188                 }
189             } else if (object instanceof Set) {
190                 Class<?> t = arguments[0];
191                 MappingNode mnode = (MappingNode) node;
192                 Iterator<NodeTuple> iter = mnode.getValue().iterator();
193                 Set<?> set = (Set<?>) object;
194                 for (Object member : set) {
195                     NodeTuple tuple = iter.next();
196                     Node keyNode = tuple.getKeyNode();
197                     if (t.equals(member.getClass())) {
198                         if (keyNode.getNodeId() == NodeId.mapping) {
199                             keyNode.setTag(Tag.MAP);
200                         }
201                     }
202                 }
203             } else if (object instanceof Map) {
204                 Class<?> keyType = arguments[0];
205                 Class<?> valueType = arguments[1];
206                 MappingNode mnode = (MappingNode) node;
207                 for (NodeTuple tuple : mnode.getValue()) {
208                     resetTag(keyType, tuple.getKeyNode());
209                     resetTag(valueType, tuple.getValueNode());
210                 }
211             } else {
212                 // the type for collection entries cannot be
213                 // detected
214             }
215         }
216     }
217 
218     private void resetTag(Class<? extends Object> type, Node node) {
219         Tag tag = node.getTag();
220         if (tag.matches(type)) {
221             if (Enum.class.isAssignableFrom(type)) {
222                 node.setTag(Tag.STR);
223             } else {
224                 node.setTag(Tag.MAP);
225             }
226         }
227     }
228 
229     /**
230      * Get JavaBean properties to be serialised. The order is respected. This
231      * method may be overridden to provide custom property selection or order.
232      * 
233      * @param type
234      *            - JavaBean to inspect the properties
235      * @return properties to serialise
236      */
237     protected Set<Property> getProperties(Class<? extends Object> type)
238             throws IntrospectionException {
239         return getPropertyUtils().getProperties(type);
240     }
241 }