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 }