Home | History | Annotate | Download | only in build
      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 '&lt;file path&gt;'"
     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 &lt;line&gt;: &lt;error&gt;"<br>
     53      * " (Occurred while parsing &lt;path&gt;)"
     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 &lt;line&gt;: &lt;error&gt;"<br>
     60      * " (Occurred while parsing &lt;path&gt;)"<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: &lt;error&gt;"<br>
     68      * "Defined at file &lt;path&gt; line &lt;line&gt;"
     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: &lt;error&gt;"<br>
     75      * "Defined at file &lt;path&gt; line &lt;line&gt;"<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      * "&lt;path&gt; line &lt;line&gt;: &lt;error&gt;"
     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 &lt;path&gt;"<br>
     89      * "&lt;error&gt; at line &lt;line&gt;"
     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 &lt;path&gt;"<br>
     96      * "&lt;error&gt; at line &lt;line&gt;"<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      * "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
    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      * "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
    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 &lt;path&gt; malformed"<br>
    119      * Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br>
    120      * 'ERROR: failure processing &lt;path&gt;)
    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