1 /** 2 * Copyright (c) 2008, SnakeYAML 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package org.yaml.snakeyaml.introspector; 15 16 import java.beans.FeatureDescriptor; 17 import java.beans.IntrospectionException; 18 import java.beans.Introspector; 19 import java.beans.PropertyDescriptor; 20 import java.lang.reflect.Field; 21 import java.lang.reflect.Method; 22 import java.lang.reflect.Modifier; 23 import java.util.Collection; 24 import java.util.HashMap; 25 import java.util.LinkedHashMap; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.TreeSet; 29 import org.yaml.snakeyaml.error.YAMLException; 30 import org.yaml.snakeyaml.util.PlatformFeatureDetector; 31 32 public class PropertyUtils { 33 34 private final Map<Class<?>, Map<String, Property>> propertiesCache = 35 new HashMap<Class<?>, Map<String, Property>>(); 36 private final Map<Class<?>, Set<Property>> readableProperties = 37 new HashMap<Class<?>, Set<Property>>(); 38 private BeanAccess beanAccess = BeanAccess.DEFAULT; 39 private boolean allowReadOnlyProperties = false; 40 private boolean skipMissingProperties = false; 41 42 private final PlatformFeatureDetector platformFeatureDetector; 43 PropertyUtils()44 public PropertyUtils() { 45 this(new PlatformFeatureDetector()); 46 } 47 PropertyUtils(PlatformFeatureDetector platformFeatureDetector)48 PropertyUtils(PlatformFeatureDetector platformFeatureDetector) { 49 this.platformFeatureDetector = platformFeatureDetector; 50 51 /* 52 * Android lacks much of java.beans (including the Introspector class, used here), because 53 * java.beans classes tend to rely on java.awt, which isn't supported in the Android SDK. That 54 * means we have to fall back on FIELD access only when SnakeYAML is running on the Android 55 * Runtime. 56 */ 57 if (platformFeatureDetector.isRunningOnAndroid()) { 58 beanAccess = BeanAccess.FIELD; 59 } 60 } 61 getPropertiesMap(Class<?> type, BeanAccess bAccess)62 protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) { 63 if (propertiesCache.containsKey(type)) { 64 return propertiesCache.get(type); 65 } 66 67 Map<String, Property> properties = new LinkedHashMap<String, Property>(); 68 boolean inaccessableFieldsExist = false; 69 if (bAccess == BeanAccess.FIELD) { 70 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 71 for (Field field : c.getDeclaredFields()) { 72 int modifiers = field.getModifiers(); 73 if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers) 74 && !properties.containsKey(field.getName())) { 75 properties.put(field.getName(), new FieldProperty(field)); 76 } 77 } 78 } 79 } else {// add JavaBean properties 80 try { 81 for (PropertyDescriptor property : Introspector.getBeanInfo(type) 82 .getPropertyDescriptors()) { 83 Method readMethod = property.getReadMethod(); 84 if ((readMethod == null || !readMethod.getName().equals("getClass")) 85 && !isTransient(property)) { 86 properties.put(property.getName(), new MethodProperty(property)); 87 } 88 } 89 } catch (IntrospectionException e) { 90 throw new YAMLException(e); 91 } 92 93 // add public fields 94 for (Class<?> c = type; c != null; c = c.getSuperclass()) { 95 for (Field field : c.getDeclaredFields()) { 96 int modifiers = field.getModifiers(); 97 if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { 98 if (Modifier.isPublic(modifiers)) { 99 properties.put(field.getName(), new FieldProperty(field)); 100 } else { 101 inaccessableFieldsExist = true; 102 } 103 } 104 } 105 } 106 } 107 if (properties.isEmpty() && inaccessableFieldsExist) { 108 throw new YAMLException("No JavaBean properties found in " + type.getName()); 109 } 110 propertiesCache.put(type, properties); 111 return properties; 112 } 113 114 private static final String TRANSIENT = "transient"; 115 isTransient(FeatureDescriptor fd)116 private boolean isTransient(FeatureDescriptor fd) { 117 return Boolean.TRUE.equals(fd.getValue(TRANSIENT)); 118 } 119 getProperties(Class<? extends Object> type)120 public Set<Property> getProperties(Class<? extends Object> type) { 121 return getProperties(type, beanAccess); 122 } 123 getProperties(Class<? extends Object> type, BeanAccess bAccess)124 public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess) { 125 if (readableProperties.containsKey(type)) { 126 return readableProperties.get(type); 127 } 128 Set<Property> properties = createPropertySet(type, bAccess); 129 readableProperties.put(type, properties); 130 return properties; 131 } 132 createPropertySet(Class<? extends Object> type, BeanAccess bAccess)133 protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess) { 134 Set<Property> properties = new TreeSet<Property>(); 135 Collection<Property> props = getPropertiesMap(type, bAccess).values(); 136 for (Property property : props) { 137 if (property.isReadable() && (allowReadOnlyProperties || property.isWritable())) { 138 properties.add(property); 139 } 140 } 141 return properties; 142 } 143 getProperty(Class<? extends Object> type, String name)144 public Property getProperty(Class<? extends Object> type, String name) { 145 return getProperty(type, name, beanAccess); 146 } 147 getProperty(Class<? extends Object> type, String name, BeanAccess bAccess)148 public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess) { 149 Map<String, Property> properties = getPropertiesMap(type, bAccess); 150 Property property = properties.get(name); 151 if (property == null && skipMissingProperties) { 152 property = new MissingProperty(name); 153 } 154 if (property == null) { 155 throw new YAMLException("Unable to find property '" + name + "' on class: " + type.getName()); 156 } 157 return property; 158 } 159 setBeanAccess(BeanAccess beanAccess)160 public void setBeanAccess(BeanAccess beanAccess) { 161 if (platformFeatureDetector.isRunningOnAndroid() && beanAccess != BeanAccess.FIELD) { 162 throw new IllegalArgumentException("JVM is Android - only BeanAccess.FIELD is available"); 163 } 164 165 if (this.beanAccess != beanAccess) { 166 this.beanAccess = beanAccess; 167 propertiesCache.clear(); 168 readableProperties.clear(); 169 } 170 } 171 setAllowReadOnlyProperties(boolean allowReadOnlyProperties)172 public void setAllowReadOnlyProperties(boolean allowReadOnlyProperties) { 173 if (this.allowReadOnlyProperties != allowReadOnlyProperties) { 174 this.allowReadOnlyProperties = allowReadOnlyProperties; 175 readableProperties.clear(); 176 } 177 } 178 isAllowReadOnlyProperties()179 public boolean isAllowReadOnlyProperties() { 180 return allowReadOnlyProperties; 181 } 182 183 /** 184 * Skip properties that are missing during deserialization of YAML to a Java object. The default 185 * is false. 186 * 187 * @param skipMissingProperties true if missing properties should be skipped, false otherwise. 188 */ setSkipMissingProperties(boolean skipMissingProperties)189 public void setSkipMissingProperties(boolean skipMissingProperties) { 190 if (this.skipMissingProperties != skipMissingProperties) { 191 this.skipMissingProperties = skipMissingProperties; 192 readableProperties.clear(); 193 } 194 } 195 isSkipMissingProperties()196 public boolean isSkipMissingProperties() { 197 return skipMissingProperties; 198 } 199 } 200