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