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-2006 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:
042: package org.netbeans.modules.languages;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.util.HashMap;
049: import java.util.Map;
050: import java.util.Set;
051: import java.io.OutputStream;
052: import java.util.HashSet;
053: import java.util.List;
054:
055: import org.netbeans.api.languages.LanguageDefinitionNotFoundException;
056: import org.netbeans.api.languages.ParseException;
057: import org.netbeans.modules.languages.features.ActionCreator;
058: import org.netbeans.api.languages.LanguageDefinitionNotFoundException;
059: import org.netbeans.modules.languages.features.ColorsManager;
060: import org.netbeans.modules.languages.features.LocalizationSupport;
061: import org.openide.filesystems.FileAttributeEvent;
062: import org.openide.filesystems.FileChangeListener;
063: import org.openide.filesystems.FileEvent;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileRenameEvent;
066: import org.openide.filesystems.FileSystem;
067: import org.openide.filesystems.FileSystem.AtomicAction;
068: import org.openide.filesystems.FileUtil;
069: import org.openide.filesystems.Repository;
070: import org.openide.util.RequestProcessor;
071:
072: /**
073: *
074: * @author Jan Jancura
075: */
076: public class LanguagesManager extends
077: org.netbeans.api.languages.LanguagesManager {
078:
079: private static LanguagesManager languagesManager;
080:
081: public static LanguagesManager getDefault() {
082: if (languagesManager == null)
083: languagesManager = new LanguagesManager();
084: return languagesManager;
085: }
086:
087: public boolean isSupported(String mimeType) {
088: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
089: return fs.findResource("Editors/" + mimeType + "/language.nbs") != null;
090: }
091:
092: public boolean createDataObjectFor(String mimeType) {
093: if (!isSupported(mimeType)) {
094: return false;
095: }
096: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
097: FileObject fo = fs.findResource("Editors/" + mimeType);
098: if (fo == null)
099: return false;
100: Boolean b = (Boolean) fo.getAttribute("createDataObject");
101: if (b == null)
102: return true;
103: return b.booleanValue();
104: }
105:
106: private Language parsingLanguage = Language.create("parsing...");
107:
108: private Map<String, Language> mimeTypeToLanguage = new HashMap<String, Language>();
109:
110: // [PENDING, XXX, HACK] workaround for internal mime type set by options for coloring preview
111: public static String normalizeMimeType(String mimeType) {
112: if (mimeType.startsWith("test")) { //NOI18N
113: int idx = mimeType.indexOf('_'); //NOI18N
114: if (idx < 0)
115: return mimeType;
116: mimeType = mimeType.substring(idx + 1);
117: }
118: return mimeType;
119: }
120:
121: public synchronized Language getLanguage(String mimeType)
122: throws LanguageDefinitionNotFoundException {
123: mimeType = normalizeMimeType(mimeType);
124: if (!mimeTypeToLanguage.containsKey(mimeType)) {
125: mimeTypeToLanguage.put(mimeType, parsingLanguage);
126: FileSystem fs = Repository.getDefault()
127: .getDefaultFileSystem();
128: FileObject fo = fs.findResource("Editors/" + mimeType
129: + "/language.nbs");
130: if (fo == null) {
131: mimeTypeToLanguage.remove(mimeType);
132: throw new LanguageDefinitionNotFoundException(
133: "Language definition for " + mimeType
134: + " not found.");
135: }
136: addListener(fo);
137: try {
138: NBSLanguageReader reader = NBSLanguageReader.create(fo,
139: mimeType);
140: final LanguageImpl language = new LanguageImpl(
141: mimeType, reader);
142: language
143: .addPropertyChangeListener(new PropertyChangeListener() {
144: public void propertyChange(
145: PropertyChangeEvent evt) {
146: initLanguage(language);
147: language
148: .removePropertyChangeListener(this );
149: }
150: });
151: final String mimeType2 = mimeType;
152: RequestProcessor.getDefault().post(new Runnable() {
153: public void run() {
154: try {
155: language.read();
156: } catch (ParseException ex) {
157: Utils.message("Editors/" + mimeType2
158: + "/language.nbs: "
159: + ex.getMessage());
160: } catch (IOException ex) {
161: Utils.message("Editors/" + mimeType2
162: + "/language.nbs: "
163: + ex.getMessage());
164: }
165: }
166: }, 2000);
167: mimeTypeToLanguage.put(mimeType, language);
168: } catch (IOException ex) {
169: mimeTypeToLanguage.put(mimeType, Language
170: .create(mimeType));
171: Utils.message("Editors/" + mimeType + "/language.nbs: "
172: + ex.getMessage());
173: }
174: }
175: if (parsingLanguage == mimeTypeToLanguage.get(mimeType))
176: throw new IllegalArgumentException();
177: return mimeTypeToLanguage.get(mimeType);
178: }
179:
180: public void addLanguage(Language l) {
181: mimeTypeToLanguage.put(l.getMimeType(), l);
182: }
183:
184: // helper methods .....................................................................................................
185:
186: private void languageChanged(String mimeType) {
187: Language language = mimeTypeToLanguage.get(mimeType);
188: if (language != null && language instanceof LanguageImpl) {
189: try {
190: FileSystem fs = Repository.getDefault()
191: .getDefaultFileSystem();
192: FileObject fo = fs.findResource("Editors/" + mimeType
193: + "/language.nbs");
194: if (fo == null) {
195: mimeTypeToLanguage.remove(mimeType);
196: throw new LanguageDefinitionNotFoundException(
197: "Language definition for " + mimeType
198: + " not found.");
199: }
200: NBSLanguageReader reader = NBSLanguageReader.create(fo,
201: mimeType);
202: ((LanguageImpl) language).read(reader);
203: } catch (ParseException ex) {
204: Utils.message("Editors/" + mimeType + "/language.nbs: "
205: + ex.getMessage());
206: } catch (IOException ex) {
207: Utils.message("Editors/" + mimeType + "/language.nbs: "
208: + ex.getMessage());
209: }
210: }
211:
212: // HACK
213: ParserManagerImpl.refreshHack();
214: }
215:
216: private Set<FileObject> listeningOn = new HashSet<FileObject>();
217: private Listener listener;
218:
219: private void addListener(FileObject fo) {
220: if (!listeningOn.contains(fo)) {
221: if (listener == null)
222: listener = new Listener();
223: fo.addFileChangeListener(listener);
224: listeningOn.add(fo);
225: }
226: }
227:
228: private static void initLanguage(final Language l) {
229: try {
230: final FileSystem fs = Repository.getDefault()
231: .getDefaultFileSystem();
232: final FileObject root = fs.findResource("Editors/"
233: + l.getMimeType());
234: fs.runAtomicAction(new AtomicAction() {
235: public void run() {
236: try {
237: // init old options
238: if (root.getFileObject("Settings.settings") == null) {
239: InputStream is = getClass()
240: .getClassLoader()
241: .getResourceAsStream(
242: "org/netbeans/modules/languages/resources/LanguagesOptions.settings");
243: try {
244: FileObject fo = root
245: .createData("Settings.settings");
246: OutputStream os = fo.getOutputStream();
247: try {
248: FileUtil.copy(is, os);
249: } finally {
250: os.close();
251: }
252: } finally {
253: is.close();
254: }
255: }
256:
257: // init code folding bar
258: if (root
259: .getFileObject("SideBar/org-netbeans-modules-languages-features-CodeFoldingSideBarFactory.instance") == null
260: && l.getFeatureList().getFeatures(
261: "FOLD").size() > 0) {
262: FileUtil
263: .createData(
264: root,
265: "FoldManager/org-netbeans-modules-languages-features-LanguagesFoldManager$Factory.instance");
266: FileUtil
267: .createData(
268: root,
269: "SideBar/org-netbeans-modules-languages-features-CodeFoldingSideBarFactory.instance")
270: .
271: // Can tune position to whatever seems right; at least put after org-netbeans-editor-GlyphGutter.instance:
272: setAttribute("position", 1000);
273: }
274:
275: // init error stripe
276: if (root
277: .getFileObject("UpToDateStatusProvider/org-netbeans-modules-languages-features-UpToDateStatusProviderFactoryImpl.instance") == null
278: //l.supportsCodeFolding () does not work if you first open language without folding than no languages will have foding.
279: )
280: FileUtil
281: .createData(
282: root,
283: "UpToDateStatusProvider/org-netbeans-modules-languages-features-UpToDateStatusProviderFactoryImpl.instance");
284:
285: initPopupMenu(root, l);
286:
287: // init navigator
288: if (l.getFeatureList().getFeatures("NAVIGATOR")
289: .size() > 0) {
290: String foldFileName = "Navigator/Panels/"
291: + l.getMimeType()
292: + "/org-netbeans-modules-languages-features-LanguagesNavigator.instance";
293: if (fs.findResource(foldFileName) == null)
294: FileUtil.createData(fs.getRoot(),
295: foldFileName);
296: }
297:
298: // init tooltips
299: FileUtil
300: .createData(root,
301: "ToolTips/org-netbeans-modules-languages-features-ToolTipAnnotation.instance");
302:
303: if (l.getFeatureList().getFeature(
304: "COMMENT_LINE") != null) {
305: // init editor toolbar
306: FileObject toolbarDefault = FileUtil
307: .createFolder(root,
308: "Toolbars/Default");
309: createSeparator(toolbarDefault,
310: "Separator-before-comment", 30000 // can tune to whatever; want after stop-macro-recording
311: );
312: FileUtil.createData(toolbarDefault,
313: "comment").setAttribute("position",
314: 31000);
315: FileUtil.createData(toolbarDefault,
316: "uncomment").setAttribute(
317: "position", 32000);
318:
319: if (root
320: .getFileObject("Keybindings/NetBeans/Defaults/keybindings.xml") == null) {
321: InputStream is = getClass()
322: .getClassLoader()
323: .getResourceAsStream(
324: "org/netbeans/modules/languages/resources/DefaultKeyBindings.xml");
325: try {
326: FileObject bindings = FileUtil
327: .createData(root,
328: "Keybindings/NetBeans/Defaults/keybindings.xml");
329: OutputStream os = bindings
330: .getOutputStream();
331: try {
332: FileUtil.copy(is, os);
333: } finally {
334: os.close();
335: }
336: } finally {
337: is.close();
338: }
339: }
340: }
341: } catch (IOException ex) {
342: Utils.notify(ex);
343: }
344: }
345: });
346: } catch (IOException ex) {
347: Utils.notify(ex);
348: }
349:
350: // init coloring
351: ColorsManager.initColorings(l);
352: }
353:
354: private static void initPopupMenu(FileObject root, Language l)
355: throws IOException {
356: List<Feature> actions = l.getFeatureList()
357: .getFeatures("ACTION");
358: // Could probably use fixed anchor points if these positions settle down:
359: int selectInPos = findPositionOfDefaultPopupAction(
360: "org-netbeans-modules-editor-NbSelectInPopupAction.instance",
361: 1000);
362: int increment = (findPositionOfDefaultPopupAction(
363: "org-openide-actions-CutAction.instance", 2000) - selectInPos)
364: / (actions.size() + 3);
365: FileObject popup = FileUtil.createFolder(root, "Popup");
366: int pos = selectInPos + increment;
367: createSeparator(popup, "SeparatorAfterSelectInPopupAction", pos);
368: boolean actionAdded = false;
369: //if (l.getFeatureList ().getFeatures("SEMANTIC_USAGE").size() > 0) {
370: actionAdded = true;
371: pos += increment;
372: FileUtil
373: .createData(popup,
374: "org-netbeans-modules-languages-features-GoToDeclarationAction.instance")
375: .setAttribute("position", pos);
376: //}
377: if (l.getFeatureList().getFeatures("INDENT").size() > 0) {
378: actionAdded = true;
379: pos += increment;
380: FileUtil.createData(popup, "format").setAttribute(
381: "position", pos);
382: }
383: for (Feature action : actions) {
384: if (action.getBoolean("explorer", false))
385: continue;
386: actionAdded = true;
387: pos += increment;
388: String name = action.getSelector().getAsString();
389: String displayName = LocalizationSupport.localize(l,
390: (String) action.getValue("name"));
391: String performer = action.getMethodName("performer");
392: String enabler = action.getMethodName("enabled");
393: /* XXX disabled for now; could use numeric position key if desired:
394: String installAfter = (String) action.getValue ("install_after");
395: String installBefore = (String) action.getValue ("install_before");
396: */
397: boolean separatorBefore = action.getBoolean(
398: "separator_before", false);
399: boolean separatorAfter = action.getBoolean(
400: "separator_after", false);
401: FileObject fobj = FileUtil.createData(popup, name
402: + ".instance"); // NOI18N
403: fobj.setAttribute("instanceCreate", new ActionCreator(
404: new Object[] { displayName, performer, enabler })); // NOI18N
405: fobj
406: .setAttribute("instanceClass",
407: "org.netbeans.modules.languages.features.GenericAction"); // NOI18N
408: fobj.setAttribute("position", pos);
409: if (separatorBefore) {
410: createSeparator(popup, name + "_separator_before", pos
411: - increment / 3);
412: }
413: if (separatorAfter) {
414: createSeparator(popup, name + "_separator_after", pos
415: + increment / 3);
416: }
417: }
418: //FileUtil.createData (popup, "org-netbeans-modules-languages-features-FormatAction.instance").setAttribute("position", ...);
419: if (actionAdded) {
420: createSeparator(popup, "SeparatorBeforeCut", pos
421: + increment);
422: }
423: if (l.getFeatureList().getFeatures("FOLD").size() > 0) {
424: FileUtil
425: .createData(popup, "generate-fold-popup")
426: .setAttribute(
427: "position",
428: findPositionOfDefaultPopupAction(
429: "org-openide-actions-PasteAction.instance",
430: 3000) + 50);
431: }
432: // init actions
433: }
434:
435: private static int findPositionOfDefaultPopupAction(String name,
436: int fallback) {
437: FileObject f = Repository.getDefault().getDefaultFileSystem()
438: .findResource("Editors/Popup/" + name);
439: if (f != null) {
440: Object pos = f.getAttribute("position");
441: if (pos instanceof Integer) {
442: return (Integer) pos;
443: }
444: }
445: return fallback;
446: }
447:
448: private static void createSeparator(FileObject folder, String name,
449: int position) throws IOException {
450: FileObject separator = FileUtil.createData(folder, name
451: + ".instance");
452: separator.setAttribute("instanceClass",
453: "javax.swing.JSeparator");
454: separator.setAttribute("position", position);
455: }
456:
457: // innerclasses ............................................................
458:
459: private class Listener implements FileChangeListener {
460:
461: public void fileAttributeChanged(FileAttributeEvent fe) {
462: }
463:
464: public void fileChanged(FileEvent fe) {
465: FileObject fo = fe.getFile();
466: String mimeType = fo.getParent().getParent().getName()
467: + '/' + fo.getParent().getName();
468: languageChanged(mimeType);
469: }
470:
471: public void fileDataCreated(FileEvent fe) {
472: }
473:
474: public void fileDeleted(FileEvent fe) {
475: FileObject fo = fe.getFile();
476: String mimeType = fo.getParent().getName();
477: languageChanged(mimeType);
478: }
479:
480: public void fileFolderCreated(FileEvent fe) {
481: }
482:
483: public void fileRenamed(FileRenameEvent fe) {
484: }
485: }
486:
487: }
|