001    package fj.test.reflect;
002    
003    import static fj.Bottom.error;
004    import fj.Class;
005    import static fj.Class.clas;
006    import fj.F;
007    import fj.Function;
008    import fj.P;
009    import static fj.P.p;
010    import fj.P2;
011    import fj.P3;
012    import fj.data.Array;
013    import static fj.data.Array.array;
014    import fj.data.List;
015    import static fj.data.List.join;
016    import static fj.data.List.list;
017    import fj.data.Option;
018    import static fj.data.Option.fromNull;
019    import static fj.data.Option.none;
020    import static fj.data.Option.some;
021    import static fj.data.Option.somes;
022    import fj.test.CheckResult;
023    import fj.test.Property;
024    import fj.test.Rand;
025    
026    import java.lang.reflect.AnnotatedElement;
027    import java.lang.reflect.Constructor;
028    import java.lang.reflect.Field;
029    import java.lang.reflect.Method;
030    import static java.lang.reflect.Modifier.isStatic;
031    
032    /**
033     * Functions for checking properties in a class that are found reflectively and according to various
034     * annotations.
035     * 
036     * @version %build.number%<br>
037     *          <ul>
038     *          <li>$LastChangedRevision: 163 $</li>
039     *          <li>$LastChangedDate: 2009-06-02 03:43:12 +1000 (Tue, 02 Jun 2009) $</li>
040     *          <li>$LastChangedBy: runarorama $</li>
041     *          </ul>
042     */
043    public final class Check {
044      private Check() {
045        throw new UnsupportedOperationException();
046      }
047    
048      /**
049       * Returns the results and names of checking the properties on the given classes using a
050       * {@link Rand#standard standard random generator}.
051       *
052       * @param c The classes to check the properties of.
053       * @param categories The categories of properties to return. If no categories are specified, all
054       * candidate properties are returned, otherwise, only those properties in the given categories are
055       * returned (properties in no category are omitted in this latter case).
056       * @return The results and names of checking the properties on the given classes using a
057       * {@link Rand#standard standard random generator}.
058       */
059      public static <T> List<P2<String, CheckResult>> check(final List<java.lang.Class<T>> c, final String... categories) {
060        return check(c, Rand.standard, categories);
061      }
062    
063      /**
064       * Returns the results and names of checking the properties on the given classes using a
065       * {@link Rand#standard standard random generator}.
066       *
067       * @param c The classes to check the properties of.
068       * @param categories The categories of properties to return. If no categories are specified, all
069       * candidate properties are returned, otherwise, only those properties in the given categories are
070       * returned (properties in no category are omitted in this latter case).
071       * @return The results and names of checking the properties on the given classes using a
072       * {@link Rand#standard standard random generator}.
073       */
074      public static <T> List<P2<String, CheckResult>> check(final List<java.lang.Class<T>> c, final List<String> categories) {
075        return check(c, Rand.standard, categories.toArray().array(String[].class));
076      }
077    
078      /**
079       * Returns the results and names of checking the properties on the given classes.
080       *
081       * @param c The classes to check the properties of.
082       * @param r The random generator to use to check the properties on the given classes.
083       * @param categories The categories of properties to return. If no categories are specified, all
084       * candidate properties are returned, otherwise, only those properties in the given categories are
085       * returned (properties in no category are omitted in this latter case).
086       * @return The results and names of checking the properties on the given classes.
087       */
088      public static <T> List<P2<String, CheckResult>> check(final List<java.lang.Class<T>> c, final Rand r, final String... categories) {
089        return join(c.map(new F<java.lang.Class<T>, List<P2<String, CheckResult>>>() {
090          public List<P2<String, CheckResult>> f(final java.lang.Class<T> c) {
091            return check(c, r, categories);
092          }
093        }));
094      }
095    
096      /**
097       * Returns the results and names of checking the properties on the given classes.
098       *
099       * @param c The classes to check the properties of.
100       * @param r The random generator to use to check the properties on the given classes.
101       * @param categories The categories of properties to return. If no categories are specified, all
102       * candidate properties are returned, otherwise, only those properties in the given categories are
103       * returned (properties in no category are omitted in this latter case).
104       * @return The results and names of checking the properties on the given classes.
105       */
106      public static <T> List<P2<String, CheckResult>> check(final List<java.lang.Class<T>> c, final Rand r, final List<String> categories) {
107        return check(c, r, categories.toArray().array(String[].class));
108      }
109    
110      /**
111       * Returns the results and names of checking the properties on the given class using a
112       * {@link Rand#standard standard random generator}.
113       *
114       * @param c The class to check the properties of.
115       * @param categories The categories of properties to return. If no categories are specified, all
116       * candidate properties are returned, otherwise, only those properties in the given categories are
117       * returned (properties in no category are omitted in this latter case).
118       * @return The results and names of checking the properties on the given class using a
119       * {@link Rand#standard standard random generator}.
120       */
121      public static <T> List<P2<String, CheckResult>> check(final java.lang.Class<T> c, final String... categories) {
122        return check(c, Rand.standard, categories);
123      }
124    
125      /**
126       * Returns the results and names of checking the properties on the given class using a
127       * {@link Rand#standard standard random generator}.
128       *
129       * @param c The class to check the properties of.
130       * @param categories The categories of properties to return. If no categories are specified, all
131       * candidate properties are returned, otherwise, only those properties in the given categories are
132       * returned (properties in no category are omitted in this latter case).
133       * @return The results and names of checking the properties on the given class using a
134       * {@link Rand#standard standard random generator}.
135       */
136      public static <T> List<P2<String, CheckResult>> check(final java.lang.Class<T> c, final List<String> categories) {
137        return check(c, Rand.standard, categories.toArray().array(String[].class));
138      }
139    
140      /**
141       * Returns the results and names of checking the properties on the given class.
142       *
143       * @param c The class to check the properties of.
144       * @param r The random generator to use to check the properties on the given class.
145       * @param categories The categories of properties to return. If no categories are specified, all
146       * candidate properties are returned, otherwise, only those properties in the given categories are
147       * returned (properties in no category are omitted in this latter case).
148       * @return The results of checking the properties on the given class.
149       */
150      public static <T> List<P2<String, CheckResult>> check(final java.lang.Class<T> c, final Rand r, final String... categories) {
151        return join(clas(c).inheritance().map(new F<Class<? super T>, List<P3<Property, String, Option<CheckParams>>>>() {
152          public List<P3<Property, String, Option<CheckParams>>> f(final Class<? super T> c) {
153            return properties(c.clas(), categories);
154          }
155        })).map(new F<P3<Property, String, Option<CheckParams>>, P2<String, CheckResult>>() {
156          public P2<String, CheckResult> f(final P3<Property, String, Option<CheckParams>> p) {
157            if(p._3().isSome()) {
158              final CheckParams ps = p._3().some();
159              return p(p._2(), p._1().check(r, ps.minSuccessful(), ps.maxDiscarded(), ps.minSize(), ps.maxSize()));
160            } else
161              return p(p._2(), p._1().check(r));
162          }
163        });
164      }
165    
166      /**
167       * Returns the results and names of checking the properties on the given class.
168       *
169       * @param c The class to check the properties of.
170       * @param r The random generator to use to check the properties on the given class.
171       * @param categories The categories of properties to return. If no categories are specified, all
172       * candidate properties are returned, otherwise, only those properties in the given categories are
173       * returned (properties in no category are omitted in this latter case).
174       * @return The results of checking the properties on the given class.
175       */
176      public static <T> List<P2<String, CheckResult>> check(final java.lang.Class<T> c, final Rand r, final List<String> categories) {
177        return check(c, r, categories.toArray().array(String[].class));
178      }
179    
180      /**
181       * Returns all properties, their name and possible check parameters in a given class that are
182       * found reflectively and according to various annotations. For example, properties or their
183       * enclosing class that are annotated with {@link NoCheck} are not considered. The name of a
184       * property is specified by the {@link Name annotation} or if this annotation is not present, the
185       * name of the method or field that represents the property.
186       *
187       * @param c The class to look for properties on.
188       * @param categories The categories of properties to return. If no categories are specified, all
189       * candidate properties are returned, otherwise, only those properties in the given categories are
190       * returned (properties in no category are omitted in this latter case).
191       * @return All properties, their name and possible check parameters in a given class that are
192       * found reflectively and according to various annotations.
193       */
194      public static <U, T extends U> List<P3<Property, String, Option<CheckParams>>> properties(final java.lang.Class<T> c, final String... categories) {
195        //noinspection ClassEscapesDefinedScope
196        final Array<P3<Property, String, Option<CheckParams>>> propFields = properties(array(c.getDeclaredFields()).map(new F<Field, PropertyMember>() {
197          public PropertyMember f(final Field f) {
198            return new PropertyMember() {
199              public java.lang.Class<?> type() {
200                return f.getType();
201              }
202    
203              public AnnotatedElement element() {
204                return f;
205              }
206    
207              public String name() {
208                return f.getName();
209              }
210    
211              public int modifiers() {
212                return f.getModifiers();
213              }
214    
215              public <X> Property invoke(final X x) throws IllegalAccessException {
216                f.setAccessible(true);
217                return (Property)f.get(x);
218              }
219    
220              public boolean isProperty() {
221                return true;
222              }
223            };
224          }
225        }), c, categories);
226    
227        //noinspection ClassEscapesDefinedScope
228        final Array<P3<Property, String, Option<CheckParams>>> propMethods = properties(array(c.getDeclaredMethods()).map(new F<Method, PropertyMember>() {
229          public PropertyMember f(final Method m) {
230            //noinspection ProhibitedExceptionDeclared
231            return new PropertyMember() {
232              public java.lang.Class<?> type() {
233                return m.getReturnType();
234              }
235    
236              public AnnotatedElement element() {
237                return m;
238              }
239    
240              public String name() {
241                return m.getName();
242              }
243    
244              public int modifiers() {
245                return m.getModifiers();
246              }
247    
248              public <X> Property invoke(final X x) throws Exception {
249                m.setAccessible(true);
250                return (Property)m.invoke(x);
251              }
252    
253              public boolean isProperty() {
254                return m.getParameterTypes().length == 0;
255              }
256            };
257          }
258        }), c, categories);
259    
260        return propFields.append(propMethods).toList();
261      }
262    
263      private interface PropertyMember {
264        java.lang.Class<?> type();
265        AnnotatedElement element();
266        String name();
267        int modifiers();
268        @SuppressWarnings({"ProhibitedExceptionDeclared"})
269        <X> Property invoke(X x) throws Exception;
270        boolean isProperty();
271      }
272    
273      private static <T> Array<P3<Property, String, Option<CheckParams>>> properties(final Array<PropertyMember> ms, final java.lang.Class<T> declaringClass, final String... categories) {
274        final Option<T> t = emptyCtor(declaringClass).map(new F<Constructor<T>, T>() {
275          public T f(final Constructor<T> ctor) {
276            try {
277              ctor.setAccessible(true);
278              return ctor.newInstance();
279            } catch(Exception e) {
280              throw error(e.toString());
281            }
282          }
283        });
284    
285        final F<AnnotatedElement, F<String, Boolean>> p = new F<AnnotatedElement, F<String, Boolean>>() {
286          public F<String, Boolean> f(final AnnotatedElement e) {
287            return new F<String, Boolean>() {
288              public Boolean f(final String s) {
289                final F<Category, Boolean> p = new F<Category, Boolean>() {
290                  public Boolean f(final Category c) {
291                    return array(c.value()).exists(new F<String, Boolean>() {
292                      public Boolean f(final String cs) {
293                        return cs.equals(s);
294                      }
295                    });
296                  }
297                };
298    
299                @SuppressWarnings("unchecked")
300                final List<Boolean> bss = somes(list(fromNull(e.getAnnotation(Category.class)).map(p),
301                  fromNull(declaringClass.getAnnotation(Category.class)).map(p)));
302                return bss.exists(Function.<Boolean>identity());
303              }
304            };
305          }
306        };
307    
308        final F<Name, String> nameS = new F<Name, String>() {
309          public String f(final Name name) {
310            return name.value();
311          }
312        };
313    
314        return ms.filter(new F<PropertyMember, Boolean>() {
315          public Boolean f(final PropertyMember m) {
316            return m.isProperty() &&
317                m.type() == Property.class &&
318                !m.element().isAnnotationPresent(NoCheck.class) &&
319                !declaringClass.isAnnotationPresent(NoCheck.class) &&
320                (categories.length == 0 || array(categories).exists(p.f(m.element()))) &&
321                (t.isSome() || isStatic(m.modifiers()));
322          }
323        }).map(new F<PropertyMember, P3<Property, String, Option<CheckParams>>>() {
324          public P3<Property, String, Option<CheckParams>> f(final PropertyMember m) {
325            try {
326              final Option<CheckParams> params = fromNull(m.element().getAnnotation(CheckParams.class)).orElse(fromNull(declaringClass.getAnnotation(CheckParams.class)));
327              final String name = fromNull(m.element().getAnnotation(Name.class)).map(nameS).orSome(m.name());
328              return p(m.invoke(t.orSome(P.<T>p(null))), name, params);
329            } catch(Exception e) {
330              throw error(e.toString());
331            }
332          }
333        });
334      }
335    
336      private static <T> Option<Constructor<T>> emptyCtor(final java.lang.Class<T> c) {
337        Option<Constructor<T>> ctor;
338    
339        //noinspection UnusedCatchParameter
340        try {
341          ctor = some(c.getDeclaredConstructor());
342        } catch(NoSuchMethodException e) {
343          ctor = none();
344        }
345        return ctor;
346      }
347    }