/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s): Alexandre Iline.
 *
 * The Original Software is the Jemmy library.
 * The Initial Developer of the Original Software is Alexandre Iline.
 * All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 *
 *
 *
 * $Id$ $Revision$ $Date$
 *
 */

package org.netbeans.jemmy;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Stack;
import java.util.StringTokenizer;

import org.netbeans.jemmy.drivers.APIDriverInstaller;
import org.netbeans.jemmy.drivers.DefaultDriverInstaller;
import org.netbeans.jemmy.drivers.DriverInstaller;
import org.netbeans.jemmy.drivers.InputDriverInstaller;
import org.netbeans.jemmy.explorer.GUIBrowser;

/**
 * 
 * Keeps default Jemmy properties.
 *
 * @author Alexandre Iline (alexandre.iline@sun.com)
 * 
 */

public class JemmyProperties {

    /**
     * The event queue model mask.
     * @see #getCurrentDispatchingModel()
     * @see #setCurrentDispatchingModel(int)
     */
    public static int QUEUE_MODEL_MASK = 1;

    /**
     * The robot using model mask.
     * @see #getCurrentDispatchingModel()
     * @see #setCurrentDispatchingModel(int)
     */
    public static int ROBOT_MODEL_MASK = 2;

    /**
     * Event shorcutting model mask. Should not be used
     * together with robot mask.
     * @see #getCurrentDispatchingModel()
     * @see #setCurrentDispatchingModel(int)
     */
    public static int SHORTCUT_MODEL_MASK = 4;

    /**
     * The robot using model mask.
     * @see #getCurrentDispatchingModel()
     * @see #setCurrentDispatchingModel(int)
     */
    public static int SMOOTH_ROBOT_MODEL_MASK = 8;

    private static final int DEFAULT_DRAG_AND_DROP_STEP_LENGTH = 100;
    private static Stack propStack = null;

    Hashtable properties;

    /***/
    protected JemmyProperties() {
	super();
	properties = new Hashtable();
	setProperty("timeouts", new Timeouts());
	setProperty("output", new TestOut());
	setProperty("resources", new BundleManager());
	setProperty("binding.map", new DefaultCharBindingMap());
	setProperty("dispatching.model", new Integer(getDefaultDispatchingModel()));
	setProperty("drag_and_drop.step_length", new Integer(DEFAULT_DRAG_AND_DROP_STEP_LENGTH));
    }

    /**
     * Returns major version (like 1.0).
     * @return a String representing the major version value.
     */
    public static String getMajorVersion() {
        return(extractValue(getProperties().getClass().
			    getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
			    "Jemmy-MajorVersion"));
    }

    /**
     * Returns minor version (like 1).
     * @return a String representing the minor version value.
     */
    public static String getMinorVersion() {
        return(extractValue(getProperties().getClass().
			    getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
			    "Jemmy-MinorVersion"));
    }

    /**
     * Returns build (like 20011231 (yyyymmdd)).
     * @return a String representing the build value.
     */
    public static String getBuild() {
        return(extractValue(getProperties().getClass().
			    getClassLoader().getResourceAsStream("org/netbeans/jemmy/version_info"),
			    "Jemmy-Build"));
    }

    /**
     * Returns full version string (like 1.0.1-20011231).
     * @return a String representing the full version value.
     */
    public static String getFullVersion() {
	return(getMajorVersion() + "." +
	       getMinorVersion() + "-" +
               getBuild());
    }

    /**
     * Returns version string (like 1.0.1).
     * @return a String representing the short version value.
     */
    public static String getVersion() {
	return(getMajorVersion() + "." +
	       getMinorVersion());
    }

    /**
     * Creates a copy of the current JemmyProperties object
     * and pushes it into the properties stack. 
     * @return New current properties.
     */
    public static JemmyProperties push() {
        return(push(getProperties().cloneThis()));
    }

    /**
     * Pops last pushed properties from the properties stack. 
     * If stack has just one element, does nothing.
     * @return Poped properties.
     */
    public static JemmyProperties pop() {
	JemmyProperties result = (JemmyProperties)propStack.pop();
	if(propStack.isEmpty()) {
	    propStack.push(result);
	}
	return(result);
    }

    /**
     * Just like getProperties().getProperty(propertyName).
     * @param	propertyName a property key
     * @return a property value
     * @see #setCurrentProperty
     * @see #setCurrentTimeout
     */
    public static Object getCurrentProperty(String propertyName) {
	return(getProperties().getProperty(propertyName));
    }

    /**
     * Just like getProperties().setProperty(propertyName, propertyValue).
     * @param	propertyName a property key
     * @param	propertyValue a property value
     * @return previous property value
     * @see #getCurrentProperty
     * @see #getCurrentTimeout
     */
    public static Object setCurrentProperty(String propertyName, Object propertyValue) {
	return(getProperties().setProperty(propertyName, propertyValue));
    }

    /**
     * Removes a property from current properties list.
     * @param propertyName a property key.
     * @return previous property value
     */
    public static Object removeCurrentProperty(String propertyName) {
	return(getProperties().removeProperty(propertyName));
    }

    /**
     * Returns the current key values.
     * @return an array of Strings representing the current key values
     */
    public static String[] getCurrentKeys() {
	return(getProperties().getKeys());
    }

    /**
     * Just like getProperties().getTimeouts().
     * @return a Timeouts object representing the current timeouts.
     * @see #setCurrentTimeouts
     */
    public static Timeouts getCurrentTimeouts() {
	return(getProperties().getTimeouts());
    }

    /**
     * Just like getProperties().setTimeouts(to).
     * @param	to New timeouts
     * @return old timeouts.
     * @see #getCurrentTimeouts
     */
    public static Timeouts setCurrentTimeouts(Timeouts to) {
	return(getProperties().setTimeouts(to));
    }

    /**
     * Just like getProperties().getTimeouts().setTimeout(name, newValue).
     * @param	name a timeout name
     * @param	newValue a timeout value
     * @return previous timeout value
     * @see #getCurrentTimeout
     */
    public static long setCurrentTimeout(String name, long newValue) {
	return(getProperties().getTimeouts().setTimeout(name, newValue));
    }

    /**
     * Just like getProperties().getTimeouts().getTimeout(name).
     * @param	name a timeout name
     * @return a timeout value
     * @see #setCurrentTimeout
     */
    public static long getCurrentTimeout(String name) {
	return(getProperties().getTimeouts().getTimeout(name));
    }

    /**
     * Just like getProperties().getTimeouts().initTimeout(name, newValue).
     * @param	name a timeout name
     * @param	newValue a timeout value
     * @return a timeout value
     * @see #setCurrentTimeout
     */
    public static long initCurrentTimeout(String name, long newValue) {
	return(getProperties().getTimeouts().initTimeout(name, newValue));
    }

    /**
     * Just like getProperties().getOutput().
     * @return a TestOut object representing the current output.
     * @see #setCurrentOutput
     */
    public static TestOut getCurrentOutput() {
	return(getProperties().getOutput());
    }

    /**
     * Just like getProperties().setOutput(out).
     * @param	out new output
     * @return a TestOut object representing the current output.
     * @see #getCurrentOutput
     */
    public static TestOut setCurrentOutput(TestOut out) {
	return(getProperties().setOutput(out));
    }
   
    /**
     * Just like getProperties().getBundleManager().
     * @return a BundleManager object representing the current bundle manager.
     * @see #setCurrentBundleManager
     */
    public static BundleManager getCurrentBundleManager() {
	return(getProperties().getBundleManager());
    }

    /**
     * Just like getProperties().setBundleManager(resources).
     * @param  resources new BundleManager
     * @return a BundleManager object representing the current bundle manager.
     * @see #getCurrentBundleManager
     */
    public static BundleManager setCurrentBundleManager(BundleManager resources) {
	return(getProperties().setBundleManager(resources));
    }

    /**
     * Just like getProperties().getBundleManager().getResource(key).
     * @param	key a resource key.
     * @return a resource value
     */
    public static String getCurrentResource(String key) {
	return(getProperties().getBundleManager().getResource(key));
    }

    /**
     * Just like getProperties().getBundleManager().getResource(bundleID, key).
     * @param	key a resource key.
     * @param bundleID a bundle ID
     * @return a resource value
     */
    public static String getCurrentResource(String bundleID, String key) {
	return(getProperties().getBundleManager().getResource(bundleID, key));
    }

    /**
     * Just like getProperties().getCharBindingMap().
     * @return a CharBindingMap object representing the current char binding map.
     * @see #setCurrentCharBindingMap
     */
    public static CharBindingMap getCurrentCharBindingMap() {
	return(getProperties().getCharBindingMap());
    }

    /**
     * Just like getProperties().setCharBindingMap(map).
     * @param	map new CharBindingMap.
     * @return old CharBindingMap object.
     * @see #getCurrentCharBindingMap
     */
    public static CharBindingMap setCurrentCharBindingMap(CharBindingMap map) {
	return(getProperties().setCharBindingMap(map));
    }

    /**
     * Returns the current dispatching model.
     * @return Event dispatching model.
     * @see #getDispatchingModel()
     * @see #setCurrentDispatchingModel(int)
     * @see #QUEUE_MODEL_MASK
     * @see #ROBOT_MODEL_MASK
     */
    public static int getCurrentDispatchingModel() {
	return(getProperties().getDispatchingModel());
    }

    /**
     * Defines event dispatching model.
     * If (model & ROBOT_MODEL_MASK) != 0 java.awt.Robot class
     * is used to reproduce user actions, otherwise actions
     * are reproduced by event posting.
     * If (model & QUEUE_MODEL_MASK) != 0 actions are reproduced through
     * event queue.
     * @param model New dispatching model value.
     * @return Previous dispatching model value.
     * @see #setDispatchingModel(int)
     * @see #getCurrentDispatchingModel()
     * @see #QUEUE_MODEL_MASK
     * @see #ROBOT_MODEL_MASK
     * @see #initDispatchingModel(boolean, boolean)
     * @see #initDispatchingModel()
     */
    public static int setCurrentDispatchingModel(int model) {
	return(getProperties().setDispatchingModel(model));
    }

    /**
     * Returns default event dispatching model.
     * @return QUEUE_MODEL_MASK
     * @see #setCurrentDispatchingModel(int)
     * @see #QUEUE_MODEL_MASK
     * @see #ROBOT_MODEL_MASK
     */
    public static int getDefaultDispatchingModel() {
	return(SHORTCUT_MODEL_MASK | QUEUE_MODEL_MASK);
    }

    /**
     * Returns the current drag and drop step length value.
     * @return Pixel count to move mouse during one drag'n'drop step.
     * @see #getDragAndDropStepLength()
     * @see #setCurrentDragAndDropStepLength(int)
     */
    public static int getCurrentDragAndDropStepLength() {
	return(getProperties().getDragAndDropStepLength());
    }

    /**
     * Specifies the current drag and drop step length value.
     * @param model Pixel count to move mouse during one drag'n'drop step.
     * @return Previous value.
     * @see #setDragAndDropStepLength(int)
     * @see #getCurrentDragAndDropStepLength()
     */
    public static int setCurrentDragAndDropStepLength(int model) {
	return(getProperties().setDragAndDropStepLength(model));
    }

    /**
     * Peeks upper JemmyProperties instance from stack.
     * @return a JemmyProperties object representing the properties value.
     */
    public static JemmyProperties getProperties() {
	if(propStack == null) {
	    propStack = new Stack();
	}
	if(propStack.empty()) {
	    propStack.add(new JemmyProperties());
	}
	return((JemmyProperties)propStack.peek());
    }

    /**
     * Prints full version into satndart output.
     * @param argv Application args.
     */
    public static void main(String[] argv) {
        if(argv.length == 0) {
	System.out.println("Jemmy version : " + getVersion());
        } else if(argv.length == 1 &&
                  argv[0].equals("-f")) {
            System.out.println("Jemmy full version : " + getFullVersion());
        } else if(argv.length > 0 &&
                  argv[0].equals("-e")) {
            String[] newArgv = new String[argv.length -1];
            for(int i = 1; i < argv.length; i++) {
                newArgv[i-1] = argv[i];
            }
            GUIBrowser.main(newArgv);
        } else {
            System.out.println("Parameters: ");
            System.out.println("<no parameters> - report Jemmy version.");
            System.out.println("\"-f\" - report full jemmy version.");
        }
    }

    /**
     * Pushes properties stack. 
     * @param props a JemmyProperties instance to put into the stack head.
     * @return a JemmyProperties object.
     */
    protected static JemmyProperties push(JemmyProperties props) {
	return((JemmyProperties)propStack.push(props));
    }

    static {
        setCurrentDispatchingModel(getDefaultDispatchingModel());
    }

    /**
     * Method to initialize timeouts and resources.
     * @param prop_file File to get filenames from. <BR>
     * Can contain definition of variables TIMEOUTS_FILE - full path to timeouts file, <BR>
     * RESOURCE_FILE - full path to resource file.
     * @see org.netbeans.jemmy.JemmyProperties#initProperties()
     */
    public void initProperties(String prop_file) {
	try {
	    getOutput().printLine("Loading properties from " + prop_file + " file");
	    Properties props = new Properties();
	    props.load(new FileInputStream(prop_file));
	    if(props.getProperty("TIMEOUTS_FILE") != null &&
	       !props.getProperty("TIMEOUTS_FILE").equals("")) {
		getOutput().printLine("Loading timeouts from " + props.getProperty("TIMEOUTS_FILE") + 
				      " file");
		getTimeouts().loadDefaults(props.getProperty("TIMEOUTS_FILE"));
	    }
	    if(props.getProperty("RESOURCE_FILE") != null &&
	       !props.getProperty("RESOURCE_FILE").equals("")) {
		getOutput().printLine("Loading resources from " + props.getProperty("RESOURCE_FILE") + 
				      " file");
		getBundleManager().loadBundleFromFile(props.getProperty("RESOURCE_FILE"), "");
	    }
	} catch(IOException e) {
	    getOutput().printStackTrace(e);
	}
    }

    /**
     * Method to initialize timeouts and resources. <BR>
     * Uses jemmy.properties system property to find file.
     * @see org.netbeans.jemmy.JemmyProperties#initProperties(String)
     */
    public void initProperties() {
	if(System.getProperty("jemmy.properties") != null && 
	   !System.getProperty("jemmy.properties").equals("")) {
	    initProperties(System.getProperty("jemmy.properties"));
	} else {
	    try {
		getTimeouts().load();
		getBundleManager().load();
	    } catch(IOException e) {
		getOutput().printStackTrace(e);
	    }
	}
    }

    /**
     * Initializes dispatching model.
     * @param queue Notifies that event queue dispatching should be used.
     * @param robot Notifies that robot dispatching should be used.
     * @param shortcut Notifies that event shorcutting should be used.
     */
    public void initDispatchingModel(boolean queue, boolean robot, boolean shortcut) {
    initDispatchingModel(queue, robot, shortcut, false);    
    }
    /**
     * Initializes dispatching model.
     * @param queue Notifies that event queue dispatching should be used.
     * @param robot Notifies that robot dispatching should be used.
     * @param shortcut Notifies that event shorcutting should be used.
     */
    public void initDispatchingModel(boolean queue, boolean robot, boolean shortcut, boolean smooth) {
	int model = getDefaultDispatchingModel();
	getOutput().print("Reproduce user actions ");
	if(queue) {
	    model = QUEUE_MODEL_MASK;
	    getOutput().printLine("through event queue.");
	} else {
	    model = model - (model & QUEUE_MODEL_MASK);
	    getOutput().printLine("directly.");
	}
	getOutput().print("Use ");
	if(robot) {
	    model = model | ROBOT_MODEL_MASK;
	    getOutput().print("java.awt.Robot class");
	} else {
	    model = model - (model & ROBOT_MODEL_MASK);
	    getOutput().print("event dispatching");
	}
	if(smooth) {
	    model = model | SMOOTH_ROBOT_MODEL_MASK;
	} else {
	    model = model - (model & SMOOTH_ROBOT_MODEL_MASK);
	}
	getOutput().printLine(" to reproduce user actions");
	if(shortcut) {
	    model = model | SHORTCUT_MODEL_MASK;
	    getOutput().print("Shortcut");
	} else {
	    model = model - (model & SHORTCUT_MODEL_MASK);
	    getOutput().print("Dispatch");
	}
	getOutput().printLine(" test events");
	setDispatchingModel(model);
    }

    /**
     * Initializes dispatching model.
     * @param queue Notifies that event queue dispatching should be used.
     * @param robot Notifies that robot dispatching should be used.
     */
    public void initDispatchingModel(boolean queue, boolean robot) {
        this.initDispatchingModel(queue, robot, false);
    }

    /**
     * Initializes dispatching model.
     * Uses "jemmy.queue_dispatching" and "jemmy.robot_dispatching" system properties
     * to determine what model should be used.
     * Possible values for the both properties: <BR>
     * "off" - switch mode off. <BR>
     * "on" - switch mode on. <BR>
     * "" - use default value.
     * @see #getDefaultDispatchingModel()
     */
    public void initDispatchingModel() {
	boolean qmask = ((getDefaultDispatchingModel() & QUEUE_MODEL_MASK) != 0);
	boolean rmask = ((getDefaultDispatchingModel() & ROBOT_MODEL_MASK) != 0);
	boolean srmask = ((getDefaultDispatchingModel() & SMOOTH_ROBOT_MODEL_MASK) != 0);
	boolean smask = ((getDefaultDispatchingModel() & SHORTCUT_MODEL_MASK) != 0);
	if( System.getProperty("jemmy.queue_dispatching") != null &&
	   !System.getProperty("jemmy.queue_dispatching").equals("")) {
	    qmask = System.getProperty("jemmy.queue_dispatching").equals("on");
	}
	if( System.getProperty("jemmy.robot_dispatching") != null &&
	   !System.getProperty("jemmy.robot_dispatching").equals("")) {
	    rmask = System.getProperty("jemmy.robot_dispatching").equals("on");
	}
	if( System.getProperty("jemmy.smooth_robot_dispatching") != null &&
	   !System.getProperty("jemmy.smooth_robot_dispatching").equals("")) {
	    srmask = System.getProperty("jemmy.smooth_robot_dispatching").equals("on");
	}
	if( System.getProperty("jemmy.shortcut_events") != null &&
	   !System.getProperty("jemmy.shortcut_events").equals("")) {
	    smask = System.getProperty("jemmy.shortcut_events").equals("on");
	}
	initDispatchingModel(qmask, rmask, smask, srmask);
    }

    /**
     * Inits properties and dispatching model from system environment variables.
     * @see #initProperties()
     * @see #initDispatchingModel()
     */
    public void init() {
	initProperties();
	initDispatchingModel();
    }

    /**
     * Returns timeouts.
     * @return the Timeouts value.
     * @see #setTimeouts
     */
    public Timeouts getTimeouts() {
	return((Timeouts)getProperty("timeouts"));
    }

    /**
     * Changes timeouts.
     * @param	to new timeouts.
     * @return  old timeouts.
     * @see #getTimeouts
     */
    public Timeouts setTimeouts(Timeouts to) {
	return((Timeouts)setProperty("timeouts", to));
    }

    /**
     * Changes a timeouts value.
     * @param name Timeout name
     * @param newValue New timeout value
     * @return previous timeout value
     * @see #getTimeout
     */
    public long setTimeout(String name, long newValue) {
	return(getTimeouts().setTimeout(name, newValue));
    }

    /**
     * Returns a timeouts value.
     * @param name Timeout name
     * @return a timeout value
     * @see #setTimeout
     */
    public long getTimeout(String name) {
	return(getTimeouts().getTimeout(name));
    }

    /**
     * Inits a timeouts value.
     * @param name Timeout name
     * @param newValue New timeout value
     * @return a timeout value
     */
    public long initTimeout(String name, long newValue) {
	return(getTimeouts().initTimeout(name, newValue));
    }

    /**
     * Returns output.
     * @return a TestOut object representing the output value
     * @see #setOutput
     */
    public TestOut getOutput() {
	return((TestOut)getProperty("output"));
    }

    /**
     * Changes output.
     * @param	out new output.
     * @return old output.
     * @see #getOutput
     */
    public TestOut setOutput(TestOut out) {
	return((TestOut)setProperty("output", out));
    }

    /**
     * Returns bundle manager.
     * @return a BundleManager object representing the bundle manager value.
     * @see #setBundleManager
     */
    public BundleManager getBundleManager() {
	return((BundleManager)getProperty("resources"));
    }

    /**
     * Changes bundle manager.
     * @param	resources new bundle manager.
     * @return old bundle manager
     * @see #getBundleManager
     */
    public BundleManager setBundleManager(BundleManager resources) {
	return((BundleManager)setProperty("resources", resources));
    }

    /**
     * Returns resource value.
     * @param key Resource key.
     * @return resource value
     */
    public String getResource(String key) {
	return(getBundleManager().getResource(key));
    }

    /**
     * Returns resource value from the specified bundle.
     * @param bundleID Id of a bundle to get resource from.
     * @param key Resource key.
     * @return resource value
     */
    public String getResource(String bundleID, String key) {
	return(getBundleManager().getResource(bundleID, key));
    }

    /**
     * Returns char binding map.
     * @return the char binding map.
     * @see #setCharBindingMap
     */
    public CharBindingMap getCharBindingMap() {
	return((CharBindingMap)getProperty("binding.map"));
    }

    /**
     * Changes char binding map.
     * @param	map new char binding map.
     * @return old char binding map.
     * @see #getCharBindingMap
     */
    public CharBindingMap setCharBindingMap(CharBindingMap map) {
	return((CharBindingMap)setProperty("binding.map", map));
    }

    /**
     * Returns the dispatching model.
     * @return Event dispatching model.
     * @see #getCurrentDispatchingModel()
     * @see #setDispatchingModel(int)
     * @see #QUEUE_MODEL_MASK
     * @see #ROBOT_MODEL_MASK
     */
    public int getDispatchingModel() {
	return(((Integer)getProperty("dispatching.model")).intValue());
    }

    private static DriverInstaller getDriverInstaller(int model) {
	String name = System.getProperty("jemmy.drivers.installer");
	DriverInstaller installer = null;
	try {
	    if(name != null && !(name.length() == 0)) {
		installer = (DriverInstaller)new ClassReference(name).newInstance(null, null);
	    }
	} catch(ClassNotFoundException e) {
	    getCurrentOutput().printLine("Cannot init driver installer:");
	    getCurrentOutput().printStackTrace(e);
        } catch(IllegalAccessException e) {
            getCurrentOutput().printLine("Cannot init driver installer:");
            getCurrentOutput().printStackTrace(e);
        } catch(NoSuchMethodException e) {
            getCurrentOutput().printLine("Cannot init driver installer:");
            getCurrentOutput().printStackTrace(e);
        } catch(InstantiationException e) {
            getCurrentOutput().printLine("Cannot init driver installer:");
            getCurrentOutput().printStackTrace(e);
        } catch(InvocationTargetException e) {
            getCurrentOutput().printLine("Cannot init driver installer:");
            getCurrentOutput().printStackTrace(e);
	}
	if(installer == null) {
	    if(System.getProperty("os.name").startsWith("Mac OS X")) {
		installer = new APIDriverInstaller((model & SHORTCUT_MODEL_MASK) != 0);
	    } else {
		installer = new DefaultDriverInstaller((model & SHORTCUT_MODEL_MASK) != 0);
	    }
	};
	getCurrentOutput().printLine("Using " + installer.getClass().getName() + " driver installer");
	return(installer);
    }

    /**
     * Specifies the dispatching model value.
     * @param model New dispatching model value.
     * @return Previous dispatching model value.
     * @see #setCurrentDispatchingModel(int)
     * @see #getDispatchingModel()
     * @see #QUEUE_MODEL_MASK
     * @see #ROBOT_MODEL_MASK
     */
    public int setDispatchingModel(int model) {
	new InputDriverInstaller((model & ROBOT_MODEL_MASK) == 0, (model & SMOOTH_ROBOT_MODEL_MASK) != 0).install();
	getDriverInstaller(model).install();
	return(((Integer)setProperty("dispatching.model", new Integer(model))).intValue());
    }

    /**
     * Returns the drag and drop step length value.
     * @return Pixel count to move mouse during one drag'n'drop step.
     * @see #getCurrentDragAndDropStepLength()
     * @see #setDragAndDropStepLength(int)
     */
    public int getDragAndDropStepLength() {
	return(((Integer)getProperty("drag_and_drop.step_length")).intValue());
    }

    /**
     * Specifies the drag and drop step length value.
     * @param length Pixel count to move mouse during one drag'n'drop step.
     * @return Previous value.
     * @see #setCurrentDragAndDropStepLength(int)
     * @see #getDragAndDropStepLength()
     */
    public int setDragAndDropStepLength(int length) {
	return(((Integer)setProperty("drag_and_drop.step_length", new Integer(length))).intValue());
    }

    /**
     * Checks if "name" propery currently has a value.
     * @param name Property name. Should by unique.
     * @return true if property was defined.
     * @see #setProperty(String, Object)
     * @see #getProperty(String)
     */
    public boolean contains(String name) {
	return(properties.containsKey(name));
    }

    /**
     * Saves object as a static link to be used by other objects.
     * @param name Property name. Should by unique.
     * @param newValue Property value.
     * @return Previous value of "name" property.
     * @see #setCurrentProperty(String, Object)
     * @see #getProperty(String)
     * @see #contains(String)
     */
    public Object setProperty(String name, Object newValue) {
	Object oldValue = null;
	if(contains(name)) {
	    oldValue = properties.get(name);
	    properties.remove(name);
	}
	properties.put(name, newValue);
	return(oldValue);
    }

    /**
     * Returns the property value.
     * @param name Property name. Should by unique.
     * @return Property value stored by setProperty(String, Object) method.
     * @see #getCurrentProperty(String)
     * @see #setProperty(String, Object)
     * @see #contains(String)
     */
    public Object getProperty(String name) {
	if(contains(name)) {
	    return(properties.get(name));
	} else {
	    return(null);
	}
    }

    /**
     * Removes the property.
     * @param	name A name of the property to be removed.
     * @return previous property value
     */
    public Object removeProperty(String name) {
	if(contains(name)) {
	    return(properties.remove(name));
	} else {
	    return(null);
	}
    }

    /**
     * Returns the key values.
     @return an array of Strings representing the key values.
     */
    public String[] getKeys() {
	Enumeration keys = properties.keys();
	String[] result = new String[properties.size()];
	int i = 0;
	while(keys.hasMoreElements()) {
	    result[i] = (String)keys.nextElement();
            i++;
	}
	return(result);
    }

    /**
     * Copy all properties from this instance into another.
     * @param	properties a JemmyProperties instance to copy properties into.
     */
    public void copyTo(JemmyProperties properties) {
	String[] keys = getKeys();
        for(int i = 0; i < keys.length; i++) {
            properties.setProperty(keys[i], getProperty(keys[i]));
	}
	//some should be cloned
 	properties.setTimeouts(getTimeouts().cloneThis());
 	properties.setBundleManager(getBundleManager().cloneThis());
    }

    /**
     * Creates an exact copy on this instance.
     * @return new JemmyProperties object.
     */
    protected JemmyProperties cloneThis() {
	JemmyProperties result = new JemmyProperties();
        copyTo(result);
	return(result);
    }

    private static String extractValue(InputStream stream, String varName) {
	try {
	    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
	    StringTokenizer token;
	    String nextLine;
	    while((nextLine = reader.readLine()) != null) {
		token = new StringTokenizer(nextLine, ":");
		String nextToken = token.nextToken();
		if(nextToken != null &&
		   nextToken.trim().equals(varName)) {
		    return(token.nextToken().trim());
		}
	    }
	    return("");
	} catch(IOException e) {
	    getCurrentOutput().printStackTrace(e);
	    return("");
	}
    }

}
