View Javadoc
1 /*
2 * Methods.java
3 * Created on August 1, 2003
4 *
5 * The Blues Framework - A lightweight application framework
6 * Copyright (C) 2003 Lonnie Pryor
7 * http://blues.lonniepryor.com
8 *
9 * This library is free software; you can redistribute it and/or modify it under the
10 * terms of the GNU Lesser General Public License as published by the Free Software
11 * Foundation; either version 2.1 of the License, or (at your option) any later
12 * version.
13 *
14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
16 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License along
19 * with this library; if not, write to:
20 *
21 * The Free Software Foundation, Inc.
22 * 59 Temple Place, Suite 330
23 * Boston, MA 02111-1307 USA
24 *
25 */
26 package com.lonniepryor.blues.util;
27
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 /***
38 * Specification interface for identifying Methods. Instances of this class may be
39 * logically combined using the AND, OR, and NOT operations. Many common use-cases
40 * are supplied via static accessor methods.
41 *
42 * @author Lonnie Pryor
43 * @version 1.0
44 */
45 public abstract class Methods {
46 /*** A specification satisfied by Methods decalred as public. */
47 private static final Methods declaredPublic = new Methods() {
48 protected boolean evaluate (Method method) {
49 return Modifier.isPublic(method.getModifiers());
50 }
51 };
52 /*** A specification satisfied by Methods decalred as protected. */
53 private static final Methods declaredProtected = new Methods() {
54 protected boolean evaluate (Method method) {
55 return Modifier.isProtected(method.getModifiers());
56 }
57 };
58 /*** A specification satisfied by Methods decalred as private. */
59 private static final Methods declaredPrivate = new Methods() {
60 protected boolean evaluate (Method method) {
61 return Modifier.isPrivate(method.getModifiers());
62 }
63 };
64 /*** A specification satisfied by Methods decalred as package protected/ *//package-summary/html">color="#AA0000">* A specification satisfied by Methods decalred as package protected/ *//package-summary.html">nt color="#AA0000">/*** A specification satisfied by Methods decalred as package protected/ *//package-summary.html">color="#AA0000">* A specification satisfied by Methods decalred as package protected. */
65 private static final Methods declaredPackageProtected = declaredPublic.or(
66 declaredProtected).or(declaredPrivate).not();
67 /*** A specification satisfied by Methods decalred as static. */
68 private static final Methods declaredStatic = new Methods() {
69 protected boolean evaluate (Method method) {
70 return Modifier.isStatic(method.getModifiers());
71 }
72 };
73 /*** A specification satisfied by Methods decalred as abstract. */
74 private static final Methods declaredAbstract = new Methods() {
75 protected boolean evaluate (Method method) {
76 return Modifier.isAbstract(method.getModifiers());
77 }
78 };
79 /*** A specification satisfied by Methods decalred as final. */
80 private static final Methods declaredFinal = new Methods() {
81 protected boolean evaluate (Method method) {
82 return Modifier.isFinal(method.getModifiers());
83 }
84 };
85 /*** A specification satisfied by Methods decalred as synchronized. */
86 private static final Methods declaredSynchronized = new Methods() {
87 protected boolean evaluate (Method method) {
88 return Modifier.isSynchronized(method.getModifiers());
89 }
90 };
91 /*** A specification satisfied by Methods decalred as native. */
92 private static final Methods declaredNative = new Methods() {
93 protected boolean evaluate (Method method) {
94 return Modifier.isNative(method.getModifiers());
95 }
96 };
97 /*** A specification satisfied by Methods that take no parameters. */
98 private static final Methods takingNoParameters = taking(
99 Parameters.withNoElements());
100 /*** A specification satisfied by Methods that take one parameter. */
101 private static final Methods takingOneParameter = taking(
102 Parameters.withOneElement());
103 /*** A specification satisfied by Methods that return void. */
104 private static final Methods returningVoid = returning(Types.voids());
105 /*** A specification satisfied valid inital characters in property names. */
106 private static final Characters initalPropertyChar = Characters.letters()
107 .and(
108 Characters.upperCase()).or(Characters.equalTo('_'));
109 /***
110 * A specification satisfied by Methods that are valid JavaBeans accessor
111 * methods.
112 */
113 private static final Methods propertyAccessors = declaredPublic.and(
114 declaredStatic.not()).and(takingNoParameters).and(
115 named(
116 Strings.startingWith("get").followedBy(initalPropertyChar).endingWithAny())
117 .and(returningVoid.not()).or(
118 named(
119 Strings.startingWith("is").followedBy(initalPropertyChar).endingWithAny())
120 .and(
121 returning(Types.equalTo(Boolean.class).or(Types.equalTo(Boolean.TYPE))))));
122 /***
123 * A specification satisfied by Methods that are valid JavaBeans mutator methods.
124 */
125 private static final Methods propertyMutators = declaredPublic.and(
126 declaredStatic.not()).and(returningVoid).and(takingOneParameter).and(
127 named(
128 Strings.startingWith("set").followedBy(initalPropertyChar).endingWithAny()));
129 /*** Index of the modifier-based specifications by name. */
130 private static final Map modifiersByName;
131
132 /***
133 * Initalize the modifier name mapping.
134 */
135 static {
136 Map tmp = new HashMap(9);
137 tmp.put("public", declaredPublic);
138 tmp.put("protected", declaredProtected);
139 tmp.put("package", declaredPackageProtected);
140 tmp.put("private", declaredPrivate);
141 tmp.put("static", declaredStatic);
142 tmp.put("abstract", declaredAbstract);
143 tmp.put("final", declaredFinal);
144 tmp.put("synchronized", declaredSynchronized);
145 tmp.put("native", declaredNative);
146 modifiersByName = Collections.unmodifiableMap(tmp);
147 }
148
149 /***
150 * Creates a new Methods object.
151 */
152 protected Methods () {
153 }
154
155 /***
156 * Returns a specification satisfied by Methods decalred as public.
157 *
158 * @return A specification satisfied by Methods decalred as public.
159 */
160 public static Methods declaredPublic () {
161 return declaredPublic;
162 }
163
164 /***
165 * Returns a specification satisfied by Methods decalred as protected.
166 *
167 * @return A specification satisfied by Methods decalred as protected.
168 */
169 public static Methods declaredProtected () {
170 return declaredProtected;
171 }
172
173 /***
174 * Returns a specification satisfied by Methods decalred as private.
175 *
176 * @return A specification satisfied by Methods decalred as private.
177 */
178 public static Methods declaredPrivate () {
179 return declaredPrivate;
180 }
181
182 /***
183 * Returns a specification satisfied by Methods decalred as package protected.
184 *
185 * @return A specification satisfied by Methods decalred as package protected.
186 */
187 public static Methods declaredPackageProtected () {
188 return declaredPackageProtected;
189 }
190
191 /***
192 * Returns a specification satisfied by Methods decalred as static.
193 *
194 * @return A specification satisfied by Methods decalred as static.
195 */
196 public static Methods declaredStatic () {
197 return declaredStatic;
198 }
199
200 /***
201 * Returns a specification satisfied by Methods decalred as abstract.
202 *
203 * @return A specification satisfied by Methods decalred as abstract.
204 */
205 public static Methods declaredAbstract () {
206 return declaredAbstract;
207 }
208
209 /***
210 * Returns a specification satisfied by Methods decalred as final.
211 *
212 * @return A specification satisfied by Methods decalred as final.
213 */
214 public static Methods declaredFinal () {
215 return declaredFinal;
216 }
217
218 /***
219 * Returns a specification satisfied by Methods decalred as synchronized.
220 *
221 * @return A specification satisfied by Methods decalred as synchronized.
222 */
223 public static Methods declaredSynchronized () {
224 return declaredSynchronized;
225 }
226
227 /***
228 * Returns a specification satisfied by Methods decalred as native.
229 *
230 * @return A specification satisfied by Methods decalred as native.
231 */
232 public static Methods declaredNative () {
233 return declaredNative;
234 }
235
236 /***
237 * Returns specification satisfied by Methods taking no parameters.
238 *
239 * @return A specification satisfied by Methods taking no parameters.
240 */
241 public static Methods takingNoParameters () {
242 return takingNoParameters;
243 }
244
245 /***
246 * Returns a specification satisfied by Methods taking one parameter.
247 *
248 * @return A specification satisfied by Methods taking one parameter.
249 */
250 public static Methods takingOneParameter () {
251 return takingOneParameter;
252 }
253
254 /***
255 * Returns a specification satisfied by Methods returning void.
256 *
257 * @return A specification satisfied by Methods returning void.
258 */
259 public static Methods returningVoid () {
260 return returningVoid;
261 }
262
263 /***
264 * Returns a specification satisfied by Methods that are valid JavaBeans accessor
265 * methods.
266 *
267 * @return A specification satisfied by Methods that are valid JavaBeans accessor
268 * methods.
269 */
270 public static Methods propertyAccessors () {
271 return propertyAccessors;
272 }
273
274 /***
275 * Returns a specification satisfied by Methods that are valid JavaBeans mutator
276 * methods.
277 *
278 * @return A specification satisfied by Methods that are valid JavaBeans mutator
279 * methods.
280 */
281 public static Methods propertyMutators () {
282 return propertyMutators;
283 }
284
285 /***
286 * Creates a new specification satisfied by Methods equal to the supplied Method.
287 *
288 * @param toTest The Method to be equal to.
289 *
290 * @return A new specification satisfied by Methods equal to the supplied Method.
291 */
292 public static Methods equalTo (final Method toTest) {
293 if (toTest == null)
294 throw new NullPointerException("toTest");
295 return new Methods() {
296 protected boolean evaluate (Method method) {
297 return toTest.equals(method);
298 }
299 };
300 }
301
302 /***
303 * Creates a new specification satisfied by Methods whose names match the
304 * supplied pattern.
305 *
306 * @param namePattern The pattern names must match.
307 *
308 * @return A new specification satisfied by Methods whose names match the
309 * supplied pattern.
310 */
311 public static Methods named (String namePattern) {
312 if (namePattern == null)
313 throw new NullPointerException("namePattern");
314 return named(Strings.parse(namePattern));
315 }
316
317 /***
318 * Creates a new specification satisfied by Methods whose names satisfy the
319 * supplied specification.
320 *
321 * @param specification The specification names must satisfy.
322 *
323 * @return A new specification satisfied by Methods whose names satisfy the
324 * supplied specification.
325 */
326 public static Methods named (final Strings specification) {
327 if (specification == null)
328 throw new NullPointerException("specification");
329 return new Methods() {
330 protected boolean evaluate (Method method) {
331 return specification.isSatisfiedBy(method.getName());
332 }
333 };
334 }
335
336 /***
337 * Creates a new specification satisfied only by Methods hosted on classes that
338 * satisfy the supplied specification.
339 *
340 * @param specification The specification that classes must match.
341 *
342 * @return A new specification satisfied only by Methods hosted on classes that
343 * satisfy the supplied specification.
344 */
345 public static Methods declaredOn (final Types specification) {
346 if (specification == null)
347 throw new NullPointerException("specification");
348 return new Methods() {
349 protected boolean evaluate (Method method) {
350 return specification.isSatisfiedBy(method.getDeclaringClass());
351 }
352 };
353 }
354
355 /***
356 * Creates a new specification satisfied only by Methods with parameter Lists
357 * that satisfy the supplied specification.
358 *
359 * @param specification The specification that parameter Lists must match.
360 *
361 * @return A new specification satisfied only by Methods with parameter Lists
362 * that satisfy the supplied specification.
363 */
364 public static Methods taking (final Parameters specification) {
365 if (specification == null)
366 throw new NullPointerException("specification");
367 return new Methods() {
368 protected boolean evaluate (Method method) {
369 return specification.isSatisfiedBy(method.getParameterTypes());
370 }
371 };
372 }
373
374 /***
375 * Creates a new specification satisfied only by Methods with throwable sets that
376 * satisfy the supplied specification.
377 *
378 * @param specification The specification that throwable sets must match.
379 *
380 * @return A new specification satisfied only by Methods with throwable sets that
381 * satisfy the supplied specification.
382 */
383 public static Methods throwing (final Throwables specification) {
384 if (specification == null)
385 throw new NullPointerException("specification");
386 return new Methods() {
387 protected boolean evaluate (Method method) {
388 return specification.isSatisfiedBy(method.getExceptionTypes());
389 }
390 };
391 }
392
393 /***
394 * Creates a new specification satisfied only by Methods that return Types that
395 * satisfy the supplied specification.
396 *
397 * @param specification The specification that return Types must match.
398 *
399 * @return A new specification satisfied only by Methods that return Types that
400 * satisfy the supplied specification.
401 */
402 public static Methods returning (final Types specification) {
403 if (specification == null)
404 throw new NullPointerException("specification");
405 return new Methods() {
406 protected boolean evaluate (Method method) {
407 return specification.isSatisfiedBy(method.getReturnType());
408 }
409 };
410 }
411
412 /***
413 * Parses a pattern string into a complete Methods specification. Format:
414 * <pre>
415 * methods ::= throws-clause
416 * throws-clause ::= param-list [ " throws " throwables ]
417 * param-list ::= name "(" [ parameters ] ")"
418 * name ::= [ return-type ] [ types | "(" types-expr ")" "." ] ( strings | "(" strings-expr ")" )
419 * return-type ::= [ modifiers ] ( types | "(" types-expr ")" )
420 * modifiers ::= ( [ "!" ]
421 * ( "public" | "protected" | "package" | "private"
422 * | "static" | "abstract" | "final" | "synchronized" | "native" ) )...
423 * </pre>
424 * Example:
425 * <pre>
426 * public void (com.lonniepryor..* && !javax..*+).foo(..)
427 * </pre>
428 * The above matches public void methods named foo taking any number of
429 * parameters on any class in the 'com.lonniepryor' namespace that is not
430 * assignable to a class in the 'javax' namespace.
431 *
432 * @param methodsPattern The pattern string.
433 *
434 * @return A specification matching the supplied constructor pattern.
435 */
436 public static Methods parse (String methodsPattern) {
437 if (methodsPattern == null)
438 throw new NullPointerException("methodsPattern");
439 int startIndex = Expression.rtrim(methodsPattern, 0, methodsPattern.length());
440 if (startIndex == 0)
441 throw new ParseException("Illegal empty methods pattern");
442 return readThrowsClause(methodsPattern, startIndex);
443 }
444
445 /***
446 * Reads the throws clause and passes control to readParamList().
447 *
448 * @param methodsPattern The pattern string.
449 * @param index The effective end of the pattern.
450 *
451 * @return A specification matching the supplied methods pattern.
452 */
453 private static Methods readThrowsClause (String methodsPattern, int index) {
454 int throwsAt = methodsPattern.lastIndexOf(" throws ", index);
455 int paramsEnds = Expression.rtrim(
456 methodsPattern, 0, (throwsAt < 0) ? index : throwsAt);
457 if (paramsEnds == 0)
458 throw new ParseException("Parameters not found in '" + methodsPattern + "'");
459 Methods specification = readParamList(methodsPattern, paramsEnds);
460 if (throwsAt >= 0)
461 specification = specification.and(
462 throwing(Throwables.parse(methodsPattern.substring(throwsAt + 8, index))));
463 return specification;
464 }
465
466 /***
467 * Reads the parameter list and passes control to readName().
468 *
469 * @param methodsPattern The pattern string.
470 * @param index The effective end of the pattern.
471 *
472 * @return A specification matching the supplied methods pattern.
473 */
474 private static Methods readParamList (String methodsPattern, int index) {
475 int paramsBegin = Expression.statementBeginsAt(methodsPattern, 0, index);
476 if (paramsBegin < 0)
477 throw new ParseException(
478 "Unmatched parameters parenthesis in '" + methodsPattern + "'");
479 int nameEnds = Expression.rtrim(methodsPattern, 0, paramsBegin);
480 if (nameEnds == 0)
481 throw new ParseException(
482 "Unable to find method name in '" + methodsPattern + "'");
483 paramsBegin = Expression.ltrim(methodsPattern, paramsBegin + 1, index - 1);
484 Methods specification = taking(
485 (paramsBegin == (index - 1)) ? Parameters.withNoElements()
486 : Parameters.parse(
487 methodsPattern.substring(paramsBegin, index - 1)));
488 return specification.and(readName(methodsPattern, nameEnds));
489 }
490
491 /***
492 * Reads the name and declaring type and passes control to readReturnType().
493 *
494 * @param methodsPattern The pattern string.
495 * @param index The effective end of the pattern.
496 *
497 * @return A specification matching the supplied methods pattern.
498 */
499 private static Methods readName (String methodsPattern, int index) {
500 int nameStart = Expression.statementBeginsAt(methodsPattern, 0, index, ".");
501 if (nameStart < 0)
502 throw new ParseException("Unmatched parenthesis in '" + methodsPattern + "'");
503 Methods specification = named(
504 Strings.parseExpression(methodsPattern.substring(nameStart, index)));
505 int nextIndex = Expression.rtrim(methodsPattern, 0, nameStart);
506 if ((nextIndex > 0) && (methodsPattern.charAt(nextIndex - 1) == '.')) {
507 int typeEnd = Expression.rtrim(methodsPattern, 0, nextIndex - 1);
508 if (typeEnd == 0)
509 throw new ParseException(
510 "Declaring type not found in '" + methodsPattern + "'");
511 int typeBegin = Expression.statementBeginsAt(methodsPattern, 0, typeEnd);
512 if (typeBegin < 0)
513 throw new ParseException(
514 "Unmatched parenthesis in '" + methodsPattern + "'");
515 specification = specification.and(
516 declaredOn(
517 Types.parseExpression(methodsPattern.substring(typeBegin, typeEnd))));
518 nextIndex = Expression.rtrim(methodsPattern, 0, typeBegin);
519 }
520 if (nextIndex > 0)
521 specification = specification.and(readReturnType(methodsPattern, nextIndex));
522 return specification;
523 }
524
525 /***
526 * Reads the return type and passes control to readModifiers().
527 *
528 * @param methodsPattern The pattern string.
529 * @param index The effective end of the pattern.
530 *
531 * @return A specification matching the supplied methods pattern.
532 */
533 private static Methods readReturnType (String methodsPattern, int index) {
534 int typeStart = Expression.statementBeginsAt(methodsPattern, 0, index);
535 if (typeStart < 0)
536 throw new ParseException("Unmatched parenthesis in '" + methodsPattern + "'");
537 Methods specification = returning(
538 Types.parseExpression(methodsPattern.substring(typeStart, index)));
539 int modsEnd = Expression.rtrim(methodsPattern, 0, typeStart);
540 if (modsEnd == 0)
541 return specification;
542 return specification.and(readModifiers(methodsPattern, modsEnd));
543 }
544
545 /***
546 * Recursivly reads the modifiers.
547 *
548 * @param methodsPattern The pattern string.
549 * @param index The effective end of the pattern.
550 *
551 * @return A specification matching the supplied methods pattern.
552 */
553 private static Methods readModifiers (String methodsPattern, int index) {
554 int nextIndex = index;
555 while (
556 (nextIndex > 0)
557 && !Character.isWhitespace(methodsPattern.charAt(nextIndex - 1))
558 && (methodsPattern.charAt(nextIndex - 1) != '!'))
559 --nextIndex;
560 Methods specification = (Methods)modifiersByName.get(
561 methodsPattern.substring(nextIndex, index));
562 if (specification == null)
563 throw new ParseException(
564 "Unknown modifier '" + methodsPattern.substring(nextIndex, index) + "'");
565 nextIndex = Expression.rtrim(methodsPattern, 0, nextIndex);
566 if ((nextIndex > 0) && (methodsPattern.charAt(nextIndex - 1) == '!')) {
567 specification = specification.not();
568 nextIndex = Expression.rtrim(methodsPattern, 0, nextIndex - 1);
569 }
570 if (nextIndex == 0)
571 return specification;
572 return specification.and(readModifiers(methodsPattern, nextIndex));
573 }
574
575 /***
576 * Returns true if the supplied Method is not null and satisfies this
577 * specification.
578 *
579 * @param method The Method to test.
580 *
581 * @return True if the supplied Method is not null and satisfies this
582 * specification.
583 */
584 public final boolean isSatisfiedBy (Method method) {
585 return (method != null) && evaluate(method);
586 }
587
588 /***
589 * Returns true if the supplied Method satisfies this specification.
590 *
591 * @param method The Method to test.
592 *
593 * @return True if the supplied Method satisfies this specification.
594 */
595 protected abstract boolean evaluate (Method method);
596
597 /***
598 * Returns true if all of the supplied Methods satisfy this specification.
599 *
600 * @param all The Collection of Methods to test.
601 *
602 * @return True if all of the supplied Methods satisfy this specification.
603 */
604 public final boolean isSatisfiedByAll (Method[] all) {
605 if (all == null)
606 throw new NullPointerException("all");
607 for (int i = 0; i < all.length; ++i)
608 if (!isSatisfiedBy(all[i]))
609 return false;
610 return true;
611 }
612
613 /***
614 * Returns true if any of the supplied Methods satisfy this specification.
615 *
616 * @param any The Collection of Methods to test.
617 *
618 * @return True if any of the supplied Methods satisfy this specification.
619 */
620 public final boolean isSatisfiedByAny (Method[] any) {
621 if (any == null)
622 throw new NullPointerException("any");
623 for (int i = 0; i < any.length; ++i)
624 if (isSatisfiedBy(any[i]))
625 return true;
626 return false;
627 }
628
629 /***
630 * Selects the first Method that satisfies this specification from the supplied
631 * Collection.
632 *
633 * @param from The Collection to select from.
634 *
635 * @return The first Method that satisfies this specification from the supplied
636 * Collection.
637 */
638 public final Method selectFirst (Method[] from) {
639 if (from == null)
640 throw new NullPointerException("from");
641 for (int i = 0; i < from.length; ++i)
642 if (isSatisfiedBy(from[i]))
643 return from[i];
644 return null;
645 }
646
647 /***
648 * Selects all the Methods that satisfy this specification from the supplied
649 * Collection.
650 *
651 * @param from The Collection to select from.
652 *
653 * @return All the Methods that satisfy this specification from the supplied
654 * Collection.
655 */
656 public final Method[] selectAll (Method[] from) {
657 if (from == null)
658 throw new NullPointerException("from");
659 List results = new ArrayList(from.length);
660 for (int i = 0; i < from.length; ++i)
661 if (isSatisfiedBy(from[i]))
662 results.add(from[i]);
663 return (Method[])results.toArray(new Method[results.size()]);
664 }
665
666 /***
667 * Returns a specification representing a logical AND of this specification on
668 * the left and the supplied specification on the right.
669 *
670 * @param specification The specification to AND with.
671 *
672 * @return A specification representing a logical AND of this specification on
673 * the left and the supplied specification on the right.
674 */
675 public final Methods and (final Methods specification) {
676 if (specification == null)
677 throw new NullPointerException("specification");
678 return new Methods() {
679 protected boolean evaluate (Method method) {
680 return Methods.this.evaluate(method) && specification.evaluate(method);
681 }
682 };
683 }
684
685 /***
686 * Returns a specification representing a logical OR of this specification on the
687 * left and the supplied specification on the right.
688 *
689 * @param specification The specification to OR with.
690 *
691 * @return A specification representing a logical OR of this specification on the
692 * left and the supplied specification on the right.
693 */
694 public final Methods or (final Methods specification) {
695 if (specification == null)
696 throw new NullPointerException("specification");
697 return new Methods() {
698 protected boolean evaluate (Method method) {
699 return Methods.this.evaluate(method) || specification.evaluate(method);
700 }
701 };
702 }
703
704 /***
705 * Returns a specification representing a logical NOT of this specification.
706 *
707 * @return A specification representing a logical NOT of this specification.
708 */
709 public final Methods not () {
710 return new Methods() {
711 protected boolean evaluate (Method method) {
712 return !Methods.this.evaluate(method);
713 }
714 };
715 }
716 }
This page was automatically generated by Maven