36 import org.jemmy.env.TestOut;
37 import org.jemmy.env.Timeout;
38 import org.jemmy.image.Image;
39 import org.jemmy.interfaces.*;
40 import org.jemmy.timing.State;
41
42 /**
43 * This is a wrap which holds reference to a control without UI hierarchy. It
44 * also encapsulates all the logic to deal with the underlying control, in terms
45 * of implementations of ControlInterface.
46 *
47 * @see Wrap#as(java.lang.Class)
48 * @see Wrap#is(java.lang.Class)
49 * @param <CONTROL> type of the encapsulated object.
50 * @author shura, erikgreijus
51 */
52 @ControlType(Object.class)
53 @ControlInterfaces({Mouse.class, Keyboard.class, Drag.class})
54 public abstract class Wrap<CONTROL extends Object> {
55
56 /**
57 *
58 */
59 public static final String BOUNDS_PROP_NAME = "bounds";
60 /**
61 *
62 */
63 public static final String CLICKPOINT_PROP_NAME = "clickPoint";
64 /**
65 *
66 */
67 public static final String CONTROL_CLASS_PROP_NAME = "control.class";
68 /**
69 *
70 */
71 public static final String CONTROL_PROP_NAME = "control";
72 /**
73 *
74 */
75 public static final String INPUT_FACTORY_PROPERTY = "input.control.interface.factory";
76 /**
77 *
78 */
79 public static final String IMAGE_LOADER_PROPERTY = "image.loader";
80 /**
81 *
82 */
83 public static final String IMAGE_CAPTURER_PROPERTY = "image.capturer";
84 /**
85 *
86 */
87 public static final String TEXT_PROP_NAME = "text";
88 /**
89 *
90 */
91 public static final String POSITION_PROP_NAME = "position";
92 /**
93 *
94 */
95 public static final String VALUE_PROP_NAME = "value";
96 /**
97 *
98 */
99 public static final String WRAPPER_CLASS_PROP_NAME = "wrapper.class";
100 /**
101 *
102 */
103 public static final String TOOLTIP_PROP_NAME = "tooltip";
104 /**
105 *
106 */
107 public static final String NAME_PROP_NAME = "name";
108 /**
109 *
110 */
111 public static final Timeout WAIT_STATE_TIMEOUT = new Timeout("wait.state", 1000);
112 /**
113 *
114 */
115 public static final String OUTPUT = Wrap.class.getName() + ".OUTPUT";
116 private static DefaultWrapper theWrapper = new DefaultWrapper(Environment.getEnvironment());
117
118 static {
119 Environment.getEnvironment().initTimeout(WAIT_STATE_TIMEOUT);
120 Environment.getEnvironment().initOutput(OUTPUT, TestOut.getNullOutput());
121 Environment.getEnvironment().initTimeout(Mouse.CLICK);
122 Environment.getEnvironment().initTimeout(Drag.BEFORE_DRAG_TIMEOUT);
123 Environment.getEnvironment().initTimeout(Drag.BEFORE_DROP_TIMEOUT);
124 Environment.getEnvironment().initTimeout(Drag.IN_DRAG_TIMEOUT);
125 Environment.getEnvironment().initTimeout(Keyboard.PUSH);
126 }
127
128 /**
129 *
130 * @return
131 */
132 public static DefaultWrapper getWrapper() {
133 return theWrapper;
134 }
135 CONTROL node;
136 Environment env;
137
138 /**
139 * Fur null source.
140 *
141 * @see org.jemmy.env.Environment
142 * @param env The environment
143 */
144 protected Wrap(Environment env) {
145 this.env = env;
146 node = null;
147 fillTheProps(false);
148 }
149
150 /**
151 *
167 return env;
168 }
169
170 public void setEnvironment(Environment env) {
171 this.env = env;
172 }
173
174 /**
175 *
176 * @return The encapsulated object
177 */
178 @Property(CONTROL_PROP_NAME)
179 public CONTROL getControl() {
180 return node;
181 }
182
183 /**
184 * Return default point to click, drag. This implementation returns the
185 * center must be overriden if something different is desired.
186 *
187 * @return
188 */
189 @Property(CLICKPOINT_PROP_NAME)
190 public Point getClickPoint() {
191 return new Point(getScreenBounds().width / 2, (getScreenBounds().height / 2));
192 }
193
194 /**
195 * Returns control bounds in screen coordinates. These bounds could include
196 * parts that are covered by other controls or clipped out by parent
197 * components. If the control is not shown {@linkplain
198 * JemmyException JemmyException} will be thrown.
199 *
200 * @return control bounds in screen coordinates.
201 * @throws JemmyException if the control is not visible
202 */
203 @Property(BOUNDS_PROP_NAME)
204 public abstract Rectangle getScreenBounds();
205
206 /**
207 * Transforms point in local control coordinate system to screen
208 * coordinates.
209 *
210 * @param local
211 * @return
212 * @see #toLocal(org.jemmy.Point)
213 */
214 public Point toAbsolute(Point local) {
215 Rectangle bounds = getScreenBounds();
216 return local.translate(bounds.x, bounds.y);
217 }
218
219 /**
220 * Transforms point in screen coordinates to local control coordinate
221 * system.
222 *
223 * @param local
224 * @return coordinates which should be used for mouse operations.
225 * @see #toAbsolute(org.jemmy.Point)
226 */
227 public Point toLocal(Point local) {
228 Rectangle bounds = getScreenBounds();
229 return local.translate(-bounds.x, -bounds.y);
230 }
231
232 /**
233 * Captures the screen area held by the component. ImageFactory performs the
234 * actual capturing.
235 *
236 * @return TODO find a replacement
237 */
238 public Image getScreenImage() {
239 Rectangle bounds = getScreenBounds();
240 return getScreenImage(new Rectangle(0, 0, bounds.width, bounds.height));
241 }
242
243 /**
244 * Captures portion of the screen area held by the component. ImageFactory
245 * performs the actual capturing.
246 *
247 * @param rect Part of the control to capture
248 * @return TODO find a replacement
249 */
250 public Image getScreenImage(Rectangle rect) {
251 if (getEnvironment().getImageCapturer() == null) {
252 throw new JemmyException("Image capturer is not specified.");
253 }
254 return getEnvironment().getImageCapturer().capture(this, rect);
255 }
256
257 /**
258 * Waits for a portion of image to be exact the same as the parameter.
259 *
260 * @see Wrap#as(java.lang.Class)
261 * @param golden
262 * @param rect A portion of control to compare.
263 * @param resID ID of a result image to save in case of failure. No image
264 * saved if null.
265 * @param diffID ID of a diff image to save in case of failure. No image
266 * saved if null.
267 */
268 public void waitImage(final Image golden, final Rectangle rect, String resID, String diffID) {
269 try {
270 waitState(new State<Object>() {
271
272 public Object reached() {
273 return (getScreenImage(rect).compareTo(golden) == null) ? true : null;
274 }
275
276 @Override
277 public String toString() {
278 return "Control having expected image";
279 }
280 });
281 } catch (TimeoutExpiredException e) {
282 if (diffID != null) {
283 getEnvironment().getOutput(OUTPUT).println("Saving difference to " + diffID);
284 getScreenImage(rect).compareTo(golden).save(diffID);
285 }
286 throw e;
287 } finally {
288 if (resID != null) {
289 getEnvironment().getOutput(OUTPUT).println("Saving result to " + resID);
290 getScreenImage(rect).save(resID);
291 }
292 }
293 }
294
295 /**
296 * Waits for image to be exact the same as the parameter.
297 *
298 * @see Wrap#as(java.lang.Class)
299 * @param golden
300 * @param resID ID of a result image to save in case of failure. No image
301 * saved if null.
302 * @param diffID ID of a diff image to save in case of failure. No image
303 * saved if null.
304 */
305 public void waitImage(final Image golden, String resID, String diffID) {
306 Rectangle bounds = getScreenBounds();
307 waitImage(golden, new Rectangle(0, 0, bounds.width, bounds.height), resID, diffID);
308 }
309
310 /**
311 * TODO javadoc
312 *
313 * @param <V>
314 * @param state
315 * @param value
316 * @return last returned State value
317 * @throws TimeoutExpiredException in case the wait is unsuccessful.
318 */
319 public <V> V waitState(State<V> state, V value) {
320 return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, state);
321 }
322
323 /**
324 * TODO javadoc
325 *
326 * @param <V>
327 * @param state
328 * @return last returned State value
329 * @throws TimeoutExpiredException in case the wait is unsuccessful.
330 */
331 public <V> V waitState(State<V> state) {
332 return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureState(state);
333 }
334
335 /**
336 * ***********************************************************************
337 */
338 /*
339 * INTERFACES
340 */
341 /**
342 * ***********************************************************************
343 */
344 private Method findAsMethod(Class<? extends ControlInterface> interfaceClass, Class type) {
345 while (type != null) {
346 for (Method m : getClass().getMethods()) {
347 As as = m.getAnnotation(As.class);
353 }
354 return m;
355 }
356 }
357 type = type.getSuperclass();
358 }
359 return null;
360 }
361
362 /**
363 * Checks if the control could be treated as a ControlInterface. If it is,
364 * <code>Wrap#as(java.lang.Class)</code> will be called. This implementation
365 * checks whether the class implements the necessary interface. It also
366 * works for root interfaces such as
367 * <code>MouseTarget</code> and
368 * <code>KeyTarget</code>, which implementations are encapsulated. If some
369 * other functionality is desired, must be overriden together with
370 * <code>as(java.lang.Class)</code>
371 *
372 * @see Wrap#is(java.lang.Class)
373 * @param <INTERFACE>
374 * @param interfaceClass
375 * @return
376 */
377 public <INTERFACE extends ControlInterface> boolean is(Class<INTERFACE> interfaceClass) {
378 if (interfaceClass.isInstance(this)) {
379 return true;
380 }
381 return findAsMethod(interfaceClass, Void.class) != null;
382 }
383
384 /**
385 * Checks if the control could be treated as a parametrized
386 * ControlInterface. If it is,
387 * <code>Wrap#as(java.lang.Class, java.lang.Class)</code> will be called.
388 * This implementation checks whether the class implements the necessary
389 * interface. It also works for root interfaces such as
390 * <code>MouseTarget</code> and
391 * <code>KeyTarget</code>, which implementations are encapsulated. If some
392 * other functionality is desired, must be overriden together with
393 * <code>as(java.lang.Class)</code>
394 *
395 * @see Wrap#is(java.lang.Class)
396 * @param <TYPE>
397 * @param <INTERFACE>
398 * @param interfaceClass
399 * @param type The parameter class.
400 * @return
401 */
402 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> boolean is(Class<INTERFACE> interfaceClass, Class<TYPE> type) {
403 if (interfaceClass.isInstance(this)) {
404 if (interfaceClass.cast(this).getType().isAssignableFrom(type)) {
405 return true;
406 }
407 }
408 return findAsMethod(interfaceClass, type) != null;
409 }
410
411 private Object callAsMethod(Class<? extends ControlInterface> interfaceClass, Class type) {
412 Method m = findAsMethod(interfaceClass, type);
413 if (m != null) {
414 try {
415 if (m.getParameterTypes().length == 0) {
416 return m.invoke(this);
417 } else if (m.getParameterTypes().length == 1) {
418 return m.invoke(this, !type.equals(Void.class) ? type : Object.class);
419 } else {
420 throw new InterfaceException(this, interfaceClass);
421 }
422 } catch (IllegalAccessException ex) {
423 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
424 } catch (IllegalArgumentException ex) {
425 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
426 } catch (InvocationTargetException ex) {
427 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
428 }
429 }
430 return null;
431 }
432
433 /**
434 * Returns an implementation of interface associated with this object. First
435 * it checks
436 *
437 * @see Wrap#is(java.lang.Class)
438 * @param <INTERFACE>
439 * @param interfaceClass
440 * @return
441 */
442 public <INTERFACE extends ControlInterface> INTERFACE as(Class<INTERFACE> interfaceClass) {
443 if (interfaceClass.isInstance(this)) {
444 return interfaceClass.cast(this);
445 }
446
447 Object res = callAsMethod(interfaceClass, Void.class);
448 if (res != null) {
449 return (INTERFACE) res;
450 }
451
452 throw new InterfaceException(this, interfaceClass);
453 }
454
455 /**
456 * Returns an implementation of interface associated with the object.
457 *
458 * @see Wrap#is(java.lang.Class)
459 * @param <TYPE>
460 * @param <INTERFACE>
461 * @param interfaceClass
462 * @param type The parameter class.
463 * @return
464 */
465 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> INTERFACE as(Class<INTERFACE> interfaceClass, Class<TYPE> type) {
466 if (interfaceClass.isInstance(this)) {
467 if (interfaceClass.cast(this).getType().isAssignableFrom(type)) {
468 return interfaceClass.cast(this);
469 }
470 }
471
472 Object res = callAsMethod(interfaceClass, type);
473 if (res != null) {
474 return (INTERFACE) res;
475 }
476
477 throw new InterfaceException(this, interfaceClass);
478 }
479 /**
480 * ***********************************************************************
481 */
482 /*
483 * INPUT
484 */
485 /**
486 * ***********************************************************************
487 */
488 private Mouse mouse = null;
489 private Drag drag = null;
490 private Keyboard keyboard = null;
491
492 /**
493 * A shortcut to
494 * <code>as(MouseTarget.class).mouse()</code>
495 *
496 * @return
497 */
498 @As(Mouse.class)
499 public Mouse mouse() {
500 if (mouse == null) {
501 mouse = getEnvironment().getInputFactory().create(this, Mouse.class);
502 }
503 return mouse;
504 }
505
506 /**
507 * A shortcut to
508 * <code>as(MouseTarget.class).drag()</code>
509 *
510 * @return
511 */
512 @As(Drag.class)
513 public Drag drag() {
514 if (drag == null) {
515 drag = getEnvironment().getInputFactory().create(this, Drag.class);
516 }
517 return drag;
518 }
519
520 /**
521 * A shortcut to
522 * <code>as(KeyTarget.class).wrap()</code>
523 *
524 * @return
525 */
526 @As(Keyboard.class)
527 public Keyboard keyboard() {
528 if (keyboard == null) {
529 keyboard = getEnvironment().getInputFactory().create(this, Keyboard.class);
530 }
531 return keyboard;
532 }
533 /**
534 * ***********************************************************************
535 */
536 /*
537 * PROPERTIES
538 */
539 /**
540 * ***********************************************************************
541 */
542 private HashMap<String, Object> properties = new HashMap<String, Object>();
543
544 /**
545 *
546 * @return
547 */
548 @Property(CONTROL_CLASS_PROP_NAME)
549 public Class<?> getControlClass() {
550 return getControl().getClass();
551 }
552
553 private void fillTheProps(boolean quiet) {
554 properties.clear();
555 properties.put(WRAPPER_CLASS_PROP_NAME, getClass());
556 readAnnotationProps(quiet);
557 readControlProps(quiet);
558 }
559
560 private void readControlProps(boolean quiet) {
561 Class<?> cls = getClass();
562 do {
563 if (cls.isAnnotationPresent(FieldProperties.class)) {
564 for (String s : cls.getAnnotation(FieldProperties.class).value()) {
565 Object value;
566 try {
567 value = getFieldProperty(s);
650 }
651 }
652 }
653 return null;
654 }
655
656 private Object getProperty(Object object, Method m) {
657 Property prop = m.getAnnotation(Property.class);
658 try {
659 return m.invoke(object);
660 } catch (IllegalAccessException ex) {
661 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
662 } catch (IllegalArgumentException ex) {
663 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
664 } catch (InvocationTargetException ex) {
665 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
666 }
667 }
668
669 /**
670 * Get property of the wrapped object. Uses first available from <nl>
671 * <li>methods annotated by
672 * <code>org.jemmy.control.Property</code></li> <li>wrapped object methods
673 * listed in
674 * <code>org.jemmy.control.MethodProperties</code></li> <li>wrapped object
675 * fields listed in
676 * <code>org.jemmy.control.FieldProperties</code></li> </nl>
677 *
678 * @param name property name
679 * @throws JemmyException if no property found
680 * @see Property
681 * @see MethodProperties
682 * @see FieldProperties
683 * @return property value
684 */
685 public Object getProperty(String name) {
686 if (WRAPPER_CLASS_PROP_NAME.equals(name)) {
687 return getClass();
688 }
689 Method m = getPropertyMethod(this.getClass(), name);
690 if (m != null) {
691 return getProperty(this, m);
692 }
693 if (hasMethodProperty(name)) {
694 return getMethodProperty(name);
695 }
696 if (hasFieldProperty(name)) {
697 return getFieldProperty(name);
698 }
699 throw new JemmyException("No property \"" + name + "\"", this);
700 }
701
702 private Object getInterfaceProperty(Class cls, Object instance, String name) {
703 Method m = getPropertyMethod(cls, name);
704 if (m != null) {
705 return getProperty(instance, m);
706 }
707 throw new JemmyException("No property \"" + name + "\" in interface " + cls.getName(), instance);
708 }
709
710 /**
711 * Get property out of the control interface. Refer to the interface doc to
712 * find out what properties are provided.
713 *
714 * @param <INTERFACE>
715 * @param name
716 * @param intrfc
717 * @return
718 */
719 public <INTERFACE extends ControlInterface> Object getProperty(String name, Class<INTERFACE> intrfc) {
720 return getInterfaceProperty(intrfc, as(intrfc), name);
721 }
722
723 /**
724 * Get property out of the control interface. Refer to the interface doc to
725 * find out what properties are provided.
726 *
727 * @param <TYPE>
728 * @param <INTERFACE>
729 * @param name
730 * @param intrfc
731 * @param type
732 * @return
733 */
734 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> Object getProperty(String name, Class<INTERFACE> intrfc, Class<TYPE> type) {
735 return getInterfaceProperty(intrfc, as(intrfc, type), name);
736 }
737
738 /**
739 * Wait for the property
740 * <code>property</code> to get the specified value.
741 * <code>WAIT_STATE_TIMOUT</code> timeout is used
742 *
743 * @param property name of the property being waited for
744 * @param value property value to wait
745 */
746 public void waitProperty(final String property, final Object value) {
747 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
748
749 public Object reached() {
750 return getProperty(property);
751 }
752
753 @Override
754 public String toString() {
755 return "Control having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')";
756 }
757 });
758 }
759
760 /**
761 * Wait for the property
762 * <code>property</code> of control interface to get the specified value.
763 * <code>WAIT_STATE_TIMOUT</code> timeout is used
764 *
765 * @param <INTERFACE>
766 * @param property
767 * @param intrfc
768 * @param value
769 */
770 public <INTERFACE extends ControlInterface> void waitProperty(final String property, final Class<INTERFACE> intrfc, final Object value) {
771 Object instance = as(intrfc);
772 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
773
774 public Object reached() {
775 return getProperty(property, intrfc);
776 }
777
778 @Override
779 public String toString() {
780 return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property, intrfc) + "')";
781 }
782 });
783 }
784
785 /**
786 * Wait for the property
787 * <code>property</code> of control interface to get the specified value.
788 * <code>WAIT_STATE_TIMOUT</code> timeout is used
789 *
790 * @param <TYPE>
791 * @param <INTERFACE>
792 * @param property
793 * @param intrfc
794 * @param type
795 * @param value
796 */
797 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> void waitProperty(final String property, final Class<INTERFACE> intrfc, final Class<TYPE> type, final Object value) {
798 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
799
800 public Object reached() {
801 return getProperty(property, intrfc, type);
802 }
803
804 @Override
805 public String toString() {
806 return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')";
807 }
808 });
809 }
810
811 /**
812 *
813 * @param name
814 * @return
815 */
816 public boolean hasFieldProperty(String name) {
817 Class<?> cls = getClass();
818 do {
819 if (cls.isAnnotationPresent(FieldProperties.class)) {
820 FieldProperties props = cls.getAnnotation(FieldProperties.class);
821 if (contains(props.value(), name)) {
822 return true;
823 }
824 }
825 } while ((cls = cls.getSuperclass()) != null);
826 return false;
827 }
828
829 /**
830 *
831 * @param name
832 * @return
833 */
834 public boolean hasMethodProperty(String name) {
835 Class<?> cls = getClass();
836 do {
837 if (cls.isAnnotationPresent(MethodProperties.class)) {
838 MethodProperties props = cls.getAnnotation(MethodProperties.class);
839 if (contains(props.value(), name)) {
840 return true;
841 }
842 }
843 } while ((cls = cls.getSuperclass()) != null);
844 return false;
845 }
846
847 private boolean contains(String[] values, String name) {
848 for (int i = 0; i < values.length; i++) {
849 if (name.equals(values[i])) {
850 return true;
851 }
852
853 }
854 return false;
855 }
856
857 /**
858 *
859 * @param name
860 * @return
861 */
862 public Object getFieldProperty(final String name) {
863 if (!hasFieldProperty(name)) {
864 throw new JemmyException("No \"" + name + "\" field property specified on " + getClass().getName());
865 }
866 GetAction action = new GetAction() {
867
868 @Override
869 public void run(Object... parameters) throws Exception {
870 setResult(getControl().getClass().getField(name).get(getControl()));
871 }
872 };
873 Object result = action.dispatch(env);
874 if (action.getThrowable() != null) {
875 throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this);
876 }
877 return result;
878 }
879
880 /**
881 *
882 * @param name
883 * @return
884 */
885 public Object getMethodProperty(final String name) {
886 if (!hasMethodProperty(name)) {
887 throw new JemmyException("No \"" + name + "\" method property specified on " + getClass().getName());
888 }
889 GetAction action = new GetAction() {
890
891 @Override
892 public void run(Object... parameters) throws Exception {
893 setResult(getControl().getClass().getMethod(name).invoke(getControl()));
894 }
895
896 @Override
897 public String toString() {
898 return "Getting property \"" + name + "\" on " + getClass().getName();
899 }
900 };
901 Object result = action.dispatch(env);
902 if (action.getThrowable() != null) {
903 throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this);
904 }
905 return result;
906 }
907
908 /**
909 *
910 * @param <P>
911 * @param valueClass
912 * @param name
913 * @return
914 */
915 public <P> P getProperty(Class<P> valueClass, String name) {
916 return valueClass.cast(getProperty(name));
917 }
918
919 /**
920 * Returns a a map of all known controls properties including values from
921 * methods marked by
922 * <code>@Property</code> and values of methods/field from
923 * <code>@MethodProperties</code>/
924 * <code>FieldProperties</code> correspondingly.
925 *
926 * @return a map of properties
927 * @throws Runtime exception should there be an exception thrown while
928 * getting a property
929 */
930 public HashMap<String, Object> getProperties() {
931 fillTheProps(false);
932 return properties;
933 }
934
935 /**
936 * Returns a a map of all controls properties which is possible to obtain.
937 * Similar to
938 * <code>getProperties()</code> only exception is swallowed should there be
939 * an exception thrown while getting a property.
940 *
941 * @return a map of properties which were possible to obtain.
942 */
943 public HashMap<String, Object> getPropertiesQiuet() {
944 fillTheProps(true);
945 return properties;
946 }
947 }
|
36 import org.jemmy.env.TestOut;
37 import org.jemmy.env.Timeout;
38 import org.jemmy.image.Image;
39 import org.jemmy.interfaces.*;
40 import org.jemmy.timing.State;
41
42 /**
43 * This is a wrap which holds reference to a control without UI hierarchy. It
44 * also encapsulates all the logic to deal with the underlying control, in terms
45 * of implementations of ControlInterface.
46 *
47 * @see Wrap#as(java.lang.Class)
48 * @see Wrap#is(java.lang.Class)
49 * @param <CONTROL> type of the encapsulated object.
50 * @author shura, erikgreijus
51 */
52 @ControlType(Object.class)
53 @ControlInterfaces({Mouse.class, Keyboard.class, Drag.class})
54 public abstract class Wrap<CONTROL extends Object> {
55
56 public static final String BOUNDS_PROP_NAME = "bounds";
57 public static final String CLICKPOINT_PROP_NAME = "clickPoint";
58 public static final String CONTROL_CLASS_PROP_NAME = "control.class";
59 public static final String CONTROL_PROP_NAME = "control";
60 public static final String INPUT_FACTORY_PROPERTY = "input.control.interface.factory";
61 public static final String IMAGE_LOADER_PROPERTY = "image.loader";
62 public static final String IMAGE_CAPTURER_PROPERTY = "image.capturer";
63 public static final String TEXT_PROP_NAME = "text";
64 public static final String POSITION_PROP_NAME = "position";
65 public static final String VALUE_PROP_NAME = "value";
66 public static final String WRAPPER_CLASS_PROP_NAME = "wrapper.class";
67 public static final String TOOLTIP_PROP_NAME = "tooltip";
68 public static final String NAME_PROP_NAME = "name";
69 public static final Timeout WAIT_STATE_TIMEOUT = new Timeout("wait.state", 1000);
70
71 public static final String OUTPUT = Wrap.class.getName() + ".OUTPUT";
72 private static DefaultWrapper theWrapper = new DefaultWrapper(Environment.getEnvironment());
73
74 static {
75 Environment.getEnvironment().initTimeout(WAIT_STATE_TIMEOUT);
76 Environment.getEnvironment().initOutput(OUTPUT, TestOut.getNullOutput());
77 Environment.getEnvironment().initTimeout(Mouse.CLICK);
78 Environment.getEnvironment().initTimeout(Drag.BEFORE_DRAG_TIMEOUT);
79 Environment.getEnvironment().initTimeout(Drag.BEFORE_DROP_TIMEOUT);
80 Environment.getEnvironment().initTimeout(Drag.IN_DRAG_TIMEOUT);
81 Environment.getEnvironment().initTimeout(Keyboard.PUSH);
82 }
83
84 public static DefaultWrapper getWrapper() {
85 return theWrapper;
86 }
87 CONTROL node;
88 Environment env;
89
90 /**
91 * Fur null source.
92 *
93 * @see org.jemmy.env.Environment
94 * @param env The environment
95 */
96 protected Wrap(Environment env) {
97 this.env = env;
98 node = null;
99 fillTheProps(false);
100 }
101
102 /**
103 *
119 return env;
120 }
121
122 public void setEnvironment(Environment env) {
123 this.env = env;
124 }
125
126 /**
127 *
128 * @return The encapsulated object
129 */
130 @Property(CONTROL_PROP_NAME)
131 public CONTROL getControl() {
132 return node;
133 }
134
135 /**
136 * Return default point to click, drag. This implementation returns the
137 * center must be overriden if something different is desired.
138 *
139 * @return the default click point
140 */
141 @Property(CLICKPOINT_PROP_NAME)
142 public Point getClickPoint() {
143 return new Point(getScreenBounds().width / 2, (getScreenBounds().height / 2));
144 }
145
146 /**
147 * Returns control bounds in screen coordinates. These bounds could include
148 * parts that are covered by other controls or clipped out by parent
149 * components. If the control is not shown {@linkplain
150 * JemmyException JemmyException} will be thrown.
151 *
152 * @return control bounds in screen coordinates.
153 * @throws JemmyException if the control is not visible
154 */
155 @Property(BOUNDS_PROP_NAME)
156 public abstract Rectangle getScreenBounds();
157
158 /**
159 * Transforms point in local control coordinate system to screen
160 * coordinates.
161 *
162 * @param local the local coordinate
163 * @return a absolute translated point
164 * @see #toLocal(org.jemmy.Point)
165 */
166 public Point toAbsolute(Point local) {
167 Rectangle bounds = getScreenBounds();
168 return local.translate(bounds.x, bounds.y);
169 }
170
171 /**
172 * Transforms point in screen coordinates to local control coordinate
173 * system.
174 *
175 * @param local the local coordinate
176 * @return coordinates which should be used for mouse operations.
177 * @see #toAbsolute(org.jemmy.Point)
178 */
179 public Point toLocal(Point local) {
180 Rectangle bounds = getScreenBounds();
181 return local.translate(-bounds.x, -bounds.y);
182 }
183
184 /**
185 * Captures the screen area held by the component. ImageFactory performs the
186 * actual capturing.
187 *
188 * @return TODO find a replacement
189 */
190 public Image getScreenImage() {
191 Rectangle bounds = getScreenBounds();
192 return getScreenImage(new Rectangle(0, 0, bounds.width, bounds.height));
193 }
194
195 /**
196 * Captures portion of the screen area held by the component. ImageFactory
197 * performs the actual capturing.
198 *
199 * @param rect Part of the control to capture
200 * @return TODO find a replacement
201 */
202 public Image getScreenImage(Rectangle rect) {
203 if (getEnvironment().getImageCapturer() == null) {
204 throw new JemmyException("Image capturer is not specified.");
205 }
206 return getEnvironment().getImageCapturer().capture(this, rect);
207 }
208
209 /**
210 * Waits for a portion of image to be exact the same as the parameter.
211 *
212 * @see Wrap#as(java.lang.Class)
213 * @param golden the image to match against
214 * @param rect A portion of control to compare.
215 * @param resID ID of a result image to save in case of failure. No image
216 * saved if null.
217 * @param diffID ID of a diff image to save in case of failure. No image
218 * saved if null.
219 */
220 public void waitImage(final Image golden, final Rectangle rect, String resID, String diffID) {
221 try {
222 waitState(new State<Object>() {
223
224 public Object reached() {
225 return (getScreenImage(rect).compareTo(golden) == null) ? true : null;
226 }
227
228 @Override
229 public String toString() {
230 return "Control having expected image";
231 }
232 });
233 } catch (TimeoutExpiredException e) {
234 if (diffID != null) {
235 getEnvironment().getOutput(OUTPUT).println("Saving difference to " + diffID);
236 getScreenImage(rect).compareTo(golden).save(diffID);
237 }
238 throw e;
239 } finally {
240 if (resID != null) {
241 getEnvironment().getOutput(OUTPUT).println("Saving result to " + resID);
242 getScreenImage(rect).save(resID);
243 }
244 }
245 }
246
247 /**
248 * Waits for image to be exact the same as the parameter.
249 *
250 * @see Wrap#as(java.lang.Class)
251 * @param golden the image to match against
252 * @param resID ID of a result image to save in case of failure. No image
253 * saved if null.
254 * @param diffID ID of a diff image to save in case of failure. No image
255 * saved if null.
256 */
257 public void waitImage(final Image golden, String resID, String diffID) {
258 Rectangle bounds = getScreenBounds();
259 waitImage(golden, new Rectangle(0, 0, bounds.width, bounds.height), resID, diffID);
260 }
261
262 /**
263 * TODO javadoc
264 *
265 * @param <V> the states type
266 * @param state the state
267 * @param value the state value
268 * @return last returned State value
269 * @throws TimeoutExpiredException in case the wait is unsuccessful.
270 */
271 public <V> V waitState(State<V> state, V value) {
272 return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, state);
273 }
274
275 /**
276 * @param <V> the states type
277 * @param state the state
278 * @return last returned State value
279 * @throws TimeoutExpiredException in case the wait is unsuccessful.
280 */
281 public <V> V waitState(State<V> state) {
282 return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureState(state);
283 }
284
285 /**
286 * ***********************************************************************
287 */
288 /*
289 * INTERFACES
290 */
291 /**
292 * ***********************************************************************
293 */
294 private Method findAsMethod(Class<? extends ControlInterface> interfaceClass, Class type) {
295 while (type != null) {
296 for (Method m : getClass().getMethods()) {
297 As as = m.getAnnotation(As.class);
303 }
304 return m;
305 }
306 }
307 type = type.getSuperclass();
308 }
309 return null;
310 }
311
312 /**
313 * Checks if the control could be treated as a ControlInterface. If it is,
314 * <code>Wrap#as(java.lang.Class)</code> will be called. This implementation
315 * checks whether the class implements the necessary interface. It also
316 * works for root interfaces such as
317 * <code>MouseTarget</code> and
318 * <code>KeyTarget</code>, which implementations are encapsulated. If some
319 * other functionality is desired, must be overriden together with
320 * <code>as(java.lang.Class)</code>
321 *
322 * @see Wrap#is(java.lang.Class)
323 * @param <INTERFACE> the control interface
324 * @param interfaceClass the interface class
325 * @return <code>true</code> if the control is an ControlInterface,
326 * <code>false</code> otherwise
327 */
328 public <INTERFACE extends ControlInterface> boolean is(Class<INTERFACE> interfaceClass) {
329 if (interfaceClass.isInstance(this)) {
330 return true;
331 }
332 return findAsMethod(interfaceClass, Void.class) != null;
333 }
334
335 /**
336 * Checks if the control could be treated as a parametrized
337 * ControlInterface. If it is,
338 * <code>Wrap#as(java.lang.Class, java.lang.Class)</code> will be called.
339 * This implementation checks whether the class implements the necessary
340 * interface. It also works for root interfaces such as
341 * <code>MouseTarget</code> and
342 * <code>KeyTarget</code>, which implementations are encapsulated. If some
343 * other functionality is desired, must be overriden together with
344 * <code>as(java.lang.Class)</code>
345 *
346 * @see Wrap#is(java.lang.Class)
347 * @param <TYPE> the type interface
348 * @param <INTERFACE> the control interface
349 * @param interfaceClass the interface class
350 * @param type The parameter class.
351 * @return <code>true</code> if the control is an ControlInterface,
352 * <code>false</code> otherwise
353 */
354 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> boolean is(Class<INTERFACE> interfaceClass, Class<TYPE> type) {
355 if (interfaceClass.isInstance(this)) {
356 if (interfaceClass.cast(this).getType().isAssignableFrom(type)) {
357 return true;
358 }
359 }
360 return findAsMethod(interfaceClass, type) != null;
361 }
362
363 private Object callAsMethod(Class<? extends ControlInterface> interfaceClass, Class type) {
364 Method m = findAsMethod(interfaceClass, type);
365 if (m != null) {
366 try {
367 if (m.getParameterTypes().length == 0) {
368 return m.invoke(this);
369 } else if (m.getParameterTypes().length == 1) {
370 return m.invoke(this, !type.equals(Void.class) ? type : Object.class);
371 } else {
372 throw new InterfaceException(this, interfaceClass);
373 }
374 } catch (IllegalAccessException ex) {
375 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
376 } catch (IllegalArgumentException ex) {
377 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
378 } catch (InvocationTargetException ex) {
379 throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this);
380 }
381 }
382 return null;
383 }
384
385 /**
386 * Returns an implementation of interface associated with this object. First
387 * it checks
388 *
389 * @see Wrap#is(java.lang.Class)
390 * @param <INTERFACE> the control interface
391 * @param interfaceClass the interface class
392 * @return the control interface instance
393 */
394 public <INTERFACE extends ControlInterface> INTERFACE as(Class<INTERFACE> interfaceClass) {
395 if (interfaceClass.isInstance(this)) {
396 return interfaceClass.cast(this);
397 }
398
399 Object res = callAsMethod(interfaceClass, Void.class);
400 if (res != null) {
401 return (INTERFACE) res;
402 }
403
404 throw new InterfaceException(this, interfaceClass);
405 }
406
407 /**
408 * Returns an implementation of interface associated with the object.
409 *
410 * @see Wrap#is(java.lang.Class)
411 * @param <TYPE> the type interface
412 * @param <INTERFACE> the control interface
413 * @param interfaceClass the interface class
414 * @param type The parameter class.
415 * @return the type control instance
416 */
417 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> INTERFACE as(Class<INTERFACE> interfaceClass, Class<TYPE> type) {
418 if (interfaceClass.isInstance(this)) {
419 if (interfaceClass.cast(this).getType().isAssignableFrom(type)) {
420 return interfaceClass.cast(this);
421 }
422 }
423
424 Object res = callAsMethod(interfaceClass, type);
425 if (res != null) {
426 return (INTERFACE) res;
427 }
428
429 throw new InterfaceException(this, interfaceClass);
430 }
431 /**
432 * ***********************************************************************
433 */
434 /*
435 * INPUT
436 */
437 /**
438 * ***********************************************************************
439 */
440 private Mouse mouse = null;
441 private Drag drag = null;
442 private Keyboard keyboard = null;
443
444 /**
445 * A shortcut to <code>as(MouseTarget.class).mouse()</code>
446 *
447 * @return the mouse target
448 */
449 @As(Mouse.class)
450 public Mouse mouse() {
451 if (mouse == null) {
452 mouse = getEnvironment().getInputFactory().create(this, Mouse.class);
453 }
454 return mouse;
455 }
456
457 /**
458 * A shortcut to <code>as(MouseTarget.class).drag()</code>
459 *
460 * @return the drag target
461 */
462 @As(Drag.class)
463 public Drag drag() {
464 if (drag == null) {
465 drag = getEnvironment().getInputFactory().create(this, Drag.class);
466 }
467 return drag;
468 }
469
470 /**
471 * A shortcut to <code>as(KeyTarget.class).wrap()</code>
472 *
473 * @return the wrap target
474 */
475 @As(Keyboard.class)
476 public Keyboard keyboard() {
477 if (keyboard == null) {
478 keyboard = getEnvironment().getInputFactory().create(this, Keyboard.class);
479 }
480 return keyboard;
481 }
482 /**
483 * ***********************************************************************
484 */
485 /*
486 * PROPERTIES
487 */
488 /**
489 * ***********************************************************************
490 */
491 private HashMap<String, Object> properties = new HashMap<String, Object>();
492
493 @Property(CONTROL_CLASS_PROP_NAME)
494 public Class<?> getControlClass() {
495 return getControl().getClass();
496 }
497
498 private void fillTheProps(boolean quiet) {
499 properties.clear();
500 properties.put(WRAPPER_CLASS_PROP_NAME, getClass());
501 readAnnotationProps(quiet);
502 readControlProps(quiet);
503 }
504
505 private void readControlProps(boolean quiet) {
506 Class<?> cls = getClass();
507 do {
508 if (cls.isAnnotationPresent(FieldProperties.class)) {
509 for (String s : cls.getAnnotation(FieldProperties.class).value()) {
510 Object value;
511 try {
512 value = getFieldProperty(s);
595 }
596 }
597 }
598 return null;
599 }
600
601 private Object getProperty(Object object, Method m) {
602 Property prop = m.getAnnotation(Property.class);
603 try {
604 return m.invoke(object);
605 } catch (IllegalAccessException ex) {
606 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
607 } catch (IllegalArgumentException ex) {
608 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
609 } catch (InvocationTargetException ex) {
610 throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this);
611 }
612 }
613
614 /**
615 * Get property of the wrapped object. Uses first available from
616 * <ol>
617 * <li>methods annotated by <code>org.jemmy.control.Property</code></li>
618 * <li>wrapped object methods listed in <code>org.jemmy.control.MethodProperties</code></li>
619 * <li>wrapped object fields listed in <code>org.jemmy.control.FieldProperties</code></li>
620 * </ol>
621 *
622 * @param name property name
623 * @throws JemmyException if no property found
624 * @see Property
625 * @see MethodProperties
626 * @see FieldProperties
627 * @return property value
628 */
629 public Object getProperty(String name) {
630 if (WRAPPER_CLASS_PROP_NAME.equals(name)) {
631 return getClass();
632 }
633 Method m = getPropertyMethod(this.getClass(), name);
634 if (m != null) {
635 return getProperty(this, m);
636 }
637 if (hasMethodProperty(name)) {
638 return getMethodProperty(name);
639 }
640 if (hasFieldProperty(name)) {
641 return getFieldProperty(name);
642 }
643 throw new JemmyException("No property \"" + name + "\"", this);
644 }
645
646 private Object getInterfaceProperty(Class cls, Object instance, String name) {
647 Method m = getPropertyMethod(cls, name);
648 if (m != null) {
649 return getProperty(instance, m);
650 }
651 throw new JemmyException("No property \"" + name + "\" in interface " + cls.getName(), instance);
652 }
653
654 /**
655 * Get property out of the control interface. Refer to the interface doc to
656 * find out what properties are provided.
657 *
658 * @param <INTERFACE> the control interface
659 * @param name the property name
660 * @param intrfc the interface class
661 * @return the control property
662 */
663 public <INTERFACE extends ControlInterface> Object getProperty(String name, Class<INTERFACE> intrfc) {
664 return getInterfaceProperty(intrfc, as(intrfc), name);
665 }
666
667 /**
668 * Get property out of the control interface. Refer to the interface doc to
669 * find out what properties are provided.
670 *
671 * @param <TYPE> the type interface
672 * @param <INTERFACE> the control interface
673 * @param name the property name
674 * @param intrfc the interface class
675 * @param type The parameter class.
676 * @return the control property
677 */
678 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> Object getProperty(String name, Class<INTERFACE> intrfc, Class<TYPE> type) {
679 return getInterfaceProperty(intrfc, as(intrfc, type), name);
680 }
681
682 /**
683 * Wait for the property
684 * <code>property</code> to get the specified value.
685 * <code>WAIT_STATE_TIMOUT</code> timeout is used
686 *
687 * @param property name of the property being waited for
688 * @param value property value to wait
689 */
690 public void waitProperty(final String property, final Object value) {
691 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
692
693 public Object reached() {
694 return getProperty(property);
695 }
696
697 @Override
698 public String toString() {
699 return "Control having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')";
700 }
701 });
702 }
703
704 /**
705 * Wait for the property
706 * <code>property</code> of control interface to get the specified value.
707 * <code>WAIT_STATE_TIMOUT</code> timeout is used
708 *
709 * @param <INTERFACE> the control interface
710 * @param property the property name
711 * @param intrfc the interface class
712 * @param value the new parameter value
713 */
714 public <INTERFACE extends ControlInterface> void waitProperty(final String property, final Class<INTERFACE> intrfc, final Object value) {
715 Object instance = as(intrfc);
716 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
717
718 public Object reached() {
719 return getProperty(property, intrfc);
720 }
721
722 @Override
723 public String toString() {
724 return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property, intrfc) + "')";
725 }
726 });
727 }
728
729 /**
730 * Wait for the property
731 * <code>property</code> of control interface to get the specified value.
732 * <code>WAIT_STATE_TIMOUT</code> timeout is used
733 *
734 * @param <TYPE> the type interface
735 * @param <INTERFACE> the control interface
736 * @param property the property name
737 * @param intrfc the interface class
738 * @param type the parameter class.
739 * @param value the new parameter value
740 */
741 public <TYPE, INTERFACE extends TypeControlInterface<TYPE>> void waitProperty(final String property, final Class<INTERFACE> intrfc, final Class<TYPE> type, final Object value) {
742 getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State<Object>() {
743
744 public Object reached() {
745 return getProperty(property, intrfc, type);
746 }
747
748 @Override
749 public String toString() {
750 return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')";
751 }
752 });
753 }
754
755 public boolean hasFieldProperty(String name) {
756 Class<?> cls = getClass();
757 do {
758 if (cls.isAnnotationPresent(FieldProperties.class)) {
759 FieldProperties props = cls.getAnnotation(FieldProperties.class);
760 if (contains(props.value(), name)) {
761 return true;
762 }
763 }
764 } while ((cls = cls.getSuperclass()) != null);
765 return false;
766 }
767
768 public boolean hasMethodProperty(String name) {
769 Class<?> cls = getClass();
770 do {
771 if (cls.isAnnotationPresent(MethodProperties.class)) {
772 MethodProperties props = cls.getAnnotation(MethodProperties.class);
773 if (contains(props.value(), name)) {
774 return true;
775 }
776 }
777 } while ((cls = cls.getSuperclass()) != null);
778 return false;
779 }
780
781 private boolean contains(String[] values, String name) {
782 for (int i = 0; i < values.length; i++) {
783 if (name.equals(values[i])) {
784 return true;
785 }
786
787 }
788 return false;
789 }
790
791 public Object getFieldProperty(final String name) {
792 if (!hasFieldProperty(name)) {
793 throw new JemmyException("No \"" + name + "\" field property specified on " + getClass().getName());
794 }
795 GetAction action = new GetAction() {
796
797 @Override
798 public void run(Object... parameters) throws Exception {
799 setResult(getControl().getClass().getField(name).get(getControl()));
800 }
801 };
802 Object result = action.dispatch(env);
803 if (action.getThrowable() != null) {
804 throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this);
805 }
806 return result;
807 }
808
809 public Object getMethodProperty(final String name) {
810 if (!hasMethodProperty(name)) {
811 throw new JemmyException("No \"" + name + "\" method property specified on " + getClass().getName());
812 }
813 GetAction action = new GetAction() {
814
815 @Override
816 public void run(Object... parameters) throws Exception {
817 setResult(getControl().getClass().getMethod(name).invoke(getControl()));
818 }
819
820 @Override
821 public String toString() {
822 return "Getting property \"" + name + "\" on " + getClass().getName();
823 }
824 };
825 Object result = action.dispatch(env);
826 if (action.getThrowable() != null) {
827 throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this);
828 }
829 return result;
830 }
831
832 public <P> P getProperty(Class<P> valueClass, String name) {
833 return valueClass.cast(getProperty(name));
834 }
835
836 /**
837 * Returns a a map of all known controls properties including values from
838 * methods marked by
839 * <code>@Property</code> and values of methods/field from
840 * <code>@MethodProperties</code>/
841 * <code>FieldProperties</code> correspondingly.
842 *
843 * @return a map of properties
844 * @throws RuntimeException should there be an exception thrown while
845 * getting a property
846 */
847 public HashMap<String, Object> getProperties() {
848 fillTheProps(false);
849 return properties;
850 }
851
852 /**
853 * Returns a a map of all controls properties which is possible to obtain.
854 * Similar to
855 * <code>getProperties()</code> only exception is swallowed should there be
856 * an exception thrown while getting a property.
857 *
858 * @return a map of properties which were possible to obtain.
859 */
860 public HashMap<String, Object> getPropertiesQiuet() {
861 fillTheProps(true);
862 return properties;
863 }
864 }
|