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.introspector; 17 18 import java.beans.IntrospectionException; 19 import java.beans.Introspector; 20 import java.beans.PropertyDescriptor; 21 import java.lang.reflect.Field; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.util.Collection; 25 import java.util.HashMap; 26 import java.util.LinkedHashMap; 27 import java.util.Map; 28 import java.util.Set; 29 import java.util.TreeSet; 30 31 import org.yaml.snakeyaml.error.YAMLException; 32 33 public class PropertyUtils { 34 35 private final Map<Class<?>, Map<String, Property>> propertiesCache = new HashMap<Class<?>, Map<String, Property>>(); 36 private final Map<Class<?>, Set<Property>> readableProperties = new HashMap<Class<?>, Set<Property>>(); 37 private BeanAccess beanAccess = BeanAccess.DEFAULT; 38 private boolean allowReadOnlyProperties = false; 39 private boolean skipMissingProperties = false; 40 getPropertiesMap(Class<?> type, BeanAccess bAccess)41 protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) 42 throws IntrospectionException { 43 if (propertiesCache.containsKey(type)) { 44 return propertiesCache.get(type); 45 } 46 47 Map<String, Property> properties = new LinkedHashMap<String, Property>(); 48 boolean inaccessableFieldsExist = false; 49 switch (bAccess) { 50 case FIELD: 51 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 52 for (Field field : c.getDeclaredFields()) { 53 int modifiers = field.getModifiers(); 54 if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers) 55 && !properties.containsKey(field.getName())) { 56 properties.put(field.getName(), new FieldProperty(field)); 57 } 58 } 59 } 60 break; 61 default: 62 // add JavaBean properties 63 for (PropertyDescriptor property : Introspector.getBeanInfo(type) 64 .getPropertyDescriptors()) { 65 Method readMethod = property.getReadMethod(); 66 if (readMethod == null || !readMethod.getName().equals("getClass")) { 67 properties.put(property.getName(), new MethodProperty(property)); 68 } 69 } 70 71 // add public fields 72 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 73 for (Field field : c.getDeclaredFields()) { 74 int modifiers = field.getModifiers(); 75 if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { 76 if (Modifier.isPublic(modifiers)) { 77 properties.put(field.getName(), new FieldProperty(field)); 78 } else { 79 inaccessableFieldsExist = true; 80 } 81 } 82 } 83 } 84 break; 85 } 86 if (properties.isEmpty() && inaccessableFieldsExist) { 87 throw new YAMLException("No JavaBean properties found in " + type.getName()); 88 } 89 propertiesCache.put(type, properties); 90 return properties; 91 } 92 getProperties(Class<? extends Object> type)93 public Set<Property> getProperties(Class<? extends Object> type) throws IntrospectionException { 94 return getProperties(type, beanAccess); 95 } 96 getProperties(Class<? extends Object> type, BeanAccess bAccess)97 public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess) 98 throws IntrospectionException { 99 if (readableProperties.containsKey(type)) { 100 return readableProperties.get(type); 101 } 102 Set<Property> properties = createPropertySet(type, bAccess); 103 readableProperties.put(type, properties); 104 return properties; 105 } 106 createPropertySet(Class<? extends Object> type, BeanAccess bAccess)107 protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess) 108 throws IntrospectionException { 109 Set<Property> properties = new TreeSet<Property>(); 110 Collection<Property> props = getPropertiesMap(type, bAccess).values(); 111 for (Property property : props) { 112 if (property.isReadable() && (allowReadOnlyProperties || property.isWritable())) { 113 properties.add(property); 114 } 115 } 116 return properties; 117 } 118 getProperty(Class<? extends Object> type, String name)119 public Property getProperty(Class<? extends Object> type, String name) 120 throws IntrospectionException { 121 return getProperty(type, name, beanAccess); 122 } 123 getProperty(Class<? extends Object> type, String name, BeanAccess bAccess)124 public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess) 125 throws IntrospectionException { 126 Map<String, Property> properties = getPropertiesMap(type, bAccess); 127 Property property = properties.get(name); 128 if (property == null && skipMissingProperties) { 129 property = new MissingProperty(name); 130 } 131 if (property == null || !property.isWritable()) { 132 throw new YAMLException("Unable to find property '" + name + "' on class: " 133 + type.getName()); 134 } 135 return property; 136 } 137 setBeanAccess(BeanAccess beanAccess)138 public void setBeanAccess(BeanAccess beanAccess) { 139 if (this.beanAccess != beanAccess) { 140 this.beanAccess = beanAccess; 141 propertiesCache.clear(); 142 readableProperties.clear(); 143 } 144 } 145 setAllowReadOnlyProperties(boolean allowReadOnlyProperties)146 public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) { 147 if (this.allowReadOnlyProperties != allowReadOnlyProperties) { 148 this.allowReadOnlyProperties = allowReadOnlyProperties; 149 readableProperties.clear(); 150 } 151 } 152 153 /** 154 * Skip properties that are missing during deserialization of YAML to a Java 155 * object. The default is false. 156 * 157 * @param skipMissingProperties 158 * true if missing properties should be skipped, false otherwise. 159 */ setSkipMissingProperties(boolean skipMissingProperties)160 public void setSkipMissingProperties(boolean skipMissingProperties) { 161 if (this.skipMissingProperties != skipMissingProperties) { 162 this.skipMissingProperties = skipMissingProperties; 163 readableProperties.clear(); 164 } 165 } 166 } 167