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