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