0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor;
0043:
0044: import java.awt.Color;
0045: import java.awt.Dimension;
0046: import java.awt.Insets;
0047: import java.lang.reflect.Method;
0048: import java.util.Map;
0049: import java.util.List;
0050: import java.util.Iterator;
0051: import java.util.HashMap;
0052: import java.util.ArrayList;
0053: import java.util.Arrays;
0054: import java.util.Collection;
0055: import java.util.Collections;
0056: import java.util.HashSet;
0057: import java.util.Set;
0058: import java.util.StringTokenizer;
0059: import java.util.WeakHashMap;
0060: import java.util.logging.Level;
0061: import java.util.logging.Logger;
0062: import java.util.prefs.PreferenceChangeEvent;
0063: import java.util.prefs.PreferenceChangeListener;
0064: import java.util.prefs.Preferences;
0065: import javax.swing.KeyStroke;
0066: import javax.swing.text.AttributeSet;
0067: import javax.swing.text.StyleConstants;
0068: import org.netbeans.api.editor.mimelookup.MimeLookup;
0069: import org.netbeans.api.editor.mimelookup.MimePath;
0070: import org.netbeans.api.editor.settings.CodeTemplateDescription;
0071: import org.netbeans.api.editor.settings.CodeTemplateSettings;
0072: import org.netbeans.api.editor.settings.FontColorSettings;
0073: import org.netbeans.api.editor.settings.KeyBindingSettings;
0074: import org.openide.util.Lookup;
0075: import org.openide.util.LookupEvent;
0076: import org.openide.util.LookupListener;
0077: import org.openide.util.RequestProcessor;
0078: import org.openide.util.WeakListeners;
0079:
0080: /**
0081: * Configurable settings that editor uses. All the methods are static
0082: * The editor is configurable mainly by using the following static
0083: * method in Settings class:
0084: *
0085: * org.netbeans.editor.Settings.setValue(Class kitClass, String settingName, Object newValue);
0086: *
0087: * kitClass - this is the class of the editor kit for which the setting is changed.
0088: * The current hierarchy of editor kits starts
0089: * with the <tt>org.netbeans.editor.BaseKit</tt> kit, the begining of the whole
0090: * kit hierarchy. There should be a different editor kit for each mime-type.
0091: *
0092: * When the particular setting is not set foar a given kit, then the superclass of
0093: * the given kit class is retrieved and the search for the setting value is performed.
0094: * Example: If the java document calls Settings.getValue() to retrieve the value
0095: * for TAB_SIZE setting and it passes JavaKit.class as the kitClass
0096: * parameter and the setting has no value on this level, then the super class
0097: * of the JavaKit is retrieved (by using Class.getSuperclass() call) which is BaseKit
0098: * in this case and the search for the value of TAB_SIZE setting
0099: * is performed again. It is finished by reaching the null value for the kitClass.
0100: * The null value can be also used as the kitClass parameter value.
0101: * In a more general look not only the kit-class hierarchy could be used
0102: * in <tt>Settings</tt>. Any class inheritance hierarchy could be used here
0103: * having the null as the common root.
0104: *
0105: * This way the inheritance of the setting values is guaranteed. By changing
0106: * the setting value on the BaseKit level (or even on the null level),
0107: * all the kit classes that don't
0108: * override the particular setting are affected.
0109: *
0110: * settingName - name of the setting to change. The base setting names
0111: * are defined as public String constants in <tt>SettingsNames</tt> class.
0112: * The additional packages that extend the basic editor functionality
0113: * can define additional setting names.
0114: *
0115: * newValue - new value for the setting. It must be always an object even
0116: * if the setting is logicaly the basic datatype such as int (java.lang.Integer
0117: * would be used in this case). A particular class types that can be used for
0118: * the value of the settings are documented for each setting.
0119: *
0120: * WARNING! Please read carefully the description for each option you're
0121: * going to change as you can make the editor stop working if you'll
0122: * change the setting in a wrong way.
0123: *
0124: * @author Miloslav Metelka
0125: * @version 1.00
0126: */
0127:
0128: public class Settings {
0129:
0130: private static final Logger LOG = Logger.getLogger(Settings.class
0131: .getName());
0132: private static final boolean LOG_STACTRACES = Boolean
0133: .getBoolean(Settings.class.getName() + ".stacktraces"); //NOI18N
0134:
0135: /** Core level used by the settings initializers. This is the level used
0136: * for the base and ext editor packages initializers only.
0137: */
0138: public static final int CORE_LEVEL = 0;
0139:
0140: /** System level used by the settings initializers. This is the (default)
0141: * first level.
0142: * It should be used by all the modules that install the new kits
0143: * into the editor.
0144: */
0145: public static final int SYSTEM_LEVEL = 1;
0146:
0147: /** Extension level used by the settings initializers. This is the second
0148: * level. It should be used by all the modules that want to extend
0149: * or modify the settings but they don't install their own kits.
0150: * The example can be a module extending the popup menu of an existing
0151: * kit.
0152: */
0153: public static final int EXTENSION_LEVEL = 2;
0154:
0155: /** Option level used by the settings initializers. This is the third
0156: * level. It should be used by the visual options created by the IDE.
0157: */
0158: public static final int OPTION_LEVEL = 3;
0159:
0160: /** User level used by the settings initializers. This is the fourth level.
0161: * All the initializers with this level will be called AFTER
0162: * all the initializers at the system level. All the user custom
0163: * initializers should be added at this level to guarantee
0164: * they will overwrite the settings added by the system.
0165: */
0166: public static final int USER_LEVEL = 4;
0167:
0168: /** List of Initializers */
0169: private static final ArrayList initializerLists = new ArrayList();
0170: private static long initializerListsVersion = 0;
0171: private static List[] listsOfInitializers = null;
0172: private static long listsOfInitializersVersion = -1;
0173:
0174: /** Current initializer sorter. */
0175: private static InitializerSorter currentInitializerSorter;
0176:
0177: /** List of Filters */
0178: private static final Filter[] NULL_FILTERS = new Filter[0];
0179: private static final String FILTERS_LOCK = new String(
0180: "Settings.FILTERS_LOCK"); //NOI18N
0181: private static volatile Filter[] filters = NULL_FILTERS;
0182:
0183: /** [kit-class, map-of-settings] pairs */
0184: private static final Map kit2Maps = new HashMap();
0185:
0186: /** Support for firing change events */
0187: private static final WeakEventListenerList listenerList = new WeakEventListenerList();
0188:
0189: private static volatile int firingEnabled = 0;
0190:
0191: /** Save repetitive creation of the empty maps using this variable.
0192: * [kit-class, map-of-settings] pairs
0193: */
0194: private static final HashMap emptyMaps = new HashMap();
0195:
0196: private static final RequestProcessor PROCESSOR = new RequestProcessor(
0197: "org.netbeans.editor.Settings.PROCESSOR"); //NOI18N
0198: private static final RequestProcessor.Task RESET_TASK = PROCESSOR
0199: .create(new Runnable() {
0200: public void run() {
0201: synchronized (Settings.class) {
0202: kit2Maps.clear();
0203: }
0204: fireSettingsChange(null, null, null, null);
0205: }
0206: });
0207:
0208: private Settings() {
0209: // no instances allowed
0210: }
0211:
0212: /** Add the initializer at the system level and perform reset. */
0213: public static void addInitializer(Initializer i) {
0214: addInitializer(i, SYSTEM_LEVEL);
0215: reset();
0216: }
0217:
0218: /** Add initializer instance to the list of current initializers.
0219: * You can call reset() after adding the initializer to make sure
0220: * it will update the current settings with its values.
0221: * However all the changes
0222: * that were made explicitly by calling setValue() will be lost
0223: * in this case.
0224: *
0225: * @param i initializer to add to the current list of initializers
0226: * @param level initializer level. It defines in which order
0227: * the initializers will be called. There are currently three levels
0228: * <tt>CORE_LEVEL</tt>, <tt>SYSTEM_LEVEL</tt> and <tt>USER_LEVEL</tt>.
0229: * It's guaranteed that initializers with the particular level
0230: * will be called in the order shown above.
0231: * The order of the initializers at the same
0232: * level is given by the order of their addition.
0233: */
0234: public static void addInitializer(Initializer i, int level) {
0235: synchronized (initializerLists) {
0236: int size = initializerLists.size();
0237: for (int j = size; j <= level; j++) {
0238: initializerLists.add(new ArrayList());
0239: }
0240: ((List) initializerLists.get(level)).add(i);
0241:
0242: // Sort the initializers if there's a valid sorter
0243: if (currentInitializerSorter != null) {
0244: currentInitializerSorter.sort(initializerLists);
0245: }
0246:
0247: initializerListsVersion++;
0248: }
0249: }
0250:
0251: /** Remove the initializer of the given name from all the levels
0252: * where it occurs.
0253: * @param name name of the initializer sorter to remove.
0254: */
0255: public static void removeInitializer(String name) {
0256: synchronized (initializerLists) {
0257: Iterator itit = initializerLists.iterator();
0258: while (itit.hasNext()) {
0259: Iterator it = ((List) itit.next()).iterator();
0260: while (it.hasNext()) {
0261: if (name
0262: .equals(((Initializer) it.next()).getName())) {
0263: it.remove();
0264: }
0265: }
0266: }
0267:
0268: // Sort the initializers if there's a valid sorter
0269: if (currentInitializerSorter != null) {
0270: currentInitializerSorter.sort(initializerLists);
0271: }
0272:
0273: initializerListsVersion++;
0274: }
0275: }
0276:
0277: /** Get the current initializer sorter. */
0278: public static InitializerSorter getInitializerSorter() {
0279: synchronized (initializerLists) {
0280: return currentInitializerSorter;
0281: }
0282: }
0283:
0284: /** Set the current initializer sorter. */
0285: public static void setInitializerSorter(
0286: InitializerSorter initializerSorter) {
0287: synchronized (initializerLists) {
0288: currentInitializerSorter = initializerSorter;
0289: }
0290: }
0291:
0292: private static List[] getListsOfInitializers() {
0293: synchronized (initializerLists) {
0294: if (listsOfInitializersVersion != initializerListsVersion) {
0295: List[] lists = (List[]) initializerLists
0296: .toArray(new List[initializerLists.size()]);
0297:
0298: // copy & immutize
0299: for (int i = 0; i < lists.length; i++) {
0300: lists[i] = Collections
0301: .unmodifiableList(new ArrayList(lists[i]));
0302: }
0303:
0304: listsOfInitializers = lists;
0305: listsOfInitializersVersion = initializerListsVersion;
0306: }
0307:
0308: return listsOfInitializers;
0309: }
0310: }
0311:
0312: /** Add filter instance to the list of current filters.
0313: * If there are already existing editor components,
0314: * and you want to apply the changes that this filter makes
0315: * to these existing
0316: * components, you can call reset(). However all the changes
0317: * that were made explicitly by calling setValue() will be lost
0318: * in this case.
0319: *
0320: * @param f filter to add to the list of the filters
0321: */
0322: public static void addFilter(Filter f) {
0323: synchronized (FILTERS_LOCK) {
0324: if (filters.length == 0) {
0325: filters = new Filter[] { f };
0326: } else {
0327: Filter[] tmp = new Filter[filters.length + 1];
0328: System.arraycopy(filters, 0, tmp, 0, filters.length);
0329: tmp[filters.length] = f;
0330:
0331: filters = tmp;
0332: }
0333: }
0334: }
0335:
0336: public static void removeFilter(Filter f) {
0337: synchronized (FILTERS_LOCK) {
0338: if (filters.length == 0) {
0339: return;
0340: } else if (filters.length == 1 && filters[0] == f) {
0341: filters = NULL_FILTERS;
0342: } else {
0343: int idx = -1;
0344: for (int i = 0; i < filters.length; i++) {
0345: if (filters[i] == f) {
0346: idx = i;
0347: break;
0348: }
0349: }
0350:
0351: if (idx != -1) {
0352: Filter[] tmp = new Filter[filters.length - 1];
0353: System.arraycopy(filters, 0, tmp, 0, idx);
0354: if (idx < tmp.length) {
0355: System.arraycopy(filters, idx + 1, tmp, idx,
0356: tmp.length - idx);
0357: }
0358:
0359: filters = tmp;
0360: }
0361: }
0362: }
0363: }
0364:
0365: /** Get the value and evaluate the evaluators. */
0366: public static Object getValue(Class kitClass, String settingName) {
0367: return getValue(kitClass, settingName, true);
0368: }
0369:
0370: /** Get the property by searching the given kit class settings and if not
0371: * found then the settings for super class and so on.
0372: * @param kitClass editor kit class for which the value of setting should
0373: * be retrieved. The null can be used as the root of the whole hierarchy.
0374: * @param settingName name of the setting for which the value should
0375: * be retrieved
0376: * @return the value of the setting
0377: */
0378: public static Object getValue(Class kitClass, String settingName,
0379: boolean evaluateEvaluators) {
0380: String mimeType = BaseKit.kitsTracker_FindMimeType(kitClass);
0381: MimePath mimePath = mimeType == null ? MimePath.EMPTY
0382: : MimePath.parse(mimeType);
0383:
0384: // Get the value
0385: Object value = getValueEx(mimePath, kitClass, settingName,
0386: evaluateEvaluators);
0387:
0388: // filter the value if necessary
0389: Filter[] currentFilters = filters;
0390: for (int i = 0; i < currentFilters.length; i++) {
0391: value = currentFilters[i].filterValue(kitClass,
0392: settingName, value);
0393: }
0394:
0395: return value;
0396: }
0397:
0398: private static Object getValueEx(MimePath mimePath, Class kitClass,
0399: String settingName, boolean evaluateEvaluators) {
0400: Object value = null;
0401: boolean hasValue = false;
0402:
0403: // read the value according to the guessed type of the setting
0404: if (settingName != null
0405: && SettingsNames.ABBREV_MAP.equals(settingName)) {
0406: Object abbrevsFromInitializers = getValueOld(kitClass,
0407: SettingsNames.ABBREV_MAP, true);
0408: Map<String, String> abbrevs = findCodeTemplates(mimePath);
0409:
0410: if (abbrevsFromInitializers instanceof Map) {
0411: value = new HashMap((Map) abbrevsFromInitializers);
0412: ((Map) value).putAll(abbrevs);
0413: } else {
0414: value = abbrevs;
0415: }
0416:
0417: hasValue = true;
0418: } else if (settingName != null
0419: && SettingsNames.KEY_BINDING_LIST.equals(settingName)) {
0420: Object keybindingsFromInitializers = getValueOld(kitClass,
0421: SettingsNames.KEY_BINDING_LIST, true);
0422: List<MultiKeyBinding> keybindings = findKeyBindings(mimePath);
0423:
0424: if (keybindingsFromInitializers instanceof List) {
0425: value = new ArrayList(
0426: (List) keybindingsFromInitializers);
0427: ((List) value).addAll(keybindings);
0428: } else {
0429: value = keybindings;
0430: }
0431:
0432: hasValue = true;
0433: } else if (settingName != null
0434: && SettingsNames.MACRO_MAP.equals(settingName)) {
0435: Object macrosFromInitializers = getValueOld(kitClass,
0436: SettingsNames.MACRO_MAP, true);
0437: Map<String, String> macros = findMacros(mimePath);
0438:
0439: if (macrosFromInitializers instanceof Map) {
0440: value = new HashMap((Map) macrosFromInitializers);
0441: ((Map) value).putAll(macros);
0442: } else {
0443: value = macros;
0444: }
0445:
0446: hasValue = true;
0447: } else {
0448: // Check if the requested setting is a coloring
0449: if (settingName != null
0450: && HIGHLIGHT_COLOR_NAMES.contains(settingName)) {
0451: value = findColor(settingName, mimePath);
0452: hasValue = true;
0453: } else if (settingName != null
0454: && HIGHLIGHT_COLORING_NAMES.contains(settingName)) {
0455: value = findColoring(settingName, mimePath, false, true);
0456: hasValue = true;
0457: } else {
0458: String coloringName = translateOldTokenColoringName(settingName);
0459: if (coloringName != null) {
0460: value = findColoring(coloringName, mimePath, true,
0461: true);
0462: hasValue = true;
0463: }
0464: }
0465:
0466: // Try getting it from editor Preferences
0467: if (!hasValue) {
0468: Preferences prefs = findPreferences(mimePath);
0469:
0470: // check if there is actually some value
0471: if (prefs != null
0472: && null != prefs.get(settingName, null)) {
0473: // try guessing the type
0474: Class type = null;
0475: String javaType = prefs.get(JAVATYPE_KEY_PREFIX
0476: + settingName, null);
0477: if (javaType != null) {
0478: type = typeFromString(javaType);
0479: }
0480:
0481: if (type != null) {
0482: if (type.equals(Boolean.class)) {
0483: value = prefs
0484: .getBoolean(settingName, false);
0485: hasValue = true;
0486: } else if (type.equals(Integer.class)) {
0487: value = prefs.getInt(settingName, 0);
0488: hasValue = true;
0489: } else if (type.equals(Long.class)) {
0490: value = prefs.getLong(settingName, 0L);
0491: hasValue = true;
0492: } else if (type.equals(Float.class)) {
0493: value = prefs.getFloat(settingName, 0.0F);
0494: hasValue = true;
0495: } else if (type.equals(Double.class)) {
0496: value = prefs.getDouble(settingName, 0.0D);
0497: hasValue = true;
0498: } else if (type.equals(Insets.class)) {
0499: value = parseInsets(prefs.get(settingName,
0500: null));
0501: hasValue = true;
0502: } else if (type.equals(Dimension.class)) {
0503: value = parseDimension(prefs.get(
0504: settingName, null));
0505: hasValue = true;
0506: } else if (type.equals(Color.class)) {
0507: value = parseColor(prefs.get(settingName,
0508: null));
0509: hasValue = true;
0510: } else if (type.equals(String.class)) {
0511: value = prefs.get(settingName, null);
0512: hasValue = true;
0513: } else {
0514: LOG
0515: .log(
0516: Level.WARNING,
0517: "Can't load setting '"
0518: + settingName
0519: + "' with value '"
0520: + prefs
0521: .get(
0522: settingName,
0523: null) //NOI18N
0524: + "' through org.netbeans.editor.Settings! Unsupported value conversion to "
0525: + type,
0526: new Throwable("Stacktrace")); //NOI18N
0527: }
0528: } else {
0529: // unknown setting type, treat it as String
0530: LOG
0531: .warning("Can't determine type of '"
0532: + settingName
0533: + "' editor setting. If you supplied this setting" //NOI18N
0534: + " through the editor implementation of java.util.prefs.Preferences you should use the 'javaType'" //NOI18N
0535: + " attribute and specify the class representing values of this setting. There seem to be legacy" //NOI18N
0536: + " clients accessing your setting through the old org.netbeans.editor.Settings."); //NOI18N
0537: }
0538: }
0539: }
0540: }
0541:
0542: // Fallback on to the kitmaps
0543: if (!hasValue) {
0544: value = getValueOld(kitClass, settingName,
0545: evaluateEvaluators);
0546: }
0547:
0548: return value;
0549: }
0550:
0551: private static Object getValueOld(Class kitClass,
0552: String settingName, boolean evaluateEvaluators) {
0553: Object value = null;
0554: List allKitMaps = getAllKitMaps(kitClass);
0555: assert allKitMaps.size() % 2 == 0 : "allKitMaps should contain pairs of [kitClass, settingsMap]."; //NOI18N
0556:
0557: for (int i = 0; i < allKitMaps.size() / 2; i++) {
0558: Class kc = (Class) allKitMaps.get(2 * i);
0559: Map map = (Map) allKitMaps.get(2 * i + 1);
0560: value = map.get(settingName);
0561: if (evaluateEvaluators && value instanceof Evaluator) {
0562: value = ((Evaluator) value).getValue(kc, settingName);
0563: }
0564: if (value != null) {
0565: break;
0566: }
0567: }
0568:
0569: return value;
0570: }
0571:
0572: /** Get the value hierarchy and evaluate the evaluators */
0573: public static KitAndValue[] getValueHierarchy(Class kitClass,
0574: String settingName) {
0575: return getValueHierarchy(kitClass, settingName, true);
0576: }
0577:
0578: /** Get array of KitAndValue objects sorted from the given kit class to its
0579: * deepest superclass and the last member can be filled whether there
0580: * is global setting (kit class of that member would be null).
0581: * This method is useful for objects like keymaps that
0582: * need to create all the parent keymaps to work properly.
0583: * The method can either evaluate evaluators or leave them untouched
0584: * which can become handy in some cases.
0585: * @param kitClass editor kit class for which the value of setting should
0586: * be retrieved. The null can be used as the root of the whole hierarchy.
0587: * @param settingName name of the setting for which the value should
0588: * be retrieved
0589: * @param evaluateEvaluators whether the evaluators should be evaluated or not
0590: * @return the array containing KitAndValue instances describing the particular
0591: * setting's value on the specific kit level.
0592: */
0593: public static KitAndValue[] getValueHierarchy(Class kitClass,
0594: String settingName, boolean evaluateEvaluators) {
0595: ArrayList<KitAndValue> kavList = new ArrayList<KitAndValue>();
0596:
0597: for (Class kc = kitClass; kc != null; kc = kc.getSuperclass()) {
0598: String mimeType = BaseKit.kitsTracker_FindMimeType(kc);
0599: MimePath mimePath = mimeType == null ? MimePath.EMPTY
0600: : MimePath.parse(mimeType);
0601:
0602: Object value = getValueEx(mimePath, kc, settingName,
0603: evaluateEvaluators);
0604: if (value != null) {
0605: kavList.add(new KitAndValue(kc, value));
0606: }
0607:
0608: if (mimePath == MimePath.EMPTY) {
0609: break;
0610: }
0611: }
0612:
0613: KitAndValue[] kavArray = kavList
0614: .toArray(new KitAndValue[kavList.size()]);
0615:
0616: // filter the value if necessary
0617: Filter[] currentFilters = filters;
0618: for (int i = 0; i < currentFilters.length; i++) {
0619: kavArray = currentFilters[i].filterValueHierarchy(kitClass,
0620: settingName, kavArray);
0621: }
0622:
0623: return kavArray;
0624: }
0625:
0626: /** Set the new value for property on kit level. The old and new values
0627: * are compared and if they are equal the setting is not changed and
0628: * nothing is fired.
0629: *
0630: * @param kitClass editor kit class for which the value of setting should
0631: * be set. The null can be used as the root of the whole hierarchy.
0632: * @param settingName the string used for searching the value
0633: * @param newValue new value to set for the property; the value can
0634: * be null to clear the value for the specified kit
0635: */
0636: public static void setValue(Class kitClass, String settingName,
0637: Object newValue) {
0638: if (settingName != null
0639: && SettingsNames.ABBREV_MAP.equals(settingName)) {
0640: LOG
0641: .log(
0642: Level.WARNING,
0643: "Can't save 'SettingsNames.ABBREV_MAP' setting through org.netbeans.editor.Settings!",
0644: new Throwable("Stacktrace")); //NOI18N
0645: } else if (settingName != null
0646: && SettingsNames.KEY_BINDING_LIST.equals(settingName)) {
0647: LOG
0648: .log(
0649: Level.WARNING,
0650: "Can't save 'SettingsNames.KEY_BINDING_LIST' setting through org.netbeans.editor.Settings!",
0651: new Throwable("Stacktrace")); //NOI18N
0652: } else if (settingName != null
0653: && SettingsNames.MACRO_MAP.equals(settingName)) {
0654: LOG
0655: .log(
0656: Level.WARNING,
0657: "Can't save 'SettingsNames.MACRO_MAP' setting through org.netbeans.editor.Settings!",
0658: new Throwable("Stacktrace")); //NOI18N
0659: } else {
0660: boolean coloring = false;
0661:
0662: // Check if the requested setting is a coloring
0663: if (settingName != null
0664: && HIGHLIGHT_COLOR_NAMES.contains(settingName)) {
0665: coloring = true;
0666: } else if (settingName != null
0667: && HIGHLIGHT_COLORING_NAMES.contains(settingName)) {
0668: coloring = true;
0669: } else {
0670: String coloringName = translateOldTokenColoringName(settingName);
0671: if (coloringName != null) {
0672: coloring = true;
0673: }
0674: }
0675:
0676: if (coloring) {
0677: LOG.log(Level.WARNING, "Can't save coloring '"
0678: + settingName
0679: + "' through org.netbeans.editor.Settings!",
0680: new Throwable("Stacktrace")); //NOI18N
0681: } else {
0682: boolean useKitMaps = false;
0683: String mimeType = BaseKit
0684: .kitsTracker_FindMimeType(kitClass);
0685: MimePath mimePath = mimeType == null ? MimePath.EMPTY
0686: : MimePath.parse(mimeType);
0687: Preferences prefs = findPreferences(mimePath);
0688:
0689: if (prefs != null) {
0690: if (newValue != null) {
0691: if (newValue instanceof Boolean) {
0692: prefs.putBoolean(settingName,
0693: (Boolean) newValue);
0694: } else if (newValue instanceof Integer) {
0695: prefs.putInt(settingName,
0696: (Integer) newValue);
0697: } else if (newValue instanceof Long) {
0698: prefs.putLong(settingName, (Long) newValue);
0699: } else if (newValue instanceof Float) {
0700: prefs.putFloat(settingName,
0701: (Float) newValue);
0702: } else if (newValue instanceof Double) {
0703: prefs.putDouble(settingName,
0704: (Double) newValue);
0705: } else if (newValue instanceof Insets) {
0706: prefs.put(settingName,
0707: insetsToString((Insets) newValue));
0708: prefs.put(
0709: JAVATYPE_KEY_PREFIX + settingName,
0710: Insets.class.getName());
0711: } else if (newValue instanceof Dimension) {
0712: prefs
0713: .put(
0714: settingName,
0715: dimensionToString((Dimension) newValue));
0716: prefs.put(
0717: JAVATYPE_KEY_PREFIX + settingName,
0718: Dimension.class.getName());
0719: } else if (newValue instanceof Color) {
0720: prefs.put(settingName,
0721: color2String((Color) newValue));
0722: prefs.put(
0723: JAVATYPE_KEY_PREFIX + settingName,
0724: Color.class.getName());
0725: } else if (newValue instanceof String) {
0726: prefs.put(settingName, (String) newValue);
0727: } else {
0728: LOG
0729: .log(
0730: Level.FINE,
0731: "Can't save setting '"
0732: + settingName
0733: + "' with value '"
0734: + newValue //NOI18N
0735: + "' through org.netbeans.editor.Settings; unsupported value conversion!",
0736: new Throwable("Stacktrace")); //NOI18N
0737: useKitMaps = true;
0738: }
0739: } else {
0740: prefs.remove(settingName);
0741: }
0742: } else {
0743: useKitMaps = true;
0744: }
0745:
0746: if (useKitMaps) {
0747: // no prefs implementation, fall back on to the kit maps
0748: synchronized (Settings.class) {
0749: Map map = getKitMap(kitClass, true);
0750: Object oldValue = map.get(settingName);
0751: if (oldValue == null
0752: && newValue == null
0753: || (oldValue != null && oldValue
0754: .equals(newValue))) {
0755: return; // no change
0756: }
0757: if (newValue != null) {
0758: map.put(settingName, newValue);
0759: } else {
0760: map.remove(settingName);
0761: }
0762: }
0763: }
0764: }
0765: }
0766:
0767: fireSettingsChange(kitClass, settingName, null, newValue);
0768: }
0769:
0770: /** Don't change the value of the setting, but fire change
0771: * event. This is useful when there's internal change in the value object
0772: * of some setting.
0773: */
0774: public static void touchValue(Class kitClass, String settingName) {
0775: fireSettingsChange(kitClass, settingName, null, null); // kit class currently not used
0776: }
0777:
0778: /** Set the value for the current kit and propagate it to all
0779: * the children of the given kit by removing
0780: * the possible values for the setting from the children kit setting maps.
0781: * Note: This call only affects the settings for the kit classes for which
0782: * the kit setting map with the setting values currently exists, i.e. when
0783: * there was at least one getValue() or setValue() call performed for any
0784: * setting on that particular kit class level. Other kit classes maps
0785: * will be initialized by the particular initializer(s) as soon as
0786: * the first getValue() or setValue() will be performed for them.
0787: * However that future process will not be affected by the current
0788: * propagateValue() call.
0789: * This method is useful for the visual options that always set
0790: * the value on all the kit levels without regard whether it's necessary or not.
0791: * If the value is then changed for the base kit, it's not propagated
0792: * naturally as there's a special setting
0793: * This method enables
0794: *
0795: * The current implementation always fires the change regardless whether
0796: * there was real change in setting value or not.
0797: * @param kitClass editor kit class for which the value of setting should
0798: * be set. The null can be used as the root of the whole hierarchy.
0799: * @param settingName the string used for searching the value
0800: * @param newValue new value to set for the property; the value can
0801: * be null to clear the value for the specified kit
0802: */
0803: public static void propagateValue(Class kitClass,
0804: String settingName, Object newValue) {
0805: synchronized (Settings.class) {
0806: Map map = getKitMap(kitClass, true);
0807: if (newValue != null) {
0808: map.put(settingName, newValue);
0809: } else {
0810: map.remove(settingName);
0811: }
0812: // resolve kits
0813: Iterator it = kit2Maps.entrySet().iterator();
0814: while (it.hasNext()) {
0815: Map.Entry me = (Map.Entry) it.next();
0816: Class kc = (Class) me.getKey();
0817: if (kitClass != kc
0818: && (kitClass == null || kitClass
0819: .isAssignableFrom(kc))) {
0820: ((Map) me.getValue()).remove(settingName);
0821: }
0822: }
0823: }
0824:
0825: fireSettingsChange(null, settingName, null, null);
0826: }
0827:
0828: /** Run the given runnable. All the changes in the settings are not fired until
0829: * the whole runnable completes. Nesting of <tt>update()</tt> call is allowed.
0830: * Only one firing is performed after the whole runnable completes
0831: * using the 'null triple'.
0832: */
0833: public static void update(final Runnable r) {
0834: // Just a backdoor for BaseOptions and OptionSupport to be able
0835: // to serialize settings related tasks.
0836: if (isAsyncTask(r)) {
0837: PROCESSOR.post(r, getTaskDelay(r));
0838: } else {
0839: boolean fire = false;
0840:
0841: synchronized (Settings.class) {
0842: firingEnabled++;
0843: try {
0844: r.run();
0845: } finally {
0846: firingEnabled--;
0847: fire = firingEnabled == 0;
0848: }
0849: }
0850:
0851: if (fire) {
0852: fireSettingsChange(null, null, null, null);
0853: }
0854: }
0855: }
0856:
0857: private static boolean isAsyncTask(Runnable r) {
0858: try {
0859: Method m = r.getClass().getDeclaredMethod("asynchronous"); //NOI18N
0860: m.setAccessible(true);
0861: return (Boolean) m.invoke(r);
0862: } catch (Exception e) {
0863: return false;
0864: }
0865: }
0866:
0867: private static int getTaskDelay(Runnable r) {
0868: try {
0869: Method m = r.getClass().getDeclaredMethod("delay"); //NOI18N
0870: m.setAccessible(true);
0871: return (Integer) m.invoke(r);
0872: } catch (Exception e) {
0873: return 0;
0874: }
0875: }
0876:
0877: /** Reset all the settings and fire the change of the settings
0878: * so that all the listeners will be notified and will reload
0879: * the settings.
0880: * The settings that were changed using setValue() and propagateValue()
0881: * are lost. Initializers will be asked for the settings values when
0882: * necessary.
0883: */
0884: public static void reset() {
0885: RESET_TASK.schedule(1);
0886: }
0887:
0888: /** Debug the current initializers */
0889: public static String initializersToString() {
0890: StringBuffer sb = new StringBuffer();
0891: List[] lists = getListsOfInitializers();
0892: for (int i = 0; i < lists.length; i++) {
0893: // debug the level
0894: switch (i) {
0895: case CORE_LEVEL:
0896: sb.append("CORE_LEVEL"); // NOI18N
0897: break;
0898:
0899: case SYSTEM_LEVEL:
0900: sb.append("SYSTEM_LEVEL"); // NOI18N
0901: break;
0902:
0903: case EXTENSION_LEVEL:
0904: sb.append("EXTENSION_LEVEL"); // NOI18N
0905: break;
0906:
0907: case OPTION_LEVEL:
0908: sb.append("OPTION_LEVEL"); // NOI18N
0909: break;
0910:
0911: case USER_LEVEL:
0912: sb.append("USER_LEVEL"); // NOI18N
0913: break;
0914:
0915: default:
0916: sb.append("level " + i); // NOI18N
0917: break;
0918: }
0919: sb.append(":\n"); // NOI18N
0920:
0921: // debug the initializers
0922: sb.append(EditorDebug.debugList((List) lists[i]));
0923: sb.append('\n'); //NOI18N
0924: }
0925:
0926: return sb.toString();
0927: }
0928:
0929: /** Add weak listener to listen to change of any property. The caller must
0930: * hold the listener object in some instance variable to prevent it
0931: * from being garbage collected.
0932: */
0933: public static void addSettingsChangeListener(
0934: SettingsChangeListener l) {
0935: listenerList.add(SettingsChangeListener.class, l);
0936: }
0937:
0938: /** Remove listener for changes in properties */
0939: public static void removeSettingsChangeListener(
0940: SettingsChangeListener l) {
0941: listenerList.remove(SettingsChangeListener.class, l);
0942: }
0943:
0944: private static void fireSettingsChange(Class kitClass,
0945: String settingName, Object oldValue, Object newValue) {
0946: if (firingEnabled == 0) {
0947: SettingsChangeListener[] listeners = (SettingsChangeListener[]) listenerList
0948: .getListeners(SettingsChangeListener.class);
0949: SettingsChangeEvent evt = new SettingsChangeEvent(
0950: Settings.class, kitClass, settingName, oldValue,
0951: newValue);
0952: for (int i = 0; i < listeners.length; i++) {
0953: listeners[i].settingsChange(evt);
0954: }
0955: }
0956: }
0957:
0958: /**
0959: * Gets (and possibly creates) kit map for particular kit. This always runs
0960: * under the Settings.class lock and because it calls initializers that
0961: * can do whatever ever they like, it is important not to fire any events
0962: * (eg. when an initializer calls setValue or something similar). See issue #118763.
0963: */
0964: private static Map getKitMap(Class kitClass, boolean forceCreation) {
0965: firingEnabled++;
0966: try {
0967: return getKitMapWithEvent(kitClass, forceCreation);
0968: } finally {
0969: firingEnabled--;
0970: }
0971: }
0972:
0973: private static Map getKitMapWithEvent(Class kitClass,
0974: boolean forceCreation) {
0975: Map kitMap = (Map) kit2Maps.get(kitClass);
0976: if (kitMap == null) {
0977: Map emptyMap = (Map) emptyMaps.get(kitClass);
0978: if (emptyMap != null) {
0979: // recursive initialization, return what we have collected so far
0980: return emptyMap;
0981: }
0982:
0983: if (emptyMap == null) {
0984: if (LOG.isLoggable(Level.FINE)) {
0985: emptyMap = new LoggingMap(kitClass, Level.FINE);
0986: } else {
0987: emptyMap = new HashMap();
0988: }
0989: emptyMaps.put(kitClass, emptyMap);
0990: }
0991:
0992: // Go through all the initializers
0993: List[] lists = getListsOfInitializers();
0994: for (int i = 0; i < lists.length; i++) {
0995: Iterator it = ((List) lists[i]).iterator();
0996: while (it.hasNext()) {
0997: Initializer initializer = (Initializer) it.next();
0998:
0999: // A call to initializer shouldn't break the whole updating
1000: try {
1001: initializer.updateSettingsMap(kitClass,
1002: emptyMap);
1003: } catch (Throwable t) {
1004: LOG.log(Level.WARNING, null, t);
1005: }
1006: }
1007: }
1008:
1009: kitMap = emptyMap;
1010:
1011: kit2Maps.put(kitClass, kitMap);
1012: emptyMaps.remove(kitClass);
1013: }
1014:
1015: return kitMap;
1016: }
1017:
1018: private static List getAllKitMaps(Class kitClass) {
1019: synchronized (Settings.class) {
1020: // Collect the kit classes that we need to ask for settings
1021: List<Class> classes = new ArrayList<Class>();
1022: for (Class kc = kitClass; kc != null; kc = kc
1023: .getSuperclass()) {
1024: classes.add(kc);
1025: }
1026:
1027: // Load the kit maps starting with BaseKit.class and going to kitClass.
1028: // This allows an initializer for a superclass to add more initializers
1029: // for subclasses. See for example NbEditorSettingsInitializer.updateSettingsMap.
1030: List list = new ArrayList();
1031: for (int i = classes.size() - 1; i >= 0; i--) {
1032: Class kc = classes.get(i);
1033: Map map = getKitMap(kc, false);
1034: if (map != null) {
1035: list.add(map);
1036: list.add(kc);
1037: }
1038: }
1039:
1040: Collections.reverse(list);
1041: return list;
1042: }
1043: }
1044:
1045: /** Kit class and value pair */
1046: public static class KitAndValue {
1047:
1048: public Class kitClass;
1049:
1050: public Object value;
1051:
1052: public KitAndValue(Class kitClass, Object value) {
1053: this .kitClass = kitClass;
1054: this .value = value;
1055: }
1056:
1057: }
1058:
1059: /** Initializer of the settings updates the map filled
1060: * with settings for the particular kit class when asked.
1061: * If the settings are being initialized all the initializers registered
1062: * by the <tt>Settings.addInitializer()</tt> are being asked to update
1063: * the settings-map through calling their <tt>updateSettingsMap()</tt>.
1064: */
1065: public static interface Initializer {
1066:
1067: /** Each initializer must have a name. The name should be unique.
1068: * The name is used for identifying the initializer during removal
1069: * and sort operations and for debuging purposes.
1070: */
1071: public String getName();
1072:
1073: /** Update map filled with the settings.
1074: * @param kitClass kit class for which the settings are being updated.
1075: * It can be null which means the root of the whole kit class hierarchy.
1076: * @param settingsMap map holding [setting-name, setting-value] pairs.
1077: * The map can be empty if this is the first initializer
1078: * that updates it or if no previous initializers updated it.
1079: */
1080: public void updateSettingsMap(Class kitClass, Map settingsMap);
1081:
1082: }
1083:
1084: /** Abstract implementation of the initializer dealing with the name. */
1085: public static abstract class AbstractInitializer implements
1086: Initializer {
1087:
1088: private String name;
1089:
1090: public AbstractInitializer(String name) {
1091: this .name = name;
1092: }
1093:
1094: public String getName() {
1095: return name;
1096: }
1097:
1098: public @Override
1099: String toString() {
1100: return getName();
1101: }
1102:
1103: } // End of AbstractInitializer class
1104:
1105: /** Sort the settings initializers that were added to the settings.
1106: * There can be only one sorter for the Settings, but it can delegate
1107: * to previously registered sorter.
1108: */
1109: public static interface InitializerSorter {
1110:
1111: public void sort(List initializersList);
1112:
1113: }
1114:
1115: /** Initializer sorter that delegates to another sorter. */
1116: public static abstract class FilterInitializerSorter {
1117:
1118: private InitializerSorter delegate;
1119:
1120: public FilterInitializerSorter(InitializerSorter delegate) {
1121: this .delegate = delegate;
1122: }
1123:
1124: public void sort(List initializersList) {
1125: if (delegate != null) {
1126: delegate.sort(initializersList);
1127: }
1128: }
1129:
1130: }
1131:
1132: /** Evaluator can be used in cases when value of some setting
1133: * depends on the value for other setting and it allows to compute
1134: * the value dynamically based on the other setting(s) value.
1135: * The <tt>Evaluator</tt> instance can be used as the value
1136: * in the <tt>Settings.setValue()</tt> call. In that case the call
1137: * to the <tt>Settings.getValue()</tt> call will 'evaluate' the Evaluator
1138: * by calling its <tt>getValue()</tt>.
1139: */
1140: public static interface Evaluator {
1141:
1142: /** Compute the particular setting's value.
1143: * @param kitClass kit class for which the setting is being retrieved.
1144: * @param settingName name of the setting to retrieve. Although the Evaluator
1145: * are usually constructed only for the concrete setting, this parameter
1146: * allows creation of the Evaluator for multiple settings.
1147: * @return the value for the requested setting. The substitution
1148: * is not attempted again, so the return value cannot be another
1149: * Evaluator instance. If the returned value is null, the same
1150: * action is taken as if there would no value set on the particular
1151: * kit level.
1152: *
1153: */
1154: public Object getValue(Class kitClass, String settingName);
1155:
1156: }
1157:
1158: /** Filter is applied on every value or KitAndValue pairs returned from getValue().
1159: * The filter can be registered by calling <tt>Settings.addFilter()</tt>.
1160: * Each call to <tt>Settings.getValue()</tt> will first retrieve the value and
1161: * then call the <tt>Filter.filterValue()</tt> to get the final value. Each call
1162: * to <tt>Settings.getValueHierarchy()</tt> will first retrieve the kit-and-value
1163: * array and then call the <tt>Filter.filterValueHierarchy()</tt>.
1164: * If more filters are registered they are all used in the order they were added.
1165: */
1166: public static interface Filter {
1167:
1168: /** Filter single value. The value can be substituted here.
1169: * @param kitClass class of the kit for which the value is retrieved
1170: * @param settingName name of the retrieved setting
1171: * @param value value to be optionally filtered
1172: */
1173: public Object filterValue(Class kitClass, String settingName,
1174: Object value);
1175:
1176: /** Filter array of kit-class and value pairs. The pairs can be completely
1177: * substituted with an array with different length and different members.
1178: * @param kitClass class of the kit for which the value is retrieved
1179: * @param settingName name of the retrieved setting
1180: * @param kavArray kit-class and value array to be filtered
1181: */
1182: public KitAndValue[] filterValueHierarchy(Class kitClass,
1183: String settingName, KitAndValue[] kavArray);
1184:
1185: }
1186:
1187: // This is just for debugging and should not normally be used.
1188: private static final class LoggingMap extends HashMap {
1189:
1190: private Class kitClass;
1191: private Level logLevel;
1192:
1193: private static final Set<String> DEPRECATED_SETTINGS = Collections
1194: .synchronizedSet(Collections
1195: .unmodifiableSet(new HashSet<String>(
1196: Arrays
1197: .asList(new String[] {
1198: // Fonts & Colors related settings
1199: SettingsNames.BLOCK_SEARCH_COLORING,
1200: SettingsNames.CARET_COLOR_INSERT_MODE,
1201: SettingsNames.CARET_COLOR_OVERWRITE_MODE,
1202: SettingsNames.CODE_FOLDING_COLORING,
1203: SettingsNames.COLORING_NAME_LIST,
1204: SettingsNames.COLORING_NAME_PRINT_SUFFIX,
1205: SettingsNames.COLORING_NAME_SUFFIX,
1206: SettingsNames.DEFAULT_COLORING,
1207: SettingsNames.GUARDED_COLORING,
1208: SettingsNames.HIGHLIGHT_SEARCH_COLORING,
1209: SettingsNames.INC_SEARCH_COLORING,
1210: SettingsNames.LINE_NUMBER_COLORING,
1211: SettingsNames.SELECTION_COLORING,
1212: SettingsNames.STATUS_BAR_BOLD_COLORING,
1213: SettingsNames.STATUS_BAR_COLORING,
1214: SettingsNames.TEXT_LIMIT_LINE_COLOR,
1215:
1216: // Keybindings related settings
1217: SettingsNames.KEY_BINDING_LIST, }))));
1218:
1219: public LoggingMap(Class kitClass, Level logLevel) {
1220: super ();
1221: this .kitClass = kitClass;
1222: this .logLevel = logLevel;
1223: }
1224:
1225: public @Override
1226: Object put(Object key, Object value) {
1227: if (key != null
1228: && (key.equals(SettingsNames.RENDERING_HINTS) || key
1229: .equals("textAntialiasing") //NOI18N
1230: )) {
1231: String msg = "Settings map: put('" + key + "' to '"
1232: + value + "') for kitClass=" + kitClass; //NOI18N
1233: if (LOG_STACTRACES) {
1234: LOG.log(logLevel, null, new Throwable(msg));
1235: } else {
1236: LOG.log(logLevel, msg);
1237: }
1238: }
1239:
1240: logDeprecatedKey(key);
1241: return super .put(key, value);
1242: }
1243:
1244: public @Override
1245: Object get(Object key) {
1246: logDeprecatedKey(key);
1247: return super .get(key);
1248: }
1249:
1250: public @Override
1251: boolean containsKey(Object key) {
1252: logDeprecatedKey(key);
1253: return super .containsKey(key);
1254: }
1255:
1256: public @Override
1257: Object remove(Object key) {
1258: logDeprecatedKey(key);
1259: return super .remove(key);
1260: }
1261:
1262: private void logDeprecatedKey(Object key) {
1263: if (LOG.isLoggable(logLevel)) {
1264: if (key != null && DEPRECATED_SETTINGS.contains(key)) {
1265: String msg = "The editor setting '"
1266: + key
1267: + "' is deprecated. Please use Editor Settings API instead."; //NOI18N
1268: if (LOG_STACTRACES) {
1269: LOG.log(logLevel, null, new Throwable(msg));
1270: } else {
1271: LOG.log(logLevel, msg);
1272: }
1273: }
1274: }
1275: }
1276: } // End of LoggingMap class
1277:
1278: // ----------------------------------------------------------
1279: // Fonts & Colors bridge to Editor Settings API
1280: // ----------------------------------------------------------
1281:
1282: private static final Map<MimePath, ChangesTrackingLookupResult<FontColorSettings>> FCS_CACHE = new WeakHashMap<MimePath, Settings.ChangesTrackingLookupResult<FontColorSettings>>();
1283:
1284: private static final Set<String> HIGHLIGHT_COLOR_NAMES = new HashSet<String>();
1285: private static final Set<String> HIGHLIGHT_COLORING_NAMES = new HashSet<String>();
1286: static {
1287: HIGHLIGHT_COLOR_NAMES
1288: .add(SettingsNames.CARET_COLOR_INSERT_MODE);
1289: HIGHLIGHT_COLOR_NAMES
1290: .add(SettingsNames.CARET_COLOR_OVERWRITE_MODE);
1291: HIGHLIGHT_COLOR_NAMES.add(SettingsNames.TEXT_LIMIT_LINE_COLOR);
1292:
1293: HIGHLIGHT_COLORING_NAMES
1294: .add(SettingsNames.LINE_NUMBER_COLORING);
1295: HIGHLIGHT_COLORING_NAMES.add(SettingsNames.GUARDED_COLORING);
1296: HIGHLIGHT_COLORING_NAMES
1297: .add(SettingsNames.CODE_FOLDING_COLORING);
1298: HIGHLIGHT_COLORING_NAMES
1299: .add(SettingsNames.CODE_FOLDING_BAR_COLORING);
1300: HIGHLIGHT_COLORING_NAMES.add(SettingsNames.SELECTION_COLORING);
1301: HIGHLIGHT_COLORING_NAMES
1302: .add(SettingsNames.HIGHLIGHT_SEARCH_COLORING);
1303: HIGHLIGHT_COLORING_NAMES.add(SettingsNames.INC_SEARCH_COLORING);
1304: HIGHLIGHT_COLORING_NAMES
1305: .add(SettingsNames.BLOCK_SEARCH_COLORING);
1306: HIGHLIGHT_COLORING_NAMES.add(SettingsNames.STATUS_BAR_COLORING);
1307: HIGHLIGHT_COLORING_NAMES
1308: .add(SettingsNames.STATUS_BAR_BOLD_COLORING);
1309: }
1310:
1311: private static Coloring findColoring(String coloringName,
1312: MimePath mimePath, boolean token, boolean highlight) {
1313: AttributeSet attribs = findAttribs(coloringName, mimePath,
1314: token, highlight);
1315: return attribs == null ? null : Coloring
1316: .fromAttributeSet(attribs);
1317: }
1318:
1319: private static Color findColor(String coloringName,
1320: MimePath mimePath) {
1321: AttributeSet attribs = findAttribs(coloringName, mimePath,
1322: false, true);
1323: return attribs == null ? null : (Color) attribs
1324: .getAttribute(StyleConstants.Foreground);
1325: }
1326:
1327: private static AttributeSet findAttribs(String coloringName,
1328: MimePath mimePath, boolean token, boolean highlight) {
1329: synchronized (FCS_CACHE) {
1330: ChangesTrackingLookupResult<FontColorSettings> ctlr = FCS_CACHE
1331: .get(mimePath);
1332:
1333: if (ctlr == null) {
1334: Lookup.Result<FontColorSettings> lookupResult = MimeLookup
1335: .getLookup(mimePath).lookupResult(
1336: FontColorSettings.class);
1337: ctlr = new ChangesTrackingLookupResult<FontColorSettings>(
1338: lookupResult, null);
1339: FCS_CACHE.put(mimePath, ctlr);
1340: }
1341:
1342: AttributeSet attribs = null;
1343: Collection<? extends FontColorSettings> allFcs = ctlr
1344: .getLookupResult().allInstances();
1345: FontColorSettings fcs = allFcs.isEmpty() ? null : allFcs
1346: .iterator().next();
1347:
1348: if (fcs != null) {
1349: if (token && !highlight) {
1350: attribs = fcs.getTokenFontColors(coloringName);
1351: } else if (!token && highlight) {
1352: attribs = fcs.getFontColors(coloringName);
1353: } else {
1354: attribs = fcs.getFontColors(coloringName);
1355: if (attribs == null) {
1356: attribs = fcs.getTokenFontColors(coloringName);
1357: }
1358: }
1359: }
1360:
1361: return attribs;
1362: }
1363: }
1364:
1365: private static String translateOldTokenColoringName(String name) {
1366: String translated = null;
1367:
1368: if (name != null
1369: && name.endsWith(SettingsNames.COLORING_NAME_SUFFIX)) {
1370: translated = name.substring(0, name.length()
1371: - SettingsNames.COLORING_NAME_SUFFIX.length());
1372: }
1373:
1374: if (name != null
1375: && name
1376: .endsWith(SettingsNames.COLORING_NAME_PRINT_SUFFIX)) {
1377: translated = name
1378: .substring(0, name.length()
1379: - SettingsNames.COLORING_NAME_PRINT_SUFFIX
1380: .length());
1381: }
1382:
1383: return translated;
1384: }
1385:
1386: // ----------------------------------------------------------
1387: // KeyBindings bridge to Editor Settings API
1388: // ----------------------------------------------------------
1389:
1390: private static final Map<MimePath, ChangesTrackingLookupResult<KeyBindingSettings>> KBS_CACHE = new WeakHashMap<MimePath, ChangesTrackingLookupResult<KeyBindingSettings>>();
1391:
1392: private static List<MultiKeyBinding> findKeyBindings(
1393: MimePath mimePath) {
1394: synchronized (KBS_CACHE) {
1395: ChangesTrackingLookupResult<KeyBindingSettings> ctlr = KBS_CACHE
1396: .get(mimePath);
1397:
1398: if (ctlr == null) {
1399: Lookup.Result<KeyBindingSettings> lookupResult = MimeLookup
1400: .getLookup(mimePath).lookupResult(
1401: KeyBindingSettings.class);
1402: ctlr = new ChangesTrackingLookupResult<KeyBindingSettings>(
1403: lookupResult, SettingsNames.KEY_BINDING_LIST);
1404: KBS_CACHE.put(mimePath, ctlr);
1405: }
1406:
1407: @SuppressWarnings("unchecked")
1408: List<MultiKeyBinding> list = (List<MultiKeyBinding>) ctlr
1409: .getCustomData();
1410: if (list == null) {
1411: Collection<? extends KeyBindingSettings> allKbs = ctlr
1412: .getLookupResult().allInstances();
1413: KeyBindingSettings kbs = allKbs.isEmpty() ? null
1414: : allKbs.iterator().next();
1415:
1416: list = new ArrayList<MultiKeyBinding>();
1417:
1418: if (kbs != null) {
1419: for (org.netbeans.api.editor.settings.MultiKeyBinding mkb : kbs
1420: .getKeyBindings()) {
1421: List<KeyStroke> keyStrokes = mkb
1422: .getKeyStrokeList();
1423: list
1424: .add(new MultiKeyBinding(
1425: keyStrokes
1426: .toArray(new KeyStroke[keyStrokes
1427: .size()]), mkb
1428: .getActionName()));
1429: }
1430:
1431: ctlr.setCustomData(list);
1432: }
1433: }
1434:
1435: return list;
1436: }
1437: }
1438:
1439: // ----------------------------------------------------------
1440: // CodeTemplates bridge to Editor Settings API
1441: // ----------------------------------------------------------
1442:
1443: private static final Map<MimePath, ChangesTrackingLookupResult<CodeTemplateSettings>> CTS_CACHE = new WeakHashMap<MimePath, ChangesTrackingLookupResult<CodeTemplateSettings>>();
1444:
1445: private static Map<String, String> findCodeTemplates(
1446: MimePath mimePath) {
1447: synchronized (CTS_CACHE) {
1448: ChangesTrackingLookupResult<CodeTemplateSettings> ctlr = CTS_CACHE
1449: .get(mimePath);
1450:
1451: if (ctlr == null) {
1452: Lookup.Result<CodeTemplateSettings> lookupResult = MimeLookup
1453: .getLookup(mimePath).lookupResult(
1454: CodeTemplateSettings.class);
1455: ctlr = new ChangesTrackingLookupResult<CodeTemplateSettings>(
1456: lookupResult, SettingsNames.ABBREV_MAP);
1457: CTS_CACHE.put(mimePath, ctlr);
1458: }
1459:
1460: @SuppressWarnings("unchecked")
1461: Map<String, String> map = (Map<String, String>) ctlr
1462: .getCustomData();
1463: if (map == null) {
1464: Collection<? extends CodeTemplateSettings> allCts = ctlr
1465: .getLookupResult().allInstances();
1466: CodeTemplateSettings cts = allCts.isEmpty() ? null
1467: : allCts.iterator().next();
1468:
1469: map = new HashMap<String, String>();
1470:
1471: if (cts != null) {
1472: for (CodeTemplateDescription ctd : cts
1473: .getCodeTemplateDescriptions()) {
1474: map.put(ctd.getAbbreviation(), ctd
1475: .getParametrizedText());
1476: }
1477:
1478: ctlr.setCustomData(map);
1479: }
1480: }
1481:
1482: return map;
1483: }
1484: }
1485:
1486: private static final class ChangesTrackingLookupResult<T>
1487: implements LookupListener {
1488:
1489: private final Lookup.Result<T> lookupResult;
1490: private final String settingName;
1491: private Object customData = null;
1492:
1493: public ChangesTrackingLookupResult(
1494: Lookup.Result<T> lookupResult, String settingName) {
1495: this .lookupResult = lookupResult;
1496: this .lookupResult.addLookupListener(WeakListeners.create(
1497: LookupListener.class, this , this .lookupResult));
1498: this .settingName = settingName;
1499: }
1500:
1501: public Lookup.Result<T> getLookupResult() {
1502: return lookupResult;
1503: }
1504:
1505: public Object getCustomData() {
1506: return customData;
1507: }
1508:
1509: public void setCustomData(Object data) {
1510: this .customData = data;
1511: }
1512:
1513: public void resultChanged(LookupEvent ev) {
1514: this .customData = null;
1515: fireSettingsChange(null, settingName, null, null);
1516: }
1517: } // End of TrackingResult class
1518:
1519: // ----------------------------------------------------------
1520: // Preferences bridge to Editor Settings API
1521: // ----------------------------------------------------------
1522:
1523: private static final Map<MimePath, PreferenceChangesTracker> PREFS_CACHE = new WeakHashMap<MimePath, PreferenceChangesTracker>();
1524:
1525: private static Preferences findPreferences(MimePath mimePath) {
1526: synchronized (PREFS_CACHE) {
1527: PreferenceChangesTracker tracker = PREFS_CACHE
1528: .get(mimePath);
1529:
1530: if (tracker == null) {
1531: Preferences prefs = MimeLookup.getLookup(mimePath)
1532: .lookup(Preferences.class);
1533:
1534: // in some tests there is no MimeLookup at all
1535: if (prefs != null) {
1536: tracker = new PreferenceChangesTracker(prefs);
1537: PREFS_CACHE.put(mimePath, tracker);
1538: }
1539: }
1540:
1541: return tracker == null ? null : tracker.getPreferences();
1542: }
1543: }
1544:
1545: private static final class PreferenceChangesTracker implements
1546: PreferenceChangeListener {
1547: private final Preferences prefs;
1548:
1549: public PreferenceChangesTracker(Preferences prefs) {
1550: this .prefs = prefs;
1551: this .prefs.addPreferenceChangeListener(WeakListeners
1552: .create(PreferenceChangeListener.class, this ,
1553: this .prefs));
1554: }
1555:
1556: public Preferences getPreferences() {
1557: return prefs;
1558: }
1559:
1560: public void preferenceChange(PreferenceChangeEvent evt) {
1561: fireSettingsChange(null, evt.getKey(), null, null);
1562: }
1563: } // End of TrackingResult class
1564:
1565: // ----------------------------------------------------------
1566: // Macros to Editor Settings API
1567: // ----------------------------------------------------------
1568:
1569: // XXX: rewrite this to not use reflection. It will require dependency on editor/macros
1570: // and editor/settings/storage. It should also listen on changes fired from EditorSettingsStorage
1571: // and call fireSettingsChange(null, SettingsName.MACRO_MAP, null, null). Should be done
1572: // after the Settings & co. is factored out to its own deprecated module so that we don't
1573: // introduce additional dependencies in editor/lib.
1574: private static Map<String, String> findMacros(MimePath mimePath) {
1575: Map macros = new HashMap();
1576:
1577: ClassLoader classLoader = Lookup.getDefault().lookup(
1578: ClassLoader.class);
1579: try {
1580: Class essClass = classLoader
1581: .loadClass("org.netbeans.modules.editor.settings.storage.api.EditorSettingsStorage"); //NOI18N
1582: Method findMethod = essClass.getDeclaredMethod("find",
1583: String.class); //NOI18N
1584: Object macrosEss = findMethod.invoke(null, "Macros"); //NOI18N
1585:
1586: if (macrosEss != null) {
1587: Class mdClass = classLoader
1588: .loadClass("org.netbeans.modules.editor.macros.storage.MacroDescription"); //NOI18N
1589: Method getCodeMethod = mdClass
1590: .getDeclaredMethod("getCode"); //NOI18N
1591:
1592: Method loadMethod = essClass.getDeclaredMethod("load",
1593: MimePath.class, String.class, Boolean.TYPE); //NOI18N
1594: Map macroDescriptions = (Map) loadMethod.invoke(
1595: macrosEss, mimePath, null, false);
1596: for (Object key : macroDescriptions.keySet()) {
1597: String macroName = (String) key;
1598: Object macroDescription = macroDescriptions
1599: .get(key);
1600:
1601: String macroCode = (String) getCodeMethod
1602: .invoke(macroDescription);
1603: macros.put(macroName, macroCode);
1604: }
1605: }
1606: } catch (Exception e) {
1607: // ignore
1608: }
1609:
1610: macros.put(null, findKeyBindings(mimePath));
1611: return macros;
1612: }
1613:
1614: /** Coverts Insets to String representation */
1615: private static String insetsToString(Insets ins) {
1616: StringBuilder sb = new StringBuilder();
1617: sb.append(ins.top);
1618: sb.append(','); //NOI18N
1619:
1620: sb.append(ins.left);
1621: sb.append(','); //NOI18N
1622:
1623: sb.append(ins.bottom);
1624: sb.append(','); //NOI18N
1625:
1626: sb.append(ins.right);
1627:
1628: return sb.toString();
1629: }
1630:
1631: /** Converts textual representation of Insets */
1632: private static Insets parseInsets(String s) {
1633: StringTokenizer st = new StringTokenizer(s, ","); //NOI18N
1634:
1635: int arr[] = new int[4];
1636: int i = 0;
1637: while (st.hasMoreElements()) {
1638: if (i > 3) {
1639: return null;
1640: }
1641: try {
1642: arr[i] = Integer.parseInt(st.nextToken());
1643: } catch (NumberFormatException nfe) {
1644: LOG.log(Level.WARNING, null, nfe);
1645: return null;
1646: }
1647: i++;
1648: }
1649: if (i != 4) {
1650: return null;
1651: } else {
1652: return new Insets(arr[0], arr[1], arr[2], arr[3]);
1653: }
1654: }
1655:
1656: private static String dimensionToString(Dimension dim) {
1657: StringBuilder sb = new StringBuilder();
1658: sb.append(dim.width);
1659: sb.append(','); //NOI18N
1660: sb.append(dim.height);
1661: return sb.toString();
1662: }
1663:
1664: private static Dimension parseDimension(String s) {
1665: StringTokenizer st = new StringTokenizer(s, ","); // NOI18N
1666:
1667: int arr[] = new int[2];
1668: int i = 0;
1669: while (st.hasMoreElements()) {
1670: if (i > 1) {
1671: return null;
1672: }
1673: try {
1674: arr[i] = Integer.parseInt(st.nextToken());
1675: } catch (NumberFormatException nfe) {
1676: LOG.log(Level.WARNING, null, nfe);
1677: return null;
1678: }
1679: i++;
1680: }
1681: if (i != 2) {
1682: return null;
1683: } else {
1684: return new Dimension(arr[0], arr[1]);
1685: }
1686: }
1687:
1688: private static String wrap(String s) {
1689: return (s.length() == 1) ? "0" + s : s; // NOI18N
1690: }
1691:
1692: /** Converts Color to hexadecimal String representation */
1693: private static String color2String(Color c) {
1694: StringBuilder sb = new StringBuilder();
1695: sb.append('#'); // NOI18N
1696: sb.append(wrap(Integer.toHexString(c.getRed()).toUpperCase()));
1697: sb
1698: .append(wrap(Integer.toHexString(c.getGreen())
1699: .toUpperCase()));
1700: sb.append(wrap(Integer.toHexString(c.getBlue()).toUpperCase()));
1701: return sb.toString();
1702: }
1703:
1704: /** Converts a String to an integer and returns the specified opaque Color. */
1705: private static Color parseColor(String s) {
1706: try {
1707: return Color.decode(s);
1708: } catch (NumberFormatException nfe) {
1709: LOG.log(Level.WARNING, null, nfe);
1710: return null;
1711: }
1712: }
1713:
1714: // private static final Map<String, Class> typesCache = new HashMap<String, Class>();
1715: // private static final class NO_TYPE {};
1716: // private static Class typeFromName(String settingName) {
1717: // synchronized (typesCache) {
1718: // Class settingType = typesCache.get(settingName);
1719: //
1720: // if (settingType == null) {
1721: // EditorSettingClass esc = null;
1722: //
1723: // try {
1724: // for (Field f : SettingsNames.class.getDeclaredFields()) {
1725: // Object value;
1726: // try {
1727: // if ((f.getModifiers() & Modifier.STATIC) == Modifier.STATIC &&
1728: // null != (value = f.get(null)) &&
1729: // value.equals(settingName)
1730: // ) {
1731: // esc = f.getAnnotation(EditorSettingClass.class);
1732: // break;
1733: // }
1734: // } catch (Exception e) {
1735: // LOG.log(Level.FINE, null, e);
1736: // }
1737: // }
1738: // } catch (SecurityException se) {
1739: // LOG.log(Level.FINE, null, se);
1740: // }
1741: //
1742: // settingType = esc == null ? NO_TYPE.class : esc.value();
1743: // typesCache.put(settingName, settingType);
1744: // }
1745: //
1746: // return settingType == NO_TYPE.class ? null : settingType;
1747: // }
1748: // }
1749:
1750: private static final String JAVATYPE_KEY_PREFIX = "nbeditor-javaType-for-legacy-setting_"; //NOI18N
1751:
1752: private static Class typeFromString(String javaType) {
1753: try {
1754: ClassLoader classLoader = Lookup.getDefault().lookup(
1755: ClassLoader.class);
1756: return classLoader == null ? null : classLoader
1757: .loadClass(javaType);
1758: } catch (ClassNotFoundException cnfe) {
1759: LOG.log(Level.WARNING, null, cnfe);
1760: return null;
1761: }
1762: }
1763: }
|