1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy;
24
25import java.io.BufferedReader;
26import java.io.FileInputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.InputStreamReader;
30import java.lang.reflect.InvocationTargetException;
31import java.util.Enumeration;
32import java.util.Hashtable;
33import java.util.Properties;
34import java.util.Stack;
35import java.util.StringTokenizer;
36
37import org.netbeans.jemmy.drivers.APIDriverInstaller;
38import org.netbeans.jemmy.drivers.DefaultDriverInstaller;
39import org.netbeans.jemmy.drivers.DriverInstaller;
40import org.netbeans.jemmy.drivers.InputDriverInstaller;
41import org.netbeans.jemmy.explorer.GUIBrowser;
42
43/**
44 *
45 * Keeps default Jemmy properties.
46 *
47 * @author Alexandre Iline (alexandre.iline@oracle.com)
48 *
49 */
50public class JemmyProperties {
51
52    /**
53     * The event queue model mask.
54     *
55     * @see #getCurrentDispatchingModel()
56     * @see #setCurrentDispatchingModel(int)
57     */
58    public static final int QUEUE_MODEL_MASK = 1;
59
60    /**
61     * The robot using model mask.
62     *
63     * @see #getCurrentDispatchingModel()
64     * @see #setCurrentDispatchingModel(int)
65     */
66    public static final int ROBOT_MODEL_MASK = 2;
67
68    /**
69     * Event shorcutting model mask. Should not be used together with robot
70     * mask.
71     *
72     * @see #getCurrentDispatchingModel()
73     * @see #setCurrentDispatchingModel(int)
74     */
75    public static final int SHORTCUT_MODEL_MASK = 4;
76
77    /**
78     * The robot using model mask.
79     *
80     * @see #getCurrentDispatchingModel()
81     * @see #setCurrentDispatchingModel(int)
82     */
83    public static final int SMOOTH_ROBOT_MODEL_MASK = 8;
84
85    private static final int DEFAULT_DRAG_AND_DROP_STEP_LENGTH = 100;
86    private static final Stack<JemmyProperties> propStack = new Stack<>();
87
88    Hashtable<String, Object> properties;
89
90    /**
91     *
92     */
93    protected JemmyProperties() {
94        super();
95        properties = new Hashtable<>();
96        setProperty("timeouts", new Timeouts());
97        setProperty("output", new TestOut());
98        setProperty("resources", new BundleManager());
99        setProperty("binding.map", new DefaultCharBindingMap());
100        setProperty("dispatching.model", getDefaultDispatchingModel());
101        setProperty("drag_and_drop.step_length", DEFAULT_DRAG_AND_DROP_STEP_LENGTH);
102    }
103
104    /**
105     * Returns major version (like 1.0).
106     *
107     * @return a String representing the major version value.
108     */
109    public static String getMajorVersion() {
110        return (extractValue(getProperties().getClass().
111                getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
112                "Jemmy-MajorVersion"));
113    }
114
115    /**
116     * Returns minor version (like 1).
117     *
118     * @return a String representing the minor version value.
119     */
120    public static String getMinorVersion() {
121        return (extractValue(getProperties().getClass().
122                getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
123                "Jemmy-MinorVersion"));
124    }
125
126    /**
127     * Returns build (like 20011231 (yyyymmdd)).
128     *
129     * @return a String representing the build value.
130     */
131    public static String getBuild() {
132        return (extractValue(getProperties().getClass().
133                getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
134                "Jemmy-Build"));
135    }
136
137    /**
138     * Returns full version string (like 1.0.1-20011231).
139     *
140     * @return a String representing the full version value.
141     */
142    public static String getFullVersion() {
143        return (getMajorVersion() + "."
144                + getMinorVersion() + "-"
145                + getBuild());
146    }
147
148    /**
149     * Returns version string (like 1.0.1).
150     *
151     * @return a String representing the short version value.
152     */
153    public static String getVersion() {
154        return (getMajorVersion() + "."
155                + getMinorVersion());
156    }
157
158    /**
159     * Creates a copy of the current JemmyProperties object and pushes it into
160     * the properties stack.
161     *
162     * @return New current properties.
163     */
164    public static JemmyProperties push() {
165        return push(getProperties().cloneThis());
166    }
167
168    /**
169     * Pops last pushed properties from the properties stack. If stack has just
170     * one element, does nothing.
171     *
172     * @return Poped properties.
173     */
174    public static JemmyProperties pop() {
175        JemmyProperties result = propStack.pop();
176        if (propStack.isEmpty()) {
177            propStack.push(result);
178        }
179        return result;
180    }
181
182    /**
183     * Just like getProperties().getProperty(propertyName).
184     *
185     * @param propertyName a property key
186     * @return a property value
187     * @see #setCurrentProperty
188     * @see #setCurrentTimeout
189     */
190    public static Object getCurrentProperty(String propertyName) {
191        return getProperties().getProperty(propertyName);
192    }
193
194    /**
195     * Just like getProperties().setProperty(propertyName, propertyValue).
196     *
197     * @param propertyName a property key
198     * @param propertyValue a property value
199     * @return previous property value
200     * @see #getCurrentProperty
201     * @see #getCurrentTimeout
202     */
203    public static Object setCurrentProperty(String propertyName, Object propertyValue) {
204        return getProperties().setProperty(propertyName, propertyValue);
205    }
206
207    /**
208     * Removes a property from current properties list.
209     *
210     * @param propertyName a property key.
211     * @return previous property value
212     */
213    public static Object removeCurrentProperty(String propertyName) {
214        return getProperties().removeProperty(propertyName);
215    }
216
217    /**
218     * Returns the current key values.
219     *
220     * @return an array of Strings representing the current key values
221     */
222    public static String[] getCurrentKeys() {
223        return getProperties().getKeys();
224    }
225
226    /**
227     * Just like getProperties().getTimeouts().
228     *
229     * @return a Timeouts object representing the current timeouts.
230     * @see #setCurrentTimeouts
231     */
232    public static Timeouts getCurrentTimeouts() {
233        return getProperties().getTimeouts();
234    }
235
236    /**
237     * Just like getProperties().setTimeouts(to).
238     *
239     * @param to New timeouts
240     * @return old timeouts.
241     * @see #getCurrentTimeouts
242     */
243    public static Timeouts setCurrentTimeouts(Timeouts to) {
244        return getProperties().setTimeouts(to);
245    }
246
247    /**
248     * Just like getProperties().getTimeouts().setTimeout(name, newValue).
249     *
250     * @param name a timeout name
251     * @param newValue a timeout value
252     * @return previous timeout value
253     * @see #getCurrentTimeout
254     */
255    public static long setCurrentTimeout(String name, long newValue) {
256        return getProperties().getTimeouts().setTimeout(name, newValue);
257    }
258
259    /**
260     * Just like getProperties().getTimeouts().getTimeout(name).
261     *
262     * @param name a timeout name
263     * @return a timeout value
264     * @see #setCurrentTimeout
265     */
266    public static long getCurrentTimeout(String name) {
267        return getProperties().getTimeouts().getTimeout(name);
268    }
269
270    /**
271     * Just like getProperties().getTimeouts().initTimeout(name, newValue).
272     *
273     * @param name a timeout name
274     * @param newValue a timeout value
275     * @return a timeout value
276     * @see #setCurrentTimeout
277     */
278    public static long initCurrentTimeout(String name, long newValue) {
279        return getProperties().getTimeouts().initTimeout(name, newValue);
280    }
281
282    /**
283     * Just like getProperties().getOutput().
284     *
285     * @return a TestOut object representing the current output.
286     * @see #setCurrentOutput
287     */
288    public static TestOut getCurrentOutput() {
289        return getProperties().getOutput();
290    }
291
292    /**
293     * Just like getProperties().setOutput(out).
294     *
295     * @param out new output
296     * @return a TestOut object representing the current output.
297     * @see #getCurrentOutput
298     */
299    public static TestOut setCurrentOutput(TestOut out) {
300        return getProperties().setOutput(out);
301    }
302
303    /**
304     * Just like getProperties().getBundleManager().
305     *
306     * @return a BundleManager object representing the current bundle manager.
307     * @see #setCurrentBundleManager
308     */
309    public static BundleManager getCurrentBundleManager() {
310        return getProperties().getBundleManager();
311    }
312
313    /**
314     * Just like getProperties().setBundleManager(resources).
315     *
316     * @param resources new BundleManager
317     * @return a BundleManager object representing the current bundle manager.
318     * @see #getCurrentBundleManager
319     */
320    public static BundleManager setCurrentBundleManager(BundleManager resources) {
321        return getProperties().setBundleManager(resources);
322    }
323
324    /**
325     * Just like getProperties().getBundleManager().getResource(key).
326     *
327     * @param key a resource key.
328     * @return a resource value
329     */
330    public static String getCurrentResource(String key) {
331        return getProperties().getBundleManager().getResource(key);
332    }
333
334    /**
335     * Just like getProperties().getBundleManager().getResource(bundleID, key).
336     *
337     * @param key a resource key.
338     * @param bundleID a bundle ID
339     * @return a resource value
340     */
341    public static String getCurrentResource(String bundleID, String key) {
342        return getProperties().getBundleManager().getResource(bundleID, key);
343    }
344
345    /**
346     * Just like getProperties().getCharBindingMap().
347     *
348     * @return a CharBindingMap object representing the current char binding
349     * map.
350     * @see #setCurrentCharBindingMap
351     */
352    public static CharBindingMap getCurrentCharBindingMap() {
353        return getProperties().getCharBindingMap();
354    }
355
356    /**
357     * Just like getProperties().setCharBindingMap(map).
358     *
359     * @param map new CharBindingMap.
360     * @return old CharBindingMap object.
361     * @see #getCurrentCharBindingMap
362     */
363    public static CharBindingMap setCurrentCharBindingMap(CharBindingMap map) {
364        return getProperties().setCharBindingMap(map);
365    }
366
367    /**
368     * Returns the current dispatching model.
369     *
370     * @return Event dispatching model.
371     * @see #getDispatchingModel()
372     * @see #setCurrentDispatchingModel(int)
373     * @see #QUEUE_MODEL_MASK
374     * @see #ROBOT_MODEL_MASK
375     */
376    public static int getCurrentDispatchingModel() {
377        return getProperties().getDispatchingModel();
378    }
379
380    /**
381     * Defines event dispatching model. If (model & ROBOT_MODEL_MASK) != 0
382     * java.awt.Robot class is used to reproduce user actions, otherwise actions
383     * are reproduced by event posting. If (model & QUEUE_MODEL_MASK) != 0
384     * actions are reproduced through event queue.
385     *
386     * @param model New dispatching model value.
387     * @return Previous dispatching model value.
388     * @see #setDispatchingModel(int)
389     * @see #getCurrentDispatchingModel()
390     * @see #QUEUE_MODEL_MASK
391     * @see #ROBOT_MODEL_MASK
392     * @see #initDispatchingModel(boolean, boolean)
393     * @see #initDispatchingModel()
394     */
395    public static int setCurrentDispatchingModel(int model) {
396        return getProperties().setDispatchingModel(model);
397    }
398
399    /**
400     * Returns default event dispatching model.
401     *
402     * @return QUEUE_MODEL_MASK
403     * @see #setCurrentDispatchingModel(int)
404     * @see #QUEUE_MODEL_MASK
405     * @see #ROBOT_MODEL_MASK
406     */
407    public static int getDefaultDispatchingModel() {
408        return SHORTCUT_MODEL_MASK | QUEUE_MODEL_MASK;
409    }
410
411    /**
412     * Returns the current drag and drop step length value.
413     *
414     * @return Pixel count to move mouse during one drag'n'drop step.
415     * @see #getDragAndDropStepLength()
416     * @see #setCurrentDragAndDropStepLength(int)
417     */
418    public static int getCurrentDragAndDropStepLength() {
419        return getProperties().getDragAndDropStepLength();
420    }
421
422    /**
423     * Specifies the current drag and drop step length value.
424     *
425     * @param model Pixel count to move mouse during one drag'n'drop step.
426     * @return Previous value.
427     * @see #setDragAndDropStepLength(int)
428     * @see #getCurrentDragAndDropStepLength()
429     */
430    public static int setCurrentDragAndDropStepLength(int model) {
431        return getProperties().setDragAndDropStepLength(model);
432    }
433
434    /**
435     * Peeks upper JemmyProperties instance from stack.
436     *
437     * @return a JemmyProperties object representing the properties value.
438     */
439    public static JemmyProperties getProperties() {
440        if (propStack.empty()) {
441            propStack.add(new JemmyProperties());
442        }
443        return propStack.peek();
444    }
445
446    /**
447     * Prints full version into standard output.
448     *
449     * @param argv Application args.
450     */
451    public static void main(String[] argv) {
452        if (argv.length == 0) {
453            System.out.println("Jemmy version : " + getVersion());
454        } else if (argv.length == 1
455                && argv[0].equals("-f")) {
456            System.out.println("Jemmy full version : " + getFullVersion());
457        } else if (argv.length > 0
458                && argv[0].equals("-e")) {
459            String[] newArgv = new String[argv.length - 1];
460            System.arraycopy(argv, 1, newArgv, 0, argv.length - 1);
461            GUIBrowser.main(newArgv);
462        } else {
463            System.out.println("Parameters: ");
464            System.out.println("<no parameters> - report Jemmy version.");
465            System.out.println("\"-f\" - report full jemmy version.");
466        }
467    }
468
469    /**
470     * Pushes properties stack.
471     *
472     * @param props a JemmyProperties instance to put into the stack head.
473     * @return a JemmyProperties object.
474     */
475    protected static JemmyProperties push(JemmyProperties props) {
476        return propStack.push(props);
477    }
478
479    static {
480        setCurrentDispatchingModel(getDefaultDispatchingModel());
481    }
482
483    /**
484     * Method to initialize timeouts and resources.
485     *
486     * @param prop_file File to get filenames from. <BR>
487     * Can contain definition of variables TIMEOUTS_FILE - full path to timeouts
488     * file, <BR>
489     * RESOURCE_FILE - full path to resource file.
490     * @see org.netbeans.jemmy.JemmyProperties#initProperties()
491     */
492    public void initProperties(String prop_file) {
493        try {
494            getOutput().printLine("Loading properties from " + prop_file + " file");
495            Properties props = new Properties();
496            try (FileInputStream fileStream = new FileInputStream(prop_file)) {
497                props.load(fileStream);
498            }
499            if (props.getProperty("TIMEOUTS_FILE") != null
500                    && !props.getProperty("TIMEOUTS_FILE").equals("")) {
501                getOutput().printLine("Loading timeouts from " + props.getProperty("TIMEOUTS_FILE")
502                        + " file");
503                getTimeouts().loadDefaults(props.getProperty("TIMEOUTS_FILE"));
504            }
505            if (props.getProperty("RESOURCE_FILE") != null
506                    && !props.getProperty("RESOURCE_FILE").equals("")) {
507                getOutput().printLine("Loading resources from " + props.getProperty("RESOURCE_FILE")
508                        + " file");
509                getBundleManager().loadBundleFromFile(props.getProperty("RESOURCE_FILE"), "");
510            }
511        } catch (IOException e) {
512            getOutput().printStackTrace(e);
513        }
514    }
515
516    /**
517     * Method to initialize timeouts and resources. <BR>
518     * Uses jemmy.properties system property to find file.
519     *
520     * @see org.netbeans.jemmy.JemmyProperties#initProperties(String)
521     */
522    public void initProperties() {
523        if (System.getProperty("jemmy.properties") != null
524                && !System.getProperty("jemmy.properties").equals("")) {
525            initProperties(System.getProperty("jemmy.properties"));
526        } else {
527            try {
528                getTimeouts().load();
529                getBundleManager().load();
530            } catch (IOException e) {
531                getOutput().printStackTrace(e);
532            }
533        }
534    }
535
536    /**
537     * Initializes dispatching model.
538     *
539     * @param queue Notifies that event queue dispatching should be used.
540     * @param robot Notifies that robot dispatching should be used.
541     * @param shortcut Notifies that event shorcutting should be used.
542     */
543    public void initDispatchingModel(boolean queue, boolean robot, boolean shortcut) {
544        initDispatchingModel(queue, robot, shortcut, false);
545    }
546
547    /**
548     * Initializes dispatching model.
549     *
550     * @param queue Notifies that event queue dispatching should be used.
551     * @param robot Notifies that robot dispatching should be used.
552     * @param shortcut Notifies that event shorcutting should be used.
553     */
554    public void initDispatchingModel(boolean queue, boolean robot, boolean shortcut, boolean smooth) {
555        int model = getDefaultDispatchingModel();
556        getOutput().print("Reproduce user actions ");
557        if (queue) {
558            model = QUEUE_MODEL_MASK;
559            getOutput().printLine("through event queue.");
560        } else {
561            model = model - (model & QUEUE_MODEL_MASK);
562            getOutput().printLine("directly.");
563        }
564        getOutput().print("Use ");
565        if (robot) {
566            model = model | ROBOT_MODEL_MASK;
567            getOutput().print("java.awt.Robot class");
568        } else {
569            model = model - (model & ROBOT_MODEL_MASK);
570            getOutput().print("event dispatching");
571        }
572        if (smooth) {
573            model = model | SMOOTH_ROBOT_MODEL_MASK;
574        } else {
575            model = model - (model & SMOOTH_ROBOT_MODEL_MASK);
576        }
577        getOutput().printLine(" to reproduce user actions");
578        if (shortcut) {
579            model = model | SHORTCUT_MODEL_MASK;
580            getOutput().print("Shortcut");
581        } else {
582            model = model - (model & SHORTCUT_MODEL_MASK);
583            getOutput().print("Dispatch");
584        }
585        getOutput().printLine(" test events");
586        setDispatchingModel(model);
587    }
588
589    /**
590     * Initializes dispatching model.
591     *
592     * @param queue Notifies that event queue dispatching should be used.
593     * @param robot Notifies that robot dispatching should be used.
594     */
595    public void initDispatchingModel(boolean queue, boolean robot) {
596        this.initDispatchingModel(queue, robot, false);
597    }
598
599    /**
600     * Initializes dispatching model. Uses "jemmy.queue_dispatching" and
601     * "jemmy.robot_dispatching" system properties to determine what model
602     * should be used. Possible values for the both properties: <BR>
603     * "off" - switch mode off. <BR>
604     * "on" - switch mode on. <BR>
605     * "" - use default value.
606     *
607     * @see #getDefaultDispatchingModel()
608     */
609    public void initDispatchingModel() {
610        boolean qmask = ((getDefaultDispatchingModel() & QUEUE_MODEL_MASK) != 0);
611        boolean rmask = ((getDefaultDispatchingModel() & ROBOT_MODEL_MASK) != 0);
612        boolean srmask = ((getDefaultDispatchingModel() & SMOOTH_ROBOT_MODEL_MASK) != 0);
613        boolean smask = ((getDefaultDispatchingModel() & SHORTCUT_MODEL_MASK) != 0);
614        if (System.getProperty("jemmy.queue_dispatching") != null
615                && !System.getProperty("jemmy.queue_dispatching").equals("")) {
616            qmask = System.getProperty("jemmy.queue_dispatching").equals("on");
617        }
618        if (System.getProperty("jemmy.robot_dispatching") != null
619                && !System.getProperty("jemmy.robot_dispatching").equals("")) {
620            rmask = System.getProperty("jemmy.robot_dispatching").equals("on");
621        }
622        if (System.getProperty("jemmy.smooth_robot_dispatching") != null
623                && !System.getProperty("jemmy.smooth_robot_dispatching").equals("")) {
624            srmask = System.getProperty("jemmy.smooth_robot_dispatching").equals("on");
625        }
626        if (System.getProperty("jemmy.shortcut_events") != null
627                && !System.getProperty("jemmy.shortcut_events").equals("")) {
628            smask = System.getProperty("jemmy.shortcut_events").equals("on");
629        }
630        initDispatchingModel(qmask, rmask, smask, srmask);
631    }
632
633    /**
634     * Inits properties and dispatching model from system environment variables.
635     *
636     * @see #initProperties()
637     * @see #initDispatchingModel()
638     */
639    public void init() {
640        initProperties();
641        initDispatchingModel();
642    }
643
644    /**
645     * Returns timeouts.
646     *
647     * @return the Timeouts value.
648     * @see #setTimeouts
649     */
650    public Timeouts getTimeouts() {
651        return (Timeouts) getProperty("timeouts");
652    }
653
654    /**
655     * Changes timeouts.
656     *
657     * @param to new timeouts.
658     * @return old timeouts.
659     * @see #getTimeouts
660     */
661    public Timeouts setTimeouts(Timeouts to) {
662        return (Timeouts) setProperty("timeouts", to);
663    }
664
665    /**
666     * Changes a timeouts value.
667     *
668     * @param name Timeout name
669     * @param newValue New timeout value
670     * @return previous timeout value
671     * @see #getTimeout
672     */
673    public long setTimeout(String name, long newValue) {
674        return getTimeouts().setTimeout(name, newValue);
675    }
676
677    /**
678     * Returns a timeouts value.
679     *
680     * @param name Timeout name
681     * @return a timeout value
682     * @see #setTimeout
683     */
684    public long getTimeout(String name) {
685        return getTimeouts().getTimeout(name);
686    }
687
688    /**
689     * Inits a timeouts value.
690     *
691     * @param name Timeout name
692     * @param newValue New timeout value
693     * @return a timeout value
694     */
695    public long initTimeout(String name, long newValue) {
696        return getTimeouts().initTimeout(name, newValue);
697    }
698
699    /**
700     * Returns output.
701     *
702     * @return a TestOut object representing the output value
703     * @see #setOutput
704     */
705    public TestOut getOutput() {
706        return (TestOut) getProperty("output");
707    }
708
709    /**
710     * Changes output.
711     *
712     * @param out new output.
713     * @return old output.
714     * @see #getOutput
715     */
716    public TestOut setOutput(TestOut out) {
717        return (TestOut) setProperty("output", out);
718    }
719
720    /**
721     * Returns bundle manager.
722     *
723     * @return a BundleManager object representing the bundle manager value.
724     * @see #setBundleManager
725     */
726    public BundleManager getBundleManager() {
727        return (BundleManager) getProperty("resources");
728    }
729
730    /**
731     * Changes bundle manager.
732     *
733     * @param resources new bundle manager.
734     * @return old bundle manager
735     * @see #getBundleManager
736     */
737    public BundleManager setBundleManager(BundleManager resources) {
738        return (BundleManager) setProperty("resources", resources);
739    }
740
741    /**
742     * Returns resource value.
743     *
744     * @param key Resource key.
745     * @return resource value
746     */
747    public String getResource(String key) {
748        return getBundleManager().getResource(key);
749    }
750
751    /**
752     * Returns resource value from the specified bundle.
753     *
754     * @param bundleID Id of a bundle to get resource from.
755     * @param key Resource key.
756     * @return resource value
757     */
758    public String getResource(String bundleID, String key) {
759        return getBundleManager().getResource(bundleID, key);
760    }
761
762    /**
763     * Returns char binding map.
764     *
765     * @return the char binding map.
766     * @see #setCharBindingMap
767     */
768    public CharBindingMap getCharBindingMap() {
769        return (CharBindingMap) getProperty("binding.map");
770    }
771
772    /**
773     * Changes char binding map.
774     *
775     * @param map new char binding map.
776     * @return old char binding map.
777     * @see #getCharBindingMap
778     */
779    public CharBindingMap setCharBindingMap(CharBindingMap map) {
780        return (CharBindingMap) setProperty("binding.map", map);
781    }
782
783    /**
784     * Returns the dispatching model.
785     *
786     * @return Event dispatching model.
787     * @see #getCurrentDispatchingModel()
788     * @see #setDispatchingModel(int)
789     * @see #QUEUE_MODEL_MASK
790     * @see #ROBOT_MODEL_MASK
791     */
792    public int getDispatchingModel() {
793        return (Integer) getProperty("dispatching.model");
794    }
795
796    private static DriverInstaller getDriverInstaller(int model) {
797        String name = System.getProperty("jemmy.drivers.installer");
798        DriverInstaller installer = null;
799        try {
800            if (name != null && !(name.length() == 0)) {
801                installer = (DriverInstaller) new ClassReference(name).newInstance(null, null);
802            }
803        } catch (ClassNotFoundException
804                | IllegalAccessException
805                | NoSuchMethodException
806                | InstantiationException
807                | InvocationTargetException e) {
808            getCurrentOutput().printLine("Cannot init driver installer:");
809            getCurrentOutput().printStackTrace(e);
810        }
811        if (installer == null) {
812            if (System.getProperty("os.name").startsWith("Mac OS X")) {
813                installer = new APIDriverInstaller((model & SHORTCUT_MODEL_MASK) != 0);
814            } else {
815                installer = new DefaultDriverInstaller((model & SHORTCUT_MODEL_MASK) != 0);
816            }
817        }
818        getCurrentOutput().printLine("Using " + installer.getClass().getName() + " driver installer");
819        return installer;
820    }
821
822    /**
823     * Specifies the dispatching model value.
824     *
825     * @param model New dispatching model value.
826     * @return Previous dispatching model value.
827     * @see #setCurrentDispatchingModel(int)
828     * @see #getDispatchingModel()
829     * @see #QUEUE_MODEL_MASK
830     * @see #ROBOT_MODEL_MASK
831     */
832    public int setDispatchingModel(int model) {
833        new InputDriverInstaller((model & ROBOT_MODEL_MASK) == 0, (model & SMOOTH_ROBOT_MODEL_MASK) != 0).install();
834        getDriverInstaller(model).install();
835        return (Integer) setProperty("dispatching.model", model);
836    }
837
838    /**
839     * Returns the drag and drop step length value.
840     *
841     * @return Pixel count to move mouse during one drag'n'drop step.
842     * @see #getCurrentDragAndDropStepLength()
843     * @see #setDragAndDropStepLength(int)
844     */
845    public int getDragAndDropStepLength() {
846        return (Integer) getProperty("drag_and_drop.step_length");
847    }
848
849    /**
850     * Specifies the drag and drop step length value.
851     *
852     * @param length Pixel count to move mouse during one drag'n'drop step.
853     * @return Previous value.
854     * @see #setCurrentDragAndDropStepLength(int)
855     * @see #getDragAndDropStepLength()
856     */
857    public int setDragAndDropStepLength(int length) {
858        return (Integer) setProperty("drag_and_drop.step_length", length);
859    }
860
861    /**
862     * Checks if "name" propery currently has a value.
863     *
864     * @param name Property name. Should by unique.
865     * @return true if property was defined.
866     * @see #setProperty(String, Object)
867     * @see #getProperty(String)
868     */
869    public boolean contains(String name) {
870        return properties.containsKey(name);
871    }
872
873    /**
874     * Saves object as a static link to be used by other objects.
875     *
876     * @param name Property name. Should by unique.
877     * @param newValue Property value.
878     * @return Previous value of "name" property.
879     * @see #setCurrentProperty(String, Object)
880     * @see #getProperty(String)
881     * @see #contains(String)
882     */
883    public Object setProperty(String name, Object newValue) {
884        Object oldValue = null;
885        if (contains(name)) {
886            oldValue = properties.get(name);
887            properties.remove(name);
888        }
889        properties.put(name, newValue);
890        return oldValue;
891    }
892
893    /**
894     * Returns the property value.
895     *
896     * @param name Property name. Should by unique.
897     * @return Property value stored by setProperty(String, Object) method.
898     * @see #getCurrentProperty(String)
899     * @see #setProperty(String, Object)
900     * @see #contains(String)
901     */
902    public Object getProperty(String name) {
903        if (contains(name)) {
904            return properties.get(name);
905        } else {
906            return null;
907        }
908    }
909
910    /**
911     * Removes the property.
912     *
913     * @param name A name of the property to be removed.
914     * @return previous property value
915     */
916    public Object removeProperty(String name) {
917        if (contains(name)) {
918            return properties.remove(name);
919        } else {
920            return null;
921        }
922    }
923
924    /**
925     * Returns the key values.
926     *
927     * @return an array of Strings representing the key values.
928     */
929    public String[] getKeys() {
930        Enumeration<String> keys = properties.keys();
931        String[] result = new String[properties.size()];
932        int i = 0;
933        while (keys.hasMoreElements()) {
934            result[i] = keys.nextElement();
935            i++;
936        }
937        return result;
938    }
939
940    /**
941     * Copy all properties from this instance into another.
942     *
943     * @param properties a JemmyProperties instance to copy properties into.
944     */
945    public void copyTo(JemmyProperties properties) {
946        String[] keys = getKeys();
947        for (String key : keys) {
948            properties.setProperty(key, getProperty(key));
949        }
950        //some should be cloned
951        properties.setTimeouts(getTimeouts().cloneThis());
952        properties.setBundleManager(getBundleManager().cloneThis());
953    }
954
955    /**
956     * Creates an exact copy on this instance.
957     *
958     * @return new JemmyProperties object.
959     */
960    protected JemmyProperties cloneThis() {
961        JemmyProperties result = new JemmyProperties();
962        copyTo(result);
963        return result;
964    }
965
966    private static String extractValue(InputStream stream, String varName) {
967        try {
968            BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
969            StringTokenizer token;
970            String nextLine;
971            while ((nextLine = reader.readLine()) != null) {
972                token = new StringTokenizer(nextLine, ":");
973                String nextToken = token.nextToken();
974                if (nextToken.trim().equals(varName)) {
975                    return token.nextToken().trim();
976                }
977            }
978            return "";
979        } catch (IOException e) {
980            getCurrentOutput().printStackTrace(e);
981            return "";
982        }
983    }
984
985}
986