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.eclipse.adt.AdtConstants; 20 import com.android.ide.eclipse.adt.AdtPlugin; 21 import com.android.ide.eclipse.adt.internal.build.Messages; 22 import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler; 23 import com.android.ide.eclipse.adt.internal.build.SourceProcessor; 24 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor; 25 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 26 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 27 import com.android.ide.eclipse.adt.io.IFileWrapper; 28 import com.android.sdklib.SdkConstants; 29 import com.android.sdklib.xml.ManifestData; 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.IResource; 35 import org.eclipse.core.resources.IResourceDelta; 36 import org.eclipse.core.resources.IResourceDeltaVisitor; 37 import org.eclipse.core.resources.IWorkspaceRoot; 38 import org.eclipse.core.resources.ResourcesPlugin; 39 import org.eclipse.core.runtime.CoreException; 40 import org.eclipse.core.runtime.IPath; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** 46 * Resource Delta visitor for the pre-compiler. 47 * <p/>This delta visitor only cares about files that are the source or the result of actions of the 48 * {@link PreCompilerBuilder}: 49 * <ul><li>R.java/Manifest.java generated by compiling the resources</li> 50 * <li>Any Java files generated by <code>aidl</code></li></ul>. 51 * 52 * Therefore it looks for the following: 53 * <ul><li>Any modification in the resource folder</li> 54 * <li>Removed files from the source folder receiving generated Java files</li> 55 * <li>Any modification to aidl files.</li> 56 * 57 */ 58 class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor { 59 60 // Result fields. 61 /** 62 * Compile flag. This is set to true if one of the changed/added/removed 63 * files is Manifest.xml, Manifest.java, or R.java. All other file changes 64 * will be taken care of by ResourceManager. 65 */ 66 private boolean mCompileResources = false; 67 68 /** Manifest check/parsing flag. */ 69 private boolean mCheckedManifestXml = false; 70 71 /** Application Package, gathered from the parsing of the manifest */ 72 private String mJavaPackage = null; 73 /** minSDKVersion attribute value, gathered from the parsing of the manifest */ 74 private String mMinSdkVersion = null; 75 76 // Internal usage fields. 77 /** 78 * In Resource folder flag. This allows us to know if we're in the 79 * resource folder. 80 */ 81 private boolean mInRes = false; 82 83 /** 84 * Current Source folder. This allows us to know if we're in a source 85 * folder, and which folder. 86 */ 87 private IFolder mSourceFolder = null; 88 89 /** List of source folders. */ 90 private final List<IPath> mSourceFolders; 91 private boolean mIsGenSourceFolder = false; 92 93 private final List<SourceChangeHandler> mSourceChangeHandlers = 94 new ArrayList<SourceChangeHandler>(); 95 private final IWorkspaceRoot mRoot; 96 97 98 99 public PreCompilerDeltaVisitor(BaseBuilder builder, List<IPath> sourceFolders, 100 List<SourceProcessor> processors) { 101 super(builder); 102 mSourceFolders = sourceFolders; 103 mRoot = ResourcesPlugin.getWorkspace().getRoot(); 104 105 for (SourceProcessor processor : processors) { 106 SourceChangeHandler handler = processor.getChangeHandler(); 107 mSourceChangeHandlers.add(handler); 108 } 109 } 110 111 /** 112 * Get whether Manifest.java, Manifest.xml, or R.java have changed 113 * @return true if any of Manifest.xml, Manifest.java, or R.java have been modified 114 */ 115 public boolean getCompileResources() { 116 return mCompileResources; 117 } 118 119 /** 120 * Returns whether the manifest file was parsed/checked for error during the resource delta 121 * visiting. 122 */ 123 public boolean getCheckedManifestXml() { 124 return mCheckedManifestXml; 125 } 126 127 /** 128 * Returns the manifest package if the manifest was checked/parsed. 129 * <p/> 130 * This can return null in two cases: 131 * <ul> 132 * <li>The manifest was not part of the resource change delta, and the manifest was 133 * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li> 134 * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>), 135 * but the package declaration is missing</li> 136 * </ul> 137 * @return the manifest package or null. 138 */ 139 public String getManifestPackage() { 140 return mJavaPackage; 141 } 142 143 /** 144 * Returns the minSDkVersion attribute from the manifest if it was checked/parsed. 145 * <p/> 146 * This can return null in two cases: 147 * <ul> 148 * <li>The manifest was not part of the resource change delta, and the manifest was 149 * not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li> 150 * <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>), 151 * but the package declaration is missing</li> 152 * </ul> 153 * @return the minSdkVersion or null. 154 */ 155 public String getMinSdkVersion() { 156 return mMinSdkVersion; 157 } 158 159 /* 160 * (non-Javadoc) 161 * 162 * @see org.eclipse.core.resources.IResourceDeltaVisitor 163 * #visit(org.eclipse.core.resources.IResourceDelta) 164 */ 165 @Override 166 public boolean visit(IResourceDelta delta) throws CoreException { 167 // we are only going to look for changes in res/, source folders and in 168 // AndroidManifest.xml since the delta visitor goes through the main 169 // folder before its children we can check when the path segment 170 // count is 2 (format will be /$Project/folder) and make sure we are 171 // processing res/, source folders or AndroidManifest.xml 172 173 IResource resource = delta.getResource(); 174 IPath path = resource.getFullPath(); 175 String[] segments = path.segments(); 176 177 // since the delta visitor also visits the root we return true if 178 // segments.length = 1 179 if (segments.length == 1) { 180 // this is always the Android project since we call 181 // Builder#getDelta(IProject) on the project itself. 182 return true; 183 } else if (segments.length == 2) { 184 // if we are at an item directly under the root directory, 185 // then we are not yet in a source or resource folder 186 mInRes = false; 187 mSourceFolder = null; 188 189 if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) { 190 // this is the resource folder that was modified. we want to 191 // see its content. 192 193 // since we're going to visit its children next, we set the 194 // flag 195 mInRes = true; 196 mSourceFolder = null; 197 return true; 198 } else if (SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(segments[1])) { 199 // any change in the manifest could trigger a new R.java 200 // class, so we don't need to check the delta kind 201 if (delta.getKind() != IResourceDelta.REMOVED) { 202 // clean the error markers on the file. 203 IFile manifestFile = (IFile)resource; 204 205 if (manifestFile.exists()) { 206 manifestFile.deleteMarkers(AdtConstants.MARKER_XML, true, 207 IResource.DEPTH_ZERO); 208 manifestFile.deleteMarkers(AdtConstants.MARKER_ANDROID, true, 209 IResource.DEPTH_ZERO); 210 } 211 212 // parse the manifest for data and error 213 ManifestData manifestData = AndroidManifestHelper.parse( 214 new IFileWrapper(manifestFile), true /*gatherData*/, this); 215 216 if (manifestData != null) { 217 mJavaPackage = manifestData.getPackage(); 218 mMinSdkVersion = manifestData.getMinSdkVersionString(); 219 } 220 221 mCheckedManifestXml = true; 222 } 223 mCompileResources = true; 224 225 // we don't want to go to the children, not like they are 226 // any for this resource anyway. 227 return false; 228 } 229 } 230 231 // at this point we can either be in the source folder or in the 232 // resource folder or in a different folder that contains a source 233 // folder. 234 // This is due to not all source folder being src/. Some could be 235 // something/somethingelse/src/ 236 237 // so first we test if we already know we are in a source or 238 // resource folder. 239 240 if (mSourceFolder != null) { 241 // if we are in the res folder, we are looking for the following changes: 242 // - added/removed/modified aidl files. 243 // - missing R.java file 244 245 // if the resource is a folder, we just go straight to the children 246 if (resource.getType() == IResource.FOLDER) { 247 return true; 248 } 249 250 if (resource.getType() != IResource.FILE) { 251 return false; 252 } 253 IFile file = (IFile)resource; 254 255 // get the modification kind 256 int kind = delta.getKind(); 257 258 // we process normal source folder and the 'gen' source folder differently. 259 if (mIsGenSourceFolder) { 260 // this is the generated java file source folder. 261 // - if R.java/Manifest.java are removed/modified, we recompile the resources 262 // - if aidl files are removed/modified, we recompile them. 263 264 boolean outputWarning = false; 265 266 String fileName = resource.getName(); 267 268 // Special case of R.java/Manifest.java. 269 if (AdtConstants.FN_RESOURCE_CLASS.equals(fileName) || 270 AdtConstants.FN_MANIFEST_CLASS.equals(fileName)) { 271 // if it was removed, there's a possibility that it was removed due to a 272 // package change, or an aidl that was removed, but the only thing 273 // that will happen is that we'll have an extra build. Not much of a problem. 274 mCompileResources = true; 275 276 // we want a warning 277 outputWarning = true; 278 } else { 279 // look to see if this file was generated by a processor. 280 for (SourceChangeHandler handler : mSourceChangeHandlers) { 281 if (handler.handleGeneratedFile(file, kind)) { 282 outputWarning = true; 283 break; // there shouldn't be 2 processors that handle the same file. 284 } 285 } 286 } 287 288 if (outputWarning) { 289 if (kind == IResourceDelta.REMOVED) { 290 // We pring an error just so that it's red, but it's just a warning really. 291 String msg = String.format(Messages.s_Removed_Recreating_s, fileName); 292 AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); 293 } else if (kind == IResourceDelta.CHANGED) { 294 // the file was modified manually! we can't allow it. 295 String msg = String.format(Messages.s_Modified_Manually_Recreating_s, 296 fileName); 297 AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); 298 } 299 } 300 } else { 301 // this is another source folder. 302 for (SourceChangeHandler handler : mSourceChangeHandlers) { 303 handler.handleSourceFile(file, kind); 304 } 305 } 306 307 // no children. 308 return false; 309 } else if (mInRes) { 310 // if we are in the res folder, we are looking for the following 311 // changes: 312 // - added/removed/modified xml files. 313 // - added/removed files of any other type 314 315 // if the resource is a folder, we just go straight to the 316 // children 317 if (resource.getType() == IResource.FOLDER) { 318 return true; 319 } 320 321 // get the extension of the resource 322 String ext = resource.getFileExtension(); 323 int kind = delta.getKind(); 324 325 String p = resource.getProjectRelativePath().toString(); 326 String message = null; 327 switch (kind) { 328 case IResourceDelta.CHANGED: 329 // display verbose message 330 message = String.format(Messages.s_Modified_Recreating_s, p); 331 break; 332 case IResourceDelta.ADDED: 333 // display verbose message 334 message = String.format(Messages.Added_s_s_Needs_Updating, p, 335 AdtConstants.FN_RESOURCE_CLASS); 336 break; 337 case IResourceDelta.REMOVED: 338 // display verbose message 339 message = String.format(Messages.s_Removed_s_Needs_Updating, p, 340 AdtConstants.FN_RESOURCE_CLASS); 341 break; 342 } 343 if (message != null) { 344 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, 345 mBuilder.getProject(), message); 346 } 347 348 for (SourceChangeHandler handler : mSourceChangeHandlers) { 349 handler.handleResourceFile((IFile)resource, kind); 350 } 351 // If it's an XML resource, check the syntax 352 if (AdtConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) { 353 // check xml Validity 354 mBuilder.checkXML(resource, this); 355 } 356 // Whether or not to generate R.java for a changed resource is taken care of by the 357 // Resource Manager. 358 } else if (resource instanceof IFolder) { 359 // in this case we may be inside a folder that contains a source 360 // folder, go through the list of known source folders 361 362 for (IPath sourceFolderPath : mSourceFolders) { 363 // first check if they match exactly. 364 if (sourceFolderPath.equals(path)) { 365 // this is a source folder! 366 mInRes = false; 367 mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above 368 mIsGenSourceFolder = path.segmentCount() == 2 && 369 path.segment(1).equals(SdkConstants.FD_GEN_SOURCES); 370 return true; 371 } 372 373 // check if we are on the way to a source folder. 374 int count = sourceFolderPath.matchingFirstSegments(path); 375 if (count == path.segmentCount()) { 376 mInRes = false; 377 return true; 378 } 379 } 380 381 // if we're here, we are visiting another folder 382 // like /$Project/bin/ for instance (we get notified for changes 383 // in .class!) 384 // This could also be another source folder and we have found 385 // R.java in a previous source folder 386 // We don't want to visit its children 387 return false; 388 } 389 390 return false; 391 } 392 393 /** 394 * Returns a handle to the folder identified by the given path in this container. 395 * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non 396 * null object only if the resource actually exists and is a folder (and not a file) 397 * @param path the path of the folder to return. 398 * @return a handle to the folder if it exists, or null otherwise. 399 */ 400 private IFolder getFolder(IPath path) { 401 IResource resource = mRoot.findMember(path); 402 if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { 403 return (IFolder)resource; 404 } 405 406 return null; 407 } 408 409 } 410