/* * Copyright 2003-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.generator.impl; import jetbrains.mps.classloading.ClassLoaderManager; import jetbrains.mps.generator.GenerationSessionContext; import jetbrains.mps.generator.ModelExports; import jetbrains.mps.generator.crossmodel.ExportLabelContext; import jetbrains.mps.generator.runtime.TemplateContext; import jetbrains.mps.smodel.SModelStereotype; import jetbrains.mps.smodel.SModelUtil_new; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Counterpart to {@link jetbrains.mps.generator.impl.GenerationSession} to handle exports * during generation of a given model. * * There's one ExportsSessionContext per model generation/GenerationSession. * To facilitate access from multiple generation threads, some instance methods are synchronized on <code>this</code> * * @author Artem Tikhomirov */ public class ExportsSessionContext { private final ExportsVault myExportsVault; private final GenerationSessionContext myContext; private SModel myExportsModel; public ExportsSessionContext(@NotNull ExportsVault exportsVault, @NotNull GenerationSessionContext context) { myExportsVault = exportsVault; myContext = context; } /** * @return holder for exports of the current generation session's model, or <code>null</code> if there are no exports */ @Nullable public ModelExports getExports() { if (myExportsModel == null || !myExportsModel.getRootNodes().iterator().hasNext()) { return null; } return new ModelExports(myExportsModel); } // ExportEntry shall keep: // SNodeReference to export label (at least now expect ExportLabel to be accessible) // SNode of Keeper object (now through myValue.getKeeper()) // SModelReference / model name for the proxy model // identity of input node (concept and node id) to match // identity of output node (concept, to instantiate, node id for proxy instance) public void record(TemplateContext templateContext, SNode/*node<ExportLabel>*/ exportLabel, List<SNode> values) { if (values.isEmpty()) { return; } String functionName = CrossModelUtil.getMarshalFunctionName(exportLabel); final SNode keeperConcept = exportLabel.getReferenceTarget("dataHolder"); // XXX here we imply templateContext.inputNode is from the model being processed (though technically it's feasible to have inputNode // from another model). We need original (not transient) model for streamManager to find proper location assert templateContext.getEnvironment().getGenerator().getInputModel() == templateContext.getInput().getModel(); // ExportsSessionContext synchronized (this) { if (myExportsModel == null) { // when the model is being generated, overwrite any existing exports file - there gonna be new values. myExportsModel = myExportsVault.newExportsModel(myContext.getOriginalInputModel()); } for (SNode v : values) { SNode keeper = SModelUtil_new.instantiateConceptDeclaration(keeperConcept, null, true); ExportLabelContext ctx = new ExportLabelContextImpl(templateContext.getInput(), v, keeper); invokeExportFunction(exportLabel.getModel(), functionName, ctx); SModel outputModel = v.getModel() != null ? v.getModel() : templateContext.getEnvironment().getOutputModel(); final SNode/*node<ExportEntry>*/ exportEntry = new CrossModelUtil().newEntry(ctx, exportLabel, myExportsModel, outputModel); // FIXME likely CrossModelUtil would get some of these arguments right into constructor. // Just unsure at the moment which one, gonna decide once there are more uses. myExportsModel.addRootNode(exportEntry); } } } // FIXME copied from j.m.u.QueryMethodGenerated private static void invokeExportFunction(SModel m, String funcName, ExportLabelContext ctx) { String packageName = m.getName().getLongName(); String className = packageName + ".ExportLabelFunctions"; try { Class elfClass = ClassLoaderManager.getInstance().getClass(m.getModule(), className); if (elfClass == null) { throw new IllegalStateException(String.format("Can't find %s class for %s model", className, m.getModelName())); } final Method method = elfClass.getMethod(funcName, ExportLabelContext.class); method.invoke(null, ctx); } catch (NoSuchMethodException ex) { throw new IllegalStateException(String.format("Can't find method %s in class %s", funcName, className), ex); } catch (InvocationTargetException ex) { throw new IllegalStateException(String.format("Failed to marshal/unmarshal export label. Method %s in class %s", funcName, className), ex); } catch (IllegalAccessException ex) { throw new IllegalStateException(String.format("Failed to marshal/unmarshal export label. Method %s in class %s", funcName, className), ex); } } // FIXME input at EXPOSE is from transient model K, while input at query time is from (transient) model L. How do we match them? // Perhaps, there should be match function in addition to marshal/unmarshal public Collection<SNode> find(@NotNull String exportLabelName, @NotNull SModel processedModel, @NotNull SNode inputNode) { // inputNode either comes from the model we generate from, and is transient, or some external model (either regular or proxy, for multi-step exports). // Use original input model in former case, and expect outer mode to be regular model (so that stream manager knows where to find exports model) // For hanging nodes, we could either say 'no', or resort to myContext.getOriginalInputModel, but for the time being, I don't see value in the latter. if (inputNode.getModel() == null) { return Collections.emptyList(); } final SModel originModel = originModel(inputNode, processedModel); SModel exportsModel = myExportsVault.getExportsModel(originModel); if (exportsModel == null) { return Collections.emptyList(); } final CrossModelUtil cmu = new CrossModelUtil(); final List<SNode>/*nlist<ExportEntry>*/ entries = cmu.find(exportsModel, exportLabelName, inputNode); final ArrayList<SNode> rv = new ArrayList<SNode>(entries.size()); for (SNode exportEntry : entries) { final SModel proxyModel = cmu.newProxyModel(myContext.getModule(), exportEntry); final SNode proxyNode = cmu.newProxyNode(exportEntry, proxyModel); identifyAsProxy(proxyNode, originModel); proxyModel.addRootNode(proxyNode); populateOutputProxy(exportEntry, inputNode, proxyNode); rv.add(proxyNode); } return rv; } /** * Fill instantiated proxy with values saved */ private void populateOutputProxy(SNode/*node<ExportEntry*/ exportEntry, SNode input, SNode outputProxy) { final SNode/*node<ExportLabel>*/ exportLabel = exportEntry.getReferenceTarget("label"); String functionName = CrossModelUtil.getUnmarshalFunctionNameFromEntry(exportEntry); ExportLabelContext ctx = new ExportLabelContextImpl(input, outputProxy, CrossModelUtil.dataKept(exportEntry)); invokeExportFunction(exportLabel.getModel(), functionName, ctx); } private static final String PROXY_KEY = "#proxy"; private static final String PROXY_ORIGIN_MODEL_KEY = "#model"; /** * Fill proxy with implementation-specific data that helps to identify and use it. */ private void identifyAsProxy(@NotNull SNode outputProxy, @NotNull SModel originModel) { outputProxy.putUserObject(PROXY_KEY, Boolean.TRUE); outputProxy.putUserObject(PROXY_ORIGIN_MODEL_KEY, originModel); } /** * Discover model origin to find exports for. */ private SModel originModel(@NotNull SNode inputNode, @NotNull SModel processedModel) { if (inputNode.getModel() == processedModel) { return myContext.getOriginalInputModel(); } if (isProxyNode(inputNode)) { return (SModel) inputNode.getUserObject(PROXY_ORIGIN_MODEL_KEY); } return inputNode.getModel(); } private static boolean isProxyNode(@NotNull SNode inputNode) { return Boolean.TRUE.equals(inputNode.getUserObject(PROXY_KEY)); } private static class ExportLabelContextImpl implements ExportLabelContext { private final SNode myInput; private final SNode myOutput; private final SNode myKeeper; public ExportLabelContextImpl(SNode input, SNode output, SNode keeper) { myInput = input; myOutput = output; myKeeper = keeper; } @Override public SNode getInput() { return myInput; } @Override public SNode getOutput() { return myOutput; } @Override public SNode getKeeper() { return myKeeper; } } }