/** * Copyright (c) 2006-2007 IBM Corporation and others. * 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: * IBM - Initial API and implementation */ package org.eclipse.emf.codegen.ecore.generator; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; 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.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.formatter.CodeFormatter; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.TextEdit; import org.eclipse.emf.codegen.ecore.CodeGenEcorePlugin; import org.eclipse.emf.codegen.jet.JETCompiler; 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.merge.properties.PropertyMerger; import org.eclipse.emf.codegen.util.CodeGenUtil; import org.eclipse.emf.codegen.util.GIFEmitter; import org.eclipse.emf.codegen.util.ImportManager; import org.eclipse.emf.common.EMFPlugin; import org.eclipse.emf.common.notify.impl.SingletonAdapterImpl; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.BasicMonitor; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.Monitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.WrappedException; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.resource.impl.ContentHandlerImpl; import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl; import org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl; /** * A base <code>GeneratorAdapter</code> implementation. This base provides support for * {@link org.eclipse.emf.codegen.jet.JETEmitter JET-based} generation of Java and other text artifacts, as well as * {@link org.eclipse.emf.codegen.util.GIFEmitter GIFEmitter-based} colourization of icons, via the following methods: * <ul> * <li>{@link #generateJava(String, String, String, JETEmitter, Object[], Monitor)} for generating Java code, with * {@link org.eclipse.emf.codegen.util.ImportManager import management}, * {@link org.eclipse.emf.codegen.merge.java.JMerger merging}, and * {@link org.eclipse.jdt.core.formatter.CodeFormatter code formatting} capabilities. * <li>{@link #generateProperties(String, JETEmitter, Object[], Monitor)} for generating property files, with * {@link org.eclipse.emf.codegen.merge.properties.PropertyMerger merging} capability. * <li>{@link #generateText(String, JETEmitter, Object[], boolean, String, Monitor)} for generating other text * artifacts. * <li>{@link #generateGIF(String, GIFEmitter, String, String, boolean, Monitor) generateGIF()} for generating icons by * colourizing grey-scale images. * </ul> * * <p>Code generation is supported in the Eclipse IDE or stand-alone. However, merging and formatting of Java code are * not available in the latter scenario. * * <p>At a minimum, subclasses must implement {@link #canGenerate(Object, Object)}, to determine whether code can be * generated for an object, and {@link #doGenerate(Object, Object, Monitor) doGenerate()}, to actually generate code * for it. * * <p>They may also override {@link #getCanGenerateChildren(Object, Object)}, * {@link #getCanGenerateParent(Object, Object)}, {@link #getGenerateChildren(Object, Object)}, and * {@link #getGenerateParent(Object, Object)}, to involve more objects these operations, as well as * {@link #doPreGenerate(Object, Object)} and {@link #doPostGenerate(Object, Object)}, to perform setup and cleanup * before and after code generation. * * <p>This class is designed to support singleton generator adapters, where a single adapter instance can be attached * to multiple objects of the same type. State relevant to a single object is only cached during a call to * {@link #preGenerate(Object, Object)}, {@link #generate(Object, Object, Monitor)}, or * {@link #postGenerate(Object, Object)}. * * @since 2.2.0 */ public abstract class AbstractGeneratorAdapter extends SingletonAdapterImpl implements GeneratorAdapter { protected final static String MANIFEST_ENCODING = "UTF-8"; protected final static String PROPERTIES_ENCODING = "ISO-8859-1"; protected GeneratorAdapterFactory adapterFactory; /** * The object for which this adapter is currently being used to generate code. This will only be set during * {@link #preGenerate(Object, Object)}, {@link #generate(Object, Object, Monitor)}, and * {@link #postGenerate(Object, Object)}. */ protected Object generatingObject; /** * The message describing what is being done. This can be set during {@link #doGenerate(Object, Object, Monitor)} * and will be used in the diagnostic message if an exception is caught. */ protected String message; /** * All the <code>JETEmitter</code>s used by this adapter. This are cached so that they can be reused at least for * different objects with the same adapter. When {@link Generator.Options#dynamicTemplates dynamic templateS} are * not being used, they can actually be reused for multiple code generation invocations. */ protected JETEmitter[] jetEmitters; /** * All the <code>GIFEmitter</code>s used by this adapter. This are cached so that they can be reused at least for * different objects with the same adapter. When {@link Generator.Options#dynamicTemplates dynamic templateS} are * not being used, they can actually be reused for multiple code generation invocations. */ protected GIFEmitter[] gifEmitters; /** * The <code>ImportManager</code> currently being used in generating Java code. This should only be created and * cleared via {@link #createImportManager(String, String)} and {@link #clearImportManager()}, so that subclasses may * respond to such changes. */ protected ImportManager importManager; /** * The line delimiter currently being used in generating textual results. This should only be set * via {@link #setLineDelimiter(String)} so that subclasses may respond to such changes. * @since 2.3 */ protected String lineDelimiter; /** * An appropriate <code>URIConverter</code> for use during code generation. This is usually applicable to the whole * set of objects for which code is being generated, so it can be cached long-term. */ protected URIConverter uriConverter; /** * If this default constructor is used, the {@link #setAdapterFactory(GeneratorAdapterFactory) setAdapterFactory()} * method must be called immediately to set the generator adapter factory that created this adapter. */ public AbstractGeneratorAdapter() { super(); } public AbstractGeneratorAdapter(GeneratorAdapterFactory adapterFactory) { this.adapterFactory = adapterFactory; } public GeneratorAdapterFactory getAdapterFactory() { return adapterFactory; } public void setAdapterFactory(GeneratorAdapterFactory adapterFactory) { this.adapterFactory = adapterFactory; } /** * Returns <code>true</code> when the type is this adapter's {@link #getAdapterFactory() factory}. * This allows generator adapters from different factories to be attached to the same objects. */ @Override public boolean isAdapterForType(Object type) { return type == adapterFactory; } /** * Returns an empty collection, indicating that by default no children are involved in determining whether code can be * generated for an object. */ public Collection<?> getCanGenerateChildren(Object object, Object projectType) { return Collections.EMPTY_LIST; } /** * Returns null, indicating that by default no parent is involved in determining whether code can be generated for an * object. */ public Object getCanGenerateParent(Object object, Object projectType) { return null; } public abstract boolean canGenerate(Object object, Object projectType); /** * Returns an empty collection, indicating that by default there are no children of an object for which code should * be generated. */ public Collection<?> getGenerateChildren(Object object, Object projectType) { return Collections.EMPTY_LIST; } /** * Returns null, indicating that by default there is no parent of an object for which could should be generated. */ public Object getGenerateParent(Object object, Object projectType) { return null; } /** * Caches the object as {@link #generatingObject}, calls {@link #doPreGenerate(Object, Object)}, and clears it again. * If {@link Generator.Options#dynamicTemplates dynamic templates} are enabled on the generator, any cached * {@link #jetEmitters JETEmitter}s and {@link #gifEmitters GIFEmitter}s are also removed, so that templates * will be recompiled during {@link #generate(Object, Object, Monitor)}. */ public final Diagnostic preGenerate(Object object, Object projectType) { try { generatingObject = object; if (getGenerator().getOptions().dynamicTemplates) { jetEmitters = null; gifEmitters = null; } return doPreGenerate(object, projectType); } finally { generatingObject = null; } } /** * Does nothing and returns {@link org.eclipse.emf.common.util.Diagnostic#OK_INSTANCE OK}. Override this to perform * setup for code generation. */ protected Diagnostic doPreGenerate(Object object, Object projectType) { return Diagnostic.OK_INSTANCE; } /** * If code can be generated for the object, as determined by {@link #canGenerate(Object, Object)}, delegates code * generation to {@link #doGenerate(Object, Object, Monitor)}. Otherwise, simply returns * {@link Diagnostic#OK_INSTANCE OK}. The object is cached as {@link #generatingObject} and the {@link #message} is * cleared before calling {@link #doGenerate(Object, Object, Monitor)}; both are cleared again afterwards. * * @see #canGenerate(Object, Object) * @see #doGenerate(Object, Object, Monitor) */ public final Diagnostic generate(Object object, Object projectType, Monitor monitor) { try { if (canGenerate(object, projectType)) { generatingObject = object; message = null; return doGenerate(object, projectType, monitor); } return Diagnostic.OK_INSTANCE; } catch (Exception exception) { return toDiagnostic(exception, message); } finally { generatingObject = null; message = null; monitor.done(); } } /** * Implement this to perform code generation of the given project type for the specified object. Use the monitor * to update progress for this long-running operation. Any exceptions thrown will be converted into a diagnostic * and returned by {@link #generate(Object, Object, Monitor) generate()}. If a {@link #message} is set, it will * be used in this diagnostic. */ protected abstract Diagnostic doGenerate(Object object, Object projectType, Monitor monitor) throws Exception; /** * Caches the object as {@link #generatingObject}, calls {@link #doPostGenerate(Object, Object)}, and clears it again. * If {@link Generator.Options#dynamicTemplates dynamic templates} are enabled on the generator, any cached * {@link #jetEmitters JETEmitter} and {@link #gifEmitters GIFEmitters} are also removed, so that templates will be * recompiled during the next {@link #generate(Object, Object, Monitor)}. */ public final Diagnostic postGenerate(Object object, Object projectType) { try { generatingObject = object; if (getGenerator().getOptions().dynamicTemplates) { jetEmitters = null; gifEmitters = null; } return doPostGenerate(object, projectType); } finally { generatingObject = null; } } /** * Does nothing and returns {@link org.eclipse.emf.common.util.Diagnostic#OK_INSTANCE OK}. Override this to perform * cleanup from code generation. */ protected Diagnostic doPostGenerate(Object object, Object projectType) { return Diagnostic.OK_INSTANCE; } /** * Converts the given exception to a <code>Diagnostic</code>. The <code>currentMessage</code>, if non-null, should * describe what was being done when the exception occurred, and will be used in forming the diagnostic's message. */ protected Diagnostic toDiagnostic(Exception exception, String currentMessage) { CodeGenEcorePlugin.INSTANCE.log(exception); currentMessage = currentMessage != null ? CodeGenEcorePlugin.INSTANCE.getString("_UI_GenerateException_diagnostic", new Object[] { currentMessage }) : CodeGenEcorePlugin.INSTANCE.getString("_UI_GenericGenerateException_diagnostic"); BasicDiagnostic diagnostic = new BasicDiagnostic(CodeGenEcorePlugin.ID, 0, currentMessage, null); //DMS this doesn't really produce nice output. // diagnostic.add(BasicDiagnostic.toDiagnostic(exception)); return diagnostic; } /** * Returns the generator for this adapter's factory. */ protected Generator getGenerator() { return getAdapterFactory().getGenerator(); } /** * The information required to construct and initialize a {@link org.eclipse.emf.codegen.jet.JETEmitter JETEmitter}, * namely the file name of the template (relative to the template path) and the qualified name of the template class. */ protected static class JETEmitterDescriptor { public String templatePathName; public String className; public JETEmitterDescriptor(String templatePathName, String className) { this.templatePathName = templatePathName; this.className = className; } } /** * Returns the <code>JETEmitter</code> for the <code>JETEmitterDescriptor</code> at the index specified by * <code>id</code> in the given array. If the <code>JETEmitter</code> has not yet been created, it will be created, * initialized, and cached at the same index in {@link #jetEmitters}. * * @param jetEmitterDescriptors an array of descriptors for all of the <code>JETEmitter</code>s used by this * generator adapter. * @param id the identifier for the desired <code>JETEmitter</code>, also the index of the descriptor in the array. */ protected JETEmitter getJETEmitter(JETEmitterDescriptor[] jetEmitterDescriptors, int id) { if (jetEmitters == null) { jetEmitters = new JETEmitter[jetEmitterDescriptors.length]; } JETEmitter jetEmitter = jetEmitters[id]; if (jetEmitter == null) { jetEmitter = createJETEmitter(jetEmitterDescriptors[id]); jetEmitters[id] = jetEmitter; } return jetEmitter; } /** * Creates and initializes a <code>JETEmitter</code> according to the given descriptor. */ protected JETEmitter createJETEmitter(JETEmitterDescriptor jetEmitterDescriptor) { JETEmitter jetEmitter = new JETEmitter(getTemplatePath(), jetEmitterDescriptor.templatePathName, getClass().getClassLoader()); try { setStaticTemplateClass(jetEmitter, jetEmitterDescriptor.className); addClasspathEntries(jetEmitter); } catch (JETException exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } return jetEmitter; } /* * Returns the dynamic template path, an ordered list of URIs corresponding to locations under which to find * templates. */ private String[] getTemplatePath() { // Consult the generator option for backwards compatibility. // @SuppressWarnings("deprecation") String[] legacyPath = getGenerator().getOptions().templatePath; if (legacyPath != null) { return legacyPath; } List<String> result = new ArrayList<String>(getUserTemplatePath()); result.addAll(getBaseTemplatePath()); return result.toArray(new String[result.size()]); } /** * Returns the user-specified portion of the dynamic template path, an ordered list of URIs corresponding to locations * under which to find templates. This implementation returns an empty list. * * <p>This method is only consulted if the generator's {@link Generator.Options#templatePath templatePath} option is * set to null. * * @see Generator.Options#templatePath * @see org.eclipse.emf.codegen.jet.JETEmitter#JETEmitter(String[], String) * @see org.eclipse.emf.codegen.jet.JETCompiler#find(String[], String) * @since org.eclipse.emf.codegen.ecore 2.2.2 */ protected List<String> getUserTemplatePath() { return Collections.emptyList(); } /* * Returns the base portion of the dynamic template path. */ private List<String> getBaseTemplatePath() { List<String> result = new ArrayList<String>(); addBaseTemplatePathEntries(result); return result; } /** * Adds template locations to the base portion of the dynamic template path, an ordered list of URIs corresponding to * locations under which to find templates. Order matters, so the pattern is to add local entries first, and then * invoke the superclass implementation. This implementation does nothing. * * <p>This method is only consulted if the generator's {@link Generator.Options#templatePath templatePath} option is * set to null. * * @see Generator.Options#templatePath * @see org.eclipse.emf.codegen.jet.JETEmitter#JETEmitter(String[], String) * @see org.eclipse.emf.codegen.jet.JETCompiler#find(String[], String) * @since org.eclipse.emf.codegen.ecore 2.2.2 */ protected void addBaseTemplatePathEntries(List<String> templatePath) { // Subclasses may override } protected static final Class<?>[] OBJECT_ARGUMENT = new Class[] { Object.class }; /** * If {@link Generator.Options#dynamicTemplates dynamic templates} are not being used, * attempts to set the emitter to use an existing, precompiled template class * that has a method with signature <code>generate(Object)</code>. * @see #setStaticTemplateClass(JETEmitter, String, String, Class[]) */ protected void setStaticTemplateClass(JETEmitter jetEmitter, String className) { setStaticTemplateClass(jetEmitter, className, "generate", OBJECT_ARGUMENT); } /** * If {@link Generator.Options#dynamicTemplates dynamic templates} are not being used, * attempts to set the emitter to use an existing, precompiled template class * that has the given method name and argument types. * @since 2.5 */ protected void setStaticTemplateClass(JETEmitter jetEmitter, String className, String methodName, Class<?>[] arguments) { if (!getGenerator().getOptions().dynamicTemplates) { try { Class<?> templateClass = getClass().getClassLoader().loadClass(className); Method emitterMethod = templateClass.getDeclaredMethod(methodName, arguments); jetEmitter.setMethod(emitterMethod); } catch (Exception exception) { // It's okay for there not be a precompiled template, so fail quietly. } } } /** * Override this to {@link org.eclipse.emf.codegen.jet.JETEmitter#addVariable(String, String) add classpath variables} * to the JETEmitter. These will be used to build and execute dynamic templates. * * @see org.eclipse.emf.codegen.jet.JETEmitter#addVariable(String, String) */ protected void addClasspathEntries(JETEmitter jetEmitter) throws JETException { if (getGenerator().getOptions().templateClasspath!= null) { for (String additionalClasspathEntry : getGenerator().getOptions().templateClasspath) { int index = additionalClasspathEntry.indexOf('='); if (index == -1) { jetEmitter.addVariable(additionalClasspathEntry, additionalClasspathEntry); } else { jetEmitter.addVariable(additionalClasspathEntry.substring(0, index), additionalClasspathEntry.substring(index + 1)); } } } } /** * Returns the <code>GIFEmitter</code> for the input path name at the index specified by <code>id</code> in the given * array. If the <code>GIFEmitter</code> has not yet been created, it will be created and cached at the same index in * {@link #gifEmitters}. * * @param inputPathNames an array of input path names for all the <code>GIFEmitter</code>s used by this generator * adapter. These are the file names, relative to the template path, of grey-scale images to be colourized. * @param id the identifier for the desired <code>GIFEmitter</code>, also the index of the input path name in the * array. */ protected GIFEmitter getGIFEmitter(String[] inputPathNames, int id) { if (gifEmitters == null) { gifEmitters = new GIFEmitter[inputPathNames.length]; } GIFEmitter gifEmitter = gifEmitters[id]; if (gifEmitter == null) { gifEmitter = createGIFEmitter(inputPathNames[id]); gifEmitters[id] = gifEmitter; } return gifEmitter; } /** * Creates a <code>GIFEmitter</code> based on the image at the give template-path-relative file name. */ protected GIFEmitter createGIFEmitter(String inputPathName) { return new GIFEmitter(JETCompiler.find(getTemplatePath(), inputPathName)); } /** * Generates an arbitrary text artifact using JET. * * @param targetPathName the path name of the target file. This should be a workspace path; when running stand-alone, * it will be converted to a platform resource URI that should be mapped to a physical file URI by the * {@link #getURIConverter() URIConverter}. * @param jetEmitter the <code>JETEmitter</code> to use for generating the text. * @param arguments the argument array to pass to the <code>JETEmitter</code>'s * {@link JETEmitter#generate(Monitor, Object[]) generate(Monitor, Object[])} method. If null, an array will * be constructed containing only the {@link #generatingObject object} for which code is being generated. * @param overwrite whether an existing file should be overwritten. * @param encoding an override of the default encoding. If "ISO-8859-1" is specified, * {@link org.eclipse.emf.codegen.util.CodeGenUtil#unicodeEscapeEncode(String) Unicode escape encoding} * is performed to represent non-Latin characters. The default encoding, when running under Eclipse, is * determined from the workspace. Failing that, or in stand-alone, the platform default is used. * @param monitor the <code>Monitor</code> through which to report progress. * * <p>This method also consults the following {@link Generator#getOptions() generator options}: * <ul> * <li>{@link Generator.Options#redirectionPattern redirectionPattern} * <li>{@link Generator.Options#forceOverwrite forceOverwrite} * <li>{@link Generator.Options#dynamicTemplates dynamicTemplates} * <li>{@link Generator.Options#resourceSet resourceSet} * </ul> */ protected void generateText (String targetPathName, JETEmitter jetEmitter, Object[] arguments, boolean overwrite, String encoding, Monitor monitor) { try { monitor.beginTask("", 3); URI targetFile = toURI(targetPathName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_GeneratingFile_message", new Object[] { targetFile })); URI targetDirectory = targetFile.trimSegments(1); ensureContainerExists(targetDirectory, createMonitor(monitor, 1)); boolean exists = exists(targetFile); if (!exists || overwrite) { if (arguments == null) { arguments = new Object[] { generatingObject }; } setLineDelimiter(getLineDelimiter(targetFile, encoding)); String emitterResult = jetEmitter.generate(createMonitor(monitor, 1), arguments, getLineDelimiter()); if (PROPERTIES_ENCODING.equals(encoding)) { emitterResult = CodeGenUtil.unicodeEscapeEncode(emitterResult); } if (encoding == null) { encoding = getEncoding(targetFile); } boolean changed = true; if (exists) { monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_ExaminingOld_message", new Object[] { targetFile })); String oldContents = getContents(targetFile, encoding); changed = !emitterResult.equals(oldContents); } if (changed) { byte[] bytes = encoding == null ? emitterResult.toString().getBytes() : emitterResult.toString().getBytes(encoding); // Apply a redirection pattern, if specified. // String redirection = getGenerator().getOptions().redirectionPattern; boolean redirect = redirection != null && redirection.indexOf("{0}") != -1; if (redirect) { String baseName = MessageFormat.format(redirection, new Object[] { targetFile.lastSegment() }); targetFile = targetDirectory.appendSegment(baseName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingAlternate_message", new Object[] { targetFile })); } if (isReadOnly(targetFile)) { if (getGenerator().getOptions().forceOverwrite) { // If the target is read-only, we can ask the platform to release it. // validateEdit(targetFile, createMonitor(monitor, 1)); setWriteable(targetFile); } else { targetFile = targetDirectory.appendSegment("." + targetFile.lastSegment() + ".new"); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingDefaultAlternate_message", new Object[] { targetFile })); } } OutputStream outputStream = createOutputStream(targetFile); outputStream.write(bytes); outputStream.close(); } } } catch (Exception exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } finally { setLineDelimiter(null); monitor.done(); } } /** * Generates a properties file using JET, with {@link org.eclipse.emf.codegen.merge.properties.PropertyMerger merging} * capability. * * <p>The encoding used for the generated file is "ISO-8859-1". * {@link org.eclipse.emf.codegen.util.CodeGenUtil#unicodeEscapeEncode(String) Unicode escape encoding} is * performed to represent non-Latin characters. * * @param targetPathName the path name of the target file. This should be a workspace path; when running stand-alone, * it will be converted to a platform resource URI that should be mapped to a physical file URI by the * {@link #getURIConverter() URIConverter}. * @param jetEmitter the <code>JETEmitter</code> to use for generating the text. * @param arguments the argument array to pass to the <code>JETEmitter</code>'s * {@link JETEmitter#generate(Monitor, Object[]) generate(Monitor, Object[])} method. If null, an array will * be constructed containing only the {@link #generatingObject object} for which code is being generated. * @param monitor the <code>Monitor</code> through which to report progress. * * <p>This method also consults the following {@link Generator#getOptions() generator options}: * <ul> * <li>{@link Generator.Options#redirectionPattern redirectionPattern} * <li>{@link Generator.Options#forceOverwrite forceOverwrite} * <li>{@link Generator.Options#dynamicTemplates dynamicTemplates} * <li>{@link Generator.Options#resourceSet resourceSet} * </ul> */ protected void generateProperties(String targetPathName, JETEmitter jetEmitter, Object[] arguments, Monitor monitor) { try { monitor.beginTask("", 3); URI targetFile = toURI(targetPathName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_GeneratingFile_message", new Object[] { targetFile })); URI targetDirectory = targetFile.trimSegments(1); ensureContainerExists(targetDirectory, createMonitor(monitor, 1)); boolean changed = false; if (arguments == null) { arguments = new Object[] { generatingObject }; } setLineDelimiter(getLineDelimiter(targetFile, PROPERTIES_ENCODING)); String emitterResult = CodeGenUtil.unicodeEscapeEncode(jetEmitter.generate(createMonitor(monitor, 1), arguments, getLineDelimiter())); byte[] bytes = emitterResult.toString().getBytes(PROPERTIES_ENCODING); if (exists(targetFile)) { // Merge with an existing file. // PropertyMerger propertyMerger = new PropertyMerger(); propertyMerger.setSourceProperties(emitterResult); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_ExaminingOld_message", new Object[] { targetFile })); String oldProperties = propertyMerger.createPropertiesForInputStream(createInputStream(targetFile)); propertyMerger.setTargetProperties(oldProperties); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_PreparingNew_message", new Object[] { targetFile })); propertyMerger.merge(); String mergedResult = propertyMerger.getTargetProperties(); changed = !mergedResult.equals(oldProperties); if (changed) { // If the target is read-only, we can ask the platform to release it, and it may be updated in the process. // if (isReadOnly(targetFile) && validateEdit(targetFile, createMonitor(monitor, 1))) { propertyMerger.setTargetProperties(propertyMerger.createPropertiesForInputStream(createInputStream(targetFile))); propertyMerger.merge(); mergedResult = propertyMerger.getTargetProperties(); } bytes = mergedResult.getBytes(PROPERTIES_ENCODING); } } else { changed = true; monitor.worked(1); } if (changed) { // Apply a redirection pattern, if specified. // String redirection = getGenerator().getOptions().redirectionPattern; boolean redirect = redirection != null && redirection.indexOf("{0}") != -1; if (redirect) { String baseName = MessageFormat.format(redirection, new Object[] { targetFile.lastSegment() }); targetFile = targetDirectory.appendSegment(baseName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingAlternate_message", new Object[] { targetFile })); } if (isReadOnly(targetFile)) { if (getGenerator().getOptions().forceOverwrite) { setWriteable(targetFile); } else { targetFile = targetDirectory.appendSegment("." + targetFile.lastSegment() + ".new"); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingDefaultAlternate_message", new Object[] { targetFile })); } } OutputStream outputStream = createOutputStream(targetFile); outputStream.write(bytes); outputStream.close(); } } catch (Exception exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } finally { setLineDelimiter(null); monitor.done(); } } /** * Generates an icon using a {@link org.eclipse.emf.codegen.util.GIFEmitter GIFEmitter} to colourize a grey-scale GIF * image. The colours to use are calculated from one or, optionally, two text keys. * * @param targetPathName the path name of the target file. This should be a workspace path; when running stand-alone, * it will be converted to a platform resource URI that should be mapped to a physical file URI by the * {@link #getURIConverter() URIConverter}. * @param gifEmitter the <code>GIFEmitter</code> to use for generating the icon. * @param parentKey the key used to determine the first colour set. * @param childKey the key used to determine the second colour set. If null, this key is ignored. * @param overwrite whether an existing file should be overwritten. * @param monitor the <code>Monitor</code> through which to report progress. * * <p>This method also consults the following {@link Generator#getOptions() generator options}: * <ul> * <li>{@link Generator.Options#redirectionPattern redirectionPattern} * <li>{@link Generator.Options#forceOverwrite forceOverwrite} * <li>{@link Generator.Options#resourceSet resourceSet} * </ul> */ protected void generateGIF (String targetPathName, GIFEmitter gifEmitter, String parentKey, String childKey, boolean overwrite, Monitor monitor) { try { monitor.beginTask("", 3); URI targetFile = toURI(targetPathName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_GeneratingImage_message", new Object[] { targetFile })); URI targetDirectory = targetFile.trimSegments(1); ensureContainerExists(targetDirectory, createMonitor(monitor, 1)); boolean exists = exists(targetFile); if (!exists || overwrite) { byte[] emitterResult = gifEmitter.generateGIF(parentKey, childKey); monitor.worked(1); // Apply a redirection pattern, if specified. // String redirection = getGenerator().getOptions().redirectionPattern; boolean redirect = redirection != null && redirection.indexOf("{0}") != -1; if (redirect) { String baseName = MessageFormat.format(redirection, new Object[] { targetFile.lastSegment() }); targetFile = targetDirectory.appendSegment(baseName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingAlternate_message", new Object[] { targetFile })); } if (isReadOnly(targetFile)) { if (getGenerator().getOptions().forceOverwrite) { // If the target is read-only, we can ask the platform to release it. // validateEdit(targetFile, createMonitor(monitor, 1)); setWriteable(targetFile); } else { targetFile = targetDirectory.appendSegment("." + targetFile.lastSegment() + ".new"); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingDefaultAlternate_message", new Object[] { targetFile })); } } OutputStream outputStream = createOutputStream(targetFile); outputStream.write(emitterResult); outputStream.close(); } } catch (Exception exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } finally { monitor.done(); } } /** * Generates a Java source file using JET, with {@link org.eclipse.emf.codegen.util.ImportManager import management} * and, when running under Eclipse, {@link org.eclipse.emf.codegen.merge.java.JMerger merging} and * {@link org.eclipse.jdt.core.formatter.CodeFormatter code formatting} capabilities. * * <p>When running under Eclipse, the encoding for the file is determined from the workspace. Failing that, or in * stand-alone, the platform default is used. * * @param targetPath the workspace path of the directory in or under which the file will be created, depending on the * specified package name. When running stand-alone, this path will be converted to a platform resource URI * that should be mapped to a physical file URI by the {@link #getURIConverter() URIConverter}. * @param packageName the package name for the generated compilation unit. * @param className the name of the public class in the generated compilation unit. * @param jetEmitter the <code>JETEmitter</code> to use for generating the code. * @param arguments the argument array to pass to the <code>JETEmitter</code>'s * {@link JETEmitter#generate(Monitor, Object[]) generate(Monitor, Object[])} method. If null, an array will * be constructed containing only the {@link #generatingObject object} for which code is being generated. * @param monitor the <code>Monitor</code> through which to report progress. * * <p>This method also consults the following {@link Generator#getOptions() generator options}: * <ul> * * <li>{@link Generator.Options#redirectionPattern redirectionPattern} * <li>{@link Generator.Options#forceOverwrite forceOverwrite} * <li>{@link Generator.Options#dynamicTemplates dynamicTemplates} * <li>{@link Generator.Options#mergerFacadeHelperClass mergerFacadeHelperClass} * <li>{@link Generator.Options#mergeRulesURI mergeRulesURI} * <li>{@link Generator.Options#codeFormatting codeFormatting} * <li>{@link Generator.Options#codeFormatterOptions codeFormatterOptions} * <li>{@link Generator.Options#resourceSet resourceSet} * </ul> */ protected void generateJava (String targetPath, String packageName, String className, JETEmitter jetEmitter, Object[] arguments, Monitor monitor) { try { monitor.beginTask("", 4); URI targetDirectory = toURI(targetPath).appendSegments(packageName.split("\\.")); URI targetFile = targetDirectory.appendSegment(className + ".java"); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_Generating_message", new Object[] { targetFile })); ensureContainerExists(targetDirectory, createMonitor(monitor, 1)); if (arguments == null) { arguments = new Object[] { generatingObject }; } createImportManager(packageName, className); setLineDelimiter(getLineDelimiter(targetFile, getEncoding(targetFile))); String emitterResult = jetEmitter.generate(createMonitor(monitor, 1), arguments, getLineDelimiter()); boolean changed = true; String newContents = emitterResult; boolean targetExists = exists(targetFile); JControlModel jControlModel = getGenerator().getJControlModel(); boolean mergeSuccessful = jControlModel.canMerge(); //DMS This is not right. It replaced "if (EMFPlugin.IS_ECLIPSE_RUNNING)" but can also be false if an invalid facade has been specified. if (mergeSuccessful) { JMerger jMerger = new JMerger(jControlModel); jMerger.setFixInterfaceBrace(jControlModel.getFacadeHelper().fixInterfaceBrace()); try { jMerger.setSourceCompilationUnit(jMerger.createCompilationUnitForContents(emitterResult)); } catch(RuntimeException runtimeException) { if (targetExists) { throw runtimeException; } else { mergeSuccessful = false; } } if (mergeSuccessful) { // Create a code formatter for this compilation unit, if needed. // Object codeFormatter = getGenerator().getOptions().codeFormatting ? createCodeFormatter(getGenerator().getOptions().codeFormatterOptions, targetFile) : null; if (targetExists) { monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_ExaminingOld_message", new Object[] { targetFile })); jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForInputStream(createInputStream(targetFile), getEncoding(targetFile))); String oldContents = jMerger.getTargetCompilationUnitContents(); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_PreparingNew_message", new Object[] { targetFile })); jMerger.merge(); newContents = formatCode(jMerger.getTargetCompilationUnitContents(), codeFormatter, getGenerator().getOptions().commentFormatting); changed = !oldContents.equals(newContents); // If the target is read-only, we can ask the platform to release it, and it may be updated in the process. // if (changed && isReadOnly(targetFile) && validateEdit(targetFile, createMonitor(monitor, 1))) { jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForInputStream(createInputStream(targetFile), getEncoding(targetFile))); jMerger.remerge(); newContents = formatCode(jMerger.getTargetCompilationUnitContents(), codeFormatter, getGenerator().getOptions().commentFormatting); } } else { changed = true; monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_PreparingNew_message", new Object[] { targetFile })); jMerger.merge(); newContents = formatCode(jMerger.getTargetCompilationUnitContents(), codeFormatter, getGenerator().getOptions().commentFormatting); } if (jControlModel.getFacadeHelper() != null) { jControlModel.getFacadeHelper().reset(); } } } if (!mergeSuccessful) { //DMS What if Eclipse is running, but an invalid facade has been specified? We still should format code, use encoding,... newContents = CodeGenUtil.convertFormat(jControlModel.getLeadingTabReplacement(), jControlModel.convertToStandardBraceStyle(), emitterResult); if (targetExists) { monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_ExaminingOld_message", new Object[] { targetFile })); String oldContents = getContents(targetFile, null); changed = !oldContents.equals(newContents); } else { changed = true; } } monitor.worked(1); if (changed) { String encoding = getEncoding(targetFile); byte[] bytes = encoding == null ? newContents.getBytes() : newContents.getBytes(encoding); // Apply a redirection pattern, if specified. // String redirection = getGenerator().getOptions().redirectionPattern; boolean redirect = redirection != null && redirection.indexOf("{0}") != -1; if (redirect) { String baseName = MessageFormat.format(redirection, new Object[] { className + ".java" }); targetFile = targetDirectory.appendSegment(baseName); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingAlternate_message", new Object[] { targetFile })); } if (isReadOnly(targetFile)) { if (getGenerator().getOptions().forceOverwrite) { setWriteable(targetFile); } else { targetFile = targetDirectory.appendSegment("." + className + ".java.new"); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_UsingDefaultAlternate_message", new Object[] { targetFile })); } } OutputStream outputStream = createOutputStream(targetFile); outputStream.write(bytes); outputStream.close(); } } catch (Exception e) { //DMS Do a better job with specific exceptions? Just use chained RuntimeExceptions? throw e instanceof RuntimeException ? (RuntimeException)e : new WrappedException(e); } // catch (JETException exception) // { // CodeGenEcorePlugin.INSTANCE.log(exception); // } // catch (Exception exception) // { // CodeGenEcorePlugin.INSTANCE.log(exception); // } finally { clearImportManager(); setLineDelimiter(null); monitor.done(); } } /** * Converts the given workspace path to a <code>URI</code>. No encoding is performed, so the URI may contain invalid * characters. Such a URI is only used to easily access and manipulate parts of the workspace path. It can then be * converted back to a string and an {@link org.eclipse.core.runtime.IPath IPath} for use in the workspace, or to an * encoded {@link #toPlatformResourceURI(URI) platform resource URI} for direct use with the EMF persistence * framework. */ protected URI toURI(String pathName) { return URI.createURI(pathName); } /** * Converts the given workspace path URI to an absolute, platform resource URI, with encoding to eliminate any * invalid characters. */ protected URI toPlatformResourceURI(URI uri) { return URI.createPlatformResourceURI(uri.toString(), true); } /** * Creates and returns a sub-monitor for the given progress monitor. When running stand-alone, the same monitor is * actually returned. * * @param monitor the parent monitor * @param ticks the number of work ticks allocated from the parent monitor */ protected Monitor createMonitor(Monitor monitor, int ticks) { return CodeGenUtil.createMonitor(monitor, ticks); } /** * Creates and caches an {@link org.eclipse.emf.codegen.util.ImportManager ImportManager} for use in generating Java * code. */ protected void createImportManager(String packageName, String className) { importManager = new ImportManager(packageName); importManager.addMasterImport(packageName, className); } /** * Clears the cached {@link org.eclipse.emf.codegen.util.ImportManager ImportManager}. */ protected void clearImportManager() { importManager = null; } /** * Returns the {@link org.eclipse.emf.codegen.util.ImportManager ImportManager} that is currently in use for * generating Java code, or null if there is none. */ protected ImportManager getImportManager() { return importManager; } /** * Sets the current line delimiter used for generating textual results. * @param lineDelimiter * @since 2.3 */ protected void setLineDelimiter(String lineDelimiter) { this.lineDelimiter = lineDelimiter; } /** * Returns the current line delimiter used for generating textual results. * @since 2.3 */ protected String getLineDelimiter() { return lineDelimiter; } /** * Ensures that a project, corresponding to the first segment in the specified workspace path, exists. If the project * does not exist, a default project will be created. If it does exist and <code>force</code> is true, it will be * reconfigured to match the default configuration. The remainder of the path suggests the folder under which source * will be generated. * * <p>When running stand-alone, this method does nothing, since simply opening a stream via a <code>URIConverter</code> * will automatically create the necessary directories. */ protected void ensureProjectExists(String workspacePath, Object object, Object projectType, boolean force, Monitor monitor) { try { if (EMFPlugin.IS_ECLIPSE_RUNNING) { EclipseHelper.ensureProjectExists(workspacePath, object, projectType, force, monitor); } } finally { monitor.done(); } } /** * Ensures that a container corresponding to the specified relative URI exists. The URI represents a workspace * path for which the project must already exist, since this method doesn't have the necessary information to * set up a project. This method will create nested folders within the project, if possible. * * <p>When running stand-alone, this method does nothing, since simply opening a stream via a <code>URIConverter</code> * will automatically create the necessary directories. */ protected void ensureContainerExists(URI workspacePath, Monitor monitor) { try { if (EMFPlugin.IS_ECLIPSE_RUNNING) { EclipseHelper.ensureContainerExists(workspacePath.toString(), monitor); } } finally { monitor.done(); } } /** * Returns an appropriate <code>URIConverter</code> for use during code generation. */ protected URIConverter getURIConverter() { ResourceSet resourceSet = getGenerator().getOptions().resourceSet; URIConverter result = resourceSet != null ? resourceSet.getURIConverter() : null; if (result != null) { return result; } if (uriConverter == null) { uriConverter = new ExtensibleURIConverterImpl(); } return uriConverter; } /** * @since 2.3 */ public String getLineDelimiter(URI workspacePath, String encoding) { InputStream inputStream = null; try { inputStream = createInputStream(workspacePath); String lineDelimiter = ContentHandlerImpl.getLineDelimiter(inputStream, encoding); if (lineDelimiter != null) { return lineDelimiter; } } catch (Exception exception) { // If we can't determine it by reading the file, // look at the preferences instead. } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } } } if (EMFPlugin.IS_ECLIPSE_RUNNING) { return PlatformResourceURIHandlerImpl.WorkbenchHelper.getLineDelimiter(workspacePath.toString(), null); } return System.getProperty(Platform.PREF_LINE_SEPARATOR); } /** * Determines whether a given workspace path URI represents a file that already exists. */ protected boolean exists(URI workspacePath) { return getURIConverter().exists(toPlatformResourceURI(workspacePath), null); } /** * Determines whether a given workspace path URI represents a read-only file. */ protected boolean isReadOnly(URI workspacePath) { if (EMFPlugin.IS_ECLIPSE_RUNNING) { return EclipseHelper.isReadOnly(workspacePath.toString()); } URI uri = getURIConverter().normalize(toPlatformResourceURI(workspacePath)); if ("file".equalsIgnoreCase(uri.scheme())) { File file = new File(uri.toFileString()); return file.exists() && !file.canWrite(); } else { return false; } } /** * Sets the file represented by a workspace path URI to be writable. When running stand-alone, this actually * <em>deletes</em> the file, since there is no Java platform API for making a file writable. */ protected void setWriteable(URI workspacePath) throws Exception { if (EMFPlugin.IS_ECLIPSE_RUNNING) { EclipseHelper.setWriteable(workspacePath.toString()); return; } URI uri = getURIConverter().normalize(toPlatformResourceURI(workspacePath)); if ("file".equalsIgnoreCase(uri.scheme())) { new File(uri.toFileString()).delete(); } } /** * When running under Eclipse, performs an * {@link org.eclipse.core.resources.IWorkspace#validateEdit(IFile[], Object) IWorkspace.validateEdit(IFile[], Object)} * for the file identified by the given workspace path URI. This notifies the workspace that the file will be edited, * providing it the opportunity to prepare the files if required. When running stand-alone, does nothing. */ protected boolean validateEdit(URI workspacePath, Monitor monitor) { try { if (EMFPlugin.IS_ECLIPSE_RUNNING) { return EclipseHelper.validateEdit(workspacePath.toString(), monitor); } return false; } finally { monitor.done(); } } /** * Creates an <code>InputStream</code> for the file identified by the given workspace path URI. */ protected InputStream createInputStream(URI workspacePath) throws Exception { if (EMFPlugin.IS_ECLIPSE_RUNNING) { return EclipseHelper.createInputStream(workspacePath.toString()); } return getURIConverter().createInputStream(toPlatformResourceURI(workspacePath), null); } /** * Creates an <code>OutputStream</code> for the file identified by the given workspace path URI. */ protected OutputStream createOutputStream(URI workspacePath) throws Exception { return getURIConverter().createOutputStream(toPlatformResourceURI(workspacePath), null); } /** * Returns the contents of the file identified by the given workspace path URI, as read using the specified encoding. */ protected String getContents(URI workspacePath, String encoding) throws Exception { BufferedInputStream bufferedInputStream = new BufferedInputStream(createInputStream(workspacePath)); byte[] input = new byte[bufferedInputStream.available()]; bufferedInputStream.read(input); bufferedInputStream.close(); return encoding == null ? new String(input) : new String(input, encoding); } /** * When running under Eclipse, queries the workspace to determine the correct encoding for the file identified by * the given workspace path URI. When running stand-alone, returns null. */ protected String getEncoding(URI workspacePath) { if (EMFPlugin.IS_ECLIPSE_RUNNING) { return EclipseHelper.getEncoding(workspacePath.toString()); } return null; } /** * When running under Eclipse, returns a code formatter; when stand-alone, returns null. If <code>options</code> is * non-null, the code formatting options it specifies are used to create the formatter. Otherwise, the project is * obtained from the given workspace path URI, and its default formatting options are used. * * @return the created code formatter. If non-null, this will be an instance of * {@link org.eclipse.jdt.core.formatter.CodeFormatter CodeFormatter}; however, it is not statically typed * as such to avoid failure when running stand-alone. */ protected Object createCodeFormatter(Map<?, ?> options, URI workspacePath) { if (EMFPlugin.IS_ECLIPSE_RUNNING) { return EclipseHelper.createCodeFormatter(options, workspacePath.toString()); } return null; } /** * If non-null, use the specified code formatter to format the given compilation unit contents. * Clients overriding this method should change to overrides {@link #formatCode(String, Object, boolean)} instead. * * @return the formatted version of the contents. If the code formatter is null or when running stand-alone, the * contents are returned unchanged. * @deprecated */ @Deprecated protected String formatCode(String contents, Object codeFormatter) { return EMFPlugin.IS_ECLIPSE_RUNNING ? EclipseHelper.formatCode(contents, codeFormatter, getLineDelimiter(), false) : contents; } /** * If non-null, use the specified code formatter to format the given compilation unit contents. * * @return the formatted version of the contents. If the code formatter is null or when running stand-alone, the * contents are returned unchanged. * @since 2.8 */ protected String formatCode(String contents, Object codeFormatter, boolean formatComments) { return formatComments && EMFPlugin.IS_ECLIPSE_RUNNING ? EclipseHelper.formatCode(contents, codeFormatter, getLineDelimiter(), formatComments) : formatCode(contents, codeFormatter); } /* * All Eclipse-dependent operations are delegated to this class. This pattern avoids any runtime failure due to * missing dependencies in the stand-alone case. */ private static class EclipseHelper { //DMS this is totally untested. public static boolean ensureProjectExists(String workspacePath, Object object, Object projectType, boolean force, Monitor monitor) { try { IPath path = new Path(workspacePath); if (path.isAbsolute()) { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IProject project = workspace.getRoot().getProject(path.segment(0)); if (!project.exists() || force) { IPath javaSource = path.uptoSegment(1).append("src"); org.eclipse.emf.codegen.ecore.Generator.createEMFProject (javaSource, null, Collections.<IProject>emptyList(), monitor, org.eclipse.emf.codegen.ecore.Generator.EMF_PLUGIN_PROJECT_STYLE); } return workspace.getRoot().getProject(path.segment(0)).exists(); } } catch (Exception exception) { //DMS should we let this exception out? CodeGenEcorePlugin.INSTANCE.log(exception); } return false; } public static boolean ensureContainerExists(String workspacePath, Monitor monitor) { IPath path = new Path(workspacePath); IContainer container = null; try { monitor.beginTask("", path.segmentCount() + 1); monitor.subTask(CodeGenEcorePlugin.INSTANCE.getString("_UI_OpeningFolder_message", new Object[] { path })); if (path.isAbsolute()) { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IProject project = workspace.getRoot().getProject(path.segment(0)); if (project.exists()) { if (!project.isOpen()) { project.open(BasicMonitor.toIProgressMonitor(CodeGenUtil.createMonitor(monitor, 1))); } else { monitor.worked(1); } container = project; for (int i = 1, length = path.segmentCount(); i < length; i++) { IFolder folder = container.getFolder(new Path(path.segment(i))); if (!folder.exists()) { folder.create(false, true, BasicMonitor.toIProgressMonitor(CodeGenUtil.createMonitor(monitor, 1))); } container = folder; } } } } catch (Exception exception) { //DMS should we let this exception out? CodeGenEcorePlugin.INSTANCE.log(exception); } finally { monitor.done(); } return container != null && container.getFullPath().equals(path); } public static boolean isReadOnly(String workspacePath) { return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(workspacePath)).isReadOnly(); } public static void setWriteable(String workspacePath) throws Exception { IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(workspacePath)); ResourceAttributes resourceAttributes = file.getResourceAttributes(); if (resourceAttributes != null) { resourceAttributes.setReadOnly(false); file.setResourceAttributes(resourceAttributes); } } public static boolean validateEdit(String workspacePath, Monitor monitor) { IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(workspacePath)); return file.getWorkspace().validateEdit(new IFile [] { file }, IWorkspace.VALIDATE_PROMPT).isOK(); } public static InputStream createInputStream(String workspacePath) throws Exception { return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(workspacePath)).getContents(true); } public static String getEncoding(String workspacePath) { try { return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(workspacePath)).getCharset(); } catch (CoreException exception) { return null; } } public static Object createCodeFormatter(Map<?, ?> options, String workspacePath) { if (options == null) { IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(new Path(workspacePath).segment(0)); if (project != null) { IJavaProject javaProject = JavaCore.create(project); if (javaProject != null) { options = javaProject.getOptions(true); } } } return ToolFactory.createCodeFormatter(options); } public static String formatCode(String contents, Object codeFormatter, String lineDelimiter, boolean formatComments) { if (codeFormatter instanceof CodeFormatter) { IDocument doc = new Document(contents); TextEdit edit = ((CodeFormatter)codeFormatter).format(CodeFormatter.K_COMPILATION_UNIT | (formatComments ? CodeFormatter.F_INCLUDE_COMMENTS : 0), doc.get(), 0, doc.get().length(), 0, lineDelimiter); if (edit != null) { try { edit.apply(doc); contents = doc.get(); } catch (Exception exception) { CodeGenEcorePlugin.INSTANCE.log(exception); } } } return contents; } } }