Home | History | Annotate | Download | only in lint
      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      */
    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
    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
    134     public IDomParser getDomParser() {
    135         return this;
    136     }
    137 
    138     @Override
    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
    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 
    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
    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
    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
    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) */
    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) */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    532     public static void showErrors(Shell shell, final IFile file) {
    533         LintListDialog dialog = new LintListDialog(shell, file);
    534         dialog.open();
    535     }
    536 
    537     @Override
    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 
    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
    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
    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      */
    593     public static IssueRegistry getRegistry() {
    594         return new BuiltinIssueRegistry();
    595     }
    596 
    597     @Override
    598     public Class<? extends Detector> replaceDetector(Class<? extends Detector> detectorClass) {
    599         return detectorClass;
    600     }
    601 
    602     @Override
    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 
    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
    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
    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
    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 
    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
    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
    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
    805         public Handle createLocationHandle(JavaContext context, lombok.ast.Node node) {
    806             return new LocationHandle(context.file, node);
    807         }
    808 
    809         @Override
    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 
    819             public LocationHandle(File file, lombok.ast.Node node) {
    820                 mFile = file;
    821                 mNode = node;
    822             }
    823 
    824             @Override
    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
    831             public void setClientData(@Nullable Object clientData) {
    832                 mClientData = clientData;
    833             }
    834 
    835             @Override
    836             @Nullable
    837             public Object getClientData() {
    838                 return mClientData;
    839             }
    840         }
    841     }
    842 }
    843 
    844