1 58 package org.oddjob.arooa.reflect; 59 60 import java.io.PrintStream ; 61 import java.lang.reflect.Constructor ; 62 import java.lang.reflect.InvocationTargetException ; 63 import java.lang.reflect.Method ; 64 import java.util.ArrayList ; 65 import java.util.Enumeration ; 66 import java.util.HashMap ; 67 import java.util.Hashtable ; 68 import java.util.Iterator ; 69 import java.util.List ; 70 import java.util.Locale ; 71 import java.util.Map ; 72 73 import org.apache.log4j.Logger; 74 import org.oddjob.OddjobException; 75 import org.oddjob.arooa.ArooaConstants; 76 import org.oddjob.arooa.ArooaContext; 77 import org.oddjob.arooa.ArooaException; 78 import org.oddjob.arooa.ArooaHandler; 79 import org.oddjob.arooa.handlers.SkipLevelHandler; 80 import org.oddjob.arooa.xml.NamespaceHelper; 81 82 90 public final class IntrospectionHelper { 91 private static final Logger logger = Logger.getLogger(IntrospectionHelper.class); 92 93 97 private final Map attributeTypes = new HashMap (); 98 99 103 private Hashtable nestedTypes; 104 105 109 private Hashtable nestedCreators; 110 111 114 private List addTypeMethods; 115 116 119 private Hashtable handlerProviders; 120 121 124 private Map nestedComponents = new HashMap (); 125 126 129 private Method addText = null; 130 131 134 private Method valueFor; 135 136 139 private Class bean; 140 141 144 private static Hashtable helpers = new Hashtable (); 145 146 152 private static final Hashtable PRIMITIVE_TYPE_MAP = new Hashtable (8); 153 154 static { 156 Class [] primitives = {Boolean.TYPE, Byte.TYPE, Character.TYPE, 157 Short.TYPE, Integer.TYPE, Long.TYPE, 158 Float.TYPE, Double.TYPE}; 159 Class [] wrappers = {Boolean .class, Byte .class, Character .class, 160 Short .class, Integer .class, Long .class, 161 Float .class, Double .class}; 162 for (int i = 0; i < primitives.length; i++) { 163 PRIMITIVE_TYPE_MAP.put (primitives[i], wrappers[i]); 164 } 165 } 166 167 213 private IntrospectionHelper(final Class bean) { 214 nestedTypes = new Hashtable (); 215 nestedCreators = new Hashtable (); 216 addTypeMethods = new ArrayList (); 217 handlerProviders = new Hashtable (); 218 219 this.bean = bean; 220 221 Method [] methods = bean.getMethods(); 222 for (int i = 0; i < methods.length; i++) { 223 final Method m = methods[i]; 224 final String name = m.getName(); 225 Class returnType = m.getReturnType(); 226 Class [] args = m.getParameterTypes(); 227 228 if (args.length == 1 && java.lang.Void.TYPE.equals(returnType) 230 && (name.equals("add") || name.equals("addConfigured"))) { 231 insertAddTypeMethod(m); 232 continue; 233 } 234 235 if ("addText".equals(name) 236 && java.lang.Void.TYPE.equals(returnType) 237 && args.length == 1 238 && java.lang.String .class.equals(args[0])) { 239 240 addText = methods[i]; 241 242 } else if (name.startsWith("set") 243 && java.lang.Void.TYPE.equals(returnType) 244 && args.length == 1) { 245 processSetMethod(m); 246 } else if (name.startsWith("set") 247 && java.lang.Void.TYPE.equals(returnType) 248 && args.length == 2 && String .class.equals(args[0])) { 249 processMappedMethod(m); 250 } else if (name.startsWith("create") && !returnType.isArray() 251 && !returnType.isPrimitive() && args.length == 0) { 252 253 String propName = getPropertyName(name, "create"); 254 if (nestedCreators.get(propName) == null) { 257 nestedTypes.put(propName, returnType); 258 nestedCreators.put(propName, new NestedCreator() { 259 public Object create(Object parent, Object ignore) 260 throws InvocationTargetException , 261 IllegalAccessException { 262 return m.invoke(parent, new Object [] {}); 263 } 264 265 public void storeConfigured(Object parent, Object child) { 266 } 267 268 public String toString() { 269 return "create NestedCreator"; 270 } 271 }); 272 } 273 } else if (name.startsWith("addConfigured") 274 && java.lang.Void.TYPE.equals(returnType) 275 && args.length == 1 276 && !java.lang.String .class.equals(args[0]) 277 && !args[0].isArray() && !args[0].isPrimitive()) { 278 try { 279 Constructor constructor = args[0] 280 .getConstructor(new Class [] {}); 281 282 final Constructor c = constructor; 283 String propName = getPropertyName(name, "addConfigured"); 284 nestedTypes.put(propName, args[0]); 285 nestedCreators.put(propName, new NestedCreator() { 286 public Object create(Object parent, Object child) 287 throws InvocationTargetException , 288 IllegalAccessException , InstantiationException { 289 if (child != null) { 290 return child; 291 } else { 292 return c.newInstance(new Object [] {}); 293 } 294 } 295 296 public void storeConfigured(Object parent, Object child) 297 throws InvocationTargetException , 298 IllegalAccessException , InstantiationException { 299 300 m.invoke(parent, new Object [] { child }); 301 } 302 303 public String toString() { 304 return "addConfigured NestedCreator for " + c; 305 } 306 }); 307 } catch (NoSuchMethodException nse) { 308 logger.debug(nse.getMessage()); 309 } 310 } else if (name.startsWith("addValue") 311 && java.lang.Void.TYPE.equals(returnType) 312 && args.length == 1 313 && !args[0].isPrimitive()) { 314 String propName = getPropertyName(name, "addValue"); 315 nestedTypes.put(propName, args[0]); 316 nestedCreators.put(propName, new NestedCreator() { 317 public Object create(Object parent, Object child) 318 throws InvocationTargetException , 319 IllegalAccessException , InstantiationException { 320 throw new IllegalStateException ("Handler should create!"); 321 } 322 323 public void storeConfigured(Object parent, Object child) 324 throws InvocationTargetException , 325 IllegalAccessException , InstantiationException { 326 m.invoke(parent, new Object [] { child }); 327 } 328 329 public String toString() { 330 return "addValue NestedCreator for "; 331 } 332 }); 333 } else if (name.startsWith("addComponent") 334 && java.lang.Void.TYPE.equals(returnType) 335 && args.length == 1 336 && !java.lang.String .class.equals(args[0]) 337 && !args[0].isArray() && !args[0].isPrimitive()) { 338 processAddComponent(m); 339 } else if (name.startsWith("add") 340 && java.lang.Void.TYPE.equals(returnType) 341 && args.length == 1 342 && !java.lang.String .class.equals(args[0]) 343 && !args[0].isArray() && !args[0].isPrimitive()) { 344 try { 345 Constructor constructor = args[0] 346 .getConstructor(new Class [] {}); 347 348 final Constructor c = constructor; 349 String propName = getPropertyName(name, "add"); 350 nestedTypes.put(propName, args[0]); 351 nestedCreators.put(propName, new NestedCreator() { 352 public Object create(Object parent, Object child) 353 throws InvocationTargetException , 354 IllegalAccessException , InstantiationException { 355 if (child != null) { 356 } else if (c.getParameterTypes().length == 0) { 358 child = c.newInstance(new Object [] {}); 359 } 360 m.invoke(parent, new Object [] { child }); 361 return child; 362 } 363 364 public void storeConfigured(Object parent, Object child) 365 throws InvocationTargetException , 366 IllegalAccessException , InstantiationException { 367 } 368 369 public String toString() { 370 return "add NestedCreator for " + c; 371 } 372 }); 373 } catch (NoSuchMethodException nse) { 374 } 376 } else if (name.startsWith("handlerFor") 377 && ArooaHandler.class.isAssignableFrom(returnType) 378 && args.length == 1 379 && ArooaContext.class.equals(args[0])) { 380 processHandlerFor(m); 381 } else if (name.equals("valueFor") 383 && !java.lang.Void.TYPE.equals(returnType) 384 && args.length == 1 && Class .class.equals(args[0])) { 385 valueFor = m; 386 } 387 388 } 389 } 390 391 396 private void processSetMethod(final Method m) { 397 final String propName = getPropertyName(m.getName(), "set"); 398 if ("".equals(propName)) { 399 return; 400 } 401 attributeTypes.put(propName, m.getParameterTypes()[0]); 402 if (handlerProviders.get(propName) == null) { 403 handlerProviders.put(propName, new HandlerProvider() { 404 public String getElementName() { 405 return propName; 406 } 407 public ArooaHandler provideHandler(Object o, 408 ArooaContext context) { 409 return (ArooaHandler) context 410 .get(ArooaConstants.PROPERTY_HANDLER); 411 } 412 }); 413 } 414 } 415 416 421 private void processMappedMethod(final Method m) { 422 final String propName = getPropertyName(m.getName(), "set"); 423 if ("".equals(propName)) { 424 return; 425 } 426 attributeTypes.put(propName, m.getParameterTypes()[1]); 427 if (handlerProviders.get(propName) == null) { 428 handlerProviders.put(propName, new HandlerProvider() { 429 public String getElementName() { 430 return propName; 431 } 432 public ArooaHandler provideHandler(Object o, 433 ArooaContext context) { 434 return new SkipLevelHandler( 435 (ArooaHandler) context 436 .get(ArooaConstants.MAPPED_HANDLER)); 437 } 438 }); 439 } 440 } 441 442 448 private void processAddComponent(final Method m) { 449 final String propName = getPropertyName(m.getName(), "addComponent"); 450 nestedComponents.put(propName, m); 451 if (handlerProviders.get(propName) == null) { 452 handlerProviders.put(propName, new HandlerProvider() { 453 public String getElementName() { 454 return propName; 455 } 456 public ArooaHandler provideHandler(Object o, 457 ArooaContext context) { 458 if ("".equals(propName)) { 459 return (ArooaHandler) context 460 .get(ArooaConstants.COMPONENT_HANDLER); 461 } 462 else { 463 return new SkipLevelHandler( 464 (ArooaHandler) context 465 .get(ArooaConstants.COMPONENT_HANDLER)); 466 } 467 } 468 }); 469 } else { 470 } 472 } 473 474 479 private void processHandlerFor(final Method m) { 480 final String propName = getPropertyName(m.getName(), "handlerFor"); 481 HandlerProvider hp = new HandlerProvider() { 482 public String getElementName() { 483 return propName; 484 } 485 public ArooaHandler provideHandler(Object parent, 486 ArooaContext context) 487 throws InvocationTargetException , 488 IllegalAccessException , InstantiationException { 489 return (ArooaHandler) m.invoke(parent, 490 new Object [] { context }); 491 } 492 }; 493 handlerProviders.put(propName, hp); 494 } 495 496 506 public static synchronized IntrospectionHelper getHelper(Class c) { 507 IntrospectionHelper ih = (IntrospectionHelper) helpers.get(c); 508 if (ih == null) { 509 ih = new IntrospectionHelper(c); 510 helpers.put(c, ih); 511 } 512 return ih; 513 } 514 515 533 538 553 public void addText(Object element, String text) 554 throws ArooaException { 555 if (addText == null) { 556 if (text.trim().length() == 0) { 558 return; 560 } else { 561 String msg = element 563 + " doesn't support nested text data."; 564 throw new ArooaException(msg); 565 } 566 } 567 try { 568 addText.invoke(element, new String [] {text}); 569 } catch (IllegalAccessException ie) { 570 throw new ArooaException(ie); 572 } catch (InvocationTargetException ite) { 573 Throwable t = ite.getTargetException(); 574 if (t instanceof ArooaException) { 575 throw (ArooaException) t; 576 } 577 throw new ArooaException(t); 578 } 579 } 580 581 587 public void throwNotSupported(Object parent, String elementName) { 588 String msg = parent.getClass() 589 + " doesn't support the nested \"" + elementName + "\" element."; 590 throw new ArooaException(msg); 591 } 592 593 private NestedCreator getNestedCreator(String parentUri, Object parent, 594 String elementName) throws ArooaException { 595 596 String uri = NamespaceHelper.extractUriFromComponentName(elementName); 597 String name = NamespaceHelper.extractNameFromComponentName(elementName); 598 599 NestedCreator nc = null; 600 601 if (uri.equals(parentUri)) { nc = (NestedCreator) nestedCreators.get(name); 603 } 604 if (nc == null) { 605 throwNotSupported(parent, elementName); 606 } 607 return nc; 608 } 609 610 628 public Object createElement(String uri, Object parent, 629 String elementName) throws ArooaException { 630 NestedCreator nc = getNestedCreator(uri, parent, elementName); 631 try { 632 Object nestedElement = nc.create(parent, null); 633 return nestedElement; 634 } catch (IllegalAccessException ie) { 635 throw new ArooaException(ie); 637 } catch (InstantiationException ine) { 638 throw new ArooaException(ine); 640 } catch (InvocationTargetException ite) { 641 Throwable t = ite.getTargetException(); 642 if (t instanceof ArooaException) { 643 throw (ArooaException) t; 644 } 645 throw new ArooaException(t); 646 } 647 } 648 649 657 public boolean supportsNestedElement(String elementName) { 658 return nestedCreators.containsKey(elementName.toLowerCase(Locale.US)) 659 || addTypeMethods.size() != 0; 660 } 661 662 679 public void storeConfiguredElement(Object parent, Object child, 680 String elementName) throws ArooaException { 681 if (elementName == null) { 682 throw new ArooaException("Element name blank in storeConfigured!"); 683 } 684 NestedCreator ns = (NestedCreator) nestedCreators.get(elementName); 685 if (ns == null) { 686 throw new ArooaException("No way to store element [" + elementName 687 + "] in object [" + parent + "] (" + parent.getClass().getName() + ")"); 688 } 689 try { 690 ns.storeConfigured(parent, child); 691 } catch (IllegalAccessException ie) { 692 throw new ArooaException(ie); 694 } catch (InstantiationException ine) { 695 throw new ArooaException(ine); 697 } catch (InvocationTargetException ite) { 698 Throwable t = ite.getTargetException(); 699 if (t instanceof ArooaException) { 700 throw (ArooaException) t; 701 } 702 throw new ArooaException(t); 703 } 704 } 705 706 717 public ArooaHandler provideHandler(Object parent, String elementName, 718 ArooaContext context) 719 throws ArooaException { 720 HandlerProvider handlerProvider = (HandlerProvider) handlerProviders.get(""); 722 if (handlerProvider == null) { 723 handlerProvider = (HandlerProvider) handlerProviders.get(elementName); 724 } 725 if (handlerProvider == null) { 726 return (ArooaHandler) context.get(ArooaConstants.ELEMENT_HANDLER); 727 } 728 context.set(ArooaConstants.ELEMENT_NAME, handlerProvider.getElementName()); 729 try { 730 return (ArooaHandler) handlerProvider.provideHandler(parent, context); 731 } catch (IllegalAccessException ie) { 732 throw new ArooaException(ie); 734 } catch (InstantiationException ine) { 735 throw new ArooaException(ine); 737 } catch (InvocationTargetException ite) { 739 Throwable t = ite.getTargetException(); 740 if (t instanceof ArooaException) { 741 throw (ArooaException) t; 742 } 743 throw new ArooaException(t); 744 } 745 } 746 747 748 765 public void storeComponent(Object parent, Object child, String elementName) 766 throws ArooaException { 767 if (elementName == null) { 768 elementName = ""; 769 } 770 Method m = (Method ) nestedComponents.get(elementName); 771 if (m == null) { 772 throwNotSupported(parent, elementName); 773 } 774 try { 775 m.invoke(parent, new Object [] { child }); 776 } catch (IllegalArgumentException ae) { 777 throw new ArooaException("element \"" + elementName + "\" (" 778 + child.getClass().getName() 779 + ") must be the correct type in " 780 + parent.getClass().getName(), ae); 781 } catch (IllegalAccessException ie) { 782 throw new ArooaException(ie); 784 } catch (InvocationTargetException ite) { 785 Throwable t = ite.getTargetException(); 786 if (t instanceof ArooaException) { 787 throw (ArooaException) t; 788 } 789 throw new ArooaException(t); 790 } 791 } 792 793 807 public Class getElementType(String elementName) 808 throws ArooaException { 809 Class nt = (Class ) nestedTypes.get(elementName); 810 if (nt == null) { 811 String msg = "Class " + bean.getName() 812 + " doesn't support the nested \"" + elementName 813 + "\" element."; 814 throw new ArooaException(msg); 815 } 816 return nt; 817 } 818 819 831 public Class getAttributeType(String attributeName) 832 throws ArooaException { 833 return (Class ) attributeTypes.get(attributeName); 834 } 835 836 841 public boolean supportsCharacters() { 842 return addText != null; 843 } 844 845 852 856 863 public Enumeration getNestedElements() { 864 return nestedTypes.keys(); 865 } 866 867 868 881 public static String getPropertyName(String methodName, String prefix) { 882 int start = prefix.length(); 883 String name = methodName.substring(start); 884 if (name.length() < 1) { 885 return name; 886 } 887 String firstChar = name.substring(0, 1).toLowerCase(Locale.US); 888 return firstChar + name.substring(1); 889 } 890 891 892 895 private interface NestedCreator { 896 897 904 Object create(Object parent, Object child) 905 throws InvocationTargetException , IllegalAccessException , InstantiationException ; 906 907 912 void storeConfigured(Object parent, Object child) 913 throws InvocationTargetException , IllegalAccessException , InstantiationException ; 914 } 915 916 private interface HandlerProvider { 917 public String getElementName(); 918 public ArooaHandler provideHandler(Object parent, ArooaContext context) 919 throws InvocationTargetException , IllegalAccessException , InstantiationException ; 920 } 921 922 923 929 930 private void insertAddTypeMethod(Method method) { 931 Class argClass = method.getParameterTypes()[0]; 932 for (int c = 0; c < addTypeMethods.size(); ++c) { 933 Method current = (Method ) addTypeMethods.get(c); 934 if (current.getParameterTypes()[0].equals(argClass)) { 935 return; } 937 if (current.getParameterTypes()[0].isAssignableFrom( 938 argClass)) { 939 addTypeMethods.add(c, method); 940 return; } 942 } 943 addTypeMethods.add(method); 944 } 945 946 961 public static Object valueFor(Object from, Class required) throws ClassCastException , OddjobException { 962 if (required == null) { 963 throw new NullPointerException ("Required class must not be null."); 964 } 965 if (from == null) { 966 return null; 967 } 968 969 IntrospectionHelper ih = getHelper(from.getClass()); 970 if (ih.valueFor == null) { 972 return from; 973 } 974 975 if (PRIMITIVE_TYPE_MAP.containsKey(required)) { 977 required = (Class ) PRIMITIVE_TYPE_MAP.get(required); 978 } 979 980 try { 981 return ih.valueFor.invoke(from, new Object [] { required } ); 982 } catch (InvocationTargetException e) { 983 Throwable t = e.getCause(); 984 if (t instanceof ClassCastException ) { 985 throw (ClassCastException )t; 986 } 987 throw new ArooaException("Conversion failed converting from " 988 + from.getClass() + " to " + required, t); 989 } catch (IllegalAccessException e) { 990 throw new ArooaException("Failed to get valueFor in " + from.getClass(), 991 e); 992 } 993 } 994 995 1004 public static Object valueFor(Object from) throws ClassCastException , OddjobException { 1005 return valueFor(from, Object .class); 1006 } 1007 1008 1012 public void dump(PrintStream out) { 1013 1014 out.println("IntrospectionHelper for " + bean); 1015 out.println("Attibutes:"); 1016 for (Iterator it = attributeTypes.entrySet().iterator(); it.hasNext(); ) { 1017 Map.Entry entry = (Map.Entry ) it.next(); 1018 out.println("\t" + entry.getKey() + " (" + entry.getValue() + ")"); 1019 } 1020 Enumeration keys = nestedCreators.keys(); 1021 Enumeration elements = nestedCreators.elements(); 1022 out.println("Nested Creators:"); 1023 while (keys.hasMoreElements()) { 1024 out.println("\t" + keys.nextElement() + "=" + elements.nextElement()); 1025 } 1026 out.println("Nested Components:"); 1027 for (Iterator it = nestedComponents.entrySet().iterator(); it.hasNext(); ) { 1028 Map.Entry entry = (Map.Entry ) it.next(); 1029 out.println("\t" + entry.getKey() + " (" + entry.getValue() + ")"); 1030 } 1031 } 1032 1033 1034 public static Class selectBestMatchingClass(Class match, Class [] set) { 1035 int highestScore = 0; 1036 List bestMatches = new ArrayList (); 1037 for (int i = 0; i < set.length; ++i) { 1038 if (set[i].isAssignableFrom(match)) { 1039 Class parent = set[i]; 1040 int score = 1; 1041 while (true) { 1042 if (parent.isArray()) { 1043 parent = parent.getComponentType(); 1044 ++score; 1045 continue; 1046 } 1047 parent = parent.getSuperclass(); 1048 if (parent != null) { 1049 ++score; 1050 continue; 1051 } 1052 break; 1053 } 1054 if (score == highestScore) { 1055 bestMatches.add(set[i]); 1056 highestScore = score; 1057 } 1058 if (score > highestScore) { 1059 bestMatches.clear(); 1060 bestMatches.add(set[i]); 1061 highestScore = score; 1062 } 1063 } 1064 } 1065 if (bestMatches.size() == 0) { 1066 return null; 1067 } 1068 if (bestMatches.size() > 1) { 1069 throw new ArooaException("Class " + match 1070 + " has ambiguous matches."); 1071 } 1072 return (Class ) bestMatches.get(0); 1073 } 1074} 1075 | Popular Tags |