1 /* 2 * Copyright (C) 2010 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 17 package com.android.ide.eclipse.adt.internal.build; 18 19 import com.android.ide.eclipse.adt.AdtConstants; 20 import com.android.ide.eclipse.adt.AdtPlugin; 21 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 22 23 import org.eclipse.core.resources.IFile; 24 import org.eclipse.core.resources.IMarker; 25 import org.eclipse.core.resources.IProject; 26 import org.eclipse.core.resources.IResource; 27 import org.eclipse.core.runtime.CoreException; 28 import org.eclipse.jface.text.FindReplaceDocumentAdapter; 29 import org.eclipse.jface.text.IDocument; 30 import org.eclipse.jface.text.IRegion; 31 import org.eclipse.jface.text.Region; 32 import org.eclipse.ui.editors.text.TextFileDocumentProvider; 33 import org.eclipse.ui.texteditor.IDocumentProvider; 34 35 import java.io.File; 36 import java.util.List; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 40 public final class AaptParser { 41 42 // TODO: rename the pattern to something that makes sense + javadoc comments. 43 /** 44 * Single line aapt warning for skipping files.<br> 45 * " (skipping hidden file '<file path>'" 46 */ 47 private final static Pattern sPattern0Line1 = Pattern.compile( 48 "^\\s+\\(skipping hidden file\\s'(.*)'\\)$"); //$NON-NLS-1$ 49 50 /** 51 * First line of dual line aapt error.<br> 52 * "ERROR at line <line>: <error>"<br> 53 * " (Occurred while parsing <path>)" 54 */ 55 private final static Pattern sPattern1Line1 = Pattern.compile( 56 "^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$ 57 /** 58 * Second line of dual line aapt error.<br> 59 * "ERROR at line <line>: <error>"<br> 60 * " (Occurred while parsing <path>)"<br> 61 * @see #sPattern1Line1 62 */ 63 private final static Pattern sPattern1Line2 = Pattern.compile( 64 "^\\s+\\(Occurred while parsing\\s+(.*)\\)$"); //$NON-NLS-1$ 65 /** 66 * First line of dual line aapt error.<br> 67 * "ERROR: <error>"<br> 68 * "Defined at file <path> line <line>" 69 */ 70 private final static Pattern sPattern2Line1 = Pattern.compile( 71 "^ERROR:\\s+(.+)$"); //$NON-NLS-1$ 72 /** 73 * Second line of dual line aapt error.<br> 74 * "ERROR: <error>"<br> 75 * "Defined at file <path> line <line>"<br> 76 * @see #sPattern2Line1 77 */ 78 private final static Pattern sPattern2Line2 = Pattern.compile( 79 "Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$ 80 /** 81 * Single line aapt error<br> 82 * "<path> line <line>: <error>" 83 */ 84 private final static Pattern sPattern3Line1 = Pattern.compile( 85 "^(.+)\\sline\\s(\\d+):\\s(.+)$"); //$NON-NLS-1$ 86 /** 87 * First line of dual line aapt error.<br> 88 * "ERROR parsing XML file <path>"<br> 89 * "<error> at line <line>" 90 */ 91 private final static Pattern sPattern4Line1 = Pattern.compile( 92 "^Error\\s+parsing\\s+XML\\s+file\\s(.+)$"); //$NON-NLS-1$ 93 /** 94 * Second line of dual line aapt error.<br> 95 * "ERROR parsing XML file <path>"<br> 96 * "<error> at line <line>"<br> 97 * @see #sPattern4Line1 98 */ 99 private final static Pattern sPattern4Line2 = Pattern.compile( 100 "^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$ 101 102 /** 103 * Single line aapt warning<br> 104 * "<path>:<line>: <error>" 105 */ 106 private final static Pattern sPattern5Line1 = Pattern.compile( 107 "^(.+?):(\\d+):\\s+WARNING:(.+)$"); //$NON-NLS-1$ 108 109 /** 110 * Single line aapt error<br> 111 * "<path>:<line>: <error>" 112 */ 113 private final static Pattern sPattern6Line1 = Pattern.compile( 114 "^(.+?):(\\d+):\\s+(.+)$"); //$NON-NLS-1$ 115 116 /** 117 * 4 line aapt error<br> 118 * "ERROR: 9-path image <path> malformed"<br> 119 * Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br> 120 * 'ERROR: failure processing <path>) 121 */ 122 private final static Pattern sPattern7Line1 = Pattern.compile( 123 "^ERROR:\\s+9-patch\\s+image\\s+(.+)\\s+malformed\\.$"); //$NON-NLS-1$ 124 125 private final static Pattern sPattern8Line1 = Pattern.compile( 126 "^(invalid resource directory name): (.*)$"); //$NON-NLS-1$ 127 128 /** 129 * Portion of the error message which states the context in which the error occurred, 130 * such as which property was being processed and what the string value was that 131 * caused the error. 132 * <p> 133 * Example: 134 * error: No resource found that matches the given name (at 'text' with value '@string/foo') 135 */ 136 private static final Pattern sValueRangePattern = 137 Pattern.compile("\\(at '(.+)' with value '(.*)'\\)"); //$NON-NLS-1$ 138 139 140 /** 141 * Portion of error message which points to the second occurrence of a repeated resource 142 * definition. 143 * <p> 144 * Example: 145 * error: Resource entry repeatedStyle1 already has bag item android:gravity. 146 */ 147 private static final Pattern sRepeatedRangePattern = 148 Pattern.compile("Resource entry (.+) already has bag item (.+)\\."); //$NON-NLS-1$ 149 150 /** 151 * Error message emitted when aapt skips a file because for example it's name is 152 * invalid, such as a layout file name which starts with _. 153 * <p> 154 * This error message is used by AAPT in Tools 19 and earlier. 155 */ 156 private static final Pattern sSkippingPattern = 157 Pattern.compile(" \\(skipping (.+) .+ '(.*)'\\)"); //$NON-NLS-1$ 158 159 /** 160 * Error message emitted when aapt skips a file because for example it's name is 161 * invalid, such as a layout file name which starts with _. 162 * <p> 163 * This error message is used by AAPT in Tools 20 and later. 164 */ 165 private static final Pattern sNewSkippingPattern = 166 Pattern.compile(" \\(skipping .+ '(.+)' due to ANDROID_AAPT_IGNORE pattern '.+'\\)"); //$NON-NLS-1$ 167 168 /** 169 * Suffix of error message which points to the first occurrence of a repeated resource 170 * definition. 171 * Example: 172 * Originally defined here. 173 */ 174 private static final String ORIGINALLY_DEFINED_MSG = "Originally defined here."; //$NON-NLS-1$ 175 176 /** 177 * Portion of error message which points to the second occurrence of a repeated resource 178 * definition. 179 * <p> 180 * Example: 181 * error: Resource entry repeatedStyle1 already has bag item android:gravity. 182 */ 183 private static final Pattern sNoResourcePattern = 184 Pattern.compile("No resource found that matches the given name: attr '(.+)'\\."); //$NON-NLS-1$ 185 186 /** 187 * Portion of error message which points to a missing required attribute in a 188 * resource definition. 189 * <p> 190 * Example: 191 * error: error: A 'name' attribute is required for <style> 192 */ 193 private static final Pattern sRequiredPattern = 194 Pattern.compile("A '(.+)' attribute is required for <(.+)>"); //$NON-NLS-1$ 195 196 /** 197 * 2 line aapt error<br> 198 * "ERROR: Invalid configuration: foo"<br> 199 * " ^^^"<br> 200 * There's no need to parse the 2nd line. 201 */ 202 private final static Pattern sPattern9Line1 = Pattern.compile( 203 "^Invalid configuration: (.+)$"); //$NON-NLS-1$ 204 205 private final static Pattern sXmlBlockPattern = Pattern.compile( 206 "W/ResourceType\\(.*\\): Bad XML block: no root element node found"); //$NON-NLS-1$ 207 208 /** 209 * Parse the output of aapt and mark the incorrect file with error markers 210 * 211 * @param results the output of aapt 212 * @param project the project containing the file to mark 213 * @return true if the parsing failed, false if success. 214 */ parseOutput(List<String> results, IProject project)215 public static boolean parseOutput(List<String> results, IProject project) { 216 int size = results.size(); 217 if (size > 0) { 218 return parseOutput(results.toArray(new String[size]), project); 219 } 220 221 return false; 222 } 223 224 /** 225 * Parse the output of aapt and mark the incorrect file with error markers 226 * 227 * @param results the output of aapt 228 * @param project the project containing the file to mark 229 * @return true if the parsing failed, false if success. 230 */ parseOutput(String[] results, IProject project)231 public static boolean parseOutput(String[] results, IProject project) { 232 // nothing to parse? just return false; 233 if (results.length == 0) { 234 return false; 235 } 236 237 // get the root of the project so that we can make IFile from full 238 // file path 239 String osRoot = project.getLocation().toOSString(); 240 241 Matcher m; 242 243 for (int i = 0; i < results.length ; i++) { 244 String p = results[i]; 245 246 m = sPattern0Line1.matcher(p); 247 if (m.matches()) { 248 // we ignore those (as this is an ignore message from aapt) 249 continue; 250 } 251 252 m = sPattern1Line1.matcher(p); 253 if (m.matches()) { 254 String lineStr = m.group(1); 255 String msg = m.group(2); 256 257 // get the matcher for the next line. 258 m = getNextLineMatcher(results, ++i, sPattern1Line2); 259 if (m == null) { 260 return true; 261 } 262 263 String location = m.group(1); 264 265 // check the values and attempt to mark the file. 266 if (checkAndMark(location, lineStr, msg, osRoot, project, 267 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 268 return true; 269 } 270 continue; 271 } 272 273 // this needs to be tested before Pattern2 since they both start with 'ERROR:' 274 m = sPattern7Line1.matcher(p); 275 if (m.matches()) { 276 String location = m.group(1); 277 String msg = p; // default msg is the line in case we don't find anything else 278 279 if (++i < results.length) { 280 msg = results[i].trim(); 281 if (++i < results.length) { 282 msg = msg + " - " + results[i].trim(); //$NON-NLS-1$ 283 284 // skip the next line 285 i++; 286 } 287 } 288 289 // display the error 290 if (checkAndMark(location, null, msg, osRoot, project, 291 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 292 return true; 293 } 294 295 // success, go to the next line 296 continue; 297 } 298 299 m = sPattern2Line1.matcher(p); 300 if (m.matches()) { 301 // get the msg 302 String msg = m.group(1); 303 304 // get the matcher for the next line. 305 m = getNextLineMatcher(results, ++i, sPattern2Line2); 306 if (m == null) { 307 return true; 308 } 309 310 String location = m.group(1); 311 String lineStr = m.group(2); 312 313 // check the values and attempt to mark the file. 314 if (checkAndMark(location, lineStr, msg, osRoot, project, 315 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 316 return true; 317 } 318 continue; 319 } 320 321 m = sPattern3Line1.matcher(p); 322 if (m.matches()) { 323 String location = m.group(1); 324 String lineStr = m.group(2); 325 String msg = m.group(3); 326 327 // check the values and attempt to mark the file. 328 if (checkAndMark(location, lineStr, msg, osRoot, project, 329 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 330 return true; 331 } 332 333 // success, go to the next line 334 continue; 335 } 336 337 m = sPattern4Line1.matcher(p); 338 if (m.matches()) { 339 // get the filename. 340 String location = m.group(1); 341 342 // get the matcher for the next line. 343 m = getNextLineMatcher(results, ++i, sPattern4Line2); 344 if (m == null) { 345 return true; 346 } 347 348 String msg = m.group(1); 349 String lineStr = m.group(2); 350 351 // check the values and attempt to mark the file. 352 if (checkAndMark(location, lineStr, msg, osRoot, project, 353 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 354 return true; 355 } 356 357 // success, go to the next line 358 continue; 359 } 360 361 m = sPattern5Line1.matcher(p); 362 if (m.matches()) { 363 String location = m.group(1); 364 String lineStr = m.group(2); 365 String msg = m.group(3); 366 367 // check the values and attempt to mark the file. 368 if (checkAndMark(location, lineStr, msg, osRoot, project, 369 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { 370 return true; 371 } 372 373 // success, go to the next line 374 continue; 375 } 376 377 m = sPattern6Line1.matcher(p); 378 if (m.matches()) { 379 String location = m.group(1); 380 String lineStr = m.group(2); 381 String msg = m.group(3); 382 383 // check the values and attempt to mark the file. 384 if (checkAndMark(location, lineStr, msg, osRoot, project, 385 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 386 return true; 387 } 388 389 // success, go to the next line 390 continue; 391 } 392 393 m = sPattern8Line1.matcher(p); 394 if (m.matches()) { 395 String location = m.group(2); 396 String msg = m.group(1); 397 398 // check the values and attempt to mark the file. 399 if (checkAndMark(location, null, msg, osRoot, project, 400 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 401 return true; 402 } 403 404 // success, go to the next line 405 continue; 406 } 407 408 m = sPattern9Line1.matcher(p); 409 if (m.matches()) { 410 String badConfig = m.group(1); 411 String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig); 412 413 // skip the next line 414 i++; 415 416 // check the values and attempt to mark the file. 417 if (checkAndMark(null /*location*/, null, msg, osRoot, project, 418 AdtConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) { 419 return true; 420 } 421 422 // success, go to the next line 423 continue; 424 } 425 426 m = sNewSkippingPattern.matcher(p); 427 if (m.matches()) { 428 String location = m.group(1); 429 430 if (location.startsWith(".") //$NON-NLS-1$ 431 || location.endsWith("~")) { //$NON-NLS-1$ 432 continue; 433 } 434 435 // check the values and attempt to mark the file. 436 if (checkAndMark(location, null, p.trim(), osRoot, project, 437 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { 438 return true; 439 } 440 441 // success, go to the next line 442 continue; 443 } 444 445 m = sSkippingPattern.matcher(p); 446 if (m.matches()) { 447 String location = m.group(2); 448 449 // Certain files can safely be skipped without marking the project 450 // as having errors. See isHidden() in AaptAssets.cpp: 451 String type = m.group(1); 452 if (type.equals("backup") //$NON-NLS-1$ // main.xml~, etc 453 || type.equals("hidden") //$NON-NLS-1$ // .gitignore, etc 454 || type.equals("index")) { //$NON-NLS-1$ // thumbs.db, etc 455 continue; 456 } 457 458 // check the values and attempt to mark the file. 459 if (checkAndMark(location, null, p.trim(), osRoot, project, 460 AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { 461 return true; 462 } 463 464 // success, go to the next line 465 continue; 466 } 467 468 m = sXmlBlockPattern.matcher(p); 469 if (m.matches()) { 470 // W/ResourceType(12345): Bad XML block: no root element node found 471 // Sadly there's NO filename reference; this error typically describes the 472 // error *after* this line. 473 if (results.length == 1) { 474 // This is the only error message: dump to console and quit 475 return true; 476 } 477 // Continue: the real culprit is displayed next and should get a marker 478 continue; 479 } 480 481 return true; 482 } 483 484 return false; 485 } 486 487 /** 488 * Check if the parameters gotten from the error output are valid, and mark 489 * the file with an AAPT marker. 490 * @param location the full OS path of the error file. If null, the project is marked 491 * @param lineStr 492 * @param message 493 * @param root The root directory of the project, in OS specific format. 494 * @param project 495 * @param markerId The marker id to put. 496 * @param severity The severity of the marker to put (IMarker.SEVERITY_*) 497 * @return true if the parameters were valid and the file was marked successfully. 498 * 499 * @see IMarker 500 */ checkAndMark(String location, String lineStr, String message, String root, IProject project, String markerId, int severity)501 private static final boolean checkAndMark(String location, String lineStr, 502 String message, String root, IProject project, String markerId, int severity) { 503 // check this is in fact a file 504 if (location != null) { 505 File f = new File(location); 506 if (f.exists() == false) { 507 return false; 508 } 509 } 510 511 // get the line number 512 int line = -1; // default value for error with no line. 513 514 if (lineStr != null) { 515 try { 516 line = Integer.parseInt(lineStr); 517 } catch (NumberFormatException e) { 518 // looks like the string we extracted wasn't a valid 519 // file number. Parsing failed and we return true 520 return false; 521 } 522 } 523 524 // add the marker 525 IResource f2 = project; 526 if (location != null) { 527 f2 = getResourceFromFullPath(location, root, project); 528 if (f2 == null) { 529 return false; 530 } 531 } 532 533 // Attempt to determine the exact range of characters affected by this error. 534 // This will look up the actual text of the file, go to the particular error line 535 // and scan for the specific string mentioned in the error. 536 int startOffset = -1; 537 int endOffset = -1; 538 if (f2 instanceof IFile) { 539 IRegion region = findRange((IFile) f2, line, message); 540 if (region != null) { 541 startOffset = region.getOffset(); 542 endOffset = startOffset + region.getLength(); 543 } 544 } 545 546 // check if there's a similar marker already, since aapt is launched twice 547 boolean markerAlreadyExists = false; 548 try { 549 IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO); 550 551 for (IMarker marker : markers) { 552 if (startOffset != -1) { 553 int tmpBegin = marker.getAttribute(IMarker.CHAR_START, -1); 554 if (tmpBegin != startOffset) { 555 break; 556 } 557 int tmpEnd = marker.getAttribute(IMarker.CHAR_END, -1); 558 if (tmpEnd != startOffset) { 559 break; 560 } 561 } 562 563 int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1); 564 if (tmpLine != line) { 565 break; 566 } 567 568 int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1); 569 if (tmpSeverity != severity) { 570 break; 571 } 572 573 String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null); 574 if (tmpMsg == null || tmpMsg.equals(message) == false) { 575 break; 576 } 577 578 // if we're here, all the marker attributes are equals, we found it 579 // and exit 580 markerAlreadyExists = true; 581 break; 582 } 583 584 } catch (CoreException e) { 585 // if we couldn't get the markers, then we just mark the file again 586 // (since markerAlreadyExists is initialized to false, we do nothing) 587 } 588 589 if (markerAlreadyExists == false) { 590 BaseProjectHelper.markResource(f2, markerId, message, line, 591 startOffset, endOffset, severity); 592 } 593 594 return true; 595 } 596 597 /** 598 * Given an aapt error message in a given file and a given (initial) line number, 599 * return the corresponding offset range for the error, or null. 600 */ findRange(IFile file, int line, String message)601 private static IRegion findRange(IFile file, int line, String message) { 602 Matcher matcher = sValueRangePattern.matcher(message); 603 if (matcher.find()) { 604 String property = matcher.group(1); 605 String value = matcher.group(2); 606 607 // First find the property. We can't just immediately look for the 608 // value, because there could be other attributes in this element 609 // earlier than the one in error, and we might accidentally pick 610 // up on a different occurrence of the value in a context where 611 // it is valid. 612 if (value.length() > 0) { 613 return findRange(file, line, property, value); 614 } else { 615 // Find first occurrence of property followed by '' or "" 616 IRegion region1 = findRange(file, line, property, "\"\""); //$NON-NLS-1$ 617 IRegion region2 = findRange(file, line, property, "''"); //$NON-NLS-1$ 618 if (region1 == null) { 619 if (region2 == null) { 620 // Highlight the property instead 621 return findRange(file, line, property, null); 622 } 623 return region2; 624 } else if (region2 == null) { 625 return region1; 626 } else if (region1.getOffset() < region2.getOffset()) { 627 return region1; 628 } else { 629 return region2; 630 } 631 } 632 } 633 634 matcher = sRepeatedRangePattern.matcher(message); 635 if (matcher.find()) { 636 String property = matcher.group(2); 637 return findRange(file, line, property, null); 638 } 639 640 matcher = sNoResourcePattern.matcher(message); 641 if (matcher.find()) { 642 String property = matcher.group(1); 643 return findRange(file, line, property, null); 644 } 645 646 matcher = sRequiredPattern.matcher(message); 647 if (matcher.find()) { 648 String elementName = matcher.group(2); 649 IRegion region = findRange(file, line, '<' + elementName, null); 650 if (region != null && region.getLength() > 1) { 651 // Skip the opening < 652 region = new Region(region.getOffset() + 1, region.getLength() - 1); 653 } 654 return region; 655 } 656 657 if (message.endsWith(ORIGINALLY_DEFINED_MSG)) { 658 return findLineTextRange(file, line); 659 } 660 661 return null; 662 } 663 664 /** 665 * Given a file and line number, return the range of the first match starting on the 666 * given line. If second is non null, also search for the second string starting at he 667 * location of the first string. 668 */ findRange(IFile file, int line, String first, String second)669 private static IRegion findRange(IFile file, int line, String first, 670 String second) { 671 IRegion region = null; 672 IDocumentProvider provider = new TextFileDocumentProvider(); 673 try { 674 provider.connect(file); 675 IDocument document = provider.getDocument(file); 676 if (document != null) { 677 IRegion lineInfo = document.getLineInformation(line - 1); 678 int lineStartOffset = lineInfo.getOffset(); 679 // The aapt errors will be anchored on the line where the 680 // element starts - which means that with formatting where 681 // attributes end up on subsequent lines we don't find it on 682 // the error line indicated by aapt. 683 // Therefore, search forwards in the document. 684 FindReplaceDocumentAdapter adapter = 685 new FindReplaceDocumentAdapter(document); 686 687 region = adapter.find(lineStartOffset, first, 688 true /*forwardSearch*/, true /*caseSensitive*/, 689 false /*wholeWord*/, false /*regExSearch*/); 690 if (region != null && second != null) { 691 region = adapter.find(region.getOffset() + first.length(), second, 692 true /*forwardSearch*/, true /*caseSensitive*/, 693 false /*wholeWord*/, false /*regExSearch*/); 694 } 695 } 696 } catch (Exception e) { 697 AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); 698 } finally { 699 provider.disconnect(file); 700 } 701 return region; 702 } 703 704 /** Returns the non-whitespace line range at the given line number. */ findLineTextRange(IFile file, int line)705 private static IRegion findLineTextRange(IFile file, int line) { 706 IDocumentProvider provider = new TextFileDocumentProvider(); 707 try { 708 provider.connect(file); 709 IDocument document = provider.getDocument(file); 710 if (document != null) { 711 IRegion lineInfo = document.getLineInformation(line - 1); 712 String lineContents = document.get(lineInfo.getOffset(), lineInfo.getLength()); 713 int lineBegin = 0; 714 int lineEnd = lineContents.length()-1; 715 716 for (; lineEnd >= 0; lineEnd--) { 717 char c = lineContents.charAt(lineEnd); 718 if (!Character.isWhitespace(c)) { 719 break; 720 } 721 } 722 lineEnd++; 723 for (; lineBegin < lineEnd; lineBegin++) { 724 char c = lineContents.charAt(lineBegin); 725 if (!Character.isWhitespace(c)) { 726 break; 727 } 728 } 729 if (lineBegin < lineEnd) { 730 return new Region(lineInfo.getOffset() + lineBegin, lineEnd - lineBegin); 731 } 732 } 733 } catch (Exception e) { 734 AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); 735 } finally { 736 provider.disconnect(file); 737 } 738 739 return null; 740 } 741 742 /** 743 * Returns a matching matcher for the next line 744 * @param lines The array of lines 745 * @param nextIndex The index of the next line 746 * @param pattern The pattern to match 747 * @return null if error or no match, the matcher otherwise. 748 */ getNextLineMatcher(String[] lines, int nextIndex, Pattern pattern)749 private static final Matcher getNextLineMatcher(String[] lines, 750 int nextIndex, Pattern pattern) { 751 // unless we can't, because we reached the last line 752 if (nextIndex == lines.length) { 753 // we expected a 2nd line, so we flag as error 754 // and we bail 755 return null; 756 } 757 758 Matcher m = pattern.matcher(lines[nextIndex]); 759 if (m.matches()) { 760 return m; 761 } 762 763 return null; 764 } 765 getResourceFromFullPath(String filename, String root, IProject project)766 private static IResource getResourceFromFullPath(String filename, String root, 767 IProject project) { 768 if (filename.startsWith(root)) { 769 String file = filename.substring(root.length()); 770 771 // get the resource 772 IResource r = project.findMember(file); 773 774 // if the resource is valid, we add the marker 775 if (r != null && r.exists()) { 776 return r; 777 } 778 } 779 780 return null; 781 } 782 783 } 784