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 public boolean visit(IResourceDelta delta) throws CoreException { 166 // we are only going to look for changes in res/, source folders and in 167 // AndroidManifest.xml since the delta visitor goes through the main 168 // folder before its children we can check when the path segment 169 // count is 2 (format will be /$Project/folder) and make sure we are 170 // processing res/, source folders or AndroidManifest.xml 171 172 IResource resource = delta.getResource(); 173 IPath path = resource.getFullPath(); 174 String[] segments = path.segments(); 175 176 // since the delta visitor also visits the root we return true if 177 // segments.length = 1 178 if (segments.length == 1) { 179 // this is always the Android project since we call 180 // Builder#getDelta(IProject) on the project itself. 181 return true; 182 } else if (segments.length == 2) { 183 // if we are at an item directly under the root directory, 184 // then we are not yet in a source or resource folder 185 mInRes = false; 186 mSourceFolder = null; 187 188 if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) { 189 // this is the resource folder that was modified. we want to 190 // see its content. 191 192 // since we're going to visit its children next, we set the 193 // flag 194 mInRes = true; 195 mSourceFolder = null; 196 return true; 197 } else if (SdkConstants.FN_ANDROID_MANIFEST_XML.equalsIgnoreCase(segments[1])) { 198 // any change in the manifest could trigger a new R.java 199 // class, so we don't need to check the delta kind 200 if (delta.getKind() != IResourceDelta.REMOVED) { 201 // clean the error markers on the file. 202 IFile manifestFile = (IFile)resource; 203 204 if (manifestFile.exists()) { 205 manifestFile.deleteMarkers(AdtConstants.MARKER_XML, true, 206 IResource.DEPTH_ZERO); 207 manifestFile.deleteMarkers(AdtConstants.MARKER_ANDROID, true, 208 IResource.DEPTH_ZERO); 209 } 210 211 // parse the manifest for data and error 212 ManifestData manifestData = AndroidManifestHelper.parse( 213 new IFileWrapper(manifestFile), true /*gatherData*/, this); 214 215 if (manifestData != null) { 216 mJavaPackage = manifestData.getPackage(); 217 mMinSdkVersion = manifestData.getMinSdkVersionString(); 218 } 219 220 mCheckedManifestXml = true; 221 } 222 mCompileResources = true; 223 224 // we don't want to go to the children, not like they are 225 // any for this resource anyway. 226 return false; 227 } 228 } 229 230 // at this point we can either be in the source folder or in the 231 // resource folder or in a different folder that contains a source 232 // folder. 233 // This is due to not all source folder being src/. Some could be 234 // something/somethingelse/src/ 235 236 // so first we test if we already know we are in a source or 237 // resource folder. 238 239 if (mSourceFolder != null) { 240 // if we are in the res folder, we are looking for the following changes: 241 // - added/removed/modified aidl files. 242 // - missing R.java file 243 244 // if the resource is a folder, we just go straight to the children 245 if (resource.getType() == IResource.FOLDER) { 246 return true; 247 } 248 249 if (resource.getType() != IResource.FILE) { 250 return false; 251 } 252 IFile file = (IFile)resource; 253 254 // get the modification kind 255 int kind = delta.getKind(); 256 257 // we process normal source folder and the 'gen' source folder differently. 258 if (mIsGenSourceFolder) { 259 // this is the generated java file source folder. 260 // - if R.java/Manifest.java are removed/modified, we recompile the resources 261 // - if aidl files are removed/modified, we recompile them. 262 263 boolean outputWarning = false; 264 265 String fileName = resource.getName(); 266 267 // Special case of R.java/Manifest.java. 268 if (AdtConstants.FN_RESOURCE_CLASS.equals(fileName) || 269 AdtConstants.FN_MANIFEST_CLASS.equals(fileName)) { 270 // if it was removed, there's a possibility that it was removed due to a 271 // package change, or an aidl that was removed, but the only thing 272 // that will happen is that we'll have an extra build. Not much of a problem. 273 mCompileResources = true; 274 275 // we want a warning 276 outputWarning = true; 277 } else { 278 // look to see if this file was generated by a processor. 279 for (SourceChangeHandler handler : mSourceChangeHandlers) { 280 if (handler.handleGeneratedFile(file, kind)) { 281 outputWarning = true; 282 break; // there shouldn't be 2 processors that handle the same file. 283 } 284 } 285 } 286 287 if (outputWarning) { 288 if (kind == IResourceDelta.REMOVED) { 289 // We pring an error just so that it's red, but it's just a warning really. 290 String msg = String.format(Messages.s_Removed_Recreating_s, fileName); 291 AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); 292 } else if (kind == IResourceDelta.CHANGED) { 293 // the file was modified manually! we can't allow it. 294 String msg = String.format(Messages.s_Modified_Manually_Recreating_s, 295 fileName); 296 AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); 297 } 298 } 299 } else { 300 // this is another source folder. 301 for (SourceChangeHandler handler : mSourceChangeHandlers) { 302 handler.handleSourceFile(file, kind); 303 } 304 } 305 306 // no children. 307 return false; 308 } else if (mInRes) { 309 // if we are in the res folder, we are looking for the following 310 // changes: 311 // - added/removed/modified xml files. 312 // - added/removed files of any other type 313 314 // if the resource is a folder, we just go straight to the 315 // children 316 if (resource.getType() == IResource.FOLDER) { 317 return true; 318 } 319 320 // get the extension of the resource 321 String ext = resource.getFileExtension(); 322 int kind = delta.getKind(); 323 324 String p = resource.getProjectRelativePath().toString(); 325 String message = null; 326 switch (kind) { 327 case IResourceDelta.CHANGED: 328 // display verbose message 329 message = String.format(Messages.s_Modified_Recreating_s, p); 330 break; 331 case IResourceDelta.ADDED: 332 // display verbose message 333 message = String.format(Messages.Added_s_s_Needs_Updating, p, 334 AdtConstants.FN_RESOURCE_CLASS); 335 break; 336 case IResourceDelta.REMOVED: 337 // display verbose message 338 message = String.format(Messages.s_Removed_s_Needs_Updating, p, 339 AdtConstants.FN_RESOURCE_CLASS); 340 break; 341 } 342 if (message != null) { 343 AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, 344 mBuilder.getProject(), message); 345 } 346 347 for (SourceChangeHandler handler : mSourceChangeHandlers) { 348 handler.handleResourceFile((IFile)resource, kind); 349 } 350 // If it's an XML resource, check the syntax 351 if (AdtConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) { 352 // check xml Validity 353 mBuilder.checkXML(resource, this); 354 } 355 // Whether or not to generate R.java for a changed resource is taken care of by the 356 // Resource Manager. 357 } else if (resource instanceof IFolder) { 358 // in this case we may be inside a folder that contains a source 359 // folder, go through the list of known source folders 360 361 for (IPath sourceFolderPath : mSourceFolders) { 362 // first check if they match exactly. 363 if (sourceFolderPath.equals(path)) { 364 // this is a source folder! 365 mInRes = false; 366 mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above 367 mIsGenSourceFolder = path.segmentCount() == 2 && 368 path.segment(1).equals(SdkConstants.FD_GEN_SOURCES); 369 return true; 370 } 371 372 // check if we are on the way to a source folder. 373 int count = sourceFolderPath.matchingFirstSegments(path); 374 if (count == path.segmentCount()) { 375 mInRes = false; 376 return true; 377 } 378 } 379 380 // if we're here, we are visiting another folder 381 // like /$Project/bin/ for instance (we get notified for changes 382 // in .class!) 383 // This could also be another source folder and we have found 384 // R.java in a previous source folder 385 // We don't want to visit its children 386 return false; 387 } 388 389 return false; 390 } 391 392 /** 393 * Returns a handle to the folder identified by the given path in this container. 394 * <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non 395 * null object only if the resource actually exists and is a folder (and not a file) 396 * @param path the path of the folder to return. 397 * @return a handle to the folder if it exists, or null otherwise. 398 */ 399 private IFolder getFolder(IPath path) { 400 IResource resource = mRoot.findMember(path); 401 if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { 402 return (IFolder)resource; 403 } 404 405 return null; 406 } 407 408 } 409