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