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