/* * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.beans.introspect; import java.beans.BeanProperty; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import static com.sun.beans.finder.ClassFinder.findClass; public final class PropertyInfo { public enum Name { bound, expert, hidden, preferred, required, visualUpdate, description, enumerationValues } private static final String VETO_EXCEPTION_NAME = "java.beans.PropertyVetoException"; private static final Class VETO_EXCEPTION; static { Class type; try { type = Class.forName(VETO_EXCEPTION_NAME); } catch (Exception exception) { type = null; } VETO_EXCEPTION = type; } private Class type; private MethodInfo read; private MethodInfo write; private PropertyInfo indexed; private List readList; private List writeList; private Map map; private PropertyInfo() { } private boolean initialize() { if (this.read != null) { this.type = this.read.type; } if (this.readList != null) { for (MethodInfo info : this.readList) { if ((this.read == null) || this.read.type.isAssignableFrom(info.type)) { this.read = info; this.type = info.type; } } this.readList = null; } if (this.writeList != null) { for (MethodInfo info : this.writeList) { if (this.type == null) { this.write = info; this.type = info.type; } else if (this.type.isAssignableFrom(info.type)) { if ((this.write == null) || this.write.type.isAssignableFrom(info.type)) { this.write = info; } } } this.writeList = null; } if (this.indexed != null) { if ((this.type != null) && !this.type.isArray()) { this.indexed = null; // property type is not an array } else if (!this.indexed.initialize()) { this.indexed = null; // cannot initialize indexed methods } else if ((this.type != null) && (this.indexed.type != this.type.getComponentType())) { this.indexed = null; // different property types } else { this.map = this.indexed.map; this.indexed.map = null; } } if ((this.type == null) && (this.indexed == null)) { return false; } boolean done = initialize(this.read); if (!done) { initialize(this.write); } return true; } private boolean initialize(MethodInfo info) { if (info != null) { BeanProperty annotation = info.method.getAnnotation(BeanProperty.class); if (annotation != null) { if (!annotation.bound()) { put(Name.bound, Boolean.FALSE); } put(Name.expert, annotation.expert()); put(Name.required, annotation.required()); put(Name.hidden, annotation.hidden()); put(Name.preferred, annotation.preferred()); put(Name.visualUpdate, annotation.visualUpdate()); put(Name.description, annotation.description()); String[] values = annotation.enumerationValues(); try { Object[] array = new Object[3 * values.length]; int index = 0; for (String value : values) { Class type = info.method.getDeclaringClass(); String name = value; int pos = value.lastIndexOf('.'); if (pos > 0) { name = value.substring(0, pos); if (name.indexOf('.') < 0) { String pkg = type.getName(); name = pkg.substring(0, 1 + Math.max( pkg.lastIndexOf('.'), pkg.lastIndexOf('$'))) + name; } type = findClass(name); name = value.substring(pos + 1); } Field field = type.getField(name); if (Modifier.isStatic(field.getModifiers()) && info.type.isAssignableFrom(field.getType())) { array[index++] = name; array[index++] = field.get(null); array[index++] = value; } } if (index == array.length) { put(Name.enumerationValues, array); } } catch (Exception ignored) { ignored.printStackTrace(); } return true; } } return false; } public Class getPropertyType() { return this.type; } public Method getReadMethod() { return (this.read == null) ? null : this.read.method; } public Method getWriteMethod() { return (this.write == null) ? null : this.write.method; } public PropertyInfo getIndexed() { return this.indexed; } public boolean isConstrained() { if (this.write != null) { if (VETO_EXCEPTION == null) { for (Class type : this.write.method.getExceptionTypes()) { if (type.getName().equals(VETO_EXCEPTION_NAME)) { return true; } } } else if (this.write.isThrow(VETO_EXCEPTION)) { return true; } } return (this.indexed != null) && this.indexed.isConstrained(); } public boolean is(Name name) { Object value = get(name); return (value instanceof Boolean) ? (Boolean) value : Name.bound.equals(name); } public Object get(Name name) { return this.map == null ? null : this.map.get(name); } private void put(Name name, boolean value) { if (value) { put(name, Boolean.TRUE); } } private void put(Name name, String value) { if (0 < value.length()) { put(name, (Object) value); } } private void put(Name name, Object value) { if (this.map == null) { this.map = new EnumMap<>(Name.class); } this.map.put(name, value); } private static List add(List list, Method method, Type type) { if (list == null) { list = new ArrayList<>(); } list.add(new MethodInfo(method, type)); return list; } private static boolean isPrefix(String name, String prefix) { return name.length() > prefix.length() && name.startsWith(prefix); } private static PropertyInfo getInfo(Map map, String key, boolean indexed) { PropertyInfo info = map.get(key); if (info == null) { info = new PropertyInfo(); map.put(key, info); } if (!indexed) { return info; } if (info.indexed == null) { info.indexed = new PropertyInfo(); } return info.indexed; } public static Map get(Class type) { List methods = ClassInfo.get(type).getMethods(); if (methods.isEmpty()) { return Collections.emptyMap(); } Map map = new TreeMap<>(); for (Method method : methods) { if (!Modifier.isStatic(method.getModifiers())) { Class returnType = method.getReturnType(); String name = method.getName(); switch (method.getParameterCount()) { case 0: if (returnType.equals(boolean.class) && isPrefix(name, "is")) { PropertyInfo info = getInfo(map, name.substring(2), false); info.read = new MethodInfo(method, boolean.class); } else if (!returnType.equals(void.class) && isPrefix(name, "get")) { PropertyInfo info = getInfo(map, name.substring(3), false); info.readList = add(info.readList, method, method.getGenericReturnType()); } break; case 1: if (returnType.equals(void.class) && isPrefix(name, "set")) { PropertyInfo info = getInfo(map, name.substring(3), false); info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]); } else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) { PropertyInfo info = getInfo(map, name.substring(3), true); info.readList = add(info.readList, method, method.getGenericReturnType()); } break; case 2: if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) { PropertyInfo info = getInfo(map, name.substring(3), true); info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]); } break; } } } Iterator iterator = map.values().iterator(); while (iterator.hasNext()) { if (!iterator.next().initialize()) { iterator.remove(); } } return !map.isEmpty() ? Collections.unmodifiableMap(map) : Collections.emptyMap(); } }