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