blob: f28eb4ac4866a5853c6b8c21bdf133ed04bc7457 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 BEA Systems, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* mkaufman@bea.com - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.apt.core.internal.generatedfile;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.apt.core.AptPlugin;
import org.eclipse.jdt.apt.core.internal.AptProject;
import org.eclipse.jdt.apt.core.internal.Messages;
import org.eclipse.jdt.apt.core.internal.env.ProcessorEnvImpl;
import org.eclipse.jdt.apt.core.internal.util.FileSystemUtil;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IProblemRequestor;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.dom.AST;
/**
* This class is used for managing generated files.
*
* There are four maps that are used. Two are used to track the relationships
* between parent files & generated files (
* <code>_parentFile2GeneratedFiles</code> & <code>_generatedFile2ParentFiles</code>).
* The other two maps are used to track cached working copies:
* <code>_generatedFile2WorkingCopy</code> maps a generated file to its
* working copy, and <code>_generatedWorkingCopy2OpenParentFiles</code>
* maps a working copy to any parent files that may be open.
*
* The file maps have entries added when a file is generated during a build.
* The file maps & working-copy maps haven entries added added when a file
* is added during a reconcile. There are various entry-points to keep the
* maps up-to-date with respect to life-cycle events on the parent & generated files.
* (e.g., parentFileDeleted(), ).
*
* SYNCHRONIZATION NOTES (IMPORTANT)
* ---------------------------------
* Synchronization around the GeneratedFileManager's maps uses the GeneratedFileManager
* instance's monitor. When acquiring this monitor, DO NOT PERFORM ANY OPERATIONS
* THAT TAKE ANY OTHER LOCKS (e.g., java model operations, or file system operations like
* creating or deleting a file or folder). If you do this, then the code is subject to
* deadlock situations. For example, a resource-changed listener may take a resource lock
* and then call into the GeneratedFileManager for clean-up, where your code could reverse
* the order in which the locks are taken. This is bad, so be careful.
*
*/
public class GeneratedFileManager {
// disable type generation during reconcile. This can cause deadlock.
// See radar bug #238684
public static final boolean GENERATE_TYPE_DURING_RECONCILE = false;
/**
* map from IFile of parent file to Set <IFile>of generated files
*/
private Map<IFile, Set<IFile>> _parentFile2GeneratedFiles = new HashMap();
/**
* map from IFile of generated file to Set <IFile>of parent files
*/
private Map<IFile, Set<IFile>> _generatedFile2ParentFiles = new HashMap();
/**
* Map from a the working copy of a generated file to its *open* parents. Note that
* the set of parent files are only those parent files that have an open editor.
* This set should be a subset for a correpsonding entry in the _generatedFile2Parents map.
*/
private Map<ICompilationUnit, Set<IFile>> _generatedWorkingCopy2OpenParentFiles = new HashMap();
/**
* Map from type name to the working copy in memory of that type name
*
* Map<String, ICompilationUnit>
*/
private Map<IFile, ICompilationUnit> _generatedFile2WorkingCopy = new HashMap();
private final IJavaProject _jProject;
private final GeneratedSourceFolderManager _gsfm;
// This is set when the build starts, and accessed during type generation.
private IPackageFragmentRoot _generatedPackageFragmentRoot;
// This is initialized/reset when the build starts, and accessed during type generation.
// It has the same life-cycle as _generatedPackageFragmentRoot.
// This bit may be set to <code>true</code> during the first type generation to prevent any
// future type generation due to configuration problem.
private boolean _skipTypeGeneration = false;
// The name of the generated source folder when the _generatedPackageFragmenRoot is
// initialized. Used for problem reporting.
private String _snapshotFolderName = null;
/**
* Clients should not instantiate this class; it is created only by @see AptProject .
*/
public GeneratedFileManager(final AptProject aptProject, final GeneratedSourceFolderManager gsfm) {
_jProject = aptProject.getJavaProject();
_gsfm = gsfm;
}
static
{
// register element-changed listener to clean up working copies
int mask = ElementChangedEvent.POST_CHANGE;
JavaCore.addElementChangedListener( new WorkingCopyCleanupListener(), mask );
}
/**
* Invoked when a file is generated during a build. The generated file and intermediate
* directories will be created if they don't exist. This method takes file-system locks,
* and assumes that the calling method has at some point acquired a workspace-level
* resource lock.
*
* @param parentFile the parent of the type being generated
* @param typeName the dot-separated java type name of the type being generated
* @param contents the java code contents of the new type .
* @param progressMonitor a progres monitor. This may be null.
* @param charsetName the character set to use when creating the new file. This can be null
* or the empty string, in which case the platform default encoding will be used.
*
* @return - the newly created IFile along with whether it was modified
*
* @throws CoreException
* @throws UnsupportedEncodingException
*/
public FileGenerationResult generateFileDuringBuild(
IFile parentFile,
String typeName,
String contents,
ProcessorEnvImpl env,
IProgressMonitor progressMonitor)
throws CoreException
{
if( _skipTypeGeneration ) return null;
// If the generated package fragment root wasn't set,
// then our classpath is incorrect. Add a marker and return
else if( _generatedPackageFragmentRoot == null ){
String message = Messages.bind(
Messages.GeneratedFileManager_missing_classpath_entry,
new String[] {_snapshotFolderName});
IMarker marker = _jProject.getProject().createMarker(AptPlugin.APT_CONFIG_PROBLEM_MARKER);
marker.setAttributes(
new String[] {
IMarker.MESSAGE,
IMarker.SEVERITY
},
new Object[] {
message,
IMarker.SEVERITY_ERROR
}
);
// disable any future type generation
_skipTypeGeneration = true;
return null;
}
try{
if( typeName.indexOf('/') != -1 )
typeName = typeName.replace('/', '.');
int separatorIndex = typeName.lastIndexOf('.');
final String typeSimpleName;
final String pkgName;
if( separatorIndex == -1 ){
pkgName = ""; //$NON-NLS-1$
typeSimpleName = typeName;
}
else{
pkgName = typeName.substring(0, separatorIndex);
typeSimpleName = typeName.substring(separatorIndex + 1, typeName.length());
}
// NOTE: Do NOT ever create any type of resource (files, folders) through the
// resource API. The resource change event will not go out until the build
// is completed. Instead always go through the JavaModel. -theodora
IFolder genSrcFolder = (IFolder)_generatedPackageFragmentRoot.getResource();
final Set<IContainer> newFolders = getNewPackageFolders(pkgName, genSrcFolder);
IPackageFragment pkgFrag = _generatedPackageFragmentRoot.createPackageFragment(pkgName, true, progressMonitor);
if( pkgFrag == null ){
final Exception e = new IllegalStateException("failed to locate package '" + pkgName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
e.printStackTrace();
throw e;
}
// mark all newly create folders as derived.
markNewFoldersAsDerived((IContainer)pkgFrag.getResource(), newFolders);
final String cuName = typeSimpleName + ".java"; //$NON-NLS-1$
ICompilationUnit unit = pkgFrag.getCompilationUnit(cuName);
IFile file = (IFile)unit.getResource();
boolean contentsDiffer = true;
if (unit.exists()) {
InputStream oldData = null;
InputStream is = null;
try {
is = new ByteArrayInputStream( contents.getBytes() );
oldData = new BufferedInputStream( ((IFile)unit.getResource()).getContents());
contentsDiffer = !FileSystemUtil.compareStreams(oldData, is);
}
catch (CoreException ce) {
// Do nothing. Assume the new content is different
}
finally {
if (oldData != null) {
try {
oldData.close();
}
catch (IOException ioe)
{}
}
if (is != null) {
try {
is.close();
}
catch (IOException ioe)
{}
}
}
}
if( contentsDiffer ){
if( unit.exists() && unit.isOpen() ){
// directly modify the content of the working copy
// so that UI will pick up the change.
IBuffer buffer = unit.getBuffer();
if (buffer == null){
throw new IllegalStateException("Unable to update unit for " + cuName); //$NON-NLS-1$
}
buffer.setContents(contents.toCharArray());
buffer.save(progressMonitor, true);
}
else{
ICompilationUnit newUnit = null;
newUnit = pkgFrag.createCompilationUnit(cuName, contents, true,
progressMonitor);
if( newUnit == null ) {
throw new IllegalStateException("Unable to create unit for " + cuName); //$NON-NLS-1$
}
if( AptPlugin.DEBUG )
AptPlugin.trace("generated " + typeName ); //$NON-NLS-1$
newUnit.save(progressMonitor, true);
}
}
// Mark the file as derived. Note that certain user actions may have
// deleted this file before we get here, so if the file doesn't exist,
// marking it derived throws a ResourceException
if (file.exists()) {
file.setDerived(true);
}
// We used to also make the file read-only. This is a bad idea,
// as refactorings then fail in the future, which is worse
// than allowing a user to modify a generated file.
// during a batch build, parentFile will be null.
// Only keep track of ownership in iterative builds
if( parentFile != null ) {
addEntryToFileMaps( parentFile, file );
}
return new FileGenerationResult(file, contentsDiffer);
}
catch(Throwable e){
AptPlugin.log(e, "(2)failed to generate type " + typeName); //$NON-NLS-1$
e.printStackTrace();
}
return null; // something failed. The catch block have already logged the error.
}
/**
* This function generates a type "in-memory" by creating or updating a working copy with the
* specified contents. The generated-source folder must be configured correctly for this to
* work. This method takes no locks, so it is safe to call when holding fine-grained resource
* locks (e.g., during some reconcile paths). Since this only works on an in-memory working
* copy of the type, the IFile for the generated type may not exist on disk. Likewise, the
* corresponding package directories of type-name may not exist on disk.
*
* TODO: figure out how to create a working copy with a client-specified character set
*
* @param parentCompilationUnit - the parent compilation unit.
* @param typeName - the dot-separated java type name for the new type
* @param contents - the contents of the new type
* @param workingCopyOwner - the working copy owner. This may be null. If null, parentCompilationUnit.getOwner()
* will be used.
* @param problemRequestor - this may be null.
* @param progressMonitor - this may be null
*
* @return The FileGenerationResult. This will return null if the generated source folder
* is not configured.
*
*/
public FileGenerationResult generateFileDuringReconcile(
ICompilationUnit parentCompilationUnit, String typeName,
String contents, WorkingCopyOwner workingCopyOwner,
IProblemRequestor problemRequestor, IProgressMonitor progressMonitor )
throws CoreException
{
if (!GENERATE_TYPE_DURING_RECONCILE)
return null;
// We have disabled Reconcile-time type generated for a long time and
// everything else has changed ever since. Don't expect the following
// code to work when we enable reconcile-time type generation again.
// -theodora
// Work item
// 1) make sure generated source directory and classpath is setup properly
// (i don't think it is today) -theodora
ICompilationUnit workingCopy = null;
FileGenerationResult result = null;
IFile parentFile = (IFile)parentCompilationUnit.getResource();
try
{
//
// get working copy (either from cache or create a new one)
//
workingCopy = getCachedWorkingCopy( parentFile, typeName );
if ( workingCopyOwner == null )
workingCopyOwner = parentCompilationUnit.getOwner();
if ( workingCopy == null )
{
// create a new working copy
workingCopy = createNewWorkingCopy(
parentFile, typeName, contents,
workingCopyOwner, problemRequestor, progressMonitor);
workingCopy.reconcile(AST.JLS3, true, workingCopyOwner,
progressMonitor);
// TODO: pass in correct flag for source-patch changed. This is probably not going to matter. Per 103183, we will either
// disable reconcile-time generation, or do it without any modifications, so we shouldn't have to worry about this.
result = new FileGenerationResult((IFile)workingCopy.getResource(), true);
}
else
{
//
// Update working copy's buffer with the contents of the type
//
boolean modified = updateWorkingCopy( contents, workingCopy, workingCopyOwner, progressMonitor );
result = new FileGenerationResult((IFile)workingCopy.getResource(), modified);
}
return result;
}
catch (JavaModelException jme)
{
AptPlugin.log(jme, "Could not generate file for type: " + typeName); //$NON-NLS-1$
}
return new FileGenerationResult((IFile)workingCopy.getResource(), true);
}
/**
* returns true if the specified file is a generated file (i.e., it has one or more parent files)
*
* @param f the file in question
* @return true
*/
public synchronized boolean isGeneratedFile( IFile f )
{
Set<IFile> s = _generatedFile2ParentFiles.get( f );
if ( s == null || s.isEmpty() )
return false;
else
return true;
}
/**
* returns true if the specified file is a parent file (i.e., it has one or more generated files)
*
* @param f - the file in question
* @return true if the file is a parent, false otherwise
*
* @see #getGeneratedFilesForParent(IFile)
* @see #isGeneratedFile(IFile)
*/
public synchronized boolean isParentFile( IFile f )
{
Set<IFile> s = _parentFile2GeneratedFiles.get( f );
if ( s == null || s.isEmpty() )
return false;
else
return true;
}
/**
* @param parent - the parent file that you want to get generated files for
* @return Set of IFile instances that are the files known to be generated
* by this parent
*
* @see #isParentFile(IFile)
* @see #isGeneratedFile(IFile)
*/
public synchronized Set<IFile> getGeneratedFilesForParent( IFile parent )
{
Set<IFile> s = _parentFile2GeneratedFiles.get( parent );
if (s == null )
s = Collections.emptySet();
else
// make a copy of the set to avoid any race conditions
s = new HashSet<IFile>( s );
return s;
}
/**
* Invoked whenever we potentially need to discard a generated working copy.
* Note that the generated working copy may not necessarily be discarded. It
* will only be discarded if specified parent file is the only open parent file
* for the specified Generated file. If there are other parent open parent files,
* then the working copy for the generated file will remain open, but the link between
* the generated file's working copy and its open parent file will be discarded.
*
* @param generatedFile - the generated file that we potentially want to discard
* @param parentFile - the parent file for the generated file
* @throws JavaModelException
*/
private void discardGeneratedWorkingCopy( IFile generatedFile, IFile parentFile )
throws JavaModelException
{
removeFromWorkingCopyMaps( generatedFile, parentFile );
}
/**
* Invoked whenever a parent working copy has been discarded.
*
* @param parentFile. The parent file whose working copy has been discarded
* @throws JavaModelException if there is a problem discarding any working copies
* generated by the parent.
*/
public void parentWorkingCopyDiscarded( IFile parentFile )
throws JavaModelException
{
Set<IFile> generatedFiles;
synchronized( this )
{
generatedFiles = _parentFile2GeneratedFiles.get( parentFile );
if ( generatedFiles == null || generatedFiles.size() == 0 )
return;
// make a copy to prevent race conditions
generatedFiles = new HashSet<IFile>( generatedFiles );
}
for ( IFile generatedFile : generatedFiles )
discardGeneratedWorkingCopy( generatedFile, parentFile );
}
/**
* Invoked whenever a parent file has been deleted
*/
public void parentFileDeleted( IFile parent, IProgressMonitor monitor )
throws CoreException
{
Set<IFile> generatedFiles;
synchronized( this )
{
generatedFiles = _parentFile2GeneratedFiles.get( parent );
// make a copy to avoid race conditions
generatedFiles = new HashSet<IFile>( generatedFiles );
}
for ( IFile generatedFile : generatedFiles )
deleteGeneratedFile( generatedFile, parent, monitor );
}
/**
* Invoked whenever we need to delete a generated file (e.g., the parent file has been deleted,
* or a parent stops generating a specific child). Note that the generated file will only
* be deleted if the specified parent file is the only parent of the specified generated file.
* If there are other parents, then the generated file will not be deleted, but the link associating
* the parent and the generated file will be removed (i.e., the the generated file will no longer consider
* the parent file a "parent").
*
*/
public boolean deleteGeneratedFile(IFile generatedFile, IFile parentFile, IProgressMonitor progressMonitor )
throws CoreException
{
removeFromFileMaps( generatedFile, parentFile );
boolean delete = false;
synchronized ( this )
{
Set<IFile> parents = _generatedFile2ParentFiles.get( generatedFile );
// this can be empty, but it shouldn't be null here unless parentFile was never a parent of generatedFile
if ( parents == null ) throw new RuntimeException("unexpected null value for parents set for file " + generatedFile); //$NON-NLS-1$
if (parents == null || parents.size() == 0)
delete = true;
}
if ( delete ){
final IFolder genFolder = _gsfm.getFolder();
assert genFolder != null : "Generated folder == null"; //$NON-NLS-1$
IContainer parent = generatedFile.getParent();
try {
generatedFile.delete(true, true, progressMonitor);
}
catch (CoreException ce) {
// File was locked or read-only
AptPlugin.logWarning(ce, "Failed to delete file: " + generatedFile); //$NON-NLS-1$
}
// not deleting the generated source folder and only
// delete generated folders containing the generated file.
while( !genFolder.equals(parent) && parent != null && parent.isDerived() ){
final IResource[] members = parent.members();
IContainer grandParent = parent.getParent();
// last one turns the light off.
if( members == null || members.length == 0 )
parent.delete(true, progressMonitor);
else
break;
parent = grandParent;
}
}
return delete;
}
/**
* Invoked whenever a previously-generated file is removed during reconcile. We put an empty buffer in the contents
* of the working copy. This effectively makes the type go away from the in-memory type system. A subsequent
* build is necessary to actually remove the file from disk, and to actually remove references in the
* the generated file manager's state.
*
* @param generatedFile - the generated file whose working-copy buffer we want to be the empty string.
* @param parentWorkingCopy - the parent working copy.
* @param progressMonitor - a progress monitor
*
* @return return true if the working-copy's buffer is set to the empty-string, false otherwise.
*
* @throws JavaModelException
*/
public boolean deleteGeneratedTypeInMemory(IFile generatedFile, ICompilationUnit parentWorkingCopy, IProgressMonitor progressMonitor )
throws JavaModelException, CoreException
{
if( !GENERATE_TYPE_DURING_RECONCILE )
return false;
// see if this is the only parent for this generated file
boolean remove = false;
IFile parentFile = (IFile) parentWorkingCopy.getResource();
ICompilationUnit workingCopy = null;
synchronized ( this )
{
// see if this generated file has any other parent files.
Set<IFile> parentFiles = _generatedFile2ParentFiles.get( generatedFile );
assert( parentFiles != null && parentFiles.contains( parentFile ) ) : "Unexpected state in GeneratedFileManager"; //$NON-NLS-1$
if ( parentFiles.size() == 1 && parentFiles.contains( parentFile ) )
{
workingCopy = _generatedFile2WorkingCopy.get( generatedFile );
remove = true;
}
else
remove = false;
}
if ( remove )
{
// we don't need to remove entries from any maps. That will happen after
// the user saves & builds.
if ( workingCopy != null )
{
updateWorkingCopy( "", workingCopy, workingCopy.getOwner(), progressMonitor ); //$NON-NLS-1$
return true;
}
else
{
// we don't have a cached working copy, so call generateWorkingCopyDuringReconcile and create an empty-stringed working copy
// for the type that was generated during build.
String typeName = getTypeNameForDerivedFile( generatedFile );
WorkingCopyOwner workingCopyOwner = parentWorkingCopy.getOwner();
generateFileDuringReconcile( parentWorkingCopy, typeName, "", workingCopyOwner, null, progressMonitor ); //$NON-NLS-1$
}
}
return remove;
}
/**
* Invoked whenever a generated file has been deleted. This method will
* clean up any in-memory state about the previously generated file.
*
* @param generatedFile - the generated file that has been deleted
* @param progressMonitor - progress monitor. this can be null.
*
* @throws JavaModelException if there is an exception when discarding an open working copy for the generated file
*/
public void generatedFileDeleted( IFile generatedFile, IProgressMonitor progressMonitor )
throws JavaModelException
{
Set<IFile> parentFiles;
synchronized( this )
{
parentFiles = _generatedFile2ParentFiles.get( generatedFile );
if ( parentFiles == null || parentFiles.isEmpty() )
return;
// make a copy to prevent race conditions
parentFiles = new HashSet<IFile>( parentFiles );
}
for ( IFile parentFile : parentFiles )
{
removeFromWorkingCopyMaps( generatedFile, parentFile );
removeFromFileMaps( generatedFile, parentFile );
}
}
/**
* given file f, return the typename corresponding to the file. This assumes
* that derived files use java naming rules (i.e., type "a.b.c" will be file
* "a/b/c.java".
*/
private String getTypeNameForDerivedFile( IFile f )
{
IPath p = f.getFullPath();
IFolder folder = _gsfm.getFolder();
IPath generatedSourcePath = folder.getFullPath();
int count = p.matchingFirstSegments( generatedSourcePath );
p = p.removeFirstSegments( count );
String s = p.toPortableString();
int idx = s.lastIndexOf( '.' );
s = p.toPortableString().replace( '/', '.' );
return s.substring( 0, idx );
}
/**
* Given a typename a.b.c, this will return the IFile for the
* type name, where the IFile is in the GENERATED_SOURCE_FOLDER_NAME.
*/
private IFile getIFileForTypeName( String typeName )
{
// split the type name into its parts
String[] parts = typeName.split( "\\."); //$NON-NLS-1$
IFolder folder = _gsfm.getFolder();
for ( int i = 0; i < parts.length - 1; i++ )
folder = folder.getFolder( parts[i] );
// the last part of the type name is the file name
String fileName = parts[parts.length - 1] + ".java"; //$NON-NLS-1$
IFile file = folder.getFile( fileName );
return file;
}
private void markNewFoldersAsDerived(IContainer folder, Set<IContainer> newFolders)
throws CoreException
{
while(folder != null){
if( newFolders.contains(folder) ){
folder.setDerived(true);
}
folder = folder.getParent();
}
}
private Set<IContainer> getNewPackageFolders(String pkgName, IFolder parent )
{
StringBuilder buffer = new StringBuilder();
Set<IContainer> newFolders = new HashSet<IContainer>();
for( int i=0, len=pkgName.length(); i<len; i++ ){
final char c = pkgName.charAt(i);
if( c != '.')
buffer.append(c);
// create a folder when we see a dot or when we are at the end.
if( c == '.' || i == len - 1){
if( buffer.length() > 0 ){
final IFolder folder = parent.getFolder(buffer.toString());
if( !folder.exists()){
newFolders.add(folder);
}
parent = folder;
// reset the buffer
buffer.setLength(0);
}
}
}
return newFolders;
}
/**
* Create all the folders corresponding to specified package name
* and mark all newly created ones as derived.
* @param pkgName dot-separated package name
* @param parent the parent folder of the folder to be created
* @throws CoreException when the folder creation fails.
*/
private void createFoldersForPackage(String pkgName, IFolder parent)
throws CoreException
{
StringBuilder buffer = new StringBuilder();
for( int i=0, len=pkgName.length(); i<len; i++ ){
final char c = pkgName.charAt(i);
if( c != '.')
buffer.append(c);
// create a folder when we see a dot or when we are at the end.
if( c == '.' || i == len - 1){
if( buffer.length() > 0 ){
final IFolder folder = parent.getFolder(buffer.toString());
if( !folder.exists()){
folder.create(true, true, null);
folder.setDerived(true);
}
parent = folder;
// reset the buffer
buffer.setLength(0);
}
}
}
}
/**
* Called at the start of build in order to cache our package fragment root
*/
public void compilationStarted() {
try{
// clear out any generated source folder config markers
IMarker[] markers = _jProject.getProject().findMarkers(AptPlugin.APT_CONFIG_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE);
if( markers != null ){
for( IMarker marker : markers )
marker.delete();
}
}
catch(CoreException e){
AptPlugin.log(e, "Unable to delete configuration marker."); //$NON-NLS-1$
}
_skipTypeGeneration = false;
_gsfm.createGeneratedSourceFolder();
final IFolder genFolder;
synchronized(this){
genFolder = _gsfm.getFolder();
_snapshotFolderName = _gsfm.getFolderName();
}
try {
_generatedPackageFragmentRoot = null;
IPackageFragmentRoot[] roots = _jProject.getAllPackageFragmentRoots();
for (IPackageFragmentRoot root : roots) {
final IResource resource = root.getResource();
if( resource != null && resource.equals(genFolder)){
_generatedPackageFragmentRoot = root;
return;
}
}
}
catch (JavaModelException jme) {
AptPlugin.log(jme, "Failure during start of compilation attempting to create generated source folder"); //$NON-NLS-1$
}
}
//
// check cache to see if we already have a working copy
//
private ICompilationUnit getCachedWorkingCopy( IFile parentFile, String typeName )
{
IFile derivedFile = getIFileForTypeName( typeName );
ICompilationUnit workingCopy= null;
synchronized( this )
{
workingCopy = _generatedFile2WorkingCopy.get( derivedFile );
}
if ( workingCopy != null )
addEntryToWorkingCopyMaps( parentFile, workingCopy );
return workingCopy;
}
private ICompilationUnit createNewWorkingCopy(IFile parentFile, String typeName,
String contents, WorkingCopyOwner workingCopyOwner,
IProblemRequestor problemRequestor, IProgressMonitor progressMonitor)
throws JavaModelException
{
IFolder folder = _gsfm.getFolder();
//
// figure out package part of type & file name
//
String pkgName;
String fname;
int idx = typeName.lastIndexOf( '.' );
if ( idx > 0 )
{
pkgName = typeName.substring( 0, idx );
fname =
typeName.substring(idx + 1, typeName.length()) + ".java"; //$NON-NLS-1$
}
else
{
pkgName = ""; //$NON-NLS-1$
fname = typeName + ".java"; //$NON-NLS-1$
}
//
// create compilation unit
//
IPackageFragmentRoot root = _jProject.getPackageFragmentRoot(folder);
IPackageFragment pkgFragment = root.getPackageFragment(pkgName );
ICompilationUnit cu = pkgFragment.getCompilationUnit( fname );
//
// BecomeWorkingCopyOperation shouldn't take any resource locks to run, so we should be thread-safe here
//
cu.becomeWorkingCopy(problemRequestor, progressMonitor);
ICompilationUnit workingCopy = cu;
//
// update working copy
//
updateWorkingCopy( contents, workingCopy, workingCopyOwner, progressMonitor );
//
// update maps
//
addEntryToWorkingCopyMaps( parentFile, workingCopy );
return workingCopy;
}
/**
* Returns true if the file was modified
*/
private static boolean updateWorkingCopy(
String contents, ICompilationUnit workingCopy,
WorkingCopyOwner workingCopyOwner, IProgressMonitor progressMonitor )
throws JavaModelException
{
IBuffer b = workingCopy.getBuffer();
char[] oldBuf = b.getCharacters();
// Diff the contents, and only set if they differ
if (oldBuf.length == contents.length()) {
boolean contentsMatch = true;
for (int i=0; i<oldBuf.length; i++) {
if (oldBuf[i] != contents.charAt(i)) {
contentsMatch = false;
break;
}
}
if (contentsMatch) {
// No change, no need to update buffer
return false;
}
}
b.setContents(contents);
workingCopy.reconcile(AST.JLS3, true, workingCopyOwner,
progressMonitor);
return true;
}
private void addEntryToWorkingCopyMaps( IFile parentFile, ICompilationUnit workingCopy )
{
IFile generatedFile = (IFile) workingCopy.getResource();
addEntryToFileMaps( parentFile, generatedFile );
synchronized( this )
{
ICompilationUnit cu = _generatedFile2WorkingCopy.get( generatedFile );
Set<IFile> parents = _generatedWorkingCopy2OpenParentFiles.get( workingCopy);
if ( cu != null )
{
//assert( cu.equals( workingCopy ) ) : "unexpected different instances of working copy for the same type";
if ( !cu.equals(workingCopy) ) throw new RuntimeException( "unexpected different instances of working copy for the same type" ); //$NON-NLS-1$
if ( parents == null || parents.size() < 1 ) throw new RuntimeException( "Unexpected size of open-parents set. Expected size >= 0"); //$NON-NLS-1$
}
else
{
_generatedFile2WorkingCopy.put( generatedFile, workingCopy );
}
if ( parents == null )
{
parents = new HashSet<IFile>();
_generatedWorkingCopy2OpenParentFiles.put( workingCopy, parents );
}
parents.add( parentFile );
}
}
public void addEntryToFileMaps( IFile parentFile, IFile generatedFile )
{
synchronized ( this )
{
// add parent file -> set of derived files
Set<IFile> fileSet = _parentFile2GeneratedFiles.get( parentFile );
if ( fileSet == null )
{
fileSet = new HashSet();
_parentFile2GeneratedFiles.put( parentFile, fileSet );
}
fileSet.add( generatedFile );
// add derived file -> set of parent files
fileSet = _generatedFile2ParentFiles.get( generatedFile );
if ( fileSet == null )
{
fileSet = new HashSet();
_generatedFile2ParentFiles.put( generatedFile, fileSet );
}
fileSet.add( parentFile );
}
}
private void removeFromFileMaps( IFile generatedFile, IFile parentFile )
throws JavaModelException
{
boolean discardWorkingCopy;
synchronized( this )
{
discardWorkingCopy = _generatedFile2WorkingCopy.containsKey(generatedFile);
}
// don't want to hold a lock when we call discardGeneratedWorkingCopy...
if ( discardWorkingCopy )
discardGeneratedWorkingCopy(generatedFile, parentFile);
synchronized( this )
{
Set<IFile> derivedFiles = _parentFile2GeneratedFiles.get(parentFile);
// assertions
if (derivedFiles == null)
throw new RuntimeException(
"derivedFiles is null and it shouldn't be"); //$NON-NLS-1$
derivedFiles.remove(generatedFile);
// update _derivedFile2Parents map
Set<IFile> parents = _generatedFile2ParentFiles.get(generatedFile);
// assertions
if (parents == null)
throw new RuntimeException(" parents is null and it shouldn't be"); //$NON-NLS-1$
if (!parents.contains(parentFile))
throw new RuntimeException("parents set does not contain parent. Parent: " + parentFile + ". Child: " + generatedFile); //$NON-NLS-1$ //$NON-NLS-2$
parents.remove(parentFile);
}
}
private void removeFromWorkingCopyMaps( IFile derivedFile, IFile parentFile )
throws JavaModelException
{
ICompilationUnit workingCopy = null;
boolean discard = false;
synchronized( this )
{
workingCopy = _generatedFile2WorkingCopy.get( derivedFile );
if ( workingCopy == null )
return;
Set<IFile> parents = _generatedWorkingCopy2OpenParentFiles.get( workingCopy );
// TODO: change these to assertions
if ( parents == null ) throw new RuntimeException( "parents == null and it shouldnt"); //$NON-NLS-1$
if ( ! parents.contains( parentFile )) throw new RuntimeException("parents set should contain parentCompilationUnit"); //$NON-NLS-1$
// remove entry from parents _derivedWorkingCopy2OpenParentFiles
parents.remove( parentFile );
// and remove entry from _derivedFile2WorkingCopy
if ( parents.size() == 0 )
{
_generatedFile2WorkingCopy.remove( derivedFile );
discard = true;
}
}
if ( discard )
workingCopy.discardWorkingCopy();
}
public void clearWorkingCopyMaps()
{
// first discard all working copies
Collection<ICompilationUnit> workingCopies;
synchronized( this )
{
// make a copy to avoid race conditions
workingCopies = new ArrayList<ICompilationUnit>( _generatedFile2WorkingCopy.values() );
_generatedWorkingCopy2OpenParentFiles.clear();
_generatedFile2WorkingCopy.clear();
}
for ( ICompilationUnit workingCopy : workingCopies )
{
try
{
workingCopy.discardWorkingCopy();
}
catch( JavaModelException jme )
{
AptPlugin.log(jme, "Could not discard working copy"); //$NON-NLS-1$
// TODO: deal with this
}
}
}
public void clearAllMaps()
{
clearWorkingCopyMaps();
synchronized( this )
{
// now clear file maps
_parentFile2GeneratedFiles.clear();
_generatedFile2ParentFiles.clear();
}
}
/**
* If the given resource is a folder, then recursively deleted all derived
* files and folders contained within it. Delete the folder if it becomes empty
* and if itself is also a derived resource.
* If the given resource is a file, delete it iff it is a derived resource.
* The resource is left untouched if it is no a folder or a file.
* @param resource
* @return <code>true</code> iff the resource has been deleted.
* @throws CoreException
*/
public boolean deleteDerivedResources(final IResource resource)
throws CoreException
{
if( resource.getType() == IResource.FOLDER ){
boolean deleteFolder = resource.isDerived();
IResource[] members = ((IFolder)resource).members();
for( int i=0, len=members.length; i<len; i++ ){
deleteFolder &= deleteDerivedResources(members[i]);
}
if( deleteFolder ){
resource.delete(true, null);
return true;
}
return false;
}
else if( resource.getType() == IResource.FILE ){
if( resource.isDerived() ){
resource.delete(true, null);
return true;
}
return false;
}
// will skip pass everything else.
else
return false;
}
/**
* This method should only be used for testing purposes to ensure
* that maps contain entries when we expect them to.
*/
public synchronized boolean containsWorkingCopyMapEntriesForParent( IFile f )
{
Collection<Set<IFile>> parentSets = _generatedWorkingCopy2OpenParentFiles.values();
if ( parentSets != null )
{
for( Set<IFile> s : parentSets )
{
if ( s.contains( f ) )
return true;
}
}
Set<IFile> generatedFiles = _parentFile2GeneratedFiles.get( f );
if ( generatedFiles != null )
{
for ( IFile gf : generatedFiles )
{
ICompilationUnit cu = _generatedFile2WorkingCopy.get( gf );
if ( cu != null )
{
Set<IFile> parents = _generatedWorkingCopy2OpenParentFiles.get( cu );
if ( parents.contains( cu ) || parents.size() == 0 )
return true;
}
}
}
return false;
}
}