1 /* 2 * Copyright (c) 2014, 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 package com.sun.beans.introspect; 26 27 import java.beans.BeanProperty; 28 import java.lang.reflect.Field; 29 import java.lang.reflect.Method; 30 import java.lang.reflect.Modifier; 31 import java.lang.reflect.Type; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.EnumMap; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.TreeMap; 39 40 import static com.sun.beans.finder.ClassFinder.findClass; 41 42 public final class PropertyInfo { 43 public enum Name {bound, expert, hidden, preferred, required, visualUpdate, description, enumerationValues} 44 45 private static final String VETO_EXCEPTION_NAME = "java.beans.PropertyVetoException"; 46 private static final Class<?> VETO_EXCEPTION; 47 48 static { 49 Class<?> type; 50 try { 51 type = Class.forName(VETO_EXCEPTION_NAME); 52 } catch (Exception exception) { 53 type = null; 54 } 55 VETO_EXCEPTION = type; 56 } 57 58 private Class<?> type; 59 private MethodInfo read; 60 private MethodInfo write; 61 private PropertyInfo indexed; 62 private List<MethodInfo> readList; 63 private List<MethodInfo> writeList; 64 private Map<Name,Object> map; 65 66 private PropertyInfo() { 67 } 68 69 private boolean initialize() { 70 if (this.read != null) { 71 this.type = this.read.type; 72 } 73 if (this.readList != null) { 74 for (MethodInfo info : this.readList) { 75 if ((this.read == null) || this.read.type.isAssignableFrom(info.type)) { 76 this.read = info; 77 this.type = info.type; 78 } 79 } 80 this.readList = null; 81 } 82 if (this.writeList != null) { 83 for (MethodInfo info : this.writeList) { 84 if (this.type == null) { 85 this.write = info; 86 this.type = info.type; 87 } else if (this.type.isAssignableFrom(info.type)) { 88 if ((this.write == null) || this.write.type.isAssignableFrom(info.type)) { 89 this.write = info; 90 } 91 } 92 } 93 this.writeList = null; 94 } 95 if (this.indexed != null) { 96 if ((this.type != null) && !this.type.isArray()) { 97 this.indexed = null; // property type is not an array 98 } else if (!this.indexed.initialize()) { 99 this.indexed = null; // cannot initialize indexed methods 100 } else if ((this.type != null) && (this.indexed.type != this.type.getComponentType())) { 101 this.indexed = null; // different property types 102 } else { 103 this.map = this.indexed.map; 104 this.indexed.map = null; 105 } 106 } 107 if ((this.type == null) && (this.indexed == null)) { 108 return false; 109 } 110 initialize(this.write); 111 initialize(this.read); 112 return true; 113 } 114 115 private void initialize(MethodInfo info) { 116 if (info != null) { 117 BeanProperty annotation = info.method.getAnnotation(BeanProperty.class); 118 if (annotation != null) { 119 if (!annotation.bound()) { 120 put(Name.bound, Boolean.FALSE); 121 } 122 put(Name.expert, annotation.expert()); 123 put(Name.required, annotation.required()); 124 put(Name.hidden, annotation.hidden()); 125 put(Name.preferred, annotation.preferred()); 126 put(Name.visualUpdate, annotation.visualUpdate()); 127 put(Name.description, annotation.description()); 128 String[] values = annotation.enumerationValues(); 129 try { 130 Object[] array = new Object[3 * values.length]; 131 int index = 0; 132 for (String value : values) { 133 Class<?> type = info.method.getDeclaringClass(); 134 String name = value; 135 int pos = value.lastIndexOf('.'); 136 if (pos > 0) { 137 name = value.substring(0, pos); 138 if (name.indexOf('.') < 0) { 139 String pkg = type.getName(); 140 name = pkg.substring(0, 1 + Math.max( 141 pkg.lastIndexOf('.'), 142 pkg.lastIndexOf('$'))) + name; 143 } 144 type = findClass(name); 145 name = value.substring(pos + 1); 146 } 147 Field field = type.getField(name); 148 if (Modifier.isStatic(field.getModifiers()) && info.type.isAssignableFrom(field.getType())) { 149 array[index++] = name; 150 array[index++] = field.get(null); 151 array[index++] = value; 152 } 153 } 154 if (index == array.length) { 155 put(Name.enumerationValues, array); 156 } 157 } catch (Exception ignored) { 158 ignored.printStackTrace(); 159 } 160 } 161 } 162 } 163 164 public Class<?> getPropertyType() { 165 return this.type; 166 } 167 168 public Method getReadMethod() { 169 return (this.read == null) ? null : this.read.method; 170 } 171 172 public Method getWriteMethod() { 173 return (this.write == null) ? null : this.write.method; 174 } 175 176 public PropertyInfo getIndexed() { 177 return this.indexed; 178 } 179 180 public boolean isConstrained() { 181 if (this.write != null) { 182 if (VETO_EXCEPTION == null) { 183 for (Class<?> type : this.write.method.getExceptionTypes()) { 184 if (type.getName().equals(VETO_EXCEPTION_NAME)) { 185 return true; 186 } 187 } 188 } else if (this.write.isThrow(VETO_EXCEPTION)) { 189 return true; 190 } 191 } 192 return (this.indexed != null) && this.indexed.isConstrained(); 193 } 194 195 public boolean is(Name name) { 196 Object value = get(name); 197 return (value instanceof Boolean) 198 ? (Boolean) value 199 : Name.bound.equals(name); 200 } 201 202 public Object get(Name name) { 203 return this.map == null ? null : this.map.get(name); 204 } 205 206 private void put(Name name, boolean value) { 207 if (value) { 208 put(name, Boolean.TRUE); 209 } 210 } 211 212 private void put(Name name, String value) { 213 if (0 < value.length()) { 214 put(name, (Object) value); 215 } 216 } 217 218 private void put(Name name, Object value) { 219 if (this.map == null) { 220 this.map = new EnumMap<>(Name.class); 221 } 222 this.map.put(name, value); 223 } 224 225 private static List<MethodInfo> add(List<MethodInfo> list, Method method, Type type) { 226 if (list == null) { 227 list = new ArrayList<>(); 228 } 229 list.add(new MethodInfo(method, type)); 230 return list; 231 } 232 233 private static boolean isPrefix(String name, String prefix) { 234 return name.length() > prefix.length() && name.startsWith(prefix); 235 } 236 237 private static PropertyInfo getInfo(Map<String,PropertyInfo> map, String key, boolean indexed) { 238 PropertyInfo info = map.get(key); 239 if (info == null) { 240 info = new PropertyInfo(); 241 map.put(key, info); 242 } 243 if (!indexed) { 244 return info; 245 } 246 if (info.indexed == null) { 247 info.indexed = new PropertyInfo(); 248 } 249 return info.indexed; 250 } 251 252 public static Map<String,PropertyInfo> get(Class<?> type) { 253 List<Method> methods = ClassInfo.get(type).getMethods(); 254 if (methods.isEmpty()) { 255 return Collections.emptyMap(); 256 } 257 Map<String,PropertyInfo> map = new TreeMap<>(); 258 for (Method method : methods) { 259 if (!Modifier.isStatic(method.getModifiers())) { 260 Class<?> returnType = method.getReturnType(); 261 String name = method.getName(); 262 switch (method.getParameterCount()) { 263 case 0: 264 if (returnType.equals(boolean.class) && isPrefix(name, "is")) { 265 PropertyInfo info = getInfo(map, name.substring(2), false); 266 info.read = new MethodInfo(method, boolean.class); 267 } else if (!returnType.equals(void.class) && isPrefix(name, "get")) { 268 PropertyInfo info = getInfo(map, name.substring(3), false); 269 info.readList = add(info.readList, method, method.getGenericReturnType()); 270 } 271 break; 272 case 1: 273 if (returnType.equals(void.class) && isPrefix(name, "set")) { 274 PropertyInfo info = getInfo(map, name.substring(3), false); 275 info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]); 276 } else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) { 277 PropertyInfo info = getInfo(map, name.substring(3), true); 278 info.readList = add(info.readList, method, method.getGenericReturnType()); 279 } 280 break; 281 case 2: 282 if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) { 283 PropertyInfo info = getInfo(map, name.substring(3), true); 284 info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]); 285 } 286 break; 287 } 288 } 289 } 290 Iterator<PropertyInfo> iterator = map.values().iterator(); 291 while (iterator.hasNext()) { 292 if (!iterator.next().initialize()) { 293 iterator.remove(); 294 } 295 } 296 return !map.isEmpty() 297 ? Collections.unmodifiableMap(map) 298 : Collections.emptyMap(); 299 } 300 }