1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.beans.introspect; 27 28 import java.beans.BeanProperty; 29 import java.lang.reflect.Field; 30 import java.lang.reflect.Method; 31 import java.lang.reflect.Modifier; 32 import java.lang.reflect.Type; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.EnumMap; 36 import java.util.Iterator; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.TreeMap; 40 41 import static com.sun.beans.finder.ClassFinder.findClass; 42 43 public final class PropertyInfo { 44 45 public enum Name { 46 bound, expert, hidden, preferred, required, visualUpdate, description, 47 enumerationValues 48 } 49 50 private static final String VETO_EXCEPTION_NAME = "java.beans.PropertyVetoException"; 51 private static final Class<?> VETO_EXCEPTION; 52 53 static { 54 Class<?> type; 55 try { 56 type = Class.forName(VETO_EXCEPTION_NAME); 57 } catch (Exception exception) { 58 type = null; 59 } 60 VETO_EXCEPTION = type; 61 } 62 63 private Class<?> type; 64 private MethodInfo read; 65 private MethodInfo write; 66 private PropertyInfo indexed; 67 private List<MethodInfo> readList; 68 private List<MethodInfo> writeList; 69 private Map<Name,Object> map; 70 71 private PropertyInfo() { 72 } 73 74 private boolean initialize() { 75 if (this.read != null) { 76 this.type = this.read.type; 77 } 78 if (this.readList != null) { 79 for (MethodInfo info : this.readList) { 80 if ((this.read == null) || this.read.type.isAssignableFrom(info.type)) { 81 this.read = info; 82 this.type = info.type; 83 } 84 } 85 this.readList = null; 86 } 87 if (this.writeList != null) { 88 for (MethodInfo info : this.writeList) { 89 if (this.type == null) { 90 this.write = info; 91 this.type = info.type; 92 } else if (this.type.isAssignableFrom(info.type)) { 93 if ((this.write == null) || this.write.type.isAssignableFrom(info.type)) { 94 this.write = info; 95 } 96 } 97 } 98 this.writeList = null; 99 } 100 if (this.indexed != null) { 101 if ((this.type != null) && !this.type.isArray()) { 102 this.indexed = null; // property type is not an array 103 } else if (!this.indexed.initialize()) { 104 this.indexed = null; // cannot initialize indexed methods 105 } else if ((this.type != null) && (this.indexed.type != this.type.getComponentType())) { 106 this.indexed = null; // different property types 107 } else { 108 this.map = this.indexed.map; 109 this.indexed.map = null; 110 } 111 } 112 if ((this.type == null) && (this.indexed == null)) { 113 return false; 114 } 115 boolean done = initialize(this.read); 116 if (!done) { 117 initialize(this.write); 118 } 119 return true; 120 } 121 122 private boolean initialize(MethodInfo info) { 123 if (info != null) { 124 BeanProperty annotation = info.method.getAnnotation(BeanProperty.class); 125 if (annotation != null) { 126 if (!annotation.bound()) { 127 put(Name.bound, Boolean.FALSE); 128 } 129 put(Name.expert, annotation.expert()); 130 put(Name.required, annotation.required()); 131 put(Name.hidden, annotation.hidden()); 132 put(Name.preferred, annotation.preferred()); 133 put(Name.visualUpdate, annotation.visualUpdate()); 134 put(Name.description, annotation.description()); 135 String[] values = annotation.enumerationValues(); 136 try { 137 Object[] array = new Object[3 * values.length]; 138 int index = 0; 139 for (String value : values) { 140 Class<?> type = info.method.getDeclaringClass(); 141 String name = value; 142 int pos = value.lastIndexOf('.'); 143 if (pos > 0) { 144 name = value.substring(0, pos); 145 if (name.indexOf('.') < 0) { 146 String pkg = type.getName(); 147 name = pkg.substring(0, 1 + Math.max( 148 pkg.lastIndexOf('.'), 149 pkg.lastIndexOf('$'))) + name; 150 } 151 type = findClass(name); 152 name = value.substring(pos + 1); 153 } 154 Field field = type.getField(name); 155 if (Modifier.isStatic(field.getModifiers()) && info.type.isAssignableFrom(field.getType())) { 156 array[index++] = name; 157 array[index++] = field.get(null); 158 array[index++] = value; 159 } 160 } 161 if (index == array.length) { 162 put(Name.enumerationValues, array); 163 } 164 } catch (Exception ignored) { 165 ignored.printStackTrace(); 166 } 167 return true; 168 } 169 } 170 return false; 171 } 172 173 public Class<?> getPropertyType() { 174 return this.type; 175 } 176 177 public Method getReadMethod() { 178 return (this.read == null) ? null : this.read.method; 179 } 180 181 public Method getWriteMethod() { 182 return (this.write == null) ? null : this.write.method; 183 } 184 185 public PropertyInfo getIndexed() { 186 return this.indexed; 187 } 188 189 public boolean isConstrained() { 190 if (this.write != null) { 191 if (VETO_EXCEPTION == null) { 192 for (Class<?> type : this.write.method.getExceptionTypes()) { 193 if (type.getName().equals(VETO_EXCEPTION_NAME)) { 194 return true; 195 } 196 } 197 } else if (this.write.isThrow(VETO_EXCEPTION)) { 198 return true; 199 } 200 } 201 return (this.indexed != null) && this.indexed.isConstrained(); 202 } 203 204 public boolean is(Name name) { 205 Object value = get(name); 206 return (value instanceof Boolean) 207 ? (Boolean) value 208 : Name.bound.equals(name); 209 } 210 211 public Object get(Name name) { 212 return this.map == null ? null : this.map.get(name); 213 } 214 215 private void put(Name name, boolean value) { 216 if (value) { 217 put(name, Boolean.TRUE); 218 } 219 } 220 221 private void put(Name name, String value) { 222 if (0 < value.length()) { 223 put(name, (Object) value); 224 } 225 } 226 227 private void put(Name name, Object value) { 228 if (this.map == null) { 229 this.map = new EnumMap<>(Name.class); 230 } 231 this.map.put(name, value); 232 } 233 234 private static List<MethodInfo> add(List<MethodInfo> list, Method method, Type type) { 235 if (list == null) { 236 list = new ArrayList<>(); 237 } 238 list.add(new MethodInfo(method, type)); 239 return list; 240 } 241 242 private static boolean isPrefix(String name, String prefix) { 243 return name.length() > prefix.length() && name.startsWith(prefix); 244 } 245 246 private static PropertyInfo getInfo(Map<String,PropertyInfo> map, String key, boolean indexed) { 247 PropertyInfo info = map.get(key); 248 if (info == null) { 249 info = new PropertyInfo(); 250 map.put(key, info); 251 } 252 if (!indexed) { 253 return info; 254 } 255 if (info.indexed == null) { 256 info.indexed = new PropertyInfo(); 257 } 258 return info.indexed; 259 } 260 261 public static Map<String,PropertyInfo> get(Class<?> type) { 262 List<Method> methods = ClassInfo.get(type).getMethods(); 263 if (methods.isEmpty()) { 264 return Collections.emptyMap(); 265 } 266 Map<String,PropertyInfo> map = new TreeMap<>(); 267 for (Method method : methods) { 268 if (!Modifier.isStatic(method.getModifiers())) { 269 Class<?> returnType = method.getReturnType(); 270 String name = method.getName(); 271 switch (method.getParameterCount()) { 272 case 0: 273 if (returnType.equals(boolean.class) && isPrefix(name, "is")) { 274 PropertyInfo info = getInfo(map, name.substring(2), false); 275 info.read = new MethodInfo(method, boolean.class); 276 } else if (!returnType.equals(void.class) && isPrefix(name, "get")) { 277 PropertyInfo info = getInfo(map, name.substring(3), false); 278 info.readList = add(info.readList, method, method.getGenericReturnType()); 279 } 280 break; 281 case 1: 282 if (returnType.equals(void.class) && isPrefix(name, "set")) { 283 PropertyInfo info = getInfo(map, name.substring(3), false); 284 info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]); 285 } else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) { 286 PropertyInfo info = getInfo(map, name.substring(3), true); 287 info.readList = add(info.readList, method, method.getGenericReturnType()); 288 } 289 break; 290 case 2: 291 if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) { 292 PropertyInfo info = getInfo(map, name.substring(3), true); 293 info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]); 294 } 295 break; 296 } 297 } 298 } 299 Iterator<PropertyInfo> iterator = map.values().iterator(); 300 while (iterator.hasNext()) { 301 if (!iterator.next().initialize()) { 302 iterator.remove(); 303 } 304 } 305 return !map.isEmpty() 306 ? Collections.unmodifiableMap(map) 307 : Collections.emptyMap(); 308 } 309 }