001 /* 002 $Id: Invoker.java,v 1.87 2005/11/13 16:42:13 blackdrag Exp $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package org.codehaus.groovy.runtime; 047 048 import groovy.lang.Closure; 049 import groovy.lang.GroovyObject; 050 import groovy.lang.GroovyRuntimeException; 051 import groovy.lang.MetaClass; 052 import groovy.lang.MetaClassRegistry; 053 import groovy.lang.MissingMethodException; 054 import groovy.lang.Range; 055 import groovy.lang.SpreadList; 056 import groovy.lang.Tuple; 057 import groovy.lang.GroovyInterceptable; 058 import org.apache.xml.serialize.OutputFormat; 059 import org.apache.xml.serialize.XMLSerializer; 060 import org.w3c.dom.Element; 061 import org.w3c.dom.Node; 062 import org.w3c.dom.NodeList; 063 064 import java.io.File; 065 import java.io.IOException; 066 import java.io.StringWriter; 067 import java.lang.reflect.Array; 068 import java.lang.reflect.Method; 069 import java.math.BigDecimal; 070 import java.security.AccessController; 071 import java.security.PrivilegedAction; 072 import java.util.ArrayList; 073 import java.util.Arrays; 074 import java.util.Collection; 075 import java.util.Collections; 076 import java.util.Enumeration; 077 import java.util.Iterator; 078 import java.util.List; 079 import java.util.Map; 080 import java.util.NoSuchElementException; 081 import java.util.regex.Matcher; 082 import java.util.regex.Pattern; 083 084 /** 085 * A helper class to invoke methods or extract properties on arbitrary Java objects dynamically 086 * 087 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 088 * @version $Revision: 1.87 $ 089 */ 090 public class Invoker { 091 092 protected static final Object[] EMPTY_ARGUMENTS = { 093 }; 094 protected static final Class[] EMPTY_TYPES = { 095 }; 096 097 public MetaClassRegistry getMetaRegistry() { 098 return metaRegistry; 099 } 100 101 private MetaClassRegistry metaRegistry = new MetaClassRegistry(); 102 103 public MetaClass getMetaClass(Object object) { 104 return metaRegistry.getMetaClass(object.getClass()); 105 } 106 107 /** 108 * Invokes the given method on the object. 109 * 110 * @param object 111 * @param methodName 112 * @param arguments 113 * @return 114 */ 115 public Object invokeMethod(Object object, String methodName, Object arguments) { 116 /* 117 System 118 .out 119 .println( 120 "Invoker - Invoking method on object: " 121 + object 122 + " method: " 123 + methodName 124 + " arguments: " 125 + InvokerHelper.toString(arguments)); 126 */ 127 128 if (object == null) { 129 throw new NullPointerException("Cannot invoke method " + methodName + "() on null object"); 130 } 131 132 // if the object is a Class, call a static method from that class 133 if (object instanceof Class) { 134 Class theClass = (Class) object; 135 MetaClass metaClass = metaRegistry.getMetaClass(theClass); 136 return metaClass.invokeStaticMethod(object, methodName, asArray(arguments)); 137 } 138 else // it's an instance 139 { 140 // if it's not an object implementing GroovyObject (thus not builder, nor a closure) 141 if (!(object instanceof GroovyObject)) { 142 Class theClass = object.getClass(); 143 MetaClass metaClass = metaRegistry.getMetaClass(theClass); 144 return metaClass.invokeMethod(object, methodName, asArray(arguments)); 145 } 146 // it's an object implementing GroovyObject 147 else { 148 GroovyObject groovy = (GroovyObject) object; 149 try { 150 // if it's a pure interceptable object (even intercepting toString(), clone(), ...) 151 if (groovy instanceof GroovyInterceptable) { 152 return groovy.invokeMethod(methodName, asArray(arguments)); 153 } 154 //else if there's a statically typed method or a GDK method 155 else { 156 return groovy.getMetaClass().invokeMethod(object, methodName, asArray(arguments)); 157 } 158 } catch (MissingMethodException e) { 159 if (e.getMethod().equals(methodName) && object.getClass() == e.getType()) { 160 // in case there's nothing else, invoke the object's own invokeMethod() 161 return groovy.invokeMethod(methodName, asArray(arguments)); 162 } else { 163 throw e; 164 } 165 } 166 } 167 } 168 } 169 170 public Object invokeSuperMethod(Object object, String methodName, Object arguments) { 171 if (object == null) { 172 throw new NullPointerException("Cannot invoke method " + methodName + "() on null object"); 173 } 174 175 Class theClass = object.getClass(); 176 177 MetaClass metaClass = metaRegistry.getMetaClass(theClass.getSuperclass()); 178 return metaClass.invokeMethod(object, methodName, asArray(arguments)); 179 } 180 181 public Object invokeStaticMethod(String type, String method, Object arguments) { 182 MetaClass metaClass = metaRegistry.getMetaClass(loadClass(type)); 183 List argumentList = asList(arguments); 184 return metaClass.invokeStaticMethod(null, method, asArray(arguments)); 185 } 186 187 public Object invokeConstructorAt(Class at, Class type, Object arguments) { 188 MetaClass metaClass = metaRegistry.getMetaClass(type); 189 return metaClass.invokeConstructorAt(at, asArray(arguments)); 190 } 191 192 public Object invokeConstructorAt(Class at, String type, Object arguments) { 193 return invokeConstructorAt(at, loadClass(type), arguments); 194 } 195 196 public Object invokeConstructorOf(Class type, Object arguments) { 197 MetaClass metaClass = metaRegistry.getMetaClass(type); 198 return metaClass.invokeConstructor(asArray(arguments)); 199 } 200 201 public Object invokeConstructorOf(String type, Object arguments) { 202 return invokeConstructorOf(loadClass(type), arguments); 203 } 204 205 /** 206 * Converts the given object into an array; if its an array then just 207 * cast otherwise wrap it in an array 208 */ 209 public Object[] asArray(Object arguments) { 210 if (arguments == null) { 211 return EMPTY_ARGUMENTS; 212 } 213 else if ((arguments instanceof Object[]) && ((Object[]) arguments).length == 0) { 214 return (Object[]) arguments; 215 } 216 else if (arguments instanceof Tuple) { 217 Tuple tuple = (Tuple) arguments; 218 Object[] objects = tuple.toArray(); 219 ArrayList array = new ArrayList(); 220 for (int i = 0; i < objects.length; i++) { 221 if (objects[i] instanceof SpreadList) { 222 SpreadList slist = (SpreadList) objects[i]; 223 for (int j = 0; j < slist.size(); j++) { 224 array.add(slist.get(j)); 225 } 226 } 227 else { 228 array.add(objects[i]); 229 } 230 } 231 return array.toArray(); 232 } 233 else if (arguments instanceof Object[]) { 234 Object[] objects = (Object[]) arguments; 235 ArrayList array = new ArrayList(); 236 for (int i = 0; i < objects.length; i++) { 237 if (objects[i] instanceof SpreadList) { 238 SpreadList slist = (SpreadList) objects[i]; 239 for (int j = 0; j < slist.size(); j++) { 240 array.add(slist.get(j)); 241 } 242 } 243 else { 244 array.add(objects[i]); 245 } 246 } 247 return array.toArray(); 248 } 249 else if (arguments instanceof SpreadList) { 250 ArrayList array = new ArrayList(); 251 SpreadList slist = (SpreadList) arguments; 252 for (int j = 0; j < slist.size(); j++) { 253 array.add(slist.get(j)); 254 } 255 return array.toArray(); 256 } 257 else { 258 return new Object[]{arguments}; 259 } 260 } 261 262 public List asList(Object value) { 263 if (value == null) { 264 return Collections.EMPTY_LIST; 265 } 266 else if (value instanceof List) { 267 return (List) value; 268 } 269 else if (value.getClass().isArray()) { 270 return Arrays.asList((Object[]) value); 271 } 272 else if (value instanceof Enumeration) { 273 List answer = new ArrayList(); 274 for (Enumeration e = (Enumeration) value; e.hasMoreElements();) { 275 answer.add(e.nextElement()); 276 } 277 return answer; 278 } 279 else { 280 // lets assume its a collection of 1 281 return Collections.singletonList(value); 282 } 283 } 284 285 /** 286 * Converts the value parameter into a <code>Collection</code>. 287 * 288 * @param value value to convert 289 * @return a Collection 290 */ 291 public Collection asCollection(Object value) { 292 if (value == null) { 293 return Collections.EMPTY_LIST; 294 } 295 else if (value instanceof Collection) { 296 return (Collection) value; 297 } 298 else if (value instanceof Map) { 299 Map map = (Map) value; 300 return map.entrySet(); 301 } 302 else if (value.getClass().isArray()) { 303 if (value.getClass().getComponentType().isPrimitive()) { 304 return InvokerHelper.primitiveArrayToList(value); 305 } 306 return Arrays.asList((Object[]) value); 307 } 308 else if (value instanceof MethodClosure) { 309 MethodClosure method = (MethodClosure) value; 310 IteratorClosureAdapter adapter = new IteratorClosureAdapter(method.getDelegate()); 311 method.call(adapter); 312 return adapter.asList(); 313 } 314 else if (value instanceof String) { 315 return DefaultGroovyMethods.toList((String) value); 316 } 317 else if (value instanceof File) { 318 try { 319 return DefaultGroovyMethods.readLines((File) value); 320 } 321 catch (IOException e) { 322 throw new GroovyRuntimeException("Error reading file: " + value, e); 323 } 324 } 325 else { 326 // lets assume its a collection of 1 327 return Collections.singletonList(value); 328 } 329 } 330 331 public Iterator asIterator(Object value) { 332 if (value == null) { 333 return Collections.EMPTY_LIST.iterator(); 334 } 335 if (value instanceof Iterator) { 336 return (Iterator) value; 337 } 338 if (value instanceof NodeList) { 339 final NodeList nodeList = (NodeList) value; 340 return new Iterator() { 341 private int current = 0; 342 343 public boolean hasNext() { 344 return current < nodeList.getLength(); 345 } 346 347 public Object next() { 348 Node node = nodeList.item(current++); 349 return node; 350 } 351 352 public void remove() { 353 throw new UnsupportedOperationException("Cannot remove() from an Enumeration"); 354 } 355 }; 356 } 357 else if (value instanceof Enumeration) { 358 final Enumeration enumeration = (Enumeration) value; 359 return new Iterator() { 360 private Object last; 361 362 public boolean hasNext() { 363 return enumeration.hasMoreElements(); 364 } 365 366 public Object next() { 367 last = enumeration.nextElement(); 368 return last; 369 } 370 371 public void remove() { 372 throw new UnsupportedOperationException("Cannot remove() from an Enumeration"); 373 } 374 }; 375 } 376 else if (value instanceof Matcher) { 377 final Matcher matcher = (Matcher) value; 378 return new Iterator() { 379 private boolean found = false; 380 private boolean done = false; 381 382 public boolean hasNext() { 383 if (done) { 384 return false; 385 } 386 if (!found) { 387 found = matcher.find(); 388 if (!found) { 389 done = true; 390 } 391 } 392 return found; 393 } 394 395 public Object next() { 396 if (!found) { 397 if (!hasNext()) { 398 throw new NoSuchElementException(); 399 } 400 } 401 found = false; 402 return matcher.group(); 403 } 404 405 public void remove() { 406 throw new UnsupportedOperationException(); 407 } 408 }; 409 } 410 else { 411 try { 412 // lets try see if there's an iterator() method 413 final Method method = value.getClass().getMethod("iterator", EMPTY_TYPES); 414 415 if (method != null) { 416 AccessController.doPrivileged(new PrivilegedAction() { 417 public Object run() { 418 method.setAccessible(true); 419 return null; 420 } 421 }); 422 423 return (Iterator) method.invoke(value, EMPTY_ARGUMENTS); 424 } 425 } 426 catch (Exception e) { 427 // ignore 428 } 429 } 430 return asCollection(value).iterator(); 431 } 432 433 /** 434 * @return true if the two objects are null or the objects are equal 435 */ 436 public boolean objectsEqual(Object left, Object right) { 437 if (left == right) { 438 return true; 439 } 440 if (left != null) { 441 if (right == null) { 442 return false; 443 } 444 if (left instanceof Comparable) { 445 return compareTo(left, right) == 0; 446 } 447 else if (left instanceof List && right instanceof List) { 448 return DefaultGroovyMethods.equals((List) left, (List) right); 449 } 450 else { 451 return left.equals(right); 452 } 453 } 454 return false; 455 } 456 457 public String inspect(Object self) { 458 return format(self, true); 459 } 460 461 /** 462 * Compares the two objects handling nulls gracefully and performing numeric type coercion if required 463 */ 464 public int compareTo(Object left, Object right) { 465 //System.out.println("Comparing: " + left + " to: " + right); 466 if (left == right) { 467 return 0; 468 } 469 if (left == null) { 470 return -1; 471 } 472 else if (right == null) { 473 return 1; 474 } 475 if (left instanceof Comparable) { 476 if (left instanceof Number) { 477 if (isValidCharacterString(right)) { 478 return asCharacter((Number) left).compareTo(asCharacter((String) right)); 479 } 480 return DefaultGroovyMethods.compareTo((Number) left, asNumber(right)); 481 } 482 else if (left instanceof Character) { 483 if (isValidCharacterString(right)) { 484 return ((Character) left).compareTo(asCharacter((String) right)); 485 } 486 else if (right instanceof Number) { 487 return ((Character) left).compareTo(asCharacter((Number) right)); 488 } 489 } 490 else if (right instanceof Number) { 491 if (isValidCharacterString(left)) { 492 return asCharacter((String) left).compareTo(asCharacter((Number) right)); 493 } 494 return DefaultGroovyMethods.compareTo(asNumber(left), (Number) right); 495 } 496 else if (left instanceof String && right instanceof Character) { 497 return ((String) left).compareTo(right.toString()); 498 } 499 Comparable comparable = (Comparable) left; 500 return comparable.compareTo(right); 501 } 502 503 if (left.getClass().isArray()) { 504 Collection leftList = asCollection(left); 505 if (right.getClass().isArray()) { 506 right = asCollection(right); 507 } 508 return ((Comparable) leftList).compareTo(right); 509 } 510 /** todo we might wanna do some type conversion here */ 511 throw new GroovyRuntimeException("Cannot compare values: " + left + " and " + right); 512 } 513 514 /** 515 * A helper method to provide some better toString() behaviour such as turning arrays 516 * into tuples 517 */ 518 public String toString(Object arguments) { 519 if (arguments instanceof Object[]) 520 return toArrayString((Object[]) arguments); 521 else if (arguments instanceof Map) 522 return toMapString((Map) arguments); 523 else if (arguments instanceof Collection) 524 return format(arguments, true); 525 else 526 return format(arguments, false); 527 } 528 529 /** 530 * A helper method to format the arguments types as a comma-separated list 531 */ 532 public String toTypeString(Object[] arguments) { 533 if (arguments == null) { 534 return "null"; 535 } 536 StringBuffer argBuf = new StringBuffer(); 537 for (int i = 0; i < arguments.length; i++) { 538 if (i > 0) { 539 argBuf.append(", "); 540 } 541 argBuf.append(arguments[i] != null ? arguments[i].getClass().getName() : "null"); 542 } 543 return argBuf.toString(); 544 } 545 546 /** 547 * A helper method to return the string representation of a map with bracket boundaries "[" and "]". 548 */ 549 public String toMapString(Map arg) { 550 if (arg == null) { 551 return "null"; 552 } 553 if (arg.isEmpty()) { 554 return "[:]"; 555 } 556 String sbdry = "["; 557 String ebdry = "]"; 558 StringBuffer buffer = new StringBuffer(sbdry); 559 boolean first = true; 560 for (Iterator iter = arg.entrySet().iterator(); iter.hasNext();) { 561 if (first) 562 first = false; 563 else 564 buffer.append(", "); 565 Map.Entry entry = (Map.Entry) iter.next(); 566 buffer.append(format(entry.getKey(), true)); 567 buffer.append(":"); 568 buffer.append(format(entry.getValue(), true)); 569 } 570 buffer.append(ebdry); 571 return buffer.toString(); 572 } 573 574 /** 575 * A helper method to return the string representation of a list with bracket boundaries "[" and "]". 576 */ 577 public String toListString(Collection arg) { 578 if (arg == null) { 579 return "null"; 580 } 581 if (arg.isEmpty()) { 582 return "[]"; 583 } 584 String sbdry = "["; 585 String ebdry = "]"; 586 StringBuffer buffer = new StringBuffer(sbdry); 587 boolean first = true; 588 for (Iterator iter = arg.iterator(); iter.hasNext();) { 589 if (first) 590 first = false; 591 else 592 buffer.append(", "); 593 Object elem = iter.next(); 594 buffer.append(format(elem, true)); 595 } 596 buffer.append(ebdry); 597 return buffer.toString(); 598 } 599 600 /** 601 * A helper method to return the string representation of an arrray of objects 602 * with brace boundaries "{" and "}". 603 */ 604 public String toArrayString(Object[] arguments) { 605 if (arguments == null) { 606 return "null"; 607 } 608 String sbdry = "{"; 609 String ebdry = "}"; 610 StringBuffer argBuf = new StringBuffer(sbdry); 611 for (int i = 0; i < arguments.length; i++) { 612 if (i > 0) { 613 argBuf.append(", "); 614 } 615 argBuf.append(format(arguments[i], true)); 616 } 617 argBuf.append(ebdry); 618 return argBuf.toString(); 619 } 620 621 protected String format(Object arguments, boolean verbose) { 622 if (arguments == null) { 623 return "null"; 624 } 625 else if (arguments.getClass().isArray()) { 626 return format(asCollection(arguments), verbose); 627 } 628 else if (arguments instanceof Range) { 629 Range range = (Range) arguments; 630 if (verbose) { 631 return range.inspect(); 632 } 633 else { 634 return range.toString(); 635 } 636 } 637 else if (arguments instanceof List) { 638 List list = (List) arguments; 639 StringBuffer buffer = new StringBuffer("["); 640 boolean first = true; 641 for (Iterator iter = list.iterator(); iter.hasNext();) { 642 if (first) { 643 first = false; 644 } 645 else { 646 buffer.append(", "); 647 } 648 buffer.append(format(iter.next(), verbose)); 649 } 650 buffer.append("]"); 651 return buffer.toString(); 652 } 653 else if (arguments instanceof Map) { 654 Map map = (Map) arguments; 655 if (map.isEmpty()) { 656 return "[:]"; 657 } 658 StringBuffer buffer = new StringBuffer("["); 659 boolean first = true; 660 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { 661 if (first) { 662 first = false; 663 } 664 else { 665 buffer.append(", "); 666 } 667 Map.Entry entry = (Map.Entry) iter.next(); 668 buffer.append(format(entry.getKey(), verbose)); 669 buffer.append(":"); 670 buffer.append(format(entry.getValue(), verbose)); 671 } 672 buffer.append("]"); 673 return buffer.toString(); 674 } 675 else if (arguments instanceof Element) { 676 Element node = (Element) arguments; 677 OutputFormat format = new OutputFormat(node.getOwnerDocument()); 678 format.setOmitXMLDeclaration(true); 679 format.setIndenting(true); 680 format.setLineWidth(0); 681 format.setPreserveSpace(true); 682 StringWriter sw = new StringWriter(); 683 XMLSerializer serializer = new XMLSerializer(sw, format); 684 try { 685 serializer.asDOMSerializer(); 686 serializer.serialize(node); 687 } 688 catch (IOException e) { 689 } 690 return sw.toString(); 691 } 692 else if (arguments instanceof String) { 693 if (verbose) { 694 String arg = ((String)arguments).replaceAll("\\n", "\\\\n"); // line feed 695 arg = arg.replaceAll("\\r", "\\\\r"); // carriage return 696 arg = arg.replaceAll("\\t", "\\\\t"); // tab 697 arg = arg.replaceAll("\\f", "\\\\f"); // form feed 698 arg = arg.replaceAll("\\\"", "\\\\\""); // double quotation amrk 699 arg = arg.replaceAll("\\\\", "\\\\"); // back slash 700 return "\"" + arg + "\""; 701 } 702 else { 703 return (String) arguments; 704 } 705 } 706 else { 707 return arguments.toString(); 708 } 709 } 710 711 /** 712 * Looks up the given property of the given object 713 */ 714 public Object getProperty(Object object, String property) { 715 if (object == null) { 716 throw new NullPointerException("Cannot get property: " + property + " on null object"); 717 } 718 else if (object instanceof GroovyObject) { 719 GroovyObject pogo = (GroovyObject) object; 720 return pogo.getProperty(property); 721 } 722 else if (object instanceof Map) { 723 Map map = (Map) object; 724 return map.get(property); 725 } 726 else { 727 return metaRegistry.getMetaClass(object.getClass()).getProperty(object, property); 728 } 729 } 730 731 /** 732 * Sets the property on the given object 733 */ 734 public void setProperty(Object object, String property, Object newValue) { 735 if (object == null) { 736 throw new GroovyRuntimeException("Cannot set property on null object"); 737 } 738 else if (object instanceof GroovyObject) { 739 GroovyObject pogo = (GroovyObject) object; 740 pogo.setProperty(property, newValue); 741 } 742 else if (object instanceof Map) { 743 Map map = (Map) object; 744 map.put(property, newValue); 745 } 746 else { 747 if (object instanceof Class) 748 metaRegistry.getMetaClass((Class) object).setProperty((Class) object, property, newValue); 749 else 750 metaRegistry.getMetaClass(object.getClass()).setProperty(object, property, newValue); 751 } 752 } 753 754 /** 755 * Looks up the given attribute (field) on the given object 756 */ 757 public Object getAttribute(Object object, String attribute) { 758 if (object == null) { 759 throw new NullPointerException("Cannot get attribute: " + attribute + " on null object"); 760 761 /** 762 } else if (object instanceof GroovyObject) { 763 GroovyObject pogo = (GroovyObject) object; 764 return pogo.getAttribute(attribute); 765 } else if (object instanceof Map) { 766 Map map = (Map) object; 767 return map.get(attribute); 768 */ 769 } 770 else { 771 if (object instanceof Class) { 772 return metaRegistry.getMetaClass((Class) object).getAttribute(object, attribute); 773 } else if (object instanceof GroovyObject) { 774 return ((GroovyObject)object).getMetaClass().getAttribute(object, attribute); 775 } else { 776 return metaRegistry.getMetaClass(object.getClass()).getAttribute(object, attribute); 777 } 778 } 779 } 780 781 /** 782 * Sets the given attribute (field) on the given object 783 */ 784 public void setAttribute(Object object, String attribute, Object newValue) { 785 if (object == null) { 786 throw new GroovyRuntimeException("Cannot set attribute on null object"); 787 /* 788 } else if (object instanceof GroovyObject) { 789 GroovyObject pogo = (GroovyObject) object; 790 pogo.setProperty(attribute, newValue); 791 } else if (object instanceof Map) { 792 Map map = (Map) object; 793 map.put(attribute, newValue); 794 */ 795 } 796 else { 797 if (object instanceof Class) { 798 metaRegistry.getMetaClass((Class) object).setAttribute(object, attribute, newValue); 799 } else if (object instanceof GroovyObject) { 800 ((GroovyObject)object).getMetaClass().setAttribute(object, attribute, newValue); 801 } else { 802 metaRegistry.getMetaClass(object.getClass()).setAttribute(object, attribute, newValue); 803 } 804 } 805 } 806 807 /** 808 * Returns the method pointer for the given object name 809 */ 810 public Closure getMethodPointer(Object object, String methodName) { 811 if (object == null) { 812 throw new NullPointerException("Cannot access method pointer for '" + methodName + "' on null object"); 813 } 814 return MetaClassHelper.getMethodPointer(object, methodName); 815 } 816 817 818 /** 819 * Attempts to load the given class via name using the current class loader 820 * for this code or the thread context class loader 821 */ 822 protected Class loadClass(String type) { 823 try { 824 return getClass().getClassLoader().loadClass(type); 825 } 826 catch (ClassNotFoundException e) { 827 try { 828 return Thread.currentThread().getContextClassLoader().loadClass(type); 829 } 830 catch (ClassNotFoundException e2) { 831 try { 832 return Class.forName(type); 833 } 834 catch (ClassNotFoundException e3) { 835 } 836 } 837 throw new GroovyRuntimeException("Could not load type: " + type, e); 838 } 839 } 840 841 /** 842 * Find the right hand regex within the left hand string and return a matcher. 843 * 844 * @param left string to compare 845 * @param right regular expression to compare the string to 846 * @return 847 */ 848 public Matcher objectFindRegex(Object left, Object right) { 849 String stringToCompare; 850 if (left instanceof String) { 851 stringToCompare = (String) left; 852 } 853 else { 854 stringToCompare = toString(left); 855 } 856 String regexToCompareTo; 857 if (right instanceof String) { 858 regexToCompareTo = (String) right; 859 } 860 else if (right instanceof Pattern) { 861 Pattern pattern = (Pattern) right; 862 return pattern.matcher(stringToCompare); 863 } 864 else { 865 regexToCompareTo = toString(right); 866 } 867 Matcher matcher = Pattern.compile(regexToCompareTo).matcher(stringToCompare); 868 return matcher; 869 } 870 871 /** 872 * Find the right hand regex within the left hand string and return a matcher. 873 * 874 * @param left string to compare 875 * @param right regular expression to compare the string to 876 * @return 877 */ 878 public boolean objectMatchRegex(Object left, Object right) { 879 Pattern pattern; 880 if (right instanceof Pattern) { 881 pattern = (Pattern) right; 882 } 883 else { 884 pattern = Pattern.compile(toString(right)); 885 } 886 String stringToCompare = toString(left); 887 Matcher matcher = pattern.matcher(stringToCompare); 888 RegexSupport.setLastMatcher(matcher); 889 return matcher.matches(); 890 } 891 892 /** 893 * Compile a regular expression from a string. 894 * 895 * @param regex 896 * @return 897 */ 898 public Pattern regexPattern(Object regex) { 899 return Pattern.compile(regex.toString()); 900 } 901 902 public Object asType(Object object, Class type) { 903 if (object == null) { 904 return null; 905 } 906 // TODO we should move these methods to groovy method, like g$asType() so that 907 // we can use operator overloading to customize on a per-type basis 908 if (type.isArray()) { 909 return asArray(object, type); 910 911 } 912 if (type.isInstance(object)) { 913 return object; 914 } 915 if (type.isAssignableFrom(Collection.class)) { 916 if (object.getClass().isArray()) { 917 // lets call the collections constructor 918 // passing in the list wrapper 919 Collection answer = null; 920 try { 921 answer = (Collection) type.newInstance(); 922 } 923 catch (Exception e) { 924 throw new ClassCastException("Could not instantiate instance of: " + type.getName() + ". Reason: " + e); 925 } 926 927 // we cannot just wrap in a List as we support primitive type arrays 928 int length = Array.getLength(object); 929 for (int i = 0; i < length; i++) { 930 Object element = Array.get(object, i); 931 answer.add(element); 932 } 933 return answer; 934 } 935 } 936 if (type.equals(String.class)) { 937 return object.toString(); 938 } 939 if (type.equals(Character.class)) { 940 if (object instanceof Number) { 941 return asCharacter((Number) object); 942 } 943 else { 944 String text = object.toString(); 945 if (text.length() == 1) { 946 return new Character(text.charAt(0)); 947 } 948 else { 949 throw new ClassCastException("Cannot cast: " + text + " to a Character"); 950 } 951 } 952 } 953 if (Number.class.isAssignableFrom(type)) { 954 if (object instanceof Character) { 955 return new Integer(((Character) object).charValue()); 956 } 957 else if (object instanceof String) { 958 String c = (String) object; 959 if (c.length() == 1) { 960 return new Integer(c.charAt(0)); 961 } 962 else { 963 throw new ClassCastException("Cannot cast: '" + c + "' to an Integer"); 964 } 965 } 966 } 967 if (object instanceof Number) { 968 Number n = (Number) object; 969 if (type.isPrimitive()) { 970 if (type == byte.class) { 971 return new Byte(n.byteValue()); 972 } 973 if (type == char.class) { 974 return new Character((char) n.intValue()); 975 } 976 if (type == short.class) { 977 return new Short(n.shortValue()); 978 } 979 if (type == int.class) { 980 return new Integer(n.intValue()); 981 } 982 if (type == long.class) { 983 return new Long(n.longValue()); 984 } 985 if (type == float.class) { 986 return new Float(n.floatValue()); 987 } 988 if (type == double.class) { 989 Double answer = new Double(n.doubleValue()); 990 //throw a runtime exception if conversion would be out-of-range for the type. 991 if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY 992 || answer.doubleValue() == Double.POSITIVE_INFINITY)) { 993 throw new GroovyRuntimeException("Automatic coercion of " + n.getClass().getName() 994 + " value " + n + " to double failed. Value is out of range."); 995 } 996 return answer; 997 } 998 } 999 else { 1000 if (Number.class.isAssignableFrom(type)) { 1001 if (type == Byte.class) { 1002 return new Byte(n.byteValue()); 1003 } 1004 if (type == Character.class) { 1005 return new Character((char) n.intValue()); 1006 } 1007 if (type == Short.class) { 1008 return new Short(n.shortValue()); 1009 } 1010 if (type == Integer.class) { 1011 return new Integer(n.intValue()); 1012 } 1013 if (type == Long.class) { 1014 return new Long(n.longValue()); 1015 } 1016 if (type == Float.class) { 1017 return new Float(n.floatValue()); 1018 } 1019 if (type == Double.class) { 1020 Double answer = new Double(n.doubleValue()); 1021 //throw a runtime exception if conversion would be out-of-range for the type. 1022 if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY 1023 || answer.doubleValue() == Double.POSITIVE_INFINITY)) { 1024 throw new GroovyRuntimeException("Automatic coercion of " + n.getClass().getName() 1025 + " value " + n + " to double failed. Value is out of range."); 1026 } 1027 return answer; 1028 } 1029 1030 } 1031 } 1032 } 1033 if (type == Boolean.class) { 1034 return asBool(object) ? Boolean.TRUE : Boolean.FALSE; 1035 } 1036 Object[] args = null; 1037 if (object instanceof Collection) { 1038 Collection list = (Collection) object; 1039 args = list.toArray(); 1040 } 1041 else if (object instanceof Object[]) { 1042 args = (Object[]) object; 1043 } 1044 if (args != null) { 1045 // lets try invoke the constructor with the list as arguments 1046 // such as for creating a Dimension, Point, Color etc. 1047 try { 1048 return invokeConstructorOf(type, args); 1049 } 1050 catch (Exception e) { 1051 // lets ignore exception and return the original object 1052 // as the caller has more context to be able to throw a more 1053 // meaningful exception 1054 } 1055 1056 } 1057 return object; 1058 } 1059 1060 public Object asArray(Object object, Class type) { 1061 Collection list = asCollection(object); 1062 int size = list.size(); 1063 Class elementType = type.getComponentType(); 1064 Object array = Array.newInstance(elementType, size); 1065 int idx = 0; 1066 1067 if (boolean.class.equals(elementType)) { 1068 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1069 Object element = iter.next(); 1070 Array.setBoolean(array, idx, asBool(element)); 1071 } 1072 } 1073 else if (byte.class.equals(elementType)) { 1074 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1075 Object element = iter.next(); 1076 Array.setByte(array, idx, asByte(element)); 1077 } 1078 } 1079 else if (char.class.equals(elementType)) { 1080 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1081 Object element = iter.next(); 1082 Array.setChar(array, idx, asChar(element)); 1083 } 1084 } 1085 else if (double.class.equals(elementType)) { 1086 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1087 Object element = iter.next(); 1088 Array.setDouble(array, idx, asDouble(element)); 1089 } 1090 } 1091 else if (float.class.equals(elementType)) { 1092 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1093 Object element = iter.next(); 1094 Array.setFloat(array, idx, asFloat(element)); 1095 } 1096 } 1097 else if (int.class.equals(elementType)) { 1098 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1099 Object element = iter.next(); 1100 Array.setInt(array, idx, asInt(element)); 1101 } 1102 } 1103 else if (long.class.equals(elementType)) { 1104 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1105 Object element = iter.next(); 1106 Array.setLong(array, idx, asLong(element)); 1107 } 1108 } 1109 else if (short.class.equals(elementType)) { 1110 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1111 Object element = iter.next(); 1112 Array.setShort(array, idx, asShort(element)); 1113 } 1114 } 1115 else { 1116 for (Iterator iter = list.iterator(); iter.hasNext(); idx++) { 1117 Object element = iter.next(); 1118 Object coercedElement = asType(element, elementType); 1119 Array.set(array, idx, coercedElement); 1120 } 1121 } 1122 return array; 1123 } 1124 1125 public Number asNumber(Object value) { 1126 if (value instanceof Number) { 1127 return (Number) value; 1128 } 1129 else if (value instanceof String) { 1130 String s = (String) value; 1131 1132 if (s.length() == 1) { 1133 return new Integer(s.charAt(0)); 1134 } 1135 else { 1136 return new BigDecimal(s); 1137 } 1138 } 1139 else if (value instanceof Character) { 1140 return new Integer(((Character) value).charValue()); 1141 } 1142 else { 1143 throw new GroovyRuntimeException("Could not convert object: " + value + " into a Number"); 1144 } 1145 } 1146 1147 public byte asByte(Object element) { 1148 return asNumber(element).byteValue(); 1149 } 1150 1151 public char asChar(Object element) { 1152 if (element instanceof String) { 1153 return asCharacter((String) element).charValue(); 1154 } 1155 return asCharacter(asNumber(element)).charValue(); 1156 } 1157 1158 public float asFloat(Object element) { 1159 return asNumber(element).floatValue(); 1160 } 1161 1162 public double asDouble(Object element) { 1163 return asNumber(element).doubleValue(); 1164 } 1165 1166 public short asShort(Object element) { 1167 return asNumber(element).shortValue(); 1168 } 1169 1170 public int asInt(Object element) { 1171 return asNumber(element).intValue(); 1172 } 1173 1174 public long asLong(Object element) { 1175 return asNumber(element).longValue(); 1176 } 1177 1178 public boolean asBool(Object object) { 1179 if (object instanceof Boolean) { 1180 Boolean booleanValue = (Boolean) object; 1181 return booleanValue.booleanValue(); 1182 } 1183 else if (object instanceof Matcher) { 1184 Matcher matcher = (Matcher) object; 1185 RegexSupport.setLastMatcher(matcher); 1186 return matcher.find(); 1187 } 1188 else if (object instanceof Collection) { 1189 Collection collection = (Collection) object; 1190 return !collection.isEmpty(); 1191 } 1192 else if (object instanceof Map) { 1193 Map map = (Map) object; 1194 return !map.isEmpty(); 1195 } 1196 else if (object instanceof String) { 1197 String string = (String) object; 1198 return string.length() > 0; 1199 } 1200 else if (object instanceof Number) { 1201 Number n = (Number) object; 1202 return n.doubleValue() != 0; 1203 } 1204 else { 1205 return object != null; 1206 } 1207 } 1208 1209 protected Character asCharacter(Number value) { 1210 return new Character((char) value.intValue()); 1211 } 1212 1213 protected Character asCharacter(String text) { 1214 return new Character(text.charAt(0)); 1215 } 1216 1217 /** 1218 * @return true if the given value is a valid character string (i.e. has length of 1) 1219 */ 1220 protected boolean isValidCharacterString(Object value) { 1221 if (value instanceof String) { 1222 String s = (String) value; 1223 if (s.length() == 1) { 1224 return true; 1225 } 1226 } 1227 return false; 1228 } 1229 1230 public void removeMetaClass(Class clazz) { 1231 getMetaRegistry().removeMetaClass(clazz); 1232 } 1233 }