• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.AndroidConstants;
22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
23 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
24 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler;
25 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener;
26 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
27 
28 import org.eclipse.core.resources.IContainer;
29 import org.eclipse.core.resources.IFile;
30 import org.eclipse.core.resources.IFolder;
31 import org.eclipse.core.resources.IMarker;
32 import org.eclipse.core.resources.IProject;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IWorkspaceRoot;
35 import org.eclipse.core.resources.IncrementalProjectBuilder;
36 import org.eclipse.core.resources.ResourcesPlugin;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IPath;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.IStatus;
41 import org.eclipse.core.runtime.Status;
42 import org.eclipse.core.runtime.SubProgressMonitor;
43 import org.eclipse.jdt.core.IClasspathEntry;
44 import org.eclipse.jdt.core.IJavaProject;
45 import org.eclipse.jdt.core.JavaCore;
46 import org.xml.sax.SAXException;
47 
48 import java.io.BufferedReader;
49 import java.io.File;
50 import java.io.IOException;
51 import java.io.InputStreamReader;
52 import java.util.ArrayList;
53 import java.util.regex.Matcher;
54 import java.util.regex.Pattern;
55 
56 import javax.xml.parsers.ParserConfigurationException;
57 import javax.xml.parsers.SAXParser;
58 import javax.xml.parsers.SAXParserFactory;
59 
60 /**
61  * Base builder for XML files. This class allows for basic XML parsing with
62  * error checking and marking the files for errors/warnings.
63  */
64 abstract class BaseBuilder extends IncrementalProjectBuilder {
65 
66     // TODO: rename the pattern to something that makes sense + javadoc comments.
67 
68     /**
69      * Single line aapt warning for skipping files.<br>
70      * "  (skipping hidden file '&lt;file path&gt;'"
71      */
72     private final static Pattern sPattern0Line1 = Pattern.compile(
73             "^\\s+\\(skipping hidden file\\s'(.*)'\\)$"); //$NON-NLS-1$
74 
75     /**
76      * First line of dual line aapt error.<br>
77      * "ERROR at line &lt;line&gt;: &lt;error&gt;"<br>
78      * " (Occurred while parsing &lt;path&gt;)"
79      */
80     private final static Pattern sPattern1Line1 = Pattern.compile(
81             "^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$
82     /**
83      * Second line of dual line aapt error.<br>
84      * "ERROR at line &lt;line&gt;: &lt;error&gt;"<br>
85      * " (Occurred while parsing &lt;path&gt;)"<br>
86      * @see #sPattern1Line1
87      */
88     private final static Pattern sPattern1Line2 = Pattern.compile(
89             "^\\s+\\(Occurred while parsing\\s+(.*)\\)$");  //$NON-NLS-1$
90     /**
91      * First line of dual line aapt error.<br>
92      * "ERROR: &lt;error&gt;"<br>
93      * "Defined at file &lt;path&gt; line &lt;line&gt;"
94      */
95     private final static Pattern sPattern2Line1 = Pattern.compile(
96             "^ERROR:\\s+(.+)$"); //$NON-NLS-1$
97     /**
98      * Second line of dual line aapt error.<br>
99      * "ERROR: &lt;error&gt;"<br>
100      * "Defined at file &lt;path&gt; line &lt;line&gt;"<br>
101      * @see #sPattern2Line1
102      */
103     private final static Pattern sPattern2Line2 = Pattern.compile(
104             "Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$
105     /**
106      * Single line aapt error<br>
107      * "&lt;path&gt; line &lt;line&gt;: &lt;error&gt;"
108      */
109     private final static Pattern sPattern3Line1 = Pattern.compile(
110             "^(.+)\\sline\\s(\\d+):\\s(.+)$"); //$NON-NLS-1$
111     /**
112      * First line of dual line aapt error.<br>
113      * "ERROR parsing XML file &lt;path&gt;"<br>
114      * "&lt;error&gt; at line &lt;line&gt;"
115      */
116     private final static Pattern sPattern4Line1 = Pattern.compile(
117             "^Error\\s+parsing\\s+XML\\s+file\\s(.+)$"); //$NON-NLS-1$
118     /**
119      * Second line of dual line aapt error.<br>
120      * "ERROR parsing XML file &lt;path&gt;"<br>
121      * "&lt;error&gt; at line &lt;line&gt;"<br>
122      * @see #sPattern4Line1
123      */
124     private final static Pattern sPattern4Line2 = Pattern.compile(
125             "^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$
126 
127     /**
128      * Single line aapt warning<br>
129      * "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
130      */
131     private final static Pattern sPattern5Line1 = Pattern.compile(
132             "^(.+?):(\\d+):\\s+WARNING:(.+)$"); //$NON-NLS-1$
133 
134     /**
135      * Single line aapt error<br>
136      * "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
137      */
138     private final static Pattern sPattern6Line1 = Pattern.compile(
139             "^(.+?):(\\d+):\\s+(.+)$"); //$NON-NLS-1$
140 
141     /**
142      * 4 line aapt error<br>
143      * "ERROR: 9-path image &lt;path&gt; malformed"<br>
144      * Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br>
145      * 'ERROR: failure processing &lt;path&gt;)
146      */
147     private final static Pattern sPattern7Line1 = Pattern.compile(
148             "^ERROR:\\s+9-patch\\s+image\\s+(.+)\\s+malformed\\.$"); //$NON-NLS-1$
149 
150     private final static Pattern sPattern8Line1 = Pattern.compile(
151             "^(invalid resource directory name): (.*)$"); //$NON-NLS-1$
152 
153     /**
154      * 2 line aapt error<br>
155      * "ERROR: Invalid configuration: foo"<br>
156      * "                              ^^^"<br>
157      * There's no need to parse the 2nd line.
158      */
159     private final static Pattern sPattern9Line1 = Pattern.compile(
160             "^Invalid configuration: (.+)$"); //$NON-NLS-1$
161 
162     /** SAX Parser factory. */
163     private SAXParserFactory mParserFactory;
164 
165     /**
166      * Base Resource Delta Visitor to handle XML error
167      */
168     protected static class BaseDeltaVisitor implements XmlErrorListener {
169 
170         /** The Xml builder used to validate XML correctness. */
171         protected BaseBuilder mBuilder;
172 
173         /**
174          * XML error flag. if true, we keep parsing the ResourceDelta but the
175          * compilation will not happen (we're putting markers)
176          */
177         public boolean mXmlError = false;
178 
BaseDeltaVisitor(BaseBuilder builder)179         public BaseDeltaVisitor(BaseBuilder builder) {
180             mBuilder = builder;
181         }
182 
183         /**
184          * Finds a matching Source folder for the current path. This checkds if the current path
185          * leads to, or is a source folder.
186          * @param sourceFolders The list of source folders
187          * @param pathSegments The segments of the current path
188          * @return The segments of the source folder, or null if no match was found
189          */
findMatchingSourceFolder(ArrayList<IPath> sourceFolders, String[] pathSegments)190         protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders,
191                 String[] pathSegments) {
192 
193             for (IPath p : sourceFolders) {
194                 // check if we are inside one of those source class path
195 
196                 // get the segments
197                 String[] srcSegments = p.segments();
198 
199                 // compare segments. We want the path of the resource
200                 // we're visiting to be
201                 boolean valid = true;
202                 int segmentCount = pathSegments.length;
203 
204                 for (int i = 0 ; i < segmentCount; i++) {
205                     String s1 = pathSegments[i];
206                     String s2 = srcSegments[i];
207 
208                     if (s1.equalsIgnoreCase(s2) == false) {
209                         valid = false;
210                         break;
211                     }
212                 }
213 
214                 if (valid) {
215                     // this folder, or one of this children is a source
216                     // folder!
217                     // we return its segments
218                     return srcSegments;
219                 }
220             }
221 
222             return null;
223         }
224 
225         /**
226          * Sent when an XML error is detected.
227          * @see XmlErrorListener
228          */
errorFound()229         public void errorFound() {
230             mXmlError = true;
231         }
232     }
233 
BaseBuilder()234     public BaseBuilder() {
235         super();
236         mParserFactory = SAXParserFactory.newInstance();
237 
238         // FIXME when the compiled XML support for namespace is in, set this to true.
239         mParserFactory.setNamespaceAware(false);
240     }
241 
242     /**
243      * Checks an Xml file for validity. Errors/warnings will be marked on the
244      * file
245      * @param resource the resource to check
246      * @param visitor a valid resource delta visitor
247      */
checkXML(IResource resource, BaseDeltaVisitor visitor)248     protected final void checkXML(IResource resource, BaseDeltaVisitor visitor) {
249 
250         // first make sure this is an xml file
251         if (resource instanceof IFile) {
252             IFile file = (IFile)resource;
253 
254             // remove previous markers
255             removeMarkersFromFile(file, AndroidConstants.MARKER_XML);
256 
257             // create  the error handler
258             XmlErrorHandler reporter = new XmlErrorHandler(file, visitor);
259             try {
260                 // parse
261                 getParser().parse(file.getContents(), reporter);
262             } catch (Exception e1) {
263             }
264         }
265     }
266 
267     /**
268      * Returns the SAXParserFactory, instantiating it first if it's not already
269      * created.
270      * @return the SAXParserFactory object
271      * @throws ParserConfigurationException
272      * @throws SAXException
273      */
getParser()274     protected final SAXParser getParser() throws ParserConfigurationException,
275             SAXException {
276         return mParserFactory.newSAXParser();
277     }
278 
279     /**
280      * Adds a marker to the current project.
281      *
282      * @param markerId The id of the marker to add.
283      * @param message the message associated with the mark
284      * @param severity the severity of the marker.
285      */
markProject(String markerId, String message, int severity)286     protected final void markProject(String markerId, String message, int severity) {
287         BaseProjectHelper.addMarker(getProject(), markerId, message, severity);
288     }
289 
290 
291     /**
292      * Removes markers from a file.
293      * @param file The file from which to delete the markers.
294      * @param markerId The id of the markers to remove. If null, all marker of
295      * type <code>IMarker.PROBLEM</code> will be removed.
296      */
removeMarkersFromFile(IFile file, String markerId)297     protected final void removeMarkersFromFile(IFile file, String markerId) {
298         try {
299             if (file.exists()) {
300                 file.deleteMarkers(markerId, true, IResource.DEPTH_ZERO);
301             }
302         } catch (CoreException ce) {
303             String msg = String.format(Messages.Marker_Delete_Error, markerId, file.toString());
304             AdtPlugin.printErrorToConsole(getProject(), msg);
305         }
306     }
307 
308     /**
309      * Removes markers from a container and its children.
310      * @param folder The container from which to delete the markers.
311      * @param markerId The id of the markers to remove. If null, all marker of
312      * type <code>IMarker.PROBLEM</code> will be removed.
313      */
removeMarkersFromContainer(IContainer folder, String markerId)314     protected final void removeMarkersFromContainer(IContainer folder, String markerId) {
315         try {
316             if (folder.exists()) {
317                 folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
318             }
319         } catch (CoreException ce) {
320             String msg = String.format(Messages.Marker_Delete_Error, markerId, folder.toString());
321             AdtPlugin.printErrorToConsole(getProject(), msg);
322         }
323     }
324 
325     /**
326      * Removes markers from a project and its children.
327      * @param project The project from which to delete the markers
328      * @param markerId The id of the markers to remove. If null, all marker of
329      * type <code>IMarker.PROBLEM</code> will be removed.
330      */
removeMarkersFromProject(IProject project, String markerId)331     protected final static void removeMarkersFromProject(IProject project,
332             String markerId) {
333         try {
334             if (project.exists()) {
335                 project.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
336             }
337         } catch (CoreException ce) {
338             String msg = String.format(Messages.Marker_Delete_Error, markerId, project.getName());
339             AdtPlugin.printErrorToConsole(project, msg);
340         }
341     }
342 
343     /**
344      * Get the stderr output of a process and return when the process is done.
345      * @param process The process to get the ouput from
346      * @param results The array to store the stderr output
347      * @return the process return code.
348      * @throws InterruptedException
349      */
grabProcessOutput(final Process process, final ArrayList<String> results)350     protected final int grabProcessOutput(final Process process,
351             final ArrayList<String> results)
352             throws InterruptedException {
353         // Due to the limited buffer size on windows for the standard io (stderr, stdout), we
354         // *need* to read both stdout and stderr all the time. If we don't and a process output
355         // a large amount, this could deadlock the process.
356 
357         // read the lines as they come. if null is returned, it's
358         // because the process finished
359         new Thread("") { //$NON-NLS-1$
360             @Override
361             public void run() {
362                 // create a buffer to read the stderr output
363                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
364                 BufferedReader errReader = new BufferedReader(is);
365 
366                 try {
367                     while (true) {
368                         String line = errReader.readLine();
369                         if (line != null) {
370                             results.add(line);
371                         } else {
372                             break;
373                         }
374                     }
375                 } catch (IOException e) {
376                     // do nothing.
377                 }
378             }
379         }.start();
380 
381         new Thread("") { //$NON-NLS-1$
382             @Override
383             public void run() {
384                 InputStreamReader is = new InputStreamReader(process.getInputStream());
385                 BufferedReader outReader = new BufferedReader(is);
386 
387                 IProject project = getProject();
388 
389                 try {
390                     while (true) {
391                         String line = outReader.readLine();
392                         if (line != null) {
393                             AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
394                                     project, line);
395                         } else {
396                             break;
397                         }
398                     }
399                 } catch (IOException e) {
400                     // do nothing.
401                 }
402             }
403 
404         }.start();
405 
406         // get the return code from the process
407         return process.waitFor();
408     }
409 
410     /**
411      * Parse the output of aapt and mark the incorrect file with error markers
412      *
413      * @param results the output of aapt
414      * @param project the project containing the file to mark
415      * @return true if the parsing failed, false if success.
416      */
parseAaptOutput(ArrayList<String> results, IProject project)417     protected final boolean parseAaptOutput(ArrayList<String> results,
418             IProject project) {
419         // nothing to parse? just return false;
420         if (results.size() == 0) {
421             return false;
422         }
423 
424         // get the root of the project so that we can make IFile from full
425         // file path
426         String osRoot = project.getLocation().toOSString();
427 
428         Matcher m;
429 
430         for (int i = 0; i < results.size(); i++) {
431             String p = results.get(i);
432 
433             m = sPattern0Line1.matcher(p);
434             if (m.matches()) {
435                 // we ignore those (as this is an ignore message from aapt)
436                 continue;
437             }
438 
439             m = sPattern1Line1.matcher(p);
440             if (m.matches()) {
441                 String lineStr = m.group(1);
442                 String msg = m.group(2);
443 
444                 // get the matcher for the next line.
445                 m = getNextLineMatcher(results, ++i, sPattern1Line2);
446                 if (m == null) {
447                     return true;
448                 }
449 
450                 String location = m.group(1);
451 
452                 // check the values and attempt to mark the file.
453                 if (checkAndMark(location, lineStr, msg, osRoot, project,
454                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
455                     return true;
456                 }
457                 continue;
458             }
459 
460             // this needs to be tested before Pattern2 since they both start with 'ERROR:'
461             m = sPattern7Line1.matcher(p);
462             if (m.matches()) {
463                 String location = m.group(1);
464                 String msg = p; // default msg is the line in case we don't find anything else
465 
466                 if (++i < results.size()) {
467                     msg = results.get(i).trim();
468                     if (++i < results.size()) {
469                         msg = msg + " - " + results.get(i).trim(); //$NON-NLS-1$
470 
471                         // skip the next line
472                         i++;
473                     }
474                 }
475 
476                 // display the error
477                 if (checkAndMark(location, null, msg, osRoot, project,
478                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
479                     return true;
480                 }
481 
482                 // success, go to the next line
483                 continue;
484             }
485 
486             m =  sPattern2Line1.matcher(p);
487             if (m.matches()) {
488                 // get the msg
489                 String msg = m.group(1);
490 
491                 // get the matcher for the next line.
492                 m = getNextLineMatcher(results, ++i, sPattern2Line2);
493                 if (m == null) {
494                     return true;
495                 }
496 
497                 String location = m.group(1);
498                 String lineStr = m.group(2);
499 
500                 // check the values and attempt to mark the file.
501                 if (checkAndMark(location, lineStr, msg, osRoot, project,
502                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
503                     return true;
504                 }
505                 continue;
506             }
507 
508             m = sPattern3Line1.matcher(p);
509             if (m.matches()) {
510                 String location = m.group(1);
511                 String lineStr = m.group(2);
512                 String msg = m.group(3);
513 
514                 // check the values and attempt to mark the file.
515                 if (checkAndMark(location, lineStr, msg, osRoot, project,
516                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
517                     return true;
518                 }
519 
520                 // success, go to the next line
521                 continue;
522             }
523 
524             m = sPattern4Line1.matcher(p);
525             if (m.matches()) {
526                 // get the filename.
527                 String location = m.group(1);
528 
529                 // get the matcher for the next line.
530                 m = getNextLineMatcher(results, ++i, sPattern4Line2);
531                 if (m == null) {
532                     return true;
533                 }
534 
535                 String msg = m.group(1);
536                 String lineStr = m.group(2);
537 
538                 // check the values and attempt to mark the file.
539                 if (checkAndMark(location, lineStr, msg, osRoot, project,
540                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
541                     return true;
542                 }
543 
544                 // success, go to the next line
545                 continue;
546             }
547 
548             m = sPattern5Line1.matcher(p);
549             if (m.matches()) {
550                 String location = m.group(1);
551                 String lineStr = m.group(2);
552                 String msg = m.group(3);
553 
554                 // check the values and attempt to mark the file.
555                 if (checkAndMark(location, lineStr, msg, osRoot, project,
556                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
557                     return true;
558                 }
559 
560                 // success, go to the next line
561                 continue;
562             }
563 
564             m = sPattern6Line1.matcher(p);
565             if (m.matches()) {
566                 String location = m.group(1);
567                 String lineStr = m.group(2);
568                 String msg = m.group(3);
569 
570                 // check the values and attempt to mark the file.
571                 if (checkAndMark(location, lineStr, msg, osRoot, project,
572                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
573                     return true;
574                 }
575 
576                 // success, go to the next line
577                 continue;
578             }
579 
580             m = sPattern8Line1.matcher(p);
581             if (m.matches()) {
582                 String location = m.group(2);
583                 String msg = m.group(1);
584 
585                 // check the values and attempt to mark the file.
586                 if (checkAndMark(location, null, msg, osRoot, project,
587                         AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
588                     return true;
589                 }
590 
591                 // success, go to the next line
592                 continue;
593             }
594 
595             m = sPattern9Line1.matcher(p);
596             if (m.matches()) {
597                 String badConfig = m.group(1);
598                 String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
599 
600                 // skip the next line
601                 i++;
602 
603                 // check the values and attempt to mark the file.
604                 if (checkAndMark(null /*location*/, null, msg, osRoot, project,
605                         AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
606                     return true;
607                 }
608 
609                 // success, go to the next line
610                 continue;
611             }
612 
613             // invalid line format, flag as error, and bail
614             return true;
615         }
616 
617         return false;
618     }
619 
620 
621 
622     /**
623      * Saves a String property into the persistent storage of the project.
624      * @param propertyName the name of the property. The id of the plugin is added to this string.
625      * @param value the value to save
626      * @return true if the save succeeded.
627      */
saveProjectStringProperty(String propertyName, String value)628     protected boolean saveProjectStringProperty(String propertyName, String value) {
629         IProject project = getProject();
630         return ProjectHelper.saveStringProperty(project, propertyName, value);
631     }
632 
633 
634     /**
635      * Loads a String property from the persistent storage of the project.
636      * @param propertyName the name of the property. The id of the plugin is added to this string.
637      * @return the property value or null if it was not found.
638      */
loadProjectStringProperty(String propertyName)639     protected String loadProjectStringProperty(String propertyName) {
640         IProject project = getProject();
641         return ProjectHelper.loadStringProperty(project, propertyName);
642     }
643 
644     /**
645      * Saves a property into the persistent storage of the project.
646      * @param propertyName the name of the property. The id of the plugin is added to this string.
647      * @param value the value to save
648      * @return true if the save succeeded.
649      */
saveProjectBooleanProperty(String propertyName, boolean value)650     protected boolean saveProjectBooleanProperty(String propertyName, boolean value) {
651         IProject project = getProject();
652         return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value));
653     }
654 
655     /**
656      * Loads a boolean property from the persistent storage of the project.
657      * @param propertyName the name of the property. The id of the plugin is added to this string.
658      * @param defaultValue The default value to return if the property was not found.
659      * @return the property value or the default value if the property was not found.
660      */
loadProjectBooleanProperty(String propertyName, boolean defaultValue)661     protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) {
662         IProject project = getProject();
663         return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue);
664     }
665 
666     /**
667      * Saves the path of a resource into the persistent storate of the project.
668      * @param propertyName the name of the property. The id of the plugin is added to this string.
669      * @param resource the resource which path is saved.
670      * @return true if the save succeeded
671      */
saveProjectResourceProperty(String propertyName, IResource resource)672     protected boolean saveProjectResourceProperty(String propertyName, IResource resource) {
673         return ProjectHelper.saveResourceProperty(getProject(), propertyName, resource);
674     }
675 
676     /**
677      * Loads the path of a resource from the persistent storage of the project, and returns the
678      * corresponding IResource object.
679      * @param propertyName the name of the property. The id of the plugin is added to this string.
680      * @return The corresponding IResource object (or children interface) or null
681      */
loadProjectResourceProperty(String propertyName)682     protected IResource loadProjectResourceProperty(String propertyName) {
683         IProject project = getProject();
684         return ProjectHelper.loadResourceProperty(project, propertyName);
685     }
686 
687     /**
688      * Check if the parameters gotten from the error output are valid, and mark
689      * the file with an AAPT marker.
690      * @param location the full OS path of the error file. If null, the project is marked
691      * @param lineStr
692      * @param message
693      * @param root The root directory of the project, in OS specific format.
694      * @param project
695      * @param markerId The marker id to put.
696      * @param severity The severity of the marker to put (IMarker.SEVERITY_*)
697      * @return true if the parameters were valid and the file was marked successfully.
698      *
699      * @see IMarker
700      */
checkAndMark(String location, String lineStr, String message, String root, IProject project, String markerId, int severity)701     private final  boolean checkAndMark(String location, String lineStr,
702             String message, String root, IProject project, String markerId, int severity) {
703         // check this is in fact a file
704         if (location != null) {
705             File f = new File(location);
706             if (f.exists() == false) {
707                 return false;
708             }
709         }
710 
711         // get the line number
712         int line = -1; // default value for error with no line.
713 
714         if (lineStr != null) {
715             try {
716                 line = Integer.parseInt(lineStr);
717             } catch (NumberFormatException e) {
718                 // looks like the string we extracted wasn't a valid
719                 // file number. Parsing failed and we return true
720                 return false;
721             }
722         }
723 
724         // add the marker
725         IResource f2 = project;
726         if (location != null) {
727             f2 = getResourceFromFullPath(location, root, project);
728             if (f2 == null) {
729                 return false;
730             }
731         }
732 
733         // check if there's a similar marker already, since aapt is launched twice
734         boolean markerAlreadyExists = false;
735         try {
736             IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
737 
738             for (IMarker marker : markers) {
739                 int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
740                 if (tmpLine != line) {
741                     break;
742                 }
743 
744                 int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1);
745                 if (tmpSeverity != severity) {
746                     break;
747                 }
748 
749                 String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
750                 if (tmpMsg == null || tmpMsg.equals(message) == false) {
751                     break;
752                 }
753 
754                 // if we're here, all the marker attributes are equals, we found it
755                 // and exit
756                 markerAlreadyExists = true;
757                 break;
758             }
759 
760         } catch (CoreException e) {
761             // if we couldn't get the markers, then we just mark the file again
762             // (since markerAlreadyExists is initialized to false, we do nothing)
763         }
764 
765         if (markerAlreadyExists == false) {
766             if (line != -1) {
767                 BaseProjectHelper.addMarker(f2, markerId, message, line,
768                         severity);
769             } else {
770                 BaseProjectHelper.addMarker(f2, markerId, message, severity);
771             }
772         }
773 
774         return true;
775     }
776 
777     /**
778      * Returns a matching matcher for the next line
779      * @param lines The array of lines
780      * @param nextIndex The index of the next line
781      * @param pattern The pattern to match
782      * @return null if error or no match, the matcher otherwise.
783      */
getNextLineMatcher(ArrayList<String> lines, int nextIndex, Pattern pattern)784     private final Matcher getNextLineMatcher(ArrayList<String> lines,
785             int nextIndex, Pattern pattern) {
786         // unless we can't, because we reached the last line
787         if (nextIndex == lines.size()) {
788             // we expected a 2nd line, so we flag as error
789             // and we bail
790             return null;
791         }
792 
793         Matcher m = pattern.matcher(lines.get(nextIndex));
794         if (m.matches()) {
795            return m;
796         }
797 
798         return null;
799     }
800 
getResourceFromFullPath(String filename, String root, IProject project)801     private IResource getResourceFromFullPath(String filename, String root,
802             IProject project) {
803         if (filename.startsWith(root)) {
804             String file = filename.substring(root.length());
805 
806             // get the resource
807             IResource r = project.findMember(file);
808 
809             // if the resource is valid, we add the marker
810             if (r.exists()) {
811                 return r;
812             }
813         }
814 
815         return null;
816     }
817 
818     /**
819      * Returns an array of external jar files used by the project.
820      * @return an array of OS-specific absolute file paths
821      */
getExternalJars()822     protected final String[] getExternalJars() {
823         // get the current project
824         IProject project = getProject();
825 
826         // get a java project from it
827         IJavaProject javaProject = JavaCore.create(project);
828 
829         IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
830 
831         ArrayList<String> oslibraryList = new ArrayList<String>();
832         IClasspathEntry[] classpaths = javaProject.readRawClasspath();
833         if (classpaths != null) {
834             for (IClasspathEntry e : classpaths) {
835                 if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
836                         e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
837                     // if this is a classpath variable reference, we resolve it.
838                     if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
839                         e = JavaCore.getResolvedClasspathEntry(e);
840                     }
841 
842                     // get the IPath
843                     IPath path = e.getPath();
844 
845                     // check the name ends with .jar
846                     if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
847                         boolean local = false;
848                         IResource resource = wsRoot.findMember(path);
849                         if (resource != null && resource.exists() &&
850                                 resource.getType() == IResource.FILE) {
851                             local = true;
852                             oslibraryList.add(resource.getLocation().toOSString());
853                         }
854 
855                         if (local == false) {
856                             // if the jar path doesn't match a workspace resource,
857                             // then we get an OSString and check if this links to a valid file.
858                             String osFullPath = path.toOSString();
859 
860                             File f = new File(osFullPath);
861                             if (f.exists()) {
862                                 oslibraryList.add(osFullPath);
863                             } else {
864                                 String message = String.format( Messages.Couldnt_Locate_s_Error,
865                                         path);
866                                 AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
867                                         project, message);
868 
869                                 // Also put a warning marker on the project
870                                 markProject(AdtConstants.MARKER_ADT, message,
871                                         IMarker.SEVERITY_WARNING);
872                             }
873                         }
874                     }
875                 }
876             }
877         }
878 
879         return oslibraryList.toArray(new String[oslibraryList.size()]);
880     }
881 
882     /**
883      * Aborts the build if the SDK/project setups are broken. This does not
884      * display any errors.
885      *
886      * @param project The {@link IJavaProject} being compiled.
887      * @throws CoreException
888      */
abortOnBadSetup(IProject project)889     protected void abortOnBadSetup(IProject project) throws CoreException {
890         // check if we have finished loading the SDK.
891         if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) {
892             // we exit silently
893             stopBuild("SDK is not loaded yet");
894         }
895 
896         // abort if there are TARGET or ADT type markers
897         IMarker[] markers = project.findMarkers(AdtConstants.MARKER_TARGET,
898                 false /*includeSubtypes*/, IResource.DEPTH_ZERO);
899 
900         if (markers.length > 0) {
901             stopBuild("");
902         }
903 
904         markers = project.findMarkers(AdtConstants.MARKER_ADT, false /*includeSubtypes*/,
905                 IResource.DEPTH_ZERO);
906 
907         if (markers.length > 0) {
908             stopBuild("");
909         }
910     }
911 
912     /**
913      * Throws an exception to cancel the build.
914      *
915      * @param error the error message
916      * @param args the printf-style arguments to the error message.
917      * @throws CoreException
918      */
stopBuild(String error, Object... args)919     protected final void stopBuild(String error, Object... args) throws CoreException {
920         throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID,
921                 String.format(error, args)));
922     }
923 
924     /**
925      * Recursively delete all the derived resources.
926      */
removeDerivedResources(IResource resource, IProgressMonitor monitor)927     protected void removeDerivedResources(IResource resource, IProgressMonitor monitor)
928             throws CoreException {
929         if (resource.exists()) {
930             if (resource.isDerived()) {
931                 resource.delete(true, new SubProgressMonitor(monitor, 10));
932             } else if (resource.getType() == IResource.FOLDER) {
933                 IFolder folder = (IFolder)resource;
934                 IResource[] members = folder.members();
935                 for (IResource member : members) {
936                     removeDerivedResources(member, monitor);
937                 }
938             }
939         }
940     }
941 }
942