blob: 65ba1cad20e06cb3a541de2faea0ac5076fe4b57 [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.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
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;
import org.eclipse.jdt.internal.core.JavaProject;
/**
* Class for managing generated files
*/
public class GeneratedFileManager {
private static void init()
{
_initialized = true;
IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.addResourceChangeListener( new ResourceChangedListener() );
}
public static GeneratedFileManager getGeneratedFileManager()
{
if ( ! _initialized )
init();
return _generatedFileManagerInstance;
}
/**
*
* @param parentFile
* @param typeName
* @param contents
* @param progressMonitor
* @param charsetName
* @return - the newly created IFile
* @throws CoreException
* @throws UnsupportedEncodingException
*/
public IFile generateFileDuringBuild(
IFile parentFile,
IProject project,
String typeName,
String contents,
IProgressMonitor progressMonitor,
String charsetName )
throws CoreException, UnsupportedEncodingException
{
try
{
// create folder for generated source files
IFolder folder = project.getFolder( GENERATED_SOURCE_FOLDER_NAME );
if (!folder.exists())
folder.create(true, false, null);
// split the type name into its parts
String[] parts = typeName.split( "\\.");
// create folders for the package parts
int i = 0;
for ( ;i < parts.length - 1; i++ )
{
folder = folder.getFolder( parts[i] );
if ( !folder.exists() )
folder.create( true, false, null );
}
String fileName = parts[i] + ".java";
IFile file = folder.getFile( fileName );
byte[] bytes;
if ( charsetName == null || charsetName == "" )
bytes = contents.getBytes();
else
bytes = contents.getBytes( charsetName );
InputStream is = new ByteArrayInputStream( bytes );
if ( !file.exists() )
{
file.create( is, true, progressMonitor );
}
else
{
makeReadOnly( file, false );
file.setContents( is, true, true, progressMonitor );
}
file.setDerived( true );
makeReadOnly( file, true );
updateFileMaps( typeName, parentFile, file );
return file;
}
catch ( Throwable t )
{
t.printStackTrace();
}
return null;
}
/**
* TODO: figure out how to create a working copy with a client-specified character set
*
*
* @param parentCompilationUnit
* @param typeName
* @param contents
* @param workingCopyOwner
* @param problemRequestor
* @param progressMonitor
* @return
*/
public ICompilationUnit generateFileDuringReconcile(
ICompilationUnit parentCompilationUnit, String typeName,
String contents, WorkingCopyOwner workingCopyOwner,
IProblemRequestor problemRequestor, IProgressMonitor progressMonitor )
{
ICompilationUnit workingCopy = null;
try
{
//
// get working copy (either from cache or create a new one)
//
workingCopy = getWorkingCopy(
parentCompilationUnit, typeName, contents,
workingCopyOwner, problemRequestor, progressMonitor);
//
// Update working copy's buffer with the contents of the type
//
updateWorkingCopy( contents, workingCopy, workingCopyOwner, progressMonitor );
return workingCopy;
}
catch (JavaModelException jme)
{
jme.printStackTrace();
}
catch (CoreException ce)
{
ce.printStackTrace();
}
return workingCopy;
}
public boolean isGeneratedFile( IFile f )
{
Set s = (Set)_derivedFile2Parents.get( f );
if ( s == null || s.isEmpty() )
return false;
else
return true;
}
public boolean isParentFile( IFile f )
{
Set s = (Set)_parent2DerivedFiles.get( f );
if ( s == null || s.isEmpty() )
return false;
else
return true;
}
/**
* @param parent
* @return set of Strings which are the type names known to be generated
* by the specified parent.
*/
public Set getGeneratedTypesForParent( IFile parent )
{
return (Set)_parent2TypeNames.get( parent );
}
/**
*
* @param parent
* @return Set of IFile instances that are the files known to be generated
* by this parent
*/
public Set getGeneratedFilesForParent( IFile parent )
{
return (Set)_parent2DerivedFiles.get( parent );
}
public void discardGeneratedWorkingCopy( String typeName, ICompilationUnit parentCompilationUnit )
throws JavaModelException
{
discardGeneratedWorkingCopy( typeName, parentCompilationUnit, true );
}
private void discardGeneratedWorkingCopy( String typeName, ICompilationUnit parentCompilationUnit, boolean deleteFromParent2TypeNames )
throws JavaModelException
{
if ( deleteFromParent2TypeNames )
{
Set typeNames = (Set)_parent2TypeNames.get( parentCompilationUnit.getResource() );
if ( typeNames == null ) throw new RuntimeException( "Unexpected null entry in _parent2TypeNames map.");
if ( ! typeNames.contains( typeName )) throw new RuntimeException ("type names set didn't contain expected value");
typeNames.remove( typeName );
}
Set parents = (Set) _typeName2Parents.get( typeName );
// TODO: change these to assertions
if ( parents == null ) throw new RuntimeException( "parents == null and it shouldnt");
if ( ! parents.contains( parentCompilationUnit )) throw new RuntimeException("parents set should contain parentCompilationUnit");
parents.remove( parentCompilationUnit );
if ( parents.size() == 0 )
{
ICompilationUnit cu = (ICompilationUnit)_typeName2WorkingCopy.get( typeName );
if ( cu == null ) throw new RuntimeException( "compilation unit is null and it shouldn't be");
_typeName2WorkingCopy.remove( typeName );
cu.discardWorkingCopy();
}
}
public void parentWorkingCopyDiscarded( ICompilationUnit parentCompilationUnit )
throws JavaModelException
{
Set typeNames = (Set)_parent2TypeNames.get( parentCompilationUnit.getResource() );
if ( typeNames == null || typeNames.size() == 0 )
return;
Iterator it = typeNames.iterator();
while ( it.hasNext() )
{
String typeName = (String) it.next();
it.remove();
discardGeneratedWorkingCopy( typeName, parentCompilationUnit, false );
}
}
public void parentFileDeleted( IFile parent, IProgressMonitor monitor )
throws CoreException
{
Set derivedFiles = (Set) _parent2DerivedFiles.get( parent );
Iterator it = derivedFiles.iterator();
while ( it.hasNext() )
{
IFile generatedFile = (IFile) it.next();
it.remove();
deleteGeneratedFile( generatedFile, parent, monitor, false );
}
}
public void deleteGeneratedFile(IFile fileToDelete, IFile parent, IProgressMonitor progressMonitor )
throws CoreException
{
deleteGeneratedFile( fileToDelete, parent, progressMonitor, true );
}
private void deleteGeneratedFile(IFile fileToDelete, IFile parent, IProgressMonitor progressMonitor, boolean deleteFromParent2DerivedFiles )
throws CoreException
{
// update _parents2DerivedFiles map
if ( deleteFromParent2DerivedFiles )
{
Set derivedFiles = (Set)_parent2DerivedFiles.get( parent );
// assertions
if ( derivedFiles == null ) throw new RuntimeException( "derivedFiles is null and it shouldn't be");
if ( ! derivedFiles.contains( fileToDelete )) throw new RuntimeException( "derivedFiles does not contain fileToDelete");
derivedFiles.remove( fileToDelete );
}
// update _derivedFile2Parents map and delete file if it has no other parents
Set parents = (Set) _derivedFile2Parents.get( fileToDelete );
// assertions
if( parents == null ) throw new RuntimeException( " parents is null and it shouldn't be" );
if( ! parents.contains( parent )) throw new RuntimeException( "parents set does not contain parent" );
parents.remove( parent );
if ( parents.size() == 0 )
{
// MRK TODO - uncomment this!!!
/*
fileToDelete.delete( true, true, progressMonitor );
*/
}
}
public void generatedFileDeleted( IFile deletedFile, IProgressMonitor progressMonitor )
{
Set parents = (Set) _derivedFile2Parents.get( deletedFile );
if ( parents == null || parents.isEmpty() )
return;
String typeName = getTypeNameForDerivedFile( deletedFile );
Iterator it = parents.iterator();
while ( it.hasNext() )
{
IFile parent = (IFile) it.next();
Set s = (Set)_parent2DerivedFiles.get( parent );
s.remove( deletedFile );
s = (Set)_parent2TypeNames.get( parent );
s.remove( typeName );
}
_derivedFile2Parents.remove( deletedFile );
_typeName2Parents.remove( typeName );
_typeName2WorkingCopy.remove( typeName );
}
/**
* 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();
IProject project = f.getProject();
IFolder folder = project.getFolder( GENERATED_SOURCE_FOLDER_NAME );
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 );
}
private ICompilationUnit getWorkingCopy(ICompilationUnit parentCompilationUnit, String typeName,
String contents, WorkingCopyOwner workingCopyOwner,
IProblemRequestor problemRequestor, IProgressMonitor progressMonitor)
throws CoreException, JavaModelException
{
//
// check cache to see if we already have a working copy
//
ICompilationUnit workingCopy = (ICompilationUnit) _typeName2WorkingCopy.get( typeName );
if ( workingCopy != null )
{
updateMaps( typeName, parentCompilationUnit, workingCopy );
return workingCopy;
}
IProject project = parentCompilationUnit.getResource().getProject();
JavaProject jp = (JavaProject) parentCompilationUnit.getJavaProject();
//
// create folder for generated source files
//
IFolder folder = project.getFolder( GENERATED_SOURCE_FOLDER_NAME );
if (!folder.exists())
folder.create(true, true, null);
//
// make sure __generated_src dir is on the cp if not already
//
updateProjectClasspath( jp, folder, progressMonitor );
//
// 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";
}
else
{
pkgName = "";
fname = typeName + ".java";
}
//
// create compilation unit
//
IPackageFragmentRoot root = jp.getPackageFragmentRoot(folder);
IPackageFragment pkgFragment =
root.createPackageFragment( pkgName, true, null );
ICompilationUnit cu = pkgFragment.getCompilationUnit( fname );
if ( cu == null || ! cu.getResource().exists() )
{
cu = pkgFragment.createCompilationUnit(
fname, contents, true, progressMonitor );
}
else
{
makeReadOnly( cu, false );
}
//
// TODO: can we call getWorkingCopy here?
//
cu.becomeWorkingCopy(problemRequestor, progressMonitor);
workingCopy = cu;
//
// update maps
//
updateMaps( typeName, parentCompilationUnit, workingCopy );
// we save this here since the resource has to exist on disk
workingCopy.commitWorkingCopy( true, progressMonitor );
//
// make the file derived so that it is not checked into source control.
//
makeDerived( workingCopy );
//
// make working copy read-only
//
makeReadOnly( workingCopy, true );
return workingCopy;
}
private void makeReadOnly( ICompilationUnit cu, boolean readOnly )
throws CoreException
{
IResource r = cu.getResource();
makeReadOnly( r, readOnly );
}
/**
* make the compilation unit read-only
*/
private void makeReadOnly( IResource r, boolean readOnly )
throws CoreException
{
if ( r.exists() )
{
ResourceAttributes ra = r.getResourceAttributes();
if (ra == null)
ra = new ResourceAttributes();
ra.setReadOnly( readOnly );
r.setResourceAttributes(ra);
}
}
private void makeDerived( ICompilationUnit cu )
throws CoreException
{
IResource r = cu.getResource();
if ( r.exists() )
r.setDerived( true );
}
private void updateWorkingCopy(
String contents, ICompilationUnit workingCopy,
WorkingCopyOwner workingCopyOwner, IProgressMonitor progressMonitor )
throws JavaModelException
{
//
// TODO - reuse existing char[] if there is one?
//
IBuffer b = workingCopy.getBuffer();
char[] buf = new char[contents.length()];
contents.getChars(0, contents.length(), buf, 0);
b.setContents(buf);
workingCopy.reconcile(AST.JLS3, true, workingCopyOwner,
progressMonitor);
}
private void updateMaps( String typeName, ICompilationUnit parentCompilationUnit, ICompilationUnit workingCopy )
{
IFile parentFile = (IFile) parentCompilationUnit.getResource();
IFile generatedFile = (IFile) workingCopy.getResource();
updateFileMaps( typeName, parentFile, generatedFile );
// type name -> set of parent compilation unit
Set s = (Set)_typeName2Parents.get( typeName );
if ( s == null )
{
s = new HashSet();
_typeName2Parents.put( typeName, s );
}
s.add( parentCompilationUnit );
// type name -> working copy
ICompilationUnit cu = (ICompilationUnit)_typeName2WorkingCopy.get( typeName );
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" );
}
else
_typeName2WorkingCopy.put( typeName, workingCopy );
}
private void updateFileMaps( String typeName, IFile parentFile, IFile generatedFile )
{
// parent IFile -> set of generated type name
Set s = (Set) _parent2TypeNames.get( parentFile );
if ( s == null )
{
s = new HashSet();
_parent2TypeNames.put( parentFile, s );
}
s.add( typeName );
// add parent file -> set of derived files
s = (Set)_parent2DerivedFiles.get( parentFile );
if ( s == null )
{
s = new HashSet();
_parent2DerivedFiles.put( parentFile, s );
}
s.add( generatedFile );
// add derived file -> set of parent files
s = (Set) _derivedFile2Parents.get( generatedFile );
if ( s == null )
{
s = new HashSet();
_derivedFile2Parents.put( generatedFile, s );
}
s.add( parentFile );
}
private void updateProjectClasspath( JavaProject jp, IFolder folder, IProgressMonitor progressMonitor )
throws JavaModelException
{
IClasspathEntry[] cp = jp.getRawClasspath();
IClasspathEntry generatedSourceClasspathEntry =
JavaCore.newSourceEntry(folder.getFullPath());
boolean found = false;
for (int i = 0; i < cp.length; i++)
{
if (cp[i].equals(generatedSourceClasspathEntry))
{
found = true;
break;
}
}
if (!found)
{
IClasspathEntry[] newCp = new IClasspathEntry[cp.length + 1];
System.arraycopy(cp, 0, newCp, 0, cp.length);
newCp[newCp.length - 1] = generatedSourceClasspathEntry;
jp.setRawClasspath(newCp, progressMonitor );
}
}
/**
* map from IFile of parent file to Set <IFile>of derived files
*/
private Map _parent2DerivedFiles = new HashMap();
/**
* map from IFile of dervied file to Set <IFile>of parent files
*/
private Map _derivedFile2Parents = new HashMap();
/**
* map from ICompilationUnit of parent working copy to Set
* <String> of type names generated by that file
*
* Map<ICompilationUnit, Set<String>>
*/
private Map _parent2TypeNames = new HashMap();
/**
* map from typename of generated file to Set<ICompilationUnit>of parent
* working copies
*
* Map<String, Set<ICompilationUnit>>
*/
private Map _typeName2Parents = new HashMap();
/**
* Map from type name to the working copy in memory of that type name
*
* Map<String, ICompilationUnit>
*/
private Map _typeName2WorkingCopy = new HashMap();
/**
* TODO: need to deal with synchronization on this instance. Also, may need to break this up per project basis...
*/
private static GeneratedFileManager _generatedFileManagerInstance = new GeneratedFileManager();
private static boolean _initialized = false;
private static final String GENERATED_SOURCE_FOLDER_NAME = "__generated_src";
}