1 package org.apache.velocity.util.introspection; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.exception.VelocityException; 23 import org.apache.velocity.runtime.RuntimeConstants; 24 import org.apache.velocity.runtime.RuntimeServices; 25 import org.apache.velocity.runtime.parser.node.AbstractExecutor; 26 import org.apache.velocity.runtime.parser.node.BooleanPropertyExecutor; 27 import org.apache.velocity.runtime.parser.node.GetExecutor; 28 import org.apache.velocity.runtime.parser.node.MapGetExecutor; 29 import org.apache.velocity.runtime.parser.node.MapSetExecutor; 30 import org.apache.velocity.runtime.parser.node.PropertyExecutor; 31 import org.apache.velocity.runtime.parser.node.PutExecutor; 32 import org.apache.velocity.runtime.parser.node.SetExecutor; 33 import org.apache.velocity.runtime.parser.node.SetPropertyExecutor; 34 import org.apache.velocity.util.ArrayIterator; 35 import org.apache.velocity.util.ArrayListWrapper; 36 import org.apache.velocity.util.ClassUtils; 37 import org.apache.velocity.util.EnumerationIterator; 38 import org.apache.velocity.util.RuntimeServicesAware; 39 40 import org.slf4j.Logger; 41 42 import java.lang.reflect.Array; 43 import java.lang.reflect.InvocationTargetException; 44 import java.lang.reflect.Method; 45 import java.lang.reflect.Type; 46 import java.util.Enumeration; 47 import java.util.Iterator; 48 import java.util.Map; 49 50 /** 51 * Implementation of Uberspect to provide the default introspective 52 * functionality of Velocity 53 * 54 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 55 * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> 56 * @version $Id$ 57 */ 58 public class UberspectImpl implements Uberspect, RuntimeServicesAware 59 { 60 /** 61 * Our runtime logger. 62 */ 63 protected Logger log; 64 65 /** 66 * the default Velocity introspector 67 */ 68 protected Introspector introspector; 69 70 /** 71 * the conversion handler 72 */ 73 protected TypeConversionHandler conversionHandler; 74 75 /** 76 * runtime services 77 */ 78 protected RuntimeServices rsvc; 79 80 /** 81 * init - generates the Introspector. As the setup code 82 * makes sure that the log gets set before this is called, 83 * we can initialize the Introspector using the log object. 84 */ 85 @Override init()86 public void init() 87 { 88 introspector = new Introspector(log, conversionHandler); 89 } 90 getConversionHandler()91 public TypeConversionHandler getConversionHandler() 92 { 93 return conversionHandler; 94 } 95 96 /** 97 * sets the runtime services 98 * @param rs runtime services 99 */ 100 @Override 101 @SuppressWarnings("deprecation") setRuntimeServices(RuntimeServices rs)102 public void setRuntimeServices(RuntimeServices rs) 103 { 104 rsvc = rs; 105 log = rsvc.getLog("introspection"); 106 107 Object conversionHandlerInstance = rs.getProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE); 108 if (conversionHandlerInstance == null) 109 { 110 String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS); 111 if (conversionHandlerClass != null && !conversionHandlerClass.equals("none")) 112 { 113 try 114 { 115 conversionHandlerInstance = ClassUtils.getNewInstance(conversionHandlerClass); 116 } 117 catch (ClassNotFoundException cnfe ) 118 { 119 String err = "The specified class for ConversionHandler (" + conversionHandlerClass 120 + ") does not exist or is not accessible to the current classloader."; 121 log.error(err); 122 throw new VelocityException(err, cnfe); 123 } 124 catch (InstantiationException ie) 125 { 126 throw new VelocityException("Could not instantiate class '" + conversionHandlerClass + "'", ie); 127 } 128 catch (IllegalAccessException ae) 129 { 130 throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae); 131 } 132 } 133 } 134 135 if (conversionHandlerInstance != null) 136 { 137 if (conversionHandlerInstance instanceof ConversionHandler) 138 { 139 log.warn("The ConversionHandler interface is deprecated - see the TypeConversionHandler interface"); 140 final ConversionHandler ch = (ConversionHandler)conversionHandlerInstance; 141 conversionHandler = new TypeConversionHandler() 142 { 143 @Override 144 public boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg) 145 { 146 Class<?> formalClass = IntrospectionUtils.getTypeClass(formal); 147 if (formalClass != null) return ch.isExplicitlyConvertible(formalClass, actual, possibleVarArg); 148 else return false; 149 } 150 151 @Override 152 public Converter<?> getNeededConverter(Type formal, Class<?> actual) 153 { 154 Class<?> formalClass = IntrospectionUtils.getTypeClass(formal); 155 if (formalClass != null) return ch.getNeededConverter(formalClass, actual); 156 else return null; 157 } 158 159 @Override 160 public void addConverter(Type formal, Class<?> actual, Converter<?> converter) 161 { 162 Class<?> formalClass = IntrospectionUtils.getTypeClass(formal); 163 if (formalClass != null) ch.addConverter(formalClass, actual, converter); 164 else throw new UnsupportedOperationException("This conversion handler doesn't know how to handle Type: " + formal.getTypeName()); 165 } 166 }; 167 } 168 else if (!(conversionHandlerInstance instanceof TypeConversionHandler)) 169 { 170 String err = "The specified class or provided instance for the conversion handler (" + conversionHandlerInstance.getClass().getName() 171 + ") does not implement " + TypeConversionHandler.class.getName() 172 + "; Velocity is not initialized correctly."; 173 174 log.error(err); 175 throw new VelocityException(err, null, rsvc.getLogContext().getStackTrace()); 176 } 177 else 178 { 179 conversionHandler = (TypeConversionHandler)conversionHandlerInstance; 180 } 181 } 182 } 183 184 /** 185 * Sets the runtime logger - this must be called before anything 186 * else. 187 * 188 * @param log The logger instance to use. 189 * @since 1.5 190 * @deprecated logger is now set by default to the namespace logger "velocity.rendering". 191 */ setLog(Logger log)192 public void setLog(Logger log) 193 { 194 this.log = log; 195 } 196 197 /** 198 * To support iterative objects used in a <code>#foreach()</code> 199 * loop. 200 * 201 * @param obj The iterative object. 202 * @param i Info about the object's location. 203 * @return An {@link Iterator} object. 204 */ 205 @Override getIterator(Object obj, Info i)206 public Iterator getIterator(Object obj, Info i) 207 { 208 if (obj.getClass().isArray()) 209 { 210 return new ArrayIterator(obj); 211 } 212 else if (obj instanceof Iterable) 213 { 214 return ((Iterable) obj).iterator(); 215 } 216 else if (obj instanceof Map) 217 { 218 return ((Map) obj).values().iterator(); 219 } 220 else if (obj instanceof Iterator) 221 { 222 log.debug("The iterative object in the #foreach() loop at {}" + 223 " is of type java.util.Iterator. Because " + 224 "it is not resettable, if used in more than once it " + 225 "may lead to unexpected results.", i); 226 return ((Iterator) obj); 227 } 228 else if (obj instanceof Enumeration) 229 { 230 log.debug("The iterative object in the #foreach() loop at {}" + 231 " is of type java.util.Enumeration. Because " + 232 "it is not resettable, if used in more than once it " + 233 "may lead to unexpected results.", i); 234 return new EnumerationIterator((Enumeration) obj); 235 } 236 else 237 { 238 // look for an iterator() method to support the JDK5 Iterable 239 // interface or any user tools/DTOs that want to work in 240 // foreach without implementing the Collection interface 241 Class<?> type = obj.getClass(); 242 try 243 { 244 Method iter = type.getMethod("iterator"); 245 Class<?> returns = iter.getReturnType(); 246 if (Iterator.class.isAssignableFrom(returns)) 247 { 248 try 249 { 250 return (Iterator)iter.invoke(obj); 251 } 252 catch (IllegalAccessException e) 253 { 254 // Cannot invoke this method, just give up 255 } 256 catch (Exception e) 257 { 258 throw new VelocityException("Error invoking the method 'iterator' on class '" 259 + obj.getClass().getName() +"'", e, rsvc.getLogContext().getStackTrace()); 260 } 261 } 262 else 263 { 264 log.debug("iterator() method of reference in #foreach loop at " + 265 "{} does not return a true Iterator.", i); 266 } 267 } 268 catch (NoSuchMethodException nsme) 269 { 270 // eat this one, but let all other exceptions thru 271 } 272 } 273 274 /* we have no clue what this is */ 275 log.debug("Could not determine type of iterator in #foreach loop at {}", i); 276 277 return null; 278 } 279 280 /** 281 * Method 282 * @param obj 283 * @param methodName 284 * @param args 285 * @param i 286 * @return A Velocity Method. 287 */ 288 @Override getMethod(Object obj, String methodName, Object[] args, Info i)289 public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) 290 { 291 if (obj == null) 292 { 293 return null; 294 } 295 296 Method m = introspector.getMethod(obj.getClass(), methodName, args); 297 if (m != null) 298 { 299 return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args)); 300 } 301 302 Class<?> cls = obj.getClass(); 303 // if it's an array 304 if (cls.isArray()) 305 { 306 // check for support via our array->list wrapper 307 m = introspector.getMethod(ArrayListWrapper.class, methodName, args); 308 if (m != null) 309 { 310 // and create a method that knows to wrap the value 311 // before invoking the method 312 return new VelMethodImpl(m, true, getNeededConverters(m.getGenericParameterTypes(), args)); 313 } 314 } 315 // watch for classes, to allow calling their static methods (VELOCITY-102) 316 else if (cls == Class.class) 317 { 318 m = introspector.getMethod((Class<?>)obj, methodName, args); 319 if (m != null) 320 { 321 return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args)); 322 } 323 } 324 return null; 325 } 326 327 /** 328 * get the list of needed converters to adapt passed argument types to method types 329 * @return null if not conversion needed, otherwise an array containing needed converters 330 */ getNeededConverters(Type[] expected, Object[] provided)331 private Converter<?>[] getNeededConverters(Type[] expected, Object[] provided) 332 { 333 if (conversionHandler == null) return null; 334 // var args are not handled here - CB TODO 335 int n = Math.min(expected.length, provided.length); 336 Converter<?>[] converters = null; 337 for (int i = 0; i < n; ++i) 338 { 339 Object arg = provided[i]; 340 if (arg == null) continue; 341 Converter<?> converter = conversionHandler.getNeededConverter(expected[i], arg.getClass()); 342 if (converter != null) 343 { 344 if (converters == null) 345 { 346 converters = new Converter[expected.length]; 347 } 348 converters[i] = converter; 349 } 350 } 351 return converters; 352 } 353 354 /** 355 * Property getter 356 * @param obj 357 * @param identifier 358 * @param i 359 * @return A Velocity Getter Method. 360 */ 361 @Override getPropertyGet(Object obj, String identifier, Info i)362 public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) 363 { 364 if (obj == null) 365 { 366 return null; 367 } 368 369 Class<?> claz = obj.getClass(); 370 371 /* 372 * first try for a getFoo() type of property 373 * (also getfoo() ) 374 */ 375 AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier); 376 377 /* 378 * Let's see if we are a map... 379 */ 380 if (!executor.isAlive()) 381 { 382 executor = new MapGetExecutor(log, obj, identifier); 383 } 384 385 /* 386 * if that didn't work, look for get("foo") 387 */ 388 389 if (!executor.isAlive()) 390 { 391 executor = new GetExecutor(log, introspector, claz, identifier); 392 } 393 394 /* 395 * finally, look for boolean isFoo() 396 */ 397 398 if (!executor.isAlive()) 399 { 400 executor = new BooleanPropertyExecutor(log, introspector, claz, 401 identifier); 402 } 403 404 /* 405 * and idem on an array 406 */ 407 if (!executor.isAlive() && obj.getClass().isArray()) 408 { 409 executor = new BooleanPropertyExecutor(log, introspector, ArrayListWrapper.class, 410 identifier, true); 411 } 412 413 return (executor.isAlive()) ? new VelGetterImpl(executor) : null; 414 } 415 416 /** 417 * Property setter 418 * @param obj 419 * @param identifier 420 * @param arg 421 * @param i 422 * @return A Velocity Setter method. 423 */ 424 @Override getPropertySet(Object obj, String identifier, Object arg, Info i)425 public VelPropertySet getPropertySet(Object obj, String identifier, 426 Object arg, Info i) 427 { 428 if (obj == null) 429 { 430 return null; 431 } 432 433 Class<?> claz = obj.getClass(); 434 435 /* 436 * first try for a setFoo() type of property 437 * (also setfoo() ) 438 */ 439 SetExecutor executor = new SetPropertyExecutor(log, introspector, claz, identifier, arg); 440 441 /* 442 * Let's see if we are a map... 443 */ 444 if (!executor.isAlive()) { 445 executor = new MapSetExecutor(log, claz, identifier); 446 } 447 448 /* 449 * if that didn't work, look for put("foo", arg) 450 */ 451 452 if (!executor.isAlive()) 453 { 454 executor = new PutExecutor(log, introspector, claz, arg, identifier); 455 } 456 457 return (executor.isAlive()) ? new VelSetterImpl(executor) : null; 458 } 459 460 /** 461 * Implementation of VelMethod 462 */ 463 public static class VelMethodImpl implements VelMethod 464 { 465 final Method method; 466 Boolean isVarArg; 467 boolean wrapArray; 468 Converter<?> converters[]; 469 470 /** 471 * @param m 472 */ VelMethodImpl(Method m)473 public VelMethodImpl(Method m) 474 { 475 this(m, false, null); 476 } 477 478 /** 479 * @param method 480 * @param wrapArray 481 * @since 1.6 482 */ VelMethodImpl(Method method, boolean wrapArray)483 public VelMethodImpl(Method method, boolean wrapArray) 484 { 485 this(method, wrapArray, null); 486 } 487 488 /** 489 * @param method 490 * @param wrapArray 491 * @param converters 492 * @since 2.0 493 */ VelMethodImpl(Method method, boolean wrapArray, Converter<?>[] converters)494 public VelMethodImpl(Method method, boolean wrapArray, Converter<?>[] converters) 495 { 496 this.method = method; 497 this.wrapArray = wrapArray; 498 this.converters = converters; 499 } 500 VelMethodImpl()501 private VelMethodImpl() 502 { 503 method = null; 504 } 505 506 /** 507 * @param o 508 * @param actual 509 * @return invocation result 510 * @see VelMethod#invoke(java.lang.Object, java.lang.Object[]) 511 */ 512 @Override invoke(Object o, Object[] actual)513 public Object invoke(Object o, Object[] actual) 514 throws IllegalAccessException, InvocationTargetException 515 { 516 // if we're pretending an array is a list... 517 if (wrapArray) 518 { 519 o = new ArrayListWrapper(o); 520 } 521 522 if (isVarArg()) 523 { 524 Class<?>[] formal = method.getParameterTypes(); 525 int index = formal.length - 1; 526 if (actual.length >= index) 527 { 528 Class<?> type = formal[index].getComponentType(); 529 actual = handleVarArg(type, index, actual); 530 } 531 } 532 533 if (converters != null) 534 { 535 // some converters may throw an ArithmeticException 536 // which we want to wrap into an IllegalArgumentException 537 try 538 { 539 for (int i = 0; i < actual.length; ++i) 540 { 541 if (converters[i] != null) 542 { 543 actual[i] = converters[i].convert(actual[i]); 544 } 545 } 546 } 547 catch (ArithmeticException ae) 548 { 549 throw new IllegalArgumentException(ae); 550 } 551 } 552 553 // call extension point invocation 554 return doInvoke(o, actual); 555 } 556 557 /** 558 * Offers an extension point for subclasses (in alternate Uberspects) 559 * to alter the invocation after any array wrapping or varargs handling 560 * has already been completed. 561 * @param o target object 562 * @param actual arguments 563 * @return invocation result 564 * @throws IllegalAccessException 565 * @throws InvocationTargetException 566 * @since 1.6 567 */ doInvoke(Object o, Object[] actual)568 protected Object doInvoke(Object o, Object[] actual) 569 throws IllegalAccessException, InvocationTargetException 570 { 571 return method.invoke(o, actual); 572 } 573 574 /** 575 * @return true if this method can accept a variable number of arguments 576 * @since 1.6 577 */ isVarArg()578 public boolean isVarArg() 579 { 580 if (isVarArg == null) 581 { 582 Class<?>[] formal = method.getParameterTypes(); 583 if (formal.length == 0) 584 { 585 this.isVarArg = Boolean.FALSE; 586 } 587 else 588 { 589 Class<?> last = formal[formal.length - 1]; 590 // if the last arg is an array, then 591 // we consider this a varargs method 592 this.isVarArg = last.isArray(); 593 } 594 } 595 return isVarArg; 596 } 597 598 /** 599 * @param type The vararg class type (aka component type 600 * of the expected array arg) 601 * @param index The index of the vararg in the method declaration 602 * (This will always be one less than the number of 603 * expected arguments.) 604 * @param actual The actual parameters being passed to this method 605 * @return The actual parameters adjusted for the varargs in order 606 * to fit the method declaration. 607 */ handleVarArg(final Class<?> type, final int index, Object[] actual)608 private Object[] handleVarArg(final Class<?> type, 609 final int index, 610 Object[] actual) 611 { 612 // if no values are being passed into the vararg 613 if (actual.length == index) 614 { 615 // copy existing args to new array 616 Object[] newActual = new Object[actual.length + 1]; 617 System.arraycopy(actual, 0, newActual, 0, actual.length); 618 // create an empty array of the expected type 619 newActual[index] = Array.newInstance(type, 0); 620 actual = newActual; 621 } 622 // if one value is being passed into the vararg 623 else if (actual.length == index + 1 && actual[index] != null) 624 { 625 // make sure the last arg is an array of the expected type 626 Class<?> argClass = actual[index].getClass(); 627 if (!argClass.isArray() && IntrospectionUtils.isMethodInvocationConvertible(type, argClass, false)) 628 { 629 // create a 1-length array to hold and replace the last param 630 Object lastActual = Array.newInstance(type, 1); 631 Array.set(lastActual, 0, actual[index]); 632 actual[index] = lastActual; 633 } 634 } 635 // if multiple values are being passed into the vararg 636 else if (actual.length > index + 1) 637 { 638 // put the last and extra actual in an array of the expected type 639 int size = actual.length - index; 640 Object lastActual = Array.newInstance(type, size); 641 for (int i = 0; i < size; i++) 642 { 643 Array.set(lastActual, i, actual[index + i]); 644 } 645 646 // put all into a new actual array of the appropriate size 647 Object[] newActual = new Object[index + 1]; 648 System.arraycopy(actual, 0, newActual, 0, index); 649 newActual[index] = lastActual; 650 651 // replace the old actual array 652 actual = newActual; 653 } 654 return actual; 655 } 656 657 /** 658 * @see org.apache.velocity.util.introspection.VelMethod#isCacheable() 659 */ 660 @Override isCacheable()661 public boolean isCacheable() 662 { 663 return true; 664 } 665 666 /** 667 * @see org.apache.velocity.util.introspection.VelMethod#getMethodName() 668 */ 669 @Override getMethodName()670 public String getMethodName() 671 { 672 return method.getName(); 673 } 674 675 /** 676 * @see org.apache.velocity.util.introspection.VelMethod#getMethod() 677 */ 678 @Override getMethod()679 public Method getMethod() 680 { 681 return method; 682 } 683 684 /** 685 * @see org.apache.velocity.util.introspection.VelMethod#getReturnType() 686 */ 687 @Override getReturnType()688 public Class<?> getReturnType() 689 { 690 return method.getReturnType(); 691 } 692 } 693 694 /** 695 * 696 * 697 */ 698 public static class VelGetterImpl implements VelPropertyGet 699 { 700 final AbstractExecutor getExecutor; 701 702 /** 703 * @param exec 704 */ VelGetterImpl(AbstractExecutor exec)705 public VelGetterImpl(AbstractExecutor exec) 706 { 707 getExecutor = exec; 708 } 709 VelGetterImpl()710 private VelGetterImpl() 711 { 712 getExecutor = null; 713 } 714 715 /** 716 * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object) 717 */ 718 @Override invoke(Object o)719 public Object invoke(Object o) 720 throws IllegalAccessException, InvocationTargetException 721 { 722 return getExecutor.execute(o); 723 } 724 725 /** 726 * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable() 727 */ 728 @Override isCacheable()729 public boolean isCacheable() 730 { 731 return true; 732 } 733 734 /** 735 * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName() 736 */ 737 @Override getMethodName()738 public String getMethodName() 739 { 740 return getExecutor.isAlive() ? getExecutor.getMethod().getName() : null; 741 } 742 } 743 744 /** 745 * 746 */ 747 public static class VelSetterImpl implements VelPropertySet 748 { 749 private final SetExecutor setExecutor; 750 751 /** 752 * @param setExecutor 753 */ VelSetterImpl(final SetExecutor setExecutor)754 public VelSetterImpl(final SetExecutor setExecutor) 755 { 756 this.setExecutor = setExecutor; 757 } 758 VelSetterImpl()759 private VelSetterImpl() 760 { 761 setExecutor = null; 762 } 763 764 /** 765 * Invoke the found Set Executor. 766 * 767 * @param o is the Object to invoke it on. 768 * @param value in the Value to set. 769 * @return The resulting Object. 770 */ 771 @Override invoke(final Object o, final Object value)772 public Object invoke(final Object o, final Object value) 773 throws IllegalAccessException, InvocationTargetException 774 { 775 return setExecutor.execute(o, value); 776 } 777 778 /** 779 * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable() 780 */ 781 @Override isCacheable()782 public boolean isCacheable() 783 { 784 return true; 785 } 786 787 /** 788 * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName() 789 */ 790 @Override getMethodName()791 public String getMethodName() 792 { 793 return setExecutor.isAlive() ? setExecutor.getMethod().getName() : null; 794 } 795 } 796 } 797