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:
042: package org.netbeans.modules.java.hints.infrastructure;
043:
044: import com.sun.source.tree.MethodInvocationTree;
045: import com.sun.source.tree.NewClassTree;
046: import com.sun.source.tree.Tree;
047: import com.sun.source.tree.Tree.Kind;
048: import java.io.IOException;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.Collections;
052: import java.util.HashMap;
053: import java.util.List;
054: import java.util.Map;
055: import javax.lang.model.element.PackageElement;
056: import javax.lang.model.type.TypeKind;
057: import javax.swing.text.Document;
058: import javax.swing.text.StyledDocument;
059: import javax.tools.Diagnostic;
060: import org.netbeans.api.java.source.CancellableTask;
061: import org.netbeans.spi.editor.hints.Fix;
062: import org.netbeans.spi.editor.hints.LazyFixList;
063: import org.netbeans.spi.editor.hints.Severity;
064: import org.openide.ErrorManager;
065: import org.openide.filesystems.FileObject;
066: import org.openide.loaders.DataObject;
067: import org.openide.text.Line;
068: import com.sun.source.util.TreePath;
069: import java.util.EnumMap;
070: import java.util.EnumSet;
071: import java.util.HashSet;
072: import java.util.Set;
073: import java.util.logging.Level;
074: import java.util.logging.Logger;
075: import javax.lang.model.element.Element;
076: import javax.lang.model.type.TypeMirror;
077: import javax.swing.text.BadLocationException;
078: import javax.swing.text.Position;
079: import javax.swing.text.Position.Bias;
080: import org.netbeans.api.java.lexer.JavaTokenId;
081: import org.netbeans.api.java.source.CompilationInfo;
082: import org.netbeans.api.lexer.Token;
083: import org.netbeans.api.lexer.TokenHierarchy;
084: import org.netbeans.api.lexer.TokenSequence;
085: import org.netbeans.modules.editor.java.Utilities;
086: import org.netbeans.modules.java.hints.spi.ErrorRule;
087: import org.netbeans.modules.java.hints.spi.ErrorRule.Data;
088: import org.netbeans.spi.editor.hints.ErrorDescription;
089: import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
090: import org.netbeans.spi.editor.hints.HintsController;
091: import org.openide.cookies.LineCookie;
092: import org.openide.text.NbDocument;
093:
094: /**
095: * @author Jan Lahoda
096: * @author leon chiver
097: */
098: public final class ErrorHintsProvider implements
099: CancellableTask<CompilationInfo> {
100:
101: public static ErrorManager ERR = ErrorManager.getDefault()
102: .getInstance("org.netbeans.modules.java.hints"); // NOI18N
103: public static Logger LOG = Logger
104: .getLogger("org.netbeans.modules.java.hints"); // NOI18N
105:
106: private FileObject file;
107:
108: /** Creates a new instance of JavaHintsProvider */
109: ErrorHintsProvider(FileObject file) {
110: this .file = file;
111: }
112:
113: private static final Map<Diagnostic.Kind, Severity> errorKind2Severity;
114:
115: static {
116: errorKind2Severity = new EnumMap<Diagnostic.Kind, Severity>(
117: Diagnostic.Kind.class);
118: errorKind2Severity.put(Diagnostic.Kind.ERROR, Severity.ERROR);
119: errorKind2Severity.put(Diagnostic.Kind.MANDATORY_WARNING,
120: Severity.WARNING);
121: errorKind2Severity.put(Diagnostic.Kind.WARNING,
122: Severity.WARNING);
123: errorKind2Severity.put(Diagnostic.Kind.NOTE, Severity.WARNING);
124: errorKind2Severity.put(Diagnostic.Kind.OTHER, Severity.WARNING);
125: }
126:
127: List<ErrorDescription> computeErrors(CompilationInfo info,
128: Document doc) throws IOException {
129: List<Diagnostic> errors = info.getDiagnostics();
130: List<ErrorDescription> descs = new ArrayList<ErrorDescription>();
131:
132: if (ERR.isLoggable(ErrorManager.INFORMATIONAL))
133: ERR.log(ErrorManager.INFORMATIONAL, "errors = " + errors);
134:
135: Map<Class, Data> data = new HashMap<Class, Data>();
136:
137: for (Diagnostic d : errors) {
138: if (isCanceled())
139: return null;
140:
141: if (ERR.isLoggable(ErrorManager.INFORMATIONAL))
142: ERR.log(ErrorManager.INFORMATIONAL, "d = " + d);
143:
144: Map<String, List<ErrorRule>> code2Rules = RulesManager
145: .getInstance().getErrors();
146:
147: List<ErrorRule> rules = code2Rules.get(d.getCode());
148:
149: if (ERR.isLoggable(ErrorManager.INFORMATIONAL)) {
150: ERR.log(ErrorManager.INFORMATIONAL, "code= "
151: + d.getCode());
152: ERR.log(ErrorManager.INFORMATIONAL, "rules = " + rules);
153: }
154:
155: LazyFixList ehm;
156:
157: if (rules != null) {
158: int pos = (int) getPrefferedPosition(info, d);
159:
160: pos = info.getPositionConverter().getOriginalPosition(
161: pos);
162:
163: ehm = new CreatorBasedLazyFixList(info.getFileObject(),
164: d.getCode(), pos, rules, data);
165: } else {
166: ehm = ErrorDescriptionFactory
167: .lazyListForFixes(Collections.<Fix> emptyList());
168: }
169:
170: if (ERR.isLoggable(ErrorManager.INFORMATIONAL))
171: ERR.log(ErrorManager.INFORMATIONAL, "ehm=" + ehm);
172:
173: final String desc = d.getMessage(null);
174: final Position[] range = getLine(info, d, doc, (int) d
175: .getStartPosition(), (int) d.getEndPosition());
176:
177: if (isCanceled())
178: return null;
179:
180: if (range[0] == null || range[1] == null)
181: continue;
182:
183: descs.add(ErrorDescriptionFactory.createErrorDescription(
184: errorKind2Severity.get(d.getKind()), desc, ehm,
185: doc, range[0], range[1]));
186: }
187:
188: if (isCanceled())
189: return null;
190:
191: LazyHintComputationFactory.getAndClearToCompute(file);
192:
193: return descs;
194: }
195:
196: public static Token findUnresolvedElementToken(
197: CompilationInfo info, int offset) throws IOException {
198: TokenHierarchy<?> th = info.getTokenHierarchy();
199: TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId
200: .language());
201:
202: if (ts == null) {
203: return null;
204: }
205:
206: ts.move(offset);
207: if (ts.moveNext()) {
208: Token t = ts.token();
209:
210: if (t.id() == JavaTokenId.DOT) {
211: ts.moveNext();
212: t = ts.token();
213: } else {
214: if (t.id() == JavaTokenId.LT) {
215: ts.moveNext();
216: t = ts.token();
217: } else {
218: if (t.id() == JavaTokenId.NEW) {
219: boolean cont = ts.moveNext();
220:
221: while (cont
222: && ts.token().id() == JavaTokenId.WHITESPACE) {
223: cont = ts.moveNext();
224: }
225:
226: if (!cont)
227: return null;
228:
229: t = ts.token();
230: }
231: }
232: }
233:
234: if (t.id() == JavaTokenId.IDENTIFIER) {
235: return ts.offsetToken();
236: }
237: }
238: return null;
239: }
240:
241: private static int[] findUnresolvedElementSpan(
242: CompilationInfo info, int offset) throws IOException {
243: Token t = findUnresolvedElementToken(info, offset);
244:
245: if (t != null) {
246: return new int[] { t.offset(null),
247: t.offset(null) + t.length() };
248: }
249:
250: return null;
251: }
252:
253: public static TreePath findUnresolvedElement(CompilationInfo info,
254: int offset) throws IOException {
255: int[] span = findUnresolvedElementSpan(info, offset);
256:
257: if (span != null) {
258: return info.getTreeUtilities().pathFor(span[0] + 1);
259: } else {
260: return null;
261: }
262: }
263:
264: private static final Set<String> INVALID_METHOD_INVOCATION = new HashSet<String>(
265: Arrays.asList("compiler.err.prob.found.req",
266: "compiler.err.cant.apply.symbol",
267: "compiler.err.cant.resolve.location"));
268:
269: private static final Set<String> CANNOT_RESOLVE = new HashSet<String>(
270: Arrays.asList("compiler.err.cant.resolve",
271: "compiler.err.cant.resolve.location",
272: "compiler.err.doesnt.exist"));
273:
274: private static final Set<String> UNDERLINE_IDENTIFIER = new HashSet<String>(
275: Arrays
276: .asList(
277: "compiler.err.local.var.accessed.from.icls.needs.final",
278: "compiler.err.var.might.not.have.been.initialized",
279: "compiler.err.report.access"));
280:
281: private static final Set<JavaTokenId> WHITESPACE = EnumSet.of(
282: JavaTokenId.BLOCK_COMMENT, JavaTokenId.JAVADOC_COMMENT,
283: JavaTokenId.LINE_COMMENT, JavaTokenId.WHITESPACE);
284:
285: private int[] handlePossibleMethodInvocation(CompilationInfo info,
286: Diagnostic d, final Document doc, int startOffset,
287: int endOffset) throws IOException {
288: int pos = (int) getPrefferedPosition(info, d);
289: TreePath tp = info.getTreeUtilities().pathFor(pos + 1);
290:
291: if (tp != null
292: && tp.getParentPath() != null
293: && tp.getParentPath().getLeaf() != null
294: && (tp.getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION || tp
295: .getParentPath().getLeaf().getKind() == Kind.NEW_CLASS)) {
296: int[] index = new int[1];
297:
298: tp = tp.getParentPath();
299:
300: if (Utilities.fuzzyResolveMethodInvocation(info, tp,
301: new TypeMirror[1], index) != null) {
302: Tree a;
303:
304: if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
305: MethodInvocationTree mit = (MethodInvocationTree) tp
306: .getLeaf();
307:
308: a = mit.getArguments().get(index[0]);
309: } else {
310: NewClassTree mit = (NewClassTree) tp.getLeaf();
311:
312: a = mit.getArguments().get(index[0]);
313: }
314:
315: return new int[] {
316: (int) info.getTrees().getSourcePositions()
317: .getStartPosition(
318: info.getCompilationUnit(), a),
319: (int) info.getTrees().getSourcePositions()
320: .getEndPosition(
321: info.getCompilationUnit(), a) };
322: }
323: }
324:
325: return null;
326: }
327:
328: private Position[] getLine(CompilationInfo info, Diagnostic d,
329: final Document doc, int startOffset, int endOffset)
330: throws IOException {
331: StyledDocument sdoc = (StyledDocument) doc;
332: DataObject dObj = (DataObject) doc
333: .getProperty(doc.StreamDescriptionProperty);
334: LineCookie lc = dObj.getCookie(LineCookie.class);
335: int lineNumber = NbDocument.findLineNumber(sdoc, info
336: .getPositionConverter()
337: .getOriginalPosition(startOffset));
338: int lineOffset = NbDocument.findLineOffset(sdoc, lineNumber);
339: Line line = lc.getLineSet().getCurrent(lineNumber);
340:
341: boolean rangePrepared = false;
342:
343: if (INVALID_METHOD_INVOCATION.contains(d.getCode())) {
344: int[] span = handlePossibleMethodInvocation(info, d, doc,
345: startOffset, endOffset);
346:
347: if (span != null) {
348: startOffset = span[0];
349: endOffset = span[1];
350: rangePrepared = true;
351: }
352: }
353:
354: if (CANNOT_RESOLVE.contains(d.getCode()) && !rangePrepared) {
355: int[] span = translatePositions(info,
356: findUnresolvedElementSpan(info,
357: (int) getPrefferedPosition(info, d)));
358:
359: if (span != null) {
360: startOffset = span[0];
361: endOffset = span[1];
362: rangePrepared = true;
363: }
364: }
365:
366: if (UNDERLINE_IDENTIFIER.contains(d.getCode())) {
367: int offset = (int) getPrefferedPosition(info, d);
368: TokenSequence<JavaTokenId> ts = info.getTokenHierarchy()
369: .tokenSequence(JavaTokenId.language());
370:
371: int diff = ts.move(offset);
372:
373: if (ts.moveNext() && diff >= 0
374: && diff < ts.token().length()) {
375: Token<JavaTokenId> t = ts.token();
376:
377: if (t.id() == JavaTokenId.DOT) {
378: while (ts.moveNext()
379: && WHITESPACE.contains(ts.token().id()))
380: ;
381: t = ts.token();
382: }
383:
384: if (t.id() == JavaTokenId.NEW) {
385: while (ts.moveNext()
386: && WHITESPACE.contains(ts.token().id()))
387: ;
388: t = ts.token();
389: }
390:
391: if (t.id() == JavaTokenId.IDENTIFIER) {
392: int[] span = translatePositions(info, new int[] {
393: ts.offset(), ts.offset() + t.length() });
394:
395: if (span != null) {
396: startOffset = span[0];
397: endOffset = span[1];
398: rangePrepared = true;
399: }
400: }
401: }
402: }
403:
404: if (!rangePrepared) {
405: String text = line.getText();
406:
407: if (text == null) {
408: //#116560, (according to the javadoc, means the document is closed):
409: cancel();
410: return null;
411: }
412:
413: int column = 0;
414: int length = text.length();
415:
416: while (column < text.length()
417: && Character.isWhitespace(text.charAt(column)))
418: column++;
419:
420: while (length > 0
421: && Character.isWhitespace(text.charAt(length - 1)))
422: length--;
423:
424: startOffset = lineOffset + column;
425: endOffset = lineOffset + length;
426: }
427:
428: if (ERR.isLoggable(ErrorManager.INFORMATIONAL)) {
429: ERR.log(ErrorManager.INFORMATIONAL, "startOffset = "
430: + startOffset);
431: ERR.log(ErrorManager.INFORMATIONAL, "endOffset = "
432: + endOffset);
433: }
434:
435: final int startOffsetFinal = startOffset;
436: final int endOffsetFinal = endOffset;
437: final Position[] result = new Position[2];
438:
439: doc.render(new Runnable() {
440: public void run() {
441: if (isCanceled())
442: return;
443:
444: int len = doc.getLength();
445:
446: if (startOffsetFinal >= len || endOffsetFinal > len) {
447: if (!isCanceled()
448: && ERR.isLoggable(ErrorManager.WARNING)) {
449: ERR.log(ErrorManager.WARNING,
450: "document changed, but not canceled?");
451: ERR.log(ErrorManager.WARNING, "len = " + len);
452: ERR.log(ErrorManager.WARNING, "startOffset = "
453: + startOffsetFinal);
454: ERR.log(ErrorManager.WARNING, "endOffset = "
455: + endOffsetFinal);
456: }
457: cancel();
458:
459: return;
460: }
461:
462: try {
463: result[0] = NbDocument.createPosition(doc,
464: startOffsetFinal, Bias.Forward);
465: result[1] = NbDocument.createPosition(doc,
466: endOffsetFinal, Bias.Backward);
467: } catch (BadLocationException e) {
468: ERR.notify(ErrorManager.ERROR, e);
469: }
470: }
471: });
472:
473: return result;
474: }
475:
476: private boolean cancel;
477:
478: synchronized boolean isCanceled() {
479: return cancel;
480: }
481:
482: public synchronized void cancel() {
483: cancel = true;
484: }
485:
486: synchronized void resume() {
487: cancel = false;
488: }
489:
490: public void run(CompilationInfo info) throws IOException {
491: resume();
492:
493: Document doc = info.getDocument();
494:
495: if (doc == null) {
496: Logger.getLogger(ErrorHintsProvider.class.getName()).log(
497: Level.FINE,
498: "SemanticHighlighter: Cannot get document!");
499: return;
500: }
501:
502: long start = System.currentTimeMillis();
503:
504: List<ErrorDescription> errors = computeErrors(info, doc);
505:
506: if (errors == null) //meaning: cancelled
507: return;
508:
509: HintsController.setErrors(doc, "java-hints", errors);
510:
511: long end = System.currentTimeMillis();
512:
513: Logger.getLogger("TIMER").log(Level.FINE, "Java Hints",
514: new Object[] { info.getFileObject(), end - start });
515: }
516:
517: private int[] translatePositions(CompilationInfo info, int[] span) {
518: if (span == null || span[0] == (-1) || span[1] == (-1))
519: return null;
520:
521: int start = info.getPositionConverter().getOriginalPosition(
522: span[0]);
523: int end = info.getPositionConverter().getOriginalPosition(
524: span[1]);
525:
526: if (start == (-1) || end == (-1))
527: return null;
528:
529: return new int[] { start, end };
530: }
531:
532: private long getPrefferedPosition(CompilationInfo info, Diagnostic d)
533: throws IOException {
534: if ("compiler.err.doesnt.exist".equals(d.getCode())) {
535: return d.getStartPosition();
536: }
537: if ("compiler.err.cant.resolve.location".equals(d.getCode())) {
538: int[] span = findUnresolvedElementSpan(info, (int) d
539: .getPosition());
540:
541: if (span != null) {
542: return span[0];
543: } else {
544: return d.getPosition();
545: }
546: }
547: if ("compiler.err.not.stmt".equals(d.getCode())) {
548: //check for "Collections.":
549: TreePath path = findUnresolvedElement(info, (int) d
550: .getStartPosition() - 1);
551: Element el = path != null ? info.getTrees()
552: .getElement(path) : null;
553:
554: if (el == null || el.asType().getKind() == TypeKind.ERROR) {
555: return d.getStartPosition() - 1;
556: }
557:
558: if (el.asType().getKind() == TypeKind.PACKAGE) {
559: //check if the package does actually exist:
560: String s = ((PackageElement) el).getQualifiedName()
561: .toString();
562: if (info.getElements().getPackageElement(s) == null) {
563: //it does not:
564: return d.getStartPosition() - 1;
565: }
566: }
567:
568: return d.getStartPosition();
569: }
570:
571: return d.getPosition();
572: }
573:
574: }
|