--- /dev/null 2017-11-08 15:39:21.000000000 -0800 +++ new/core/JemmyCore/build.xml 2017-11-08 15:39:21.000000000 -0800 @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${testlist} + + + + + + + + + + + + + + + + + + + + + + + + + + + + --- /dev/null 2017-11-08 15:39:21.000000000 -0800 +++ new/core/JemmyCore/doc/Jemmy3Lookup.html 2017-11-08 15:39:21.000000000 -0800 @@ -0,0 +1,153 @@ + + + + + + Jemmy lookup principles + + + + + + + +

Jemmy lookup principles

+

The document describes principles of Jemmy lookup API. Such +principles are the same for any extension built on JemmyCore +for any particular component library. The idea here is to provide +searching capabilities so that +

+ +

Search criteria

+

Search criteria specified by implementing LookupCriteria +interface. There are several implementations of the criteria in +JemmyCore which are abstract. +

+

Example: +

+
        public class ByTextSearch<T extends JTextComponent> implements LookupCriteria<T> {
+            String text;
+            public ByTextSearch(String text) {
+                this.text = text;
+            }
+            public boolean check(T control) {
+                return control.getText().equals(text);
+            }
+        }
+    

+Naturally, any kind of custom search criteria could be specified my +creating an implementation if the interface. +

+

Search functionality

+

Parent

+

Parent interface represents the start point for a component +lookup. Parent could be a

+ +

from the lookup perspective, parent only is able to get an access +to a Lookup imstance.

+

Lookup

+

The search capabilities are presented by Lookup +interface. Most importantly the interface defines methods to narrow +the search with another search criteria and optionally control type, +which (the methods) return an instance on the same interface. That +allows to implement any number of search criteria. +

+

Example: +

+
        button = ... .wrap(0);
+        frame.lookup(CoordinateLookup.leftOf(button)).
+        lookup(JTextField.class, new Any<JTextField>()).
+        ...;
+    

+Besides that, the interface defines methods to get the control, to +wrap it, to get the number of controls found, etc. . +

+

AbstractLookup

+

This is an internal yet very important class. It implements +creating the sub-lookups. This particular implementation does it in a +way that the actual component hierarchy is not requested up until the +moment it has to be. It's only when there is a question on the actual +control list is asked, the hierarchy is explored. +

+

Example. This does not query the AWT hierarchy: +

+
        import org.jemmy.awt.FrameOperator;
+        ...
+        FrameOperator frame = ...
+        Lookup<JTextField> lookup = frame.lookup(JTextField.class, new TrueLookup<JTextField>());
+    

+These do: +

+
        lookup.get(0);
+        lookup.wrap(0);
+        lookup.size();
+        lookup.wait(2);
+    

+AbstractLookup does not implement the functionality of +getting the control list, which is implemented by next two classes: +

+

HierarchyLookup and PlainLookup

+

As it is clear from the names, these classes provide logic to deal +with hierarchical control structure and plain control list, +correspondentelly. Doing so, the implementation rely on two other +interfaces which specific to the component library: ControlHierarchy +and ControlLookup. +

+

Accessing/wrapping the controls

+

Two methods get(int) and wrap(int) return +the control itself and the wrapper of it. The type of the returned +control is the CONTROL type parameter of Lookup<CONTROL> +interface. Wrap type is Wrap<CONTROL>, hence +there is no need to cast the results to get the control itself. There +is no need to cast the wrapper either, but this is described in a +separate document: Jemmy control interfaces +

+ + + --- /dev/null 2017-11-08 15:39:21.000000000 -0800 +++ new/core/JemmyCore/doc/Jemmy3UserTutorial.html 2017-11-08 15:39:21.000000000 -0800 @@ -0,0 +1,143 @@ + + + + + Jemmy 3 user tutorial + + + + +

Jemmy 3 user tutorial

+ + Note This tutorial is complementary to javadoc - it only describes principles of Jemmy API usage - not the API itself.

+ + Jemmy 3 have several fundamental principles which everybody should get himself familiar with before starting test implementation.

+ + Relations to Jemmy2. If you are familiar with the Jemmy 2 API (TBD link), you would find this totally different. Everything is implemented in a different way and there is a good reason for that (below). During the tutorial, I will sometimes compare Jemmy 3 & 2 design principles. If you are not familiar with Jemmy2, just ignore those paragraphs.

+ + Jemmy3, firstly, consist of Jemmy Core module which provide functionality not dependent on any particular UI library weather AWT or Swing or anything else. The Jemmy Core uses a couple of classes from AWT, such as java.awt.Robot to perform operations with components and org.jemmy.Point, java.awt.Rectangle to mention screen coordinates. Even Robot, however, in the future could be abstracted from the core so that mobile testing is possible.

+ + The three main aspects of UI testing are: + + +

Lookup

+ + Jemmy2 So, in Jemmy2, the lookup functionality was implemented through "operator"'s constructor and also not-so-convenient static methods. This (the constructors) is acceptable and proven to be good approach for every particular UI component library. It is possible to implement the lookup functionality in a similar way with Jemmy 3. The "generic" Jemmy 3 approach is quite different, however.

+ + So, how do you go about finding a control to deal with. First you have to know some Parent<SomeControlType> which would be some class which is aware of a set of controls of SomeControlType type (for example, javax.swing.JButton in Swing or javafx.scene.control.Button in JavaFX). The set could either be hierarchical, which is typically the case - take AWT or Scenegraph or just about anything, or a plain structure - it does not matter from the usage point of view, because Parent instance knows what is it dealing with.

+ + Such Parents could be (TBD throw in some examples)

+ + Having the Parent, you could proceed to getting Lookup<SomeControlType> instance by calling lookup(LookupCriteria<SomeControlType>) method. +
+        someParent.lookup(new SomeLookupCriteria<SomeControlType>())// this gives an instance of Lookup<SomeControlType>
+        
+ + Jemmy2 LookupCriteria used to be called ComponentChooser.

+ + So, out of those components which someParent is aware about, you, so far picked those which pass the SomeLookupCriteria. Similarly, you could narrow the search with a LookupCriteria and the control type: +
+        someParent.lookup(Class<SomeControlSubType>new SomeLookupCriteria<SomeControlSubType>())// this gives an instance of Lookup<SomeControlSubType>
+        
+ where SomeControlSubType extends SomeControlType

+ + Lookup<SomeControlType> represents a set of controls, so you could proceed directly to examining one of those by index. You could use get(int) method to get one of this. It is important to notice that the get(int) method returns an instance of SomeControlType, so you do not need to cast it.

+ + You could also check how many are there (size()) or ask the lookup to wait for at least a certain number of them (wait(int)).

+ + You could also get the control wrapped for you (wrap(int)) so that you could use some test logic of dealing with the control. The wrapping will be described in details later in the tutorial.

+ + Perhaps, the most important feature of Lookup is that it itself is a Parent<SomeControlType> (or Parent<SomeControlSubType>). Thus, you could proceed with narrowing down the search further with lookup(LookupCriteria) method (or lookup(Class, LookupCriteria)).

+ + So, finally, looking up for a control gets to ... +
+        someParent.lookup([Class, ]LookupCriteria)[.lookup([Class, ]LookupCriteria) ...].(get(int)|wrap()|size()|wait(int))
+        
+ + It is also worthy to note that, if the default abstract Jemmy Core's implementation of Lookup is used, the actual component hierarchy is not examined up to the moment it has to be. That is, lookup(...) methods itself do not go through the list of controls trying to apply the criteria, when they called. It is only when one of the get(int)|wrap()|size()|wait(int) methods called, the hierarchy is explored. Hence, there is neither memory spend for creating intermediate lists nor performance is wasted. For more info, check the Jemmy developers tutorial.

+ +

Wrapping

+ + Now, finding out the control is a necessary step to do. However, in order to deal with the control - to simulate user input, such as text typing, mouse pressing whatnot some code must be used, which is implemented in some other Java class. The most natural way, perhaps, is to "wrap" the actual control which some class which knows how to perform operations on the control type.

+ + Jemmy2 Such classes were the "operators". Wrapping happened naturally as the lookup was implemented through the operator constructors.

+ + In Jemmy 3, the wrapping happens when you call the wrap(int) methods of a lookup. Naturally, the wrappers are UI library specific, so, Jemmy Core could not possible be aware about them. The library specific Jemmy Core extension is taking care about supplying them. For more info, check the Jemmy developers tutorial (TBD).

+ + All of the wrapper classes extend Wrap<CONTROL> class, which, itself, provides core functionality to perform mouse and keyboard operations on the control.

+ +

Interfaces

+ + Mouse and keyboard operations are only the basis for user interaction simulation. It is also the only functionality which is applicable to every control, independently of its type. There is much more to user input than this. Good example is text typing. The test developer would expect something in a way of type(String) method for a text field of any kind. Naturally, Wrap class itself does not have anything like this.

+ + That's where the ControlInterface concept comes to the game. Having had a wrapper, gotten by wrap(int) method, you could proceed to treating is as MyInterface (which should be a class/interface implementing/extending ControlInterface - just to have some control over it): +
+        myWrappedControl.as(MyInterface.class) /// this would be an instance of MyInterface
+        
+ + Please consult Jemmy developers tutorial (TBD) on the ways to implement as method.

+ + You could verify in the test code if the wrapper actually could be used as MyInterface by calling myWrappedControl.is(MyInterface.class), however generally it is up to Jemmy Core extension developer which interfaces the wrapper implements.

+ + Getting back to our example of the text typing, the test developer would be able to use code like this: +
+        myWrappedControl.as(Text.class).type("some new text");
+        
+ + One side effect of the interfaces is that the implementations of wrappers could be private or package private, as the wrapper classes are not accessed directly from the test.

+ +

Parameterised interfaces

+ + Some interfaces could itself be parameterised by a type. One example of such is Parent<CONTROL> which is itself an interface.

+ + To decrease amount of necessary casting you could use is(Class interfaceClass, Class parameterClass) and as(Class interfaceClass, Class parameterClass) methods. It could look like this: +
+        someParent.lookup(new SomeCriteria<SomeControlType>()).wrap(0).as(Parent.class, SomeOtherControlType.class).lookup(new SomeOtherCriteria<SomeOtherControlType>).get(0) // this would give an instance of SomeOtherControlType which leaves within some instance of SomeControlType.
+        
+ +

Waiting and timing

+ + It is important to understand that just about any call in Jemmy involves some time checking. When you call wait(int) on a lookup, it waits for a given number of controls to exist which pass the criteria, when you call get(int) or wrap(int), it first calls wait(int) method to ensure that big enough number of controls are there.

+ + In addition, when you call just about any operation on a control, it has its own time limit to be completed within.

+ + Should you need to implement your own waiting or time check of any kind, it is a good idea to use Waiter supplied by Jemmy.

+ + If the time expires, TimeoutExpiredException is thrown. That allows not to worry about time checking within the test itself. If something goes wrong, test will simply fail with the exception. It also allows to check the negative scenarios by catching the exception.

+ + All the timeouts are specified through Environment instance.

+ +

Environment

+ + Any Parent or Lookup always have an instance of Environment associated with it. Whenever Wrap is created, Environment is cloned (i.e. child Environment is created) and assigned to the Wrap.

+ + Environment hierarchy is build the way that you could always change an environment instance not worrying about other parts of the test behaving differently. All the child environments for the one you are changing will behave differently, which gives you the power to customise behaviour of single Wrap (by changing its environment) or the while hierarchy (by changing Environment associated with a Parent).

+ + + + --- /dev/null 2017-11-08 15:39:22.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/Dimension.java 2017-11-08 15:39:22.000000000 -0800 @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy; + + +import java.io.Serializable; + + +/** + * Replacement for java.awt.Dimension + * @author Alexander Kouznetsov + */ +public class Dimension implements Serializable { + + /** + * The width dimension; negative values can be used. + * + * @serial + * @see #getSize + * @see #setSize + */ + public int width; + + /** + * The height dimension; negative values can be used. + * + * @serial + * @see #getSize + * @see #setSize + */ + public int height; + + /* + * JDK 1.1 serialVersionUID + */ + private static final long serialVersionUID = 4723952579491349524L; + + /** + * Creates an instance of Dimension with a width + * of zero and a height of zero. + */ + public Dimension() { + this(0, 0); + } + + /** + * Creates an instance of Dimension whose width + * and height are the same as for the specified dimension. + * + * @param d the specified dimension for the + * width and + * height values + */ + public Dimension(Dimension d) { + this(d.width, d.height); + } + + /** + * Constructs a Dimension and initializes + * it to the specified width and specified height. + * + * @param width the specified width + * @param height the specified height + */ + public Dimension(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Constructs a Dimension and initializes + * it to the specified width and specified height. All {@code double} + * values are rounded and stored as {@code int} values. + * + * @param width the specified width + * @param height the specified height + */ + public Dimension(double width, double height) { + this.width = (int) Math.round(width); + this.height = (int) Math.round(height); + } + + /** + * {@inheritDoc} + * @return + */ + public double getWidth() { + return width; + } + + /** + * {@inheritDoc} + * @return + */ + public double getHeight() { + return height; + } + + /** + * Sets the size of this Dimension object to + * the specified width and height in double precision. + * Note that if width or height + * are larger than Integer.MAX_VALUE, they will + * be reset to Integer.MAX_VALUE. + * + * @param width the new width for the Dimension object + * @param height the new height for the Dimension object + */ + public void setSize(double width, double height) { + this.width = (int) Math.ceil(width); + this.height = (int) Math.ceil(height); + } + + /** + * Gets the size of this Dimension object. + * @return the size of this dimension, a new instance of + * Dimension with the same width and height + * @see #setSize + */ + public Dimension getSize() { + return new Dimension(width, height); + } + + /** + * Sets the size of this Dimension object to the specified size. + * @param d the new size for this Dimension object + * @see Dimension#getSize + */ + public void setSize(Dimension d) { + setSize(d.width, d.height); + } + + /** + * Sets the size of this Dimension object + * to the specified width and height. + * @param width the new width for this Dimension object + * @param height the new height for this Dimension object + * @see Dimension#getSize + */ + public void setSize(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Checks whether two dimension objects have equal values. + * @param obj + * @return + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Dimension) { + Dimension d = (Dimension)obj; + return (width == d.width) && (height == d.height); + } + return false; + } + + /** + * Returns the hash code for this Dimension. + * + * @return a hash code for this Dimension + */ + @Override + public int hashCode() { + int sum = width + height; + return sum * (sum + 1)/2 + width; + } + + /** + * Returns a string representation of the values of this + * Dimension object's height and + * width fields. This method is intended to be used only + * for debugging purposes, and the content and format of the returned + * string may vary between implementations. The returned string may be + * empty but may not be null. + * + * @return a string representation of this Dimension + * object + */ + @Override + public String toString() { + return getClass().getName() + "[width=" + width + ",height=" + height + "]"; + } +} --- /dev/null 2017-11-08 15:39:22.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/JemmyException.java 2017-11-08 15:39:22.000000000 -0800 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy; + + +/** + * + * Parent of all Jemmy exceptions. + * Exception can be thrown from inside jemmy methods, + * if some exception occurs from code invoked from jemmy. + * + * @author Alexandre Iline (alexandre.iline@oracle.com), Alexander Kouznetsov (Alexander.Kouznetsov@oracle.com) + */ + +public class JemmyException extends RuntimeException { + + private Object object = null; + + /** + * Constructor. + * @param description An exception description. + */ + public JemmyException(String description) { + super(description); + } + + /** + * Constructor. + * @param description An exception description. + * @param innerException Exception from code invoked from jemmy. + */ + public JemmyException(String description, Throwable innerException) { + super(description, innerException); + } + + /** + * Constructor. + * @param description An exception description. + * @param object Object regarding which exception is thrown. + */ + public JemmyException(String description, Object object) { + this(description, null, object); + } + + /** + * Constructor. + * @param description An exception description. + * @param innerException Exception from code invoked from jemmy. + * @param object Object regarding which exception is thrown. + */ + public JemmyException(String description, Throwable innerException, Object object) { + this(description + " (Object: " + object + ")", innerException); + this.object = object; + } + + /** + * Returns "object" constructor parameter. + * @return the Object value associated with the exception. + */ + public Object getObject() { + return(object); + } +} --- /dev/null 2017-11-08 15:39:22.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/Point.java 2017-11-08 15:39:22.000000000 -0800 @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy; + + +import java.io.Serializable; + + +/** + * Replacement for java.awt.Point + * @author Alexander Kouznetsov + */ +public class Point implements Serializable{ + /** + * The X coordinate of this Point. + * If no X coordinate is set it will default to 0. + * + * @serial + * @see #getLocation() + * @see #move(int, int) + */ + public int x; + + /** + * The Y coordinate of this Point. + * If no Y coordinate is set it will default to 0. + * + * @serial + * @see #getLocation() + * @see #move(int, int) + */ + public int y; + + /* + * JDK 1.1 serialVersionUID + */ + private static final long serialVersionUID = -5276940640259749850L; + + /** + * Constructs and initializes a point at the origin + * (0, 0) of the coordinate space. + */ + public Point() { + this(0, 0); + } + + /** + * Constructs and initializes a point with the same location as + * the specified Point object. + * @param p a point + */ + public Point(Point p) { + this(p.x, p.y); + } + + /** + * Constructs and initializes a point at the specified + * {@code (x,y)} location in the coordinate space. + * @param x the X coordinate of the newly constructed Point + * @param y the Y coordinate of the newly constructed Point + */ + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Constructs and initializes a point at the specified + * {@code (x,y)} location in the coordinate space. All {@code double} + * values are rounded and stored as {@code int} values. + * @param x the X coordinate of the newly constructed Point + * @param y the Y coordinate of the newly constructed Point + */ + public Point(double x, double y) { + this.x = (int) Math.round(x); + this.y = (int) Math.round(y); + } + + /** + * {@inheritDoc} + * @return + */ + public int getX() { + return x; + } + + /** + * {@inheritDoc} + * @return + */ + public int getY() { + return y; + } + + /** + * Returns the location of this point. + * @return a copy of this point, at the same location + * @see org.jemmy.Point#setLocation(org.jemmy.Point) + * @see org.jemmy.Point#setLocation(int, int) + */ + public Point getLocation() { + return new Point(x, y); + } + + /** + * Sets the location of the point to the specified location. + * @param p a point, the new location for this point + * @return + * @see org.jemmy.Point#getLocation + */ + public Point setLocation(Point p) { + setLocation(p.x, p.y); + return this; + } + + /** + * Changes the point to have the specified location. + *

+ * Its behavior is identical with move(int, int). + * @param x the X coordinate of the new location + * @param y the Y coordinate of the new location + * @return self + * @see org.jemmy.Point#getLocation + * @see org.jemmy.Point#move(int, int) + */ + public Point setLocation(int x, int y) { + move(x, y); + return this; + } + + /** + * Sets the location of this point to the specified double coordinates. + * The double values will be rounded to integer values. + * Any number smaller than Integer.MIN_VALUE + * will be reset to MIN_VALUE, and any number + * larger than Integer.MAX_VALUE will be + * reset to MAX_VALUE. + * + * @param x the X coordinate of the new location + * @param y the Y coordinate of the new location + * @return self + * @see #getLocation + */ + public Point setLocation(double x, double y) { + this.x = (int) Math.round(x); + this.y = (int) Math.round(y); + return this; + } + + /** + * Moves this point to the specified location in the + * {@code (x,y)} coordinate plane. This method + * is identical with setLocation(int, int). + * @param x the X coordinate of the new location + * @param y the Y coordinate of the new location + * @return self + */ + public Point move(int x, int y) { + this.x = x; + this.y = y; + return this; + } + + /** + * Translates this point, at location {@code (x,y)}, + * by {@code dx} along the {@code x} axis and {@code dy} + * along the {@code y} axis so that it now represents the point + * {@code (x + dx,y + dy)}. + * + * @param dx the distance to move this point + * along the X axis + * @param dy the distance to move this point + * along the Y axis + * @return self + */ + public Point translate(int dx, int dy) { + this.x += dx; + this.y += dy; + return this; + } + + /** + * + * @param v + * @return self + */ + public Point translate(Vector v) { + this.x = (int) Math.round(x + v.getX()); + this.y = (int) Math.round(y + v.getY()); + return this; + } + /** + * Determines whether or not two points are equal. Two instances of + * Point are equal if the values of their + * x and y member fields, representing + * their position in the coordinate space, are the same. + * @param obj an object to be compared with this Point + * @return true if the object to be compared is + * an instance of Point and has + * the same values; false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Point) { + Point pt = (Point)obj; + return (x == pt.x) && (y == pt.y); + } + return super.equals(obj); + } + + /** + * {@inheritDoc} + * @return + */ + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + this.x; + hash = 89 * hash + this.y; + return hash; + } + + /** + * Returns a string representation of this point and its location + * in the {@code (x,y)} coordinate space. This method is + * intended to be used only for debugging purposes, and the content + * and format of the returned string may vary between implementations. + * The returned string may be empty but may not be null. + * + * @return a string representation of this point + */ + @Override + public String toString() { + return getClass().getName() + "[x=" + x + ",y=" + y + "]"; + } +} --- /dev/null 2017-11-08 15:39:23.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/Rectangle.java 2017-11-08 15:39:23.000000000 -0800 @@ -0,0 +1,982 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy; + + +import java.io.Serializable; + + +/** + * Replacement for java.awt.Rectangle + * @author Alexander Kouznetsov + */ +public class Rectangle implements Serializable { + + /** + * The X coordinate of the upper-left corner of the Rectangle. + * + * @serial + * @see #setLocation(int, int) + * @see #getLocation() + */ + public int x; + + /** + * The Y coordinate of the upper-left corner of the Rectangle. + * + * @serial + * @see #setLocation(int, int) + * @see #getLocation() + */ + public int y; + + /** + * The width of the Rectangle. + * @serial + * @see #setSize(int, int) + * @see #getSize() + */ + public int width; + + /** + * The height of the Rectangle. + * + * @serial + * @see #setSize(int, int) + * @see #getSize() + */ + public int height; + + /* + * JDK 1.1 serialVersionUID + */ + private static final long serialVersionUID = -4345857070255674764L; + + + /** + * Constructs a new Rectangle whose upper-left corner + * is at (0, 0) in the coordinate space, and whose width and + * height are both zero. + */ + public Rectangle() { + this(0, 0, 0, 0); + } + + /** + * Constructs a new Rectangle, initialized to match + * the values of the specified Rectangle. + * @param r the Rectangle from which to copy initial values + * to a newly constructed Rectangle + */ + public Rectangle(Rectangle r) { + this(r.x, r.y, r.width, r.height); + } + + /** + * Constructs a new Rectangle whose upper-left corner is + * specified as + * {@code (x,y)} and whose width and height + * are specified by the arguments of the same name. + * @param x the specified X coordinate + * @param y the specified Y coordinate + * @param width the width of the Rectangle + * @param height the height of the Rectangle + */ + public Rectangle(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Constructs a new Rectangle whose upper-left corner + * is at (0, 0) in the coordinate space, and whose width and + * height are specified by the arguments of the same name. + * @param width the width of the Rectangle + * @param height the height of the Rectangle + */ + public Rectangle(int width, int height) { + this(0, 0, width, height); + } + + /** + * Constructs a new Rectangle whose upper-left corner is + * specified by the {@link Point} argument, and + * whose width and height are specified by the + * {@link Dimension} argument. + * @param p a Point that is the upper-left corner of + * the Rectangle + * @param d a Dimension, representing the + * width and height of the Rectangle + */ + public Rectangle(Point p, Dimension d) { + this(p.x, p.y, d.width, d.height); + } + + /** + * Constructs a new Rectangle whose upper-left corner is the + * specified Point, and whose width and height are both zero. + * @param p a Point that is the top left corner + * of the Rectangle + */ + public Rectangle(Point p) { + this(p.x, p.y, 0, 0); + } + + /** + * Constructs a new Rectangle whose top left corner is + * (0, 0) and whose width and height are specified + * by the Dimension argument. + * @param d a Dimension, specifying width and height + */ + public Rectangle(Dimension d) { + this(0, 0, d.width, d.height); + } + + /** + * Constructs a new Rectangle whose upper-left corner is + * specified as {@code (x,y)} and whose width and height + * are specified by the arguments of the same name. All {@code double} + * values are rounded and stored as {@code int} values. + * @param x the specified X coordinate + * @param y the specified Y coordinate + * @param width the width of the Rectangle + * @param height the height of the Rectangle + */ + public Rectangle(double x, double y, double width, double height) { + this((int) Math.round(x), (int) Math.round(y), + (int) Math.round(width), (int) Math.round(height)); + } + + /** + * Returns the X coordinate of the bounding Rectangle in + * double precision. + * @return the X coordinate of the bounding Rectangle. + */ + public double getX() { + return x; + } + + /** + * Returns the Y coordinate of the bounding Rectangle in + * double precision. + * @return the Y coordinate of the bounding Rectangle. + */ + public double getY() { + return y; + } + + /** + * Returns the width of the bounding Rectangle in + * double precision. + * @return the width of the bounding Rectangle. + */ + public double getWidth() { + return width; + } + + /** + * Returns the height of the bounding Rectangle in + * double precision. + * @return the height of the bounding Rectangle. + */ + public double getHeight() { + return height; + } + + /** + * Gets the bounding Rectangle of this Rectangle. + *

+ * @return a new Rectangle, equal to the + * bounding Rectangle for this Rectangle. + * @see #setBounds(Rectangle) + * @see #setBounds(int, int, int, int) + */ + public Rectangle getBounds() { + return new Rectangle(x, y, width, height); + } + + /** + * Sets the bounding Rectangle of this Rectangle + * to match the specified Rectangle. + *

+ * @param r the specified Rectangle + * @see #getBounds + */ + public void setBounds(Rectangle r) { + setBounds(r.x, r.y, r.width, r.height); + } + + /** + * Sets the bounding Rectangle of this + * Rectangle to the specified + * x, y, width, + * and height. + *

+ * @param x the new X coordinate for the upper-left + * corner of this Rectangle + * @param y the new Y coordinate for the upper-left + * corner of this Rectangle + * @param width the new width for this Rectangle + * @param height the new height for this Rectangle + * @see #getBounds + */ + public void setBounds(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + /** + * Sets the bounds of this {@code Rectangle} to the integer bounds + * which encompass the specified {@code x}, {@code y}, {@code width}, + * and {@code height}. + * If the parameters specify a {@code Rectangle} that exceeds the + * maximum range of integers, the result will be the best + * representation of the specified {@code Rectangle} intersected + * with the maximum integer bounds. + * @param x the X coordinate of the upper-left corner of + * the specified rectangle + * @param y the Y coordinate of the upper-left corner of + * the specified rectangle + * @param width the width of the specified rectangle + * @param height the new height of the specified rectangle + */ + public void setRect(double x, double y, double width, double height) { + int newx, newy, neww, newh; + + if (x > 2.0 * Integer.MAX_VALUE) { + // Too far in positive X direction to represent... + // We cannot even reach the left side of the specified + // rectangle even with both x & width set to MAX_VALUE. + // The intersection with the "maximal integer rectangle" + // is non-existant so we should use a width < 0. + // REMIND: Should we try to determine a more "meaningful" + // adjusted value for neww than just "-1"? + newx = Integer.MAX_VALUE; + neww = -1; + } else { + newx = clip(x, false); + if (width >= 0) width += x-newx; + neww = clip(width, width >= 0); + } + + if (y > 2.0 * Integer.MAX_VALUE) { + // Too far in positive Y direction to represent... + newy = Integer.MAX_VALUE; + newh = -1; + } else { + newy = clip(y, false); + if (height >= 0) height += y-newy; + newh = clip(height, height >= 0); + } + + setBounds(newx, newy, neww, newh); + } + // Return best integer representation for v, clipped to integer + // range and floor-ed or ceiling-ed, depending on the boolean. + private static int clip(double v, boolean doceil) { + if (v <= Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } + if (v >= Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) (doceil ? Math.ceil(v) : Math.floor(v)); + } + + /** + * Returns the location of this Rectangle. + *

+ * @return the Point that is the upper-left corner of + * this Rectangle. + * @see #setLocation(Point) + * @see #setLocation(int, int) + */ + public Point getLocation() { + return new Point(x, y); + } + + /** + * Moves this Rectangle to the specified location. + *

+ * @param p the Point specifying the new location + * for this Rectangle + * @see #getLocation + */ + public void setLocation(Point p) { + setLocation(p.x, p.y); + } + + /** + * Moves this Rectangle to the specified location. + *

+ * @param x the X coordinate of the new location + * @param y the Y coordinate of the new location + * @see #getLocation + */ + public void setLocation(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Translates this Rectangle the indicated distance, + * to the right along the X coordinate axis, and + * downward along the Y coordinate axis. + * @param dx the distance to move this Rectangle + * along the X axis + * @param dy the distance to move this Rectangle + * along the Y axis + * @see #setLocation(int, int) + * @see #setLocation(org.jemmy.Point) + */ + public void translate(int dx, int dy) { + int oldv = this.x; + int newv = oldv + dx; + if (dx < 0) { + // moving leftward + if (newv > oldv) { + // negative overflow + // Only adjust width if it was valid (>= 0). + if (width >= 0) { + // The right edge is now conceptually at + // newv+width, but we may move newv to prevent + // overflow. But we want the right edge to + // remain at its new location in spite of the + // clipping. Think of the following adjustment + // conceptually the same as: + // width += newv; newv = MIN_VALUE; width -= newv; + width += newv - Integer.MIN_VALUE; + // width may go negative if the right edge went past + // MIN_VALUE, but it cannot overflow since it cannot + // have moved more than MIN_VALUE and any non-negative + // number + MIN_VALUE does not overflow. + } + newv = Integer.MIN_VALUE; + } + } else { + // moving rightward (or staying still) + if (newv < oldv) { + // positive overflow + if (width >= 0) { + // Conceptually the same as: + // width += newv; newv = MAX_VALUE; width -= newv; + width += newv - Integer.MAX_VALUE; + // With large widths and large displacements + // we may overflow so we need to check it. + if (width < 0) width = Integer.MAX_VALUE; + } + newv = Integer.MAX_VALUE; + } + } + this.x = newv; + + oldv = this.y; + newv = oldv + dy; + if (dy < 0) { + // moving upward + if (newv > oldv) { + // negative overflow + if (height >= 0) { + height += newv - Integer.MIN_VALUE; + // See above comment about no overflow in this case + } + newv = Integer.MIN_VALUE; + } + } else { + // moving downward (or staying still) + if (newv < oldv) { + // positive overflow + if (height >= 0) { + height += newv - Integer.MAX_VALUE; + if (height < 0) height = Integer.MAX_VALUE; + } + newv = Integer.MAX_VALUE; + } + } + this.y = newv; + } + + /** + * Gets the size of this Rectangle, represented by + * the returned Dimension. + *

+ * @return a Dimension, representing the size of + * this Rectangle. + * @see #setSize(Dimension) + * @see #setSize(int, int) + */ + public Dimension getSize() { + return new Dimension(width, height); + } + + /** + * Sets the size of this Rectangle to match the + * specified Dimension. + *

+ * @param d the new size for the Dimension object + * @see #getSize + */ + public void setSize(Dimension d) { + setSize(d.width, d.height); + } + + /** + * Sets the size of this Rectangle to the specified + * width and height. + *

+ * @param width the new width for this Rectangle + * @param height the new height for this Rectangle + * @see #getSize + */ + public void setSize(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Checks whether or not this Rectangle contains the + * specified Point. + * @param p the Point to test + * @return true if the specified Point + * is inside this Rectangle; + * false otherwise. + */ + public boolean contains(Point p) { + return contains(p.x, p.y); + } + + /** + * Checks whether or not this Rectangle contains the + * point at the specified location {@code (x,y)}. + * + * @param x the specified X coordinate + * @param y the specified Y coordinate + * @return true if the point + * {@code (x,y)} is inside this + * Rectangle; + * false otherwise. + */ + public boolean contains(int x, int y) { + return contains(x, y, 1, 1); + } + + /** + * Checks whether or not this Rectangle entirely contains + * the specified Rectangle. + * + * @param r the specified Rectangle + * @return true if the Rectangle + * is contained entirely inside this Rectangle; + * false otherwise + */ + public boolean contains(Rectangle r) { + return contains(r.x, r.y, r.width, r.height); + } + + /** + * Checks whether this Rectangle entirely contains + * the Rectangle + * at the specified location {@code (X,Y)} with the + * specified dimensions {@code (W,H)}. + * @param X the specified X coordinate + * @param Y the specified Y coordinate + * @param W the width of the Rectangle + * @param H the height of the Rectangle + * @return true if the Rectangle specified by + * {@code (X, Y, W, H)} + * is entirely enclosed inside this Rectangle; + * false otherwise. + */ + public boolean contains(int X, int Y, int W, int H) { + int w = this.width; + int h = this.height; + if ((w | h | W | H) < 0) { + // At least one of the dimensions is negative... + return false; + } + // Note: if any dimension is zero, tests below must return false... + int xx = this.x; + int yy = this.y; + if (X < xx || Y < yy) { + return false; + } + w += xx; + W += X; + if (W <= X) { + // X+W overflowed or W was zero, return false if... + // either original w or W was zero or + // x+w did not overflow or + // the overflowed x+w is smaller than the overflowed X+W + if (w >= xx || W > w) return false; + } else { + // X+W did not overflow and W was not zero, return false if... + // original w was zero or + // x+w did not overflow and x+w is smaller than X+W + if (w >= xx && W > w) return false; + } + h += yy; + H += Y; + if (H <= Y) { + if (h >= yy || H > h) return false; + } else { + if (h >= yy && H > h) return false; + } + return true; + } + + /** + * Determines whether or not this Rectangle and the specified + * Rectangle intersect. Two rectangles intersect if + * their intersection is nonempty. + * + * @param r the specified Rectangle + * @return true if the specified Rectangle + * and this Rectangle intersect; + * false otherwise. + */ + public boolean intersects(Rectangle r) { + int tw = this.width; + int th = this.height; + int rw = r.width; + int rh = r.height; + if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { + return false; + } + int tx = this.x; + int ty = this.y; + int rx = r.x; + int ry = r.y; + rw += rx; + rh += ry; + tw += tx; + th += ty; + // overflow || intersect + return ((rw < rx || rw > tx) && + (rh < ry || rh > ty) && + (tw < tx || tw > rx) && + (th < ty || th > ry)); + } + + /** + * Computes the intersection of this Rectangle with the + * specified Rectangle. Returns a new Rectangle + * that represents the intersection of the two rectangles. + * If the two rectangles do not intersect, the result will be + * an empty rectangle. + * + * @param r the specified Rectangle + * @return the largest Rectangle contained in both the + * specified Rectangle and in + * this Rectangle; or if the rectangles + * do not intersect, an empty rectangle. + */ + public Rectangle intersection(Rectangle r) { + int tx1 = this.x; + int ty1 = this.y; + int rx1 = r.x; + int ry1 = r.y; + long tx2 = tx1; tx2 += this.width; + long ty2 = ty1; ty2 += this.height; + long rx2 = rx1; rx2 += r.width; + long ry2 = ry1; ry2 += r.height; + if (tx1 < rx1) tx1 = rx1; + if (ty1 < ry1) ty1 = ry1; + if (tx2 > rx2) tx2 = rx2; + if (ty2 > ry2) ty2 = ry2; + tx2 -= tx1; + ty2 -= ty1; + // tx2,ty2 will never overflow (they will never be + // larger than the smallest of the two source w,h) + // they might underflow, though... + if (tx2 < Integer.MIN_VALUE) tx2 = Integer.MIN_VALUE; + if (ty2 < Integer.MIN_VALUE) ty2 = Integer.MIN_VALUE; + return new Rectangle(tx1, ty1, (int) tx2, (int) ty2); + } + + /** + * Computes the union of this Rectangle with the + * specified Rectangle. Returns a new + * Rectangle that + * represents the union of the two rectangles. + *

+ * If either {@code Rectangle} has any dimension less than zero + * the rules for non-existant rectangles + * apply. + * If only one has a dimension less than zero, then the result + * will be a copy of the other {@code Rectangle}. + * If both have dimension less than zero, then the result will + * have at least one dimension less than zero. + *

+ * If the resulting {@code Rectangle} would have a dimension + * too large to be expressed as an {@code int}, the result + * will have a dimension of {@code Integer.MAX_VALUE} along + * that dimension. + * @param r the specified Rectangle + * @return the smallest Rectangle containing both + * the specified Rectangle and this + * Rectangle. + */ + public Rectangle union(Rectangle r) { + long tx2 = this.width; + long ty2 = this.height; + if ((tx2 | ty2) < 0) { + // This rectangle has negative dimensions... + // If r has non-negative dimensions then it is the answer. + // If r is non-existant (has a negative dimension), then both + // are non-existant and we can return any non-existant rectangle + // as an answer. Thus, returning r meets that criterion. + // Either way, r is our answer. + return new Rectangle(r); + } + long rx2 = r.width; + long ry2 = r.height; + if ((rx2 | ry2) < 0) { + return new Rectangle(this); + } + int tx1 = this.x; + int ty1 = this.y; + tx2 += tx1; + ty2 += ty1; + int rx1 = r.x; + int ry1 = r.y; + rx2 += rx1; + ry2 += ry1; + if (tx1 > rx1) tx1 = rx1; + if (ty1 > ry1) ty1 = ry1; + if (tx2 < rx2) tx2 = rx2; + if (ty2 < ry2) ty2 = ry2; + tx2 -= tx1; + ty2 -= ty1; + // tx2,ty2 will never underflow since both original rectangles + // were already proven to be non-empty + // they might overflow, though... + if (tx2 > Integer.MAX_VALUE) tx2 = Integer.MAX_VALUE; + if (ty2 > Integer.MAX_VALUE) ty2 = Integer.MAX_VALUE; + return new Rectangle(tx1, ty1, (int) tx2, (int) ty2); + } + + /** + * Adds a point, specified by the integer arguments {@code newx,newy} + * to the bounds of this {@code Rectangle}. + *

+ * If this {@code Rectangle} has any dimension less than zero, + * the rules for non-existant + * rectangles apply. + * In that case, the new bounds of this {@code Rectangle} will + * have a location equal to the specified coordinates and + * width and height equal to zero. + *

+ * After adding a point, a call to contains with the + * added point as an argument does not necessarily return + * true. The contains method does not + * return true for points on the right or bottom + * edges of a Rectangle. Therefore, if the added point + * falls on the right or bottom edge of the enlarged + * Rectangle, contains returns + * false for that point. + * If the specified point must be contained within the new + * {@code Rectangle}, a 1x1 rectangle should be added instead: + *

+     *     r.add(newx, newy, 1, 1);
+     * 
+ * @param newx the X coordinate of the new point + * @param newy the Y coordinate of the new point + */ + public void add(int newx, int newy) { + if ((width | height) < 0) { + this.x = newx; + this.y = newy; + this.width = this.height = 0; + return; + } + int x1 = this.x; + int y1 = this.y; + long x2 = this.width; + long y2 = this.height; + x2 += x1; + y2 += y1; + if (x1 > newx) x1 = newx; + if (y1 > newy) y1 = newy; + if (x2 < newx) x2 = newx; + if (y2 < newy) y2 = newy; + x2 -= x1; + y2 -= y1; + if (x2 > Integer.MAX_VALUE) x2 = Integer.MAX_VALUE; + if (y2 > Integer.MAX_VALUE) y2 = Integer.MAX_VALUE; + setBounds(x1, y1, (int) x2, (int) y2); + } + + /** + * Adds the specified {@code Point} to the bounds of this + * {@code Rectangle}. + *

+ * If this {@code Rectangle} has any dimension less than zero, + * the rules for non-existant + * rectangles apply. + * In that case, the new bounds of this {@code Rectangle} will + * have a location equal to the coordinates of the specified + * {@code Point} and width and height equal to zero. + *

+ * After adding a Point, a call to contains + * with the added Point as an argument does not + * necessarily return true. The contains + * method does not return true for points on the right + * or bottom edges of a Rectangle. Therefore if the added + * Point falls on the right or bottom edge of the + * enlarged Rectangle, contains returns + * false for that Point. + * If the specified point must be contained within the new + * {@code Rectangle}, a 1x1 rectangle should be added instead: + *

+     *     r.add(pt.x, pt.y, 1, 1);
+     * 
+ * @param pt the new Point to add to this + * Rectangle + */ + public void add(Point pt) { + add(pt.x, pt.y); + } + + /** + * Adds a Rectangle to this Rectangle. + * The resulting Rectangle is the union of the two + * rectangles. + *

+ * If either {@code Rectangle} has any dimension less than 0, the + * result will have the dimensions of the other {@code Rectangle}. + * If both {@code Rectangle}s have at least one dimension less + * than 0, the result will have at least one dimension less than 0. + *

+ * If either {@code Rectangle} has one or both dimensions equal + * to 0, the result along those axes with 0 dimensions will be + * equivalent to the results obtained by adding the corresponding + * origin coordinate to the result rectangle along that axis, + * similar to the operation of the {@link #add(Point)} method, + * but contribute no further dimension beyond that. + *

+ * If the resulting {@code Rectangle} would have a dimension + * too large to be expressed as an {@code int}, the result + * will have a dimension of {@code Integer.MAX_VALUE} along + * that dimension. + * @param r the specified Rectangle + */ + public void add(Rectangle r) { + long tx2 = this.width; + long ty2 = this.height; + if ((tx2 | ty2) < 0) { + setBounds(r.x, r.y, r.width, r.height); + } + long rx2 = r.width; + long ry2 = r.height; + if ((rx2 | ry2) < 0) { + return; + } + int tx1 = this.x; + int ty1 = this.y; + tx2 += tx1; + ty2 += ty1; + int rx1 = r.x; + int ry1 = r.y; + rx2 += rx1; + ry2 += ry1; + if (tx1 > rx1) tx1 = rx1; + if (ty1 > ry1) ty1 = ry1; + if (tx2 < rx2) tx2 = rx2; + if (ty2 < ry2) ty2 = ry2; + tx2 -= tx1; + ty2 -= ty1; + // tx2,ty2 will never underflow since both original + // rectangles were non-empty + // they might overflow, though... + if (tx2 > Integer.MAX_VALUE) tx2 = Integer.MAX_VALUE; + if (ty2 > Integer.MAX_VALUE) ty2 = Integer.MAX_VALUE; + setBounds(tx1, ty1, (int) tx2, (int) ty2); + } + + /** + * Resizes the Rectangle both horizontally and vertically. + *

+ * This method modifies the Rectangle so that it is + * h units larger on both the left and right side, + * and v units larger at both the top and bottom. + *

+ * The new Rectangle has {@code (x - h, y - v)} + * as its upper-left corner, + * width of {@code (width + 2h)}, + * and a height of {@code (height + 2v)}. + *

+ * If negative values are supplied for h and + * v, the size of the Rectangle + * decreases accordingly. + * The {@code grow} method will check for integer overflow + * and underflow, but does not check whether the resulting + * values of {@code width} and {@code height} grow + * from negative to non-negative or shrink from non-negative + * to negative. + * @param h the horizontal expansion + * @param v the vertical expansion + */ + public void grow(int h, int v) { + long x0 = this.x; + long y0 = this.y; + long x1 = this.width; + long y1 = this.height; + x1 += x0; + y1 += y0; + + x0 -= h; + y0 -= v; + x1 += h; + y1 += v; + + if (x1 < x0) { + // Non-existant in X direction + // Final width must remain negative so subtract x0 before + // it is clipped so that we avoid the risk that the clipping + // of x0 will reverse the ordering of x0 and x1. + x1 -= x0; + if (x1 < Integer.MIN_VALUE) x1 = Integer.MIN_VALUE; + if (x0 < Integer.MIN_VALUE) x0 = Integer.MIN_VALUE; + else if (x0 > Integer.MAX_VALUE) x0 = Integer.MAX_VALUE; + } else { // (x1 >= x0) + // Clip x0 before we subtract it from x1 in case the clipping + // affects the representable area of the rectangle. + if (x0 < Integer.MIN_VALUE) x0 = Integer.MIN_VALUE; + else if (x0 > Integer.MAX_VALUE) x0 = Integer.MAX_VALUE; + x1 -= x0; + // The only way x1 can be negative now is if we clipped + // x0 against MIN and x1 is less than MIN - in which case + // we want to leave the width negative since the result + // did not intersect the representable area. + if (x1 < Integer.MIN_VALUE) x1 = Integer.MIN_VALUE; + else if (x1 > Integer.MAX_VALUE) x1 = Integer.MAX_VALUE; + } + + if (y1 < y0) { + // Non-existant in Y direction + y1 -= y0; + if (y1 < Integer.MIN_VALUE) y1 = Integer.MIN_VALUE; + if (y0 < Integer.MIN_VALUE) y0 = Integer.MIN_VALUE; + else if (y0 > Integer.MAX_VALUE) y0 = Integer.MAX_VALUE; + } else { // (y1 >= y0) + if (y0 < Integer.MIN_VALUE) y0 = Integer.MIN_VALUE; + else if (y0 > Integer.MAX_VALUE) y0 = Integer.MAX_VALUE; + y1 -= y0; + if (y1 < Integer.MIN_VALUE) y1 = Integer.MIN_VALUE; + else if (y1 > Integer.MAX_VALUE) y1 = Integer.MAX_VALUE; + } + + setBounds((int) x0, (int) y0, (int) x1, (int) y1); + } + + /** + * {@inheritDoc} + * @return + */ + public boolean isEmpty() { + return (width <= 0) || (height <= 0); + } + + /** + * Checks whether two rectangles are equal. + *

+ * The result is true if and only if the argument is not + * null and is a Rectangle object that has the + * same upper-left corner, width, and height as + * this Rectangle. + * @param obj the Object to compare with + * this Rectangle + * @return true if the objects are equal; + * false otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof Rectangle) { + Rectangle r = (Rectangle)obj; + return ((x == r.x) && + (y == r.y) && + (width == r.width) && + (height == r.height)); + } + return super.equals(obj); + } + + /** + * {@inheritDoc } + * @return + */ + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + this.x; + hash = 29 * hash + this.y; + hash = 29 * hash + this.width; + hash = 29 * hash + this.height; + return hash; + } + + /** + * Returns a String representing this + * Rectangle and its values. + * @return a String representing this + * Rectangle object's coordinate and size values. + */ + @Override + public String toString() { + return getClass().getName() + "[x=" + x + ",y=" + y + ",width=" + width + ",height=" + height + "]"; + } + + /** + * Parses given string to restore Rectangle instance from the string. + * String assumed to be previously created using + * {@linkplain #toString() Rectangle.toString()} method. + * @param str String to parse. + * @return Recreated Rectangle instance. + */ + public static Rectangle parseRectangle(String str) { + if (str != null && str.startsWith(Rectangle.class.getName())) { + try { + String[] t = + str.substring(Rectangle.class.getName().length() + 1) + .split("\\,|\\]"); + Rectangle res = new Rectangle(); + for(String pair : t) { + String[] p = pair.split("\\="); + String var = p[0]; + int value = Integer.parseInt(p[1]); + res.getClass().getDeclaredField(var).setInt(res, value); + } + return res; + } catch (Exception ex) { + throw new JemmyException( + "Failed to parse Rectangle '" + str + "'", ex); + } + } + return null; + } +} --- /dev/null 2017-11-08 15:39:23.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/TimeoutExpiredException.java 2017-11-08 15:39:23.000000000 -0800 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy; + +/** + * + * Exception is supposed to be used to notice that some + * waiting was expired. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ + +public class TimeoutExpiredException extends JemmyException{ + + /** + * Constructor. + * @param description Waiting description. + */ + public TimeoutExpiredException(String description) { + super(description); + } +} --- /dev/null 2017-11-08 15:39:23.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/Vector.java 2017-11-08 15:39:23.000000000 -0800 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy; + +/** + * The class for easy computations. + * @author shura + */ +public class Vector { + + private double x; + private double y; + + /** + * + * @param x + * @param y + */ + public Vector(double x, double y) { + this.x = x; + this.y = y; + } + + /** + * + * @param from + * @param to + */ + public Vector(Point from, Point to) { + x = to.x - from.x; + y = to.y - from.y; + } + + /** + * + * @return + */ + public double getX() { + return x; + } + + /** + * + * @return + */ + public double getY() { + return y; + } + + /** + * + * @return + */ + public double lenght() { + return Math.sqrt(x*x + y*y); + } + + /** + * + * @param newLenght + * @return self + */ + public Vector setLenght(double newLenght) { + double lenght = lenght(); + x = x * newLenght / lenght; + y = y * newLenght / lenght; + return this; + } + + /** + * @param multiplier + * @return self + */ + public Vector multiply(double multiplier) { + x*=multiplier; + y*=multiplier; + return this; + } + + /** + * + * @return a clone + */ + @Override + public Vector clone() { + return new Vector(x, y); + } + + /** + * + * @return + */ + @Override + public String toString() { + return "(" + x + "," + y + ")"; + } + + /** + * Adds another vector (x1 + x2, y1 + y2) + * @param v + * @return self + */ + public Vector add(Vector v) { + x+=v.x; + y+=v.y; + return this; + } + + +} --- /dev/null 2017-11-08 15:39:24.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/Version.java 2017-11-08 15:39:24.000000000 -0800 @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * + * @author shura + */ +public class Version { + /** + * + */ + public static final Version VERSION = new Version(); + + private int major; + private int minor; + private int mini; + private String build; + + /** + * + */ + public Version() { + this(Version.class.getPackage().getName()); + } + + /** + * + * @param pkg + */ + public Version(String pkg) { + try { + Properties props = new Properties(); + String fileName = pkg.replace(".", "/") + "/jemmy.properties"; + InputStream in = getClass().getClassLoader().getResourceAsStream(fileName); + if(in == null) { + throw new JemmyException("Can not get version - no " + fileName + " file"); + } + props.load(in); + major = Integer.parseInt(props.getProperty("version.major")); + minor = Integer.parseInt(props.getProperty("version.minor")); + mini = Integer.parseInt(props.getProperty("version.mini")); + build = props.getProperty("build"); + } catch (IOException ex) { + throw new JemmyException("Can not get version.", ex); + } + } + + /** + * + * @return + */ + public int getMajor() { + return major; + } + + /** + * + * @return + */ + public int getMini() { + return mini; + } + + /** + * + * @return + */ + public int getMinor() { + return minor; + } + + /** + * + * @return + */ + public String getVersion() { + return major + "." + minor + "." + mini; + } + + /** + * + * @return + */ + public String getBuild() { + return build; + } + + /** + * + * @param old + * @return + */ + public boolean newer(String old) { + StringTokenizer tn = new StringTokenizer(old, "."); + if(major >= Integer.parseInt(tn.nextToken())) { + if(minor >= Integer.parseInt(tn.nextToken())) { + if(mini >= Integer.parseInt(tn.nextToken())) { + return true; + } + } + } + return false; + } + + /** + * + * @param args + */ + public static void main(String[] args) { + System.out.println("JemmyCore version: " + VERSION.getVersion() + "." + VERSION.build); + } +} --- /dev/null 2017-11-08 15:39:24.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/AbstractExecutor.java 2017-11-08 15:39:24.000000000 -0800 @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + +import org.jemmy.env.Environment; +import org.jemmy.env.TestOut; +import org.jemmy.env.Timeout; +import org.jemmy.timing.State; +import org.jemmy.timing.Waiter; + +/** + * + * @author shura + */ +public abstract class AbstractExecutor implements ActionExecutor { + + /** + * Default timeout for action {@linkplain Action#run(java.lang.Object[]) + * run()} method to be completed. + */ + public static final Timeout MAX_ACTION_TIME = new Timeout("max.action.time", 60000); + /** + * Indentifies output which would be used to print information for all actions + * executed not on event queue. + * @see AbstractExecutor#execute(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) + * @see AbstractExecutor#executeDetached(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) + * @see Environment#getOutput(java.lang.String) + */ + public static final String NON_QUEUE_ACTION_OUTPUT = "org.jemmy.action.AbstractExecutor.NON_QUEUE_ACTION_OUTPUT"; + /** + * Indentifies output which would be used to print information for all actions + * executed on event queue. + * @see AbstractExecutor#execute(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) + * @see AbstractExecutor#executeDetached(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) + * @see Environment#getOutput(java.lang.String) + */ + public static final String QUEUE_ACTION_OUTPUT = "org.jemmy.action.AbstractExecutor.QUEUE_ACTION_OUTPUT"; + private ActionQueue queue; + + /** + * + */ + public AbstractExecutor() { + queue = new ActionQueue(); + } + + static { + Environment.getEnvironment().initTimeout(MAX_ACTION_TIME); + Environment.getEnvironment().initOutput(QUEUE_ACTION_OUTPUT, TestOut.getNullOutput()); + Environment.getEnvironment().initOutput(NON_QUEUE_ACTION_OUTPUT, TestOut.getNullOutput()); + } + + /** + * + * @return + */ + protected int actionsInQueue() { + return queue.actionsInQueue(); + } + + /** + * {@inheritDoc } + * Prints out what action is executed into output + * specified by either NON_QUEUE_ACTION_OUTPUT or QUEUE_ACTION_OUTPUT + * depending whether the action is called on queue or not. No output provided for + * nested actions - only the top level ones are printed. + * @see TestOut#getOutput(java.lang.String) + */ + public final void execute(Environment env, boolean dispatch, final Action action, Object... parameters) { + printStrace(env, "Action: ", action); + action.setAllowedTime(env.getTimeout(MAX_ACTION_TIME.getName()).getValue()); + if (dispatch) { + executeQueue(env, action, parameters); + } else { + if (isInAction()) { + action.execute(parameters); + } else { + queue.invokeAndWait(action, parameters); + } + } + } + + /** + * {@inheritDoc } + * Prints out what action is executed into output + * specified by either NON_QUEUE_ACTION_OUTPUT or QUEUE_ACTION_OUTPUT + * depending whether the action is called on queue or not. No output provided for + * nested actions - only the top level ones are printed. + * @see TestOut#getOutput(java.lang.String) + */ + public final void executeDetached(Environment env, boolean dispatch, final Action action, final Object... parameters) { + printStrace(env, "Action detached: ", action); + if (dispatch) { + executeQueueDetached(env, action, parameters); + } else { + if (isInAction()) { + new Thread(new Runnable() { + + public void run() { + action.execute(parameters); + } + }).start(); + } else { + queue.invoke(action, parameters); + } + } + } + + private void printStrace(Environment env, String text, Action action) { + String toString = action.toString(); + if (toString != null && toString.length() > 0) { + if (!isInAction()) { + if (isOnQueue()) { + env.getOutput(QUEUE_ACTION_OUTPUT).println(text + action.toString()); + } else { + env.getOutput(NON_QUEUE_ACTION_OUTPUT).println(text + action.toString()); + } + } + } + } + + /** + * {@inheritDoc} + */ + public final boolean isInAction() { + return queue.getQueueThread() == Thread.currentThread() || isOnQueue(); + } + + /** + * Schedules to execute an action through the UI system's dispatch thread and + * waits for the action to be completed. This method is called from + * {@linkplain #execute(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) execute()} + * method when it is invoked with dispatch argument set to true. + * @param env Environment. + * @param action action to execute. + * @param parameters parameters to pass to {@linkplain Action#run(java.lang.Object[]) action.run()} method. + */ + public abstract void executeQueue(Environment env, Action action, Object... parameters); + + /** + * Schedules to execute an action through the UI system's dispatch thread and + * exits immediately. This method is called from + * {@linkplain #executeDetached(org.jemmy.env.Environment, boolean, org.jemmy.action.Action, java.lang.Object[]) executeDetached()} + * method when it is invoked with dispatch argument set to true. + * @param env Environment. + * @param action action to execute. + * @param parameters parameters to pass to {@linkplain Action#run(java.lang.Object[]) action.run()} method. + */ + public abstract void executeQueueDetached(Environment env, Action action, Object... parameters); + + /** + * Checks whether this is invoked from the UI system event queue. + * @return true if invoked from the UI system event queue. + */ + public abstract boolean isOnQueue(); + + /** + * Waits for UI to became quiet which is determined using + * {@linkplain #isQuiet() isQuiet()} method. + * @param waitTime maximum time for waiting. + */ + public void waitQuiet(Timeout waitTime) { + new Waiter(waitTime).ensureState(new State() { + + public Object reached() { + return isQuiet() ? true : null; + } + }); + } + + /** + * Tells whether the UI is quiet which usually means that all scheduled + * actions are dispatched and the queue is empty. + * @see #waitQuiet(org.jemmy.env.Timeout) + * @return true if the UI is quiet. + */ + protected abstract boolean isQuiet(); +} --- /dev/null 2017-11-08 15:39:25.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/Action.java 2017-11-08 15:39:24.000000000 -0800 @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + +import org.jemmy.JemmyException; + + +/** + * + * @author shura, KAM + */ +public abstract class Action { + + private boolean interrupted = false; + private long startTime = -1, endTime = 0, allowedTime = 0; + private Throwable throwable = null; + + /** + * Executes {@linkplain #run(java.lang.Object[]) run()} method of this + * Action, saving the duration of its execution and storing any + * RuntimeException and Error which may occur during its work. + * @param parameters Parameters to pass to {@linkplain + * #run(java.lang.Object[]) run()} method + * @see #getThrowable() + * @see #failed() + */ + public final void execute(Object... parameters) { + startTime = System.currentTimeMillis(); + try { + run(parameters); + } catch (Error e) { + throwable = e; + throw e; + } catch (RuntimeException e) { + throwable = e; + throw e; + } catch (Exception e) { + throwable = e; + throw new JemmyException("Exception in action " + this.toString(), e); + } finally { + endTime = System.currentTimeMillis(); + } + } + + /** + * + * @return + */ + public long getEndTime() { + return endTime; + } + + /** + * + * @return + */ + public long getStartTime() { + return startTime; + } + + /** + * Should be used from {@linkplain #run(java.lang.Object[]) run()) method + * to check whether execution time is withing allowed time + * @return true if difference between current time and start time is less + * then allowed time; false otherwice + */ + protected boolean withinAllowedTime() { + return System.currentTimeMillis() - startTime < allowedTime; + } + + /** + * + * @return + */ + public long getAllowedTime() { + return allowedTime; + } + + /** + * + * @param allowedTime + */ + public void setAllowedTime(long allowedTime) { + this.allowedTime = allowedTime; + } + + /** + * + * @param parameters + */ + public abstract void run(Object... parameters) throws Exception; + + /** + * + * @return + */ + public boolean isInterrupted() { + return interrupted; + } + + /** + * + */ + public void interrupt() { + this.interrupted = true; + } + + /** + * Returns throwable that occurred during run() invocation. + * @return Error or RuntimeException. + */ + public Throwable getThrowable() { + return throwable; + } + + /** + * Indicates whether action invocation failed. + * @return true if some exception occurred during run() invocation. + */ + public boolean failed() { + return throwable != null; + } + + /** + * Override this method to provide action description which + * will be printed into output. + * @return null If nothing should be printed into output. + */ + @Override + public String toString() { + return super.toString(); + } +} --- /dev/null 2017-11-08 15:39:25.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/ActionExecutor.java 2017-11-08 15:39:25.000000000 -0800 @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.action; + + +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; + + +/** + * Interface to execute user's action at appropriate moment. + * @author shura + */ +public interface ActionExecutor { + + /** + * + */ + public static final String ACTION_EXECUTOR_PROPERTY = "action.executor"; + + /** + * Schedules to execute an action and waits for it to finish. + * @param env Environment. + * @param dispatch if true the action is executed on UI system dispatch + * thread. This is usually necessary to invoke methods of the UI to get + * the correct state or to update it. + * @param action Action to execute. + * @param parameters Parameters to pass to + * {@linkplain Action#run(java.lang.Object[]) action.run()} method. + */ + public void execute(Environment env, boolean dispatch, Action action, Object... parameters); + + /** + * Schedules to execute an action and exits immediately. Used to be called + * DoSomethingNoBlock operations in jemmy2. + * @param env Environment. + * @param dispatch if true the action is executed on UI system dispatch + * thread. This is usually necessary to invoke methods of the UI to get + * the correct state or to update it. + * @param action Action to execute. + * @param parameters Parameters to pass to + * {@linkplain Action#run(java.lang.Object[]) action.run()} method. + */ + public void executeDetached(Environment env, boolean dispatch, Action action, Object... parameters); + + /** + * Checks whether the current thread is already performing an action. + * @return true if the current thread is already performing an action. + * @see AbstractExecutor#isDispatchThread() + */ + public boolean isInAction(); + + /** + * Waits for no activities to be going on. Implementation may be different + * for different mechanisms. + * @param waitTime maximum time for waiting. + * @see AbstractExecutor#isQuiet() + */ + public void waitQuiet(Timeout waitTime); +} --- /dev/null 2017-11-08 15:39:25.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/ActionQueue.java 2017-11-08 15:39:25.000000000 -0800 @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + + +import java.util.LinkedList; +import org.jemmy.JemmyException; +import org.jemmy.TimeoutExpiredException; + + +/** + * + * @author shura, KAM + */ +class ActionQueue { + + private Thread queueThread; + private final LinkedList queue; + private boolean stop = false; + + public ActionQueue() { + queue = new LinkedList(); + queueThread = new Thread(new Runnable() { + + public void run() { + int size; + while (!stop) { + synchronized (queue) { + size = queue.size(); + if (size == 0) { + try { + queue.wait(); + } catch (InterruptedException ex) { + } + } + } + if (size > 0) { + ActionRecord r; + synchronized (queue) { + r = queue.poll(); + } + try { + r.execute(); + } catch (Exception e) { + System.err.println("Action '" + r + "' failed with the following exception: "); + e.printStackTrace(System.err); + System.err.flush(); + } + r.setCompleted(true); + } + } + } + }, "ActionQueue.queueThread"); + queueThread.start(); + } + + public int actionsInQueue() { + synchronized(queue) { + return queue.size(); + } + } + + public void stop() { + stop = true; + } + + /** + * Returns internal ActionQueue event dispatching thread + * @return queue dispatching thread of ActionQueue object + */ + public Thread getQueueThread() { + return queueThread; + } + + /** + * Schedules execution of an action throught the internal ActionQueue queue + * and exits immediately + * @param action action to execute + * @param parameters parameters to pass to action.run() method + */ + public void invoke(Action action, Object... parameters) { + synchronized (queue) { + queue.add(new ActionRecord(action, parameters)); + queue.notifyAll(); + } + } + + /** + * Schedules execution of an action through the internal ActionQueue queue + * and waits until it is completed + * @param action action to execute + * @param parameters parameters to pass to action.run() method + */ + public void invokeAndWait(Action action, Object... parameters) { + ActionRecord r = new ActionRecord(action, parameters); + synchronized (queue) { + queue.add(r); + queue.notifyAll(); + } + r.waitCompleted(); + + if (r.failed()) { + throw new JemmyException("Action '" + r + "' invoked through ActionQueue failed", r.getThrowable()); + } + } + + private class ActionRecord { + + Action action; + Object[] parameters; + boolean completed; + boolean started; + + public ActionRecord(Action action, Object[] parameters) { + this.action = action; + this.parameters = parameters; + } + + public boolean failed() { + return action.failed(); + } + + public Throwable getThrowable() { + return action.getThrowable(); + } + + public Object[] getParameters() { + return parameters; + } + + public boolean isCompleted() { + return completed; + } + + public synchronized void setCompleted(boolean completed) { + this.completed = completed; + notifyAll(); + } + + public void execute() { + synchronized (this) { + started = true; + notifyAll(); + } + action.execute(parameters); + } + + public synchronized void waitCompleted() { + try { + while (!started) { + wait(); + } + if (!completed) { + wait(action.getAllowedTime()); + if (!completed) { + action.interrupt(); + throw new TimeoutExpiredException("Action did not finish in " + action.getAllowedTime() + " ms: " + action); + } + } + } catch (InterruptedException ex) { + } + } + + @Override + public String toString() { + return action.toString(); + } + } +} --- /dev/null 2017-11-08 15:39:25.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/DefaultExecutor.java 2017-11-08 15:39:25.000000000 -0800 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + + +import org.jemmy.env.Environment; + + +/** + * + * @author shura + */ +public class DefaultExecutor extends AbstractExecutor { + + /** + * + */ + public DefaultExecutor() { + } + + /** + * Executes through the ActionQueue as there is no system dispatch thread. + * @param env {@inheritDoc } + * @param action {@inheritDoc } + * @param parameters {@inheritDoc } + * @see Action#run(java.lang.Object[]) + */ + public void executeQueue(Environment env, Action action, Object... parameters) { + execute(env, false, action, parameters); + } + + /** + * Executes through the ActionQueue as there is no system dispatch thread. + * @param env {@inheritDoc } + * @param action {@inheritDoc } + * @param parameters {@inheritDoc } + * @see Action#run(java.lang.Object[]) + */ + public void executeQueueDetached(Environment env, Action action, Object... parameters) { + executeDetached(env, false, action, parameters); + } + + /** + * Always returns false as there is no system dispatch thread. + * @return always false. + */ + public boolean isOnQueue() { + return false; + } + + /** + * {@inheritDoc} + */ + public boolean isQuiet() { + return actionsInQueue() == 0 && !isInAction(); + } + +} --- /dev/null 2017-11-08 15:39:26.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/FutureAction.java 2017-11-08 15:39:26.000000000 -0800 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + +import org.jemmy.env.Environment; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +/** + * Created with IntelliJ IDEA. User: Andrey Nazarov Date: 14.05.13 Time: 20:49 + */ +public class FutureAction extends Action { + + final FutureTask task; + + public FutureAction(Environment env, Callable callable) { + task = new FutureTask(callable); + env.getExecutor().execute(env, true, this); + } + + public FutureAction(Environment env, Runnable runnable) { + task = new FutureTask(runnable, null); + env.getExecutor().execute(env, true, this); + } + + @Override + public void run(Object... parameters) throws Exception { + task.run(); + } + + public T get() { + try { + return task.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } +} --- /dev/null 2017-11-08 15:39:26.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/action/GetAction.java 2017-11-08 15:39:26.000000000 -0800 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.action; + + +import org.jemmy.env.Environment; + + +/** + * An action to get some value. + * @param + * @author shura + */ +public abstract class GetAction extends Action { + + private boolean finished = false; + private T result = null; + + /** + * + */ + public GetAction() { + } + + /** + * + * @return + */ + public boolean isFinished() { + return finished; + } + + /** + * + * @return + */ + public T getResult() { + return result; + } + + /** + * + * @param result + */ + protected void setResult(T result) { + this.result = result; + finished = true; + } + + /** + * Dispatches action through the system UI queue to get the result. + * @param env Environment to + * {@linkplain Environment#getExecutor() get} executor and to pass to + * {@linkplain ActionExecutor#execute(org.jemmy.env.Environment, boolean, + * org.jemmy.action.Action, java.lang.Object[]) execute()} method. + * @param parameters Parameters to pass to {@linkplain + * #run(java.lang.Object[]) run()} method. + * @return value returned by {@linkplain #getResult() getResult()} method. + */ + public T dispatch(Environment env, Object... parameters) { + env.getExecutor().execute(env, true, this, parameters); + return getResult(); + } + +} --- /dev/null 2017-11-08 15:39:26.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/AbstractWrapper.java 2017-11-08 15:39:26.000000000 -0800 @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import org.jemmy.env.Environment; + + +/** + * This is an implementation of the {@code Wrapper} which instantiates a wrap + * of a class returned by {@code getWrapClass(Class)}. + * @author shura + */ +public abstract class AbstractWrapper implements Wrapper { + + private Environment env; + + /** + * + * @param env + */ + @SuppressWarnings("unchecked") + public AbstractWrapper(Environment env) { + this.env = env; + } + + /** + * + * @return + */ + public Environment getEnvironment() { + return env; + } + + protected abstract Class getWrapClass(Class controlClass); + + /** + * + * @param + * @param controlClass + * @param control + * @return Wrap + */ + public Wrap wrap(Class controlClass, T control) { + Class cls = control.getClass(); + Class wrp; + do { + wrp = getWrapClass(cls); + if (wrp != null) { + try { + return doWrap(control, cls, wrp); + } catch (InstantiationException ex) { + throw new WrapperException(cls, wrp, ex); + } catch (IllegalAccessException ex) { + throw new WrapperException(cls, wrp, ex); + } catch (IllegalArgumentException ex) { + throw new WrapperException(cls, wrp, ex); + } catch (InvocationTargetException ex) { + throw new WrapperException(cls, wrp, ex); + } catch (NoSuchMethodException ex) { + throw new WrapperException(cls, wrp, ex); + } catch (SecurityException ex) { + throw new WrapperException(cls, wrp, ex); + } + } + } while ((cls = cls.getSuperclass()) != null); + throw new WrapperException(control); + } + + /** + * + * @param + * @param control + * @param controlClass + * @param wrapperClass + * @return Wrap + * @throws java.lang.NoSuchMethodException + * @throws java.lang.InstantiationException + * @throws java.lang.IllegalAccessException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.reflect.InvocationTargetException + */ + @SuppressWarnings("unchecked") + protected Wrap doWrap(T control, Class controlClass, Class wrapperClass) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Constructor cns = null; + Class cls = controlClass; + do { + try { + cns = wrapperClass.getConstructor(Environment.class, cls); + } catch (NoSuchMethodException e) { + } + } while ((cls = cls.getSuperclass()) != null); + if (cns != null) { + return (Wrap) cns.newInstance(new Environment(env), control); + } else { + throw new WrapperException(controlClass, wrapperClass); + } + } +} --- /dev/null 2017-11-08 15:39:27.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/As.java 2017-11-08 15:39:27.000000000 -0800 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import java.lang.annotation.*; +import org.jemmy.interfaces.TypeControlInterface; + +/** + * This annotation should be used to annotate methods which turn a wrap + * into a control interface. It is, then, used in Wrap.as(*) and + * Wrap.is(*) methods and in *Dock.as*() methods. + * @see Wrap#as(java.lang.Class) + * @see Wrap#as(java.lang.Class, java.lang.Class) + * @see Wrap#is(java.lang.Class) + * @see Wrap#is(java.lang.Class, java.lang.Class) + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface As { + /** + * This should point out what is the encapsulated type for + * TypeControlInterface + * @see TypeControlInterface + * @return + */ + Class value() default Void.class; +} --- /dev/null 2017-11-08 15:39:27.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/ControlInterfaces.java 2017-11-08 15:39:27.000000000 -0800 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.jemmy.interfaces.ControlInterface; +import org.jemmy.interfaces.TypeControlInterface; + +/** + * To be applied on classes - ancestors of Wrp + * class. + * @see Wrap + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface ControlInterfaces { + /** + * List of interfaces supported by this wrap. + * @see ControlInterface + */ + Class[] value(); + /** + * List of encapsulated types for the TypeControlInterface + * interfaces listed in value. + * Note that this list should be shorter that the value to not + * provide anything for a ControlInterface which is not a + * TypeControlInterface + * @see TypeControlInterface + */ + Class[] encapsulates() default {}; + /** + * This provides names for the dock methods which would be generated. If the array + * does not have enough elements, the method would be named as "as" + value[i].getName(). + */ + String[] name() default {}; +} --- /dev/null 2017-11-08 15:39:27.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/ControlType.java 2017-11-08 15:39:27.000000000 -0800 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import java.lang.annotation.*; + +/** + * TODO: JavaDoc + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface ControlType { + /** + * + * @return + */ + Class[] value(); +} --- /dev/null 2017-11-08 15:39:28.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/DefaultWrapper.java 2017-11-08 15:39:28.000000000 -0800 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import org.jemmy.env.Environment; + +/** + * + * @author shura + */ +public class DefaultWrapper extends WrapperImpl { + + /** + * + * @param env + * @param wrapList + */ + @SuppressWarnings("unchecked") + public DefaultWrapper(Environment env, Class... wrapList) { + super(env); + addAnnotated(wrapList); + } + + /** + * + * @param env + */ + @SuppressWarnings("unchecked") + public DefaultWrapper(Environment env) { + super(env); + } + + /** + * + * @param list + */ + @SuppressWarnings("unchecked") + public final void addAnnotated(Class... list) { + for (Class cn : list) { + if (!cn.isAnnotationPresent(ControlType.class)) { + throw new IllegalStateException("\"" + cn.getName() + "\"" + + " must be annotated with @Control"); + } + if (cn.isAnnotationPresent(ControlType.class)) { + ControlType cc = (ControlType) cn.getAnnotation(ControlType.class); + for(Class ccv : cc.value()) { + add(ccv, cn); + } + } + } + } + + +} --- /dev/null 2017-11-08 15:39:28.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/FieldProperties.java 2017-11-08 15:39:28.000000000 -0800 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This lists names of wrapped object's fields which could be accessed through + * Wrap.getProperty(String) interface. Fields should be public. + * @see Wrap#getProperty(java.lang.String) + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface FieldProperties { + /** + * List of field names. + */ + String[] value(); + /** + * List of field types. In case this list is shorter, all the unmatched ones + * from value are considered to be of type Object. + */ + Class[] types() default {}; + /** + * Are the properties worth a waiter. + */ + boolean[] waitable() default {}; +} --- /dev/null 2017-11-08 15:39:28.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/JemmySupportWrapper.java 2017-11-08 15:39:28.000000000 -0800 @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import java.io.IOException; +import java.io.InputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.jemmy.env.Environment; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * This loads information about the supported wrappers from an xml file which is + * generated by JemmySupport module. + * @author shura + */ +public class JemmySupportWrapper extends LazyWrapper { + + public static final String CONTROL = "control"; + public static final String WRAP = "wrap"; + + public JemmySupportWrapper(ClassLoader loader, InputStream in, Environment env) throws ParserConfigurationException, SAXException, IOException { + super(loader, env); + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(in); + readAll(doc.getDocumentElement()); + } + + public JemmySupportWrapper(ClassLoader loader, String resource, Environment env) throws ParserConfigurationException, SAXException, IOException { + this(loader, loader.getResourceAsStream(resource), env); + } + + private void readAll(Element element) { + NodeList cntrls = element.getElementsByTagName(CONTROL); + for(int i = 0; i < cntrls.getLength(); i++) { + readControl((Element)cntrls.item(i)); + } + } + + private void readControl(Element control) { + add(control.getAttribute(CONTROL), control.getAttribute(WRAP)); + } + +} --- /dev/null 2017-11-08 15:39:29.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/LazyWrapper.java 2017-11-08 15:39:29.000000000 -0800 @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + + +import java.util.HashMap; +import org.jemmy.JemmyException; +import org.jemmy.env.Environment; + + +/** + * This is an implementation of {@code Wrapper} which keeps a map between + * control class names and wrap class names. In only loads the wrap classes when + * first needed. + * @author shura + */ +public class LazyWrapper extends AbstractWrapper { + + private final HashMap theWrappers = new HashMap(); + private final ClassLoader loader; + /** + * + * @param env + */ + @SuppressWarnings("unchecked") + public LazyWrapper(ClassLoader loader, Environment env) { + super(env); + this.loader = loader; + } + + /** + * + * @param

+ * @param controlClass + * @param wrapperClass + */ + public

void add(String controlClass, String wrapperClass) { + theWrappers.put(controlClass, wrapperClass); + } + + @Override + protected Class getWrapClass(Class controlClass) { + String wrapClassName = theWrappers.get(controlClass.getName()); + if(wrapClassName == null) { + return null; + } + try { + return (Class)loader.loadClass(wrapClassName); + } catch (ClassNotFoundException ex) { + throw new JemmyException("Unable to load wrap for " + controlClass.getName() + + " which is " + wrapClassName, ex); + } + } + +} --- /dev/null 2017-11-08 15:39:29.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/MethodProperties.java 2017-11-08 15:39:29.000000000 -0800 @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This lists names of wrapped object's methods which could be accessed through + * Wrap.getProperty(String) interface. Methods should be public and + * should not have any parameters. + * @see Wrap#getProperty(java.lang.String) + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface MethodProperties { + /** + * List of method names + */ + String[] value(); + /** + * List of field types. In case this list is shorter, all the unmatched ones + * from value are considered to be of type Object. + */ + Class[] types() default {}; + /** + * Are the properties worth a waiter. + */ + boolean[] waitable() default {}; +} --- /dev/null 2017-11-08 15:39:29.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/Property.java 2017-11-08 15:39:29.000000000 -0800 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotates a method which provides a property value for the provided + * name of a wrapped control. To be used on public Wrap inheritors methods + * with no parameters. Property value is then accessible through + * Wrap.getProperty(String) + * @see Wrap#getProperty(java.lang.String) + * @author shura + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface Property { + /** + * + * @return a property name. + */ + String value(); + /** + * Are the properties worth a waiter. + */ + boolean waitable() default false; +} --- /dev/null 2017-11-08 15:39:30.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/ScreenArea.java 2017-11-08 15:39:30.000000000 -0800 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + + +import org.jemmy.Rectangle; + + +/** + * + * @author shura + */ +public interface ScreenArea { + /** + * + * @return + */ + public abstract Rectangle getScreenBounds(); +} --- /dev/null 2017-11-08 15:39:30.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/SelectorImpl.java 2017-11-08 15:39:30.000000000 -0800 @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import org.jemmy.JemmyException; +import org.jemmy.interfaces.Selectable; +import org.jemmy.interfaces.Selector; +import org.jemmy.interfaces.Showable; +import org.jemmy.timing.State; + +/** + * + * @param + * @param + * @author shura + */ +public class SelectorImpl implements Selector { + + Wrap target; + Selectable selectable; + + /** + * + * @param target + * @param selectable + */ + public SelectorImpl(Wrap target, Selectable selectable) { + this.target = target; + this.selectable = selectable; + } + + /** + * + * @param state + */ + @SuppressWarnings("unchecked") + public void select(final STATE state) { + if (target.is(Showable.class)) { + target.as(Showable.class).shower().show(); + } + int attempts = 0; + if (!selectable.getState().equals(state)) { + do { + final STATE currentState = selectable.getState(); + if (attempts >= selectable.getStates().size()) { + throw new JemmyException("State is not reached in " + attempts + " attempts", state); + } + target.mouse().click(clickCount(state)); + target.getEnvironment().getWaiter(Wrap.WAIT_STATE_TIMEOUT.getName()).ensureState(new State() { + + public Object reached() { + return selectable.getState().equals(currentState) ? null : ""; + } + + @Override + public String toString() { + return "selectable state (" + selectable.getState() + ") equals '" + state + "'"; + } + + }); + attempts++; + } while (!selectable.getState().equals(state)); + } + } + + private int clickCount(STATE state) { + int current = selectable.getStates().indexOf(selectable.getState()); + int desired = selectable.getStates().indexOf(state); + if (desired >= current) { + return desired - current; + } else { + return selectable.getStates().size() - current + desired; + } + } + + private class StateChangeState implements State { + + Selectable source; + STATE original; + + public StateChangeState(Selectable source) { + this.source = source; + this.original = source.getState(); + } + + public STATE reached() { + return (source.getState() != original) ? source.getState() : null; + } + } +} --- /dev/null 2017-11-08 15:39:30.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/Wrap.java 2017-11-08 15:39:30.000000000 -0800 @@ -0,0 +1,945 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import org.jemmy.JemmyException; +import org.jemmy.Point; +import org.jemmy.Rectangle; +import org.jemmy.TimeoutExpiredException; +import org.jemmy.action.GetAction; +import org.jemmy.env.Environment; +import org.jemmy.env.TestOut; +import org.jemmy.env.Timeout; +import org.jemmy.image.Image; +import org.jemmy.interfaces.*; +import org.jemmy.timing.State; + +/** + * This is a wrap which holds reference to a control without UI hierarchy. It + * also encapsulates all the logic to deal with the underlying control, in terms + * of implementations of ControlInterface. + * + * @see Wrap#as(java.lang.Class) + * @see Wrap#is(java.lang.Class) + * @param type of the encapsulated object. + * @author shura, erikgreijus + */ +@ControlType(Object.class) +@ControlInterfaces({Mouse.class, Keyboard.class, Drag.class}) +public abstract class Wrap { + + /** + * + */ + public static final String BOUNDS_PROP_NAME = "bounds"; + /** + * + */ + public static final String CLICKPOINT_PROP_NAME = "clickPoint"; + /** + * + */ + public static final String CONTROL_CLASS_PROP_NAME = "control.class"; + /** + * + */ + public static final String CONTROL_PROP_NAME = "control"; + /** + * + */ + public static final String INPUT_FACTORY_PROPERTY = "input.control.interface.factory"; + /** + * + */ + public static final String IMAGE_LOADER_PROPERTY = "image.loader"; + /** + * + */ + public static final String IMAGE_CAPTURER_PROPERTY = "image.capturer"; + /** + * + */ + public static final String TEXT_PROP_NAME = "text"; + /** + * + */ + public static final String POSITION_PROP_NAME = "position"; + /** + * + */ + public static final String VALUE_PROP_NAME = "value"; + /** + * + */ + public static final String WRAPPER_CLASS_PROP_NAME = "wrapper.class"; + /** + * + */ + public static final String TOOLTIP_PROP_NAME = "tooltip"; + /** + * + */ + public static final String NAME_PROP_NAME = "name"; + /** + * + */ + public static final Timeout WAIT_STATE_TIMEOUT = new Timeout("wait.state", 1000); + /** + * + */ + public static final String OUTPUT = Wrap.class.getName() + ".OUTPUT"; + private static DefaultWrapper theWrapper = new DefaultWrapper(Environment.getEnvironment()); + + static { + Environment.getEnvironment().initTimeout(WAIT_STATE_TIMEOUT); + Environment.getEnvironment().initOutput(OUTPUT, TestOut.getNullOutput()); + Environment.getEnvironment().initTimeout(Mouse.CLICK); + Environment.getEnvironment().initTimeout(Drag.BEFORE_DRAG_TIMEOUT); + Environment.getEnvironment().initTimeout(Drag.BEFORE_DROP_TIMEOUT); + Environment.getEnvironment().initTimeout(Drag.IN_DRAG_TIMEOUT); + Environment.getEnvironment().initTimeout(Keyboard.PUSH); + } + + /** + * + * @return + */ + public static DefaultWrapper getWrapper() { + return theWrapper; + } + CONTROL node; + Environment env; + + /** + * Fur null source. + * + * @see org.jemmy.env.Environment + * @param env The environment + */ + protected Wrap(Environment env) { + this.env = env; + node = null; + fillTheProps(false); + } + + /** + * + * @see org.jemmy.env.Environment + * @param env The environment + * @param node The encapsulated object + */ + protected Wrap(Environment env, CONTROL node) { + this.env = env; + this.node = node; + } + + /** + * + * @see org.jemmy.env.Environment + * @return environment instance used by this + */ + public Environment getEnvironment() { + return env; + } + + public void setEnvironment(Environment env) { + this.env = env; + } + + /** + * + * @return The encapsulated object + */ + @Property(CONTROL_PROP_NAME) + public CONTROL getControl() { + return node; + } + + /** + * Return default point to click, drag. This implementation returns the + * center must be overriden if something different is desired. + * + * @return + */ + @Property(CLICKPOINT_PROP_NAME) + public Point getClickPoint() { + return new Point(getScreenBounds().width / 2, (getScreenBounds().height / 2)); + } + + /** + * Returns control bounds in screen coordinates. These bounds could include + * parts that are covered by other controls or clipped out by parent + * components. If the control is not shown {@linkplain + * JemmyException JemmyException} will be thrown. + * + * @return control bounds in screen coordinates. + * @throws JemmyException if the control is not visible + */ + @Property(BOUNDS_PROP_NAME) + public abstract Rectangle getScreenBounds(); + + /** + * Transforms point in local control coordinate system to screen + * coordinates. + * + * @param local + * @return + * @see #toLocal(org.jemmy.Point) + */ + public Point toAbsolute(Point local) { + Rectangle bounds = getScreenBounds(); + return local.translate(bounds.x, bounds.y); + } + + /** + * Transforms point in screen coordinates to local control coordinate + * system. + * + * @param local + * @return coordinates which should be used for mouse operations. + * @see #toAbsolute(org.jemmy.Point) + */ + public Point toLocal(Point local) { + Rectangle bounds = getScreenBounds(); + return local.translate(-bounds.x, -bounds.y); + } + + /** + * Captures the screen area held by the component. ImageFactory performs the + * actual capturing. + * + * @return TODO find a replacement + */ + public Image getScreenImage() { + Rectangle bounds = getScreenBounds(); + return getScreenImage(new Rectangle(0, 0, bounds.width, bounds.height)); + } + + /** + * Captures portion of the screen area held by the component. ImageFactory + * performs the actual capturing. + * + * @param rect Part of the control to capture + * @return TODO find a replacement + */ + public Image getScreenImage(Rectangle rect) { + if (getEnvironment().getImageCapturer() == null) { + throw new JemmyException("Image capturer is not specified."); + } + return getEnvironment().getImageCapturer().capture(this, rect); + } + + /** + * Waits for a portion of image to be exact the same as the parameter. + * + * @see Wrap#as(java.lang.Class) + * @param golden + * @param rect A portion of control to compare. + * @param resID ID of a result image to save in case of failure. No image + * saved if null. + * @param diffID ID of a diff image to save in case of failure. No image + * saved if null. + */ + public void waitImage(final Image golden, final Rectangle rect, String resID, String diffID) { + try { + waitState(new State() { + + public Object reached() { + return (getScreenImage(rect).compareTo(golden) == null) ? true : null; + } + + @Override + public String toString() { + return "Control having expected image"; + } + }); + } catch (TimeoutExpiredException e) { + if (diffID != null) { + getEnvironment().getOutput(OUTPUT).println("Saving difference to " + diffID); + getScreenImage(rect).compareTo(golden).save(diffID); + } + throw e; + } finally { + if (resID != null) { + getEnvironment().getOutput(OUTPUT).println("Saving result to " + resID); + getScreenImage(rect).save(resID); + } + } + } + + /** + * Waits for image to be exact the same as the parameter. + * + * @see Wrap#as(java.lang.Class) + * @param golden + * @param resID ID of a result image to save in case of failure. No image + * saved if null. + * @param diffID ID of a diff image to save in case of failure. No image + * saved if null. + */ + public void waitImage(final Image golden, String resID, String diffID) { + Rectangle bounds = getScreenBounds(); + waitImage(golden, new Rectangle(0, 0, bounds.width, bounds.height), resID, diffID); + } + + /** + * TODO javadoc + * + * @param + * @param state + * @param value + * @return last returned State value + * @throws TimeoutExpiredException in case the wait is unsuccessful. + */ + public V waitState(State state, V value) { + return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, state); + } + + /** + * TODO javadoc + * + * @param + * @param state + * @return last returned State value + * @throws TimeoutExpiredException in case the wait is unsuccessful. + */ + public V waitState(State state) { + return getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureState(state); + } + + /** + * *********************************************************************** + */ + /* + * INTERFACES + */ + /** + * *********************************************************************** + */ + private Method findAsMethod(Class interfaceClass, Class type) { + while (type != null) { + for (Method m : getClass().getMethods()) { + As as = m.getAnnotation(As.class); + Class returnType = m.getReturnType(); + if (as != null && interfaceClass.isAssignableFrom(returnType) && as.value().equals(type)) { + if (m.getParameterTypes().length > 0 && type.equals(Void.class) + || m.getParameterTypes().length > 1 && !type.equals(Void.class)) { + throw new IllegalStateException("wrong number of parameters in an @As method"); + } + return m; + } + } + type = type.getSuperclass(); + } + return null; + } + + /** + * Checks if the control could be treated as a ControlInterface. If it is, + * Wrap#as(java.lang.Class) will be called. This implementation + * checks whether the class implements the necessary interface. It also + * works for root interfaces such as + * MouseTarget and + * KeyTarget, which implementations are encapsulated. If some + * other functionality is desired, must be overriden together with + * as(java.lang.Class) + * + * @see Wrap#is(java.lang.Class) + * @param + * @param interfaceClass + * @return + */ + public boolean is(Class interfaceClass) { + if (interfaceClass.isInstance(this)) { + return true; + } + return findAsMethod(interfaceClass, Void.class) != null; + } + + /** + * Checks if the control could be treated as a parametrized + * ControlInterface. If it is, + * Wrap#as(java.lang.Class, java.lang.Class) will be called. + * This implementation checks whether the class implements the necessary + * interface. It also works for root interfaces such as + * MouseTarget and + * KeyTarget, which implementations are encapsulated. If some + * other functionality is desired, must be overriden together with + * as(java.lang.Class) + * + * @see Wrap#is(java.lang.Class) + * @param + * @param + * @param interfaceClass + * @param type The parameter class. + * @return + */ + public > boolean is(Class interfaceClass, Class type) { + if (interfaceClass.isInstance(this)) { + if (interfaceClass.cast(this).getType().isAssignableFrom(type)) { + return true; + } + } + return findAsMethod(interfaceClass, type) != null; + } + + private Object callAsMethod(Class interfaceClass, Class type) { + Method m = findAsMethod(interfaceClass, type); + if (m != null) { + try { + if (m.getParameterTypes().length == 0) { + return m.invoke(this); + } else if (m.getParameterTypes().length == 1) { + return m.invoke(this, !type.equals(Void.class) ? type : Object.class); + } else { + throw new InterfaceException(this, interfaceClass); + } + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this); + } catch (InvocationTargetException ex) { + throw new JemmyException("Unable to call method \"" + m.getName() + "()\"", ex, this); + } + } + return null; + } + + /** + * Returns an implementation of interface associated with this object. First + * it checks + * + * @see Wrap#is(java.lang.Class) + * @param + * @param interfaceClass + * @return + */ + public INTERFACE as(Class interfaceClass) { + if (interfaceClass.isInstance(this)) { + return interfaceClass.cast(this); + } + + Object res = callAsMethod(interfaceClass, Void.class); + if (res != null) { + return (INTERFACE) res; + } + + throw new InterfaceException(this, interfaceClass); + } + + /** + * Returns an implementation of interface associated with the object. + * + * @see Wrap#is(java.lang.Class) + * @param + * @param + * @param interfaceClass + * @param type The parameter class. + * @return + */ + public > INTERFACE as(Class interfaceClass, Class type) { + if (interfaceClass.isInstance(this)) { + if (interfaceClass.cast(this).getType().isAssignableFrom(type)) { + return interfaceClass.cast(this); + } + } + + Object res = callAsMethod(interfaceClass, type); + if (res != null) { + return (INTERFACE) res; + } + + throw new InterfaceException(this, interfaceClass); + } + /** + * *********************************************************************** + */ + /* + * INPUT + */ + /** + * *********************************************************************** + */ + private Mouse mouse = null; + private Drag drag = null; + private Keyboard keyboard = null; + + /** + * A shortcut to + * as(MouseTarget.class).mouse() + * + * @return + */ + @As(Mouse.class) + public Mouse mouse() { + if (mouse == null) { + mouse = getEnvironment().getInputFactory().create(this, Mouse.class); + } + return mouse; + } + + /** + * A shortcut to + * as(MouseTarget.class).drag() + * + * @return + */ + @As(Drag.class) + public Drag drag() { + if (drag == null) { + drag = getEnvironment().getInputFactory().create(this, Drag.class); + } + return drag; + } + + /** + * A shortcut to + * as(KeyTarget.class).wrap() + * + * @return + */ + @As(Keyboard.class) + public Keyboard keyboard() { + if (keyboard == null) { + keyboard = getEnvironment().getInputFactory().create(this, Keyboard.class); + } + return keyboard; + } + /** + * *********************************************************************** + */ + /* + * PROPERTIES + */ + /** + * *********************************************************************** + */ + private HashMap properties = new HashMap(); + + /** + * + * @return + */ + @Property(CONTROL_CLASS_PROP_NAME) + public Class getControlClass() { + return getControl().getClass(); + } + + private void fillTheProps(boolean quiet) { + properties.clear(); + properties.put(WRAPPER_CLASS_PROP_NAME, getClass()); + readAnnotationProps(quiet); + readControlProps(quiet); + } + + private void readControlProps(boolean quiet) { + Class cls = getClass(); + do { + if (cls.isAnnotationPresent(FieldProperties.class)) { + for (String s : cls.getAnnotation(FieldProperties.class).value()) { + Object value; + try { + value = getFieldProperty(s); + } catch (Exception e) { + getEnvironment().getOutput().printStackTrace(e); + value = e.toString(); + if (!(e instanceof JemmyException) && !quiet) { + throw new JemmyException("Exception while getting property \"" + s + "\"", e); + } + } + properties.put(s, value); + } + } + if (cls.isAnnotationPresent(MethodProperties.class)) { + for (String s : cls.getAnnotation(MethodProperties.class).value()) { + Object value; + try { + value = getMethodProperty(s); + } catch (Exception e) { + getEnvironment().getOutput().printStackTrace(e); + value = e.toString(); + if (!(e instanceof JemmyException) && !quiet) { + throw new JemmyException("Exception while getting property \"" + s + "\"", e); + } + } + properties.put(s, value); + } + } + } while ((cls = cls.getSuperclass()) != null); + } + + private void addAnnotationProps(Class cls, boolean quiet) { + for (Method m : cls.getMethods()) { + if (m.isAnnotationPresent(Property.class)) { + String name = m.getAnnotation(Property.class).value(); + if (!properties.containsKey(name)) { + Object value; + try { + value = getProperty(this, m); + } catch (Exception e) { + if (quiet) { + getEnvironment().getOutput().printStackTrace(e); + value = e.toString(); + } else { + throw new JemmyException("Exception while getting property \"" + name + "\"", e); + } + } + properties.put(name, value); + } + } + } + } + + private void readAnnotationProps(boolean quiet) { + Class cls = getClass(); + do { + addAnnotationProps(cls, quiet); + } while ((cls = cls.getSuperclass()) != null); + for (Class intf : getClass().getInterfaces()) { + addAnnotationProps(intf, quiet); + } + } + + private void checkPropertyMethod(Method m) { + if (m.getParameterTypes().length > 0) { + throw new JemmyException("Method marked by @Property must not have parameters: " + + m.getDeclaringClass().getName() + "." + m.getName()); + } + } + + private Method getPropertyMethod(Class cls, String name) { + Class scls = cls; + do { + for (Method m : scls.getMethods()) { + if (m.isAnnotationPresent(Property.class) && m.getAnnotation(Property.class).value().equals(name)) { + checkPropertyMethod(m); + return m; + } + } + } while ((scls = scls.getSuperclass()) != null); + for (Class intf : cls.getInterfaces()) { + for (Method m : intf.getMethods()) { + if (m.isAnnotationPresent(Property.class) && m.getAnnotation(Property.class).value().equals(name)) { + checkPropertyMethod(m); + return m; + } + } + } + return null; + } + + private Object getProperty(Object object, Method m) { + Property prop = m.getAnnotation(Property.class); + try { + return m.invoke(object); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this); + } catch (InvocationTargetException ex) { + throw new JemmyException("Unable to obtain property \"" + ((prop != null) ? prop.value() : "null") + "\"", ex, this); + } + } + + /** + * Get property of the wrapped object. Uses first available from + *
  • methods annotated by + * org.jemmy.control.Property
  • wrapped object methods + * listed in + * org.jemmy.control.MethodProperties
  • wrapped object + * fields listed in + * org.jemmy.control.FieldProperties
  • + * + * @param name property name + * @throws JemmyException if no property found + * @see Property + * @see MethodProperties + * @see FieldProperties + * @return property value + */ + public Object getProperty(String name) { + if (WRAPPER_CLASS_PROP_NAME.equals(name)) { + return getClass(); + } + Method m = getPropertyMethod(this.getClass(), name); + if (m != null) { + return getProperty(this, m); + } + if (hasMethodProperty(name)) { + return getMethodProperty(name); + } + if (hasFieldProperty(name)) { + return getFieldProperty(name); + } + throw new JemmyException("No property \"" + name + "\"", this); + } + + private Object getInterfaceProperty(Class cls, Object instance, String name) { + Method m = getPropertyMethod(cls, name); + if (m != null) { + return getProperty(instance, m); + } + throw new JemmyException("No property \"" + name + "\" in interface " + cls.getName(), instance); + } + + /** + * Get property out of the control interface. Refer to the interface doc to + * find out what properties are provided. + * + * @param + * @param name + * @param intrfc + * @return + */ + public Object getProperty(String name, Class intrfc) { + return getInterfaceProperty(intrfc, as(intrfc), name); + } + + /** + * Get property out of the control interface. Refer to the interface doc to + * find out what properties are provided. + * + * @param + * @param + * @param name + * @param intrfc + * @param type + * @return + */ + public > Object getProperty(String name, Class intrfc, Class type) { + return getInterfaceProperty(intrfc, as(intrfc, type), name); + } + + /** + * Wait for the property + * property to get the specified value. + * WAIT_STATE_TIMOUT timeout is used + * + * @param property name of the property being waited for + * @param value property value to wait + */ + public void waitProperty(final String property, final Object value) { + getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State() { + + public Object reached() { + return getProperty(property); + } + + @Override + public String toString() { + return "Control having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')"; + } + }); + } + + /** + * Wait for the property + * property of control interface to get the specified value. + * WAIT_STATE_TIMOUT timeout is used + * + * @param + * @param property + * @param intrfc + * @param value + */ + public void waitProperty(final String property, final Class intrfc, final Object value) { + Object instance = as(intrfc); + getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State() { + + public Object reached() { + return getProperty(property, intrfc); + } + + @Override + public String toString() { + return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property, intrfc) + "')"; + } + }); + } + + /** + * Wait for the property + * property of control interface to get the specified value. + * WAIT_STATE_TIMOUT timeout is used + * + * @param + * @param + * @param property + * @param intrfc + * @param type + * @param value + */ + public > void waitProperty(final String property, final Class intrfc, final Class type, final Object value) { + getEnvironment().getWaiter(WAIT_STATE_TIMEOUT).ensureValue(value, new State() { + + public Object reached() { + return getProperty(property, intrfc, type); + } + + @Override + public String toString() { + return "Interface " + intrfc.getName() + " having property " + property + " expected value '" + value + "' (Property = '" + getProperty(property) + "')"; + } + }); + } + + /** + * + * @param name + * @return + */ + public boolean hasFieldProperty(String name) { + Class cls = getClass(); + do { + if (cls.isAnnotationPresent(FieldProperties.class)) { + FieldProperties props = cls.getAnnotation(FieldProperties.class); + if (contains(props.value(), name)) { + return true; + } + } + } while ((cls = cls.getSuperclass()) != null); + return false; + } + + /** + * + * @param name + * @return + */ + public boolean hasMethodProperty(String name) { + Class cls = getClass(); + do { + if (cls.isAnnotationPresent(MethodProperties.class)) { + MethodProperties props = cls.getAnnotation(MethodProperties.class); + if (contains(props.value(), name)) { + return true; + } + } + } while ((cls = cls.getSuperclass()) != null); + return false; + } + + private boolean contains(String[] values, String name) { + for (int i = 0; i < values.length; i++) { + if (name.equals(values[i])) { + return true; + } + + } + return false; + } + + /** + * + * @param name + * @return + */ + public Object getFieldProperty(final String name) { + if (!hasFieldProperty(name)) { + throw new JemmyException("No \"" + name + "\" field property specified on " + getClass().getName()); + } + GetAction action = new GetAction() { + + @Override + public void run(Object... parameters) throws Exception { + setResult(getControl().getClass().getField(name).get(getControl())); + } + }; + Object result = action.dispatch(env); + if (action.getThrowable() != null) { + throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this); + } + return result; + } + + /** + * + * @param name + * @return + */ + public Object getMethodProperty(final String name) { + if (!hasMethodProperty(name)) { + throw new JemmyException("No \"" + name + "\" method property specified on " + getClass().getName()); + } + GetAction action = new GetAction() { + + @Override + public void run(Object... parameters) throws Exception { + setResult(getControl().getClass().getMethod(name).invoke(getControl())); + } + + @Override + public String toString() { + return "Getting property \"" + name + "\" on " + getClass().getName(); + } + }; + Object result = action.dispatch(env); + if (action.getThrowable() != null) { + throw new JemmyException("Unable to obtain property \"" + name + "\"", action.getThrowable(), this); + } + return result; + } + + /** + * + * @param

    + * @param valueClass + * @param name + * @return + */ + public

    P getProperty(Class

    valueClass, String name) { + return valueClass.cast(getProperty(name)); + } + + /** + * Returns a a map of all known controls properties including values from + * methods marked by + * @Property and values of methods/field from + * @MethodProperties/ + * FieldProperties correspondingly. + * + * @return a map of properties + * @throws Runtime exception should there be an exception thrown while + * getting a property + */ + public HashMap getProperties() { + fillTheProps(false); + return properties; + } + + /** + * Returns a a map of all controls properties which is possible to obtain. + * Similar to + * getProperties() only exception is swallowed should there be + * an exception thrown while getting a property. + * + * @return a map of properties which were possible to obtain. + */ + public HashMap getPropertiesQiuet() { + fillTheProps(true); + return properties; + } +} --- /dev/null 2017-11-08 15:39:31.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/Wrapper.java 2017-11-08 15:39:31.000000000 -0800 @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +/** + * + * @author shura + */ +public interface Wrapper { + /** + * + * @param + * @param controlClass + * @param control + * @return Wrap + */ + public Wrap wrap(Class controlClass, T control); +} --- /dev/null 2017-11-08 15:39:31.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/WrapperDelegate.java 2017-11-08 15:39:31.000000000 -0800 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + +import org.jemmy.env.Environment; + +/** + * This allows to reuse another {@code Wrapper} with a different environment + * @author shura + */ +public class WrapperDelegate implements Wrapper { + private final Wrapper real; + private final Environment env; + + public WrapperDelegate(Wrapper real, Environment env) { + this.real = real; + this.env = env; + } + + public Wrap wrap(Class controlClass, T control) { + Wrap res = real.wrap(controlClass, control); + res.setEnvironment(new Environment(env)); + return res; + } +} --- /dev/null 2017-11-08 15:39:31.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/WrapperException.java 2017-11-08 15:39:31.000000000 -0800 @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.control; + +import org.jemmy.JemmyException; + +/** + * + * @author shura + */ +public class WrapperException extends JemmyException { + + /** + * + * @param + * @param controlClass + * @param wrapperClass + * @param e + */ + public WrapperException(Class controlClass, Class wrapperClass, Exception e) { + super(controlClass.getName() + " is not accepted by " + wrapperClass.getName(), e); + } + + /** + * + * @param + * @param controlClass + * @param wrapperClass + */ + public WrapperException(Class controlClass, Class wrapperClass) { + super(controlClass.getName() + " is not accepted by " + wrapperClass.getName()); + } + + /** + * + * @param + * @param control + */ + public WrapperException(Object control) { + super("Unable to find a wrapper", control); + } + +} --- /dev/null 2017-11-08 15:39:32.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/control/WrapperImpl.java 2017-11-08 15:39:32.000000000 -0800 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.control; + + +import java.util.HashMap; +import org.jemmy.env.Environment; + + +/** + * This is an implementation of {@code Wrapper} which keeps a map between + * control classes and wrap classes. + * @author shura + */ +public class WrapperImpl extends AbstractWrapper { + + private HashMap> theWrappers; + + /** + * + * @param env + */ + @SuppressWarnings("unchecked") + public WrapperImpl(Environment env) { + super(env); + theWrappers = new HashMap>(); + } + + /** + * + * @param

    + * @param controlClass + * @param wrapperClass + */ + public

    void add(Class controlClass, Class wrapperClass) { + theWrappers.put(controlClass, wrapperClass); + // TODO: Improve output +// getEnvironment().getOutput().println("Added \"" + wrapperClass.getName() + "\"" + +// " wrapper for \"" + controlClass.getName() + "\" control type"); + } + + @Override + protected Class getWrapClass(Class controlClass) { + return theWrappers.get(controlClass); + } + +} --- /dev/null 2017-11-08 15:39:32.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/DefaultParent.java 2017-11-08 15:39:32.000000000 -0800 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import org.jemmy.interfaces.Parent; + +/** + * Putting this on a public static method allows to generate constructors which + * do not take parent as a parameter, taking whatever is coming from this method + * instead. An annotated method should take one parameter - class of the controls + * to by looked for and should return parent for that type. + * @see Parent + * @author shura + */ +@Target(ElementType.METHOD) +@Documented +public @interface DefaultParent { + /** + * Description of a parent represented by the annotated method. + */ + String value(); +} --- /dev/null 2017-11-08 15:39:32.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/DefaultWrapper.java 2017-11-08 15:39:32.000000000 -0800 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Putting this on a public static method allows to generate constructors which + * do not take parent as a parameter, taking whatever is coming from this method + * instead. An annotated method should take one parameter - class of the controls + * to by looked for and should return parent for that type. + * @see Parent + * @author shura + */ +@Target(ElementType.METHOD) +@Documented +public @interface DefaultWrapper { +} --- /dev/null 2017-11-08 15:39:33.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/Dock.java 2017-11-08 15:39:33.000000000 -0800 @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.dock; + +import org.jemmy.Rectangle; +import org.jemmy.action.GetAction; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.interfaces.Drag; +import org.jemmy.interfaces.Keyboard; +import org.jemmy.interfaces.Mouse; +import org.jemmy.interfaces.Parent; +import org.jemmy.lookup.Lookup; +import org.jemmy.lookup.LookupCriteria; + +/** + * Superclass for all "docks" - classes which simple provide API for lookup, interfaces + * and properties. + * @author shura + */ +public class Dock { + + /** + * Default suffix to construct result image name. + */ + public static final String DEFAULT_RESULT_IMAGE_SUFFIX = "default.result.image.suffix"; + /** + * Default suffix to construct diff image name. + */ + public static final String DEFAULT_DIFF_IMAGE_SUFFIX = "default.diff.image.suffix"; + + static { + Environment.getEnvironment().setPropertyIfNotSet(DEFAULT_DIFF_IMAGE_SUFFIX, "-diff"); + Environment.getEnvironment().setPropertyIfNotSet(DEFAULT_RESULT_IMAGE_SUFFIX, "-result"); + } + + private Wrap wrap; + + protected Dock(Wrap wrap) { + this.wrap = wrap; + } + + /** + * Method which at the end actually get called from all dock lookup + * constructors. + * @param + * @param parent + * @param controlType + * @param index + * @param criteria + * @return + */ + protected static Wrap lookup(Parent parent, Class controlType, int index, LookupCriteria... criteria) { + Lookup lookup; + if (criteria.length > 0) { + lookup = parent.lookup(controlType, criteria[0]); + for (int i = 1; i < criteria.length; i++) { + lookup = lookup.lookup(controlType, criteria[i]); + } + } else { + lookup = parent.lookup(controlType); + } + return lookup.wrap(index); + } + + /** + * + * @return Wrap instance obtainer through lookup + */ + public Wrap wrap() { + return wrap; + } + + /** + * + * @return Wrap instance obtainer through lookup + */ + public Object control() { + return wrap.getControl(); + } + + /** + * Shortcut to wrap().mouse() + */ + public Mouse mouse() { + return wrap.mouse(); + } + + /** + * Shortcut to wrap().keyboard() + */ + public Keyboard keyboard() { + return wrap.keyboard(); + } + + /** + * Shortcut to wrap().drag() + */ + public Drag drag() { + return wrap.drag(); + } + + /** + * Shortcut to wrap().getScreenBounds() + */ + public Rectangle bounds() { + return wrap.getScreenBounds(); + } + + protected

    P getProperty(GetAction

    action) { + action.execute(); + return action.getResult(); + } + + /** + * + * @return wrap().getEnvironment(). + */ + public Environment environment() { + return wrap.getEnvironment(); + } + + /** + * Loads image with goldenId id waits for the control to match it. + * @see Wrap#waitImage(org.jemmy.image.Image, org.jemmy.Rectangle, java.lang.String, java.lang.String) + */ + public void waitImage(String goldenId, Rectangle rect, String resID, String diffID) { + wrap.waitImage(environment().getImageLoader().load(goldenId), rect, resID, diffID); + } + + /** + * Constructs names for diff and result images and waits for the control to match it. + * Diff and result names + * constructed by adding suffixes. Suffixes are obtained from environment with + * default values being "-diff" and "-result" + * @see #waitImage(java.lang.String, org.jemmy.Rectangle, java.lang.String, java.lang.String) + * @see #DEFAULT_DIFF_IMAGE_SUFFIX + * @see #DEFAULT_RESULT_IMAGE_SUFFIX + */ + public void waitImage(String goldenId, Rectangle rect) { + waitImage(goldenId, + rect, + goldenId + environment().getProperty(DEFAULT_RESULT_IMAGE_SUFFIX), + goldenId + environment().getProperty(DEFAULT_DIFF_IMAGE_SUFFIX)); + } + + /** + * Loads image with goldenId id waits for the control to match it. + * @see Wrap#waitImage(org.jemmy.image.Image, java.lang.String, java.lang.String) + */ + public void waitImage(String goldenId, String resID, String diffID) { + wrap.waitImage(environment().getImageLoader().load(goldenId), resID, diffID); + } + + /** + * Constructs names for diff and result images and waits for the control to match it. + * Diff and result names + * constructed by adding suffixes. Suffixes are obtained from environment with + * default values being "-diff" and "-result" + * @see #waitImage(java.lang.String, java.lang.String, java.lang.String) + * @see #DEFAULT_DIFF_IMAGE_SUFFIX + * @see #DEFAULT_RESULT_IMAGE_SUFFIX + */ + public void waitImage(String goldenId) { + waitImage(goldenId, + goldenId + environment().getProperty(DEFAULT_RESULT_IMAGE_SUFFIX), + goldenId + environment().getProperty(DEFAULT_DIFF_IMAGE_SUFFIX)); + } + } --- /dev/null 2017-11-08 15:39:33.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/DockInfo.java 2017-11-08 15:39:33.000000000 -0800 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import org.jemmy.control.Wrap; + +/** + * This should be used on classes - inheritors of Wrap class to give + * annotation processor some information. + * @see Wrap + * @author shura + */ +@Target(ElementType.TYPE) +@Documented +public @interface DockInfo { + /** + * Desired name of the dock class, should one be generated. + * Usually empty ("", as nulls are not allowed) in which case the calculated value + * is taken whatever logic annotation processor decides to use. + */ + String name() default ""; + + /** + * Should there be extra constructors which take another lookup criteria - a class + * of a desired control? That class must be a subtype of the one wrapped by the wrap + * class annotated with this annotation. + */ + boolean generateSubtypeLookups() default false; + + /** + * Should generated wrap() method return this class or + * Wrap and also should there be a constructor with + * one parameter - the wrap. + */ + boolean anonymous() default false; + + /** + * Should the lookup constructors have LookupCriteria<Type>... + * parameter or the LookupCriteria<Type> parameter. + */ + boolean multipleCriteria() default true; +} --- /dev/null 2017-11-08 15:39:33.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/ObjectLookup.java 2017-11-08 15:39:33.000000000 -0800 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import org.jemmy.interfaces.Parent; +import org.jemmy.lookup.LookupCriteria; + +/** + * This should be used to allow to build dock lookup constructors which take + * some values (such as text, value, orientation, etc) instead of lookup criteria. + * Methods annotated with this should take class of the looked up control as a + * first parameter and return parent around that type. + * @see Parent + * @see LookupCriteria + * @author shura + */ +@Target(ElementType.METHOD) +@Documented +public @interface ObjectLookup { + /** + * Description of the method parameters. To be used in the javadoc later. + */ + String value(); +} --- /dev/null 2017-11-08 15:39:34.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/PreferredParent.java 2017-11-08 15:39:34.000000000 -0800 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import org.jemmy.control.Wrap; +import org.jemmy.interfaces.Parent; + +/** + * The annotation to be used on a child-type wrap (such as ListItemWrap), pointing + * to a parent type wrap (such as ListViewWrap). + */ +@Target(ElementType.TYPE) +@Documented +public @interface PreferredParent { + Class value(); +} --- /dev/null 2017-11-08 15:39:34.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/dock/Shortcut.java 2017-11-08 15:39:34.000000000 -0800 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.dock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Andrey Nazarov + */ +@Target(ElementType.METHOD) +@Documented +@Retention(RetentionPolicy.CLASS) +public @interface Shortcut { + /** + * If name is not specified, then shortcut's name will same as name of interface method + */ + String name() default ""; +} --- /dev/null 2017-11-08 15:39:34.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/env/Environment.java 2017-11-08 15:39:34.000000000 -0800 @@ -0,0 +1,631 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.env; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import org.jemmy.JemmyException; +import org.jemmy.action.ActionExecutor; +import org.jemmy.action.DefaultExecutor; +import org.jemmy.control.Wrap; +import org.jemmy.image.ImageCapturer; +import org.jemmy.image.ImageLoader; +import org.jemmy.input.CharBindingMap; +import org.jemmy.interfaces.ControlInterfaceFactory; +import org.jemmy.timing.Waiter; + +/** + * + * @author shura, mrkam, erikgreijus + */ +public class Environment { + + /** + * + */ + public static final String JEMMY_PROPERTIES_FILE_PROPERTY = "jemmy.properties"; + public static final String TIMEOUTS_FILE_PROPERTY = "timeouts"; + /** + * Information output for Environment class + */ + public static final String OUTPUT = Environment.class.getName() + ".OUTPUT"; + private final static Environment env = new Environment(null); + + /** + * + * @return + */ + public static Environment getEnvironment() { + return env; + } + + static { + env.setOutput(new TestOut(System.in, System.out, System.err)); + env.setExecutor(new DefaultExecutor()); + } + private HashMap environment = new HashMap(); + private Environment parent; + + /** + * + * @param parent + */ + public Environment(Environment parent) { + this.parent = parent; + environment = new HashMap(); + if (parent == null) { + loadProperties(System.getProperty(JEMMY_PROPERTIES_FILE_PROPERTY)); + } + } + + /** + * + */ + public Environment() { + this(getEnvironment()); + } + + /** + * + * @return + */ + public Environment getParentEnvironment() { + return parent; + } + + /** + * + * @param parent + */ + public void setParentEnvironment(Environment parent) { + this.parent = parent; + } + + public void loadProperties(String propFileName) { + if (propFileName == null || propFileName.length() == 0) { + propFileName = System.getProperty("user.home") + File.separator + ".jemmy.properties"; + } + File propFile = new File(propFileName); + System.out.println("Loading jemmy properties from " + propFile); + if (propFile.exists()) { + Properties props = new Properties(); + try { + props.load(new FileInputStream(propFile)); + } catch (IOException ex) { + throw new JemmyException("Unable to load properties", ex, propFileName); + } + for (String k : props.stringPropertyNames()) { + if (k.equals(TIMEOUTS_FILE_PROPERTY)) { + loadTimeouts(propFile.getParentFile(), props.getProperty(k)); + } else { + setProperty(k, props.getProperty(k)); + } + } + } else { + System.out.println("Property file " + propFile + " does not exists. Ignoring."); + } + } + + private void loadTimeouts(File propDir, String file) { + File timeoutsFile = new File(file); + if (!timeoutsFile.isAbsolute()) { + timeoutsFile = new File(propDir.getAbsolutePath() + File.separator + file); + } + System.out.println("Loading timeouts from " + timeoutsFile.getAbsolutePath()); + try { + Properties timeouts = new Properties(); + timeouts.load(new FileInputStream(timeoutsFile)); + for (String k : timeouts.stringPropertyNames()) { + setTimeout(k, Long.parseLong(timeouts.getProperty(k))); + } + } catch (IOException ex) { + throw new JemmyException("Unable to load timeouts", ex, timeoutsFile.getAbsolutePath()); + } + } + + /** + * + * @param cls + * @return + */ + public List get(Class cls) { + Set all = environment.keySet(); + ArrayList result = new ArrayList(); + for (PropertyKey key : all) { + if (key.getCls().equals(cls)) { + result.add(environment.get(key)); + } + } + return result; + } + + /** + * + * @param defaultExecutor + * @return + */ + public ActionExecutor setExecutor(ActionExecutor defaultExecutor) { + return (ActionExecutor) setProperty(ActionExecutor.class, defaultExecutor); + } + + /** + * + * @return + */ + public ActionExecutor getExecutor() { + ActionExecutor res = (ActionExecutor) getProperty(ActionExecutor.class); + if (res == null) { + String executorClassName = (String) getProperty(ActionExecutor.ACTION_EXECUTOR_PROPERTY); + try { + res = ActionExecutor.class.cast(Class.forName(executorClassName).newInstance()); + setExecutor(res); + } catch (InstantiationException ex) { + throw new JemmyException("Unable to instantiate executor ", ex, executorClassName); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to instantiate executor ", ex, executorClassName); + } catch (ClassNotFoundException ex) { + throw new JemmyException("No executorclass ", ex, executorClassName); + } + } + return res; + } + + public T setProperty(Class cls, Object ref, T obj) { + return setProperty(new PropertyKey(cls, ref), obj); + } + + private T setPropertyIfNotSet(Class cls, Object ref, T obj) { + return setPropertyIfNotSet(new PropertyKey(cls, ref), obj); + } + + private T getProperty(Class cls, Object ref) { + return getProperty(cls, ref, null); + } + + @SuppressWarnings("unchecked") + public T getProperty(Class cls, Object ref, T defaultValue) { + for (PropertyKey pk : environment.keySet()) { + if (pk.equals(new PropertyKey(cls, ref))) { + return (T) environment.get(pk); + } + } + if (getParentEnvironment() != null) { + return getParentEnvironment().getProperty(cls, ref, defaultValue); + } else { + return defaultValue; + } + } + + /** + * + * @param + * @param cls + * @param obj if null then property is removed + * @return + */ + public T setProperty(Class cls, T obj) { + return setProperty(cls, null, obj); + } + + /** + * + * @param + * @param cls + * @param obj if null then property is removed + * @return + */ + public T setPropertyIfNotSet(Class cls, T obj) { + return setPropertyIfNotSet(cls, null, obj); + } + + /** + * + * @param + * @param cls + * @return + */ + public T getProperty(Class cls) { + return getProperty(cls, null); + } + + /** + * + * @param name + * @param obj if null then property is removed + * @return + */ + public Object setProperty(String name, Object obj) { + return setProperty(Object.class, name, obj); + } + + /** + * + * @param name + * @param obj + * @return + */ + public Object setPropertyIfNotSet(String name, Object obj) { + return setPropertyIfNotSet(Object.class, name, obj); + } + + /** + * + * @param name + * @return + */ + public Object getProperty(String name) { + return getProperty(Object.class, name); + } + + /** + * + * @param name + * @param defaultValue + * @return + */ + public Object getProperty(String name, Object defaultValue) { + return getProperty(Environment.class, name, defaultValue); + } + + private T setProperty(PropertyKey key, Object value) { + if (value == null) { + return key.cls.cast(environment.remove(key)); + } else { + return key.cls.cast(environment.put(key, value)); + } + } + + private T setPropertyIfNotSet(PropertyKey key, T value) { + if (getParentEnvironment() != null) { + T res = key.cls.cast(getParentEnvironment().getProperty(key)); + if (res != null) { + return res; + } + } + T res = key.cls.cast(environment.get(key)); + if (res == null) { + return key.cls.cast(environment.put(key, value)); + } else { + return res; + } + } + + private Object getProperty(PropertyKey key) { + return environment.get(key); + } + + /** + * + * @param out + * @return + */ + public TestOut setOutput(TestOut out) { + return (TestOut) setProperty(TestOut.class, out); + } + + /** + * + * @return + */ + public TestOut getOutput() { + return (TestOut) getProperty(TestOut.class); + } + + /** + * Set some specific output. All classes which provide output should use + * some specific outputs. Please consult javadoc for a class in question. + * Use null to unset the property. + * + * @param outputName + * @param out + * @return + */ + public TestOut setOutput(String outputName, TestOut out) { + return (TestOut) setProperty(TestOut.class, outputName, out); + } + + /** + * Initializes some specific output only if it is not yet set. + * + * @param outputName + * @param out + * @return + */ + public TestOut initOutput(String outputName, TestOut out) { + TestOut res = (TestOut) getProperty(TestOut.class, outputName); + if (res == null) { + return setOutput(outputName, out); + } else { + return res; + } + } + + /** + * Get's a specific output. If nothing assigned, returns + * getOutput() + * + * @param outputName + * @return + */ + public TestOut getOutput(String outputName) { + TestOut res = (TestOut) getProperty(TestOut.class, outputName); + return (res != null) ? res : getOutput(); + } + + /** + * + * @param timeout + * @return + */ + public Waiter getWaiter(Timeout timeout) { + return getWaiter(timeout.getName()); + } + + /** + * + * @param timeoutName + * @return + */ + public Waiter getWaiter(String timeoutName) { + return new Waiter(getTimeout(timeoutName)); + } + + /** + * + * @param timeout + * @return + */ + public Timeout getTimeout(Timeout timeout) { + return getTimeout(timeout.getName()); + } + + /** + * + * @param name + * @return + */ + public Timeout getTimeout(String name) { + return (Timeout) getProperty(Timeout.class, name); + } + + /** + * Sets timeout. + * + * @param timeout Timeout to set. + * @return replaced timeout if it was already set. + */ + public Timeout setTimeout(Timeout timeout) { + return (Timeout) setProperty(Timeout.class, timeout.getName(), timeout); + } + + /** + * Initializes timeout only if it is not set. + * + * @param timeout Timeout to set. + * @return replaced timeout if it was already set. + */ + public Timeout initTimeout(Timeout timeout) { + if (getProperty(Timeout.class, timeout.getName()) == null) { + return setTimeout(timeout); + } + return getTimeout(timeout); + } + + /** + * Sets new value for the timeout specified by Timeout object instance. + * + * @param timeout Timeout object instance which identifies the name of the + * timeout to set. + * @param value new value for the timout. + * @return replaced timeout if it was already set. + */ + public Timeout setTimeout(Timeout timeout, long value) { + return setTimeout(timeout.getName(), value); + } + + /** + * Sets new value for the timeout. + * + * @param name Name of the timeout. + * @param value Value of the timeout. + * @return replaced timeout if it was already set. + */ + public Timeout setTimeout(String name, long value) { + return setTimeout(new Timeout(name, value)); + } + + /** + * + * @return + */ + public CharBindingMap getBindingMap() { + return (CharBindingMap) getProperty(CharBindingMap.class); + } + + /** + * + * @param map + * @return + */ + public CharBindingMap setBindingMap(CharBindingMap map) { + return (CharBindingMap) setProperty(CharBindingMap.class, map); + } + + /** + * + * @return + */ + public ImageLoader getImageLoader() { + ImageLoader res = (ImageLoader) getProperty(ImageLoader.class); + if (res == null) { + String loaderClass = (String) getProperty(Wrap.IMAGE_LOADER_PROPERTY); + if (loaderClass == null) { + throw new IllegalStateException("No image loader provided!"); + } + try { + res = ImageLoader.class.cast(Class.forName(String.class.cast(loaderClass)).newInstance()); + setImageLoader(res); + } catch (InstantiationException ex) { + throw new JemmyException("Unable to instantiate image loader ", ex, loaderClass); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to instantiate image loader ", ex, loaderClass); + } catch (ClassNotFoundException ex) { + throw new JemmyException("No image loader class ", ex, loaderClass); + } + } + return res; + } + + /** + * + * @return + */ + public ImageCapturer getImageCapturer() { + ImageCapturer res = (ImageCapturer) getProperty(ImageCapturer.class); + if (res == null) { + String capturerClass = (String) getProperty(Wrap.IMAGE_CAPTURER_PROPERTY); + if (capturerClass == null) { + throw new IllegalStateException("No image capturer provided!"); + } + try { + res = ImageCapturer.class.cast(Class.forName(String.class.cast(capturerClass)).newInstance()); + setImageCapturer(res); + } catch (InstantiationException ex) { + throw new JemmyException("Unable to instantiate image capturer ", ex, capturerClass); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to instantiate image capturer ", ex, capturerClass); + } catch (ClassNotFoundException ex) { + throw new JemmyException("No image capturer class ", ex, capturerClass); + } + } + return res; + } + + /** + * + * @param imageLoader + * @return + */ + public ImageLoader setImageLoader(ImageLoader imageLoader) { + return (ImageLoader) setProperty(ImageLoader.class, imageLoader); + } + + /** + * + * @param imageCapturer + * @return + */ + public ImageCapturer setImageCapturer(ImageCapturer imageCapturer) { + getOutput(OUTPUT).println("ImageCapturer set to " + imageCapturer); + return (ImageCapturer) setProperty(ImageCapturer.class, imageCapturer); + } + + /** + * + * @return + */ + public ControlInterfaceFactory getInputFactory() { + ControlInterfaceFactory res = (ControlInterfaceFactory) getProperty(ControlInterfaceFactory.class); + if (res == null) { + String factoryClass = (String) getProperty(Wrap.INPUT_FACTORY_PROPERTY); + if (factoryClass != null) { + try { + res = ControlInterfaceFactory.class.cast(Class.forName(String.class.cast(factoryClass)).newInstance()); + setInputFactory(res); + } catch (InstantiationException ex) { + throw new JemmyException("Unable to instantiate input factory", ex, factoryClass); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to instantiate input factory", ex, factoryClass); + } catch (ClassNotFoundException ex) { + throw new JemmyException("Unable to load input factory", ex, factoryClass); + } + } + } + return res; + } + + /** + * + * @param factory + * @return + */ + public ControlInterfaceFactory setInputFactory(ControlInterfaceFactory factory) { + getOutput(OUTPUT).println("Input factory set to " + factory); + return (ControlInterfaceFactory) setProperty(ControlInterfaceFactory.class, factory); + } + + private static class PropertyKey { + + private Class cls; + private Object ref; + + public PropertyKey(Class cls, Object ref) { + this.cls = cls; + this.ref = ref; + } + + private PropertyKey(Class cls) { + this(cls, null); + } + + public Class getCls() { + return cls; + } + + public Object getRef() { + return ref; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PropertyKey other = (PropertyKey) obj; + if (this.cls != other.cls && (this.cls == null || !this.cls.equals(other.cls))) { + return false; + } + if (this.ref != other.ref && (this.ref == null || !this.ref.equals(other.ref))) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + (this.cls != null ? this.cls.hashCode() : 0); + hash = 41 * hash + (this.ref != null ? this.ref.hashCode() : 0); + return hash; + } + } +} --- /dev/null 2017-11-08 15:39:35.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/env/TestOut.java 2017-11-08 15:39:35.000000000 -0800 @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.env; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * + * Test output. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public class TestOut { + + private InputStream input; + private PrintWriter output; + private PrintWriter errput; + private BufferedReader buffInput; + private boolean autoFlushMode = true; + + /** + * Constructor. + * @param in Input stream + * @param out Output stream + * @param err Errput stream + */ + public TestOut(InputStream in, PrintStream out, PrintStream err) { + this(in, out, err, null); + } + + /** + * Constructor. + * @param in Input stream + * @param out Output stream + * @param err Errput stream + * @param golden Golgen output stream + */ + public TestOut(InputStream in, PrintStream out, PrintStream err, PrintStream golden) { + super(); + PrintWriter tout = null; + if (out != null) { + tout = new PrintWriter(out); + } + PrintWriter terr = null; + if (err != null) { + terr = new PrintWriter(err); + } + initStreams(in, tout, terr); + } + + /** + * Constructor. + * @param in Input stream + * @param out Output stream + * @param err Errput stream + */ + public TestOut(InputStream in, PrintWriter out, PrintWriter err) { + super(); + initStreams(in, out, err); + autoFlushMode = true; + } + + /** + * Creates unstance using System.in, System.out and System.err streams. + */ + public TestOut() { + this(System.in, + new PrintWriter(System.out), + new PrintWriter(System.err)); + } + + /** + * Creates output which does not print any message anywhere. + * @return a TestOut object which does not print any message anywhere. + */ + public static TestOut getNullOutput() { + return (new TestOut((InputStream) null, (PrintWriter) null, (PrintWriter) null)); + } + + /** + * Specifies either flush is invoked after each output. + * @param autoFlushMode If true flush is invoking after each output. + * @return Old value of the auto flush mode. + * @see #getAutoFlushMode + */ + public boolean setAutoFlushMode(boolean autoFlushMode) { + boolean oldValue = getAutoFlushMode(); + this.autoFlushMode = autoFlushMode; + return (oldValue); + } + + /** + * Says if flush is invoked after each output. + * @return Value of the auto flush mode. + * @see #setAutoFlushMode + */ + public boolean getAutoFlushMode() { + return (autoFlushMode); + } + + /** + * Read one byte from input. + * @return an int from input stream. + * @exception IOException + */ + public int read() throws IOException { + if (input != null) { + return (input.read()); + } else { + return (-1); + } + } + + /** + * Read a line from input. + * @return a line from input stream. + * @exception IOException + */ + public String readln() throws IOException { + if (buffInput != null) { + return (buffInput.readLine()); + } else { + return (null); + } + } + + /** + * Prints a line into output. + * @param line a string to print into output stream. + */ + public void print(String line) { + if (output != null) { + output.print(line); + } + } + + /** + * Prints a line and then terminate the line by writing the line separator string. + * @param line a string to print into output stream. + */ + public void println(String line) { + if (output != null) { + output.println(line); + if (autoFlushMode) { + output.flush(); + } + } + } + + /** + * Prints a line into error output. + * @param line a string to print into error output stream. + */ + public void printerrln(String line) { + if (errput != null) { + errput.println(line); + if (autoFlushMode) { + errput.flush(); + } + } + } + + /** + * Prints a line into either output or errput. + * @param toOut If true prints a line into output. + * @param line a string to print. + */ + public void println(boolean toOut, String line) { + if (toOut) { + println(line); + } else { + printerrln(line); + } + } + + /** + * Prints a error line. + * @param text a error text. + */ + public void printerr(String text) { + printerrln("Error:"); + printerrln(text); + } + + /** + * Prints an exception stack trace into error stream. + * @param e exception + */ + public void printStackTrace(Throwable e) { + if (errput != null) { + e.printStackTrace(errput); + if (autoFlushMode) { + errput.flush(); + } + } + } + + /** + * Returns input stream. + * @return an input stream + */ + public InputStream getInput() { + return (input); + } + + /** + * Returns output writer. + * @return an output stream + */ + public PrintWriter getOutput() { + return (output); + } + + /** + * Returns errput writer. + * @return a error stream + */ + public PrintWriter getErrput() { + return (errput); + } + + /** + * Creates an output which prints only error messages. + * @return a TestOut instance which has only error stream. + */ + public TestOut createErrorOutput() { + return (new TestOut(null, null, getErrput())); + } + + /** + * Flushes all output threads. + */ + public void flush() { + if (output != null) { + output.flush(); + } + if (errput != null) { + errput.flush(); + } + } + + private void initStreams(InputStream in, PrintWriter out, PrintWriter err) { + input = in; + output = out; + errput = err; + if (input != null) { + buffInput = new BufferedReader(new InputStreamReader(in)); + } else { + buffInput = null; + } + } +} --- /dev/null 2017-11-08 15:39:35.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/env/Timeout.java 2017-11-08 15:39:35.000000000 -0800 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.env; + +import org.jemmy.TimeoutExpiredException; +import org.jemmy.JemmyException; + +/** + * Represents one timeout. + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public class Timeout extends Object implements Cloneable { + + private String name; + private long value; + private long startTime; + + /** + * Constructor. + * @param name Timeout name. + * @param value Timeout value in milliseconds. + */ + public Timeout(String name, long value) { + this.name = name; + this.value = value; + } + + /** + * Returns timeout name. + * @return timeout name. + */ + public String getName() { + return (name); + } + + /** + * Returns timeout value. + * @return timeout value. + */ + public long getValue() { + return (value); + } + + public void setValue(long value) { + this.value = value; + } + + /** + * Sleeps for timeout value. + */ + public void sleep() { + if (getValue() > 0) { + try { + Thread.sleep(getValue()); + } catch (InterruptedException e) { + throw (new JemmyException("Sleep " + + getName() + + " was interrupted!", + e)); + } + } + } + + /** + * Starts timeout measuring. + */ + public void start() { + startTime = System.currentTimeMillis(); + } + + /** + * Checks if timeout has been expired after start() invocation. + * @return true if timeout has been expired. + */ + public boolean expired() { + return (System.currentTimeMillis() - startTime > getValue()); + } + + /** + * Throws a TimeoutExpiredException exception if timeout has been expired. + * @throws TimeoutExpiredException if timeout has been expired after start() invocation. + */ + public void check() { + if (expired()) { + throw (new TimeoutExpiredException(getName() + + " timeout expired!")); + } + } + + /** + * + * @return + */ + @Override + public String toString() { + return "Timeout [" + name + ", " + value + "]"; + } + + @Override + public Timeout clone() { + return new Timeout(name, value); + } +} --- /dev/null 2017-11-08 15:39:35.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/Image.java 2017-11-08 15:39:35.000000000 -0800 @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + +/** + * + * @author shura + */ +public interface Image { + /** + * Compares the image with the other one. + * + * @param img + * @return difference of the images, null if identical + */ + public Image compareTo(Image img); + /** + * Saves the image. + * @see ImageLoader#load(String) + * @param ID could be anything - depends on the implementation. + */ + public void save(String ID); +} --- /dev/null 2017-11-08 15:39:36.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/ImageCapturer.java 2017-11-08 15:39:36.000000000 -0800 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + +import org.jemmy.Rectangle; +import org.jemmy.control.Wrap; + +/** + * + * @author mrkam + */ +public interface ImageCapturer { + + /** + * Obtains a screen image for the control. + * @param control + * @param rect a control area to be captured + * @return + */ + Image capture(Wrap control, Rectangle rect); + +} --- /dev/null 2017-11-08 15:39:36.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/ImageComparator.java 2017-11-08 15:39:36.000000000 -0800 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + +/** + * Interface for all classes performing image comparison. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public interface ImageComparator { + + /** + * Checks if images are the same according to whatever comparison logic is used. + * @param image1 an image to compare. + * @param image2 an image to compare. + * @return null if images match each other; difference otherwise + */ + public Image compare(Image image1, Image image2); + + /** + * A string qualifying an image comparison algorithm. To be used in logs and + * tools. + * @return + */ + public String getID(); +} --- /dev/null 2017-11-08 15:39:36.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/ImageLoader.java 2017-11-08 15:39:36.000000000 -0800 @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + +/** + * + * @author mrkam + */ +public interface ImageLoader { + + /** + * Loads image by ID. Depending on an implementation, ID could be file name, + * class name, image name, etc. + * @see Image#save(java.lang.String) + * @param ID + * @return + */ + Image load(String ID); + +} --- /dev/null 2017-11-08 15:39:37.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/ImageStore.java 2017-11-08 15:39:37.000000000 -0800 @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +/** + * + * @author shura + */ +public interface ImageStore { + public void save(Image image, String id) throws Exception; +} --- /dev/null 2017-11-08 15:39:37.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/AverageDistanceComparator.java 2017-11-08 15:39:37.000000000 -0800 @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; + +/** + * + * @author shura + */ +public class AverageDistanceComparator extends ThresholdComparator { + + /** + * + * @param threshold + */ + public AverageDistanceComparator(double threshold) { + super(0, Math.sqrt(3)); + setThreshold(threshold); + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public boolean compare(Raster image1, Raster image2) { + Dimension size = PixelImageComparator.computeDiffSize(image1, image2); + if (size == null) { + return false; + } + int totalPixels = size.width * size.height; + double distance = 0; + double[] colors1 = new double[image1.getSupported().length]; + double[] colors2 = new double[image2.getSupported().length]; + for (int x = 0; x < size.width; x++) { + for (int y = 0; y < size.height; y++) { + image1.getColors(x, y, colors1); + image2.getColors(x, y, colors2); + distance += distance(image1.getSupported(), colors1, image2.getSupported(), colors2) / totalPixels; + } + } + return distance < getThreshold(); + } + + public static final Raster.Component[] DISTANCE_COMPONENTS = { + Raster.Component.RED, Raster.Component.BLUE, Raster.Component.GREEN + }; + static double distance(Raster.Component[] comps1, double[] colors1, Raster.Component[] comps2, double[] colors2) { + double res = 0; + double diff; + for (Raster.Component c : DISTANCE_COMPONENTS) { + diff = colors2[PixelImageComparator.arrayIndexOf(comps2, c)] - + colors1[PixelImageComparator.arrayIndexOf(comps1, c)]; + res += diff * diff; + } + return Math.sqrt(res); + } + + /** + * + * @return + */ + public String getID() { + return AverageDistanceComparator.class.getName() + ":" + getThreshold(); + } +} --- /dev/null 2017-11-08 15:39:37.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/BaseCountingRasterComparator.java 2017-11-08 15:39:37.000000000 -0800 @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; + +/** + * + * @author shura + */ +public abstract class BaseCountingRasterComparator extends ThresholdComparator { + + /** + * + * @param threshold + */ + public BaseCountingRasterComparator(double threshold) { + super(0, 1); + setThreshold(threshold); + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public boolean compare(Raster image1, Raster image2) { + Dimension size = PixelImageComparator.computeDiffSize(image1, image2); + if(size == null) { + return false; + } + int totalPixels = size.width * size.height; + int offPixels = 0; + double[] colors1 = new double[image1.getSupported().length]; + double[] colors2 = new double[image2.getSupported().length]; + for (int x = 0; x < size.width; x++) { + for (int y = 0; y < size.height; y++) { + image1.getColors(x, y, colors1); + image2.getColors(x, y, colors2); + if (!compare(image1.getSupported(), colors1, + image2.getSupported(), colors2)) { + offPixels++; + } + } + } + return (offPixels <= totalPixels * getThreshold()); + } + + /** + * + * @param comps1 + * @param colors1 + * @param comps2 + * @param colors2 + * @return + */ + protected abstract boolean compare(Raster.Component[] comps1, double[] colors1, Raster.Component[] comps2, double[] colors2); + +} --- /dev/null 2017-11-08 15:39:38.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/ColorMappingComparator.java 2017-11-08 15:39:38.000000000 -0800 @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import java.util.List; +import org.jemmy.Dimension; +import org.jemmy.image.pixel.Raster.Component; + +/** + * + * @author shura + */ +public abstract class ColorMappingComparator implements RasterComparator { + + final private ColorMap left; + final private ColorMap right; + private RasterComparator subComparator; + + /** + * + * @param left + * @param right + * @param subComparator + */ + public ColorMappingComparator(ColorMap left, ColorMap right, + RasterComparator subComparator) { + this.subComparator = subComparator; + this.left = left; + this.right = right; + } + + /** + * + * @return + */ + public RasterComparator getSubComparator() { + return subComparator; + } + + /** + * + * @param subComparator + */ + public void setSubComparator(RasterComparator subComparator) { + this.subComparator = subComparator; + } + + /** + * + * @param both + * @param subComparator + */ + public ColorMappingComparator(ColorMap both, RasterComparator subComparator) { + this(both, both, subComparator); + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public boolean compare(Raster image1, Raster image2) { + return subComparator.compare(map(image1, left), map(image2, right)); + } + + /** + * + * @param image + * @param map + * @return + */ + public WriteableRaster map(Raster image, ColorMap map) { + WriteableRaster res = createView(image.getSize()); + double[] colors = new double[image.getSupported().length]; + double[] newColors = new double[image.getSupported().length]; + for (int x = 0; x < image.getSize().width; x++) { + for (int y = 0; y < image.getSize().height; y++) { + image.getColors(x, y, colors); + map.map(image.getSupported(), colors, newColors); + res.setColors(x, y, newColors); + } + } + return res; + } + + /** + * + * @param size + * @return + */ + protected abstract WriteableRaster createView(Dimension size); + + /** + * + * @return + */ + public String getID() { + return ColorMappingComparator.class.getName() + ":" + + left.getID() + "," + right.getID() + "(" + + subComparator.getID() + ")"; + } + + /** + * + */ + public interface ColorMap { + + /** + * + * @param components + * @param values + * @param newValues + */ + public void map(Component[] components, double[] values, double[] newValues); + /** + * + * @return + */ + public String getID(); + } +} --- /dev/null 2017-11-08 15:39:38.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/MaxDistanceComparator.java 2017-11-08 15:39:38.000000000 -0800 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; + +/** + * + * @author shura + */ +public class MaxDistanceComparator extends ThresholdComparator{ + + /** + * + * @param threshold + */ + public MaxDistanceComparator(double threshold) { + super(0, Math.sqrt(3)); + setThreshold(threshold); + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public boolean compare(Raster image1, Raster image2) { + Dimension size = PixelImageComparator.computeDiffSize(image1, image2); + if (size == null) { + return false; + } + double[] colors1 = new double[image1.getSupported().length]; + double[] colors2 = new double[image2.getSupported().length]; + double distance = 0; + for (int x = 0; x < size.width; x++) { + for (int y = 0; y < size.height; y++) { + image1.getColors(x, y, colors1); + image2.getColors(x, y, colors2); + distance = Math.max(distance, AverageDistanceComparator.distance(image1.getSupported(), colors1, image2.getSupported(), colors2)); + } + } + return distance <= getThreshold(); + } + + /** + * + * @return + */ + public String getID() { + return MaxDistanceComparator.class.getName() + ":" + getThreshold(); + } +} --- /dev/null 2017-11-08 15:39:38.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PNGFileImageStore.java 2017-11-08 15:39:38.000000000 -0800 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import java.io.File; +import java.io.IOException; +import org.jemmy.image.Image; +import org.jemmy.image.ImageStore; + +/** + * + * @author shura + */ +public class PNGFileImageStore implements ImageStore { + + private final File root; + + public PNGFileImageStore(File root) { + this.root = root; + } + + public PNGFileImageStore() { + this(null); + } + + @Override + public void save(Image image, String id) throws IOException { + if (!(image instanceof Raster)) { + throw new IllegalArgumentException("This implementation only takes rasters"); + } + if (root != null) { + id = root.getAbsolutePath() + File.separator + id; + } + File file = new File(id); + File parentDir = file.getParentFile(); + if (parentDir != null) { + parentDir.mkdirs(); + } + new PNGSaver(file).encode((Raster) image); + } + + public File getRoot() { + return root; + } +} --- /dev/null 2017-11-08 15:39:39.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PNGLoader.java 2017-11-08 15:39:39.000000000 -0800 @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image.pixel; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; +import org.jemmy.JemmyException; +import org.jemmy.image.pixel.Raster.Component; + +/** + * Allows to load PNG graphical file. + * @author Alexandre Iline + */ +public abstract class PNGLoader { + + InputStream in; + + /** + * Constructs a PNGDecoder object. + * @param in input stream to read PNG image from. + */ + public PNGLoader(InputStream in) { + this.in = in; + } + + byte read() throws IOException { + byte b = (byte)in.read(); + return(b); + } + + int readInt() throws IOException { + byte b[] = read(4); + return(((b[0]&0xff)<<24) + + ((b[1]&0xff)<<16) + + ((b[2]&0xff)<<8) + + ((b[3]&0xff))); + } + + byte[] read(int count) throws IOException { + byte[] result = new byte[count]; + for(int i = 0; i < count; i++) { + result[i] = read(); + } + return(result); + } + + void checkEquality(byte[] b1, byte[] b2) { + if(!Arrays.equals(b1, b2)) { + throw(new JemmyException("Format error")); + } + } + + /** + * Decodes image from an input stream passed into constructor. + * @return a BufferedImage object + * @throws IOException + */ + public Raster decode() throws IOException { + return decode(true); + } + + protected abstract WriteableRaster createRaster(int width, int height); + + /** + * Decodes image from an input stream passed into constructor. + * @return a BufferedImage object + * @param closeStream requests method to close the stream after the image is read + * @throws IOException + */ + public Raster decode(boolean closeStream) throws IOException { + + byte[] id = read(12); + checkEquality(id, new byte[] {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}); + + byte[] ihdr = read(4); + checkEquality(ihdr, "IHDR".getBytes()); + + int width = readInt(); + int height = readInt(); + + WriteableRaster result = createRaster(width, height); + + byte[] head = read(5); + int mode; + if(Arrays.equals(head, new byte[]{1, 0, 0, 0, 0})) { + mode = PNGSaver.BW_MODE; + } else if(Arrays.equals(head, new byte[]{8, 0, 0, 0, 0})) { + mode = PNGSaver.GREYSCALE_MODE; + } else if(Arrays.equals(head, new byte[]{8, 2, 0, 0, 0})) { + mode = PNGSaver.COLOR_MODE; + } else { + throw(new JemmyException("Format error")); + } + + readInt();//!!crc + + int size = readInt(); + + byte[] idat = read(4); + checkEquality(idat, "IDAT".getBytes()); + + byte[] data = read(size); + + + Inflater inflater = new Inflater(); + inflater.setInput(data, 0, size); + + int[] colors = new int[3]; + int[] black = new int[] {0, 0, 0}; + int[] white = new int[] {1, 1, 1}; + + try { + switch (mode) { + case PNGSaver.BW_MODE: + { + int bytes = (int)(width / 8); + if((width % 8) != 0) { + bytes++; + } + byte colorset; + byte[] row = new byte[bytes]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < bytes; x++) { + colorset = row[x]; + for (int sh = 0; sh < 8; sh++) { + if(x * 8 + sh >= width) { + break; + } + if((colorset & 0x80) == 0x80) { + setColors(result, x * 8 + sh, y, white); + } else { + setColors(result, x * 8 + sh, y, black); + } + colorset <<= 1; + } + } + } + } + break; + case PNGSaver.GREYSCALE_MODE: + { + byte[] row = new byte[width]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < width; x++) { + colors[0] = row[x]; + colors[1] = colors[0]; + colors[2] = colors[0]; + setColors(result, x, y, colors); + } + } + } + break; + case PNGSaver.COLOR_MODE: + { + byte[] row = new byte[width * 3]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < width; x++) { + colors[0] = (row[x * 3 + 0]&0xff); + colors[1] = (row[x * 3 + 1]&0xff); + colors[2] = (row[x * 3 + 2]&0xff); + setColors(result, x, y, colors); + } + } + } + } + } catch(DataFormatException e) { + throw(new JemmyException("ZIP error", e)); + } + + readInt();//!!crc + readInt();//0 + + byte[] iend = read(4); + checkEquality(iend, "IEND".getBytes()); + + readInt();//!!crc + if (closeStream) { + in.close(); + } + + return(result); + } + + private void setColors(WriteableRaster raster, int x, int y, int[] colors) { + Component[] supported = raster.getSupported(); + double[] imageColors = new double[supported.length]; + for (int i = 0; i < supported.length; i++) { + if(supported[i] == Component.ALPHA) { + imageColors[i] = 1; + } else { + imageColors[i] = (double)colors[ + PixelImageComparator.arrayIndexOf(PNGSaver.RGB, supported[i])]/0xFF; + } + } + raster.setColors(x, y, imageColors); + } + +} --- /dev/null 2017-11-08 15:39:39.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PNGSaver.java 2017-11-08 15:39:39.000000000 -0800 @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import java.io.*; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import org.jemmy.image.pixel.Raster.Component; + +/** + * + * @author shura + */ +public class PNGSaver { + + /** + * black and white image mode. + */ + public static final byte BW_MODE = 0; + /** + * grey scale image mode. + */ + public static final byte GREYSCALE_MODE = 1; + /** + * full color image mode. + */ + public static final byte COLOR_MODE = 2; + OutputStream out; + CRC32 crc; + byte mode; + + /** + * + * @param file + * @throws java.io.FileNotFoundException + */ + public PNGSaver(File file) throws FileNotFoundException { + this(new FileOutputStream(file)); + } + + /** + * public constructor of PNGEncoder class with greyscale mode by default. + * + * @param out output stream for PNG image format to write into + */ + public PNGSaver(OutputStream out) { + this(out, COLOR_MODE); + } + + /** + * public constructor of PNGEncoder class. + * + * @param out output stream for PNG image format to write into + * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE + */ + public PNGSaver(OutputStream out, byte mode) { + crc = new CRC32(); + this.out = out; + if (mode < 0 || mode > 2) { + throw new IllegalArgumentException("Unknown color mode"); + } + this.mode = mode; + } + + void write(int i) throws IOException { + byte b[] = {(byte) ((i >> 24) & 0xff), (byte) ((i >> 16) & 0xff), (byte) ((i >> 8) & 0xff), (byte) (i & 0xff)}; + write(b); + } + + void write(byte b[]) throws IOException { + out.write(b); + crc.update(b); + } + + /** + * main encoding method (stays blocked till encoding is finished). + * + * @param image BufferedImage to encode + * @throws IOException IOException + */ + public void encode(Raster image) throws IOException { + encode(image, true); + } + + /** + * main encoding method (stays blocked till encoding is finished). + * + * @param image BufferedImage to encode + * @param closeStream requests method to close the stream after the image is + * written + * @throws IOException IOException + */ + private void encode(Raster image, boolean closeStream) throws IOException { + int width = image.getSize().width; + int height = image.getSize().height; + final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}; + write(id); + crc.reset(); + write("IHDR".getBytes()); + write(width); + write(height); + byte head[] = null; + switch (mode) { + case BW_MODE: + head = new byte[]{1, 0, 0, 0, 0}; + break; + case GREYSCALE_MODE: + head = new byte[]{8, 0, 0, 0, 0}; + break; + case COLOR_MODE: + head = new byte[]{8, 2, 0, 0, 0}; + break; + } + write(head); + write((int) crc.getValue()); + ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536); + BufferedOutputStream bos = new BufferedOutputStream(new DeflaterOutputStream(compressed, new Deflater(9))); + int pixel; + int color; + int colorset; + switch (mode) { + case BW_MODE: + int rest = width % 8; + int bytes = width / 8; + for (int y = 0; y < height; y++) { + bos.write(0); + for (int x = 0; x < bytes; x++) { + colorset = 0; + for (int sh = 0; sh < 8; sh++) { + pixel = getRGB(image, x * 8 + sh, y); + color = ((pixel >> 16) & 0xff); + color += ((pixel >> 8) & 0xff); + color += (pixel & 0xff); + colorset <<= 1; + if (color >= 3 * 128) { + colorset |= 1; + } + } + bos.write((byte) colorset); + } + if (rest > 0) { + colorset = 0; + for (int sh = 0; sh < width % 8; sh++) { + pixel = getRGB(image, bytes * 8 + sh, y); + color = ((pixel >> 16) & 0xff); + color += ((pixel >> 8) & 0xff); + color += (pixel & 0xff); + colorset <<= 1; + if (color >= 3 * 128) { + colorset |= 1; + } + } + colorset <<= 8 - rest; + bos.write((byte) colorset); + } + } + break; + case GREYSCALE_MODE: + for (int y = 0; y < height; y++) { + bos.write(0); + for (int x = 0; x < width; x++) { + pixel = getRGB(image, x, y); + color = ((pixel >> 16) & 0xff); + color += ((pixel >> 8) & 0xff); + color += (pixel & 0xff); + bos.write((byte) (color / 3)); + } + } + break; + case COLOR_MODE: + for (int y = 0; y < height; y++) { + bos.write(0); + for (int x = 0; x < width; x++) { + pixel = getRGB(image, x, y); + bos.write((byte) ((pixel >> 16) & 0xff)); + bos.write((byte) ((pixel >> 8) & 0xff)); + bos.write((byte) (pixel & 0xff)); + } + } + break; + } + bos.close(); + write(compressed.size()); + crc.reset(); + write("IDAT".getBytes()); + write(compressed.toByteArray()); + write((int) crc.getValue()); + write(0); + crc.reset(); + write("IEND".getBytes()); + write((int) crc.getValue()); + out.flush(); + if (closeStream) { + out.close(); + } + } + static final Component[] RGB = new Component[]{ + Component.RED, Component.GREEN, Component.BLUE}; + + private int getRGB(Raster image, int x, int y) { + int res = 0; + double[] colors = new double[image.getSupported().length]; + image.getColors(x, y, colors); + for (Component c : RGB) { + res <<= 8; + res |= (int)(colors[PixelImageComparator.arrayIndexOf(image.getSupported(), c)] * 0xFF); + } + return res; + } +} --- /dev/null 2017-11-08 15:39:39.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PeakSignalNoiseRatioComparator.java 2017-11-08 15:39:39.000000000 -0800 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +/** + * Comparator, that implements peak signal to noise ratio, popular and reliable method of measuring + * image compression quality or image distortion. + * PSNR is used to measure the quality of reconstruction of lossy compression codecs. The signal is + * the reference (golden) image, and the noise is actual image. Higher PSNR indicates lesser + * difference between images for the most cases. + * Comparator is intended to compare images with large amount of very minor differences and one + * has to be extremely careful with the range of validity of this metric; it is only conclusively + * valid when it is used to compare results from the same content. + * + * @author andrey.rusakov@oracle.com + */ +public class PeakSignalNoiseRatioComparator implements RasterComparator { + + public static final String OUTPUT = PeakSignalNoiseRatioComparator.class.getName() + ".OUTPUT"; + + private static final double MAX_CHANNEL = 1.0; + private final double minRatio; + + private static double getMeanSquareError(Raster image, Raster original) { + double result = 0; + double[] colors1 = new double[image.getSupported().length]; + double[] colors2 = new double[original.getSupported().length]; + + int w = Math.min(image.getSize().width, original.getSize().width); + int h = Math.min(image.getSize().height, original.getSize().height); + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + image.getColors(i, j, colors1); + original.getColors(i, j, colors2); + double distance = AverageDistanceComparator.distance(image.getSupported(), colors1, + original.getSupported(), colors2); + result += distance * distance / (AverageDistanceComparator.DISTANCE_COMPONENTS.length * w * h); + } + } + return result; + } + + private static double getPeakSignalNoiseRatio(Raster image, Raster original) { + double mse = getMeanSquareError(image, original); + return 20 * Math.log10(MAX_CHANNEL) - 10 * Math.log10(mse); + } + + public PeakSignalNoiseRatioComparator(double minRatio) { + this.minRatio = minRatio; + } + + @Override + public boolean compare(Raster image1, Raster image2) { + return getPeakSignalNoiseRatio(image1, image2) > minRatio; + } + + @Override + public String getID() { + return getClass().getName() + ":" + minRatio; + } + +} --- /dev/null 2017-11-08 15:39:40.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PixelEqualityRasterComparator.java 2017-11-08 15:39:40.000000000 -0800 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; +import org.jemmy.image.Image; +import org.jemmy.image.pixel.Raster.Component; + +/** + * + * @author shura + */ +public class PixelEqualityRasterComparator extends BaseCountingRasterComparator { + + /** + * + * @param treshold + */ + public PixelEqualityRasterComparator(double treshold) { + super(treshold); + } + + /** + * + * @param comps1 + * @param colors1 + * @param comps2 + * @param colors2 + * @return + */ + @Override + protected boolean compare(Raster.Component[] comps1, double[] colors1, + Raster.Component[] comps2, double[] colors2) { + if (colors1.length == colors2.length) { + for (int i = 0; i < colors1.length; i++) { + if (colors1[i] != colors2[PixelImageComparator.arrayIndexOf(comps2, comps1[i])]) { + return false; + } + } + return true; + } else { + return false; + } + } + + /** + * + * @return + */ + public String getID() { + return PixelEqualityRasterComparator.class.getName() + ":" + getThreshold(); + } +} --- /dev/null 2017-11-08 15:39:40.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/PixelImageComparator.java 2017-11-08 15:39:40.000000000 -0800 @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import java.util.Arrays; +import org.jemmy.Dimension; +import org.jemmy.env.Environment; +import org.jemmy.image.Image; +import org.jemmy.image.ImageComparator; +import org.jemmy.image.pixel.Raster.Component; + +/** + * + * @author shura + */ +public abstract class PixelImageComparator implements ImageComparator { + + static { + Environment.getEnvironment().setPropertyIfNotSet(RasterComparator.class, + new PixelEqualityRasterComparator(0)); +// new MaxDistanceComparator((double)1/0x8f)); + } + + private RasterComparator comparator = null; + private Environment env = null; + + /** + * + * @param comparator + */ + public PixelImageComparator(RasterComparator comparator) { + this.comparator = comparator; + } + + public PixelImageComparator(Environment env) { + this.env = env; + } + + public synchronized RasterComparator getRasterComparator() { + if(comparator == null) { + return env.getProperty(RasterComparator.class); + } else { + return comparator; + } + } + + /** + * + * @param one + * @param two + * @return + */ + public static Dimension computeDiffSize(Raster one, Raster two) { + if (one.getSize().equals(two.getSize())) { + return one.getSize(); + } else { + return null; + } + } + + public Image compare(Image image1, Image image2) { + Raster pi1 = toRaster(image1); + Raster pi2 = toRaster(image2); + if (!getRasterComparator().compare(pi1, pi2)) { + return toImage(computeDifference(pi1, pi2)); + } else { + return null; + } + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public WriteableRaster computeDifference(Raster image1, Raster image2) { + Dimension size = computeDiffSize(image1, image2); + if (size == null) { + size = new Dimension(Math.max(image1.getSize().width, image2.getSize().width), + Math.max(image1.getSize().height, image2.getSize().height)); + } + WriteableRaster res = createDiffRaster(image1, image2); + double[] colors1 = new double[image1.getSupported().length]; + double[] colors2 = new double[image2.getSupported().length]; + double[] colorsRes = new double[res.getSupported().length]; + for (int x = 0; x < size.width; x++) { + for (int y = 0; y < size.height; y++) { + if (x < image1.getSize().width && y < image1.getSize().height) { + image1.getColors(x, y, colors1); + } else { + Arrays.fill(colors1, 0); + } + if (x < image2.getSize().width && y < image2.getSize().height) { + image2.getColors(x, y, colors2); + } else { + Arrays.fill(colors2, 1); + } + calcDiffColor(image1.getSupported(), colors1, image2.getSupported(), colors2, + res.getSupported(), colorsRes); + res.setColors(x, y, colorsRes); + } + } + return res; + } + + private static final Component[] diffComponents = { + Component.RED, Component.BLUE, Component.GREEN + }; + /** + * + * @param comps1 + * @param colors1 + * @param comps2 + * @param colors2 + * @param compsRes + * @param colorsRes + */ + protected void calcDiffColor(Raster.Component[] comps1, double[] colors1, + Raster.Component[] comps2, double[] colors2, Raster.Component[] compsRes, double[] colorsRes) { + double square1, square2; + double dist = 0; + + for (Component c : diffComponents) { + square1 = getComponentValue(comps1, colors1, c); + square2 = getComponentValue(comps2, colors2, c); + dist += (square2 - square1) * (square2 - square1); + } + for (Component c : diffComponents) { + colorsRes[arrayIndexOf(compsRes, c)] = Math.sqrt(dist) / Math.sqrt(3); + } + colorsRes[arrayIndexOf(compsRes, Component.ALPHA)] = 1; + } + + public String getID() { + return getRasterComparator().getID(); + } + + /** + * + * @param image + * @return + */ + protected abstract Image toImage(Raster image); + + /** + * + * @param image + * @return + */ + protected abstract Raster toRaster(Image image); + + /** + * + * @param r1 + * @param r2 + * @return + */ + protected abstract WriteableRaster createDiffRaster(Raster r1, Raster r2); + + /** + * + * @param comps + * @param comp + * @return + */ + public static int arrayIndexOf(Raster.Component[] comps, Raster.Component comp) { + for (int i = 0; i < comps.length; i++) { + if (comp == comps[i]) { + return i; + } + } + throw new IllegalArgumentException("Unknown component " + comp); + } + + /** + * Returns color component value using its alpha information + * + * @param components available color components + * @param colors color components values + * @param comp required color component + * + * @return value of the required color component. + * If pixel is not opaque, then it is blended with white + * opaque background + */ + protected double getComponentValue(Component[] components, double[] colors, Component comp) { + + double result = colors[arrayIndexOf(components, comp)]; + + if(result < 0.0 || result > 1.0) throw new IllegalStateException("Component value = " + result); + + //Find alpha index if exists + int idxAlpha = -1; + try { + idxAlpha = arrayIndexOf(components, Component.ALPHA); + } catch (IllegalArgumentException ex) { + } + + //If alpha component is available + if (idxAlpha != -1) { + double alpha = colors[idxAlpha]; + + if(alpha < 0.0 || alpha > 1.0) throw new IllegalStateException("Alpha value = " + alpha); + + //If not opaque + if (alpha < 1.0) { + //Blend with opaque white + result = Math.min(1.0, alpha * result + 1 - alpha); + } + } + + return result; + } +} --- /dev/null 2017-11-08 15:39:40.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/Raster.java 2017-11-08 15:39:40.000000000 -0800 @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; + +/** + * + * @author shura + */ +public interface Raster { + + /** + * + */ + public enum Component { + + /** + * + */ + RED, + /** + * + */ + GREEN, + /** + * + */ + BLUE, + /** + * + */ + ALPHA; + }; + + /** + * + * @return + */ + public Dimension getSize(); + + /** + * Supposed to write pixel component value in the RGBA order. + * @param x + * @param y + * @param colors + */ + public void getColors(int x, int y, double[] colors); + + /** + * Returns a list of supported components. + * + * @return + */ + public Component[] getSupported(); + +} --- /dev/null 2017-11-08 15:39:41.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/RasterComparator.java 2017-11-08 15:39:41.000000000 -0800 @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import java.util.List; + +/** + * + * @author shura + */ +public interface RasterComparator { + /** + * + * @param image1 + * @param image2 + * @return + */ + public boolean compare(Raster image1, Raster image2); + /** + * + * @return + */ + public String getID(); +} --- /dev/null 2017-11-08 15:39:41.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/ResizeComparator.java 2017-11-08 15:39:41.000000000 -0800 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +import org.jemmy.Dimension; +import org.jemmy.image.Image; +import org.jemmy.image.ImageComparator; + +/** + * + * @author shura + */ +public abstract class ResizeComparator implements ImageComparator { + + private ImageComparator subComparator; + private Mode mode; + + /** + * + */ + public enum Mode { + + /** + * + */ + LEFT, + /** + * + */ + RIGTH, + /** + * + */ + MAX + }; + + /** + * + * @param subComparator + * @param mode + */ + public ResizeComparator(ImageComparator subComparator, Mode mode) { + this.subComparator = subComparator; + this.mode = mode; + } + + /** + * + * @param subComparator + */ + public ResizeComparator(ImageComparator subComparator) { + this(subComparator, Mode.MAX); + } + + /** + * + * @return + */ + public ImageComparator getSubComparator() { + return subComparator; + } + + /** + * + * @param subComparator + */ + public void setSubComparator(ImageComparator subComparator) { + this.subComparator = subComparator; + } + + /** + * + * @param image1 + * @param image2 + * @return + */ + public Image compare(Image image1, Image image2) { + if(image1 == null || image2 == null) { + return (image1 == null) ? image2 : image1; + } + Dimension size; + switch (mode) { + case LEFT: + size = getSize(image1); + break; + case RIGTH: + size = getSize(image2); + break; + case MAX: + Dimension size1 = getSize(image1); + Dimension size2 = getSize(image2); + size = new Dimension(Math.max(size1.width, size2.width), + Math.max(size1.height, size2.height)); + break; + default: + throw new IllegalStateException("mode is not recognized"); + } + Image r1 = resize(image1, size); + Image r2 = resize(image2, size); + if(r1 == null) { + return image1; + } + if(r2 == null) { + return image2; + } + return subComparator.compare(r1, r2); + } + + /** + * + * @param image + * @param size + * @return + */ + abstract public Image resize(Image image, Dimension size); + + /** + * + * @param image + * @return + */ + abstract public Dimension getSize(Image image); + + /** + * + * @return + */ + public String getID() { + return ResizeComparator.class.getName() + "(" + subComparator.getID() + ")"; + } +} --- /dev/null 2017-11-08 15:39:41.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/ThresholdComparator.java 2017-11-08 15:39:41.000000000 -0800 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +/** + * + * @author shura + */ +abstract class ThresholdComparator implements RasterComparator { + + private double min; + private double threshold; + private double max; + + public ThresholdComparator(double min, double max) { + this.min = min; + this.max = max; + } + + /** + * + * @param threshold + */ + public final void setThreshold(double threshold) { + if (threshold < min || threshold > max) { + throw new IllegalArgumentException("Treshold expected to be withing (" + + min + ", " + max + "1). Got: " + threshold); + } + this.threshold = threshold; + } + + public double getThreshold() { + return threshold; + } +} --- /dev/null 2017-11-08 15:39:42.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/image/pixel/WriteableRaster.java 2017-11-08 15:39:42.000000000 -0800 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.pixel; + +/** + * + * @author shura + */ +public interface WriteableRaster extends Raster { + /** + * + * @param x + * @param y + * @param values + */ + public void setColors(int x, int y, double[] values); +} --- /dev/null 2017-11-08 15:39:42.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/input/AbstractCaretOwner.java 2017-11-08 15:39:42.000000000 -0800 @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.input; + +import org.jemmy.interfaces.*; +import org.jemmy.interfaces.Caret.Direction; + +/** + * + * @author shura + */ +public abstract class AbstractCaretOwner implements CaretOwner { + private double error; + + public double allowedError() { + return error; + } + + public void allowError(double error) { + if(error < 0) { + throw new IllegalArgumentException("Precision could not be less than 0"); + } + this.error = error; + } + + public void to(double position) { + caret().to(new ToPosition(this, position, error)); + } + + public static class ToPosition implements Direction { + + private double error; + private double value; + private CaretOwner caret; + + public ToPosition(CaretOwner caret, double value, double allowedError) { + this.caret = caret; + this.value = value; + this.error = allowedError; + } + + public ToPosition(CaretOwner caret, double value) { + this(caret, value, 0); + } + + public int to() { + double diff = diff(); + return (Math.abs(diff) <= error) ? 0 : ((diff > 0) ? 1 : -1); + } + + @Override + public String toString() { + return "value == " + position() + " with " + error + " error"; + } + + protected double diff() { + return position() - caret.position(); + } + + public CaretOwner getCaret() { + return caret; + } + + /** + * + * @return + */ + protected double position() { + return value; + } + } + +} --- /dev/null 2017-11-08 15:39:42.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/input/AbstractScroll.java 2017-11-08 15:39:42.000000000 -0800 @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import org.jemmy.interfaces.Scroll; + +/** + * + * @author shura + */ +public abstract class AbstractScroll extends AbstractCaretOwner implements Scroll { + +} --- /dev/null 2017-11-08 15:39:43.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/input/CaretImpl.java 2017-11-08 15:39:43.000000000 -0800 @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import java.util.ArrayList; +import org.jemmy.JemmyException; +import org.jemmy.Point; +import org.jemmy.action.Action; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.interfaces.Caret; +import org.jemmy.interfaces.CaretOwner; +import org.jemmy.interfaces.Focusable; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardModifier; +import org.jemmy.interfaces.Mouse.MouseButtons; + +/** + * + * @author shura + */ +public class CaretImpl implements Caret { + + /** + * Time to sleep between scrolling actions + */ + public static final Timeout SCROLL_TIMEOUT = + new Timeout("ScrollerImpl.scroll", 1); + + static { + Environment.getEnvironment().initTimeout(SCROLL_TIMEOUT); + } + private Wrap wrap; + private CaretOwner caretOwner; + private ArrayList actions; + + /** + * + * @param wrap + * @param caretOwner only position() is used + */ + public CaretImpl(Wrap wrap, CaretOwner caretOwner) { + this.wrap = wrap; + this.caretOwner = caretOwner; + actions = new ArrayList(); + } + + /** + * + * @return + */ + public Wrap getWrap() { + return wrap; + } + + /** + * + * @param action + */ + protected void addScrollAction(ScrollAction action) { + actions.add(0, action); + } + + public void to(final double value) { + to(new DirectionToPosition(caretOwner, value)); + } + + public void to(final Caret.Direction direction) { + wrap.getEnvironment().getExecutor().execute(wrap.getEnvironment(), false, new Action() { + + @Override + public void run(Object... parameters) { + if (direction.to() == 0) { + return; + } + if (wrap.is(Focusable.class)) { + wrap.as(Focusable.class).focuser().focus(); + } + int orig = direction.to(); + if (orig == 0) { + return; + } + double prevPos = caretOwner.position(); + double prevDist = Double.MAX_VALUE; + for (int i = 0; i < actions.size(); i++) { + while (!isInterrupted() && (direction.to() * orig) >= 0) { + actions.get(i).scrollTo(orig); + wrap.getEnvironment().getTimeout(SCROLL_TIMEOUT).sleep(); + //if didn't move - use the smaller adjustment + //like, puching up when in the first line + if(caretOwner.position() == prevPos) { + //if did not move and there are more - move to next + if(i < actions.size() - 1) { + break; + } else { + //try more and finally fail by timeout + //throw new JemmyException("Unable to scoll.", wrap); + } + } + prevPos = caretOwner.position(); + if (direction.to() == 0) { + return; + } + } + orig = direction.to(); + } + } + + @Override + public String toString() { + return "Scrolling to " + direction.toString() + " condition"; + } + }); + } + + /** + * @deprecated Use ApproximateCaretOwner.ToPosition or PreciseCaretOwner.ToPosition + */ + public static class DirectionToPosition implements Direction { + + private double value; + private CaretOwner caret; + private double precision; + + /** + * + * @param caret + * @param value + */ + public DirectionToPosition(CaretOwner caret, double value, double precision) { + this.value = value; + this.caret = caret; + this.precision = precision; + } + + public DirectionToPosition(CaretOwner caret, double value) { + this(caret, value, 0); + } + + public int to() { + double diff = position() - caret.position(); + return (diff == 0) ? 0 : ((diff > 0) ? 1 : -1); + } + + /** + * + * @return + */ + @Override + public String toString() { + return "value == " + position(); + } + + /** + * + * @return + */ + protected double position() { + return value; + } + } + + /** + * + */ + protected static interface ScrollAction { + + /** + * + * @param direction + */ + public void scrollTo(int direction); + } + + /** + * + */ + protected class MouseScrollAction implements ScrollAction { + + Point up, down; + KeyboardModifier[] upMods, downMods; + + /** + * + * @param down + * @param downMods + * @param up + * @param upMods + */ + public MouseScrollAction(Point down, KeyboardModifier[] downMods, Point up, KeyboardModifier[] upMods) { + this.up = up; + this.down = down; + this.upMods = upMods; + this.downMods = downMods; + } + + /** + * + * @param down + * @param up + */ + public MouseScrollAction(Point down, Point up) { + this(up, new KeyboardModifier[0], up, new KeyboardModifier[0]); + } + + /** + * + * @param direction + */ + public void scrollTo(int direction) { + wrap.mouse().click(1, (direction > 0) ? up : down, MouseButtons.BUTTON1, + (direction > 0) ? upMods : downMods); + } + } + + /** + * + */ + protected class KeyboardScrollAction implements ScrollAction { + + KeyboardButton down, up; + KeyboardModifier[] downMods, upMods; + + /** + * + * @param down + * @param downMods + * @param up + * @param upMods + */ + public KeyboardScrollAction(KeyboardButton down, KeyboardModifier[] downMods, KeyboardButton up, KeyboardModifier[] upMods) { + this.down = down; + this.up = up; + this.downMods = downMods; + this.upMods = upMods; + } + + /** + * + * @param down + * @param up + */ + public KeyboardScrollAction(KeyboardButton down, KeyboardButton up) { + this(down, new KeyboardModifier[0], up, new KeyboardModifier[0]); + } + + /** + * + * @param direction + */ + public void scrollTo(int direction) { + wrap.keyboard().pushKey((direction > 0) ? up : down, + (direction > 0) ? upMods : downMods); + } + } +} --- /dev/null 2017-11-08 15:39:43.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/input/CaretText.java 2017-11-08 15:39:43.000000000 -0800 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.input; + +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardModifier; +import org.jemmy.interfaces.Text; + +/** + * + * @author shura + */ +public abstract class CaretText extends AbstractCaretOwner implements Text { + + static { + Environment.getEnvironment().setPropertyIfNotSet(RegexCaretDirection.REGEX_FLAGS, 0); + } + + TextCaret caret; + TextImpl text; + Wrap wrap; + + /** + * + * @param wrap + */ + public CaretText(Wrap wrap) { + this.wrap = wrap; + text = new TextImpl(wrap) { + public String text() { + return CaretText.this.text(); + } + }; + } + + public TextCaret caret() { + if (caret == null) { + initCaret(); + } + return caret; + } + + protected void initCaret() { + caret = new TextCaret(wrap, this); + } + + /** + * + * @return + */ + protected int getFlags() { + return (Integer)wrap.getEnvironment(). + getProperty(RegexCaretDirection.REGEX_FLAGS, 0); + } + + public void type(String newText) { + text.type(newText); + } + + public void clear() { + text.clear(); + } + + /** + * Moves caret to a beginning/end of an index'th occurance of the regex. + * @param regex + * @param front + * @param index + */ + public void to(String regex, boolean front, int index) { + caret().to(new RegexCaretDirection(this, this, regex, getFlags(), front, index)); + } + + /** + * Moves caret to a beginning/end of the first occurance of the regex. + * @param regex + * @param front + */ + public void to(String regex, boolean front) { + to(regex, front, 0); + } + + /** + * Moves caret to a beginning the first occurance of the regex. + * @param regex + */ + public void to(String regex) { + to(regex, true); + } + + /** + * + * @param left + * @param leftMods + * @param right + * @param rightMods + */ + public void addNavKeys(KeyboardButton left, KeyboardModifier[] leftMods, + KeyboardButton right, KeyboardModifier[] rightMods) { + caret().addNavKeys(left, leftMods, right, rightMods); + } + + /** + * + * @param left + * @param right + */ + public void addNavKeys(KeyboardButton left, KeyboardButton right) { + addNavKeys(left, new KeyboardModifier[0], right, new KeyboardModifier[0]); + } +} --- /dev/null 2017-11-08 15:39:43.000000000 -0800 +++ new/core/JemmyCore/src/org/jemmy/input/CharBindingMap.java 2017-11-08 15:39:43.000000000 -0800 @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2007, 2017 Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.input; + +import org.jemmy.interfaces.Button; +import org.jemmy.interfaces.Modifier; + +/** + * + * Defines char-to-key binding. The generation of a symbol will, + * in general, require modifier keys to be pressed prior to pressing + * a primary key. Classes that implement CharBindingMap + * communicate what modifiers and primary key are required to generate + * a given symbol. + * + * @param