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 }