001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.editor.lib;
042:
043: import java.beans.PropertyChangeListener;
044: import java.beans.PropertyChangeSupport;
045: import java.lang.reflect.Field;
046: import java.util.Collection;
047: import java.util.Collections;
048: import java.util.HashMap;
049: import java.util.List;
050: import java.util.Map;
051: import java.util.WeakHashMap;
052: import java.util.logging.Logger;
053: import javax.swing.text.AttributeSet;
054: import javax.swing.text.EditorKit;
055: import org.netbeans.api.editor.mimelookup.MimeLookup;
056: import org.netbeans.api.editor.mimelookup.MimePath;
057: import org.netbeans.api.editor.settings.FontColorNames;
058: import org.netbeans.api.editor.settings.FontColorSettings;
059: import org.netbeans.api.lexer.Language;
060: import org.netbeans.api.lexer.TokenId;
061: import org.netbeans.editor.Coloring;
062: import org.netbeans.editor.SettingsNames;
063: import org.netbeans.editor.SettingsUtil;
064: import org.netbeans.editor.TokenCategory;
065: import org.netbeans.editor.TokenContext;
066: import org.netbeans.editor.TokenContextPath;
067: import org.netbeans.editor.TokenID;
068: import org.openide.util.Lookup;
069: import org.openide.util.LookupEvent;
070: import org.openide.util.LookupListener;
071: import org.openide.util.WeakListeners;
072:
073: /**
074: *
075: * @author Vita Stejskal
076: */
077: public final class ColoringMap {
078:
079: private static final Logger LOG = Logger
080: .getLogger(ColoringMap.class.getName());
081:
082: public static final String PROP_COLORING_MAP = "ColoringMap.PROP_COLORING_MAP"; //NOI18N
083:
084: public static ColoringMap get(String mimeType) {
085: if (!IN_GET.get()) {
086: IN_GET.set(true);
087: try {
088: return getInternal(mimeType);
089: } finally {
090: IN_GET.set(false);
091: }
092: } else {
093: return EMPTY;
094: }
095: }
096:
097: public Map<String, Coloring> getMap() {
098: synchronized (LOCK) {
099: if (map == null) {
100: map = loadTheMap(legacyNonTokenColoringNames,
101: lexerLanguage, syntaxLanguages, lookupResult
102: .allInstances());
103: }
104:
105: return map;
106: }
107: }
108:
109: public void addPropertyChangeListener(PropertyChangeListener l) {
110: PCS.addPropertyChangeListener(l);
111: }
112:
113: public void removePropertyChangeListener(PropertyChangeListener l) {
114: PCS.removePropertyChangeListener(l);
115: }
116:
117: // ---------------------------------------------------------
118: // Private implementation
119: // ---------------------------------------------------------
120:
121: private static final Map<MimePath, ColoringMap> CACHE = new WeakHashMap<MimePath, ColoringMap>();
122: private static final ColoringMap EMPTY = new ColoringMap();
123: private static final ThreadLocal<Boolean> IN_GET = new ThreadLocal<Boolean>() {
124: @Override
125: protected Boolean initialValue() {
126: return Boolean.FALSE;
127: }
128: };
129:
130: private final List<String> legacyNonTokenColoringNames;
131: private final Language<?> lexerLanguage;
132: private final List<? extends TokenContext> syntaxLanguages;
133: private final Lookup.Result<FontColorSettings> lookupResult;
134: private final LookupListener lookupListener = new LookupListener() {
135: public void resultChanged(LookupEvent ev) {
136: synchronized (LOCK) {
137: map = null;
138: }
139:
140: PCS.firePropertyChange(PROP_COLORING_MAP, null, null);
141: }
142: };
143:
144: private final PropertyChangeSupport PCS = new PropertyChangeSupport(
145: this );
146:
147: private final String LOCK = new String("ColoringMap.LOCK"); //NOI18N
148:
149: private Map<String, Coloring> map = null;
150:
151: private ColoringMap() {
152: this .legacyNonTokenColoringNames = null;
153: this .lexerLanguage = null;
154: this .syntaxLanguages = null;
155: this .lookupResult = null;
156: this .map = Collections.<String, Coloring> emptyMap();
157: }
158:
159: private ColoringMap(List<String> legacyNonTokenColoringNames,
160: Language<?> lexerLanguage,
161: List<? extends TokenContext> syntaxLanguages,
162: Lookup.Result<FontColorSettings> lookupResult) {
163: this .legacyNonTokenColoringNames = legacyNonTokenColoringNames;
164: this .lexerLanguage = lexerLanguage;
165: this .syntaxLanguages = syntaxLanguages;
166: this .lookupResult = lookupResult;
167:
168: this .map = loadTheMap(legacyNonTokenColoringNames,
169: lexerLanguage, syntaxLanguages, lookupResult
170: .allInstances());
171:
172: this .lookupResult.addLookupListener(WeakListeners
173: .create(LookupListener.class, lookupListener,
174: this .lookupResult));
175: }
176:
177: private static ColoringMap getInternal(String mimeType) {
178: MimePath mimePath = mimeType == null || mimeType.length() == 0 ? MimePath.EMPTY
179: : MimePath.parse(mimeType);
180:
181: synchronized (CACHE) {
182: ColoringMap cm = CACHE.get(mimePath);
183:
184: if (cm != null) {
185: return cm;
186: }
187: }
188:
189: List<String> legacyNonTokenColoringNames = findLegacyNonTokenColoringNames(mimePath);
190: Lookup.Result<FontColorSettings> lookupResult = MimeLookup
191: .getLookup(mimePath).lookupResult(
192: FontColorSettings.class);
193:
194: Language<?> lexerLanguage = null;
195: List<? extends TokenContext> syntaxLanguage = null;
196:
197: if (mimePath.size() > 0) {
198: lexerLanguage = Language.find(mimePath.getPath());
199: syntaxLanguage = findSyntaxLanguage(mimePath);
200: }
201:
202: LOG.fine("Creating ColoringMap for '" + mimeType
203: + "' ---------------------------"); //NOI18N
204: ColoringMap myCm = new ColoringMap(legacyNonTokenColoringNames,
205: lexerLanguage, syntaxLanguage, lookupResult);
206: LOG
207: .fine("----------------------------------------------------------------------"); //NOI18N
208:
209: synchronized (CACHE) {
210: ColoringMap cm = CACHE.get(mimePath);
211:
212: if (cm == null) {
213: cm = myCm;
214: CACHE.put(mimePath, cm);
215: }
216:
217: return cm;
218: }
219: }
220:
221: private static Map<String, Coloring> loadTheMap(
222: List<String> legacyNonTokenColoringNames,
223: Language<?> lexerLanguage,
224: List<? extends TokenContext> syntaxLanguages,
225: Collection<? extends FontColorSettings> fontsColors) {
226: HashMap<String, Coloring> coloringMap = new HashMap<String, Coloring>();
227:
228: if (!fontsColors.isEmpty()) {
229: FontColorSettings fcs = fontsColors.iterator().next();
230:
231: if (legacyNonTokenColoringNames != null) {
232: collectLegacyNonTokenColorings(coloringMap,
233: legacyNonTokenColoringNames, fcs);
234: }
235:
236: collectNonTokenColorings(coloringMap, fcs);
237:
238: if (syntaxLanguages != null) {
239: collectLegacyTokenColorings(coloringMap,
240: syntaxLanguages, fcs);
241: }
242:
243: if (lexerLanguage != null) {
244: collectTokenColorings(coloringMap, lexerLanguage, fcs);
245: }
246: }
247:
248: return Collections.unmodifiableMap(coloringMap);
249: }
250:
251: private static void collectNonTokenColorings(
252: HashMap<String, Coloring> coloringMap, FontColorSettings fcs) {
253: // Introspect the fields in FontColorNames class
254: for (Field field : FontColorNames.class.getDeclaredFields()) {
255: Object fieldValue = null;
256:
257: try {
258: fieldValue = field.get(null);
259: } catch (IllegalAccessException e) {
260: // ignore
261: }
262:
263: if (fieldValue instanceof String) {
264: String coloringName = (String) fieldValue;
265: AttributeSet attribs = fcs.getFontColors(coloringName);
266: if (attribs != null) {
267: LOG.fine("Loading coloring '" + coloringName + "'"); //NOI18N
268: coloringMap.put(coloringName, Coloring
269: .fromAttributeSet(attribs));
270: }
271: }
272: }
273: }
274:
275: private static void collectLegacyNonTokenColorings(
276: HashMap<String, Coloring> coloringMap,
277: List<String> legacyNonTokenColoringNames,
278: FontColorSettings fcs) {
279: for (int i = legacyNonTokenColoringNames.size() - 1; i >= 0; i--) {
280: String coloringName = legacyNonTokenColoringNames.get(i);
281: AttributeSet attribs = fcs.getFontColors(coloringName);
282: if (attribs != null) {
283: LOG.fine("Loading legacy coloring '" + coloringName
284: + "'"); //NOI18N
285: coloringMap.put(coloringName, Coloring
286: .fromAttributeSet(attribs));
287: }
288: }
289: }
290:
291: private static void collectTokenColorings(
292: HashMap<String, Coloring> coloringMap,
293: Language<?> lexerLanguage, FontColorSettings fcs) {
294: // Add token-categories colorings
295: for (String category : lexerLanguage.tokenCategories()) {
296: AttributeSet attribs = fcs.getTokenFontColors(category);
297: if (attribs != null) {
298: LOG.fine("Loading token coloring '" + category + "'"); //NOI18N
299: coloringMap.put(category, Coloring
300: .fromAttributeSet(attribs));
301: }
302: }
303:
304: // Add token-ids colorings
305: for (TokenId tokenId : lexerLanguage.tokenIds()) {
306: AttributeSet attribs = fcs.getTokenFontColors(tokenId
307: .name());
308: if (attribs != null) {
309: LOG.fine("Loading token coloring '" + tokenId.name()
310: + "'"); //NOI18N
311: coloringMap.put(tokenId.name(), Coloring
312: .fromAttributeSet(attribs));
313: }
314: }
315: }
316:
317: private static void collectLegacyTokenColorings(
318: HashMap<String, Coloring> coloringMap,
319: List<? extends TokenContext> tokenContextList,
320: FontColorSettings fcs) {
321: for (int i = tokenContextList.size() - 1; i >= 0; i--) {
322: TokenContext tc = tokenContextList.get(i);
323: TokenContextPath[] allPaths = tc.getAllContextPaths();
324: for (int j = 0; j < allPaths.length; j++) {
325: TokenContext firstContext = allPaths[j].getContexts()[0];
326:
327: // Add token-categories colorings
328: TokenCategory[] tokenCategories = firstContext
329: .getTokenCategories();
330: for (int k = 0; k < tokenCategories.length; k++) {
331: String fullName = allPaths[j]
332: .getFullTokenName(tokenCategories[k]);
333: AttributeSet attribs = fcs
334: .getTokenFontColors(fullName);
335: if (attribs != null) {
336: LOG.fine("Loading legacy token coloring '"
337: + fullName + "'"); //NOI18N
338: coloringMap.put(fullName, Coloring
339: .fromAttributeSet(attribs));
340: }
341: }
342:
343: // Add token-ids colorings
344: TokenID[] tokenIDs = firstContext.getTokenIDs();
345: for (int k = 0; k < tokenIDs.length; k++) {
346: String fullName = allPaths[j]
347: .getFullTokenName(tokenIDs[k]);
348: AttributeSet attribs = fcs
349: .getTokenFontColors(fullName);
350: if (attribs != null) {
351: LOG.fine("Loading legacy token coloring '"
352: + fullName + "'"); //NOI18N
353: coloringMap.put(fullName, Coloring
354: .fromAttributeSet(attribs));
355: }
356: }
357: }
358: }
359: }
360:
361: private static List<String> findLegacyNonTokenColoringNames(
362: MimePath mimePath) {
363: EditorKit kit = MimeLookup.getLookup(mimePath).lookup(
364: EditorKit.class);
365: List<String> legacyNonTokenColoringNames = null;
366:
367: if (kit != null) {
368: List list = SettingsUtil.getCumulativeList(kit.getClass(),
369: SettingsNames.COLORING_NAME_LIST, null);
370: legacyNonTokenColoringNames = list;
371: }
372:
373: return legacyNonTokenColoringNames;
374: }
375:
376: private static List<? extends TokenContext> findSyntaxLanguage(
377: MimePath mimePath) {
378: EditorKit kit = MimeLookup.getLookup(mimePath).lookup(
379: EditorKit.class);
380: List<? extends TokenContext> languages = null;
381:
382: if (kit != null) {
383: List tcl = SettingsUtil.getList(kit.getClass(),
384: SettingsNames.TOKEN_CONTEXT_LIST, null);
385: languages = tcl;
386: }
387:
388: return languages;
389: }
390: }
|