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