• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.lint;
17 
18 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
19 import static com.android.ide.eclipse.adt.AdtConstants.MARKER_LINT;
20 
21 import com.android.annotations.NonNull;
22 import com.android.annotations.Nullable;
23 import com.android.ide.eclipse.adt.AdtPlugin;
24 import com.android.ide.eclipse.adt.AdtUtils;
25 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
26 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
27 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
28 import com.android.tools.lint.checks.BuiltinIssueRegistry;
29 import com.android.tools.lint.client.api.Configuration;
30 import com.android.tools.lint.client.api.IDomParser;
31 import com.android.tools.lint.client.api.IJavaParser;
32 import com.android.tools.lint.client.api.IssueRegistry;
33 import com.android.tools.lint.client.api.LintClient;
34 import com.android.tools.lint.detector.api.Context;
35 import com.android.tools.lint.detector.api.DefaultPosition;
36 import com.android.tools.lint.detector.api.Detector;
37 import com.android.tools.lint.detector.api.Issue;
38 import com.android.tools.lint.detector.api.JavaContext;
39 import com.android.tools.lint.detector.api.LintUtils;
40 import com.android.tools.lint.detector.api.Location;
41 import com.android.tools.lint.detector.api.Location.Handle;
42 import com.android.tools.lint.detector.api.Position;
43 import com.android.tools.lint.detector.api.Project;
44 import com.android.tools.lint.detector.api.Severity;
45 import com.android.tools.lint.detector.api.XmlContext;
46 import com.android.util.Pair;
47 
48 import org.eclipse.core.resources.IFile;
49 import org.eclipse.core.resources.IMarker;
50 import org.eclipse.core.resources.IProject;
51 import org.eclipse.core.resources.IResource;
52 import org.eclipse.core.runtime.CoreException;
53 import org.eclipse.core.runtime.IStatus;
54 import org.eclipse.jdt.core.IJavaProject;
55 import org.eclipse.jdt.core.compiler.CategorizedProblem;
56 import org.eclipse.jdt.internal.compiler.CompilationResult;
57 import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
58 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
59 import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
60 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
61 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
62 import org.eclipse.jdt.internal.compiler.parser.Parser;
63 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
64 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
65 import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
66 import org.eclipse.jface.text.BadLocationException;
67 import org.eclipse.jface.text.IDocument;
68 import org.eclipse.jface.text.IRegion;
69 import org.eclipse.swt.widgets.Shell;
70 import org.eclipse.ui.IEditorPart;
71 import org.eclipse.ui.PartInitException;
72 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
73 import org.eclipse.ui.ide.IDE;
74 import org.eclipse.ui.texteditor.IDocumentProvider;
75 import org.eclipse.wst.sse.core.StructuredModelManager;
76 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
77 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
78 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
79 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
80 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
81 import org.w3c.dom.Document;
82 import org.w3c.dom.Node;
83 
84 import java.io.File;
85 import java.io.IOException;
86 import java.util.Collections;
87 import java.util.List;
88 
89 import lombok.ast.ecj.EcjTreeConverter;
90 import lombok.ast.grammar.ParseProblem;
91 import lombok.ast.grammar.Source;
92 
93 /**
94  * Eclipse implementation for running lint on workspace files and projects.
95  */
96 @SuppressWarnings("restriction") // DOM model
97 public class EclipseLintClient extends LintClient implements IDomParser {
98     static final String MARKER_CHECKID_PROPERTY = "checkid";    //$NON-NLS-1$
99     private static final String MODEL_PROPERTY = "model";       //$NON-NLS-1$
100     private final List<? extends IResource> mResources;
101     private final IDocument mDocument;
102     private boolean mWasFatal;
103     private boolean mFatalOnly;
104     private EclipseJavaParser mJavaParser;
105 
106     /**
107      * Creates a new {@link EclipseLintClient}.
108      *
109      * @param registry the associated detector registry
110      * @param resources the associated resources (project, file or null)
111      * @param document the associated document, or null if the {@code resource}
112      *            param is not a file
113      * @param fatalOnly whether only fatal issues should be reported (and therefore checked)
114      */
EclipseLintClient(IssueRegistry registry, List<? extends IResource> resources, IDocument document, boolean fatalOnly)115     public EclipseLintClient(IssueRegistry registry, List<? extends IResource> resources,
116             IDocument document, boolean fatalOnly) {
117         mResources = resources;
118         mDocument = document;
119         mFatalOnly = fatalOnly;
120     }
121 
122     // ----- Extends LintClient -----
123 
124     @Override
log(Severity severity, Throwable exception, String format, Object... args)125     public void log(Severity severity, Throwable exception, String format, Object... args) {
126         if (exception == null) {
127             AdtPlugin.log(IStatus.WARNING, format, args);
128         } else {
129             AdtPlugin.log(exception, format, args);
130         }
131     }
132 
133     @Override
getDomParser()134     public IDomParser getDomParser() {
135         return this;
136     }
137 
138     @Override
getJavaParser()139     public IJavaParser getJavaParser() {
140         if (mJavaParser == null) {
141             mJavaParser = new EclipseJavaParser();
142         }
143 
144         return mJavaParser;
145     }
146 
147     // ----- Implements IDomParser -----
148 
149     @Override
parseXml(XmlContext context)150     public Document parseXml(XmlContext context) {
151         // Map File to IFile
152         IFile file = AdtUtils.fileToIFile(context.file);
153         if (file == null || !file.exists()) {
154             String path = context.file.getPath();
155             AdtPlugin.log(IStatus.ERROR, "Can't find file %1$s in workspace", path);
156             return null;
157         }
158 
159         IStructuredModel model = null;
160         try {
161             IModelManager modelManager = StructuredModelManager.getModelManager();
162             model = modelManager.getModelForRead(file);
163             if (model instanceof IDOMModel) {
164                 context.setProperty(MODEL_PROPERTY, model);
165                 IDOMModel domModel = (IDOMModel) model;
166                 return domModel.getDocument();
167             }
168         } catch (IOException e) {
169             AdtPlugin.log(e, "Cannot read XML file");
170         } catch (CoreException e) {
171             AdtPlugin.log(e, null);
172         }
173 
174         return null;
175     }
176 
getProject(Project project)177     private IProject getProject(Project project) {
178         if (mResources != null) {
179             if (mResources.size() == 1) {
180                 return mResources.get(0).getProject();
181             }
182 
183             for (IResource resource : mResources) {
184                 IProject p = resource.getProject();
185                 if (project.getDir().equals(AdtUtils.getAbsolutePath(p).toFile())) {
186                     return p;
187                 }
188             }
189         }
190         return null;
191     }
192 
193     @Override
getConfiguration(Project project)194     public Configuration getConfiguration(Project project) {
195         if (project != null) {
196             IProject eclipseProject = getProject(project);
197             if (eclipseProject != null) {
198                 return ProjectLintConfiguration.get(this, eclipseProject, mFatalOnly);
199             }
200         }
201 
202         return GlobalLintConfiguration.get();
203     }
204 
205     @Override
report(Context context, Issue issue, Severity s, Location location, String message, Object data)206     public void report(Context context, Issue issue, Severity s, Location location,
207             String message, Object data) {
208         int severity = getMarkerSeverity(s);
209         IMarker marker = null;
210         if (location != null) {
211             Position startPosition = location.getStart();
212             if (startPosition == null) {
213                 if (location.getFile() != null) {
214                     IResource resource = AdtUtils.fileToResource(location.getFile());
215                     if (resource != null && resource.isAccessible()) {
216                         marker = BaseProjectHelper.markResource(resource, MARKER_LINT,
217                                 message, 0, severity);
218                     }
219                 }
220             } else {
221                 Position endPosition = location.getEnd();
222                 int line = startPosition.getLine() + 1; // Marker API is 1-based
223                 IFile file = AdtUtils.fileToIFile(location.getFile());
224                 if (file != null && file.isAccessible()) {
225                     Pair<Integer, Integer> r = getRange(file, mDocument,
226                             startPosition, endPosition);
227                     int startOffset = r.getFirst();
228                     int endOffset = r.getSecond();
229                     marker = BaseProjectHelper.markResource(file, MARKER_LINT,
230                             message, line, startOffset, endOffset, severity);
231                 }
232             }
233         }
234 
235         if (marker == null) {
236             marker = BaseProjectHelper.markResource(mResources.get(0), MARKER_LINT,
237                         message, 0, severity);
238         }
239 
240         if (marker != null) {
241             // Store marker id such that we can recognize it from the suppress quickfix
242             try {
243                 marker.setAttribute(MARKER_CHECKID_PROPERTY, issue.getId());
244             } catch (CoreException e) {
245                 AdtPlugin.log(e, null);
246             }
247         }
248 
249         if (s == Severity.FATAL) {
250             mWasFatal = true;
251         }
252     }
253 
254     @Override
255     @Nullable
findResource(@onNull String relativePath)256     public File findResource(@NonNull String relativePath) {
257         // Look within the $ANDROID_SDK
258         String sdkFolder = AdtPrefs.getPrefs().getOsSdkFolder();
259         if (sdkFolder != null) {
260             File file = new File(sdkFolder, relativePath);
261             if (file.exists()) {
262                 return file;
263             }
264         }
265 
266         return null;
267     }
268 
269     /** Clears any lint markers from the given resource (project, folder or file) */
clearMarkers(IResource resource)270     static void clearMarkers(IResource resource) {
271         clearMarkers(Collections.singletonList(resource));
272     }
273 
274     /** Clears any lint markers from the given list of resource (project, folder or file) */
clearMarkers(List<? extends IResource> resources)275     static void clearMarkers(List<? extends IResource> resources) {
276         for (IResource resource : resources) {
277             try {
278                 if (resource.isAccessible()) {
279                     resource.deleteMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
280                 }
281             } catch (CoreException e) {
282                 AdtPlugin.log(e, null);
283             }
284         }
285 
286         LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(AdtUtils.getActiveEditor());
287         if (delegate != null) {
288             delegate.getGraphicalEditor().getLayoutActionBar().updateErrorIndicator();
289         }
290     }
291 
292     /**
293      * Removes all markers of the given id from the given resource.
294      *
295      * @param resource the resource to remove markers from (file or project, or
296      *            null for all open projects)
297      * @param id the id for the issue whose markers should be deleted
298      */
removeMarkers(IResource resource, String id)299     public static void removeMarkers(IResource resource, String id) {
300         if (resource == null) {
301             IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null);
302             for (IJavaProject project : androidProjects) {
303                 IProject p = project.getProject();
304                 if (p != null) {
305                     // Recurse, but with a different parameter so it will not continue recursing
306                     removeMarkers(p, id);
307                 }
308             }
309             return;
310         }
311         IMarker[] markers = getMarkers(resource);
312         for (IMarker marker : markers) {
313             if (id.equals(getId(marker))) {
314                 try {
315                     marker.delete();
316                 } catch (CoreException e) {
317                     AdtPlugin.log(e, null);
318                 }
319             }
320         }
321     }
322 
323     /**
324      * Returns whether the given resource has one or more lint markers
325      *
326      * @param resource the resource to be checked, typically a source file
327      * @return true if the given resource has one or more lint markers
328      */
hasMarkers(IResource resource)329     public static boolean hasMarkers(IResource resource) {
330         return getMarkers(resource).length > 0;
331     }
332 
333     /**
334      * Returns the lint marker for the given resource (which may be a project, folder or file)
335      *
336      * @param resource the resource to be checked, typically a source file
337      * @return an array of markers, possibly empty but never null
338      */
getMarkers(IResource resource)339     public static IMarker[] getMarkers(IResource resource) {
340         try {
341             if (resource.isAccessible()) {
342                 return resource.findMarkers(MARKER_LINT, false, IResource.DEPTH_INFINITE);
343             }
344         } catch (CoreException e) {
345             AdtPlugin.log(e, null);
346         }
347 
348         return new IMarker[0];
349     }
350 
getMarkerSeverity(Severity severity)351     private static int getMarkerSeverity(Severity severity) {
352         switch (severity) {
353             case INFORMATIONAL:
354                 return IMarker.SEVERITY_INFO;
355             case WARNING:
356                 return IMarker.SEVERITY_WARNING;
357             case FATAL:
358             case ERROR:
359             default:
360                 return IMarker.SEVERITY_ERROR;
361         }
362     }
363 
getRange(IFile file, IDocument doc, Position startPosition, Position endPosition)364     private static Pair<Integer, Integer> getRange(IFile file, IDocument doc,
365             Position startPosition, Position endPosition) {
366         int startOffset = startPosition.getOffset();
367         int endOffset = endPosition != null ? endPosition.getOffset() : -1;
368         if (endOffset != -1) {
369             // Attribute ranges often include trailing whitespace; trim this up
370             if (doc == null) {
371                 IDocumentProvider provider = new TextFileDocumentProvider();
372                 try {
373                     provider.connect(file);
374                     doc = provider.getDocument(file);
375                     if (doc != null) {
376                         return adjustOffsets(doc, startOffset, endOffset);
377                     }
378                 } catch (Exception e) {
379                     AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
380                 } finally {
381                     provider.disconnect(file);
382                 }
383             } else {
384                 return adjustOffsets(doc, startOffset, endOffset);
385             }
386         }
387 
388         return Pair.of(startOffset, startOffset);
389     }
390 
391     /**
392      * Trim off any trailing space on the given offset range in the given
393      * document, and don't span multiple lines on ranges since it makes (for
394      * example) the XML editor just glow with yellow underlines for all the
395      * attributes etc. Highlighting just the element beginning gets the point
396      * across. It also makes it more obvious where there are warnings on both
397      * the overall element and on individual attributes since without this the
398      * warnings on attributes would just overlap with the whole-element
399      * highlighting.
400      */
adjustOffsets(IDocument doc, int startOffset, int endOffset)401     private static Pair<Integer, Integer> adjustOffsets(IDocument doc, int startOffset,
402             int endOffset) {
403         if (doc != null) {
404             while (endOffset > startOffset && endOffset < doc.getLength()) {
405                 try {
406                     if (!Character.isWhitespace(doc.getChar(endOffset - 1))) {
407                         break;
408                     } else {
409                         endOffset--;
410                     }
411                 } catch (BadLocationException e) {
412                     // Pass - we've already validated offset range above
413                     break;
414                 }
415             }
416 
417             // Also don't span lines
418             int lineEnd = startOffset;
419             while (lineEnd < endOffset) {
420                 try {
421                     char c = doc.getChar(lineEnd);
422                     if (c == '\n' || c == '\r') {
423                         endOffset = lineEnd;
424                         break;
425                     }
426                 } catch (BadLocationException e) {
427                     // Pass - we've already validated offset range above
428                     break;
429                 }
430                 lineEnd++;
431             }
432         }
433 
434         return Pair.of(startOffset, endOffset);
435     }
436 
437     /**
438      * Returns true if a fatal error was encountered
439      *
440      * @return true if a fatal error was encountered
441      */
hasFatalErrors()442     public boolean hasFatalErrors() {
443         return mWasFatal;
444     }
445 
446     /**
447      * Describe the issue for the given marker
448      *
449      * @param marker the marker to look up
450      * @return a full description of the corresponding issue, never null
451      */
describe(IMarker marker)452     public static String describe(IMarker marker) {
453         IssueRegistry registry = getRegistry();
454         String markerId = getId(marker);
455         Issue issue = registry.getIssue(markerId);
456         if (issue == null) {
457             return "";
458         }
459 
460         String summary = issue.getDescription();
461         String explanation = issue.getExplanation();
462 
463         StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
464         try {
465             sb.append((String) marker.getAttribute(IMarker.MESSAGE));
466             sb.append('\n').append('\n');
467         } catch (CoreException e) {
468         }
469         sb.append("Issue: ");
470         sb.append(summary);
471         sb.append('\n');
472         sb.append("Id: ");
473         sb.append(issue.getId());
474         sb.append('\n').append('\n');
475         sb.append(explanation);
476 
477         if (issue.getMoreInfo() != null) {
478             sb.append('\n').append('\n');
479             sb.append(issue.getMoreInfo());
480         }
481 
482         return sb.toString();
483     }
484 
485     /**
486      * Returns the id for the given marker
487      *
488      * @param marker the marker to look up
489      * @return the corresponding issue id, or null
490      */
getId(IMarker marker)491     public static String getId(IMarker marker) {
492         try {
493             return (String) marker.getAttribute(MARKER_CHECKID_PROPERTY);
494         } catch (CoreException e) {
495             return null;
496         }
497     }
498 
499     /**
500      * Shows the given marker in the editor
501      *
502      * @param marker the marker to be shown
503      */
showMarker(IMarker marker)504     public static void showMarker(IMarker marker) {
505         IRegion region = null;
506         try {
507             int start = marker.getAttribute(IMarker.CHAR_START, -1);
508             int end = marker.getAttribute(IMarker.CHAR_END, -1);
509             if (start >= 0 && end >= 0) {
510                 region = new org.eclipse.jface.text.Region(start, end - start);
511             }
512 
513             IResource resource = marker.getResource();
514             if (resource instanceof IFile) {
515                 IEditorPart editor =
516                         AdtPlugin.openFile((IFile) resource, region, true /* showEditorTab */);
517                 if (editor != null) {
518                     IDE.gotoMarker(editor, marker);
519                 }
520             }
521         } catch (PartInitException ex) {
522             AdtPlugin.log(ex, null);
523         }
524     }
525 
526     /**
527      * Show a dialog with errors for the given file
528      *
529      * @param shell the parent shell to attach the dialog to
530      * @param file the file to show the errors for
531      */
showErrors(Shell shell, final IFile file)532     public static void showErrors(Shell shell, final IFile file) {
533         LintListDialog dialog = new LintListDialog(shell, file);
534         dialog.open();
535     }
536 
537     @Override
readFile(File f)538     public String readFile(File f) {
539         // Map File to IFile
540         IFile file = AdtUtils.fileToIFile(f);
541         if (file == null || !file.exists()) {
542             String path = f.getPath();
543             AdtPlugin.log(IStatus.ERROR, "Can't find file %1$s in workspace", path);
544             return readPlainFile(f);
545         }
546 
547         if (AdtUtils.endsWithIgnoreCase(file.getName(), DOT_XML)) {
548             IStructuredModel model = null;
549             try {
550                 IModelManager modelManager = StructuredModelManager.getModelManager();
551                 model = modelManager.getModelForRead(file);
552                 return model.getStructuredDocument().get();
553             } catch (IOException e) {
554                 AdtPlugin.log(e, "Cannot read XML file");
555             } catch (CoreException e) {
556                 AdtPlugin.log(e, null);
557             } finally {
558                 if (model != null) {
559                     // TODO: This may be too early...
560                     model.releaseFromRead();
561                 }
562             }
563         }
564 
565         return readPlainFile(f);
566     }
567 
readPlainFile(File file)568     private String readPlainFile(File file) {
569         try {
570             return LintUtils.getEncodedString(file);
571         } catch (IOException e) {
572             return ""; //$NON-NLS-1$
573         }
574     }
575 
576     @Override
getLocation(XmlContext context, Node node)577     public Location getLocation(XmlContext context, Node node) {
578         IStructuredModel model = (IStructuredModel) context.getProperty(MODEL_PROPERTY);
579         return new LazyLocation(context.file, model.getStructuredDocument(), (IndexedRegion) node);
580     }
581 
582     @Override
createLocationHandle(final XmlContext context, final Node node)583     public Handle createLocationHandle(final XmlContext context, final Node node) {
584         IStructuredModel model = (IStructuredModel) context.getProperty(MODEL_PROPERTY);
585         return new LazyLocation(context.file, model.getStructuredDocument(), (IndexedRegion) node);
586     }
587 
588     /**
589      * Returns the registry of issues to check from within Eclipse.
590      *
591      * @return the issue registry to use to access detectors and issues
592      */
getRegistry()593     public static IssueRegistry getRegistry() {
594         return new BuiltinIssueRegistry();
595     }
596 
597     @Override
replaceDetector(Class<? extends Detector> detectorClass)598     public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) {
599         return detectorClass;
600     }
601 
602     @Override
dispose(XmlContext context, Document document)603     public void dispose(XmlContext context, Document document) {
604         IStructuredModel model = (IStructuredModel) context.getProperty(MODEL_PROPERTY);
605         assert model != null : context.file;
606         if (model != null) {
607             model.releaseFromRead();
608         }
609     }
610 
611     private static class LazyLocation extends Location implements Location.Handle {
612         private final IStructuredDocument mDocument;
613         private final IndexedRegion mRegion;
614         private Position mStart;
615         private Position mEnd;
616 
LazyLocation(File file, IStructuredDocument document, IndexedRegion region)617         public LazyLocation(File file, IStructuredDocument document, IndexedRegion region) {
618             super(file, null /*start*/, null /*end*/);
619             mDocument = document;
620             mRegion = region;
621         }
622 
623         @Override
getStart()624         public Position getStart() {
625             if (mStart == null) {
626                 int line = -1;
627                 int column = -1;
628                 int offset = mRegion.getStartOffset();
629 
630                 if (mRegion instanceof org.w3c.dom.Text && mDocument != null) {
631                     // For text nodes, skip whitespace prefix, if any
632                     for (int i = offset;
633                             i < mRegion.getEndOffset() && i < mDocument.getLength(); i++) {
634                         try {
635                             char c = mDocument.getChar(i);
636                             if (!Character.isWhitespace(c)) {
637                                 offset = i;
638                                 break;
639                             }
640                         } catch (BadLocationException e) {
641                             break;
642                         }
643                     }
644                 }
645 
646                 if (mDocument != null && offset < mDocument.getLength()) {
647                     line = mDocument.getLineOfOffset(offset);
648                     column = -1;
649                     try {
650                         int lineOffset = mDocument.getLineOffset(line);
651                         column = offset - lineOffset;
652                     } catch (BadLocationException e) {
653                         AdtPlugin.log(e, null);
654                     }
655                 }
656 
657                 mStart = new DefaultPosition(line, column, offset);
658             }
659 
660             return mStart;
661         }
662 
663         @Override
getEnd()664         public Position getEnd() {
665             if (mEnd == null) {
666                 mEnd = new DefaultPosition(-1, -1, mRegion.getEndOffset());
667             }
668 
669             return mEnd;
670         }
671 
672         @Override
resolve()673         public Location resolve() {
674             return this;
675         }
676     }
677 
678     private static class EclipseJavaParser implements IJavaParser {
679         private static final boolean USE_ECLIPSE_PARSER = true;
680         private final Parser mParser;
681 
EclipseJavaParser()682         EclipseJavaParser() {
683             if (USE_ECLIPSE_PARSER) {
684                 CompilerOptions options = new CompilerOptions();
685                 // Read settings from project? Note that this doesn't really matter because
686                 // we will only be parsing, not actually compiling.
687                 options.complianceLevel = ClassFileConstants.JDK1_6;
688                 options.sourceLevel = ClassFileConstants.JDK1_6;
689                 options.targetJDK = ClassFileConstants.JDK1_6;
690                 options.parseLiteralExpressionsAsConstants = true;
691                 ProblemReporter problemReporter = new ProblemReporter(
692                         DefaultErrorHandlingPolicies.exitOnFirstError(),
693                         options,
694                         new DefaultProblemFactory());
695                 mParser = new Parser(problemReporter, options.parseLiteralExpressionsAsConstants);
696                 mParser.javadocParser.checkDocComment = false;
697             } else {
698                 mParser = null;
699             }
700         }
701 
702         @Override
parseJava(JavaContext context)703         public lombok.ast.Node parseJava(JavaContext context) {
704             if (USE_ECLIPSE_PARSER) {
705                 // Use Eclipse's compiler
706                 EcjTreeConverter converter = new EcjTreeConverter();
707                 String code = context.getContents();
708 
709                 CompilationUnit sourceUnit = new CompilationUnit(code.toCharArray(),
710                         context.file.getName(), "UTF-8"); //$NON-NLS-1$
711                 CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
712                 CompilationUnitDeclaration unit = null;
713                 try {
714                     unit = mParser.parse(sourceUnit, compilationResult);
715                 } catch (AbortCompilation e) {
716 
717                     String message;
718                     Location location;
719                     if (e.problem != null) {
720                         CategorizedProblem problem = e.problem;
721                         message = problem.getMessage();
722                         location = Location.create(context.file,
723                                 new DefaultPosition(problem.getSourceLineNumber() - 1, -1,
724                                         problem.getSourceStart()),
725                                 new DefaultPosition(problem.getSourceLineNumber() - 1, -1,
726                                         problem.getSourceEnd()));
727                     } else {
728                         location = Location.create(context.file);
729                         message = e.getCause() != null ? e.getCause().getLocalizedMessage() :
730                             e.getLocalizedMessage();
731                     }
732 
733                     context.report(IssueRegistry.PARSER_ERROR, location, message, null);
734                     return null;
735                 }
736                 if (unit == null) {
737                     return null;
738                 }
739 
740                 try {
741                     converter.visit(code, unit);
742                     List<? extends lombok.ast.Node> nodes = converter.getAll();
743 
744                     // There could be more than one node when there are errors; pick out the
745                     // compilation unit node
746                     for (lombok.ast.Node node : nodes) {
747                         if (node instanceof lombok.ast.CompilationUnit) {
748                             return node;
749                         }
750                     }
751 
752                     return null;
753                 } catch (Throwable t) {
754                     AdtPlugin.log(t, "Failed converting ECJ parse tree to Lombok for file %1$s",
755                             context.file.getPath());
756                     return null;
757                 }
758             } else {
759                 // Use Lombok for now
760                 Source source = new Source(context.getContents(), context.file.getName());
761                 List<lombok.ast.Node> nodes = source.getNodes();
762 
763                 // Don't analyze files containing errors
764                 List<ParseProblem> problems = source.getProblems();
765                 if (problems != null && problems.size() > 0) {
766                     /* Silently ignore the errors. There are still some bugs in Lombok/Parboiled
767                      * (triggered if you run lint on the AOSP framework directory for example),
768                      * and having these show up as fatal errors when it's really a tool bug
769                      * is bad. To make matters worse, the error messages aren't clear:
770                      * http://code.google.com/p/projectlombok/issues/detail?id=313
771                     for (ParseProblem problem : problems) {
772                         lombok.ast.Position position = problem.getPosition();
773                         Location location = Location.create(context.file,
774                                 context.getContents(), position.getStart(), position.getEnd());
775                         String message = problem.getMessage();
776                         context.report(
777                                 IssueRegistry.PARSER_ERROR, location,
778                                 message,
779                                 null);
780 
781                     }
782                     */
783                     return null;
784                 }
785 
786                 // There could be more than one node when there are errors; pick out the
787                 // compilation unit node
788                 for (lombok.ast.Node node : nodes) {
789                     if (node instanceof lombok.ast.CompilationUnit) {
790                         return node;
791                     }
792                 }
793                 return null;
794             }
795         }
796 
797         @Override
getLocation(JavaContext context, lombok.ast.Node node)798         public Location getLocation(JavaContext context, lombok.ast.Node node) {
799             lombok.ast.Position position = node.getPosition();
800             return Location.create(context.file, context.getContents(),
801                     position.getStart(), position.getEnd());
802         }
803 
804         @Override
createLocationHandle(JavaContext context, lombok.ast.Node node)805         public Handle createLocationHandle(JavaContext context, lombok.ast.Node node) {
806             return new LocationHandle(context.file, node);
807         }
808 
809         @Override
dispose(JavaContext context, lombok.ast.Node compilationUnit)810         public void dispose(JavaContext context, lombok.ast.Node compilationUnit) {
811         }
812 
813         /* Handle for creating positions cheaply and returning full fledged locations later */
814         private class LocationHandle implements Handle {
815             private File mFile;
816             private lombok.ast.Node mNode;
817             private Object mClientData;
818 
LocationHandle(File file, lombok.ast.Node node)819             public LocationHandle(File file, lombok.ast.Node node) {
820                 mFile = file;
821                 mNode = node;
822             }
823 
824             @Override
resolve()825             public Location resolve() {
826                 lombok.ast.Position pos = mNode.getPosition();
827                 return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd());
828             }
829 
830             @Override
setClientData(@ullable Object clientData)831             public void setClientData(@Nullable Object clientData) {
832                 mClientData = clientData;
833             }
834 
835             @Override
836             @Nullable
getClientData()837             public Object getClientData() {
838                 return mClientData;
839             }
840         }
841     }
842 }
843 
844