1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.tomgibara.pronto.util;
19
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23
24 /**
25 * Collection of static method to help with handling reflective code.
26 *
27 * @author Tom Gibara
28 */
29
30
31 public final class Reflect {
32
33 private static final MethodFilter SET_FILTER = new MethodFilter() {
34 public boolean accept(final Method method) {
35
36 String name = method.getName();
37 if (name.length() < 4) return false;
38 if (!name.startsWith("set")) return false;
39 if (Character.isLowerCase(name.charAt(3))) return false;
40
41 if (method.getReturnType() != Void.TYPE) return false;
42 if (method.getParameterTypes().length != 1) return false;
43
44 return true;
45 };
46 };
47
48 private static final MethodFilter GET_FILTER = new MethodFilter() {
49 public boolean accept(final Method method) {
50 String name = method.getName();
51 if (name.equals("getClass")) return false;
52 if (name.startsWith("get")) {
53
54 if (name.length() < 4) return false;
55 if (Character.isLowerCase(name.charAt(3))) return false;
56
57 if (method.getReturnType() == Void.TYPE) return false;
58 if (method.getParameterTypes().length != 0) return false;
59
60 return true;
61 } else if (name.startsWith("is")) {
62
63 if (name.length() < 3) return false;
64 if (Character.isLowerCase(name.charAt(2))) return false;
65
66 if (method.getReturnType() != Boolean.TYPE) return false;
67 if (method.getParameterTypes().length != 0) return false;
68
69 return true;
70 } else {
71 return false;
72 }
73 };
74 };
75
76 /**
77 * Converts a studleyCaps string (with or without an initial capital) into a
78 * hyphenated lower-case string. As an example both "MaxValue" and
79 * "maxValue" become "max-value".
80 *
81 * @param name
82 * the name to be converted
83 * @return the converted name.
84 */
85
86 public static String capsToHyphens(final String name) {
87 StringBuilder sb = new StringBuilder(name.length() + 4);
88
89
90
91
92 boolean wasUpper = true;
93 for (int i = 0; i < name.length(); i++) {
94 char c = name.charAt(i);
95 boolean isUpper = Character.isUpperCase(c);
96 if (isUpper) {
97 if (!wasUpper) sb.append('-');
98 sb.append(Character.toLowerCase(c));
99 } else {
100 sb.append(c);
101 }
102 wasUpper = isUpper;
103 }
104 return sb.toString();
105 }
106
107 /**
108 * Converts a method name into a propery name by trimming the 'set', 'get'
109 * or 'is' prefix and then converting capital letters to hyphens. For
110 * example: getBaseHTML becomes "base-url"
111 *
112 * @param methodName the name of a java method
113 * @return the method name converted into a property name
114 */
115
116 public static String propertyName(final String methodName) {
117 String name;
118 if (methodName.startsWith("get")) {
119 name = methodName.substring(3);
120 } else if (methodName.startsWith("set")) {
121 name = methodName.substring(3);
122 } else if (methodName.startsWith("is")) {
123 name = methodName.substring(2);
124 } else {
125 name = methodName;
126 }
127 return capsToHyphens(name);
128 }
129
130 /**
131 * Filters an array of methods, selecting only the void setXXX methods with
132 * one argument. This method always returns a new array instance.
133 *
134 * @param methods
135 * the methods from which setter methods are to be extracted
136 * @return a new array containing only the setter methods
137 */
138
139 public static Method[] setters(final Method[] methods) {
140 return filter(methods, SET_FILTER);
141 }
142
143 /**
144 * Filters an array of methods, selecting only the non-void getXXX and isXXX
145 * methods with no arguments. This method always returns a new array
146 * instance. The getClass method is not returned.
147 *
148 * @param methods
149 * the methods from which getter methods are to be extracted
150 * @return a new array containing only the getter methods
151 */
152
153 public static Method[] getters(final Method[] methods) {
154 return filter(methods, GET_FILTER);
155 }
156
157 /**
158 * Returns the default (no-argument) constructor for the supplied class or
159 * null if it's not available.
160 *
161 * @param clss
162 * a class
163 * @return the default constructor for the supplied class
164 */
165
166 public static Constructor defaultConstructor(final Class clss) {
167 Constructor[] constructors = clss.getConstructors();
168 for (int i = 0; i < constructors.length; i++) {
169 Constructor constructor = constructors[i];
170 if (constructor.getParameterTypes().length == 0) return constructor;
171 }
172 return null;
173 }
174
175 /**
176 * Identifies the public constructor to which Java would dispatch the
177 * supplied array of types.
178 *
179 * @param clss
180 * the class with the constructors
181 * @param parameterTypes
182 * an array of parameter types
183 *
184 * @return the 'most-apt' constructor or null
185 */
186
187 public static Constructor matchingConstructor(final Class clss, final Class[] parameterTypes) {
188 Constructor[] constructors = clss.getConstructors();
189 Constructor best = null;
190 for (int i = 0; i < constructors.length; i++) {
191 Constructor constructor = constructors[i];
192 Class[] constructorTypes = constructor.getParameterTypes();
193 if (constructorTypes.length != parameterTypes.length) continue;
194 if (isAssignableFrom(constructorTypes, parameterTypes)) {
195 if (best == null) {
196 best = constructor;
197 } else {
198 if (isAssignableFrom(constructorTypes, best.getParameterTypes())) {
199 best = constructor;
200 } else {
201
202 }
203 }
204 }
205 }
206 return best;
207 }
208
209 @SuppressWarnings("unchecked")
210 private static boolean isAssignableFrom(final Class[] cs1, final Class[] cs2) {
211 if (cs1.length != cs2.length) throw new IllegalArgumentException("unequal array lengths: " + cs1.length + " "
212 + cs2.length);
213 for (int i = 0; i < cs1.length; i++) {
214 if (!cs1[i].isAssignableFrom(cs2[i])) return false;
215 }
216 return true;
217 }
218
219 private static Method[] filter(final Method[] methods, final MethodFilter filter) {
220 ArrayList<Method> list = new ArrayList<Method>(methods.length);
221 for (Method method : methods) {
222 if (filter.accept(method)) list.add(method);
223 }
224 return list.toArray(new Method[list.size()]);
225 }
226
227 /**
228 * Filters methods.
229 */
230
231 private interface MethodFilter {
232
233 boolean accept(Method method);
234
235 }
236
237 private Reflect() {
238 }
239
240 }