• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
154     private static final Pattern sSkippingPattern =
155         Pattern.compile("    \\(skipping .+ .+ '(.*)'\\)"); //$NON-NLS-1$
156 
157     /**
158      * Suffix of error message which points to the first occurrence of a repeated resource
159      * definition.
160      * Example:
161      * Originally defined here.
162      */
163     private static final String ORIGINALLY_DEFINED_MSG = "Originally defined here."; //$NON-NLS-1$
164 
165     /**
166      * Portion of error message which points to the second occurrence of a repeated resource
167      * definition.
168      * <p>
169      * Example:
170      * error: Resource entry repeatedStyle1 already has bag item android:gravity.
171      */
172     private static final Pattern sNoResourcePattern =
173         Pattern.compile("No resource found that matches the given name: attr '(.+)'\\."); //$NON-NLS-1$
174 
175     /**
176      * Portion of error message which points to a missing required attribute in a
177      * resource definition.
178      * <p>
179      * Example:
180      * error: error: A 'name' attribute is required for <style>
181      */
182     private static final Pattern sRequiredPattern =
183         Pattern.compile("A '(.+)' attribute is required for <(.+)>"); //$NON-NLS-1$
184 
185     /**
186      * 2 line aapt error<br>
187      * "ERROR: Invalid configuration: foo"<br>
188      * "                              ^^^"<br>
189      * There's no need to parse the 2nd line.
190      */
191     private final static Pattern sPattern9Line1 = Pattern.compile(
192             "^Invalid configuration: (.+)$"); //$NON-NLS-1$
193 
194     /**
195      * Parse the output of aapt and mark the incorrect file with error markers
196      *
197      * @param results the output of aapt
198      * @param project the project containing the file to mark
199      * @return true if the parsing failed, false if success.
200      */
parseOutput(List<String> results, IProject project)201     public static boolean parseOutput(List<String> results, IProject project) {
202         int size = results.size();
203         if (size > 0) {
204             return parseOutput(results.toArray(new String[size]), project);
205         }
206 
207         return false;
208     }
209 
210     /**
211      * Parse the output of aapt and mark the incorrect file with error markers
212      *
213      * @param results the output of aapt
214      * @param project the project containing the file to mark
215      * @return true if the parsing failed, false if success.
216      */
parseOutput(String[] results, IProject project)217     public static boolean parseOutput(String[] results, IProject project) {
218         // nothing to parse? just return false;
219         if (results.length == 0) {
220             return false;
221         }
222 
223         // get the root of the project so that we can make IFile from full
224         // file path
225         String osRoot = project.getLocation().toOSString();
226 
227         Matcher m;
228 
229         for (int i = 0; i < results.length ; i++) {
230             String p = results[i];
231 
232             m = sPattern0Line1.matcher(p);
233             if (m.matches()) {
234                 // we ignore those (as this is an ignore message from aapt)
235                 continue;
236             }
237 
238             m = sPattern1Line1.matcher(p);
239             if (m.matches()) {
240                 String lineStr = m.group(1);
241                 String msg = m.group(2);
242 
243                 // get the matcher for the next line.
244                 m = getNextLineMatcher(results, ++i, sPattern1Line2);
245                 if (m == null) {
246                     return true;
247                 }
248 
249                 String location = m.group(1);
250 
251                 // check the values and attempt to mark the file.
252                 if (checkAndMark(location, lineStr, msg, osRoot, project,
253                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
254                     return true;
255                 }
256                 continue;
257             }
258 
259             // this needs to be tested before Pattern2 since they both start with 'ERROR:'
260             m = sPattern7Line1.matcher(p);
261             if (m.matches()) {
262                 String location = m.group(1);
263                 String msg = p; // default msg is the line in case we don't find anything else
264 
265                 if (++i < results.length) {
266                     msg = results[i].trim();
267                     if (++i < results.length) {
268                         msg = msg + " - " + results[i].trim(); //$NON-NLS-1$
269 
270                         // skip the next line
271                         i++;
272                     }
273                 }
274 
275                 // display the error
276                 if (checkAndMark(location, null, msg, osRoot, project,
277                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
278                     return true;
279                 }
280 
281                 // success, go to the next line
282                 continue;
283             }
284 
285             m =  sPattern2Line1.matcher(p);
286             if (m.matches()) {
287                 // get the msg
288                 String msg = m.group(1);
289 
290                 // get the matcher for the next line.
291                 m = getNextLineMatcher(results, ++i, sPattern2Line2);
292                 if (m == null) {
293                     return true;
294                 }
295 
296                 String location = m.group(1);
297                 String lineStr = m.group(2);
298 
299                 // check the values and attempt to mark the file.
300                 if (checkAndMark(location, lineStr, msg, osRoot, project,
301                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
302                     return true;
303                 }
304                 continue;
305             }
306 
307             m = sPattern3Line1.matcher(p);
308             if (m.matches()) {
309                 String location = m.group(1);
310                 String lineStr = m.group(2);
311                 String msg = m.group(3);
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 
319                 // success, go to the next line
320                 continue;
321             }
322 
323             m = sPattern4Line1.matcher(p);
324             if (m.matches()) {
325                 // get the filename.
326                 String location = m.group(1);
327 
328                 // get the matcher for the next line.
329                 m = getNextLineMatcher(results, ++i, sPattern4Line2);
330                 if (m == null) {
331                     return true;
332                 }
333 
334                 String msg = m.group(1);
335                 String lineStr = m.group(2);
336 
337                 // check the values and attempt to mark the file.
338                 if (checkAndMark(location, lineStr, msg, osRoot, project,
339                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
340                     return true;
341                 }
342 
343                 // success, go to the next line
344                 continue;
345             }
346 
347             m = sPattern5Line1.matcher(p);
348             if (m.matches()) {
349                 String location = m.group(1);
350                 String lineStr = m.group(2);
351                 String msg = m.group(3);
352 
353                 // check the values and attempt to mark the file.
354                 if (checkAndMark(location, lineStr, msg, osRoot, project,
355                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
356                     return true;
357                 }
358 
359                 // success, go to the next line
360                 continue;
361             }
362 
363             m = sPattern6Line1.matcher(p);
364             if (m.matches()) {
365                 String location = m.group(1);
366                 String lineStr = m.group(2);
367                 String msg = m.group(3);
368 
369                 // check the values and attempt to mark the file.
370                 if (checkAndMark(location, lineStr, msg, osRoot, project,
371                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
372                     return true;
373                 }
374 
375                 // success, go to the next line
376                 continue;
377             }
378 
379             m = sPattern8Line1.matcher(p);
380             if (m.matches()) {
381                 String location = m.group(2);
382                 String msg = m.group(1);
383 
384                 // check the values and attempt to mark the file.
385                 if (checkAndMark(location, null, msg, osRoot, project,
386                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
387                     return true;
388                 }
389 
390                 // success, go to the next line
391                 continue;
392             }
393 
394             m = sPattern9Line1.matcher(p);
395             if (m.matches()) {
396                 String badConfig = m.group(1);
397                 String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
398 
399                 // skip the next line
400                 i++;
401 
402                 // check the values and attempt to mark the file.
403                 if (checkAndMark(null /*location*/, null, msg, osRoot, project,
404                         AdtConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
405                     return true;
406                 }
407 
408                 // success, go to the next line
409                 continue;
410             }
411 
412             m = sSkippingPattern.matcher(p);
413             if (m.matches()) {
414                 String location = m.group(1);
415 
416                 // check the values and attempt to mark the file.
417                 if (checkAndMark(location, null, p.trim(), osRoot, project,
418                         AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
419                     return true;
420                 }
421 
422                 // success, go to the next line
423                 continue;
424             }
425 
426             // invalid line format, flag as error, and bail
427             return true;
428         }
429 
430         return false;
431     }
432 
433     /**
434      * Check if the parameters gotten from the error output are valid, and mark
435      * the file with an AAPT marker.
436      * @param location the full OS path of the error file. If null, the project is marked
437      * @param lineStr
438      * @param message
439      * @param root The root directory of the project, in OS specific format.
440      * @param project
441      * @param markerId The marker id to put.
442      * @param severity The severity of the marker to put (IMarker.SEVERITY_*)
443      * @return true if the parameters were valid and the file was marked successfully.
444      *
445      * @see IMarker
446      */
checkAndMark(String location, String lineStr, String message, String root, IProject project, String markerId, int severity)447     private static final  boolean checkAndMark(String location, String lineStr,
448             String message, String root, IProject project, String markerId, int severity) {
449         // check this is in fact a file
450         if (location != null) {
451             File f = new File(location);
452             if (f.exists() == false) {
453                 return false;
454             }
455         }
456 
457         // get the line number
458         int line = -1; // default value for error with no line.
459 
460         if (lineStr != null) {
461             try {
462                 line = Integer.parseInt(lineStr);
463             } catch (NumberFormatException e) {
464                 // looks like the string we extracted wasn't a valid
465                 // file number. Parsing failed and we return true
466                 return false;
467             }
468         }
469 
470         // add the marker
471         IResource f2 = project;
472         if (location != null) {
473             f2 = getResourceFromFullPath(location, root, project);
474             if (f2 == null) {
475                 return false;
476             }
477         }
478 
479         // Attempt to determine the exact range of characters affected by this error.
480         // This will look up the actual text of the file, go to the particular error line
481         // and scan for the specific string mentioned in the error.
482         int startOffset = -1;
483         int endOffset = -1;
484         if (f2 instanceof IFile) {
485             IRegion region = findRange((IFile) f2, line, message);
486             if (region != null) {
487                 startOffset = region.getOffset();
488                 endOffset = startOffset + region.getLength();
489             }
490         }
491 
492         // check if there's a similar marker already, since aapt is launched twice
493         boolean markerAlreadyExists = false;
494         try {
495             IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
496 
497             for (IMarker marker : markers) {
498                 if (startOffset != -1) {
499                     int tmpBegin = marker.getAttribute(IMarker.CHAR_START, -1);
500                     if (tmpBegin != startOffset) {
501                         break;
502                     }
503                     int tmpEnd = marker.getAttribute(IMarker.CHAR_END, -1);
504                     if (tmpEnd != startOffset) {
505                         break;
506                     }
507                 }
508 
509                 int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
510                 if (tmpLine != line) {
511                     break;
512                 }
513 
514                 int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1);
515                 if (tmpSeverity != severity) {
516                     break;
517                 }
518 
519                 String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
520                 if (tmpMsg == null || tmpMsg.equals(message) == false) {
521                     break;
522                 }
523 
524                 // if we're here, all the marker attributes are equals, we found it
525                 // and exit
526                 markerAlreadyExists = true;
527                 break;
528             }
529 
530         } catch (CoreException e) {
531             // if we couldn't get the markers, then we just mark the file again
532             // (since markerAlreadyExists is initialized to false, we do nothing)
533         }
534 
535         if (markerAlreadyExists == false) {
536             BaseProjectHelper.markResource(f2, markerId, message, line,
537                     startOffset, endOffset, severity);
538         }
539 
540         return true;
541     }
542 
543     /**
544      * Given an aapt error message in a given file and a given (initial) line number,
545      * return the corresponding offset range for the error, or null.
546      */
findRange(IFile file, int line, String message)547     private static IRegion findRange(IFile file, int line, String message) {
548         Matcher matcher = sValueRangePattern.matcher(message);
549         if (matcher.find()) {
550             String property = matcher.group(1);
551             String value = matcher.group(2);
552 
553             // First find the property. We can't just immediately look for the
554             // value, because there could be other attributes in this element
555             // earlier than the one in error, and we might accidentally pick
556             // up on a different occurrence of the value in a context where
557             // it is valid.
558             if (value.length() > 0) {
559                 return findRange(file, line, property, value);
560             } else {
561                 // Find first occurrence of property followed by '' or ""
562                 IRegion region1 = findRange(file, line, property, "\"\""); //$NON-NLS-1$
563                 IRegion region2 = findRange(file, line, property, "''");   //$NON-NLS-1$
564                 if (region1 == null) {
565                     if (region2 == null) {
566                         // Highlight the property instead
567                         return findRange(file, line, property, null);
568                     }
569                     return region2;
570                 } else if (region2 == null) {
571                     return region1;
572                 } else if (region1.getOffset() < region2.getOffset()) {
573                     return region1;
574                 } else {
575                     return region2;
576                 }
577             }
578         }
579 
580         matcher = sRepeatedRangePattern.matcher(message);
581         if (matcher.find()) {
582             String property = matcher.group(2);
583             return findRange(file, line, property, null);
584         }
585 
586         matcher = sNoResourcePattern.matcher(message);
587         if (matcher.find()) {
588             String property = matcher.group(1);
589             return findRange(file, line, property, null);
590         }
591 
592         matcher = sRequiredPattern.matcher(message);
593         if (matcher.find()) {
594             String elementName = matcher.group(2);
595             IRegion region = findRange(file, line, '<' + elementName, null);
596             if (region != null && region.getLength() > 1) {
597                 // Skip the opening <
598                 region = new Region(region.getOffset() + 1, region.getLength() - 1);
599             }
600             return region;
601         }
602 
603         if (message.endsWith(ORIGINALLY_DEFINED_MSG)) {
604             return findLineTextRange(file, line);
605         }
606 
607         return null;
608     }
609 
610     /**
611      * Given a file and line number, return the range of the first match starting on the
612      * given line. If second is non null, also search for the second string starting at he
613      * location of the first string.
614      */
findRange(IFile file, int line, String first, String second)615     private static IRegion findRange(IFile file, int line, String first,
616             String second) {
617         IRegion region = null;
618         IDocumentProvider provider = new TextFileDocumentProvider();
619         try {
620             provider.connect(file);
621             IDocument document = provider.getDocument(file);
622             if (document != null) {
623                 IRegion lineInfo = document.getLineInformation(line - 1);
624                 int lineStartOffset = lineInfo.getOffset();
625                 // The aapt errors will be anchored on the line where the
626                 // element starts - which means that with formatting where
627                 // attributes end up on subsequent lines we don't find it on
628                 // the error line indicated by aapt.
629                 // Therefore, search forwards in the document.
630                 FindReplaceDocumentAdapter adapter =
631                     new FindReplaceDocumentAdapter(document);
632 
633                 region = adapter.find(lineStartOffset, first,
634                         true /*forwardSearch*/, true /*caseSensitive*/,
635                         false /*wholeWord*/, false /*regExSearch*/);
636                 if (region != null && second != null) {
637                     region = adapter.find(region.getOffset() + first.length(), second,
638                             true /*forwardSearch*/, true /*caseSensitive*/,
639                             false /*wholeWord*/, false /*regExSearch*/);
640                 }
641             }
642         } catch (Exception e) {
643             AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
644         } finally {
645             provider.disconnect(file);
646         }
647         return region;
648     }
649 
650     /** Returns the non-whitespace line range at the given line number. */
findLineTextRange(IFile file, int line)651     private static IRegion findLineTextRange(IFile file, int line) {
652         IDocumentProvider provider = new TextFileDocumentProvider();
653         try {
654             provider.connect(file);
655             IDocument document = provider.getDocument(file);
656             if (document != null) {
657                 IRegion lineInfo = document.getLineInformation(line - 1);
658                 String lineContents = document.get(lineInfo.getOffset(), lineInfo.getLength());
659                 int lineBegin = 0;
660                 int lineEnd = lineContents.length()-1;
661 
662                 for (; lineEnd >= 0; lineEnd--) {
663                     char c = lineContents.charAt(lineEnd);
664                     if (!Character.isWhitespace(c)) {
665                         break;
666                     }
667                 }
668                 lineEnd++;
669                 for (; lineBegin < lineEnd; lineBegin++) {
670                     char c = lineContents.charAt(lineBegin);
671                     if (!Character.isWhitespace(c)) {
672                         break;
673                     }
674                 }
675                 if (lineBegin < lineEnd) {
676                     return new Region(lineInfo.getOffset() + lineBegin, lineEnd - lineBegin);
677                 }
678             }
679         } catch (Exception e) {
680             AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
681         } finally {
682             provider.disconnect(file);
683         }
684 
685         return null;
686     }
687 
688     /**
689      * Returns a matching matcher for the next line
690      * @param lines The array of lines
691      * @param nextIndex The index of the next line
692      * @param pattern The pattern to match
693      * @return null if error or no match, the matcher otherwise.
694      */
getNextLineMatcher(String[] lines, int nextIndex, Pattern pattern)695     private static final Matcher getNextLineMatcher(String[] lines,
696             int nextIndex, Pattern pattern) {
697         // unless we can't, because we reached the last line
698         if (nextIndex == lines.length) {
699             // we expected a 2nd line, so we flag as error
700             // and we bail
701             return null;
702         }
703 
704         Matcher m = pattern.matcher(lines[nextIndex]);
705         if (m.matches()) {
706            return m;
707         }
708 
709         return null;
710     }
711 
getResourceFromFullPath(String filename, String root, IProject project)712     private static IResource getResourceFromFullPath(String filename, String root,
713             IProject project) {
714         if (filename.startsWith(root)) {
715             String file = filename.substring(root.length());
716 
717             // get the resource
718             IResource r = project.findMember(file);
719 
720             // if the resource is valid, we add the marker
721             if (r != null && r.exists()) {
722                 return r;
723             }
724         }
725 
726         return null;
727     }
728 
729 }
730