View Javadoc

1   /*
2    * Copyright (C) 2006  Tom Gibara
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this library; if not, write to the Free Software
16   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
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  // TODO test all methods
31  public final class Reflect {
32  
33      private static final MethodFilter SET_FILTER = new MethodFilter() {
34          public boolean accept(final Method method) {
35              // check name
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              // check types
41              if (method.getReturnType() != Void.TYPE) return false;
42              if (method.getParameterTypes().length != 1) return false;
43              // all okay
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                  // check name
54                  if (name.length() < 4) return false;
55                  if (Character.isLowerCase(name.charAt(3))) return false;
56                  // check types
57                  if (method.getReturnType() == Void.TYPE) return false;
58                  if (method.getParameterTypes().length != 0) return false;
59                  // all okay
60                  return true;
61              } else if (name.startsWith("is")) {
62                  // check name
63                  if (name.length() < 3) return false;
64                  if (Character.isLowerCase(name.charAt(2))) return false;
65                  // check types
66                  if (method.getReturnType() != Boolean.TYPE) return false;
67                  if (method.getParameterTypes().length != 0) return false;
68                  // all okay
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); // assume
88                                                                      // typically
89                                                                      // at most
90                                                                      // four
91                                                                      // hyphens
92          boolean wasUpper = true; // to prevent an initial hyphen
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                         /* we don't want to */
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 }