View Javadoc

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