| 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; |
| } |
| } |