/*******************************************************************************
* Copyright (c) 2005, 2009 committers of openArchitectureWare 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:
* committers of openArchitectureWare - initial API and implementation
*******************************************************************************/
package org.eclipse.xpand2;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.mwe.core.ConfigurationException;
import org.eclipse.emf.mwe.core.WorkflowContext;
import org.eclipse.emf.mwe.core.issues.Issues;
import org.eclipse.emf.mwe.core.monitor.ProgressMonitor;
import org.eclipse.internal.xpand2.XpandTokens;
import org.eclipse.internal.xpand2.ast.Definition;
import org.eclipse.internal.xpand2.ast.ExpandStatement;
import org.eclipse.internal.xpand2.ast.Template;
import org.eclipse.internal.xpand2.parser.XpandParseFacade;
import org.eclipse.internal.xpand2.pr.ProtectedRegionResolver;
import org.eclipse.internal.xpand2.pr.ProtectedRegionResolverImpl;
import org.eclipse.internal.xtend.util.ProfileCollector;
import org.eclipse.internal.xtend.xtend.parser.ParseException;
import org.eclipse.xpand2.output.Outlet;
import org.eclipse.xpand2.output.Output;
import org.eclipse.xpand2.output.OutputImpl;
import org.eclipse.xpand2.output.PostProcessor;
import org.eclipse.xtend.expression.AbstractExpressionsUsingWorkflowComponent;
import org.eclipse.xtend.expression.ResourceManager;
import org.eclipse.xtend.expression.Variable;
import org.eclipse.xtend.typesystem.MetaModel;
public class Generator extends AbstractExpressionsUsingWorkflowComponent {
private static final String COMPONENT_NAME = "Xpand Generator";
private String genPath = null;
private String srcPath = null;
private String prSrcPaths = null;
private String prExcludes = null;
private boolean prDefaultExcludes = true;
private String expand = null;
private String fileEncoding = System.getProperty("file.encoding");
private List<?> beautifier = new ArrayList<Object>();
private final List<String> advices = new ArrayList<String>();
private final List<String> extensionAdvices = new ArrayList<String>();
private boolean automaticHyphens = false;
/**
* @deprecated
*/
private String collectProfileSummary = null;
/**
* @deprecated For profiling use the Profiler Component instead
*/
private String verboseProfileFilename = null;
private Output output = null;
/**
* Sets the collection profile summary.
*
* @param summary
* the summary
* @deprecated
*/
public void setCollectProfileSummary(final String summary) {
collectProfileSummary = summary;
}
/**
* Sets the filename for verbose profile.
*
* @param fileName
* filename for verbose profile
*/
public void setVerboseProfileFilename(final String fileName) {
verboseProfileFilename = fileName;
}
/**
* Enables or disables the automatic hyphenation. If automatic hyphenation
* is enabled, redundant blank lines are avoided automatically.
*
* @param automaticHyphens
* If <code>true</code>, automatic hyphenation is enabled,
* otherwise disabled.
*/
public void setAutomaticHyphens(final boolean automaticHyphens) {
this.automaticHyphens = automaticHyphens;
}
/**
* @see org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent#getLogMessage()
*/
@Override
public String getLogMessage() {
final Set<String> outletDescriptions = new HashSet<String>();
for (final Outlet outlet : outlets) {
outletDescriptions.add(outlet.toString());
}
final String outletDesc = outletDescriptions.size() == 1 ? outletDescriptions.iterator().next()
: outletDescriptions.toString();
return "generating '" + expand + "' => " + outletDesc;
}
/**
* Adds an advice.
*
* @param advice
* the advice
*/
@Override
public void addAdvice(final String advice) {
if (!advices.contains(advice)) {
advices.add(advice);
}
}
/**
* Adds an extension advice.
*
* @param extensionAdvice
* the extension advice
*/
@Override
public void addExtensionAdvice(final String extensionAdvice) {
if (!extensionAdvices.contains(extensionAdvice)) {
extensionAdvices.add(extensionAdvice);
}
}
/**
* Returns the list of beautifiers that will be applied to the generated
* output.
*
* @return list of beautifiers
*/
public List<?> getBeautifier() {
return beautifier;
}
/**
* Sets the list of beautifiers that will be applied to the generated output.
*
* @param beautifiers
* list of beautifiers
*/
public void setBeautifier(final List<?> beautifiers) {
beautifier = beautifiers;
}
/**
* Sets the character encoding used for the output file.
*
* @param fileEncoding
* name of character encoding
*/
public void setFileEncoding(final String fileEncoding) {
this.fileEncoding = fileEncoding;
}
/**
* Returns the name of character encoding used for the output file.
*
* @return name of character encoding
*/
public String getFileEncoding() {
return fileEncoding;
}
/**
*
* @deprecated use outlets instead
*/
@Deprecated
public void setGenPath(final String genPath) {
this.genPath = fixPath(genPath);
}
/**
* Sets the statement that is to expand by the generator.
*
* @param invoke
* statement to expand
*/
public void setExpand(final String invoke) {
expand = invoke;
}
/**
* Enables oder disables the default excludes for protected regions.
*
* @param prDefaultExcludes
* If <code>true</code>, the default excludes are enabled,
* otherwise disabled.
*/
public void setPrDefaultExcludes(final boolean prDefaultExcludes) {
this.prDefaultExcludes = prDefaultExcludes;
}
/**
* Sets the additional excludes for protected regions.
*
* @param prExcludes
* the excludes
*/
public void setPrExcludes(final String prExcludes) {
this.prExcludes = prExcludes;
}
/**
* Sets the source paths for protected regions.
*
* @param prSrcPathes
* the source paths
*/
public void setPrSrcPaths(final String prSrcPathes) {
this.prSrcPaths = prSrcPathes;
}
/**
* @see org.eclipse.emf.mwe.core.lib.AbstractWorkflowComponent#getComponentName()
*/
@Override
public String getComponentName() {
return COMPONENT_NAME;
}
/**
*
* @deprecated use outlets instead
*/
@Deprecated
public void setSrcPath(final String srcPath) {
this.srcPath = fixPath(srcPath);
}
private String fixPath(final String p) {
if (p.endsWith("\\"))
return p.replace('\\', '/');
if (p.endsWith("/"))
return p;
return p + "/";
}
@Override
protected void invokeInternal2(final WorkflowContext ctx, final ProgressMonitor monitor, final Issues issues) {
OutputStream verboseProfileOutputStream = null;
if (verboseProfileFilename != null) {
try {
verboseProfileOutputStream = new BufferedOutputStream(new FileOutputStream(verboseProfileFilename));
ProfileCollector.getInstance().setDetailedLoggingWriter(verboseProfileOutputStream);
}
catch (final IOException exc) {
log.warn("Could not open profiling log file", exc);
}
}
final Output out = getOutput();
final List<Outlet> outlets = getInitializedOutlets();
for (final Outlet outlet : outlets) {
out.addOutlet(outlet);
}
ProtectedRegionResolver prs = getProtectedRegionResolver();
ResourceManager resourceManager = getResourceManager();
XpandExecutionContextImpl executionContext = new XpandExecutionContextImpl(
resourceManager,
out,
prs,
getGlobalVars(ctx),
monitor,
exceptionHandler,
getNullEvaluationHandler(),
callback);
for (final MetaModel mm : metaModels) {
executionContext.registerMetaModel(mm);
}
final ExpandStatement es = getStatement();
if (es == null)
throw new ConfigurationException("property 'expand' has wrong syntax!");
final String[] names = ctx.getSlotNames();
for (final String name : names) {
executionContext = (XpandExecutionContextImpl) executionContext.cloneWithVariable(new Variable(name, ctx
.get(name)));
}
for (final String advice : advices) {
final String[] allAdvices = advice.split(",");
for (final String string : allAdvices) {
executionContext.registerAdvices(string.trim());
}
}
for (final String advice : extensionAdvices) {
final String[] allAdvices = advice.split(",");
for (final String string : allAdvices) {
executionContext.registerExtensionAdvices(string.trim());
}
}
es.evaluate(executionContext);
for (final Outlet element : outlets) {
final String name = (element.getName() == null ? "[default]" : element.getName()) + "(" + element.getPath()
+ ")";
if (element.getFilesWrittenAndClosed() > 0) {
log.info("Written " + element.getFilesWrittenAndClosed() + " files to outlet " + name);
}
if (element.getFilesCreated() > element.getFilesWrittenAndClosed()) {
log.info("Skipped writing of " + (element.getFilesCreated() - element.getFilesWrittenAndClosed())
+ " files to outlet " + name);
}
}
ProfileCollector.getInstance().finish();
if ("true".equalsIgnoreCase(this.collectProfileSummary)) {
log.info("profiling info: \n" + ProfileCollector.getInstance().toString());
}
if (verboseProfileOutputStream != null) {
try {
verboseProfileOutputStream.close();
}
catch (final IOException exc) {
log.warn("problem closing profile log file", exc);
}
}
}
protected ProtectedRegionResolver getProtectedRegionResolver () {
ProtectedRegionResolverImpl prs = null;
if (prSrcPaths != null) {
prs = new ProtectedRegionResolverImpl();
prs.setDefaultExcludes(prDefaultExcludes);
prs.setIgnoreList(prExcludes);
prs.setSrcPathes(prSrcPaths);
prs.setFileEncoding(fileEncoding);
}
return prs;
}
private final List<Outlet> outlets = new ArrayList<Outlet>(2);
/**
* Adds an outlet.
*
* @param outlet
* the outlet
*/
public void addOutlet(final Outlet outlet) {
outlets.add(outlet);
}
/**
* Sets the output.
*
* @param output
* the output
*/
public void setOutput(final Output output) {
this.output = output;
}
private Output getOutput() {
if (output == null) {
// lazy initialization
final OutputImpl out = new OutputImpl();
out.setAutomaticHyphens(automaticHyphens);
this.output = out;
}
return output;
}
private List<Outlet> initializedOutlets = null;
private List<Outlet> getInitializedOutlets() {
if (initializedOutlets == null) {
final List<Outlet> result = new ArrayList<Outlet>(outlets);
if (result.isEmpty()) {
if (genPath != null) { // backward compatibility
Outlet o = new Outlet();
o.setAppend(false);
o.setFileEncoding(fileEncoding);
o.setOverwrite(true);
o.setPath(genPath);
result.add(o);
o = new Outlet();
o.setAppend(true);
o.setFileEncoding(fileEncoding);
o.setName("APPEND");
o.setOverwrite(true);
o.setPath(genPath);
result.add(o);
}
if (srcPath != null) {
final Outlet o = new Outlet();
o.setAppend(false);
o.setFileEncoding(fileEncoding);
o.setName("ONCE");
o.setOverwrite(false);
o.setPath(srcPath);
result.add(o);
}
}
for (final Outlet o : result) {
if (o.postprocessors.isEmpty()) {
for (final Object name : beautifier) {
final PostProcessor element = (PostProcessor) name;
o.addPostprocessor(element);
}
}
// Initialize file encoding for outlets. If it is not set then
// take the Generator
// default encoding. If this not set also then take System
// default.
if (o.getFileEncoding() == null) {
o.setFileEncoding(fileEncoding);
}
}
initializedOutlets = result;
}
return initializedOutlets;
}
/**
* Retrieves the configured and initialized outlets of the generator.
*
* @since 4.2
*/
public final List<Outlet> getOutlets() {
return Collections.unmodifiableList(getInitializedOutlets());
}
private ExpandStatement getStatement() {
final Template tpl = XpandParseFacade.file(new StringReader(XpandTokens.LT + "DEFINE test FOR test"
+ XpandTokens.RT + XpandTokens.LT + "EXPAND " + expand + XpandTokens.RT + XpandTokens.LT + "ENDDEFINE"
+ XpandTokens.RT), null);
ExpandStatement es = null;
try {
es = (ExpandStatement) ((Definition) tpl.getDefinitions()[0]).getBody()[1];
}
catch (final Exception e) {
log.error(e);
}
return es;
}
@Override
protected void checkConfigurationInternal(final Issues issues) {
super.checkConfigurationInternal(issues);
if (genPath == null && getInitializedOutlets().isEmpty()) {
issues.addError(this, "You need to configure at least one outlet!");
}
if ((genPath != null || srcPath != null) && !outlets.isEmpty()) {
issues.addWarning(this, "'genPath' and 'srcPath' properties are ignored since you have specified outlets!");
}
int defaultOutlets = 0;
for (final Outlet o : getInitializedOutlets()) {
if (o.getName() == null) {
defaultOutlets++;
}
}
if (defaultOutlets > 1) {
issues.addError(this,
"Only one outlet can be the default outlet. Please specifiy a name for the other outlets!");
}
else if (defaultOutlets == 0) {
issues.addWarning(this, "No default outlet configured!");
}
if (expand == null) {
issues.addError(this, "property 'expand' not configured!");
}
else {
try {
final ExpandStatement es = getStatement();
if (es == null) {
issues.addError(this, "property 'expand' has wrong syntax!");
}
}
catch (final ParseException e) {
issues.addError(this, "property 'expand' has wrong syntax : " + e.getMessage());
}
}
}
}