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. Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package org.jemmy.control;
  26 
  27 import java.lang.reflect.InvocationTargetException;
  28 import java.lang.reflect.Method;
  29 import java.util.HashMap;
  30 import org.jemmy.JemmyException;
  31 import org.jemmy.Point;
  32 import org.jemmy.Rectangle;
  33 import org.jemmy.TimeoutExpiredException;
  34 import org.jemmy.action.GetAction;
  35 import org.jemmy.env.Environment;
  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      *
 152      * @see org.jemmy.env.Environment
 153      * @param env The environment
 154      * @param node The encapsulated object
 155      */
 156     protected Wrap(Environment env, CONTROL node) {
 157         this.env = env;
 158         this.node = node;
 159     }
 160 
 161     /**
 162      *
 163      * @see org.jemmy.env.Environment
 164      * @return environment instance used by this
 165      */
 166     public Environment getEnvironment() {
 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);
 348                 Class returnType = m.getReturnType();
 349                 if (as != null && interfaceClass.isAssignableFrom(returnType) && as.value().equals(type)) {
 350                     if (m.getParameterTypes().length > 0 && type.equals(Void.class)
 351                             || m.getParameterTypes().length > 1 && !type.equals(Void.class)) {
 352                         throw new IllegalStateException("wrong number of parameters in an @As method");
 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);
 568                     } catch (Exception e) {
 569                         getEnvironment().getOutput().printStackTrace(e);
 570                         value = e.toString();
 571                         if (!(e instanceof JemmyException) && !quiet) {
 572                             throw new JemmyException("Exception while getting property \"" + s + "\"", e);
 573                         }
 574                     }
 575                     properties.put(s, value);
 576                 }
 577             }
 578             if (cls.isAnnotationPresent(MethodProperties.class)) {
 579                 for (String s : cls.getAnnotation(MethodProperties.class).value()) {
 580                     Object value;
 581                     try {
 582                         value = getMethodProperty(s);
 583                     } catch (Exception e) {
 584                         getEnvironment().getOutput().printStackTrace(e);
 585                         value = e.toString();
 586                         if (!(e instanceof JemmyException) && !quiet) {
 587                             throw new JemmyException("Exception while getting property \"" + s + "\"", e);
 588                         }
 589                     }
 590                     properties.put(s, value);
 591                 }
 592             }
 593         } while ((cls = cls.getSuperclass()) != null);
 594     }
 595 
 596     private void addAnnotationProps(Class cls, boolean quiet) {
 597         for (Method m : cls.getMethods()) {
 598             if (m.isAnnotationPresent(Property.class)) {
 599                 String name = m.getAnnotation(Property.class).value();
 600                 if (!properties.containsKey(name)) {
 601                     Object value;
 602                     try {
 603                         value = getProperty(this, m);
 604                     } catch (Exception e) {
 605                         if (quiet) {
 606                             getEnvironment().getOutput().printStackTrace(e);
 607                             value = e.toString();
 608                         } else {
 609                             throw new JemmyException("Exception while getting property \"" + name + "\"", e);
 610                         }
 611                     }
 612                     properties.put(name, value);
 613                 }
 614             }
 615         }
 616     }
 617 
 618     private void readAnnotationProps(boolean quiet) {
 619         Class cls = getClass();
 620         do {
 621             addAnnotationProps(cls, quiet);
 622         } while ((cls = cls.getSuperclass()) != null);
 623         for (Class intf : getClass().getInterfaces()) {
 624             addAnnotationProps(intf, quiet);
 625         }
 626     }
 627 
 628     private void checkPropertyMethod(Method m) {
 629         if (m.getParameterTypes().length > 0) {
 630             throw new JemmyException("Method marked by @Property must not have parameters: "
 631                     + m.getDeclaringClass().getName() + "." + m.getName());
 632         }
 633     }
 634 
 635     private Method getPropertyMethod(Class cls, String name) {
 636         Class scls = cls;
 637         do {
 638             for (Method m : scls.getMethods()) {
 639                 if (m.isAnnotationPresent(Property.class) && m.getAnnotation(Property.class).value().equals(name)) {
 640                     checkPropertyMethod(m);
 641                     return m;
 642                 }
 643             }
 644         } while ((scls = scls.getSuperclass()) != null);
 645         for (Class intf : cls.getInterfaces()) {
 646             for (Method m : intf.getMethods()) {
 647                 if (m.isAnnotationPresent(Property.class) && m.getAnnotation(Property.class).value().equals(name)) {
 648                     checkPropertyMethod(m);
 649                     return m;
 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 }