Home | History | Annotate | Download | only in builders
      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.sdklib.IAndroidTarget;
     30 
     31 import org.eclipse.core.resources.IContainer;
     32 import org.eclipse.core.resources.IFile;
     33 import org.eclipse.core.resources.IFolder;
     34 import org.eclipse.core.resources.IMarker;
     35 import org.eclipse.core.resources.IProject;
     36 import org.eclipse.core.resources.IResource;
     37 import org.eclipse.core.resources.IncrementalProjectBuilder;
     38 import org.eclipse.core.runtime.CoreException;
     39 import org.eclipse.core.runtime.IPath;
     40 import org.eclipse.core.runtime.IProgressMonitor;
     41 import org.eclipse.jdt.core.IJavaProject;
     42 import org.xml.sax.SAXException;
     43 
     44 import java.util.ArrayList;
     45 
     46 import javax.xml.parsers.ParserConfigurationException;
     47 import javax.xml.parsers.SAXParser;
     48 import javax.xml.parsers.SAXParserFactory;
     49 
     50 /**
     51  * Base builder for XML files. This class allows for basic XML parsing with
     52  * error checking and marking the files for errors/warnings.
     53  */
     54 public abstract class BaseBuilder extends IncrementalProjectBuilder {
     55 
     56 
     57     /** SAX Parser factory. */
     58     private SAXParserFactory mParserFactory;
     59 
     60     /**
     61      * Base Resource Delta Visitor to handle XML error
     62      */
     63     protected static class BaseDeltaVisitor implements XmlErrorListener {
     64 
     65         /** The Xml builder used to validate XML correctness. */
     66         protected BaseBuilder mBuilder;
     67 
     68         /**
     69          * XML error flag. if true, we keep parsing the ResourceDelta but the
     70          * compilation will not happen (we're putting markers)
     71          */
     72         public boolean mXmlError = false;
     73 
     74         public BaseDeltaVisitor(BaseBuilder builder) {
     75             mBuilder = builder;
     76         }
     77 
     78         /**
     79          * Finds a matching Source folder for the current path. This checks if the current path
     80          * leads to, or is a source folder.
     81          * @param sourceFolders The list of source folders
     82          * @param pathSegments The segments of the current path
     83          * @return The segments of the source folder, or null if no match was found
     84          */
     85         protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders,
     86                 String[] pathSegments) {
     87 
     88             for (IPath p : sourceFolders) {
     89                 // check if we are inside one of those source class path
     90 
     91                 // get the segments
     92                 String[] srcSegments = p.segments();
     93 
     94                 // compare segments. We want the path of the resource
     95                 // we're visiting to be
     96                 boolean valid = true;
     97                 int segmentCount = pathSegments.length;
     98 
     99                 for (int i = 0 ; i < segmentCount; i++) {
    100                     String s1 = pathSegments[i];
    101                     String s2 = srcSegments[i];
    102 
    103                     if (s1.equalsIgnoreCase(s2) == false) {
    104                         valid = false;
    105                         break;
    106                     }
    107                 }
    108 
    109                 if (valid) {
    110                     // this folder, or one of this children is a source
    111                     // folder!
    112                     // we return its segments
    113                     return srcSegments;
    114                 }
    115             }
    116 
    117             return null;
    118         }
    119 
    120         /**
    121          * Sent when an XML error is detected.
    122          * @see XmlErrorListener
    123          */
    124         public void errorFound() {
    125             mXmlError = true;
    126         }
    127     }
    128 
    129     protected static class AbortBuildException extends Exception {
    130         private static final long serialVersionUID = 1L;
    131     }
    132 
    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      */
    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             removeMarkersFromResource(file, AdtConstants.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      */
    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      */
    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 resource and only the resource (not its children).
    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      */
    198     public final void removeMarkersFromResource(IResource resource, String markerId) {
    199         try {
    200             if (resource.exists()) {
    201                 resource.deleteMarkers(markerId, true, IResource.DEPTH_ZERO);
    202             }
    203         } catch (CoreException ce) {
    204             String msg = String.format(Messages.Marker_Delete_Error, markerId, resource.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      */
    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      * Get the stderr output of a process and return when the process is done.
    228      * @param process The process to get the ouput from
    229      * @param results The array to store the stderr output
    230      * @return the process return code.
    231      * @throws InterruptedException
    232      */
    233     protected final int grabProcessOutput(final Process process,
    234             final ArrayList<String> results) throws InterruptedException {
    235         return BuildHelper.grabProcessOutput(getProject(), process, results);
    236     }
    237 
    238 
    239 
    240     /**
    241      * Saves a String property into the persistent storage of the project.
    242      * @param propertyName the name of the property. The id of the plugin is added to this string.
    243      * @param value the value to save
    244      * @return true if the save succeeded.
    245      */
    246     protected boolean saveProjectStringProperty(String propertyName, String value) {
    247         IProject project = getProject();
    248         return ProjectHelper.saveStringProperty(project, propertyName, value);
    249     }
    250 
    251 
    252     /**
    253      * Loads a String property from the persistent storage of the project.
    254      * @param propertyName the name of the property. The id of the plugin is added to this string.
    255      * @return the property value or null if it was not found.
    256      */
    257     protected String loadProjectStringProperty(String propertyName) {
    258         IProject project = getProject();
    259         return ProjectHelper.loadStringProperty(project, propertyName);
    260     }
    261 
    262     /**
    263      * Saves a property into the persistent storage of the project.
    264      * @param propertyName the name of the property. The id of the plugin is added to this string.
    265      * @param value the value to save
    266      * @return true if the save succeeded.
    267      */
    268     protected boolean saveProjectBooleanProperty(String propertyName, boolean value) {
    269         IProject project = getProject();
    270         return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value));
    271     }
    272 
    273     /**
    274      * Loads a boolean property from the persistent storage of the project.
    275      * @param propertyName the name of the property. The id of the plugin is added to this string.
    276      * @param defaultValue The default value to return if the property was not found.
    277      * @return the property value or the default value if the property was not found.
    278      */
    279     protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) {
    280         IProject project = getProject();
    281         return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue);
    282     }
    283 
    284     /**
    285      * Aborts the build if the SDK/project setups are broken. This does not
    286      * display any errors.
    287      *
    288      * @param javaProject The {@link IJavaProject} being compiled.
    289      * @throws CoreException
    290      */
    291     protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
    292         IProject iProject = javaProject.getProject();
    293         // check if we have finished loading the project target.
    294         Sdk sdk = Sdk.getCurrent();
    295         if (sdk == null) {
    296             throw new AbortBuildException();
    297         }
    298 
    299         // get the target for the project
    300         IAndroidTarget target = sdk.getTarget(javaProject.getProject());
    301 
    302         if (target == null) {
    303             throw new AbortBuildException();
    304         }
    305 
    306         // check on the target data.
    307         if (sdk.checkAndLoadTargetData(target, javaProject) != LoadStatus.LOADED) {
    308             throw new AbortBuildException();
    309        }
    310 
    311         // abort if there are TARGET or ADT type markers
    312         stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO,
    313                 false /*checkSeverity*/);
    314         stopOnMarker(iProject, AdtConstants.MARKER_ADT, IResource.DEPTH_ZERO,
    315                 false /*checkSeverity*/);
    316     }
    317 
    318     protected void stopOnMarker(IProject project, String markerType, int depth,
    319             boolean checkSeverity)
    320             throws AbortBuildException {
    321         try {
    322             IMarker[] markers = project.findMarkers(markerType, false /*includeSubtypes*/, depth);
    323 
    324             if (markers.length > 0) {
    325                 if (checkSeverity == false) {
    326                     throw new AbortBuildException();
    327                 } else {
    328                     for (IMarker marker : markers) {
    329                         int severity = marker.getAttribute(IMarker.SEVERITY, -1 /*defaultValue*/);
    330                         if (severity == IMarker.SEVERITY_ERROR) {
    331                             throw new AbortBuildException();
    332                         }
    333                     }
    334                 }
    335             }
    336         } catch (CoreException e) {
    337             // don't stop, something's really screwed up and the build will break later with
    338             // a better error message.
    339         }
    340     }
    341 
    342     /**
    343      * Recursively delete all the derived resources from a root resource. The root resource is not
    344      * deleted.
    345      * @param rootResource the root resource
    346      * @param monitor a progress monitor.
    347      * @throws CoreException
    348      *
    349      */
    350     protected void removeDerivedResources(IResource rootResource, IProgressMonitor monitor)
    351             throws CoreException {
    352         removeDerivedResources(rootResource, false, monitor);
    353     }
    354 
    355     /**
    356      * delete a resource and its children. returns true if the root resource was deleted. All
    357      * sub-folders *will* be deleted if they were emptied (not if they started empty).
    358      * @param rootResource the root resource
    359      * @param deleteRoot whether to delete the root folder.
    360      * @param monitor a progress monitor.
    361      * @throws CoreException
    362      */
    363     private void removeDerivedResources(IResource rootResource, boolean deleteRoot,
    364             IProgressMonitor monitor) throws CoreException {
    365         if (rootResource.exists()) {
    366             // if it's a folder, delete derived member.
    367             if (rootResource.getType() == IResource.FOLDER) {
    368                 IFolder folder = (IFolder)rootResource;
    369                 IResource[] members = folder.members();
    370                 boolean wasNotEmpty = members.length > 0;
    371                 for (IResource member : members) {
    372                     removeDerivedResources(member, true /*deleteRoot*/, monitor);
    373                 }
    374 
    375                 // if the folder had content that is now all removed, delete the folder.
    376                 if (deleteRoot && wasNotEmpty && folder.members().length == 0) {
    377                     rootResource.getLocation().toFile().delete();
    378                 }
    379             }
    380 
    381             // if the root resource is derived, delete it.
    382             if (rootResource.isDerived()) {
    383                 rootResource.getLocation().toFile().delete();
    384             }
    385         }
    386     }
    387 }
    388