KickJava   Java API By Example, From Geeks To Geeks.

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


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  * wharley@bea.com - initial API and implementation
10  *******************************************************************************/

11
12 package org.eclipse.jdt.apt.core.internal.generatedfile;
13
14 import org.eclipse.core.resources.IFolder;
15 import org.eclipse.core.resources.IResource;
16 import org.eclipse.core.resources.IWorkspace;
17 import org.eclipse.core.resources.IWorkspaceRoot;
18 import org.eclipse.core.resources.IWorkspaceRunnable;
19 import org.eclipse.core.resources.ResourcesPlugin;
20 import org.eclipse.core.runtime.CoreException;
21 import org.eclipse.core.runtime.IPath;
22 import org.eclipse.core.runtime.IProgressMonitor;
23 import org.eclipse.core.runtime.IStatus;
24 import org.eclipse.core.runtime.OperationCanceledException;
25 import org.eclipse.core.runtime.Path;
26 import org.eclipse.jdt.apt.core.internal.AptPlugin;
27 import org.eclipse.jdt.apt.core.internal.AptProject;
28 import org.eclipse.jdt.apt.core.internal.util.FileSystemUtil;
29 import org.eclipse.jdt.apt.core.util.AptConfig;
30 import org.eclipse.jdt.core.IClasspathEntry;
31 import org.eclipse.jdt.core.IJavaProject;
32 import org.eclipse.jdt.core.JavaModelException;
33
34 /**
35  * Manage the generated source folder for an APT project.
36  * Every AptProject has a GeneratedSourceFolderManager. Depending on whether APT
37  * is enabled for the project, there may or may not be an actual generated
38  * source folder on disk; GeneratedSourceFolderManager is responsible for creating
39  * and deleting this folder as needed whenever APT settings are changed.
40  * <p>
41  * The job of the GeneratedSourceFolderManager is to keep the following data
42  * in agreement:
43  * <ul>
44  * <li>whether APT is enabled</li>
45  * <li>the name of the generated source folder</li>
46  * <li>the existence of the actual folder on disk</li>
47  * <li>the presence of a classpath entry for the folder</li>
48  * <li>problem markers indicating a disagreement in any of the above</li>
49  * </ul>
50  * We attempt to change the classpath entry and the folder on disk whenever
51  * the enabled/disabled state or the folder name change. These changes are
52  * discovered via the preferenceChanged() method.
53  * <p>
54  * GeneratedSourceFolderManager is responsible only for the folder itself, not
55  * its contents. Contents are managed by @see GeneratedFileManager.
56  *
57  */

58 public class GeneratedSourceFolderManager {
59     
60     private final AptProject _aptProject;
61
62     /**
63      * The folder where generated source files are placed. This will be
64      * null if APT is disabled, or in any other error state (e.g., folder
65      * does not exist on disk; folder exists on disk but classpath entry
66      * does not exist).
67      * <p>
68      * In general, if we see that this member is null but the ENABLED
69      * preference is true, we will try to create the folder and add it to
70      * the classpath; if we see that this member is non-null but the
71      * ENABLED preference is false, we will try to delete this folder's
72      * contents and remove it from the classpath; and if we see that the
73      * ENABLED preference is true, but the GENSRCDIR folder name preference
74      * is different than the name of this folder, we will try to delete
75      * this folder's contents, remove it from the classpath, and create a
76      * new folder and add it to the classpath. When we do this work depends
77      * on when we get notified of relevant changes and on what locks we are
78      * able to obtain.
79      */

80     private IFolder _generatedSourceFolder = null;
81     
82     /**
83      * Should be constructed only by AptProject. Other clients should call
84      * @see AptProject#getGeneratedSourceFolderManager() to get this object.
85      */

86     public GeneratedSourceFolderManager(AptProject aptProject)
87     {
88         _aptProject = aptProject;
89         final IJavaProject javaProject = aptProject.getJavaProject();
90         
91         // Set _generatedSourceFolder only if APT is enabled, the folder exists,
92
// and the folder is on the classpath.
93
// Otherwise leave it null, which will cause us to try to fix things later on.
94
if (AptConfig.isEnabled(javaProject)) {
95             final IFolder folder = getFolder();
96             if (folder.exists()) {
97                 if (isOnClasspath(folder)) {
98                     _generatedSourceFolder = folder;
99                 }
100             }
101         }
102     }
103     
104     /**
105      * Add the folder to the classpath, unless it's already there.
106      * @param srcFolder the folder to add to the classpath. Must not be null.
107      * @return true if, at the end of the routine, the folder is on the classpath.
108      */

109     private boolean addToClasspath(IFolder srcFolder) {
110         boolean onClasspath = false;
111         try {
112             ClasspathUtil.updateProjectClasspath( _aptProject.getJavaProject(), srcFolder, null );
113             if(AptPlugin.DEBUG)
114                 AptPlugin.trace("Ensured classpath has an entry for " + srcFolder); //$NON-NLS-1$
115
onClasspath = true;
116         }
117         catch (CoreException e) {
118             e.printStackTrace();
119             AptPlugin.log(e, "Failed to add classpath entry for generated source folder " + srcFolder.getName()); //$NON-NLS-1$
120
}
121         return onClasspath;
122     }
123     
124     /**
125      * Call this to create the folder and add it to the classpath, when APT is enabled
126      * (in which case the folder did not previously exist) or when the folder name is
127      * changed (in which case the old stuff must also be removed).
128      * <p>
129      * This method will take a resource lock if the generated source folder needs
130      * to be created on disk, and it will take a java model lock if the project's
131      * source paths need to be updated. Care should be taken when calling this
132      * method to ensure that locking behavior is correct.
133      * <p>
134      * This should only be called on an event thread, with no locks on the project
135      * or classpath.
136      */

137     private void configure() {
138         
139         assert(_generatedSourceFolder == null): "Should have already removed old folder by now"; //$NON-NLS-1$
140
IFolder srcFolder = getFolderPreference();
141         if (srcFolder == null) {
142             IStatus status = AptPlugin.createStatus(null, "Could not create generated source folder (" + //$NON-NLS-1$
143
AptConfig.getGenSrcDir(_aptProject.getJavaProject()) + ")"); //$NON-NLS-1$
144
AptPlugin.log(status);
145             return;
146         }
147         
148         // Ensure that the new folder exists on disk.
149
if (createOnDisk(srcFolder)) {
150             // Add it to the classpath.
151
if (addToClasspath(srcFolder)) {
152                 // Only if we get this far do we actually set _generatedSourceFolder.
153
synchronized ( this ) {
154                     _generatedSourceFolder = srcFolder;
155                 }
156             }
157         }
158     }
159     
160     
161     /**
162      * Creates the generated source folder if necessary. This should be called just
163      * before doing a build.
164      * No changes to the classpath will be made.
165      */

166     public void ensureFolderExists(){
167         // If APT is disabled, do nothing.
168
if (!AptConfig.isEnabled(_aptProject.getJavaProject())) {
169             return;
170         }
171         
172         // In principle we could bail out here, if (_generatedSourceFolder != null).
173
// However, this method is an opportunity to detect and fix problems such
174
// as the folder getting deleted without generatedSourceFolderDeleted()
175
// getting called (e.g., without user having done a refresh).
176
IFolder srcFolder = getFolder();
177         if (srcFolder == null) {
178             IStatus status = AptPlugin.createStatus(null, "Could not create generated source folder (" + //$NON-NLS-1$
179
AptConfig.getGenSrcDir(_aptProject.getJavaProject()) + ")"); //$NON-NLS-1$
180
AptPlugin.log(status);
181             return;
182         }
183         
184         if (createOnDisk(srcFolder)) {
185             if (isOnClasspath(srcFolder)) {
186                 synchronized (this) {
187                     // Only set _generatedSourceFolder if folder is on disk and on classpath.
188
_generatedSourceFolder = srcFolder;
189                 }
190             }
191         }
192     }
193
194     /**
195      * Create a folder on disk, unless it already exists.
196      * <p>
197      * This method will frequently be called on multiple threads simultaneously
198      * (e.g., build thread and UI thread).
199      * @param srcFolder the folder to create. Must not be null.
200      * @return true if, at the end of the routine, the folder exists on disk.
201      */

202     private boolean createOnDisk(IFolder srcFolder) {
203         boolean exists = false;
204         try {
205             // don't take any locks while creating the folder, since we are doing file-system operations
206
srcFolder.refreshLocal( IResource.DEPTH_INFINITE, null );
207             if (!srcFolder.exists()) {
208                 FileSystemUtil.makeDerivedParentFolders(srcFolder);
209                 if(AptPlugin.DEBUG)
210                     AptPlugin.trace("Created folder " + srcFolder + " on disk"); //$NON-NLS-1$ //$NON-NLS-2$
211
}
212             exists = true;
213         }
214         catch (CoreException e) {
215             e.printStackTrace();
216             AptPlugin.log(e, "Failed to ensure existence of generated source folder " + srcFolder.getName()); //$NON-NLS-1$
217
}
218         return exists;
219     }
220
221     /**
222      * Call this method when the APT_ENABLED preference has changed.
223      *
224      * Configure the generated source folder according to whether APT is enabled
225      * or disabled. If enabled, the folder will be created and a classpath entry
226      * will be added. If disabled, the folder and classpath entry will be removed.
227      * <p>
228      * This should only be called on an event thread, with no locks on the project
229      * or classpath.
230      */

231     public void enabledPreferenceChanged()
232     {
233         final boolean enable = AptConfig.isEnabled(_aptProject.getJavaProject());
234         // Short-circuit if nothing changed.
235
if (enable == (_generatedSourceFolder != null)) {
236             if( AptPlugin.DEBUG ) {
237                 AptPlugin.trace("enabledChanged() doing nothing; state is already " + enable); //$NON-NLS-1$
238
}
239             // no change in state
240
return;
241         }
242         
243         if ( AptPlugin.DEBUG ) {
244             AptPlugin.trace("enabledChanged() changing state to " + enable + //$NON-NLS-1$
245
" for " + _aptProject.getJavaProject().getElementName()); //$NON-NLS-1$
246
}
247         if( enable ) {
248             configure();
249         }
250         else {
251             removeFolder();
252         }
253     }
254
255     /**
256      * Respond to a change in the name of the generated source folder.
257      * If APT is enabled, remove the old folder and classpath entry and
258      * create new ones.
259      * <p>
260      * This should only be called on an event thread, with no locks on the project
261      * or classpath.
262      */

263     public void folderNamePreferenceChanged()
264     {
265         // if APT is disabled, we don't need to do anything
266
final boolean aptEnabled = AptConfig.isEnabled(_aptProject.getJavaProject());
267         if (!aptEnabled) {
268             return;
269         }
270         
271         // if name didn't change, we don't need to do anything
272
if (_generatedSourceFolder != null && _generatedSourceFolder.equals(getFolderPreference())) {
273             if( AptPlugin.DEBUG ) {
274                 AptPlugin.trace("folderNameChanged() doing nothing; name is already " + //$NON-NLS-1$
275
_generatedSourceFolder.getProjectRelativePath());
276             }
277             return;
278         }
279         
280         removeFolder();
281         configure();
282     }
283     
284     /**
285      * Invoked when the generated source folder has been deleted. This will
286      * flush any in-memory state tracking generated files, and cause the
287      * generated source folder to be recreated the next time we build.
288      *
289      * Note: this should only be called within a resource change event to ensure that the classpath
290      * is correct during any build. Resource change event never occurs during a build.
291      */

292     public void folderDeleted()
293     {
294         _aptProject.projectClean( false );
295         
296         IFolder srcFolder;
297         synchronized(this){
298             srcFolder = _generatedSourceFolder;
299             _generatedSourceFolder = null;
300         }
301         if(AptPlugin.DEBUG)
302             AptPlugin.trace("set _generatedSourceFolder to null; was " + srcFolder ); //$NON-NLS-1$
303
}
304     
305     /**
306      * This method will return the binary output location for the generated source folder.
307      * If the generated-source folder is not configured (i.e., not created or not added to
308      * the project's source path, then this method will return the default binary output
309      * location for the project.
310      *
311      * @return the IPath corresponding to the binary output location for the
312      * generated source folder. This is relative to the project.
313      *
314      * @throws JavaModelException
315      *
316      * @see #getFolder()
317      */

318     public IPath getBinaryOutputLocation()
319          throws JavaModelException
320     {
321         IPath outputRootPath = null;
322         IFolder generatedSourceFolder = getFolder();
323         if ( generatedSourceFolder != null && generatedSourceFolder.exists() )
324         {
325             IClasspathEntry cpe = ClasspathUtil.findProjectSourcePath( _aptProject.getJavaProject(), generatedSourceFolder );
326             if ( cpe != null )
327                 outputRootPath = cpe.getOutputLocation();
328         }
329         
330         // no output root, so get project's default output location
331
if ( outputRootPath == null )
332             outputRootPath = _aptProject.getJavaProject().getOutputLocation();
333
334         // output location is relative to the workspace, we want to make it relative to project
335
int segments = outputRootPath.matchingFirstSegments( _aptProject.getJavaProject().getPath() );
336         outputRootPath = outputRootPath.removeFirstSegments( segments );
337         
338         return outputRootPath;
339     }
340     
341     /**
342      * Get the current generated source folder; or if it is null, return
343      * an IFolder corresponding to the current generated source folder name.
344      * This is a handle-only operation and does not have anything to do with
345      * whether the folder exists on disk.
346      * @throws IllegalArgumentException if the name is invalid (e.g., "..").
347      */

348     public IFolder getFolder(){
349         
350         synchronized (this) {
351             if( _generatedSourceFolder != null )
352                 return _generatedSourceFolder;
353         }
354         
355         return getFolderPreference();
356     }
357
358     /**
359      * Get an IFolder that corresponds to the folder name preference.
360      * This has nothing to do with whether APT is enabled or disabled,
361      * nothing to do with whether the folder exists on disk; it's just
362      * a handle corresponding to a name.
363      * @return null if the IFolder could not be created, which probably
364      * means that the name is something illegal like "..".
365      */

366     private IFolder getFolderPreference() {
367         final String JavaDoc folderName = AptConfig.getGenSrcDir(_aptProject.getJavaProject());
368         IFolder folder = null;
369         try {
370             folder = _aptProject.getJavaProject().getProject().getFolder( folderName );
371         }
372         catch (IllegalArgumentException JavaDoc e) {
373             // In the event that the folderName is invalid, just return null.
374
}
375         return folder;
376     }
377     
378     /**
379      * returns true if the specified folder is the source folder used where
380      * generated files are placed.
381      *
382      * @param folder - the folder to determine if it is the generated source folder
383      * @return true if it is the generated source folder, false otherwise.
384      *
385      * @see #getFolder()
386      */

387     public boolean isGeneratedSourceFolder( IFolder folder )
388     {
389         return folder != null && folder.equals( getFolder() );
390     }
391
392     private boolean isOnClasspath(IFolder srcFolder) {
393         boolean found = false;
394         try {
395             if (ClasspathUtil.doesClasspathContainEntry(
396                     _aptProject.getJavaProject(), null, srcFolder.getFullPath(), null)) {
397                 found = true;
398             }
399         } catch (JavaModelException e) {
400             e.printStackTrace();
401         }
402         return found;
403     }
404
405     /**
406      * Remove a folder from disk and from the classpath.
407      * @param srcFolder
408      */

409     private void removeFolder() {
410         final IFolder srcFolder;
411         synchronized ( this )
412         {
413             srcFolder = _generatedSourceFolder;
414             _generatedSourceFolder = null;
415         }
416         if (srcFolder == null) {
417             return;
418         }
419         
420         // Clear out the generated file maps
421
_aptProject.projectClean(false);
422         
423         // clean up the classpath first so that when we actually delete the
424
// generated source folder we won't cause a classpath error.
425
try {
426             if (srcFolder.isDerived()) {
427                 ClasspathUtil.removeFromProjectClasspath( _aptProject.getJavaProject(), srcFolder, null );
428             }
429         } catch (JavaModelException e) {
430             AptPlugin.log( e, "Failed to remove classpath entry for old generated src folder " + srcFolder.getName() ); //$NON-NLS-1$
431
}
432         
433         final IWorkspaceRunnable runnable = new IWorkspaceRunnable(){
434             public void run(IProgressMonitor monitor)
435             {
436                 try {
437                     IResource parent = srcFolder.getParent();
438                     boolean deleted = FileSystemUtil.deleteDerivedResources(srcFolder);
439                     
440                     // We also want to delete our parent folder(s) if they are derived and empty
441
if (deleted) {
442                         while (parent.isDerived() && parent.getType() == IResource.FOLDER) {
443                             IFolder parentFolder = (IFolder)parent;
444                             if (parentFolder.members().length == 0) {
445                                 parent = parentFolder.getParent();
446                                 FileSystemUtil.deleteDerivedResources(parentFolder);
447                             }
448                             else {
449                                 break;
450                             }
451                         }
452                     }
453                     
454                 } catch(CoreException e) {
455                     AptPlugin.log(e, "failed to delete old generated source folder " + srcFolder.getName() ); //$NON-NLS-1$
456
} catch(OperationCanceledException cancel) {
457                     AptPlugin.log(cancel, "deletion of generated source folder got cancelled"); //$NON-NLS-1$
458
}
459             }
460         };
461         IWorkspace ws = ResourcesPlugin.getWorkspace();
462         try{
463             ws.run(runnable, ws.getRoot(), IWorkspace.AVOID_UPDATE, null);
464         }catch(CoreException e){
465             AptPlugin.log(e, "Runnable for deleting old generated source folder " + srcFolder.getName() + " failed."); //$NON-NLS-1$ //$NON-NLS-2$
466
}
467     }
468
469     /**
470      * Check whether the proposed name is permitted.
471      * @param folderName can be anything, including null.
472      * @return true if attempting to set the generated source folder to
473      * <code>dirString</code> is likely to succeed.
474      */

475     public static boolean validate(final IJavaProject jproj, final String JavaDoc folderName) {
476         boolean succeeded = false;
477         try {
478             if (jproj != null) {
479                 // If we have a specific project, we can just ask.
480
IFolder folder = null;
481                 folder = jproj.getProject().getFolder( folderName );
482                 succeeded = (folder != null);
483             }
484             else {
485                 // We're being asked about the default, so no specific project;
486
// here we have to guess. The code that will later fail if we
487
// get it wrong is IProject.getFolder(String). So we use some
488
// heuristics.
489
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
490                 IPath state = AptPlugin.getPlugin().getStateLocation();
491                 IPath proposed = new Path(folderName);
492                 IPath combined = state.append(proposed);
493                 if (combined.segmentCount() <= state.segmentCount()) {
494                     // proposed folder depth is too shallow
495
return false;
496                 }
497                 IFolder folder = root.getFolder(combined);
498                 succeeded = (folder != null);
499             }
500         }
501         catch (IllegalArgumentException JavaDoc e) {
502             return false;
503         }
504         return succeeded;
505     }
506
507 }
508
Popular Tags