1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package org.codehaus.groovy.runtime;
47
48 import groovy.lang.GroovyObject;
49 import groovy.lang.GroovyRuntimeException;
50 import groovy.lang.MetaClass;
51 import groovy.lang.MetaClassRegistry;
52 import groovy.lang.Range;
53 import groovy.lang.Tuple;
54
55 import java.io.File;
56 import java.io.IOException;
57 import java.io.StringWriter;
58 import java.lang.reflect.Method;
59 import java.security.AccessController;
60 import java.security.PrivilegedAction;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.Collections;
65 import java.util.Enumeration;
66 import java.util.Iterator;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.NoSuchElementException;
70 import java.util.regex.Matcher;
71 import java.util.regex.Pattern;
72
73 import org.apache.xml.serialize.OutputFormat;
74 import org.apache.xml.serialize.XMLSerializer;
75 import org.w3c.dom.Element;
76 import org.w3c.dom.Node;
77 import org.w3c.dom.NodeList;
78
79 /***
80 * A helper class to invoke methods or extract properties on arbitrary Java objects dynamically
81 *
82 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
83 * @version $Revision: 1.61 $
84 */
85 public class Invoker {
86
87 protected static final Object[] EMPTY_ARGUMENTS = {
88 };
89 protected static final Class[] EMPTY_TYPES = {
90 };
91
92 public MetaClassRegistry getMetaRegistry() {
93 return metaRegistry;
94 }
95
96 private MetaClassRegistry metaRegistry = new MetaClassRegistry();
97
98 public MetaClass getMetaClass(Object object) {
99 return metaRegistry.getMetaClass(object.getClass());
100 }
101
102 /***
103 * Invokes the given method on the object.
104 *
105 * @param object
106 * @param methodName
107 * @param arguments
108 * @return
109 */
110 public Object invokeMethod(Object object, String methodName, Object arguments) {
111
112
113
114
115
116
117
118
119
120
121
122
123
124 if (object == null) {
125 throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
126 }
127
128 if (object instanceof GroovyObject) {
129 GroovyObject groovy = (GroovyObject) object;
130 return groovy.invokeMethod(methodName, arguments);
131 }
132 else {
133 if (object instanceof Class) {
134 Class theClass = (Class) object;
135
136 MetaClass metaClass = metaRegistry.getMetaClass(theClass);
137 return metaClass.invokeStaticMethod(object, methodName, asArray(arguments));
138 }
139 else {
140 Class theClass = object.getClass();
141
142 MetaClass metaClass = metaRegistry.getMetaClass(theClass);
143 return metaClass.invokeMethod(object, methodName, asArray(arguments));
144 }
145 }
146 }
147
148 public Object invokeSuperMethod(Object object, String methodName, Object arguments) {
149 if (object == null) {
150 throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
151 }
152
153 Class theClass = object.getClass();
154
155 MetaClass metaClass = metaRegistry.getMetaClass(theClass.getSuperclass());
156 return metaClass.invokeMethod(object, methodName, asArray(arguments));
157 }
158
159 public Object invokeStaticMethod(String type, String method, Object arguments) {
160 MetaClass metaClass = metaRegistry.getMetaClass(loadClass(type));
161 List argumentList = asList(arguments);
162 return metaClass.invokeStaticMethod(null, method, asArray(arguments));
163 }
164
165 public Object invokeConstructor(String type, Object arguments) {
166
167 return invokeConstructorOf(loadClass(type), arguments);
168 }
169
170 public Object invokeConstructorOf(Class type, Object arguments) {
171 MetaClass metaClass = metaRegistry.getMetaClass(type);
172 return metaClass.invokeConstructor(asArray(arguments));
173 }
174
175 /***
176 * Converts the given object into an array; if its an array then just
177 * cast otherwise wrap it in an array
178 */
179 public Object[] asArray(Object arguments) {
180 if (arguments == null) {
181 return EMPTY_ARGUMENTS;
182 }
183 if (arguments instanceof Tuple) {
184 Tuple tuple = (Tuple) arguments;
185 return tuple.toArray();
186 }
187 if (arguments instanceof Object[]) {
188 return (Object[]) arguments;
189 }
190 else {
191 return new Object[] { arguments };
192 }
193 }
194
195 public List asList(Object value) {
196 if (value == null) {
197 return Collections.EMPTY_LIST;
198 }
199 else if (value instanceof List) {
200 return (List) value;
201 }
202 else if (value.getClass().isArray()) {
203 return Arrays.asList((Object[]) value);
204 }
205 else if (value instanceof Enumeration) {
206 List answer = new ArrayList();
207 for (Enumeration e = (Enumeration) value; e.hasMoreElements();) {
208 answer.add(e.nextElement());
209 }
210 return answer;
211 }
212 else {
213
214 return Collections.singletonList(value);
215 }
216 }
217
218 /***
219 * @param arguments
220 * @return
221 */
222 public Collection asCollection(Object value) {
223 if (value == null) {
224 return Collections.EMPTY_LIST;
225 }
226 else if (value instanceof Collection) {
227 return (Collection) value;
228 }
229 else if (value instanceof Map) {
230 Map map = (Map) value;
231 return map.entrySet();
232 }
233 else if (value.getClass().isArray()) {
234 if (value.getClass().getComponentType().isPrimitive()) {
235 return InvokerHelper.primitiveArrayToList(value);
236 }
237 return Arrays.asList((Object[]) value);
238 }
239 else if (value instanceof MethodClosure) {
240 MethodClosure method = (MethodClosure) value;
241 IteratorClosureAdapter adapter = new IteratorClosureAdapter(method.getDelegate());
242 method.call(adapter);
243 return adapter.asList();
244 }
245 else if (value instanceof String) {
246 return DefaultGroovyMethods.toList((String) value);
247 }
248 else if (value instanceof File) {
249 try {
250 return DefaultGroovyMethods.readLines((File) value);
251 }
252 catch (IOException e) {
253 throw new GroovyRuntimeException("Error reading file: " + value, e);
254 }
255 }
256 else {
257
258 return Collections.singletonList(value);
259 }
260 }
261
262 public Iterator asIterator(Object value) {
263 if (value == null) {
264 Collections.EMPTY_LIST.iterator();
265 }
266 if (value instanceof Iterator) {
267 return (Iterator) value;
268 }
269 if (value instanceof NodeList) {
270 final NodeList nodeList = (NodeList) value;
271 return new Iterator() {
272 private int current = 0;
273 public boolean hasNext() {
274 return current < nodeList.getLength();
275 }
276 public Object next() {
277 Node node = nodeList.item(current++);
278 return node;
279 }
280 public void remove() {
281 throw new UnsupportedOperationException("Cannot remove() from an Enumeration");
282 }
283 };
284 }
285 else if (value instanceof Enumeration) {
286 final Enumeration enumeration = (Enumeration) value;
287 return new Iterator() {
288 private Object last;
289
290 public boolean hasNext() {
291 return enumeration.hasMoreElements();
292 }
293
294 public Object next() {
295 last = enumeration.nextElement();
296 return last;
297 }
298
299 public void remove() {
300 throw new UnsupportedOperationException("Cannot remove() from an Enumeration");
301 }
302 };
303 }
304 else if (value instanceof Matcher) {
305 final Matcher matcher = (Matcher) value;
306 return new Iterator() {
307 private boolean found = false;
308 private boolean done = false;
309 public boolean hasNext() {
310 if (done)
311 return false;
312 if (!found) {
313 found = matcher.find();
314 if (!found)
315 done = true;
316 }
317 return found;
318 }
319 public Object next() {
320 if (!found) {
321 if (!hasNext()) {
322 throw new NoSuchElementException();
323 }
324 }
325 found = false;
326 return matcher.group();
327 }
328 public void remove() {
329 throw new UnsupportedOperationException();
330 }
331 };
332 }
333 else {
334 try {
335
336 final Method method = value.getClass().getMethod("iterator", EMPTY_TYPES);
337
338 if (method != null) {
339 AccessController.doPrivileged(new PrivilegedAction() {
340 public Object run() {
341 method.setAccessible(true);
342 return null;
343 }
344 });
345
346 return (Iterator) method.invoke(value, EMPTY_ARGUMENTS);
347 }
348 }
349 catch (Exception e) {
350
351 }
352 }
353 return asCollection(value).iterator();
354 }
355
356 /***
357 * @return true if the two objects are null or the objects are equal
358 */
359 public boolean objectsEqual(Object left, Object right) {
360 if (left == right) {
361 return true;
362 }
363 if (left != null) {
364 if (right == null) {
365 return false;
366 }
367 if (left instanceof Comparable) {
368 return compareTo(left, right) == 0;
369 }
370 else {
371 return left.equals(right);
372 }
373 }
374 return false;
375 }
376
377 public String inspect(Object self) {
378 return format(self, true);
379 }
380
381 /***
382 * Compares the two objects handling nulls gracefully and performing numeric type coercion if required
383 */
384 public int compareTo(Object left, Object right) {
385
386 if (left == right) {
387 return 0;
388 }
389 if (left == null) {
390 return -1;
391 }
392 else if (right == null) {
393 return 1;
394 }
395 if (left instanceof Comparable) {
396 if (left instanceof Number) {
397 if (isValidCharacterString(right)) {
398 return asCharacter((Number) left).compareTo(asCharacter((String) right));
399 }
400 return DefaultGroovyMethods.compareTo((Number) left, asNumber(right));
401 }
402 else if (left instanceof Character) {
403 if (isValidCharacterString(right)) {
404 return ((Character) left).compareTo(asCharacter((String) right));
405 }
406 else if (right instanceof Number) {
407 return ((Character) left).compareTo(asCharacter((Number) right));
408 }
409 }
410 else if (right instanceof Number) {
411 if (isValidCharacterString(left)) {
412 return asCharacter((String) left).compareTo(asCharacter((Number) right));
413 }
414 return DefaultGroovyMethods.compareTo(asNumber(left), (Number) right);
415 }
416 else if (left instanceof String && right instanceof Character) {
417 return ((String)left).compareTo(right.toString());
418 }
419 Comparable comparable = (Comparable) left;
420 return comparable.compareTo(right);
421 }
422 if (left.getClass().isArray()) {
423 Collection leftList = asCollection(left);
424 if (right.getClass().isArray()) {
425 right = asCollection(right);
426 }
427 return ((Comparable) leftList).compareTo(right);
428 }
429 /*** todo we might wanna do some type conversion here */
430 throw new GroovyRuntimeException("Cannot compare values: " + left + " and " + right);
431 }
432
433 /***
434 * A helper method to provide some better toString() behaviour such as turning arrays
435 * into tuples
436 */
437 public String toString(Object arguments) {
438 return format(arguments, false);
439 }
440
441 /***
442 * A helper method to format the arguments types as a comma-separated list
443 */
444 public String toTypeString(Object[] arguments) {
445 if (arguments == null) {
446 return "null";
447 }
448 StringBuffer argBuf = new StringBuffer();
449 for (int i = 0; i < arguments.length; i++) {
450 if (i>0)
451 argBuf.append(", ");
452 argBuf.append(arguments[i] != null ? arguments[i].getClass().getName() : "null");
453 }
454 return argBuf.toString();
455 }
456
457 protected String format(Object arguments, boolean verbose) {
458 if (arguments == null) {
459 return "null";
460 }
461 else if (arguments.getClass().isArray()) {
462 return format(asCollection(arguments), verbose);
463 }
464 else if (arguments instanceof Range) {
465 Range range = (Range) arguments;
466 if (verbose) {
467 return range.inspect();
468 }
469 else {
470 return range.toString();
471 }
472 }
473 else if (arguments instanceof List) {
474 List list = (List) arguments;
475 StringBuffer buffer = new StringBuffer("[");
476 boolean first = true;
477 for (Iterator iter = list.iterator(); iter.hasNext();) {
478 if (first) {
479 first = false;
480 }
481 else {
482 buffer.append(", ");
483 }
484 buffer.append(format(iter.next(), verbose));
485 }
486 buffer.append("]");
487 return buffer.toString();
488 }
489 else if (arguments instanceof Map) {
490 Map map = (Map) arguments;
491 if (map.isEmpty()) {
492 return "[:]";
493 }
494 StringBuffer buffer = new StringBuffer("[");
495 boolean first = true;
496 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
497 if (first) {
498 first = false;
499 }
500 else {
501 buffer.append(", ");
502 }
503 Map.Entry entry = (Map.Entry) iter.next();
504 buffer.append(format(entry.getKey(), verbose));
505 buffer.append(":");
506 buffer.append(format(entry.getValue(), verbose));
507 }
508 buffer.append("]");
509 return buffer.toString();
510 }
511 else if (arguments instanceof Element) {
512 Element node = (Element) arguments;
513 OutputFormat format = new OutputFormat(node.getOwnerDocument());
514 format.setOmitXMLDeclaration(true);
515 format.setIndenting(true);
516 format.setLineWidth(0);
517 format.setPreserveSpace(true);
518 StringWriter sw = new StringWriter();
519 XMLSerializer serializer = new XMLSerializer (sw, format);
520 try {
521 serializer.asDOMSerializer();
522 serializer.serialize(node);
523 } catch (IOException e) {
524 }
525 return sw.toString();
526 }
527 else if (arguments instanceof String) {
528 if (verbose) {
529 return "\"" + arguments + "\"";
530 }
531 else {
532 return (String) arguments;
533 }
534 }
535 else {
536 return arguments.toString();
537 }
538 }
539
540 /***
541 * Sets the property on the given object
542 *
543 * @param object
544 * @param property
545 * @param newValue
546 * @return
547 */
548 public void setProperty(Object object, String property, Object newValue) {
549 if (object == null) {
550 throw new GroovyRuntimeException("Cannot set property on null object");
551 }
552 else if (object instanceof GroovyObject) {
553 GroovyObject pogo = (GroovyObject) object;
554 pogo.setProperty(property, newValue);
555 }
556 else if (object instanceof Map) {
557 Map map = (Map) object;
558 map.put(property, newValue);
559 }
560 else {
561 metaRegistry.getMetaClass(object.getClass()).setProperty(object, property, newValue);
562 }
563 }
564
565 /***
566 * Looks up the given property of the given object
567 *
568 * @param object
569 * @param property
570 * @return
571 */
572 public Object getProperty(Object object, String property) {
573 if (object == null) {
574 throw new NullPointerException("Cannot get property: " + property + " on null object");
575 }
576 else if (object instanceof GroovyObject) {
577 GroovyObject pogo = (GroovyObject) object;
578 return pogo.getProperty(property);
579 }
580 else if (object instanceof Map) {
581 Map map = (Map) object;
582 return map.get(property);
583 }
584 else {
585 return metaRegistry.getMetaClass(object.getClass()).getProperty(object, property);
586 }
587 }
588
589 public int asInt(Object value) {
590 if (value instanceof Number) {
591 Number n = (Number) value;
592 return n.intValue();
593 }
594 throw new GroovyRuntimeException("Could not convert object: " + value + " into an int");
595 }
596
597 public Number asNumber(Object value) {
598 if (value instanceof Number) {
599 return (Number) value;
600 }
601 else if (value instanceof String) {
602 String s = (String) value;
603
604 if (s.length() == 1)
605 return new Integer(s.charAt(0));
606 else
607 return Double.valueOf(s);
608 }
609 else if (value instanceof Character) {
610 return new Integer(((Character) value).charValue());
611 }
612 else {
613 throw new GroovyRuntimeException("Could not convert object: " + value + " into a Number");
614 }
615 }
616
617 /***
618 * Attempts to load the given class via name using the current class loader
619 * for this code or the thread context class loader
620 */
621 protected Class loadClass(String type) {
622 try {
623 return getClass().getClassLoader().loadClass(type);
624 }
625 catch (ClassNotFoundException e) {
626 try {
627 return Thread.currentThread().getContextClassLoader().loadClass(type);
628 }
629 catch (ClassNotFoundException e2) {
630 try {
631 return Class.forName(type);
632 }
633 catch (ClassNotFoundException e3) {
634 }
635 }
636 throw new GroovyRuntimeException("Could not load type: " + type, e);
637 }
638 }
639
640 /***
641 * Find the right hand regex within the left hand string and return a matcher.
642 *
643 * @param left string to compare
644 * @param right regular expression to compare the string to
645 * @return
646 */
647 public Matcher objectFindRegex(Object left, Object right) {
648 String stringToCompare;
649 if (left instanceof String) {
650 stringToCompare = (String) left;
651 }
652 else {
653 stringToCompare = toString(left);
654 }
655 String regexToCompareTo;
656 if (right instanceof String) {
657 regexToCompareTo = (String) right;
658 }
659 else if (right instanceof Pattern) {
660 Pattern pattern = (Pattern) right;
661 return pattern.matcher(stringToCompare);
662 }
663 else {
664 regexToCompareTo = toString(right);
665 }
666 Matcher matcher = Pattern.compile(regexToCompareTo).matcher(stringToCompare);
667 return matcher;
668 }
669
670 /***
671 * Find the right hand regex within the left hand string and return a matcher.
672 *
673 * @param left string to compare
674 * @param right regular expression to compare the string to
675 * @return
676 */
677 public boolean objectMatchRegex(Object left, Object right) {
678 Pattern pattern;
679 if (right instanceof Pattern) {
680 pattern = (Pattern) right;
681 }
682 else {
683 pattern = Pattern.compile(toString(right));
684 }
685 String stringToCompare = toString(left);
686 Matcher matcher = pattern.matcher(stringToCompare);
687 RegexSupport.setLastMatcher(matcher);
688 return matcher.matches();
689 }
690
691 /***
692 * Compile a regular expression from a string.
693 *
694 * @param regex
695 * @return
696 */
697 public Pattern regexPattern(Object regex) {
698 return Pattern.compile(regex.toString());
699 }
700
701 public Object asType(Object object, Class type) {
702 if (object == null) {
703 return null;
704 }
705 if (type.isInstance(object)) {
706 return object;
707 }
708 if (type.equals(String.class)) {
709 return object.toString();
710 }
711 if (type.equals(Character.class)) {
712 if (object instanceof Number) {
713 return asCharacter((Number) object);
714 }
715 else {
716 String text = object.toString();
717 if (text.length() == 1) {
718 return new Character(text.charAt(0));
719 }
720 else {
721 throw new ClassCastException("Cannot cast: " + text + " to a Character");
722 }
723 }
724 }
725 if (Number.class.isAssignableFrom(type)) {
726 if (object instanceof Character) {
727 return new Integer(((Character) object).charValue());
728 }
729 else if (object instanceof String) {
730 String c = (String) object;
731 if (c.length() == 1) {
732 return new Integer(c.charAt(0));
733 }
734 else {
735 throw new ClassCastException("Cannot cast: '" + c + "' to an Integer");
736 }
737 }
738 }
739 if (object instanceof Number) {
740 Number n = (Number) object;
741 if (type.isPrimitive()) {
742 if (type == byte.class) {
743 return new Byte(n.byteValue());
744 }
745 if (type == char.class) {
746 return new Character((char) n.intValue());
747 }
748 if (type == short.class) {
749 return new Short(n.shortValue());
750 }
751 if (type == int.class) {
752 return new Integer(n.intValue());
753 }
754 if (type == long.class) {
755 return new Long(n.longValue());
756 }
757 if (type == float.class) {
758 return new Float(n.floatValue());
759 }
760 if (type == double.class) {
761 Double answer = new Double(n.doubleValue());
762
763 if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY
764 || answer.doubleValue() == Double.POSITIVE_INFINITY)) {
765 throw new GroovyRuntimeException("Automatic coercion of "+n.getClass().getName()
766 +" value "+n+" to double failed. Value is out of range.");
767 }
768 return answer;
769 }
770 }
771 else {
772 if (Number.class.isAssignableFrom(type)) {
773 if (type == Byte.class) {
774 return new Byte(n.byteValue());
775 }
776 if (type == Character.class) {
777 return new Character((char) n.intValue());
778 }
779 if (type == Short.class) {
780 return new Short(n.shortValue());
781 }
782 if (type == Integer.class) {
783 return new Integer(n.intValue());
784 }
785 if (type == Long.class) {
786 return new Long(n.longValue());
787 }
788 if (type == Float.class) {
789 return new Float(n.floatValue());
790 }
791 if (type == Double.class) {
792 Double answer = new Double(n.doubleValue());
793
794 if (!(n instanceof Double) && (answer.doubleValue() == Double.NEGATIVE_INFINITY
795 || answer.doubleValue() == Double.POSITIVE_INFINITY)) {
796 throw new GroovyRuntimeException("Automatic coercion of "+n.getClass().getName()
797 +" value "+n+" to double failed. Value is out of range.");
798 }
799 return answer;
800 }
801
802 }
803 }
804 }
805 if (type == Boolean.class) {
806 return asBool(object) ? Boolean.TRUE : Boolean.FALSE;
807 }
808 return object;
809 }
810
811 public boolean asBool(Object object) {
812 if (object instanceof Boolean) {
813 Boolean booleanValue = (Boolean) object;
814 return booleanValue.booleanValue();
815 }
816 else if (object instanceof Matcher) {
817 Matcher matcher = (Matcher) object;
818 RegexSupport.setLastMatcher(matcher);
819 return matcher.find();
820 }
821 else if (object instanceof Collection) {
822 Collection collection = (Collection) object;
823 return !collection.isEmpty();
824 }
825 else if (object instanceof Number) {
826 Number n = (Number) object;
827 return n.intValue() != 0;
828 }
829 else {
830 return object != null;
831 }
832 }
833
834 protected Character asCharacter(Number value) {
835 return new Character((char) value.intValue());
836 }
837
838 protected Character asCharacter(String text) {
839 return new Character(text.charAt(0));
840 }
841
842 /***
843 * @return true if the given value is a valid character string (i.e. has length of 1)
844 */
845 protected boolean isValidCharacterString(Object value) {
846 if (value instanceof String) {
847 String s = (String) value;
848 if (s.length() == 1) {
849 return true;
850 }
851 }
852 return false;
853 }
854
855 }