All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.editor.Settings Maven / Gradle / Ivy

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.editor;

import java.util.Map;
import java.util.List;
import java.util.Iterator;
import java.util.HashMap;
import java.util.ArrayList;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.text.JTextComponent;

/**
* Configurable settings that editor uses. All the methods are static
* The editor is configurable mainly by using the following static
* method in Settings class:
* 
*   org.netbeans.editor.Settings.setValue(Class kitClass, String settingName, Object newValue);
* 
* kitClass - this is the class of the editor kit for which the setting is changed.
*   The current hierarchy of editor kits starts
*   with the org.netbeans.editor.BaseKit kit, the begining of the whole
*   kit hierarchy. There should be a different editor kit for each mime-type.
* 
*   When the particular setting is not set foar a given kit, then the superclass of
*   the given kit class is retrieved and the search for the setting value is performed.
*   Example: If the java document calls Settings.getValue() to retrieve the value
*   for TAB_SIZE setting and it passes JavaKit.class as the kitClass
*   parameter and the setting has no value on this level, then the super class
*   of the JavaKit is retrieved (by using Class.getSuperclass() call) which is BaseKit
*   in this case and the search for the value of TAB_SIZE setting
*   is performed again. It is finished by reaching the null value for the kitClass.
*   The null value can be also used as the kitClass parameter value.
*   In a more general look not only the kit-class hierarchy could be used
*   in Settings. Any class inheritance hierarchy could be used here
*   having the null as the common root.
*
*   This way the inheritance of the setting values is guaranteed. By changing
*   the setting value on the BaseKit level (or even on the null level),
*   all the kit classes that don't
*   override the particular setting are affected.
* 
* settingName - name of the setting to change. The base setting names
*   are defined as public String constants in SettingsNames class.
*   The additional packages that extend the basic editor functionality
*   can define additional setting names.
* 
* newValue - new value for the setting. It must be always an object even
*   if the setting is logicaly the basic datatype such as int (java.lang.Integer
*   would be used in this case). A particular class types that can be used for
*   the value of the settings are documented for each setting.
*
* WARNING! Please read carefully the description for each option you're
*   going to change as you can make the editor stop working if you'll
*   change the setting in a wrong way.
*
* @author Miloslav Metelka
* @version 1.00
*/

public class Settings {

    /** Core level used by the settings initializers. This is the level used
    * for the base and ext editor packages initializers only.
    */
    public static final int CORE_LEVEL = 0;

    /** System level used by the settings initializers. This is the (default)
    * first level.
    * It should be used by all the modules that install the new kits
    * into the editor.
    */
    public static final int SYSTEM_LEVEL = 1;

    /** Extension level used by the settings initializers. This is the second
    * level. It should be used by all the modules that want to extend
    * or modify the settings but they don't install their own kits.
    * The example can be a module extending the popup menu of an existing
    * kit.
    */
    public static final int EXTENSION_LEVEL = 2;

    /** Option level used by the settings initializers. This is the third
    * level. It should be used by the visual options created by the IDE.
    */
    public static final int OPTION_LEVEL = 3;

    /** User level used by the settings initializers. This is the fourth level.
    * All the initializers with this level will be called AFTER
    * all the initializers at the system level. All the user custom
    * initializers should be added at this level to guarantee
    * they will overwrite the settings added by the system.
    */
    public static final int USER_LEVEL = 4;

    /** List of Initializers */
    private static final ArrayList initializerLists = new ArrayList();

    /** Current initializer sorter. */
    private static InitializerSorter currentInitializerSorter;

    /** List of Filters */
    private static final ArrayList filterList = new ArrayList();

    /** [kit-class, map-of-settings] pairs */
    private static final Map kit2Maps = new HashMap();

    /** Support for firing change events */
    private static final WeakEventListenerList listenerList = new WeakEventListenerList();

    /** Internal map instance signing that initializer returned null
    * map for particular kit. To sign this fact and not query initializer
    * again, this simple map is used.
    */
    private static final Map NULL_MAP = new HashMap(1);

    private static boolean firingEnabled = true;

    /** Save repetitive creation of the empty maps using this variable */
    private static HashMap emptyMap = null;

    private Settings() {
        // no instances allowed
    }

    /** Add the initializer at the system level and perform reset. */
    public static synchronized void addInitializer(Initializer i) {
        addInitializer(i, SYSTEM_LEVEL);
        reset();
    }

    /** Add initializer instance to the list of current initializers.
    * You can call reset() after adding the initializer to make sure
    * it will update the current settings with its values.
    * However all the changes
    * that were made explicitly by calling setValue() will be lost
    * in this case.
    *
    * @param i initializer to add to the current list of initializers
    * @param level initializer level. It defines in which order
    *  the initializers will be called. There are currently three levels
    *  CORE_LEVEL, SYSTEM_LEVEL and USER_LEVEL.
    *  It's guaranteed that initializers with the particular level
    *  will be called in the order shown above.
    *  The order of the initializers at the same
    *  level is given by the order of their addition.
    */
    public static synchronized void addInitializer(Initializer i, int level) {
        int size = initializerLists.size();
        for (int j = size; j <= level; j++) {
            initializerLists.add(new ArrayList());
        }
        ((List)initializerLists.get(level)).add(i);

        // Sort the initializers if there's a valid sorter
        if (currentInitializerSorter != null) {
            currentInitializerSorter.sort(initializerLists);
        }
    }

    /** Remove the initializer of the given name from all the levels
    * where it occurs.
    * @param name name of the initializer sorter to remove.
    */
    public static synchronized void removeInitializer(String name) {
        Iterator itit = initializerLists.iterator();
        while (itit.hasNext()) {
            Iterator it = ((List)itit.next()).iterator();
            while (it.hasNext()) {
                if (name.equals(((Initializer)it.next()).getName())) {
                    it.remove();
                }
            }
        }

        // Sort the initializers if there's a valid sorter
        if (currentInitializerSorter != null) {
            currentInitializerSorter.sort(initializerLists);
        }
    }

    /** Get the current initializer sorter. */
    public static synchronized InitializerSorter getInitializerSorter() {
        return currentInitializerSorter;
    }

    /** Set the current initializer sorter. */
    public static synchronized void setInitializerSorter(InitializerSorter initializerSorter) {
        currentInitializerSorter = initializerSorter;
    }

    /** Add filter instance to the list of current filters.
    * If there are already existing editor components,
    * and you want to apply the changes that this filter makes
    * to these existing
    * components, you can call reset(). However all the changes
    * that were made explicitly by calling setValue() will be lost
    * in this case.
    *
    * @param f filter to add to the list of the filters
    */
    public static synchronized void addFilter(Filter f) {
        filterList.add(f);
    }

    public static synchronized void removeFilter(Filter f) {
        Iterator it = filterList.iterator();
        while (it.hasNext()) {
            if (it.next() == f) {
                it.remove();
            }
        }
    }

    /** Get the value and evaluate the evaluators. */
    public static Object getValue(Class kitClass, String settingName) {
        return getValue(kitClass, settingName, true);
    }

    /** Get the property by searching the given kit class settings and if not
    * found then the settings for super class and so on.
    * @param kitClass editor kit class for which the value of setting should
    *   be retrieved. The null can be used as the root of the whole hierarchy.
    * @param settingName name of the setting for which the value should
    *   be retrieved
    * @return the value of the setting
    */
    public static synchronized Object getValue(Class kitClass, String settingName,
            boolean evaluateEvaluators) {
        Object value = null;
        Class kc = kitClass;
        while (true) {
            Map map = getKitMap(kc, false);
            if (map != null) {
                value = map.get(settingName);
                if (evaluateEvaluators && value instanceof Evaluator) {
                    value = ((Evaluator)value).getValue(kitClass, settingName);
                }
                if (value != null) {
                    break;
                }
            }
            if (kc == null) {
                break;
            }
            kc = kc.getSuperclass();
        }

        // filter the value if necessary
        int cnt = filterList.size();
        for (int i = 0; i < cnt; i++) {
            value = ((Filter)filterList.get(i)).filterValue(kitClass, settingName, value);
        }

        return value;
    }

    /** Get the value hierarchy and evaluate the evaluators */
    public static KitAndValue[] getValueHierarchy(Class kitClass,
            String settingName) {
        return getValueHierarchy(kitClass, settingName, true);
    }

    /** Get array of KitAndValue objects sorted from the given kit class to its
    * deepest superclass and the last member can be filled whether there
    * is global setting (kit class of that member would be null).
    * This method is useful for objects like keymaps that
    * need to create all the parent keymaps to work properly.
    * The method can either evaluate evaluators or leave them untouched
    * which can become handy in some cases.
    * @param kitClass editor kit class for which the value of setting should
    *   be retrieved. The null can be used as the root of the whole hierarchy.
    * @param settingName name of the setting for which the value should
    *   be retrieved
    * @param evaluateEvaluators whether the evaluators should be evaluated or not
    * @return the array containing KitAndValue instances describing the particular
    *   setting's value on the specific kit level.
    */
    public static synchronized KitAndValue[] getValueHierarchy(Class kitClass,
            String settingName, boolean evaluateEvaluators) {
        ArrayList kavList = new ArrayList();
        Class kc = kitClass;
        while (true) {
            Map map = getKitMap(kc, false);
            if (map != null) {
                Object value = map.get(settingName);
                if (evaluateEvaluators && value instanceof Evaluator) {
                    value = ((Evaluator)value).getValue(kitClass, settingName);
                }
                if (value != null) {
                    kavList.add(new KitAndValue(kc, value));
                }
            }
            if (kc == null) {
                break;
            }
            kc = kc.getSuperclass();
        }
        KitAndValue[] kavArray = (KitAndValue[])kavList.toArray(
                                     new KitAndValue[kavList.size()]);

        // filter the value if necessary
        int cnt = filterList.size();
        for (int i = 0; i < cnt; i++) {
            kavArray = ((Filter)filterList.get(i)).filterValueHierarchy(
                           kitClass, settingName, kavArray);
        }

        return kavArray;
    }

    /** Set the new value for property on kit level. The old and new values
    * are compared and if they are equal the setting is not changed and
    * nothing is fired.
    * 
    * @param kitClass editor kit class for which the value of setting should
    *   be set. The null can be used as the root of the whole hierarchy.
    * @param settingName the string used for searching the value
    * @param newValue new value to set for the property; the value can
    *   be null to clear the value for the specified kit
    */
    public static synchronized void setValue(Class kitClass, String settingName,
            Object newValue) {
        Map map = getKitMap(kitClass, true);
        Object oldValue = map.get(settingName);
        if (oldValue == null && newValue == null
                || (oldValue != null && oldValue.equals(newValue))
           ) {
            return; // no change
        }
        if (newValue != null) {
            map.put(settingName, newValue);
        } else {
            map.remove(settingName);
        }
        fireSettingsChange(kitClass, settingName, oldValue, newValue);
    }

    /** Don't change the value of the setting, but fire change
    * event. This is useful when there's internal change in the value object
    * of some setting.
    */
    public static synchronized void touchValue(Class kitClass, String settingName) {
        fireSettingsChange(kitClass, settingName, null, null); // kit class currently not used
    }

    /** Set the value for the current kit and propagate it to all
    * the children of the given kit by removing
    * the possible values for the setting from the children kit setting maps.
    * Note: This call only affects the settings for the kit classes for which
    * the kit setting map with the setting values currently exists, i.e. when
    * there was at least one getValue() or setValue() call performed for any
    * setting on that particular kit class level. Other kit classes maps
    * will be initialized by the particular initializer(s) as soon as
    * the first getValue() or setValue() will be performed for them.
    * However that future process will not be affected by the current
    * propagateValue() call.
    * This method is useful for the visual options that always set
    * the value on all the kit levels without regard whether it's necessary or not.
    * If the value is then changed for the base kit, it's not propagated
    * naturally as there's a special setting
    * This method enables 
    * 
    * The current implementation always fires the change regardless whether
    * there was real change in setting value or not.
    * @param kitClass editor kit class for which the value of setting should
    *   be set.  The null can be used as the root of the whole hierarchy.
    * @param settingName the string used for searching the value
    * @param newValue new value to set for the property; the value can
    *   be null to clear the value for the specified kit
    */
    public static synchronized void propagateValue(Class kitClass,
            String settingName, Object newValue) {
        Map map = getKitMap(kitClass, true);
        if (newValue != null) {
            map.put(settingName, newValue);
        } else {
            map.remove(settingName);
        }
        // resolve kits
        Iterator it = kit2Maps.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry me = (Map.Entry)it.next();
            Class kc = (Class)me.getKey();
            if (kitClass != kc && (kitClass == null || kitClass.isAssignableFrom(kc))) {
                ((Map)me.getValue()).remove(settingName);
            }
        }
        fireSettingsChange(null, settingName, null, null);
    }

    /** Run the given runnable. All the changes in the settings are not fired until
    * the whole runnable completes. Nesting of update() call is allowed.
    * Only one firing is performed after the whole runnable completes
    * using the 'null triple'.
    */
    public static synchronized void update(Runnable r) {
        boolean turnedOff = firingEnabled;
        firingEnabled = false;
        try {
            r.run();
        } finally {
            if (turnedOff) {
                firingEnabled = true;
                fireSettingsChange(null, null, null, null);
            }
        }
    }

    /** Reset all the settings and fire the change of the settings
    * so that all the listeners will be notified and will reload
    * the settings.
    * The settings that were changed using setValue() and propagateValue()
    * are lost. Initializers will be asked for the settings values when
    * necessary.
    */
    public static synchronized void reset() {
        kit2Maps.clear();
        fireSettingsChange(null, null, null, null);
    }

    /** Debug the current initializers */
    public static synchronized String initializersToString() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < initializerLists.size(); i++) {
            // debug the level
            switch (i) {
            case CORE_LEVEL:
                sb.append("CORE_LEVEL"); // NOI18N
                break;

            case SYSTEM_LEVEL:
                sb.append("SYSTEM_LEVEL"); // NOI18N
                break;

            case EXTENSION_LEVEL:
                sb.append("EXTENSION_LEVEL"); // NOI18N
                break;
                
            case OPTION_LEVEL:
                sb.append("OPTION_LEVEL"); // NOI18N
                break;

            case USER_LEVEL:
                sb.append("USER_LEVEL"); // NOI18N
                break;

            default:
                sb.append("level " + i); // NOI18N
                break;
            }
            sb.append(":\n"); // NOI18N

            // debug the initializers
            sb.append(EditorDebug.debugList((List)initializerLists.get(i)));
            sb.append('\n');
        }

        return sb.toString();
    }

    /** Add weak listener to listen to change of any property. The caller must
    * hold the listener object in some instance variable to prevent it
    * from being garbage collected.
    */
    public static void addSettingsChangeListener(SettingsChangeListener l) {
        listenerList.add(SettingsChangeListener.class, l);
    }

    /** Remove listener for changes in properties */
    public static void removeSettingsChangeListener(SettingsChangeListener l) {
        listenerList.remove(SettingsChangeListener.class, l);
    }

    private static void fireSettingsChange(Class kitClass, String settingName,
                                           Object oldValue, Object newValue) {
        if (firingEnabled) {
            SettingsChangeListener[] listeners = (SettingsChangeListener[])
                                                 listenerList.getListeners(SettingsChangeListener.class);
            SettingsChangeEvent evt = new SettingsChangeEvent(Settings.class,
                                      kitClass, settingName, oldValue, newValue);
            for (int i = 0; i < listeners.length; i++) {
                listeners[i].settingsChange(evt);
            }
        }
    }

    /** Get (and possibly create) kit map for particular kit */
    private static Map getKitMap(Class kitClass, boolean forceCreation) {
        Map kitMap = (Map)kit2Maps.get(kitClass);
        if (kitMap == null) {
            if (emptyMap == null) {
                emptyMap = new HashMap();
            }

            // Go through all the initializers
            Iterator itit = initializerLists.iterator();
            while (itit.hasNext()) {
                Iterator it = ((List)itit.next()).iterator();
                while (it.hasNext()) {
                    Initializer i = (Initializer)it.next();

                    // A call to initializer shouldn't break the whole updating
                    try {
                        i.updateSettingsMap(kitClass, emptyMap);
                    } catch (Throwable t) {
                        Utilities.annotateLoggable(t);
                    }
                }
            }

            if (emptyMap.size() > 0) {
                kitMap = emptyMap;
                emptyMap = null;
            }

            if (kitMap == null) { // no initialization done for this kit
                kitMap = NULL_MAP; // initializer will not be called again
            }
            kit2Maps.put(kitClass, kitMap);
        }

        if (kitMap == NULL_MAP) {
            if (!forceCreation) {
                return null;
            } else {
                kitMap = new HashMap(); // create empty map
                kit2Maps.put(kitClass, kitMap);
            }
        }
        return kitMap;
    }


    /** Kit class and value pair */
    public static class KitAndValue {

        public Class kitClass;

        public Object value;

        public KitAndValue(Class kitClass, Object value) {
            this.kitClass = kitClass;
            this.value = value;
        }

    }


    /** Initializer of the settings updates the map filled
    * with settings for the particular kit class when asked.
    * If the settings are being initialized all the initializers registered
    * by the Settings.addInitializer() are being asked to update
    * the settings-map through calling their updateSettingsMap().
    */
    public static interface Initializer {

        /** Each initializer must have a name. The name should be unique.
        * The name is used for identifying the initializer during removal
        * and sort operations and for debuging purposes.
        */
        public String getName();

        /** Update map filled with the settings.
        * @param kitClass kit class for which the settings are being updated.
        *    It can be null which means the root of the whole kit class hierarchy.
        * @param settingsMap map holding [setting-name, setting-value] pairs.
        *   The map can be empty if this is the first initializer
        *   that updates it or if no previous initializers updated it.
        */
        public void updateSettingsMap(Class kitClass, Map settingsMap);

    }

    /** Abstract implementation of the initializer dealing with the name. */
    public static abstract class AbstractInitializer implements Initializer {

        private String name;

        public AbstractInitializer(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public String toString() {
            return getName();
        }

    }

    /** Sort the settings initializers that were added to the settings.
    * There can be only one sorter for the Settings, but it can delegate
    * to previously registered sorter.
    */
    public static interface InitializerSorter {

        public void sort(List initializersList);

    }

    /** Initializer sorter that delegates to another sorter. */
    public static abstract class FilterInitializerSorter {

        private InitializerSorter delegate;

        public FilterInitializerSorter(InitializerSorter delegate) {
            this.delegate = delegate;
        }

        public void sort(List initializersList) {
            if (delegate != null) {
                delegate.sort(initializersList);
            }
        }

    }



    /** Evaluator can be used in cases when value of some setting
    * depends on the value for other setting and it allows to compute
    * the value dynamically based on the other setting(s) value.
    * The Evaluator instance can be used as the value
    * in the Settings.setValue() call. In that case the call
    * to the Settings.getValue() call will 'evaluate' the Evaluator
    * by calling its getValue().
    */
    public static interface Evaluator {

        /** Compute the particular setting's value.
        * @param kitClass kit class for which the setting is being retrieved.
        * @param settingName name of the setting to retrieve. Although the Evaluator
        *   are usually constructed only for the concrete setting, this parameter
        *   allows creation of the Evaluator for multiple settings.
        * @return the value for the requested setting. The substitution
        *   is not attempted again, so the return value cannot be another
        *   Evaluator instance. If the returned value is null, the same
        *   action is taken as if there would no value set on the particular
        *   kit level.
        *   
        */
        public Object getValue(Class kitClass, String settingName);

    }


    /** Filter is applied on every value or KitAndValue pairs returned from getValue().
    * The filter can be registered by calling Settings.addFilter().
    * Each call to Settings.getValue() will first retrieve the value and
    * then call the Filter.filterValue() to get the final value. Each call
    * to Settings.getValueHierarchy() will first retrieve the kit-and-value
    * array and then call the Filter.filterValueHierarchy().
    * If more filters are registered they are all used in the order they were added.
    */
    public static interface Filter {

        /** Filter single value. The value can be substituted here.
        * @param kitClass class of the kit for which the value is retrieved
        * @param settingName name of the retrieved setting
        * @param value value to be optionally filtered
        */
        public Object filterValue(Class kitClass, String settingName, Object value);

        /** Filter array of kit-class and value pairs. The pairs can be completely
        * substituted with an array with different length and different members.
        * @param kitClass class of the kit for which the value is retrieved
        * @param settingName name of the retrieved setting
        * @param kavArray kit-class and value array to be filtered
        */
        public KitAndValue[] filterValueHierarchy(Class kitClass, String settingName,
                KitAndValue[] kavArray);

    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy