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.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 
     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          */
     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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    441     protected void launchJob(Job newJob) {
    442         newJob.setPriority(Job.BUILD);
    443         newJob.schedule();
    444     }
    445 }
    446