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.javascript.hints.infrastructure;
043:
044: import org.netbeans.modules.javascript.hints.spi.HintSeverity;
045: import java.io.IOException;
046: import java.util.ArrayList;
047: import java.util.Collections;
048: import java.util.Enumeration;
049: import java.util.HashMap;
050: import java.util.LinkedList;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.Map.Entry;
054: import java.util.Set;
055: import java.util.logging.Level;
056: import java.util.logging.Logger;
057: import java.util.prefs.Preferences;
058: import javax.swing.tree.DefaultMutableTreeNode;
059: import javax.swing.tree.DefaultTreeModel;
060: import javax.swing.tree.TreeModel;
061: import org.netbeans.modules.gsf.api.CompilationInfo;
062: import org.netbeans.modules.javascript.hints.StrictWarning;
063: import org.netbeans.modules.javascript.hints.options.HintsSettings;
064: import org.openide.cookies.InstanceCookie;
065: import org.openide.filesystems.FileObject;
066: import org.openide.filesystems.FileSystem;
067: import org.openide.filesystems.Repository;
068: import org.openide.loaders.DataObject;
069: import org.netbeans.modules.javascript.hints.spi.ErrorRule;
070: import org.netbeans.modules.javascript.hints.spi.AstRule;
071: import org.netbeans.modules.javascript.hints.spi.Rule;
072: import org.netbeans.modules.javascript.hints.spi.SelectionRule;
073: import org.netbeans.modules.javascript.hints.spi.UserConfigurableRule;
074: import org.openide.util.NbPreferences;
075:
076: /**
077: * Manages rules read from the system filesystem.
078: *
079: * (Copied from java/hints)
080: *
081: * @author Petr Hrebejk
082: */
083: public class RulesManager {
084:
085: // The logger
086: public static final Logger LOG = Logger
087: .getLogger("org.netbeans.modules.javascript.hints"); // NOI18N
088:
089: // Extensions of files
090: private static final String INSTANCE_EXT = ".instance";
091:
092: // Non GUI attribute for NON GUI rules
093: private static final String NON_GUI = "nonGUI"; // NOI18N
094:
095: private static final String RULES_FOLDER = "org-netbeans-modules-javascript-hints/rules/"; // NOI18N
096: private static final String ERRORS = "errors"; // NOI18N
097: private static final String HINTS = "hints"; // NOI18N
098: private static final String SUGGESTIONS = "suggestions"; // NOI18N
099: private static final String SELECTION = "selection"; // NOI18N
100:
101: // Maps of registered rules
102: private static Map<String, List<ErrorRule>> errors = new HashMap<String, List<ErrorRule>>();
103: private static Map<Integer, List<AstRule>> hints = new HashMap<Integer, List<AstRule>>();
104: private static Map<Integer, List<AstRule>> suggestions = new HashMap<Integer, List<AstRule>>();
105: private static List<SelectionRule> selectionHints = new ArrayList<SelectionRule>();
106:
107: // Tree models for the settings GUI
108: private static TreeModel errorsTreeModel;
109: private static TreeModel hintsTreeModel;
110: private static TreeModel suggestionsTreeModel;
111:
112: private static RulesManager INSTANCE;
113:
114: private RulesManager() {
115: // XXX Start listening on the rules forder. To handle module set changes.
116: initErrors();
117: initHints();
118: initSuggestions();
119: initSelectionHints();
120: }
121:
122: public static synchronized RulesManager getInstance() {
123: if (INSTANCE == null) {
124: INSTANCE = new RulesManager();
125: }
126: return INSTANCE;
127: }
128:
129: // public boolean isEnabled(Rule rule) {
130: // return HintsSettings.isEnabled(rule, getPreferences(rule, HintsSettings.getCurrentProfileId()));
131: // }
132:
133: public HintSeverity getSeverity(UserConfigurableRule rule) {
134: return HintsSettings.getSeverity(rule, getPreferences(rule,
135: HintsSettings.getCurrentProfileId()));
136: }
137:
138: /** Gets preferences node, which stores the options for given hint. It is not
139: * necessary to override this method unless you want to create some special
140: * behavior. The default implementation will create the the preferences node
141: * by calling <code>NbPreferences.forModule(this.getClass()).node(profile).node(getId());</code>
142: * @profile Profile to get the node for. May be null for current profile
143: * @return Preferences node for given hint.
144: */
145: public Preferences getPreferences(UserConfigurableRule rule,
146: String profile) {
147: profile = profile == null ? HintsSettings.getCurrentProfileId()
148: : profile;
149: return NbPreferences.forModule(this .getClass()).node(profile)
150: .node(rule.getId());
151: }
152:
153: public Map<String, List<ErrorRule>> getErrors() {
154: return errors;
155: }
156:
157: public Map<Integer, List<AstRule>> getHints() {
158: return hints;
159: }
160:
161: public List<SelectionRule> getSelectionHints() {
162: return selectionHints;
163: }
164:
165: public Map<Integer, List<AstRule>> getHints(boolean onLine,
166: CompilationInfo info) {
167: Map<Integer, List<AstRule>> result = new HashMap<Integer, List<AstRule>>();
168:
169: for (Entry<Integer, List<AstRule>> e : getHints().entrySet()) {
170: List<AstRule> nueRules = new LinkedList<AstRule>();
171:
172: for (AstRule r : e.getValue()) {
173: Preferences p = getPreferences(r, null);
174:
175: if (p == null) {
176: if (!onLine) {
177: if (!r.appliesTo(info)) {
178: continue;
179: }
180: nueRules.add(r);
181: }
182: continue;
183: }
184:
185: if (getSeverity(r) == HintSeverity.CURRENT_LINE_WARNING) {
186: if (onLine) {
187: if (!r.appliesTo(info)) {
188: continue;
189: }
190: nueRules.add(r);
191: }
192: } else {
193: if (!onLine) {
194: if (!r.appliesTo(info)) {
195: continue;
196: }
197: nueRules.add(r);
198: }
199: }
200: }
201:
202: if (!nueRules.isEmpty()) {
203: result.put(e.getKey(), nueRules);
204: }
205: }
206:
207: return result;
208: }
209:
210: public Map<Integer, List<AstRule>> getSuggestions() {
211: return suggestions;
212: }
213:
214: public TreeModel getErrorsTreeModel() {
215: return errorsTreeModel;
216: }
217:
218: public TreeModel getHintsTreeModel() {
219: return hintsTreeModel;
220: }
221:
222: public TreeModel getSuggestionsTreeModel() {
223: return suggestionsTreeModel;
224: }
225:
226: // Private methods ---------------------------------------------------------
227:
228: /** Public only for testing infrastructure. Others should not touch!! */
229: public static final String[] KNOWN_STRICT_ERROR_KEYS = new String[] {
230: "msg.bad.octal.literal", // NOI18N
231: "msg.reserved.keyword", // NOI18N
232: "msg.dup.parms", // NOI18N
233: "msg.return.inconsistent", // NOI18N
234: "msg.no.return.value", // NOI18N
235: "msg.anon.no.return.value", // NOI18N
236: // Handled by separate hint implementation, AccidentalAssignment
237: //"msg.equal.as.assign", // NOI18N
238: "msg.var.hides.arg", // NOI18N
239: "msg.var.redecl", // NOI18N
240: "msg.no.side.effects" // NOI18N
241: };
242:
243: private static void initErrors() {
244: DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
245: errorsTreeModel = new DefaultTreeModel(rootNode);
246: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
247: FileObject folder = fs.getRoot().getFileObject(
248: RULES_FOLDER + ERRORS);
249: List<Pair<Rule, FileObject>> rules = readRules(folder);
250:
251: categorizeErrorRules(rules, errors, folder, rootNode);
252:
253: // Add builtin wrappers for strict warnings
254: for (String key : KNOWN_STRICT_ERROR_KEYS) {
255: StrictWarning rule = new StrictWarning(key);
256: if ("msg.reserved.keyword".equals(key)) {
257: rule.setDefaultSeverity(HintSeverity.ERROR);
258: }
259: errors
260: .put(key, Collections
261: .<ErrorRule> singletonList(rule));
262: }
263: }
264:
265: private static void initHints() {
266: DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
267: hintsTreeModel = new DefaultTreeModel(rootNode);
268: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
269: FileObject folder = fs.getRoot().getFileObject(
270: RULES_FOLDER + HINTS);
271: List<Pair<Rule, FileObject>> rules = readRules(folder);
272: categorizeAstRules(rules, hints, folder, rootNode);
273: }
274:
275: private static void initSuggestions() {
276: DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
277: suggestionsTreeModel = new DefaultTreeModel(rootNode);
278: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
279: FileObject folder = fs.getRoot().getFileObject(
280: RULES_FOLDER + SUGGESTIONS);
281: List<Pair<Rule, FileObject>> rules = readRules(folder);
282: categorizeAstRules(rules, suggestions, folder, rootNode);
283: }
284:
285: private static void initSelectionHints() {
286: DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
287: suggestionsTreeModel = new DefaultTreeModel(rootNode);
288: FileSystem fs = Repository.getDefault().getDefaultFileSystem();
289: FileObject folder = fs.getRoot().getFileObject(
290: RULES_FOLDER + SELECTION);
291: List<Pair<Rule, FileObject>> rules = readRules(folder);
292: categorizeSelectionRules(rules, selectionHints, folder,
293: rootNode);
294: }
295:
296: /** Read rules from system filesystem */
297: private static List<Pair<Rule, FileObject>> readRules(
298: FileObject folder) {
299:
300: List<Pair<Rule, FileObject>> rules = new LinkedList<Pair<Rule, FileObject>>();
301:
302: if (folder == null) {
303: return rules;
304: }
305:
306: //HashMap<FileObject,DefaultMutableTreeNode> dir2node = new HashMap<FileObject,DefaultMutableTreeNode>();
307:
308: // XXX Probably not he best order
309: Enumeration e = folder.getData(true);
310: while (e.hasMoreElements()) {
311: FileObject o = (FileObject) e.nextElement();
312: String name = o.getNameExt().toLowerCase();
313:
314: if (o.canRead()) {
315: Rule r = null;
316: if (name.endsWith(INSTANCE_EXT)) {
317: r = instantiateRule(o);
318: }
319: if (r != null) {
320: rules.add(new Pair<Rule, FileObject>(r, o));
321: }
322: }
323: }
324: return rules;
325: }
326:
327: private static void categorizeErrorRules(
328: List<Pair<Rule, FileObject>> rules,
329: Map<String, List<ErrorRule>> dest, FileObject rootFolder,
330: DefaultMutableTreeNode rootNode) {
331:
332: Map<FileObject, DefaultMutableTreeNode> dir2node = new HashMap<FileObject, DefaultMutableTreeNode>();
333: dir2node.put(rootFolder, rootNode);
334:
335: for (Pair<Rule, FileObject> pair : rules) {
336: Rule rule = pair.getA();
337: FileObject fo = pair.getB();
338:
339: if (rule instanceof ErrorRule) {
340: addRule((ErrorRule) rule, dest);
341: FileObject parent = fo.getParent();
342: DefaultMutableTreeNode category = dir2node.get(parent);
343: if (category == null) {
344: category = new DefaultMutableTreeNode(parent);
345: rootNode.add(category);
346: dir2node.put(parent, category);
347: }
348: category.add(new DefaultMutableTreeNode(rule, false));
349: } else {
350: LOG
351: .log(Level.WARNING, "The rule defined in "
352: + fo.getPath()
353: + "is not instance of ErrorRule");
354: }
355: }
356: }
357:
358: private static void categorizeAstRules(
359: List<Pair<Rule, FileObject>> rules,
360: Map<Integer, List<AstRule>> dest, FileObject rootFolder,
361: DefaultMutableTreeNode rootNode) {
362:
363: Map<FileObject, DefaultMutableTreeNode> dir2node = new HashMap<FileObject, DefaultMutableTreeNode>();
364: dir2node.put(rootFolder, rootNode);
365:
366: for (Pair<Rule, FileObject> pair : rules) {
367: Rule rule = pair.getA();
368: FileObject fo = pair.getB();
369:
370: if (rule instanceof AstRule) {
371:
372: Object nonGuiObject = fo.getAttribute(NON_GUI);
373: boolean toGui = true;
374:
375: if (nonGuiObject != null
376: && nonGuiObject instanceof Boolean
377: && ((Boolean) nonGuiObject).booleanValue()) {
378: toGui = false;
379: }
380:
381: addRule((AstRule) rule, dest);
382: FileObject parent = fo.getParent();
383: DefaultMutableTreeNode category = dir2node.get(parent);
384: if (category == null) {
385: category = new DefaultMutableTreeNode(parent);
386: rootNode.add(category);
387: dir2node.put(parent, category);
388: }
389: if (toGui) {
390: category
391: .add(new DefaultMutableTreeNode(rule, false));
392: }
393: } else {
394: LOG.log(Level.WARNING, "The rule defined in "
395: + fo.getPath() + "is not instance of AstRule");
396: }
397:
398: }
399: }
400:
401: private static void categorizeSelectionRules(
402: List<Pair<Rule, FileObject>> rules,
403: List<SelectionRule> dest, FileObject rootFolder,
404: DefaultMutableTreeNode rootNode) {
405: Map<FileObject, DefaultMutableTreeNode> dir2node = new HashMap<FileObject, DefaultMutableTreeNode>();
406: dir2node.put(rootFolder, rootNode);
407:
408: for (Pair<Rule, FileObject> pair : rules) {
409: Rule rule = pair.getA();
410: FileObject fo = pair.getB();
411:
412: if (rule instanceof SelectionRule) {
413: addRule((SelectionRule) rule, dest);
414: FileObject parent = fo.getParent();
415: DefaultMutableTreeNode category = dir2node.get(parent);
416: if (category == null) {
417: category = new DefaultMutableTreeNode(parent);
418: rootNode.add(category);
419: dir2node.put(parent, category);
420: }
421: category.add(new DefaultMutableTreeNode(rule, false));
422: } else {
423: LOG.log(Level.WARNING, "The rule defined in "
424: + fo.getPath()
425: + "is not instance of SelectionRule");
426: }
427: }
428: }
429:
430: private static void addRule(AstRule rule,
431: Map<Integer, List<AstRule>> dest) {
432:
433: for (Integer kind : rule.getKinds()) {
434: List<AstRule> l = dest.get(kind);
435: if (l == null) {
436: l = new LinkedList<AstRule>();
437: dest.put(kind, l);
438: }
439: l.add(rule);
440: }
441: }
442:
443: @SuppressWarnings("unchecked")
444: private static void addRule(ErrorRule rule,
445: Map<String, List<ErrorRule>> dest) {
446:
447: for (String code : (Set<String>) rule.getCodes()) {
448: List<ErrorRule> l = dest.get(code);
449: if (l == null) {
450: l = new LinkedList<ErrorRule>();
451: dest.put(code, l);
452: }
453: l.add(rule);
454: }
455: }
456:
457: @SuppressWarnings("unchecked")
458: private static void addRule(SelectionRule rule,
459: List<SelectionRule> dest) {
460: dest.add(rule);
461: }
462:
463: private static Rule instantiateRule(FileObject fileObject) {
464: try {
465: DataObject dobj = DataObject.find(fileObject);
466: InstanceCookie ic = dobj.getCookie(InstanceCookie.class);
467: Object instance = ic.instanceCreate();
468:
469: if (instance instanceof Rule) {
470: return (Rule) instance;
471: } else {
472: return null;
473: }
474: } catch (IOException e) {
475: LOG.log(Level.INFO, null, e);
476: } catch (ClassNotFoundException e) {
477: LOG.log(Level.INFO, null, e);
478: }
479:
480: return null;
481: }
482: }
|