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