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