1 /* 2 * Copyright (C) 2011 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; 18 19 import com.android.SdkConstants; 20 import com.android.annotations.NonNull; 21 import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; 22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 23 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 24 import com.android.sdklib.BuildToolInfo; 25 import com.android.sdklib.IAndroidTarget; 26 27 import org.eclipse.core.resources.IFile; 28 import org.eclipse.core.resources.IFolder; 29 import org.eclipse.core.resources.IProject; 30 import org.eclipse.core.resources.IResource; 31 import org.eclipse.core.resources.IWorkspaceRoot; 32 import org.eclipse.core.resources.ResourcesPlugin; 33 import org.eclipse.core.runtime.CoreException; 34 import org.eclipse.core.runtime.IPath; 35 import org.eclipse.core.runtime.IProgressMonitor; 36 import org.eclipse.jdt.core.IJavaProject; 37 38 import java.io.File; 39 import java.util.ArrayList; 40 import java.util.Collection; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Locale; 44 import java.util.Map; 45 import java.util.Set; 46 47 /** 48 * Base class to handle generated java code. 49 * 50 * It provides management for modified source file list, deleted source file list, reconciliation 51 * of previous lists, storing the current state of the build. 52 * 53 */ 54 public abstract class SourceProcessor { 55 56 public final static int COMPILE_STATUS_NONE = 0; 57 public final static int COMPILE_STATUS_CODE = 0x1; 58 public final static int COMPILE_STATUS_RES = 0x2; 59 60 /** List of all source files, their dependencies, and their output. */ 61 private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>(); 62 63 private final IJavaProject mJavaProject; 64 private BuildToolInfo mBuildToolInfo; 65 private final IFolder mGenFolder; 66 private final SourceChangeHandler mDeltaVisitor; 67 68 /** List of source files pending compilation at the next build */ 69 private final List<IFile> mToCompile = new ArrayList<IFile>(); 70 71 /** List of removed source files pending cleaning at the next build. */ 72 private final List<IFile> mRemoved = new ArrayList<IFile>(); 73 74 private int mLastCompilationStatus = COMPILE_STATUS_NONE; 75 76 /** 77 * Quotes a path inside "". If the platform is not windows, the path is returned as is. 78 * @param path the path to quote 79 * @return the quoted path. 80 */ 81 public static String quote(String path) { 82 if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) { 83 return "\"" + path + "\""; 84 } 85 86 return path; 87 } 88 89 protected SourceProcessor(@NonNull IJavaProject javaProject, 90 @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder, 91 @NonNull SourceChangeHandler deltaVisitor) { 92 mJavaProject = javaProject; 93 mBuildToolInfo = buildToolInfo; 94 mGenFolder = genFolder; 95 mDeltaVisitor = deltaVisitor; 96 97 mDeltaVisitor.init(this); 98 99 IProject project = javaProject.getProject(); 100 101 // get all the source files 102 buildSourceFileList(); 103 104 // load the known dependencies 105 loadOutputAndDependencies(); 106 107 boolean mustCompile = loadState(project); 108 109 // if we stored that we have to compile some files, we build the list that will compile them 110 // all. For now we have to reuse the full list since we don't know which files needed 111 // compilation. 112 if (mustCompile) { 113 mToCompile.addAll(mFiles.keySet()); 114 } 115 } 116 117 protected SourceProcessor(@NonNull IJavaProject javaProject, 118 @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder) { 119 this(javaProject, buildToolInfo, genFolder, new SourceChangeHandler()); 120 } 121 122 public void setBuildToolInfo(BuildToolInfo buildToolInfo) { 123 mBuildToolInfo = buildToolInfo; 124 } 125 126 127 /** 128 * Returns whether the given file is an output of this processor by return the source 129 * file that generated it. 130 * @param file the file to test. 131 * @return the source file that generated the given file or null. 132 */ 133 IFile isOutput(IFile file) { 134 for (SourceFileData data : mFiles.values()) { 135 if (data.generated(file)) { 136 return data.getSourceFile(); 137 } 138 } 139 140 return null; 141 } 142 143 /** 144 * Returns whether the given file is a dependency for other files by returning a list 145 * of file depending on the given file. 146 * @param file the file to test. 147 * @return a list of files that depend on the given file or an empty list if there 148 * are no matches. 149 */ 150 List<IFile> isDependency(IFile file) { 151 ArrayList<IFile> files = new ArrayList<IFile>(); 152 for (SourceFileData data : mFiles.values()) { 153 if (data.dependsOn(file)) { 154 files.add(data.getSourceFile()); 155 } 156 } 157 158 return files; 159 } 160 161 void addData(SourceFileData data) { 162 mFiles.put(data.getSourceFile(), data); 163 } 164 165 SourceFileData getFileData(IFile file) { 166 return mFiles.get(file); 167 } 168 169 Collection<SourceFileData> getAllFileData() { 170 return mFiles.values(); 171 } 172 173 public final SourceChangeHandler getChangeHandler() { 174 return mDeltaVisitor; 175 } 176 177 final IJavaProject getJavaProject() { 178 return mJavaProject; 179 } 180 181 final BuildToolInfo getBuildToolInfo() { 182 return mBuildToolInfo; 183 } 184 185 final IFolder getGenFolder() { 186 return mGenFolder; 187 } 188 189 final List<IFile> getToCompile() { 190 return mToCompile; 191 } 192 193 final List<IFile> getRemovedFile() { 194 return mRemoved; 195 } 196 197 final void addFileToCompile(IFile file) { 198 mToCompile.add(file); 199 } 200 201 public final void prepareFullBuild(IProject project) { 202 mDeltaVisitor.reset(); 203 204 mToCompile.clear(); 205 mRemoved.clear(); 206 207 // get all the source files 208 buildSourceFileList(); 209 210 mToCompile.addAll(mFiles.keySet()); 211 212 saveState(project); 213 } 214 215 public final void doneVisiting(IProject project) { 216 // merge the previous file modification lists and the new one. 217 mergeFileModifications(mDeltaVisitor); 218 219 mDeltaVisitor.reset(); 220 221 saveState(project); 222 } 223 224 /** 225 * Returns the extension of the source files handled by this processor. 226 * @return 227 */ 228 protected abstract Set<String> getExtensions(); 229 230 protected abstract String getSavePropertyName(); 231 232 /** 233 * Compiles the source files and return a status bitmask of the type of file that was generated. 234 * 235 */ 236 public final int compileFiles(BaseBuilder builder, 237 IProject project, IAndroidTarget projectTarget, 238 List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor) 239 throws CoreException { 240 241 mLastCompilationStatus = COMPILE_STATUS_NONE; 242 243 if (mToCompile.size() == 0 && mRemoved.size() == 0) { 244 return mLastCompilationStatus; 245 } 246 247 // if a source file is being removed before we managed to compile it, it'll be in 248 // both list. We *need* to remove it from the compile list or it'll never go away. 249 for (IFile sourceFile : mRemoved) { 250 int pos = mToCompile.indexOf(sourceFile); 251 if (pos != -1) { 252 mToCompile.remove(pos); 253 } 254 } 255 256 // list of files that have failed compilation. 257 List<IFile> stillNeedCompilation = new ArrayList<IFile>(); 258 259 doCompileFiles(mToCompile, builder, project, projectTarget, sourceFolders, 260 stillNeedCompilation, libraryProjectsOut, monitor); 261 262 mToCompile.clear(); 263 mToCompile.addAll(stillNeedCompilation); 264 265 // Remove the files created from source files that have been removed. 266 for (IFile sourceFile : mRemoved) { 267 // look if we already know the output 268 SourceFileData data = getFileData(sourceFile); 269 if (data != null) { 270 doRemoveFiles(data); 271 } 272 } 273 274 // remove the associated file data. 275 for (IFile removedFile : mRemoved) { 276 mFiles.remove(removedFile); 277 } 278 279 mRemoved.clear(); 280 281 // store the build state. If there are any files that failed to compile, we will 282 // force a full aidl compile on the next project open. (unless a full compilation succeed 283 // before the project is closed/re-opened.) 284 saveState(project); 285 286 return mLastCompilationStatus; 287 } 288 289 protected abstract void doCompileFiles( 290 List<IFile> filesToCompile, BaseBuilder builder, 291 IProject project, IAndroidTarget projectTarget, 292 List<IPath> sourceFolders, List<IFile> notCompiledOut, 293 List<File> libraryProjectsOut, IProgressMonitor monitor) throws CoreException; 294 295 /** 296 * Adds a compilation status. It can be any of (in combination too): 297 * <p/> 298 * {@link #COMPILE_STATUS_CODE} means this processor created source code files. 299 * {@link #COMPILE_STATUS_RES} means this process created resources. 300 */ 301 protected void setCompilationStatus(int status) { 302 mLastCompilationStatus |= status; 303 } 304 305 protected void doRemoveFiles(SourceFileData data) throws CoreException { 306 List<IFile> outputFiles = data.getOutputFiles(); 307 for (IFile outputFile : outputFiles) { 308 if (outputFile.exists()) { 309 outputFile.getLocation().toFile().delete(); 310 } 311 } 312 } 313 314 public final boolean loadState(IProject project) { 315 return ProjectHelper.loadBooleanProperty(project, getSavePropertyName(), 316 true /*defaultValue*/); 317 } 318 319 public final void saveState(IProject project) { 320 // TODO: Optimize by saving only the files that need compilation 321 ProjectHelper.saveStringProperty(project, getSavePropertyName(), 322 Boolean.toString(mToCompile.size() > 0)); 323 } 324 325 protected abstract void loadOutputAndDependencies(); 326 327 328 protected IPath getSourceFolderFor(IFile file) { 329 // find the source folder for the class so that we can infer the package from the 330 // difference between the file and its source folder. 331 List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(getJavaProject()); 332 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 333 334 for (IPath sourceFolderPath : sourceFolders) { 335 IFolder sourceFolder = root.getFolder(sourceFolderPath); 336 // we don't look in the 'gen' source folder as there will be no source in there. 337 if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { 338 // look for the source file parent, until we find this source folder. 339 IResource parent = file; 340 while ((parent = parent.getParent()) != null) { 341 if (parent.equals(sourceFolder)) { 342 return sourceFolderPath; 343 } 344 } 345 } 346 } 347 348 return null; 349 } 350 351 /** 352 * Goes through the build paths and fills the list of files to compile. 353 * 354 * @param project The project. 355 * @param sourceFolderPathList The list of source folder paths. 356 */ 357 private final void buildSourceFileList() { 358 mFiles.clear(); 359 360 IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); 361 List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(mJavaProject); 362 363 for (IPath sourceFolderPath : sourceFolderPathList) { 364 IFolder sourceFolder = root.getFolder(sourceFolderPath); 365 // we don't look in the 'gen' source folder as there will be no source in there. 366 if (sourceFolder.exists() && sourceFolder.equals(getGenFolder()) == false) { 367 scanFolderForSourceFiles(sourceFolder, sourceFolder); 368 } 369 } 370 } 371 372 /** 373 * Scans a folder and fills the list of files to compile. 374 * @param sourceFolder the root source folder. 375 * @param folder The folder to scan. 376 */ 377 private void scanFolderForSourceFiles(IFolder sourceFolder, IFolder folder) { 378 try { 379 IResource[] members = folder.members(); 380 for (IResource r : members) { 381 // get the type of the resource 382 switch (r.getType()) { 383 case IResource.FILE: { 384 // if this a file, check that the file actually exist 385 // and that it's the type of of file that's used in this processor 386 String extension = r.exists() ? r.getFileExtension() : null; 387 if (extension != null && 388 getExtensions().contains(extension.toLowerCase(Locale.US))) { 389 mFiles.put((IFile) r, new SourceFileData((IFile) r)); 390 } 391 break; 392 } 393 case IResource.FOLDER: 394 // recursively go through children 395 scanFolderForSourceFiles(sourceFolder, (IFolder)r); 396 break; 397 default: 398 // this would mean it's a project or the workspace root 399 // which is unlikely to happen. we do nothing 400 break; 401 } 402 } 403 } catch (CoreException e) { 404 // Couldn't get the members list for some reason. Just return. 405 } 406 } 407 408 409 /** 410 * Merge the current list of source file to compile/remove with the one coming from the 411 * delta visitor 412 * @param visitor the delta visitor. 413 */ 414 private void mergeFileModifications(SourceChangeHandler visitor) { 415 Set<IFile> toRemove = visitor.getRemovedFiles(); 416 Set<IFile> toCompile = visitor.getFilesToCompile(); 417 418 // loop through the new toRemove list, and add it to the old one, 419 // plus remove any file that was still to compile and that are now 420 // removed 421 for (IFile r : toRemove) { 422 if (mRemoved.indexOf(r) == -1) { 423 mRemoved.add(r); 424 } 425 426 int index = mToCompile.indexOf(r); 427 if (index != -1) { 428 mToCompile.remove(index); 429 } 430 } 431 432 // now loop through the new files to compile and add it to the list. 433 // Also look for them in the remove list, this would mean that they 434 // were removed, then added back, and we shouldn't remove them, just 435 // recompile them. 436 for (IFile r : toCompile) { 437 if (mToCompile.indexOf(r) == -1) { 438 mToCompile.add(r); 439 } 440 441 int index = mRemoved.indexOf(r); 442 if (index != -1) { 443 mRemoved.remove(index); 444 } 445 } 446 } 447 } 448