KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > apt > core > internal > generatedfile > GeneratedFileManager


1 /*******************************************************************************
2  * Copyright (c) 2005, 2007 BEA Systems, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * mkaufman@bea.com - initial API and implementation
10  * wharley@bea.com - refactored, and reinstated reconcile-time type gen
11  *******************************************************************************/

12
13 package org.eclipse.jdt.apt.core.internal.generatedfile;
14
15 import java.io.BufferedInputStream JavaDoc;
16 import java.io.ByteArrayInputStream JavaDoc;
17 import java.io.IOException JavaDoc;
18 import java.io.InputStream JavaDoc;
19 import java.util.ArrayList JavaDoc;
20 import java.util.Collection JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Set JavaDoc;
27 import java.util.regex.Pattern JavaDoc;
28
29 import org.eclipse.core.resources.IContainer;
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IFolder;
32 import org.eclipse.core.resources.IMarker;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.runtime.CoreException;
35 import org.eclipse.core.runtime.IPath;
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.jdt.apt.core.internal.AptPlugin;
38 import org.eclipse.jdt.apt.core.internal.AptProject;
39 import org.eclipse.jdt.apt.core.internal.Messages;
40 import org.eclipse.jdt.apt.core.internal.util.FileSystemUtil;
41 import org.eclipse.jdt.apt.core.internal.util.ManyToMany;
42 import org.eclipse.jdt.core.ElementChangedEvent;
43 import org.eclipse.jdt.core.ICompilationUnit;
44 import org.eclipse.jdt.core.IJavaModelStatusConstants;
45 import org.eclipse.jdt.core.IJavaProject;
46 import org.eclipse.jdt.core.IPackageFragment;
47 import org.eclipse.jdt.core.IPackageFragmentRoot;
48 import org.eclipse.jdt.core.JavaCore;
49 import org.eclipse.jdt.core.JavaModelException;
50 import org.eclipse.jdt.core.WorkingCopyOwner;
51
52 /**
53  * This class is used for managing generated files; in particular, keeping track of
54  * dependencies so that no-longer-generated files can be deleted, and managing the
55  * lifecycle of working copies in memory.
56  * <p>
57  * During build, a generated file may be a "type", in the sense of a generated Java source
58  * file, or it may be a generated class file or an arbitrary resource (such as an XML
59  * file). During reconcile, it is only possible to generate Java source files. Also,
60  * during reconcile, it is not possible to write to disk or delete files from disk; all
61  * operations take place in memory only, using "working copies" provided by the Java
62  * Model.
63  *
64  * <h2>DATA STRUCTURES</h2>
65  * <code>_buildDeps</code> is a many-to-many map that tracks which parent files
66  * are responsible for which generated files. Entries in this map are created when files
67  * are created during builds. This map is serialized so that dependencies can be reloaded
68  * when a project is opened without having to do a full build.
69  * <p>
70  * When types are generated during reconcile, they are not actually laid down on disk (ie
71  * we do not commit the working copy). However, the file handles are still used as keys
72  * into the various maps in this case.
73  * <p>
74  * <code>_reconcileDeps</code> is the reconcile-time analogue of
75  * <code>_buildDeps</code>. This map is not serialized.
76  * <p>
77  * Given a working copy, it is easy to determine the IFile that it models by calling
78  * <code>ICompilationUnit.getResource()</code>. To go the other way, we store maps
79  * of IFile to ICompilationUnit. Working copies that represent generated types are
80  * stored in <code>_reconcileGenTypes</code>; working copies that represent deleted types
81  * are stored in <code>_hiddenBuiltTypes</code>.
82  * <p>
83  * List invariants: for the many-to-many maps, every forward entry must correspond to a
84  * reverse entry; this is managed (and verified) by the ManyToMany map code. Also, every
85  * entry in the <code>_reconcileGenTypes</code> list must correspond to an entry in the
86  * <code>_reconcileDeps</code> map. There can be no overlap between these
87  * entries and the <code>_hiddenBuiltTypes</code> map. Whenever a working copy is placed
88  * into this overall collection, it must have <code>becomeWorkingCopy()</code> called on
89  * it; whenever it is removed, it must have <code>discardWorkingCopy()</code> called on
90  * it.
91  *
92  * <h2>SYNCHRONIZATION NOTES</h2>
93  * Synchronization around the GeneratedFileManager's maps uses the GeneratedFileMap
94  * instance's monitor. When acquiring this monitor, DO NOT PERFORM ANY OPERATIONS THAT
95  * TAKE ANY OTHER LOCKS (e.g., java model operations, or file system operations like
96  * creating or deleting a file or folder). If you do this, then the code is subject to
97  * deadlock situations. For example, a resource-changed listener may take a resource lock
98  * and then call into the GeneratedFileManager for clean-up, where your code could reverse
99  * the order in which the locks are taken. This is bad, so be careful.
100  *
101  * <h2>RECONCILE vs. BUILD</h2>
102  * Reconciles are based on in-memory type information, i.e., working copies. Builds are
103  * based on files on disk. At any given moment, a build thread and any number of reconcile
104  * threads may be executing. All share the same GeneratedFileManager object, but each
105  * thread will have a separate BuildEnvironment. Multiple files are built in a loop, with
106  * files generated on one round being compiled (and possibly generating new files) on the
107  * next; only one file at a time is reconciled, but when a file is generated during
108  * reconcile it will invoke a recursive call to reconcile, with a unique
109  * ReconcileBuildEnvironment.
110  * <p>
111  * What is the relationship between reconcile-time dependency information and build-time
112  * dependency information? In general, there is one set of dependency maps for build time
113  * information and a separate set for reconcile time information (with the latter being
114  * shared by all reconcile threads). Reconciles do not write to build-time information,
115  * nor do they write to the disk. Builds, however, may have to interact with
116  * reconcile-time info. The tricky bit is that a change to a file "B.java" in the
117  * foreground editor window might affect the way that background file "A.java" generates
118  * "AGen.java". That is, editing B.java is in effect making A.java dirty; but the Eclipse
119  * build system has no way of knowing that, so A will not be reconciled.
120  * <p>
121  * The nearest Eclipse analogy is to refactoring, where a refactor operation in the
122  * foreground editor can modify background files; Eclipse solves this problem by requiring
123  * that all files be saved before and after a refactoring operation, but that solution is
124  * not practical for the simple case of making edits to a file that might happen to be an
125  * annotation processing dependency. The JSR269 API works around this problem by letting
126  * processors state these out-of-band dependencies explicitly, but com.sun.mirror.apt has
127  * no such mechanism.
128  * <p>
129  * The approach taken here is that when a build is performed, we discard the working
130  * copies of any files that are open in editors but that are not dirty (meaning the file
131  * on disk is the same as the version in the editor). This still means that if file A is
132  * dirty, AGen will not be updated even when B is edited; thus, making a breaking change
133  * to A and then making a change to B that is supposed to fix the break will not work.
134  */

135 public class GeneratedFileManager
136 {
137     
138     /**
139      * Access to the package fragment root for generated types.
140      * Encapsulated into this class so that synchronization can be guaranteed.
141      */

142     private class GeneratedPackageFragmentRoot {
143         
144         // The name and root are returned as a single object to ensure synchronization.
145
final class NameAndRoot {
146             final String JavaDoc name;
147             final IPackageFragmentRoot root;
148             NameAndRoot(String JavaDoc name, IPackageFragmentRoot root) {
149                 this.name = name;
150                 this.root = root;
151             }
152         }
153         
154         private IPackageFragmentRoot _root = null;
155         
156         private String JavaDoc _folderName = null;
157         
158         /**
159          * Get the package fragment root and the name of the folder
160          * it corresponds to. If the folder is not on the classpath,
161          * the root will be null.
162          */

163         public synchronized NameAndRoot get() {
164             return new NameAndRoot(_folderName, _root);
165         }
166         
167         /**
168          * Force the package fragment root and folder name to be recalculated.
169          * Check whether the new folder is actually on the classpath; if not,
170          * set root to be null.
171          */

172         public synchronized void set() {
173             IFolder genFolder = _gsfm.getFolder();
174             _root = null;
175             if (_jProject.isOnClasspath(genFolder)) {
176                 _root = _jProject.getPackageFragmentRoot(genFolder);
177             }
178             _folderName = genFolder.getProjectRelativePath().toString();
179         }
180     }
181
182     /**
183      * If true, when buffer contents are updated during a reconcile, reconcile() will
184      * be called on the new contents. This is not necessary to update the open editor,
185      * but if the generated file is itself a parent file, it will cause recursive
186      * type generation.
187      */

188     private static final boolean RECURSIVE_RECONCILE = true;
189
190     /**
191      * Disable type generation during reconcile. In the past, reconcile-time type
192      * generation caused deadlocks; see (BEA internal) Radar bug #238684. As of
193      * Eclipse 3.3 this should work.
194      */

195     private static final boolean GENERATE_TYPE_DURING_RECONCILE = true;
196     
197     /**
198      * If true, the integrity of internal data structures will be verified after various
199      * operations are performed.
200      */

201     private static final boolean ENABLE_INTEGRITY_CHECKS = true;
202     
203     /**
204      * A singleton instance of CompilationUnitHelper, which encapsulates operations on working copies.
205      */

206     private static final CompilationUnitHelper _CUHELPER = new CompilationUnitHelper();
207     
208     /**
209      * The regex delimiter used to parse package names.
210      */

211     private static final Pattern JavaDoc _PACKAGE_DELIMITER = Pattern.compile("\\."); //$NON-NLS-1$
212

213     static {
214         // register element-changed listener to clean up working copies
215
int mask = ElementChangedEvent.POST_CHANGE;
216         JavaCore.addElementChangedListener(new WorkingCopyCleanupListener(), mask);
217     }
218
219     /**
220      * Many-to-many map from parent files to files generated during build. These files all
221      * exist on disk. This map is used to keep track of dependencies during build, and is
222      * read-only during reconcile. This map is serialized.
223      */

224     private final GeneratedFileMap _buildDeps;
225     
226     /**
227      * Set of files that have been generated during build by processors that
228      * support reconcile-time type generation. Files in this set are expected to
229      * be generated during reconcile, and therefore will be deleted after a reconcile
230      * if they're not generated. This is different from the value set of
231      * _reconcileDeps in that the contents of this set are known to have been
232      * generated during a build.
233      */

234     private final Set JavaDoc<IFile> _clearDuringReconcile;
235
236     /**
237      * Many-to-many map from parent files to files generated during reconcile.
238      * Both the keys and the values may correspond to files that exist on disk or only in
239      * memory. This map is used to keep track of dependencies created during reconcile,
240      * and is not accessed during build. This map is not serialized.
241      */

242     private final ManyToMany<IFile, IFile> _reconcileDeps;
243     
244     /**
245      * Many-to-many map from parent files to files that are generated in build but not
246      * during reconcile. We need this so we can tell parents that were never reconciled
247      * (meaning their generated children on disk are valid) from parents that have been
248      * edited so that they no longer generate their children (meaning the generated
249      * children may need to be removed from the typesystem). This map is not serialized.
250      */

251     private final ManyToMany<IFile, IFile> _reconcileNonDeps;
252
253     /**
254      * Map of types that were generated during build but are being hidden (removed from
255      * the reconcile-time typesystem) by blank WorkingCopies. These are tracked separately
256      * from regular working copies for the sake of clarity. The keys all correspond to
257      * files that exist on disk; if they didn't, there would be no reason for an entry.
258      * <p>
259      * This is a map of file to working copy of that file, <strong>NOT</strong> a map of
260      * parents to generated children. The keys in this map are a subset of the values in
261      * {@link #_reconcileNonDeps}. This map exists so that given a file, we can find the
262      * working copy that represents it.
263      * <p>
264      * Every working copy exists either in this map or in {@link #_hiddenBuiltTypes}, but
265      * not in both. These maps exist to track the lifecycle of a working copy. When a new
266      * working copy is created, {@link ICompilationUnit#becomeWorkingCopy()} is called. If
267      * an entry is removed from this map without being added to the other,
268      * {@link ICompilationUnit#discardWorkingCopy()} must be called.
269      *
270      * @see #_reconcileGenTypes
271      */

272     private final Map JavaDoc<IFile, ICompilationUnit> _hiddenBuiltTypes;
273
274     /**
275      * Cache of working copies (in-memory types created or modified during reconcile).
276      * Includes working copies that represent changes to types that were generated during
277      * a build and thus exist on disk, as well as working copies for types newly generated
278      * during reconcile that thus do not exist on disk.
279      * <p>
280      * This is a map of file to working copy of that file, <strong>NOT</strong> a map of
281      * parents to generated children. There is a 1:1 correspondence between keys in this
282      * map and values in {@link #_reconcileDeps}. This map exists so that given a file,
283      * we can find the working copy that represents it.
284      * <p>
285      * Every working copy exists either in this map or in {@link #_hiddenBuiltTypes}, but
286      * not in both. These maps exist to track the lifecycle of a working copy. When a new
287      * working copy is created, {@link ICompilationUnit#becomeWorkingCopy()} is called. If
288      * an entry is removed from this map without being added to the other,
289      * {@link ICompilationUnit#discardWorkingCopy()} must be called.
290      *
291      * @see #_hiddenBuiltTypes
292      */

293     private final Map JavaDoc<IFile, ICompilationUnit> _reconcileGenTypes;
294
295     /**
296      * Access to the package fragment root for generated types. Encapsulated into a
297      * helper class in order to ensure synchronization.
298      */

299     private final GeneratedPackageFragmentRoot _generatedPackageFragmentRoot;
300
301     private final IJavaProject _jProject;
302
303     private final GeneratedSourceFolderManager _gsfm;
304
305     /**
306      * Initialized when the build starts, and accessed during type generation.
307      * This has the same lifecycle as _generatedPackageFragmentRoot.
308      * If there is a configuration problem, this may be set to <code>true</code>
309      * during generation of the first type to prevent any other types from
310      * being generated.
311      */

312     private boolean _skipTypeGeneration = false;
313
314     /**
315      * Clients should not instantiate this class; it is created only by {@link AptProject}.
316      */

317     public GeneratedFileManager(final AptProject aptProject, final GeneratedSourceFolderManager gsfm) {
318         _jProject = aptProject.getJavaProject();
319         _gsfm = gsfm;
320         _buildDeps = new GeneratedFileMap(_jProject.getProject());
321         _clearDuringReconcile = new HashSet JavaDoc<IFile>();
322         _reconcileDeps = new ManyToMany<IFile, IFile>();
323         _reconcileNonDeps = new ManyToMany<IFile, IFile>();
324         _hiddenBuiltTypes = new HashMap JavaDoc<IFile, ICompilationUnit>();
325         _reconcileGenTypes = new HashMap JavaDoc<IFile, ICompilationUnit>();
326         _generatedPackageFragmentRoot = new GeneratedPackageFragmentRoot();
327     }
328
329     /**
330      * Add a non-Java-source entry to the build-time dependency maps. Java source files are added to
331      * the maps when they are generated, as by {@link #generateFileDuringBuild}, but files of other
332      * types must be added explicitly by the code that creates the file.
333      * <p>
334      * This method must only be called during build, not reconcile. It is not possible to add
335      * non-Java-source files during reconcile.
336      */

337     public void addGeneratedFileDependency(Collection JavaDoc<IFile> parentFiles, IFile generatedFile)
338     {
339         addBuiltFileToMaps(parentFiles, generatedFile, false);
340     }
341
342     /**
343      * Called at the start of build in order to cache our package fragment root
344      */

345     public void compilationStarted()
346     {
347         try {
348             // clear out any generated source folder config markers
349
IMarker[] markers = _jProject.getProject().findMarkers(AptPlugin.APT_CONFIG_PROBLEM_MARKER, true,
350                     IResource.DEPTH_INFINITE);
351             if (markers != null) {
352                 for (IMarker marker : markers)
353                     marker.delete();
354             }
355         } catch (CoreException e) {
356             AptPlugin.log(e, "Unable to delete configuration marker."); //$NON-NLS-1$
357
}
358         _skipTypeGeneration = false;
359         _gsfm.ensureFolderExists();
360         _generatedPackageFragmentRoot.set();
361
362     }
363     
364     /**
365      * This method should only be used for testing purposes to ensure that maps contain
366      * entries when we expect them to.
367      */

368     public synchronized boolean containsWorkingCopyMapEntriesForParent(IFile f)
369     {
370         return _reconcileDeps.containsKey(f);
371     }
372
373     /**
374      * Invoked at the end of a build to delete files that are no longer parented by
375      * <code>parentFile</code>. Files that are multiply parented will not actually be
376      * deleted, but the association from this parent to the generated file will be
377      * removed, so that when the last parent ceases to generate a given file it will be
378      * deleted at that time.
379      *
380      * @param newlyGeneratedFiles
381      * the set of files generated by <code>parentFile</code> on the most
382      * recent compilation; these files will be spared deletion.
383      * @return the set of source files that were actually deleted, or an empty set.
384      * The returned set does not include non-source (e.g. text or xml) files.
385      */

386     public Set JavaDoc<IFile> deleteObsoleteFilesAfterBuild(IFile parentFile, Set JavaDoc<IFile> newlyGeneratedFiles)
387     {
388         Set JavaDoc<IFile> deleted;
389         List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>();
390         Set JavaDoc<IFile> toReport = new HashSet JavaDoc<IFile>();
391         deleted = computeObsoleteFiles(parentFile, newlyGeneratedFiles, toDiscard, toReport);
392         
393         for (IFile toDelete : deleted) {
394             if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
395                     "deleted obsolete file during build: " + toDelete); //$NON-NLS-1$
396
deletePhysicalFile(toDelete);
397         }
398         
399         // Discard blank WCs *after* we delete the corresponding files:
400
// we don't want the type to become briefly visible to a reconcile thread.
401
for (ICompilationUnit wcToDiscard : toDiscard) {
402             _CUHELPER.discardWorkingCopy(wcToDiscard);
403         }
404
405         return toReport;
406     }
407
408     /**
409      * Invoked at the end of a reconcile to get rid of any files that are no longer being
410      * generated. If the file existed on disk, we can't actually delete it, we can only
411      * create a blank WorkingCopy to hide it. Therefore, we can only remove Java source
412      * files, not arbitrary files. If the file was generated during reconcile and exists
413      * only in memory, we can actually remove it altogether.
414      * <p>
415      * Only some processors specify (via {@link org.eclipse.jdt.apt.core.util.AptPreferenceConstants#RTTG_ENABLED_OPTION})
416      * that they support type generation during reconcile. We need to remove obsolete
417      * files generated by those processors, but preserve files generated by
418      * other processors.
419      *
420      * @param parentWC
421      * the WorkingCopy being reconciled
422      * @param newlyGeneratedFiles
423      * the complete list of files generated during the reconcile (including
424      * files that exist on disk as well as files that only exist in memory)
425      */

426     public void deleteObsoleteTypesAfterReconcile(ICompilationUnit parentWC, Set JavaDoc<IFile> newlyGeneratedFiles)
427     {
428         IFile parentFile = (IFile) parentWC.getResource();
429
430         List JavaDoc<ICompilationUnit> toSetBlank = new ArrayList JavaDoc<ICompilationUnit>();
431         List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>();
432         computeObsoleteReconcileTypes(parentFile, newlyGeneratedFiles, _CUHELPER, toSetBlank, toDiscard);
433
434         for (ICompilationUnit wcToDiscard : toDiscard) {
435             if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
436                     "discarded obsolete working copy during reconcile: " + wcToDiscard.getElementName()); //$NON-NLS-1$
437
_CUHELPER.discardWorkingCopy(wcToDiscard);
438         }
439
440         WorkingCopyOwner workingCopyOwner = parentWC.getOwner();
441         for (ICompilationUnit wcToSetBlank : toSetBlank) {
442             if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
443                     "hiding file with blank working copy during reconcile: " + wcToSetBlank.getElementName()); //$NON-NLS-1$
444
_CUHELPER.updateWorkingCopyContents("", wcToSetBlank, workingCopyOwner, RECURSIVE_RECONCILE); //$NON-NLS-1$
445
}
446
447         assert checkIntegrity();
448     }
449
450     /**
451      * Called by the resource change listener when a file is deleted (eg by the user).
452      * Removes any files parented by this file, and removes the file from dependency maps
453      * if it is generated. This does not remove working copies parented by the file; that
454      * will happen when the working copy corresponding to the parent file is discarded.
455      *
456      * @param f
457      */

458     public void fileDeleted(IFile f)
459     {
460         List JavaDoc<IFile> toDelete = removeFileFromBuildMaps(f);
461
462         for (IFile fileToDelete : toDelete) {
463             deletePhysicalFile(fileToDelete);
464         }
465
466     }
467
468     /**
469      * Invoked when a file is generated during a build. The generated file and
470      * intermediate directories will be created if they don't exist. This method takes
471      * file-system locks, and assumes that the calling method has at some point acquired a
472      * workspace-level resource lock.
473      *
474      * @param parentFiles
475      * the parent or parents of the type being generated. May be empty, and/or
476      * may contain null entries, but must not itself be null.
477      * @param typeName
478      * the dot-separated java type name of the type being generated
479      * @param contents
480      * the java code contents of the new type .
481      * @param clearDuringReconcile
482      * if true, this file should be removed after any reconcile in which it was not
483      * regenerated. This typically is used when the file is being generated by a
484      * processor that supports {@linkplain org.eclipse.jdt.apt.core.util.AptPreferenceConstants#RTTG_ENABLED_OPTION
485      * reconcile-time type generation}.
486      * @param progressMonitor
487      * a progress monitor. This may be null.
488      * @return - the newly created IFile along with whether it was modified
489      * @throws CoreException
490      */

491     public FileGenerationResult generateFileDuringBuild(Collection JavaDoc<IFile> parentFiles, String JavaDoc typeName, String JavaDoc contents,
492             boolean clearDuringReconcile, IProgressMonitor progressMonitor) throws CoreException
493     {
494         if (_skipTypeGeneration)
495             return null;
496         
497         GeneratedPackageFragmentRoot.NameAndRoot gpfr = _generatedPackageFragmentRoot.get();
498         IPackageFragmentRoot root = gpfr.root;
499         if (root == null) {
500             // If the generated package fragment root wasn't set, then our classpath
501
// is incorrect. Add a marker and return. We do this here, rather than in
502
// the set() method, because if they're not going to generate any types
503
// then it doesn't matter that the classpath is wrong.
504
String JavaDoc message = Messages.bind(Messages.GeneratedFileManager_missing_classpath_entry,
505                     new String JavaDoc[] { gpfr.name });
506             IMarker marker = _jProject.getProject().createMarker(AptPlugin.APT_CONFIG_PROBLEM_MARKER);
507             marker.setAttributes(new String JavaDoc[] { IMarker.MESSAGE, IMarker.SEVERITY }, new Object JavaDoc[] { message,
508                     IMarker.SEVERITY_ERROR });
509             // disable any future type generation
510
_skipTypeGeneration = true;
511             return null;
512         }
513
514         // Do the new contents differ from what is already on disk?
515
// We need to know so we can tell the caller whether this is a modification.
516
IFile file = getIFileForTypeName(typeName);
517         boolean contentsDiffer = compareFileContents(contents, file);
518
519         try {
520             if (contentsDiffer) {
521                 final String JavaDoc[] names = parseTypeName(typeName);
522                 final String JavaDoc pkgName = names[0];
523                 final String JavaDoc cuName = names[1];
524                 
525                 // Get a list of the folders that will have to be created for this package to exist
526
IFolder genSrcFolder = (IFolder) root.getResource();
527                 final Set JavaDoc<IFolder> newFolders = computeNewPackageFolders(pkgName, genSrcFolder);
528     
529                 // Create the package fragment in the Java Model. This creates all needed parent folders.
530
IPackageFragment pkgFrag = _CUHELPER.createPackageFragment(pkgName, root, progressMonitor);
531     
532                 // Mark all newly created folders (but not pre-existing ones) as derived.
533
for (IContainer folder : newFolders) {
534                     try {
535                         folder.setDerived(true);
536                     } catch (CoreException e) {
537                         AptPlugin.logWarning(e, "Unable to mark generated type folder as derived: " + folder.getName()); //$NON-NLS-1$
538
break;
539                     }
540                 }
541                 
542                 saveCompilationUnit(pkgFrag, cuName, contents, progressMonitor);
543             }
544
545             // during a batch build, parentFile will be null.
546
// Only keep track of ownership in iterative builds
547
addBuiltFileToMaps(parentFiles, file, true);
548             if (clearDuringReconcile) {
549                 _clearDuringReconcile.add(file);
550             }
551
552             // Mark the file as derived. Note that certain user actions may have
553
// deleted this file before we get here, so if the file doesn't
554
// exist, marking it derived throws a ResourceException.
555
if (file.exists()) {
556                 file.setDerived(true);
557             }
558             // We used to also make the file read-only. This is a bad idea,
559
// as refactorings then fail in the future, which is worse
560
// than allowing a user to modify a generated file.
561

562             assert checkIntegrity();
563
564             return new FileGenerationResult(file, contentsDiffer);
565         } catch (CoreException e) {
566             AptPlugin.log(e, "Unable to generate type " + typeName); //$NON-NLS-1$
567
return null;
568         }
569     }
570
571     /**
572      * This function generates a type "in-memory" by creating or updating a working copy
573      * with the specified contents. The generated-source folder must be configured
574      * correctly for this to work. This method takes no locks, so it is safe to call when
575      * holding fine-grained resource locks (e.g., during some reconcile paths). Since this
576      * only works on an in-memory working copy of the type, the IFile for the generated
577      * type might not exist on disk. Likewise, the corresponding package directories of
578      * type-name might not exist on disk.
579      *
580      * TODO: figure out how to create a working copy with a client-specified character set
581      *
582      * @param parentCompilationUnit the parent compilation unit.
583      * @param typeName the dot-separated java type name for the new type
584      * @param contents the contents of the new type
585      * @return The FileGenerationResult. This will return null if the generated source
586      * folder is not configured, or if there is some other error during type
587      * generation.
588      *
589      */

590     public FileGenerationResult generateFileDuringReconcile(ICompilationUnit parentCompilationUnit, String JavaDoc typeName,
591             String JavaDoc contents) throws CoreException
592     {
593         if (!GENERATE_TYPE_DURING_RECONCILE)
594             return null;
595
596         IFile parentFile = (IFile) parentCompilationUnit.getResource();
597         
598         ICompilationUnit workingCopy = getWorkingCopyForReconcile(parentFile, typeName, _CUHELPER);
599
600         // Update its contents and recursively reconcile
601
boolean modified = _CUHELPER.updateWorkingCopyContents(
602                 contents, workingCopy, parentCompilationUnit.getOwner(), RECURSIVE_RECONCILE);
603         if (AptPlugin.DEBUG_GFM) {
604             if (modified)
605                 AptPlugin.trace("working copy modified during reconcile: " + typeName); //$NON-NLS-1$
606
else
607                 AptPlugin.trace("working copy unmodified during reconcile: " + typeName); //$NON-NLS-1$
608
}
609
610         IFile generatedFile = (IFile) workingCopy.getResource();
611         return new FileGenerationResult(generatedFile, modified);
612     }
613
614     /**
615      * @param parent -
616      * the parent file that you want to get generated files for
617      * @return Set of IFile instances that are the files known to be generated by this
618      * parent, or an empty collection if there are none.
619      *
620      * @see #isParentFile(IFile)
621      * @see #isGeneratedFile(IFile)
622      */

623     public synchronized Set JavaDoc<IFile> getGeneratedFilesForParent(IFile parent)
624     {
625         return _buildDeps.getValues(parent);
626     }
627     
628     /**
629      * returns true if the specified file is a generated file (i.e., it has one or more
630      * parent files)
631      *
632      * @param f
633      * the file in question
634      * @return true
635      */

636     public synchronized boolean isGeneratedFile(IFile f)
637     {
638         return _buildDeps.containsValue(f);
639     }
640     
641
642
643     /**
644      * returns true if the specified file is a parent file (i.e., it has one or more
645      * generated files)
646      *
647      * @param f -
648      * the file in question
649      * @return true if the file is a parent, false otherwise
650      *
651      * @see #getGeneratedFilesForParent(IFile)
652      * @see #isGeneratedFile(IFile)
653      */

654     public synchronized boolean isParentFile(IFile f)
655     {
656         return _buildDeps.containsKey(f);
657     }
658
659     /**
660      * Perform the actions necessary to respond to a clean.
661      */

662     public void projectCleaned() {
663         Iterable JavaDoc<ICompilationUnit> toDiscard = computeClean();
664         for (ICompilationUnit wc : toDiscard) {
665             _CUHELPER.discardWorkingCopy(wc);
666         }
667         if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
668                 "cleared build file dependencies"); //$NON-NLS-1$
669
}
670
671     /**
672      * Perform the actions necessary to respond to a project being closed.
673      * Throw out the reconcile-time information and working copies; throw
674      * out the build-time dependency information but leave its serialized
675      * version on disk in case the project is re-opened.
676      */

677     public void projectClosed()
678     {
679         if (AptPlugin.DEBUG_GFM) AptPlugin.trace("discarding working copy state"); //$NON-NLS-1$
680
List JavaDoc<ICompilationUnit> toDiscard;
681         toDiscard = computeProjectClosed(false);
682         for (ICompilationUnit wc : toDiscard) {
683             _CUHELPER.discardWorkingCopy(wc);
684         }
685     }
686
687     /**
688      * Perform the actions necessary to respond to a project being deleted.
689      * Throw out everything related to the project, including its serialized
690      * build dependencies.
691      */

692     public void projectDeleted()
693     {
694         if (AptPlugin.DEBUG_GFM) AptPlugin.trace("discarding all state"); //$NON-NLS-1$
695
List JavaDoc<ICompilationUnit> toDiscard;
696         toDiscard = computeProjectClosed(true);
697         for (ICompilationUnit wc : toDiscard) {
698             _CUHELPER.discardWorkingCopy(wc);
699         }
700     }
701
702     /**
703      * Called at the start of reconcile in order to cache our package fragment root
704      */

705     public void reconcileStarted()
706     {
707         _generatedPackageFragmentRoot.set();
708     }
709
710     /**
711      * Invoked when a working copy is released, ie, an editor is closed. This
712      * includes IDE shutdown.
713      *
714      * @param wc
715      * must not be null, but does not have to be a parent.
716      * @throws CoreException
717      */

718     public void workingCopyDiscarded(ICompilationUnit wc) throws CoreException
719     {
720         List JavaDoc<ICompilationUnit> toDiscard = removeFileFromReconcileMaps((IFile)(wc.getResource()));
721         if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
722                 "Working copy discarded: " + wc.getElementName() + //$NON-NLS-1$
723
" removing " + toDiscard.size() + " children"); //$NON-NLS-1$//$NON-NLS-2$
724
for (ICompilationUnit obsoleteWC : toDiscard) {
725             _CUHELPER.discardWorkingCopy(obsoleteWC);
726         }
727     }
728
729     /**
730      * Serialize the generated file dependency data for builds, so that when a workspace
731      * is reopened, incremental builds will work correctly.
732      */

733     public void writeState()
734     {
735         _buildDeps.writeState();
736     }
737
738     /**
739      * Add a file dependency at build time. This updates the build dependency map but does
740      * not affect the reconcile-time dependencies.
741      * <p>
742      * This method only affects maps; it does not touch disk or modify working copies.
743      *
744      * @param isSource true for source files that will be compiled; false for non-source, e.g., text or xml.
745      */

746     private synchronized void addBuiltFileToMaps(Collection JavaDoc<IFile> parentFiles, IFile generatedFile, boolean isSource)
747     {
748         // during a batch build, parentFile will be null.
749
// Only keep track of ownership in iterative builds
750
for (IFile parentFile : parentFiles) {
751             if (parentFile != null) {
752                 boolean added = _buildDeps.put(parentFile, generatedFile, isSource);
753                 if (AptPlugin.DEBUG_GFM_MAPS) {
754                     if (added)
755                         AptPlugin.trace("build file dependency added: " + parentFile + " -> " + generatedFile); //$NON-NLS-1$//$NON-NLS-2$
756
else
757                         AptPlugin.trace("build file dependency already exists: " + parentFile + " -> " + generatedFile); //$NON-NLS-1$//$NON-NLS-2$
758
}
759             }
760         }
761     }
762     
763     /**
764      * Check integrity of data structures.
765      * @return true always, so that it can be called within an assert to turn it off at runtime
766      */

767     private synchronized boolean checkIntegrity() throws IllegalStateException JavaDoc
768     {
769         if (!ENABLE_INTEGRITY_CHECKS || !AptPlugin.DEBUG_GFM_MAPS) {
770             return true;
771         }
772         
773         // There is a 1:1 correspondence between values in _reconcileDeps and
774
// keys in _reconcileGenTypes.
775
Set JavaDoc<IFile> depChildren = _reconcileDeps.getValueSet(); // copy - safe to modify
776
Set JavaDoc<IFile> genTypes = _reconcileGenTypes.keySet(); // not a copy!
777
List JavaDoc<IFile> extraFiles = new ArrayList JavaDoc<IFile>();
778         for (IFile f : genTypes) {
779             if (!depChildren.remove(f)) {
780                 extraFiles.add(f);
781             }
782         }
783         if (!extraFiles.isEmpty()) {
784             logExtraFiles("File(s) in reconcile-generated list but not in reconcile dependency map: ", //$NON-NLS-1$
785
extraFiles);
786         }
787         if (!depChildren.isEmpty()) {
788             logExtraFiles("File(s) in reconcile dependency map but not in reconcile-generated list: ", //$NON-NLS-1$
789
depChildren);
790         }
791         
792         // Every file in _clearDuringReconcile must be a value in _buildDeps.
793
List JavaDoc<IFile> extraClearDuringReconcileFiles = new ArrayList JavaDoc<IFile>();
794         for (IFile clearDuringReconcile : _clearDuringReconcile) {
795             if (!_buildDeps.containsValue(clearDuringReconcile)) {
796                 extraClearDuringReconcileFiles.add(clearDuringReconcile);
797             }
798         }
799         if (!extraClearDuringReconcileFiles.isEmpty()) {
800             logExtraFiles("File(s) in list to clear during reconcile but not in build dependency map: ", //$NON-NLS-1$
801
extraClearDuringReconcileFiles);
802         }
803         
804         // Every key in _hiddenBuiltTypes must be a value in _reconcileNonDeps.
805
List JavaDoc<IFile> extraHiddenTypes = new ArrayList JavaDoc<IFile>();
806         for (IFile hidden : _hiddenBuiltTypes.keySet()) {
807             if (!_reconcileNonDeps.containsValue(hidden)) {
808                 extraHiddenTypes.add(hidden);
809             }
810         }
811         if (!extraHiddenTypes.isEmpty()) {
812             logExtraFiles("File(s) in hidden types list but not in reconcile-obsoleted list: ", //$NON-NLS-1$
813
extraHiddenTypes);
814         }
815         
816         // There can be no parent/child pairs that exist in both _reconcileDeps
817
// and _reconcileNonDeps.
818
Map JavaDoc<IFile, IFile> reconcileOverlaps = new HashMap JavaDoc<IFile, IFile>();
819         for (IFile parent : _reconcileNonDeps.getKeySet()) {
820             for (IFile child : _reconcileNonDeps.getValues(parent)) {
821                 if (_reconcileDeps.containsKeyValuePair(parent, child)) {
822                     reconcileOverlaps.put(parent, child);
823                 }
824             }
825         }
826         if (!reconcileOverlaps.isEmpty()) {
827             logExtraFilePairs("Entries exist in both reconcile map and reconcile-obsoleted maps: ", //$NON-NLS-1$
828
reconcileOverlaps);
829         }
830         
831         // Every parent/child pair in _reconcileNonDeps must have a matching
832
// parent/child pair in _buildDeps.
833
Map JavaDoc<IFile, IFile> extraNonDeps = new HashMap JavaDoc<IFile, IFile>();
834         for (IFile parent : _reconcileNonDeps.getKeySet()) {
835             for (IFile child : _reconcileNonDeps.getValues(parent)) {
836                 if (!_buildDeps.containsKeyValuePair(parent, child)) {
837                     extraNonDeps.put(parent, child);
838                 }
839             }
840         }
841         if (!extraNonDeps.isEmpty()) {
842             logExtraFilePairs("Entries exist in reconcile-obsoleted map but not in build map: ", //$NON-NLS-1$
843
extraNonDeps);
844         }
845         
846         // Values in _hiddenBuiltTypes must not be null
847
List JavaDoc<IFile> nullHiddenTypes = new ArrayList JavaDoc<IFile>();
848         for (Map.Entry JavaDoc<IFile, ICompilationUnit> entry : _hiddenBuiltTypes.entrySet()) {
849             if (entry.getValue() == null) {
850                 nullHiddenTypes.add(entry.getKey());
851             }
852         }
853         if (!nullHiddenTypes.isEmpty()) {
854             logExtraFiles("Null entries in hidden type list: ", nullHiddenTypes); //$NON-NLS-1$
855
}
856         
857         // Values in _reconcileGenTypes must not be null
858
List JavaDoc<IFile> nullReconcileTypes = new ArrayList JavaDoc<IFile>();
859         for (Map.Entry JavaDoc<IFile, ICompilationUnit> entry : _reconcileGenTypes.entrySet()) {
860             if (entry.getValue() == null) {
861                 nullReconcileTypes.add(entry.getKey());
862             }
863         }
864         if (!nullReconcileTypes.isEmpty()) {
865             logExtraFiles("Null entries in reconcile type list: ", nullReconcileTypes); //$NON-NLS-1$
866
}
867             
868         return true;
869     }
870
871     /**
872      * Clear the working copy maps, that is, the reconcile-time dependency information.
873      * Returns a list of working copies that are no longer referenced and should be
874      * discarded. Typically called when a project is being closed or deleted.
875      * <p>
876      * It's not obvious we actually need this. As long as the IDE discards the parent
877      * working copies before the whole GeneratedFileManager is discarded, there'll be
878      * nothing left to clear by the time we get here. This is a "just in case."
879      * <p>
880      * This method affects maps only; it does not touch disk nor create, modify, nor
881      * discard any working copies. This method is atomic with respect to data structure
882      * integrity.
883      *
884      * @param deleteState
885      * true if this should delete the serialized build dependencies.
886      *
887      * @return a list of working copies which must be discarded by the caller
888      */

889     private synchronized List JavaDoc<ICompilationUnit> computeProjectClosed(boolean deleteState)
890     {
891         int size = _hiddenBuiltTypes.size() + _reconcileGenTypes.size();
892         List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>(size);
893         toDiscard.addAll(_hiddenBuiltTypes.values());
894         toDiscard.addAll(_reconcileGenTypes.values());
895         _reconcileGenTypes.clear();
896         _hiddenBuiltTypes.clear();
897         _reconcileDeps.clear();
898         _reconcileNonDeps.clear();
899         
900         if (deleteState) {
901             _buildDeps.clearState();
902         }
903         else {
904             _buildDeps.clear();
905         }
906         _clearDuringReconcile.clear();
907
908         assert checkIntegrity();
909         return toDiscard;
910     }
911
912     /**
913      * Compare <code>contents</code> with the contents of <code>file</code>.
914      * @param contents the text to compare with the file's contents on disk.
915      * @param file does not have to exist.
916      * @return true if the file on disk cannot be read, or if its contents differ.
917      */

918     private boolean compareFileContents(String JavaDoc contents, IFile file)
919     {
920         boolean contentsDiffer = true;
921         if (file.exists()) {
922             InputStream JavaDoc oldData = null;
923             InputStream JavaDoc is = null;
924             try {
925                 is = new ByteArrayInputStream JavaDoc(contents.getBytes());
926                 oldData = new BufferedInputStream JavaDoc(file.getContents());
927                 contentsDiffer = !FileSystemUtil.compareStreams(oldData, is);
928             } catch (CoreException ce) {
929                 // Do nothing. Assume the new content is different
930
} finally {
931                 if (oldData != null) {
932                     try {
933                         oldData.close();
934                     } catch (IOException JavaDoc ioe) {
935                     }
936                 }
937                 if (is != null) {
938                     try {
939                         is.close();
940                     } catch (IOException JavaDoc ioe) {
941                     }
942                 }
943             }
944         }
945         return contentsDiffer;
946     }
947
948     /**
949      * Make the map updates necessary to discard build state. Typically called while
950      * processing a clean. In addition to throwing away the build dependencies, we also
951      * throw away all the blank working copies used to hide existing generated files, on
952      * the premise that since they were deleted in the clean we don't need to hide them
953      * any more. We leave the rest of the reconcile-time dependency info, though.
954      * <p>
955      * This method is atomic with regard to data structure integrity. This method
956      * does not touch disk nor create, discard, or modify compilation units.
957      *
958      * @return a list of working copies that the caller must discard by calling
959      * {@link CompilationUnitHelper#discardWorkingCopy(ICompilationUnit)}.
960      */

961     private synchronized List JavaDoc<ICompilationUnit> computeClean()
962     {
963         _buildDeps.clearState();
964         _clearDuringReconcile.clear();
965         _reconcileNonDeps.clear();
966         List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>(_hiddenBuiltTypes.values());
967         _hiddenBuiltTypes.clear();
968         
969         assert checkIntegrity();
970         return toDiscard;
971     }
972
973     /**
974      * Get the IFolder handles for any additional folders needed to
975      * contain a type in package <code>pkgName</code> under root
976      * <code>parent</code>. This does not actually create the folders
977      * on disk, it just gets resource handles.
978      *
979      * @return a set containing all the newly created folders.
980      */

981     private Set JavaDoc<IFolder> computeNewPackageFolders(String JavaDoc pkgName, IFolder parent)
982     {
983         Set JavaDoc<IFolder> newFolders = new HashSet JavaDoc<IFolder>();
984         String JavaDoc[] folders = _PACKAGE_DELIMITER.split(pkgName);
985         for (String JavaDoc folderName : folders) {
986             final IFolder folder = parent.getFolder(folderName);
987             if (!folder.exists()) {
988                 newFolders.add(folder);
989             }
990             parent = folder;
991         }
992         return newFolders;
993     }
994
995     /**
996      * Calculate the list of previously generated files that are no longer
997      * being generated and thus need to be deleted.
998      * <p>
999      * This method does not touch the disk, nor does it create, update, or
1000     * discard working copies. This method is atomic with regard to the
1001     * integrity of data structures.
1002     *
1003     * @param parentFile only files solely parented by this file will be
1004     * added to the list to be deleted.
1005     * @param newlyGeneratedFiles files on this list will be spared.
1006     * @param toDiscard must be non-null. The caller should pass in an empty
1007     * list; on return the list will contain working copies which the caller
1008     * is responsible for discarding.
1009     * @param toReport must be non-null. The caller should pass in an empty
1010     * set; on return the set will contain IFiles representing source files
1011     * (but not non-source files such as text or xml files) which are being
1012     * deleted and which should therefore be removed from compilation.
1013     * @return a list of files which the caller should delete, ie by calling
1014     * {@link #deletePhysicalFile(IFile)}.
1015     */

1016    private synchronized Set JavaDoc<IFile> computeObsoleteFiles(
1017            IFile parentFile, Set JavaDoc<IFile> newlyGeneratedFiles,
1018            List JavaDoc<ICompilationUnit> toDiscard,
1019            Set JavaDoc<IFile> toReport)
1020    {
1021        Set JavaDoc<IFile> deleted = new HashSet JavaDoc<IFile>();
1022        Set JavaDoc<IFile> obsoleteFiles = _buildDeps.getValues(parentFile);
1023        // spare all the newly generated files
1024
obsoleteFiles.removeAll(newlyGeneratedFiles);
1025        for (IFile generatedFile : obsoleteFiles) {
1026            boolean isSource = _buildDeps.isSource(generatedFile);
1027            _buildDeps.remove(parentFile, generatedFile);
1028            if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
1029                    "removed build file dependency: " + parentFile + " -> " + generatedFile); //$NON-NLS-1$ //$NON-NLS-2$
1030
// If the file is still parented by any other parent, spare it
1031
if (!_buildDeps.containsValue(generatedFile)) {
1032                deleted.add(generatedFile);
1033                if (isSource) {
1034                    toReport.add(generatedFile);
1035                }
1036            }
1037        }
1038        _clearDuringReconcile.removeAll(deleted);
1039        toDiscard.addAll(computeObsoleteHiddenTypes(parentFile, deleted));
1040        assert checkIntegrity();
1041        return deleted;
1042    }
1043
1044    /**
1045         * Calculate what needs to happen to working copies after a reconcile in order to get
1046         * rid of any no-longer-generated files. If there's an existing generated file, we
1047         * need to hide it with a blank working copy; if there's no existing file, we need to
1048         * get rid of any generated working copy.
1049         * <p>
1050         * A case to keep in mind: the user imports a project with already-existing generated
1051         * files, but without a serialized build dependency map. Then they edit a parent
1052         * file, causing a generated type to disappear. We need to discover and hide the
1053         * generated file on disk, even though it is not in the build-time dependency map.
1054         *
1055         * @param parentFile
1056         * the parent type being reconciled, which need not exist on disk.
1057         * @param newlyGeneratedFiles
1058         * the set of files generated in the last reconcile
1059         * @param toSetBlank
1060         * a list, to which this will add files that the caller must then set blank
1061         * with {@link CompilationUnitHelper#updateWorkingCopyContents(String,
1062         * ICompilationUnit, WorkingCopyOwner, boolean)}
1063         * @param toDiscard
1064         * a list, to which this will add files that the caller must then discard
1065         * with {@link CompilationUnitHelper#discardWorkingCopy(ICompilationUnit)}.
1066         */

1067        private synchronized void computeObsoleteReconcileTypes(
1068                IFile parentFile, Set JavaDoc<IFile> newlyGeneratedFiles,
1069                CompilationUnitHelper cuh,
1070                List JavaDoc<ICompilationUnit> toSetBlank, List JavaDoc<ICompilationUnit> toDiscard)
1071        {
1072            // Get types previously but no longer generated during reconcile
1073
Set JavaDoc<IFile> obsoleteFiles = _reconcileDeps.getValues(parentFile);
1074            Map JavaDoc<IFile, ICompilationUnit> typesToDiscard = new HashMap JavaDoc<IFile, ICompilationUnit>();
1075            obsoleteFiles.removeAll(newlyGeneratedFiles);
1076            for (IFile obsoleteFile : obsoleteFiles) {
1077                _reconcileDeps.remove(parentFile, obsoleteFile);
1078                if (_reconcileDeps.getKeys(obsoleteFile).isEmpty()) {
1079                    ICompilationUnit wc = _reconcileGenTypes.remove(obsoleteFile);
1080                    assert wc != null :
1081                        "Value in reconcile deps missing from reconcile type list: " + obsoleteFile; //$NON-NLS-1$
1082
typesToDiscard.put(obsoleteFile, wc);
1083                }
1084            }
1085            
1086            Set JavaDoc<IFile> builtChildren = _buildDeps.getValues(parentFile);
1087            builtChildren.retainAll(_clearDuringReconcile);
1088            builtChildren.removeAll(newlyGeneratedFiles);
1089            for (IFile builtChild : builtChildren) {
1090                _reconcileNonDeps.put(parentFile, builtChild);
1091                // If it's on typesToDiscard there are no other reconcile-time parents.
1092
// If there are no other parents that are not masked by a nonDep entry...
1093
boolean foundOtherParent = false;
1094                Set JavaDoc<IFile> parents = _buildDeps.getKeys(builtChild);
1095                parents.remove(parentFile);
1096                for (IFile otherParent : parents) {
1097                    if (!_reconcileNonDeps.containsKeyValuePair(otherParent, builtChild)) {
1098                        foundOtherParent = true;
1099                        break;
1100                    }
1101                }
1102                if (!foundOtherParent) {
1103                    ICompilationUnit wc = typesToDiscard.remove(builtChild);
1104                    if (wc == null) {
1105                        IPackageFragmentRoot root = _generatedPackageFragmentRoot.get().root;
1106                        String JavaDoc typeName = getTypeNameForDerivedFile(builtChild);
1107                        wc = cuh.getWorkingCopy(typeName, root);
1108                    }
1109                    _hiddenBuiltTypes.put(builtChild, wc);
1110                    toSetBlank.add(wc);
1111                }
1112            }
1113        
1114            // discard any working copies that we're not setting blank
1115
toDiscard.addAll(typesToDiscard.values());
1116            
1117            assert checkIntegrity();
1118        }
1119
1120    /**
1121     * Calculate the list of blank working copies that are no longer needed because the
1122     * files that they hide have been deleted during a build. Remove these working copies
1123     * from the _hiddenBuiltTypes list and return them in a list. The caller MUST then
1124     * discard the contents of the list (outside of any synchronized block) by calling
1125     * CompilationUnitHelper.discardWorkingCopy().
1126     * <p>
1127     * This method does not touch the disk and does not create, update, or discard working
1128     * copies. This method is atomic with regard to data structure integrity.
1129     *
1130     * @param parentFile
1131     * used to be a parent but may no longer be.
1132     * @param deletedFiles
1133     * a list of files which are being deleted, which might or might not have
1134     * been hidden by blank working copies.
1135     *
1136     * @return a list of working copies which the caller must discard
1137     */

1138    private synchronized List JavaDoc<ICompilationUnit> computeObsoleteHiddenTypes(IFile parentFile, Set JavaDoc<IFile> deletedFiles)
1139    {
1140        List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>();
1141        for (IFile deletedFile : deletedFiles) {
1142            if (_reconcileNonDeps.remove(parentFile, deletedFile)) {
1143                ICompilationUnit wc = _hiddenBuiltTypes.remove(deletedFile);
1144                if (wc != null) {
1145                    toDiscard.add(wc);
1146                }
1147            }
1148        }
1149        assert checkIntegrity();
1150        return toDiscard;
1151    }
1152
1153    /**
1154     * Delete a generated file from disk. Also deletes the parent folder hierarchy, up to
1155     * but not including the root generated source folder, as long as the folders are
1156     * empty and are marked as "derived".
1157     * <p>
1158     * This does not affect or refer to the dependency maps.
1159     *
1160     * @param file is assumed to be under the generated source folder.
1161     */

1162    private void deletePhysicalFile(IFile file)
1163    {
1164        final IFolder genFolder = _gsfm.getFolder();
1165        assert genFolder != null : "Generated folder == null"; //$NON-NLS-1$
1166
IContainer parent = file.getParent(); // parent in the folder sense,
1167
// not the typegen sense
1168
try {
1169            if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
1170                    "delete physical file: " + file); //$NON-NLS-1$
1171
file.delete(true, true, /* progressMonitor */null);
1172        } catch (CoreException e) {
1173            // File was locked or read-only
1174
AptPlugin.logWarning(e, "Unable to delete generated file: " + file); //$NON-NLS-1$
1175
}
1176        // Delete the parent folders
1177
while (!genFolder.equals(parent) && parent != null && parent.isDerived()) {
1178            IResource[] members = null;
1179            try {
1180                members = parent.members();
1181            } catch (CoreException e) {
1182                AptPlugin.logWarning(e, "Unable to read contents of generated file folder " + parent); //$NON-NLS-1$
1183
}
1184            IContainer grandParent = parent.getParent();
1185            // last one turns the light off.
1186
if (members == null || members.length == 0)
1187                try {
1188                    parent.delete(true, /* progressMonitor */null);
1189                } catch (CoreException e) {
1190                    AptPlugin.logWarning(e, "Unable to delete generated file folder " + parent); //$NON-NLS-1$
1191
}
1192            else
1193                break;
1194            parent = grandParent;
1195        }
1196    }
1197    
1198    /**
1199     * Given a typename a.b.c, this will return the IFile for the type name, where the
1200     * IFile is in the GENERATED_SOURCE_FOLDER_NAME.
1201     * <p>
1202     * This does not affect or refer to the dependency maps.
1203     */

1204    private IFile getIFileForTypeName(String JavaDoc typeName)
1205    {
1206        // split the type name into its parts
1207
String JavaDoc[] parts = _PACKAGE_DELIMITER.split(typeName);
1208
1209        IFolder folder = _gsfm.getFolder();
1210        for (int i = 0; i < parts.length - 1; i++)
1211            folder = folder.getFolder(parts[i]);
1212
1213        // the last part of the type name is the file name
1214
String JavaDoc fileName = parts[parts.length - 1] + ".java"; //$NON-NLS-1$
1215
IFile file = folder.getFile(fileName);
1216        return file;
1217    }
1218
1219    /**
1220     * given file f, return the typename corresponding to the file. This assumes
1221     * that derived files use java naming rules (i.e., type "a.b.c" will be file
1222     * "a/b/c.java".
1223     */

1224    private String JavaDoc getTypeNameForDerivedFile( IFile f )
1225    {
1226        IPath p = f.getFullPath();
1227
1228        IFolder folder = _gsfm.getFolder();
1229        IPath generatedSourcePath = folder.getFullPath();
1230        
1231        int count = p.matchingFirstSegments( generatedSourcePath );
1232        p = p.removeFirstSegments( count );
1233    
1234        String JavaDoc s = p.toPortableString();
1235        int idx = s.lastIndexOf( '.' );
1236        s = p.toPortableString().replace( '/', '.' );
1237        return s.substring( 0, idx );
1238    }
1239    
1240    /**
1241     * Get a working copy for the specified generated type. If we already have
1242     * one cached, use that; if not, create a new one. Update the reconcile-time
1243     * dependency maps.
1244     * <p>
1245     * This method does not touch disk, nor does it update or discard any working
1246     * copies. However, it may call CompilationUnitHelper to get a new working copy.
1247     * This method is atomic with respect to data structures.
1248     *
1249     * @param parentFile the IFile whose processing is causing the new type to be generated
1250     * @param typeName the name of the type to be generated
1251     * @param cuh the CompilationUnitHelper utility object
1252     * @return a working copy ready to be updated with the new type's contents
1253     */

1254    private synchronized ICompilationUnit getWorkingCopyForReconcile(IFile parentFile, String JavaDoc typeName, CompilationUnitHelper cuh)
1255    {
1256        IPackageFragmentRoot root = _generatedPackageFragmentRoot.get().root;
1257        IFile generatedFile = getIFileForTypeName(typeName);
1258        ICompilationUnit workingCopy;
1259        
1260        workingCopy = _hiddenBuiltTypes.remove(generatedFile);
1261        if (null != workingCopy) {
1262            // file is currently hidden with a blank WC. Move that WC to the regular list.
1263
_reconcileNonDeps.remove(parentFile, generatedFile);
1264            _reconcileGenTypes.put(generatedFile, workingCopy);
1265            _reconcileDeps.put(parentFile, generatedFile);
1266            if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
1267                    "moved working copy from hidden to regular list: " + generatedFile); //$NON-NLS-1$
1268
} else {
1269            workingCopy = _reconcileGenTypes.get(generatedFile);
1270            if (null != workingCopy) {
1271                if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
1272                        "obtained existing working copy from regular list: " + generatedFile); //$NON-NLS-1$
1273
} else {
1274                // we've not yet created a working copy for this file, so make one now.
1275
workingCopy = cuh.getWorkingCopy(typeName, root);
1276                _reconcileDeps.put(parentFile, generatedFile);
1277                _reconcileGenTypes.put(generatedFile, workingCopy);
1278                if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
1279                        "added new working copy to regular list: " + generatedFile); //$NON-NLS-1$
1280
}
1281        }
1282
1283        assert checkIntegrity();
1284        return workingCopy;
1285    }
1286    
1287    /**
1288     * Check whether a child file has any parents that could apply in reconcile.
1289     *
1290     * @return true if <code>child</code> has no other parents in
1291     * {@link #_reconcileDeps}, and also no other parents in {@link #_buildDeps}
1292     * that are not masked by a corresponding entry in {@link #_reconcileNonDeps}.
1293     */

1294    private boolean hasNoOtherReconcileParents(IFile child, IFile parent) {
1295        if (_reconcileDeps.valueHasOtherKeys(child, parent))
1296            return true;
1297        Set JavaDoc<IFile> buildParents = _buildDeps.getKeys(child);
1298        buildParents.remove(parent);
1299        buildParents.removeAll(_reconcileNonDeps.getKeys(child));
1300        return buildParents.isEmpty();
1301    }
1302
1303    /**
1304     * Log extra file pairs, with a message like "message p1->g1, p2->g2".
1305     * Assumes that pairs has at least one entry.
1306     */

1307    private void logExtraFilePairs(String JavaDoc message, Map JavaDoc<IFile, IFile> pairs) {
1308        StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1309        sb.append(message);
1310        Iterator JavaDoc<Map.Entry JavaDoc<IFile, IFile>> iter = pairs.entrySet().iterator();
1311        while (true) {
1312            Map.Entry JavaDoc<IFile, IFile> entry = iter.next();
1313            sb.append(entry.getKey().getName());
1314            sb.append("->"); //$NON-NLS-1$
1315
sb.append(entry.getValue().getName());
1316            if (!iter.hasNext()) {
1317                break;
1318            }
1319            sb.append(", "); //$NON-NLS-1$
1320
}
1321        String JavaDoc s = sb.toString();
1322        AptPlugin.log(new IllegalStateException JavaDoc(s), s);
1323    }
1324
1325    /**
1326     * Log extra files, with a message like "message file1, file2, file3".
1327     * Assumes that files has at least one entry.
1328     */

1329    private void logExtraFiles(String JavaDoc message, Iterable JavaDoc<IFile> files) {
1330        StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
1331        sb.append(message);
1332        Iterator JavaDoc<IFile> iter = files.iterator();
1333        while (true) {
1334            sb.append(iter.next().getName());
1335            if (!iter.hasNext()) {
1336                break;
1337            }
1338            sb.append(", "); //$NON-NLS-1$
1339
}
1340        String JavaDoc s = sb.toString();
1341        AptPlugin.log(new IllegalStateException JavaDoc(s), s);
1342    }
1343
1344    /**
1345     * Given a fully qualified type name, generate the package name and the local filename
1346     * including the extension. For instance, type name <code>foo.bar.Baz</code> is
1347     * turned into package <code>foo.bar</code> and filename <code>Baz.java</code>.
1348     * <p>
1349     * TODO: this is almost identical to code in CompilationUnitHelper. Is the difference
1350     * intentional?
1351     *
1352     * @param qualifiedName
1353     * a fully qualified type name
1354     * @return a String array containing {package name, filename}
1355     */

1356    private static String JavaDoc[] parseTypeName(String JavaDoc qualifiedName) {
1357        
1358        //TODO: the code in CompilationUnitHelper doesn't perform this check. Should it?
1359
if (qualifiedName.indexOf('/') != -1)
1360            qualifiedName = qualifiedName.replace('/', '.');
1361        
1362        String JavaDoc[] names = new String JavaDoc[2];
1363        String JavaDoc pkgName;
1364        String JavaDoc fname;
1365        int idx = qualifiedName.lastIndexOf( '.' );
1366        if ( idx > 0 )
1367        {
1368            pkgName = qualifiedName.substring( 0, idx );
1369            fname =
1370                qualifiedName.substring(idx + 1, qualifiedName.length()) + ".java"; //$NON-NLS-1$
1371
}
1372        else
1373        {
1374            pkgName = ""; //$NON-NLS-1$
1375
fname = qualifiedName + ".java"; //$NON-NLS-1$
1376
}
1377        names[0] = pkgName;
1378        names[1] = fname;
1379        return names;
1380    }
1381
1382    /**
1383     * Remove a file from the build-time dependency maps, and calculate the consequences
1384     * of the removal. This is called in response to a file being deleted by the
1385     * environment.
1386     * <p>
1387     * This operation affects the maps only. This operation is atomic with respect to map
1388     * integrity. This operation does not touch the disk nor create, update, or discard
1389     * any working copies.
1390     *
1391     * @param f
1392     * can be a parent, generated, both, or neither.
1393     * @return a list of generated files that are no longer relevant and must be deleted.
1394     * This operation must be done by the caller without holding any locks. The
1395     * list may be empty but will not be null.
1396     */

1397    private synchronized List JavaDoc<IFile> removeFileFromBuildMaps(IFile f)
1398    {
1399        List JavaDoc<IFile> toDelete = new ArrayList JavaDoc<IFile>();
1400        // Is this file the sole parent of files generated during build?
1401
// If so, add them to the deletion list. Then remove the file from
1402
// the build dependency list.
1403
Set JavaDoc<IFile> childFiles = _buildDeps.getValues(f);
1404        for (IFile childFile : childFiles) {
1405            Set JavaDoc<IFile> parentFiles = _buildDeps.getKeys(childFile);
1406            if (parentFiles.size() == 1 && parentFiles.contains(f)) {
1407                toDelete.add(childFile);
1408            }
1409        }
1410        boolean removed = _buildDeps.removeKey(f);
1411        if (removed) {
1412            if (AptPlugin.DEBUG_GFM_MAPS) AptPlugin.trace(
1413                    "removed parent file from build dependencies: " + f); //$NON-NLS-1$
1414
}
1415
1416        assert checkIntegrity();
1417        return toDelete;
1418    }
1419
1420    /**
1421     * Remove the generated children of a working copy from the reconcile dependency maps.
1422     * Typically invoked when a working copy of a parent file has been discarded by the
1423     * editor; in this case we want to remove any generated working copies that it
1424     * parented.
1425     * <p>
1426     * This method does not touch disk nor create, modify, or discard working copies. This
1427     * method is atomic with regard to data structure integrity.
1428     *
1429     * @param file
1430     * a file representing a working copy that is not necessarily a parent or
1431     * generated file
1432     * @return a list of generated working copies that are no longer referenced and should
1433     * be discarded by calling
1434     * {@link CompilationUnitHelper#discardWorkingCopy(ICompilationUnit)}
1435     */

1436    private synchronized List JavaDoc<ICompilationUnit> removeFileFromReconcileMaps(IFile file)
1437    {
1438        List JavaDoc<ICompilationUnit> toDiscard = new ArrayList JavaDoc<ICompilationUnit>();
1439        // remove all the orphaned children
1440
Set JavaDoc<IFile> genFiles = _reconcileDeps.getValues(file);
1441        for (IFile child : genFiles) {
1442            if (hasNoOtherReconcileParents(child, file)) {
1443                ICompilationUnit childWC = _reconcileGenTypes.remove(child);
1444                assert null != childWC : "Every value in _reconcileDeps must be a key in _reconcileGenTypes"; //$NON-NLS-1$
1445
toDiscard.add(childWC);
1446            }
1447        }
1448        _reconcileDeps.removeKey(file);
1449
1450        // remove obsolete entries in non-generated list
1451
Set JavaDoc<IFile> nonGenFiles = _reconcileNonDeps.getValues(file);
1452        for (IFile child : nonGenFiles) {
1453            ICompilationUnit hidingWC = _hiddenBuiltTypes.remove(child);
1454            if (null != hidingWC) {
1455                toDiscard.add(hidingWC);
1456            }
1457        }
1458        _reconcileNonDeps.removeKey(file);
1459        
1460        assert checkIntegrity();
1461        return toDiscard;
1462    }
1463
1464    /**
1465     * Write <code>contents</code> to disk in the form of a compilation unit named
1466     * <code>name</code> under package fragment <code>pkgFrag</code>. The way in
1467     * which the write is done depends whether the compilation unit is a working copy.
1468     * <p>
1469     * The working copy is used in reconcile. In principle changing the contents during
1470     * build should be a problem, since the Java builder is based on file contents rather
1471     * than on the current Java Model. However, annotation processors get their type info
1472     * from the Java Model even during build, so there is in general no difference between
1473     * build and reconcile. This causes certain bugs (if a build is performed while there
1474     * is unsaved content in editors), so it may change in the future, and this routine
1475     * will need to be fixed. - WHarley 11/06
1476     * <p>
1477     * This method touches the disk and modifies working copies. It can only be called
1478     * during build, not during reconcile, and it should not be called while holding any
1479     * locks (other than the workspace rules held by the build).
1480     *
1481     * @param pkgFrag
1482     * the package fragment in which the type will be created. The fragment's
1483     * folders must already exist on disk.
1484     * @param cuName
1485     * the simple name of the type, with extension, such as 'Obj.java'
1486     * @param contents
1487     * the text of the compilation unit
1488     * @param progressMonitor
1489     */

1490    private void saveCompilationUnit(IPackageFragment pkgFrag, final String JavaDoc cuName, String JavaDoc contents,
1491            IProgressMonitor progressMonitor)
1492    {
1493        
1494        ICompilationUnit unit = pkgFrag.getCompilationUnit(cuName);
1495        boolean isWorkingCopy = unit.isWorkingCopy();
1496        if (isWorkingCopy) {
1497            try {
1498                // If we have a working copy, all we
1499
// need to do is update its contents and commit it...
1500
_CUHELPER.commitNewContents(unit, contents, progressMonitor);
1501                if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
1502                        "Committed existing working copy during build: " + unit.getElementName()); //$NON-NLS-1$
1503
}
1504            catch (JavaModelException e) {
1505                // ...unless, that is, the resource has been deleted behind our back
1506
// due to a clean. In that case, discard the working copy and try again.
1507
if (e.getJavaModelStatus().getCode() == IJavaModelStatusConstants.INVALID_RESOURCE) {
1508                    _CUHELPER.discardWorkingCopy(unit);
1509                    isWorkingCopy = false;
1510                    if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
1511                            "Discarded invalid existing working copy in order to try again: " + unit.getElementName()); //$NON-NLS-1$
1512
}
1513                else {
1514                    AptPlugin.log(e, "Unable to commit working copy to disk: " + unit.getElementName()); //$NON-NLS-1$
1515
return;
1516                }
1517            }
1518        }
1519        if (!isWorkingCopy) {
1520            try {
1521                unit = pkgFrag.createCompilationUnit(cuName, contents, true, progressMonitor);
1522                if (AptPlugin.DEBUG_GFM) AptPlugin.trace(
1523                        "Created compilation unit during build: " + unit.getElementName()); //$NON-NLS-1$
1524
} catch (JavaModelException e) {
1525                AptPlugin.log(e, "Unable to create compilation unit on disk: " + //$NON-NLS-1$
1526
cuName + " in pkg fragment: " + pkgFrag.getElementName()); //$NON-NLS-1$
1527
}
1528        }
1529    }
1530
1531}
1532
Popular Tags