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 */ 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 */ 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 */ 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 */ 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 */ 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. */ 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 */ 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 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