blob: 4e70db168cd76f4bab30d6298df576a01ccd2a63 [file] [log] [blame]
package org.eclipse.emf.examples.jet.article2.codegen;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.codegen.jet.JETEmitter;
import org.eclipse.emf.codegen.jet.JETException;
import org.eclipse.emf.codegen.merge.java.JControlModel;
import org.eclipse.emf.codegen.merge.java.JMerger;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.DiagnosticException;
import org.eclipse.emf.common.util.Monitor;
/**
* This class encapsulates access to the JET and JMerge packages.
*
* @author Remko Popma
* @version $Revision: 1.5 $ ($Date: 2006/12/29 21:12:36 $)
*/
public class JETGateway
{
private Config mConfig = null;
public JETGateway(Config config)
{
mConfig = config;
}
/**
* Invokes the JET template specified in the <code>Config</code> with the
* model specified in the <code>Config</code> and returns the generated text
* as a String.
* <p>
* This implementation uses a <code>JETEmitter</code> to translate the
* template to a Java implementation class. The translated class is created in
* a hidden project called <code>.JETEmitters</code>.
* <p>
* In order to be able to compile the translated template implementation
* class, the classes used by the model specified in the <code>Config</code>
* must be available in the classpath. For this reason, this method sets the
* first runtime library of the plugin specified in the <code>Config</code>
* as a classpath variable to the <code>.JETEmitters</code> project.
*
* @param monitor
* the progress monitor to use. May be <code>null</code>.
* @return the source code text generated by the JET template
* @throws CoreException
*/
public String generate(IProgressMonitor monitor) throws CoreException
{
try
{
monitor = createIfNull(monitor);
Config config = getConfig();
JETEmitter emitter = new JETEmitter(config.getTemplateFullUri(), getClass().getClassLoader());
emitter.addVariable(config.getClasspathVariable(), config.getPluginId());
Monitor sub = new BasicMonitor.EclipseSubProgress(monitor, 1);
String result = emitter.generate(sub, new Object []{ config.getModel() });
monitor.worked(1);
return result;
}
catch (JETException exception)
{
throw DiagnosticException.toCoreException(exception);
}
}
/**
* Merges the specified emitterResult with the contents of an existing file
* and returns the result. The existing file is not modified.
* <p>
* The location of the file to merge with is found by finding or creating the
* container (folder) for the <code>Config</code>'s package in the
* <code>Config</code>'s target folder. The name of the file to merge with
* is the <code>Config</code>'s target file.
*
* @param monitor
* the progress monitor to use. May be <code>null</code>.
* @param emitterResult
* generated content to merge with the existing content
* @return the result of merging the specified generated contents with the
* existing file
* @throws CoreException
* if an error occurs accessing the contents of the file
*/
public String merge(IProgressMonitor monitor, String emitterResult) throws CoreException
{
monitor = createIfNull(monitor);
Config config = getConfig();
IContainer container = findOrCreateContainer(monitor, config.getTargetFolder(), config.getPackageName());
if (container == null)
{
throw
new CoreException
(new Status
(IStatus.ERROR,
"org.eclipse.emf.examples.jet.article2",
0,
"Could not find or create container for package " + config.getPackageName() + " in " + config.getTargetFolder(),
null));
}
IFile targetFile = container.getFile(new Path(config.getTargetFile()));
if (!targetFile.exists())
{
monitor.worked(1);
return emitterResult;
}
JControlModel jControlModel = new JControlModel();
jControlModel.initialize(CodeGenUtil.instantiateFacadeHelper(JMerger.DEFAULT_FACADE_HELPER_CLASS), config.getMergeXmlFullUri());
JMerger jMerger = new JMerger(jControlModel);
jMerger.setSourceCompilationUnit(jMerger.createCompilationUnitForContents(emitterResult));
jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForInputStream(targetFile.getContents(true)));
String oldContents = jControlModel.getFacadeHelper().getOriginalContents(jMerger.getTargetCompilationUnit());
jMerger.merge();
monitor.worked(1);
String result = jMerger.getTargetCompilationUnitContents();
if (oldContents.equals(result))
{
return result;
}
if (!targetFile.isReadOnly())
{
return result;
}
// The file may be read-only because it is checked out
// by a VCM component. Here we ask permission to change the file.
if (targetFile.getWorkspace().validateEdit(new IFile []{ targetFile }, BasicMonitor.subProgress(monitor, 1)).isOK())
{
jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForInputStream(targetFile.getContents(true)));
jMerger.remerge();
return jMerger.getTargetCompilationUnitContents();
}
return result;
}
/**
* Saves the specified contents to a location specified by the
* <code>Config</code> settings. The location of the file to save is found
* by finding or creating the container (folder) for the <code>Config</code>
* 's package in the <code>Config</code>'s target folder. The name of the
* file to save is the <code>Config</code>'s target file.
*
* @param monitor
* the progress monitor to use. May be <code>null</code>.
* @param contents
* the byte contents of the file to save
* @throws CoreException
*/
public IFile save(IProgressMonitor monitor, byte[] contents) throws CoreException
{
monitor = createIfNull(monitor);
Config config = getConfig();
IContainer container = findOrCreateContainer(monitor, config.getTargetFolder(), config.getPackageName());
if (container == null)
{
throw
new CoreException
(new Status
(IStatus.ERROR,
"org.eclipse.emf.examples.jet.article2",
0,
"Could not find or create container for package " + config.getPackageName() + " in " + config.getTargetFolder(),
null));
}
IFile targetFile = container.getFile(new Path(config.getTargetFile()));
IFile result = getWritableTargetFile(targetFile, container, config.getTargetFile());
InputStream newContents = new ByteArrayInputStream(contents);
if (result.exists())
{
result.setContents(newContents, true, true, BasicMonitor.subProgress(monitor, 1));
}
else
{
result.create(newContents, true, BasicMonitor.subProgress(monitor, 1));
}
return result;
}
/**
* Returns a non-null progress monitor.
*
* @param monitor
* an existing progress monitor
* @return a new <code>NullProgressMonitor</code> if the specified monitor
* is <code>null</code>, or the existing monitor otherwise
*/
private IProgressMonitor createIfNull(IProgressMonitor monitor)
{
if (monitor == null)
{
return new NullProgressMonitor();
}
return monitor;
}
private IContainer findOrCreateContainer(IProgressMonitor progressMonitor, String targetDirectory, String packageName) throws CoreException
{
IPath outputPath = new Path(targetDirectory + "/" + packageName.replace('.', '/'));
progressMonitor.beginTask("", 4);
IProgressMonitor sub = BasicMonitor.subProgress(progressMonitor, 1);
IPath localLocation = null; // use default
IContainer container = CodeGenUtil.EclipseUtil.findOrCreateContainer(outputPath, true, localLocation, sub);
return container;
}
/**
* Returns a <code>IFile</code> that can be written to. If the specified
* file is read-write, it is returned unchanged. If the specified file is
* read-only and {@link Config#isForceOverwrite()}returns <code>true</code>,
* the file is made writable, otherwise a new file is returned in the
* specified container with filename <code>"." + fileName + ".new"</code>.
*
* @param container
* container to create the new file in if the specified file cannot
* be made writable
* @param targetFile
* the file to make writable
* @param fileName
* used to create a new file name if the specified file cannot be
* made writable
* @return a <code>IFile</code> that can be written to
*/
private IFile getWritableTargetFile(IFile targetFile, IContainer container, String fileName)
{
if (targetFile.isReadOnly())
{
if (getConfig().isForceOverwrite())
{
ResourceAttributes attributes = targetFile.getResourceAttributes();
attributes.setReadOnly(false);
try
{
targetFile.setResourceAttributes(attributes);
}
catch (CoreException e)
{
// Ignore
}
}
else
{
targetFile = container.getFile(new Path("." + fileName + ".new"));
}
}
return targetFile;
}
/**
* Returns the <code>Config</code> object
*
* @return the <code>Config</code> object
*/
protected Config getConfig()
{
return mConfig;
}
}