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 }