Home | History | Annotate | Download | only in representer
      1 /**
      2  * Copyright (c) 2008, 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.Collections;
     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         // Skip primitive arrays.
    167         if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
    168             return;
    169         }
    170 
    171         Class<?>[] arguments = property.getActualTypeArguments();
    172         if (arguments != null) {
    173             if (node.getNodeId() == NodeId.sequence) {
    174                 // apply map tag where class is the same
    175                 Class<? extends Object> t = arguments[0];
    176                 SequenceNode snode = (SequenceNode) node;
    177                 Iterable<Object> memberList = Collections.EMPTY_LIST;
    178                 if (object.getClass().isArray()) {
    179                     memberList = Arrays.asList((Object[]) object);
    180                 } else if (object instanceof Iterable<?>) {
    181                     // list
    182                     memberList = (Iterable<Object>) object;
    183                 }
    184                 Iterator<Object> iter = memberList.iterator();
    185                 if (iter.hasNext()) {
    186                     for (Node childNode : snode.getValue()) {
    187                         Object member = iter.next();
    188                         if (member != null) {
    189                             if (t.equals(member.getClass()))
    190                                 if (childNode.getNodeId() == NodeId.mapping) {
    191                                     childNode.setTag(Tag.MAP);
    192                                 }
    193                         }
    194                     }
    195                 }
    196             } else if (object instanceof Set) {
    197                 Class<?> t = arguments[0];
    198                 MappingNode mnode = (MappingNode) node;
    199                 Iterator<NodeTuple> iter = mnode.getValue().iterator();
    200                 Set<?> set = (Set<?>) object;
    201                 for (Object member : set) {
    202                     NodeTuple tuple = iter.next();
    203                     Node keyNode = tuple.getKeyNode();
    204                     if (t.equals(member.getClass())) {
    205                         if (keyNode.getNodeId() == NodeId.mapping) {
    206                             keyNode.setTag(Tag.MAP);
    207                         }
    208                     }
    209                 }
    210             } else if (object instanceof Map) {
    211                 Class<?> keyType = arguments[0];
    212                 Class<?> valueType = arguments[1];
    213                 MappingNode mnode = (MappingNode) node;
    214                 for (NodeTuple tuple : mnode.getValue()) {
    215                     resetTag(keyType, tuple.getKeyNode());
    216                     resetTag(valueType, tuple.getValueNode());
    217                 }
    218             } else {
    219                 // the type for collection entries cannot be
    220                 // detected
    221             }
    222         }
    223     }
    224 
    225     private void resetTag(Class<? extends Object> type, Node node) {
    226         Tag tag = node.getTag();
    227         if (tag.matches(type)) {
    228             if (Enum.class.isAssignableFrom(type)) {
    229                 node.setTag(Tag.STR);
    230             } else {
    231                 node.setTag(Tag.MAP);
    232             }
    233         }
    234     }
    235 
    236     /**
    237      * Get JavaBean properties to be serialised. The order is respected. This
    238      * method may be overridden to provide custom property selection or order.
    239      *
    240      * @param type
    241      *            - JavaBean to inspect the properties
    242      * @return properties to serialise
    243      */
    244     protected Set<Property> getProperties(Class<? extends Object> type)
    245             throws IntrospectionException {
    246         return getPropertyUtils().getProperties(type);
    247     }
    248 }
    249