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