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