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