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 }