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.core.runtime.SubProgressMonitor; 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 }, new SubProgressMonitor(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, new SubProgressMonitor(monitor, 1)); } else { result.create(newContents, true, new SubProgressMonitor(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 = new SubProgressMonitor(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; } }