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
26package com.sun.beans.introspect;
27
28import java.beans.BeanProperty;
29import java.lang.reflect.Field;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.lang.reflect.Type;
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.EnumMap;
36import java.util.Iterator;
37import java.util.List;
38import java.util.Map;
39import java.util.TreeMap;
40
41import static com.sun.beans.finder.ClassFinder.findClass;
42
43public 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}
310