/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Pref.java
 *
 * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.database.text;

import com.sun.electric.Main;
import com.sun.electric.database.id.LibId;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.user.ActivityLogger;
import com.sun.electric.util.ClientOS;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.SAXParseException;

/**
 * This class manages appearance options.
 * There are two types of options: <I>appearance</I> and <I>meaning</I>.
 * Appearance options affect the way that the design is presented to the user.
 * Examples are grid dot spacing, layer stipple patterns, etc.
 * Meaning options affect the way that a design is produced (for fabrication,
 * simulation, and other outputs).  Examples are CIF layer names, technology
 * options, etc.)
 * Meaning options are managed by Setting class now.
 * <P>
 * Some Prefs can be used in server Jobs, other are client only.
 * Server Prefs are created by special factory methods during Electric initialization.
 * They can't be created later.
 * <P>
 * All options are saved in a machine-specific way by the Java Preferences class.
 * In addition, "meaning" options are stored in libraries.  When the libraries
 * are read back into Electric, the stored meaning options are checked against
 * the current meaning options, the two are reconciled.
 * <P>
 * Where are these options stored?  It varies with each operating system.
 * <UL>
 * <LI><B>Windows:</B>
 *   In the registry.
 *   Look in: HKEY_CURRENT_USER / Software / JavaSoft / Prefs / com / sun / electric.
 * </LI>
 * <LI><B>UNIX/Linux:</B>
 *   In your home directory.
 *   Look in: ~/.java/.userPrefs/com/sun/electric
 * </LI>
 * <LI><B>Macintosh System 10:</B>
 *   In your home directory, under Library/Preferences.
 *   Look at: ~/Library/Preferences/com.sun.electric.plist
 * </LI>
 * </UL>
 */
public class Pref {

    public final static boolean FROM_THREAD_ENVIRONMENT = false;
    private static boolean forbidPreferences;

    public static class Group {

        private final String relativePath;
        private boolean lockCreation;
        Preferences preferences;
        private final TreeMap<String, Pref> prefs = new TreeMap<String, Pref>();

        private Group(String relativePath) {
            this.relativePath = relativePath;
        }

        public String relativePath() {
            return relativePath;
        }

        public Collection<Pref> getPrefs() {
            return Collections.unmodifiableCollection(prefs.values());
        }

        /**
         * Prefs can be created only at initialization phase.
         * This method forbids further cration of Prefs.
         */
        public void lockCreation() {
            lockCreation = true;
        }

        public void setCachedObjsFromPreferences() {
            for (Pref pref : prefs.values()) {
                pref.setCachedObjFromPreferences();
            }
        }

        private void putValue(String key, Object value) {
            if (preferences == null) {
                preferences = getPrefRoot().node(relativePath);
            }
            putValue(preferences, key, value);
            if (doFlushing) {
                flushOptions(preferences);
            } else {
                queueForFlushing.add(preferences);
            }
        }

        private void putValue(Preferences preferences, String key, Object value) {
            if (value instanceof Boolean) {
                preferences.putBoolean(key, ((Boolean) value).booleanValue());
            } else if (value instanceof Integer) {
                preferences.putInt(key, ((Integer) value).intValue());
            } else if (value instanceof Long) {
                preferences.putLong(key, ((Long) value).longValue());
            } else if (value instanceof Double) {
                preferences.putDouble(key, ((Double) value).doubleValue());
            } else {
                assert value instanceof String;
                preferences.put(key, (String) value);
            }
        }

        private Object getValue(String key, Object def) {
            if (preferences == null) {
                preferences = getPrefRoot().node(relativePath);
            }
            return getValueImpl(preferences, key, def);
        }

        private void remove(String key) {
            if (preferences == null) {
                preferences = getPrefRoot().node(relativePath);
            }
            remove(preferences, key);
            if (doFlushing) {
                flushOptions(preferences);
            } else {
                queueForFlushing.add(preferences);
            }

        }

        private void remove(Preferences preferences, String key) {
            preferences.remove(key);
        }
    }

    public static Group groupForPackage(Class classFromPackage) {

        String className = classFromPackage.getName();
        String mainClassName = "com.sun.electric.";
        if (!className.startsWith(mainClassName)) {
            throw new IllegalArgumentException("Class is not in Electric tree");
        }
        className = className.substring(mainClassName.length());
        int pkgEndIndex = className.lastIndexOf('.');
        String packageName = className.substring(0, pkgEndIndex);
        return groupForPackage(packageName.replace('.', '/'));
    }

    public static Group groupForPackage(String relativePath) {
        synchronized (allGroups) {
            for (Group group : allGroups) {
                if (group.relativePath.equals(relativePath)) {
                    return group;
                }
            }
            Group newGroup = new Group(relativePath);
            allGroups.add(newGroup);
            return newGroup;
        }
    }
    private final String name;
    private final Group group;
    private final boolean serverAccessible;
    private Object cachedObj;
    private Object factoryObj;
    private static final ArrayList<Group> allGroups = new ArrayList<Group>();
    private static boolean doFlushing = true;
    private static Set<Preferences> queueForFlushing;
    private static boolean lockCreation;
    private static final HashSet<Pref> reportedAccess = new HashSet<Pref>();

    /**
     * The constructor for the Pref.
     * @param name the name of this Pref.
     */
    protected Pref(Group group, String name, boolean serverAccessible, Object factoryObj) {
        if (lockCreation && serverAccessible) {
            throw new IllegalStateException("Pref " + group.relativePath() + "/" + name + " is created from improper place");
        }
        this.name = name;
        this.group = group;
        this.serverAccessible = serverAccessible;
        this.factoryObj = factoryObj;
        synchronized (group.prefs) {
        	if (Job.getDebug() && group.prefs.containsKey(name))
        		System.out.println("Repeated Preference '" + name + "'");
            assert !group.prefs.containsKey(name);
            group.prefs.put(name, this);
        }
//        setCachedObjFromPreferences();
    }

    /**
     * Currently Setting can be created only at initialization phase.
     * This method forbids further cration of Settings.
     */
    public static void lockCreation() {
        lockCreation = true;
    }

    public static void forbidPreferences() {
        forbidPreferences = true;
    }

    public static void allowPreferences() {
        forbidPreferences = false;
    }

    public static void setCachedObjsFromPreferences() {
        synchronized (allGroups) {
            for (Group group : allGroups) {
                group.setCachedObjsFromPreferences();
            }
        }
    }

    public static void importPrefs(URL fileURL) {
        // import preferences
        try {
            URLConnection urlCon = fileURL.openConnection();
            InputStream inputStream = urlCon.getInputStream();

            // reset all preferences to factory values
            try {
                clearPrefs(getPrefRoot());
            } catch (BackingStoreException e) {
                System.out.println("Error resetting Electric preferences");
                e.printStackTrace();
            }

            // import preferences
            Preferences.importPreferences(inputStream);
            inputStream.close();

        } catch (InvalidPreferencesFormatException e) {
            String message = "Invalid preferences format";
            if (e.getCause() instanceof SAXParseException) {
                SAXParseException se = (SAXParseException) e.getCause();
                message += " (line " + se.getLineNumber() + ")";
            }
            System.out.println(message + ": " + e.getMessage());
            return;
        } catch (IOException e) {
            System.out.println("Error reading preferences file");
            e.printStackTrace();
            return;
        }
    }

    private static void clearPrefs(Preferences topNode) throws BackingStoreException {
        topNode.clear();
        for (String child : topNode.childrenNames()) {
            clearPrefs(topNode.node(child));
        }
    }

    /**
     * Method to export the preferences to an XML file. This function is public due to the regressions.
     * @param fileName the file to write.
     */
    public static void exportPrefs(String fileName) {
        exportPrefs(fileName, getPrefRoot());
    }

    /**
     * Method to export the preferences to an XML file. This function is public due to the regressions.
     * @param fileName the file to write.
     */
    public static void exportPrefs(String fileName, Preferences prefRoot) {
        if (fileName == null) {
            return;
        }

        // save preferences there
        try {
            // dump the preferences as a giant XML string (unformatted)
            ByteArrayOutputStream bs = new ByteArrayOutputStream();
            prefRoot.exportSubtree(bs);
            String xmlDump = bs.toString();

            // remove the DTD statement (causes trouble)
            int sunPos = xmlDump.indexOf("java.sun.com");
            String insertDTD = "";
            if (sunPos >= 0) {
                int openPos = xmlDump.lastIndexOf('<', sunPos);
                int closePos = xmlDump.indexOf('>', sunPos);
                if (openPos >= 0 && closePos >= 0) {
                    insertDTD = xmlDump.substring(openPos, closePos + 1);
                    xmlDump = xmlDump.substring(0, openPos) + xmlDump.substring(closePos + 1);
                }
            }

            // reformat the XML
            StreamSource source = new StreamSource(new StringReader(xmlDump));
            TransformerFactory factory = TransformerFactory.newInstance();
            factory.setAttribute("indent-number", new Integer(2));
            Transformer transformer = factory.newTransformer();
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            OutputStreamWriter outSW = (ClientOS.isOSMac()) ? new OutputStreamWriter(bos) : new OutputStreamWriter(bos, "utf-8");
//            OutputStreamWriter outSW = new OutputStreamWriter(bos, "UTF-8");
            StreamResult result = new StreamResult(outSW);
            transformer.transform(source, result);

            // add the removed DTD line back into the XML
            String xmlFormatted = bos.toString();
            int closePos = xmlFormatted.indexOf('>');
            if (closePos >= 0) {
                xmlFormatted = xmlFormatted.substring(0, closePos + 1) + "\n" + insertDTD + xmlFormatted.substring(closePos + 1);
            }

            // save the XML to disk
            PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
            printWriter.print(xmlFormatted);
            printWriter.close();
        } catch (Exception e) {
            if (Job.getDebug()) {
                e.printStackTrace();
            }
            System.out.println("Error exporting Preferences");
            return;
        }

        System.out.println("Preferences saved to " + fileName);
    }

    /**
     * Factory methods to create a boolean Pref objects.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeBooleanPref(String name, Group group, boolean factory) {
        return new Pref(group, name, false, Boolean.valueOf(factory));
    }

    /**
     * Factory methods to create an integer Pref objects.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeIntPref(String name, Group group, int factory) {
        return new Pref(group, name, false, Integer.valueOf(factory));
    }

    /**
     * Factory methods to create a long Pref objects.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeLongPref(String name, Group group, long factory) {
        return new Pref(group, name, false, Long.valueOf(factory));
    }

    /**
     * Factory methods to create a double Pref objects.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeDoublePref(String name, Group group, double factory) {
        return new Pref(group, name, false, Double.valueOf(factory));
    }

    /**
     * Factory methods to create a string Pref objects.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeStringPref(String name, Group group, String factory) {
        if (factory == null) {
            throw new NullPointerException();
        }
        return new Pref(group, name, false, factory);
    }

    /**
     * Factory methods to create a boolean Pref objects.
     * The Pref is accessible from server Jobs.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeBooleanServerPref(String name, Group group, boolean factory) {
        return new Pref(group, name, true, Boolean.valueOf(factory));
    }

    /**
     * Factory methods to create an integer Pref objects.
     * The Pref is accessible from server Jobs.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeIntServerPref(String name, Group group, int factory) {
        return new Pref(group, name, true, Integer.valueOf(factory));
    }

    /**
     * Factory methods to create a string Pref objects.
     * The Pref is accessible from server Jobs.
     * @param name the name of this Pref.
     * @param group group of preferences to which a new Pref belongs
     * @param factory the "factory" default value (if nothing is stored).
     */
    public static Pref makeStringServerPref(String name, Group group, String factory) {
        if (factory == null) {
            throw new NullPointerException();
        }
        return new Pref(group, name, true, factory);
    }

    /**
     * Method to get the boolean value on this Pref object.
     * The object must have been created as "boolean".
     * @return the boolean value on this Pref object.
     */
    public boolean getBoolean() {
        return ((Boolean) getValue()).booleanValue();
    }

    /**
     * Method to get the integer value on this Pref object.
     * The object must have been created as "integer".
     * @return the integer value on this Pref object.
     */
    public int getInt() {
        return ((Integer) getValue()).intValue();
    }

    /**
     * Method to get the long value on this Pref object.
     * The object must have been created as "long".
     * @return the long value on this Pref object.
     */
    public long getLong() {
        return ((Long) getValue()).longValue();
    }

    /**
     * Method to get the double value on this Pref object.
     * The object must have been created as "double".
     * @return the double value on this Pref object.
     */
    public double getDouble() {
        return ((Number) getValue()).doubleValue();
    }

    /**
     * Method to get the string value on this Pref object.
     * The object must have been created as "string".
     * @return the string value on this Pref object.
     */
    public String getString() {
        return (String) getValue();
    }

    /**
     * Method to get the boolean value on this Pref object.
     * The object must have been created as "boolean".
     * @param prefRoot root Preferences node
     * @return the boolean value on this Pref object.
     */
    public boolean getBoolean(Preferences prefRoot) {
        return ((Boolean) getValue(prefRoot)).booleanValue();
    }

    /**
     * Method to get the integer value on this Pref object.
     * The object must have been created as "integer".
     * @param prefRoot root Preferences node
     * @return the integer value on this Pref object.
     */
    public int getInt(Preferences prefRoot) {
        return ((Integer) getValue(prefRoot)).intValue();
    }

    /**
     * Method to get the long value on this Pref object.
     * The object must have been created as "long".
     * @param prefRoot root Preferences node
     * @return the long value on this Pref object.
     */
    public long getLong(Preferences prefRoot) {
        return ((Long) getValue(prefRoot)).longValue();
    }

    /**
     * Method to get the double value on this Pref object.
     * The object must have been created as "double".
     * @param prefRoot root Preferences node
     * @return the double value on this Pref object.
     */
    public double getDouble(Preferences prefRoot) {
        return ((Double) getValue(prefRoot)).doubleValue();
    }

    /**
     * Method to get the string value on this Pref object.
     * The object must have been created as "string".
     * @param prefRoot root Preferences node
     * @return the string value on this Pref object.
     */
    public String getString(Preferences prefRoot) {
        return (String) getValue(prefRoot);
    }

    /**
     * Method to get the factory-default value of this Pref object.
     * @return the factory-default value of this Pref object.
     */
    public Object getFactoryValue() {
        return factoryObj;
    }

    /**
     * Method to get the factory-default boolean value of this Pref object.
     * @return the factory-default boolean value of this Pref object.
     */
    public boolean getBooleanFactoryValue() {
        return ((Boolean) factoryObj).booleanValue();
    }

    /**
     * Method to get the factory-default integer value of this Pref object.
     * @return the factory-default integer value of this Pref object.
     */
    public int getIntFactoryValue() {
        return ((Integer) factoryObj).intValue();
    }

    /**
     * Method to get the factory-default long value of this Pref object.
     * @return the factory-default long value of this Pref object.
     */
    public long getLongFactoryValue() {
        return ((Long) factoryObj).longValue();
    }

    /**
     * Method to get the factory-default double value of this Pref object.
     * @return the factory-default double value of this Pref object.
     */
    public double getDoubleFactoryValue() {
        return ((Double) factoryObj).doubleValue();
    }

    /**
     * Method to get the factory-default String value of this Pref object.
     * @return the factory-default String value of this Pref object.
     */
    public String getStringFactoryValue() {
        return (String) factoryObj;
    }

    /**
     * Method to get the name of this Pref object.
     * @return the name of this Pref object.
     */
    public String getPrefName() {
        return name;
    }

    /**
     * Method to get the pref name of this Pref object.
     * @return the name of this Setting object.
     */
    public String getPrefPath() {
        return group.relativePath() + "/" + name;
    }

    /**
     * Method to get the value of this Pref object as an Object.
     * The proper way to get the current value is to use one of the type-specific
     * methods such as getInt(), getBoolean(), etc.
     * @return the Object value of this Pref object.
     */
    public Object getValue() {
        if (!Job.isClientThread()) {
            if (!serverAccessible && !reportedAccess.contains(this)) {
                String msg = getPrefName() + " is accessed from " + Job.getRunningJob();
                if (Job.getDebug()) {
                    ActivityLogger.logMessage(msg);
                    System.out.println(msg);
                }
                reportedAccess.add(this);
            }
        }
        if (cachedObj == null) {
            setCachedObjFromPreferences();
        }
        return cachedObj;
    }

    public Object getValue(Preferences prefRoot) {
        return getValueImpl(prefRoot.node(group.relativePath), name, factoryObj);
    }

    private static Object getValueImpl(Preferences preferences, String key, Object def) {
        Object value = def;
        if (def instanceof Boolean) {
            boolean defV = ((Boolean) def).booleanValue();
            boolean v = preferences.getBoolean(key, defV);
            if (v != defV) {
                value = Boolean.valueOf(v);
            }
        } else if (def instanceof Integer) {
            int defV = ((Integer) def).intValue();
            int v = preferences.getInt(key, defV);
            if (v != defV) {
                value = Integer.valueOf(v);
            }
        } else if (def instanceof Long) {
            long defV = ((Long) def).longValue();
            long v = preferences.getLong(key, defV);
            if (v != defV) {
                value = Long.valueOf(v);
            }
        } else if (def instanceof Double) {
            double defV = ((Double) def).doubleValue();
            double v = preferences.getDouble(key, defV);
            if (v != defV) {
                value = Double.valueOf(v);
            }
        } else {
            assert def instanceof String;
            value = preferences.get(key, (String) def);
        }
        return value;
    }

    private void setValue(Object value) {
        assert value.getClass() == factoryObj.getClass();
        cachedObj = value.equals(factoryObj) ? factoryObj : value;
        group.putValue(name, cachedObj);
    }

    private void setCachedObjFromPreferences() {
        cachedObj = group.getValue(name, factoryObj);
    }

    /**
     * Method to delay the saving of preferences to disk.
     * Since individual saving is time-consuming, batches of preference
     * changes are wrapped with this, and "resumePrefFlushing()".
     */
    public static void delayPrefFlushing() {
        doFlushing = false;
        queueForFlushing = new HashSet<Preferences>();
    }

    /**
     * Method to resume the saving of preferences to disk.
     * Since individual saving is time-consuming, batches of preference
     * changes are wrapped with this, and "resumePrefFlushing()".
     * Besides resuming saving, this method also saves anything queued
     * while saving was delayed.
     */
    public static void resumePrefFlushing() {
        doFlushing = true;
        for (Preferences p : queueForFlushing) {
            flushOptions(p);
        }
        flushAll();
    }

    /**
     * Returns the root of Preferences subtree with Electric options.
     * Currently this is "/com/sun/electric/" subtree.
     * @return the root of Preferences subtree with Electric options.
     */
    public static Preferences getPrefRoot() {
        if (forbidPreferences) {
            return getFactoryPrefRoot();
        }
        return Preferences.userNodeForPackage(Main.class);
    }

    /**
     * Returns the root of Preferences subtree with Electric options for a specified LibId.
     * @param libId specified LibId
     * @return the root of Preferences subtree with Electric options for a specified LibId.
     */
    public static Preferences getLibraryPreferences(LibId libId) {
        return Pref.getPrefRoot().node("database/hierarchy/" + libId.libName);
    }

    /**
     * Returns the root of dummy Preferences subtree with factory default Electric options.
     * @return the root of Preferences subtree with factory default Electric options.
     */
    public static Preferences getFactoryPrefRoot() {
        return EmptyPreferencesFactory.factoryPrefRoot;
    }

    /**
     * Method to immediately flush all Electric preferences to disk.
     */
    public static void flushAll() {
        flushOptions(getPrefRoot());
    }

    /**
     * Method to set a new boolean value on this Pref object.
     * @param v the new boolean value of this Pref object.
     */
    public void setBoolean(boolean v) {
        checkModify();
        boolean cachedBool = getBoolean();
        if (v != cachedBool) {
            setValue(Boolean.valueOf(v));
        }
    }

    /**
     * Method to set a new integer value on this Pref object.
     * @param v the new integer value of this Pref object.
     */
    public void setInt(int v) {
        checkModify();
        int cachedInt = getInt();
        if (v != cachedInt) {
            setValue(Integer.valueOf(v));
        }
    }

    /**
     * Method to set a new long value on this Pref object.
     * @param v the new long value of this Pref object.
     */
    public void setLong(long v) {
        checkModify();
        long cachedLong = getLong();
        if (v != cachedLong) {
            setValue(Long.valueOf(v));
        }
    }

    /**
     * Method to set a new double value on this Pref object.
     * @param v the new double value of this Pref object.
     */
    public void setDouble(double v) {
        checkModify();
        double cachedDouble = getDouble();
        if (v != cachedDouble) {
            setValue(Double.valueOf(v));
        }
    }

    /**
     * Method to set a new string value on this Pref object.
     * @param str the new string value of this Pref object.
     */
    public void setString(String str) {
        checkModify();
        String cachedString = getString();
        if (!str.equals(cachedString)) {
            setValue(str);
        }
    }

    /**
     * Method to set a new boolean value on this Pref object.
     * @param v the new boolean value of this Pref object.
     */
    public void putBoolean(Preferences prefRoot, boolean removeDefaults, boolean v) {
        Preferences prefs = prefRoot.node(group.relativePath);
        if (removeDefaults && v == ((Boolean) factoryObj).booleanValue()) {
            prefs.remove(name);
        } else {
            prefs.putBoolean(name, v);
        }
    }

    /**
     * Method to set a new integer value on this Pref object.
     * @param v the new integer value of this Pref object.
     */
    public void putInt(Preferences prefRoot, boolean removeDefaults, int v) {
        Preferences prefs = prefRoot.node(group.relativePath);
        if (removeDefaults && v == ((Integer) factoryObj).intValue()) {
            prefs.remove(name);
        } else {
            prefs.putInt(name, v);
        }
    }

    /**
     * Method to set a new long value on this Pref object.
     * @param v the new long value of this Pref object.
     */
    public void putLong(Preferences prefRoot, boolean removeDefaults, long v) {
        Preferences prefs = prefRoot.node(group.relativePath);
        if (removeDefaults && v == ((Long) factoryObj).longValue()) {
            prefs.remove(name);
        } else {
            prefs.putLong(name, v);
        }
    }

    /**
     * Method to set a new double value on this Pref object.
     * @param v the new double value of this Pref object.
     */
    public void putDouble(Preferences prefRoot, boolean removeDefaults, double v) {
        Preferences prefs = prefRoot.node(group.relativePath);
        if (removeDefaults && v == ((Double) factoryObj).doubleValue()) {
            prefs.remove(name);
        } else {
            prefs.putDouble(name, v);
        }
    }

    /**
     * Method to set a new string value on this Pref object.
     * @param str the new string value of this Pref object.
     */
    public void putString(Preferences prefRoot, boolean removeDefaults, String str) {
        Preferences prefs = prefRoot.node(group.relativePath);
        if (removeDefaults && str.equals(factoryObj)) {
            prefs.remove(name);
        } else {
            prefs.put(name, str);
        }
    }

    /**
     * Method to reset Pref value to factory default.
     */
    public void factoryReset() {
        if (!Job.isClientThread()) {
            throw new IllegalStateException();
        }
        cachedObj = factoryObj;
        group.remove(name);
    }

    private void checkModify() {
        if (!Job.isClientThread()) {
            if (!serverAccessible) {
                String msg = getPrefName() + " is modified in " + Job.getRunningJob();
                if (Job.getDebug()) {
                    ActivityLogger.logMessage(msg);
                    System.out.println(msg);
                }
            }
        }
    }

    public static Collection<Group> getAllGroups() {
        return Collections.unmodifiableCollection(allGroups);
    }

//    private static int numStrings;
//    private static int lenStrings;
//    private static int numValueStrings = 0;
//    private static int lenValueStrings = 0;
//
//    public static void printAllPrefs(PrintStream out, Environment environment, boolean all) {
//        numValueStrings = lenValueStrings = 0;
//        TreeMap<String,Pref> sortedPrefs = new TreeMap<String,Pref>();
//        synchronized (allGroups) {
//            for (Group group: allGroups) {
//                for (Pref pref: group.prefs.values()) {
//                    if (!all && !pref.serverAccessible) continue;
//                    sortedPrefs.put(pref.group.relativePath() + "/" + pref.name, pref);
//                }
//            }
//        }
//        Preferences rootNode = getPrefRoot();
//        numStrings = lenStrings = 0;
//        try {
//            gatherPrefs(out, 0, rootNode, null);
//        } catch (BackingStoreException e) {
//            e.printStackTrace();
//        }
//        out.println(lenStrings + " chars in " + numStrings + " strings");
//        out.println(lenValueStrings + " chars in " + numValueStrings + " value strings");
//        printAllSettings(out, environment);
//        out.println("ELECTRIC USER PREFERENCES");
//        int  i = 0;
//        for (Pref pref: sortedPrefs.values())
//            out.println((i++) + pref.group.relativePath() + " " + pref.name + " " + pref.cachedObj);
//        for (Technology tech: environment.techPool.values()) {
//            i = 0;
//            for (Pref.Group group: tech.getTechnologyAllPreferences()) {
//                for (Pref pref: group.prefs.values()) {
//                    if (!all && !pref.serverAccessible) continue;
//                    out.println((i++) + pref.group.relativePath() + " " + tech + " " + pref.name + " " + pref.cachedObj);
//                }
//            }
//        }
//    }
//
//    private static void gatherPrefs(PrintStream out, int level, Preferences topNode, List<String> ks) throws BackingStoreException {
//        for (int i = 0; i < level; i++) out.print("  ");
//        String[] keys = topNode.keys();
//        for (int i = 0; i < keys.length; i++) {
//            numStrings++;
//            lenStrings += keys.length;
//            String value = topNode.get(keys[i], null);
//            numValueStrings++;
//            lenValueStrings += value.length();
//        }
//        out.println(topNode.name() + " " + keys.length);
//        if (topNode.absolutePath().equals("/com/sun/electric/database/hierarchy")) return;
//        String[] children = topNode.childrenNames();
//        for (int i = 0; i < children.length; i++) {
//            String childName = children[i];
//            numStrings++;
//            lenStrings += children[i].length();
//            Preferences childNode = topNode.node(childName);
//            gatherPrefs(out, level + 1, childNode, ks);
//        }
//    }
//
//    private static void printAllSettings(PrintStream out, Environment environment) {
//        Map<Setting,Object> settings = environment.getSettings();
//        TreeMap<String,Setting> sortedSettings = new TreeMap<String,Setting>();
//        for (Setting setting: settings.keySet())
//            sortedSettings.put(setting.getXmlPath(), setting);
//        out.println("PROJECT PREFERENCES");
//        int i = 0;
//        for (Setting setting: sortedSettings.values())
//            out.println((i++) + "\t" + setting.getXmlPath() + " " + settings.get(setting));
//    }
    /****************************** private methods ******************************/
    /**
     * Method to force all Preferences to be saved.
     */
    private static void flushOptions(Preferences p) {
        try {
            p.flush();
        } catch (Exception e) {
            System.out.println("Failed to save preferences (are there two copies of Electric running?)");
            if (ClientOS.isOSLinux())
            	System.out.println("   You may need to delete lock files in ~/.java/.userPrefs");
        }
    }
}
