• 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.builders;
18 
19 import com.android.ide.common.sdk.LoadStatus;
20 import com.android.ide.eclipse.adt.AdtConstants;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 import com.android.ide.eclipse.adt.internal.build.BuildHelper;
23 import com.android.ide.eclipse.adt.internal.build.Messages;
24 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
25 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
26 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler;
27 import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener;
28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
29 import com.android.ide.eclipse.adt.io.IFileWrapper;
30 import com.android.io.IAbstractFile;
31 import com.android.io.StreamException;
32 import com.android.sdklib.IAndroidTarget;
33 
34 import org.eclipse.core.resources.IContainer;
35 import org.eclipse.core.resources.IFile;
36 import org.eclipse.core.resources.IFolder;
37 import org.eclipse.core.resources.IMarker;
38 import org.eclipse.core.resources.IProject;
39 import org.eclipse.core.resources.IResource;
40 import org.eclipse.core.resources.IncrementalProjectBuilder;
41 import org.eclipse.core.runtime.CoreException;
42 import org.eclipse.core.runtime.IPath;
43 import org.eclipse.core.runtime.IProgressMonitor;
44 import org.eclipse.core.runtime.jobs.Job;
45 import org.eclipse.jdt.core.IJavaProject;
46 import org.xml.sax.SAXException;
47 
48 import java.util.ArrayList;
49 
50 import javax.xml.parsers.ParserConfigurationException;
51 import javax.xml.parsers.SAXParser;
52 import javax.xml.parsers.SAXParserFactory;
53 
54 /**
55  * Base builder for XML files. This class allows for basic XML parsing with
56  * error checking and marking the files for errors/warnings.
57  */
58 public abstract class BaseBuilder extends IncrementalProjectBuilder {
59 
60     protected final static boolean DEBUG = false;
61 
62     /** SAX Parser factory. */
63     private SAXParserFactory mParserFactory;
64 
65     /**
66      * Base Resource Delta Visitor to handle XML error
67      */
68     protected static class BaseDeltaVisitor implements XmlErrorListener {
69 
70         /** The Xml builder used to validate XML correctness. */
71         protected BaseBuilder mBuilder;
72 
73         /**
74          * XML error flag. if true, we keep parsing the ResourceDelta but the
75          * compilation will not happen (we're putting markers)
76          */
77         public boolean mXmlError = false;
78 
BaseDeltaVisitor(BaseBuilder builder)79         public BaseDeltaVisitor(BaseBuilder builder) {
80             mBuilder = builder;
81         }
82 
83         /**
84          * Finds a matching Source folder for the current path. This checks if the current path
85          * leads to, or is a source folder.
86          * @param sourceFolders The list of source folders
87          * @param pathSegments The segments of the current path
88          * @return The segments of the source folder, or null if no match was found
89          */
findMatchingSourceFolder(ArrayList<IPath> sourceFolders, String[] pathSegments)90         protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders,
91                 String[] pathSegments) {
92 
93             for (IPath p : sourceFolders) {
94                 // check if we are inside one of those source class path
95 
96                 // get the segments
97                 String[] srcSegments = p.segments();
98 
99                 // compare segments. We want the path of the resource
100                 // we're visiting to be
101                 boolean valid = true;
102                 int segmentCount = pathSegments.length;
103 
104                 for (int i = 0 ; i < segmentCount; i++) {
105                     String s1 = pathSegments[i];
106                     String s2 = srcSegments[i];
107 
108                     if (s1.equalsIgnoreCase(s2) == false) {
109                         valid = false;
110                         break;
111                     }
112                 }
113 
114                 if (valid) {
115                     // this folder, or one of this children is a source
116                     // folder!
117                     // we return its segments
118                     return srcSegments;
119                 }
120             }
121 
122             return null;
123         }
124 
125         /**
126          * Sent when an XML error is detected.
127          * @see XmlErrorListener
128          */
129         @Override
errorFound()130         public void errorFound() {
131             mXmlError = true;
132         }
133     }
134 
135     protected static class AbortBuildException extends Exception {
136         private static final long serialVersionUID = 1L;
137     }
138 
BaseBuilder()139     public BaseBuilder() {
140         super();
141         mParserFactory = SAXParserFactory.newInstance();
142 
143         // FIXME when the compiled XML support for namespace is in, set this to true.
144         mParserFactory.setNamespaceAware(false);
145     }
146 
147     /**
148      * Checks an Xml file for validity. Errors/warnings will be marked on the
149      * file
150      * @param resource the resource to check
151      * @param visitor a valid resource delta visitor
152      */
checkXML(IResource resource, BaseDeltaVisitor visitor)153     protected final void checkXML(IResource resource, BaseDeltaVisitor visitor) {
154 
155         // first make sure this is an xml file
156         if (resource instanceof IFile) {
157             IFile file = (IFile)resource;
158 
159             // remove previous markers
160             removeMarkersFromResource(file, AdtConstants.MARKER_XML);
161 
162             // create  the error handler
163             XmlErrorHandler reporter = new XmlErrorHandler(file, visitor);
164             try {
165                 // parse
166                 getParser().parse(file.getContents(), reporter);
167             } catch (Exception e1) {
168             }
169         }
170     }
171 
172     /**
173      * Returns the SAXParserFactory, instantiating it first if it's not already
174      * created.
175      * @return the SAXParserFactory object
176      * @throws ParserConfigurationException
177      * @throws SAXException
178      */
getParser()179     protected final SAXParser getParser() throws ParserConfigurationException,
180             SAXException {
181         return mParserFactory.newSAXParser();
182     }
183 
184     /**
185      * Adds a marker to the current project.  This methods catches thrown {@link CoreException},
186      * and returns null instead.
187      *
188      * @param markerId The id of the marker to add.
189      * @param message the message associated with the mark
190      * @param severity the severity of the marker.
191      * @return the marker that was created (or null if failure)
192      * @see IMarker
193      */
markProject(String markerId, String message, int severity)194     protected final IMarker markProject(String markerId, String message, int severity) {
195         return BaseProjectHelper.markResource(getProject(), markerId, message, severity);
196     }
197 
198     /**
199      * Removes markers from a resource and only the resource (not its children).
200      * @param file The file from which to delete the markers.
201      * @param markerId The id of the markers to remove. If null, all marker of
202      * type <code>IMarker.PROBLEM</code> will be removed.
203      */
removeMarkersFromResource(IResource resource, String markerId)204     public final void removeMarkersFromResource(IResource resource, String markerId) {
205         try {
206             if (resource.exists()) {
207                 resource.deleteMarkers(markerId, true, IResource.DEPTH_ZERO);
208             }
209         } catch (CoreException ce) {
210             String msg = String.format(Messages.Marker_Delete_Error, markerId, resource.toString());
211             AdtPlugin.printErrorToConsole(getProject(), msg);
212         }
213     }
214 
215     /**
216      * Removes markers from a container and its children.
217      * @param folder The container from which to delete the markers.
218      * @param markerId The id of the markers to remove. If null, all marker of
219      * type <code>IMarker.PROBLEM</code> will be removed.
220      */
removeMarkersFromContainer(IContainer folder, String markerId)221     protected final void removeMarkersFromContainer(IContainer folder, String markerId) {
222         try {
223             if (folder.exists()) {
224                 folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
225             }
226         } catch (CoreException ce) {
227             String msg = String.format(Messages.Marker_Delete_Error, markerId, folder.toString());
228             AdtPlugin.printErrorToConsole(getProject(), msg);
229         }
230     }
231 
232     /**
233      * Get the stderr output of a process and return when the process is done.
234      * @param process The process to get the ouput from
235      * @param results The array to store the stderr output
236      * @return the process return code.
237      * @throws InterruptedException
238      */
grabProcessOutput(final Process process, final ArrayList<String> results)239     protected final int grabProcessOutput(final Process process,
240             final ArrayList<String> results) throws InterruptedException {
241         return BuildHelper.grabProcessOutput(getProject(), process, results);
242     }
243 
244 
245 
246     /**
247      * Saves a String property into the persistent storage of the project.
248      * @param propertyName the name of the property. The id of the plugin is added to this string.
249      * @param value the value to save
250      * @return true if the save succeeded.
251      */
saveProjectStringProperty(String propertyName, String value)252     protected boolean saveProjectStringProperty(String propertyName, String value) {
253         IProject project = getProject();
254         return ProjectHelper.saveStringProperty(project, propertyName, value);
255     }
256 
257 
258     /**
259      * Loads a String property from the persistent storage of the project.
260      * @param propertyName the name of the property. The id of the plugin is added to this string.
261      * @return the property value or null if it was not found.
262      */
loadProjectStringProperty(String propertyName)263     protected String loadProjectStringProperty(String propertyName) {
264         IProject project = getProject();
265         return ProjectHelper.loadStringProperty(project, propertyName);
266     }
267 
268     /**
269      * Saves a property into the persistent storage of the project.
270      * @param propertyName the name of the property. The id of the plugin is added to this string.
271      * @param value the value to save
272      * @return true if the save succeeded.
273      */
saveProjectBooleanProperty(String propertyName, boolean value)274     protected boolean saveProjectBooleanProperty(String propertyName, boolean value) {
275         IProject project = getProject();
276         return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value));
277     }
278 
279     /**
280      * Loads a boolean property from the persistent storage of the project.
281      * @param propertyName the name of the property. The id of the plugin is added to this string.
282      * @param defaultValue The default value to return if the property was not found.
283      * @return the property value or the default value if the property was not found.
284      */
loadProjectBooleanProperty(String propertyName, boolean defaultValue)285     protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) {
286         IProject project = getProject();
287         return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue);
288     }
289 
290     /**
291      * Aborts the build if the SDK/project setups are broken. This does not
292      * display any errors.
293      *
294      * @param javaProject The {@link IJavaProject} being compiled.
295      * @throws CoreException
296      */
abortOnBadSetup(IJavaProject javaProject)297     protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
298         IProject iProject = javaProject.getProject();
299         // check if we have finished loading the project target.
300         Sdk sdk = Sdk.getCurrent();
301         if (sdk == null) {
302             throw new AbortBuildException();
303         }
304 
305         // get the target for the project
306         IAndroidTarget target = sdk.getTarget(javaProject.getProject());
307 
308         if (target == null) {
309             throw new AbortBuildException();
310         }
311 
312         // check on the target data.
313         if (sdk.checkAndLoadTargetData(target, javaProject) != LoadStatus.LOADED) {
314             throw new AbortBuildException();
315        }
316 
317         // abort if there are TARGET or ADT type markers
318         stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO,
319                 false /*checkSeverity*/);
320         stopOnMarker(iProject, AdtConstants.MARKER_ADT, IResource.DEPTH_ZERO,
321                 false /*checkSeverity*/);
322     }
323 
stopOnMarker(IProject project, String markerType, int depth, boolean checkSeverity)324     protected void stopOnMarker(IProject project, String markerType, int depth,
325             boolean checkSeverity)
326             throws AbortBuildException {
327         try {
328             IMarker[] markers = project.findMarkers(markerType, false /*includeSubtypes*/, depth);
329 
330             if (markers.length > 0) {
331                 if (checkSeverity == false) {
332                     throw new AbortBuildException();
333                 } else {
334                     for (IMarker marker : markers) {
335                         int severity = marker.getAttribute(IMarker.SEVERITY, -1 /*defaultValue*/);
336                         if (severity == IMarker.SEVERITY_ERROR) {
337                             throw new AbortBuildException();
338                         }
339                     }
340                 }
341             }
342         } catch (CoreException e) {
343             // don't stop, something's really screwed up and the build will break later with
344             // a better error message.
345         }
346     }
347 
348     /**
349      * Handles a {@link StreamException} by logging the info and marking the project.
350      * This should generally be followed by exiting the build process.
351      *
352      * @param e the exception
353      */
handleStreamException(StreamException e)354     protected void handleStreamException(StreamException e) {
355         IAbstractFile file = e.getFile();
356 
357         String msg;
358 
359         IResource target = getProject();
360         if (file instanceof IFileWrapper) {
361             target = ((IFileWrapper) file).getIFile();
362 
363             if (e.getError() == StreamException.Error.OUTOFSYNC) {
364                 msg = "File is Out of sync";
365             } else {
366                 msg = "Error reading file. Read log for details";
367             }
368 
369         } else {
370             if (e.getError() == StreamException.Error.OUTOFSYNC) {
371                 msg = String.format("Out of sync file: %s", file.getOsLocation());
372             } else {
373                 msg = String.format("Error reading file %s. Read log for details",
374                         file.getOsLocation());
375             }
376         }
377 
378         AdtPlugin.logAndPrintError(e, getProject().getName(), msg);
379         BaseProjectHelper.markResource(target, AdtConstants.MARKER_ADT, msg,
380                 IMarker.SEVERITY_ERROR);
381     }
382 
383     /**
384      * Handles a generic {@link Throwable} by logging the info and marking the project.
385      * This should generally be followed by exiting the build process.
386      *
387      * @param t the {@link Throwable}.
388      * @param message the message to log and to associate with the marker.
389      */
handleException(Throwable t, String message)390     protected void handleException(Throwable t, String message) {
391         AdtPlugin.logAndPrintError(t, getProject().getName(), message);
392         markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
393     }
394 
395     /**
396      * Recursively delete all the derived resources from a root resource. The root resource is not
397      * deleted.
398      * @param rootResource the root resource
399      * @param monitor a progress monitor.
400      * @throws CoreException
401      *
402      */
removeDerivedResources(IResource rootResource, IProgressMonitor monitor)403     protected void removeDerivedResources(IResource rootResource, IProgressMonitor monitor)
404             throws CoreException {
405         removeDerivedResources(rootResource, false, monitor);
406     }
407 
408     /**
409      * delete a resource and its children. returns true if the root resource was deleted. All
410      * sub-folders *will* be deleted if they were emptied (not if they started empty).
411      * @param rootResource the root resource
412      * @param deleteRoot whether to delete the root folder.
413      * @param monitor a progress monitor.
414      * @throws CoreException
415      */
removeDerivedResources(IResource rootResource, boolean deleteRoot, IProgressMonitor monitor)416     private void removeDerivedResources(IResource rootResource, boolean deleteRoot,
417             IProgressMonitor monitor) throws CoreException {
418         if (rootResource.exists()) {
419             // if it's a folder, delete derived member.
420             if (rootResource.getType() == IResource.FOLDER) {
421                 IFolder folder = (IFolder)rootResource;
422                 IResource[] members = folder.members();
423                 boolean wasNotEmpty = members.length > 0;
424                 for (IResource member : members) {
425                     removeDerivedResources(member, true /*deleteRoot*/, monitor);
426                 }
427 
428                 // if the folder had content that is now all removed, delete the folder.
429                 if (deleteRoot && wasNotEmpty && folder.members().length == 0) {
430                     rootResource.getLocation().toFile().delete();
431                 }
432             }
433 
434             // if the root resource is derived, delete it.
435             if (rootResource.isDerived()) {
436                 rootResource.getLocation().toFile().delete();
437             }
438         }
439     }
440 
launchJob(Job newJob)441     protected void launchJob(Job newJob) {
442         newJob.setPriority(Job.BUILD);
443         newJob.schedule();
444     }
445 }
446