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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.modules.gsf;
041:
042: import java.io.IOException;
043: import java.util.ArrayList;
044: import java.util.Collection;
045: import java.util.HashSet;
046: import java.util.List;
047: import java.util.Set;
048: import javax.swing.text.StyledDocument;
049: import org.netbeans.modules.gsf.api.CancellableTask;
050: import org.netbeans.modules.gsf.api.Error;
051: import org.netbeans.modules.gsf.api.HintsProvider;
052: import org.netbeans.api.project.Project;
053: import org.netbeans.api.project.ProjectUtils;
054: import org.netbeans.api.project.SourceGroup;
055: import org.netbeans.api.project.Sources;
056: import org.netbeans.modules.gsf.api.ParserResult;
057: import org.netbeans.napi.gsfret.source.CompilationController;
058: import org.netbeans.napi.gsfret.source.Phase;
059: import org.netbeans.napi.gsfret.source.Source;
060: import org.netbeans.napi.gsfret.source.UiUtils;
061: import org.netbeans.spi.editor.hints.ErrorDescription;
062: import org.netbeans.spi.editor.hints.Severity;
063: import org.netbeans.spi.tasklist.PushTaskScanner;
064: import org.netbeans.spi.tasklist.Task;
065: import org.netbeans.spi.tasklist.TaskScanningScope;
066: import org.openide.filesystems.FileObject;
067: import org.openide.filesystems.FileUtil;
068: import org.openide.text.NbDocument;
069: import org.openide.util.Exceptions;
070: import org.openide.util.NbBundle;
071: import org.openide.util.RequestProcessor;
072: import org.openide.util.TaskListener;
073:
074: /**
075: * Task provider which provides tasks for the tasklist corresponding
076: * to hints in files.
077: *
078: * @todo Caching
079: * @todo Register via instanceCreate to ensure this is a singleton
080: * (Didn't work - see uncommented code below; try to fix.)
081: * @todo Exclude tasks that are not Rule#showInTaskList==true
082: *
083: * Much of this class is based on the similar JavaTaskProvider in
084: * java/source by Stanislav Aubrecht and Jan Lahoda
085: *
086: * @author Tor Norbye
087: */
088: public class GsfTaskProvider extends PushTaskScanner {
089: private TaskScanningScope scope;
090: private Callback callback;
091: // Registered by core/tasklist's layer
092: private static final String TASKLIST_ERROR = "nb-tasklist-error"; //NOI18N
093: private static final String TASKLIST_WARNING = "nb-tasklist-warning"; //NOI18N
094: private static final String TASKLIST_ERROR_HINT = "nb-tasklist-errorhint"; //NOI18N
095: private static final String TASKLIST_WARNING_HINT = "nb-tasklist-warninghint"; //NOI18N
096: private static final Set<RequestProcessor.Task> TASKS = new HashSet<RequestProcessor.Task>();
097: private static boolean clearing;
098: private static final RequestProcessor WORKER = new RequestProcessor(
099: "GSF Task Provider");
100:
101: //private static final GsfTaskProvider INSTANCE = new GsfTaskProvider(LanguageRegistry.getInstance().getLanguagesDisplayName());
102: //public static GsfTaskProvider getInstance() {
103: // return INSTANCE;
104: //}
105: //
106: // For some reason, the instanceCreate method didn't work here, so use a lame setup instead.
107: // If you set up instanceCreate again, make sure the refresh() call doesn't do work before
108: // the tasklist is open...
109: private static GsfTaskProvider INSTANCE;
110:
111: public GsfTaskProvider() {
112: this (LanguageRegistry.getInstance().getLanguagesDisplayName());
113: INSTANCE = this ;
114: }
115:
116: private GsfTaskProvider(String languageList) {
117: super (NbBundle.getMessage(GsfTaskProvider.class, "GsfTasks",
118: languageList), NbBundle.getMessage(
119: GsfTaskProvider.class, "GsfTasksDesc", languageList),
120: null);
121: }
122:
123: @Override
124: public synchronized void setScope(TaskScanningScope scope,
125: Callback callback) {
126: //cancel all current operations:
127: cancelAllCurrent();
128:
129: this .scope = scope;
130: this .callback = callback;
131:
132: if (scope == null || callback == null) {
133: return;
134: }
135:
136: for (FileObject file : scope.getLookup().lookupAll(
137: FileObject.class)) {
138: enqueue(new Work(file, callback));
139: }
140:
141: for (Project p : scope.getLookup().lookupAll(Project.class)) {
142: // TODO - find out which subgroups to use
143: for (SourceGroup sg : ProjectUtils.getSources(p)
144: .getSourceGroups(Sources.TYPE_GENERIC)) {
145: enqueue(new Work(sg.getRootFolder(), callback));
146: }
147: }
148: }
149:
150: public static void refresh(FileObject file) {
151: if (INSTANCE != null) {
152: INSTANCE.refreshImpl(file);
153: }
154: }
155:
156: private synchronized void refreshImpl(FileObject file) {
157: if (scope == null || callback == null) {
158: return; //nothing to refresh
159: }
160: if (!scope.isInScope(file)) {
161: if (!file.isFolder()) {
162: return;
163: }
164:
165: //the given file may be a parent of some file that is in the scope:
166: for (FileObject inScope : scope.getLookup().lookupAll(
167: FileObject.class)) {
168: if (FileUtil.isParentOf(file, inScope)) {
169: enqueue(new Work(inScope, callback));
170: }
171: }
172:
173: return;
174: }
175:
176: enqueue(new Work(file, callback));
177: }
178:
179: private static void enqueue(Work w) {
180: synchronized (TASKS) {
181: if (INSTANCE != null && TASKS.size() == 0) {
182: INSTANCE.callback.started();
183: }
184: final RequestProcessor.Task task = WORKER.post(w);
185:
186: TASKS.add(task);
187: task.addTaskListener(new TaskListener() {
188: public void taskFinished(org.openide.util.Task task) {
189: synchronized (TASKS) {
190: if (!clearing) {
191: TASKS.remove(task);
192: if (INSTANCE != null && TASKS.size() == 0) {
193: INSTANCE.callback.finished();
194: }
195: }
196: }
197: }
198: });
199: if (task.isFinished()) {
200: TASKS.remove(task);
201: if (INSTANCE != null && TASKS.size() == 0) {
202: INSTANCE.callback.finished();
203: }
204: }
205: }
206: }
207:
208: private static void cancelAllCurrent() {
209: synchronized (TASKS) {
210: clearing = true;
211: try {
212: for (RequestProcessor.Task t : TASKS) {
213: t.cancel();
214: }
215: TASKS.clear();
216: } finally {
217: clearing = false;
218: }
219: }
220: }
221:
222: private static final class Work implements Runnable {
223: private FileObject fileOrRoot;
224: private Callback callback;
225:
226: public Work(FileObject fileOrRoot, Callback callback) {
227: this .fileOrRoot = fileOrRoot;
228: this .callback = callback;
229: }
230:
231: public FileObject getFileOrRoot() {
232: return fileOrRoot;
233: }
234:
235: public Callback getCallback() {
236: return callback;
237: }
238:
239: public void run() {
240: FileObject file = getFileOrRoot();
241: refreshFile(file);
242: }
243:
244: private void refreshFile(final FileObject file) {
245: if (file.isFolder()) {
246: // HACK Bypass all the libraries in Rails projects
247: // TODO FIXME The hints providers need to pass in relevant directories
248: if (file.getName().equals("vendor")
249: && file.getParent().getFileObject("nbproject") != null) { // NOI18N
250: return;
251: }
252: for (FileObject child : file.getChildren()) {
253: refreshFile(child);
254: }
255: return;
256: }
257: final LanguageRegistry registry = LanguageRegistry
258: .getInstance();
259: final List<Language> applicableLanguages = registry
260: .getApplicableLanguages(file.getMIMEType());
261: boolean applicable = false;
262: for (Language language : applicableLanguages) {
263: HintsProvider provider = language.getHintsProvider();
264: if (provider != null) {
265: applicable = true;
266: }
267: }
268: if (!applicable) {
269: // No point compiling the file if there are no hintsproviders
270: return;
271: }
272:
273: final List<ErrorDescription> result = new ArrayList<ErrorDescription>();
274:
275: Source source = Source.forFileObject(file);
276: if (source == null) {
277: return;
278: }
279:
280: final List<Task> tasks = new ArrayList<Task>();
281:
282: CancellableTask<CompilationController> runner = new CancellableTask<CompilationController>() {
283: public void cancel() {
284: }
285:
286: public void run(CompilationController info)
287: throws Exception {
288: // Ensure document is forced open
289: UiUtils.getDocument(info.getFileObject(), true);
290:
291: info.toPhase(Phase.RESOLVED);
292:
293: for (String mimeType : info.getEmbeddedMimeTypes()) {
294: Collection<? extends ParserResult> embeddedResults = info
295: .getEmbeddedResults(mimeType);
296: for (ParserResult parserResult : embeddedResults) {
297: Language language = registry
298: .getLanguageByMimeType(mimeType);
299: HintsProvider provider = language
300: .getHintsProvider();
301: if (provider == null) {
302: continue;
303: }
304:
305: List<Error> errors = provider
306: .computeErrors(info, result);
307: provider.computeHints(info, result);
308: for (Error error : errors) {
309: try {
310: int astOffset = error
311: .getStartPosition();
312: int lexOffset;
313: if (parserResult
314: .getTranslatedSource() != null) {
315: lexOffset = parserResult
316: .getTranslatedSource()
317: .getLexicalOffset(
318: astOffset);
319: if (lexOffset == -1) {
320: continue;
321: }
322: } else {
323: lexOffset = astOffset;
324: }
325:
326: int lineno = NbDocument
327: .findLineNumber(
328: (StyledDocument) info
329: .getDocument(),
330: lexOffset) + 1;
331: Task task = Task
332: .create(
333: file,
334: error.getSeverity() == org.netbeans.modules.gsf.api.Severity.ERROR ? TASKLIST_ERROR
335: : TASKLIST_WARNING,
336: error
337: .getDisplayName(),
338: lineno);
339: tasks.add(task);
340: } catch (IOException ioe) {
341: Exceptions.printStackTrace(ioe);
342: }
343: }
344: }
345: }
346: }
347: };
348:
349: try {
350: source.runUserActionTask(runner, true);
351: } catch (IOException ex) {
352: Exceptions.printStackTrace(ex);
353: }
354:
355: for (final ErrorDescription hint : result) {
356: try {
357: Task task = Task
358: .create(file, severityToTaskListString(hint
359: .getSeverity()), hint
360: .getDescription(), hint.getRange()
361: .getBegin().getLine() + 1);
362: tasks.add(task);
363: } catch (IOException ioe) {
364: Exceptions.printStackTrace(ioe);
365: }
366: }
367:
368: callback.setTasks(file, tasks);
369: }
370: }
371:
372: private static String severityToTaskListString(Severity severity) {
373: return (severity == Severity.ERROR) ? TASKLIST_ERROR_HINT
374: : TASKLIST_WARNING_HINT;
375: }
376: }
|