/* Copyright (c) 2008 Arno Haase, Andr� Arnold. 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: Arno Haase - initial API and implementation Andr� Arnold */ package org.eclipse.xtend.middleend.xpand; import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.internal.xpand2.XpandTokens; import org.eclipse.internal.xpand2.ast.Definition; import org.eclipse.internal.xpand2.ast.Statement; import org.eclipse.internal.xpand2.ast.Template; import org.eclipse.internal.xpand2.parser.XpandParseFacade; import org.eclipse.xpand2.XpandExecutionContext; import org.eclipse.xpand2.XpandExecutionContextImpl; import org.eclipse.xpand2.output.FileHandle; 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.backend.common.ExecutionContext; import org.eclipse.xtend.backend.common.ExpressionBase; import org.eclipse.xtend.backend.common.FunctionDefContext; import org.eclipse.xtend.backend.common.NamedFunction; import org.eclipse.xtend.backend.common.QualifiedName; import org.eclipse.xtend.backend.functions.FunctionDefContextInternal; import org.eclipse.xtend.backend.syslib.FileIoOperations; import org.eclipse.xtend.backend.syslib.FileOutlet; import org.eclipse.xtend.backend.syslib.InMemoryPostprocessor; import org.eclipse.xtend.backend.syslib.SysLibNames; import org.eclipse.xtend.backend.syslib.UriBasedPostprocessor; import org.eclipse.xtend.expression.Variable; import org.eclipse.xtend.middleend.LanguageContributor; import org.eclipse.xtend.middleend.MiddleEnd; import org.eclipse.xtend.middleend.MiddleEndFactory; import org.eclipse.xtend.middleend.xpand.internal.OldDefinitionConverter; import org.eclipse.xtend.middleend.xpand.internal.xpandlib.pr.XpandProtectedRegionResolver; import org.eclipse.xtend.middleend.xpand.plugin.OldXpandRegistryFactory; import org.eclipse.xtend.middleend.xpand.plugin.XpandDefinitionName; import org.eclipse.xtend.middleend.xtend.OldHelper; import org.eclipse.xtend.middleend.xtend.internal.TypeToBackendType; import org.eclipse.xtend.middleend.xtend.internal.xtendlib.XtendGlobalVarOperations; import org.eclipse.xtend.middleend.xtend.plugin.OldXtendRegistryFactory; import org.eclipse.xtend.typesystem.MetaModel; /** * * @author Arno Haase (http://www.haase-consulting.com) * @author Andr� Arnold */ public final class XpandBackendFacade { private final String _xpandFile; private final MiddleEnd _middleEnd; private final String _fileEncoding; private final Collection<MetaModel> _mms; private final Collection<Outlet> _outlets; private static Log log = LogFactory.getLog(XpandBackendFacade.class); /** * This method executes Xpand code that is passed in as a string, script language style.<br> * * There are two restrictions. Firstly, no DEFINEs are allowed - the string that is passed in must be a valid body for a DEFINE. Never * mind the "parameters" - the "variables" parameter defines all variables that will be defined during execution. Use "this" as a * variable name to specify the variable that is implicitly bound as the "special" parameter passed to a definition.<br> * * Secondly, no IMPORT or EXTENSION statements are possible. So types must be referenced by their fully qualified names, and no calls * to extensions are possible. Calls to other templates that are available as files are possible, just as you would expect.<br> * * Both the "variables" and "outlets" parameter may be null. */ public static Object executeStatement (String code, Collection<MetaModel> mms, Map<String, Object> variables, Collection <Outlet> outlets, XpandProtectedRegionResolver resolver, boolean isLogStackTrace) { return executeStatement (code, null, mms, variables, outlets, resolver, isLogStackTrace); } /** * This method executes Xpand code that is passed in as a string, script language style.<br> * * There are two restrictions. Firstly, no DEFINEs are allowed - the string that is passed in must be a valid body for a DEFINE. Never * mind the "parameters" - the "variables" parameter defines all variables that will be defined during execution. Use "this" as a * variable name to specify the variable that is implicitly bound as the "special" parameter passed to a definition.<br> * * Secondly, no IMPORT or EXTENSION statements are possible. So types must be referenced by their fully qualified names, and no calls * to extensions are possible. Calls to other templates that are available as files are possible, just as you would expect.<br> * * Both the "variables" and "outlets" parameter may be null. */ public static Object executeStatement (String code, String fileEncoding, Collection<MetaModel> mms, Map<String, Object> variables, Collection <Outlet> outlets, XpandProtectedRegionResolver resolver, boolean isLogStackTrace) { return executeStatement (code, fileEncoding, mms, variables, outlets, null, resolver, isLogStackTrace); } /** * This method executes Xpand code that is passed in as a string, script language style.<br> * * There are two restrictions. Firstly, no DEFINEs are allowed - the string that is passed in must be a valid body for a DEFINE. Never * mind the "parameters" - the "variables" parameter defines all variables that will be defined during execution. Use "this" as a * variable name to specify the variable that is implicitly bound as the "special" parameter passed to a definition.<br> * * Secondly, no IMPORT or EXTENSION statements are possible. So types must be referenced by their fully qualified names, and no calls * to extensions are possible. Calls to other templates that are available as files are possible, just as you would expect.<br> * * The "variables", "outlets" and "advice" parameter may be null. */ public static Object executeStatement (String code, String fileEncoding, Collection<MetaModel> mms, Map<String, Object> variables, Collection <Outlet> outlets, List<String> advice, XpandProtectedRegionResolver resolver, boolean isLogStackTrace) { return createForFile (null, fileEncoding, mms, outlets).executeStatement (code, variables, advice, resolver, isLogStackTrace); } public Object executeStatement (String code, Map<String, Object> variables, List<String> advice, XpandProtectedRegionResolver resolver, boolean isLogStackTrace) { return executeStatement(code, variables, new HashMap <String, Object>(), advice, resolver, isLogStackTrace); } public Object executeStatement (String code, Map<String, Object> variables, Map<String, Object> globalVars, List<String> advice, XpandProtectedRegionResolver resolver, boolean isLogStackTrace) { if (variables == null) variables = new HashMap<String, Object> (); if (advice == null) advice = new ArrayList<String> (); for (String adv: advice) _middleEnd.applyAdvice (adv); final Template tpl = XpandParseFacade.file (new StringReader (XpandTokens.LT + "DEFINE dUmMy FOR dUmMy" + XpandTokens.RT + code + XpandTokens.LT + "ENDDEFINE" + XpandTokens.RT), null); final Statement[] statements = ((Definition) tpl.getDefinitions()[0]).getBody(); XpandExecutionContext ctx = createXpandExecutionContext (_fileEncoding, _mms, _outlets); for (String varName: variables.keySet()) ctx = (XpandExecutionContext) ctx.cloneWithVariable (new Variable (varName, ctx.getType (variables.get (varName)))); final Set<XpandDefinitionName> referenced = new HashSet<XpandDefinitionName>(); final OldDefinitionConverter defConverter = new OldDefinitionConverter (ctx, new TypeToBackendType (_middleEnd.getTypesystem(), ctx)); final ExpressionBase converted = defConverter.convertStatementSequence (statements, tpl, referenced, null); final FunctionDefContextInternal fdc; if (_middleEnd.getExecutionContext() != null && _middleEnd.getExecutionContext().getFunctionDefContext() != null) fdc = (FunctionDefContextInternal)_middleEnd.getExecutionContext().getFunctionDefContext(); else fdc = _middleEnd.createEmptyFdc(); for (XpandDefinitionName xdn: referenced) for (NamedFunction f: _middleEnd.getFunctions (OldHelper.normalizeXpandResourceName(xdn.getCanonicalTemplateFileName ())).getPublicFunctions()) fdc.register (f, false); _middleEnd.getExecutionContext().setFunctionDefContext (fdc); _middleEnd.getExecutionContext().getLocalVarContext().getLocalVars().putAll (variables); registerOutlets (_middleEnd.getExecutionContext(), _outlets); registerProtectedRegionResolver(_middleEnd.getExecutionContext(), resolver); _middleEnd.getExecutionContext().getContributionStateContext().storeState(XtendGlobalVarOperations.GLOBAL_VAR_VALUES_KEY, globalVars); Object o=converted.evaluate (_middleEnd.getExecutionContext()); return o; } public static void registerOutlets (ExecutionContext ctx, Collection<Outlet> outlets) { for (Outlet oldOutlet: outlets) { final FileOutlet newOutlet = new FileOutlet (); newOutlet.setAppend (oldOutlet.isAppend()); newOutlet.setBaseDir (new File (oldOutlet.getPath())); if (oldOutlet.getFileEncoding() != null) newOutlet.setFileEncoding (oldOutlet.getFileEncoding()); newOutlet.setOverwrite (oldOutlet.isOverwrite()); for (PostProcessor pp: oldOutlet.postprocessors) { newOutlet.register (new InMemoryPpAdapter (pp, oldOutlet)); newOutlet.register (new UriBasedPpAdapter (pp, oldOutlet)); } final String outletName = (oldOutlet.getName() != null) ? oldOutlet.getName() : FileIoOperations.DEFAULT_OUTLET_NAME; ctx.getFunctionDefContext ().invoke (ctx, new QualifiedName (SysLibNames.REGISTER_OUTLET), Arrays.asList (outletName, newOutlet)); } } public static void registerProtectedRegionResolver (ExecutionContext ctx, XpandProtectedRegionResolver resolver) { if (resolver != null) ctx.getContributionStateContext ().storeState (XpandProtectedRegionResolver.XPAND_PROTECTED_REGION_RESOLVER, resolver); } private static class InMemoryPpAdapter implements InMemoryPostprocessor { private final PostProcessor _oldPp; private final Outlet _outlet; public InMemoryPpAdapter (PostProcessor oldPp, Outlet outlet) { _oldPp = oldPp; _outlet = outlet; } public CharSequence process (CharSequence unprocessed, String uri) { final FileHandle fh = new FileHandleImpl (unprocessed, _outlet, new File (uri)); _oldPp.beforeWriteAndClose (fh); return fh.getBuffer(); } } private static class FileHandleImpl implements FileHandle { private CharSequence _buffer; private final Outlet _outlet; private final File _file; public FileHandleImpl (CharSequence buffer, Outlet outlet, File file) { _buffer = buffer; _outlet = outlet; _file = file; } public CharSequence getBuffer () { return _buffer; } public String getFileEncoding () { return _outlet.getFileEncoding(); } public Outlet getOutlet () { return _outlet; } public File getTargetFile () { return _file; } public boolean isAppend () { return _outlet.isAppend(); } public boolean isOverwrite () { return _outlet.isOverwrite(); } public void setBuffer (CharSequence buffer) { _buffer = buffer; } public void writeAndClose () { throw new UnsupportedOperationException (); } public String getAbsolutePath() { return getTargetFile().getAbsolutePath(); } } private static class UriBasedPpAdapter implements UriBasedPostprocessor { private final PostProcessor _oldPp; private final Outlet _outlet; public UriBasedPpAdapter (PostProcessor oldPp, Outlet outlet) { _oldPp = oldPp; _outlet = outlet; } public void process (String uri) { final FileHandle fh = new FileHandleImpl ("", _outlet, new File (uri)); _oldPp.afterClose (fh); } } public static XpandBackendFacade createForFile (String xpandFilename, String fileEncoding, Collection<MetaModel> mms, Collection <Outlet> outlets) { return new XpandBackendFacade (xpandFilename, fileEncoding, mms, outlets); } private XpandExecutionContext createXpandExecutionContext (String fileEncoding, Collection<MetaModel> mms, Collection<Outlet> outlets) { fileEncoding = OldHelper.normalizedFileEncoding (fileEncoding); final Output output = new OutputImpl (); for (Outlet outlet: outlets) output.addOutlet (outlet); //TODO ProtectedRegionResolver final XpandExecutionContextImpl ctx = new XpandExecutionContextImpl (output, null); for (MetaModel mm: mms) ctx.registerMetaModel (mm); ctx.getResourceManager().setFileEncoding (fileEncoding); return ctx; } private Map<Class<?>, Object> createSpecificParameters (String fileEncoding, Collection<MetaModel> mms, Collection<Outlet> outlets) { final XpandExecutionContext ctx = createXpandExecutionContext (fileEncoding, mms, outlets); final Map<Class<?>, Object> result = new HashMap<Class<?>, Object> (); result.put (OldXtendRegistryFactory.class, ctx); result.put (OldXpandRegistryFactory.class, ctx); return result; } private XpandBackendFacade (String xpandFilename, String fileEncoding, Collection<MetaModel> mms, Collection <Outlet> outlets) { if (outlets == null) outlets = new ArrayList<Outlet> (); _xpandFile = OldHelper.normalizeXpandResourceName (xpandFilename); if (MiddleEndFactory.canCreateFromExtentions ()) { _middleEnd = MiddleEndFactory.createFromExtensions (OldHelper.guessTypesystem (mms), createSpecificParameters (fileEncoding, mms, outlets)); } else { _middleEnd = MiddleEndFactory.create (OldHelper.guessTypesystem (mms), LanguageContributor.INSTANCE.getFreshMiddleEnds (createSpecificParameters (fileEncoding, mms, outlets))); } _fileEncoding = fileEncoding; _mms = mms; _outlets = outlets; } public Collection<NamedFunction> getContributedFunctions () { return _middleEnd.getFunctions(_xpandFile).getPublicFunctions(); } public FunctionDefContext getFunctionDefContext () { return _middleEnd.getFunctions (_xpandFile); } }