/* * 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.logging.Logger; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.project.structure.modules.Dependency; import jetbrains.mps.project.structure.modules.ModuleDescriptor; import jetbrains.mps.smodel.CopyUtil; import jetbrains.mps.smodel.DynamicReference; import jetbrains.mps.smodel.Language; import jetbrains.mps.smodel.ModelImports; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.textgen.trace.TracingUtil; import jetbrains.mps.util.SNodeOperations; import org.apache.log4j.LogManager; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SReference; public class CloneUtil { private static final Logger LOG = Logger.wrap(LogManager.getLogger(CloneUtil.class)); private final SModel myInputModel; private final SModel myOutputModel; private final SModelReference myOutputModelRef; private boolean myTraceOriginalInput = false; private final Factory myFactory; public CloneUtil(SModel inputModel, SModel outputModel) { this(inputModel, outputModel, new RegularSModelFactory()); } public CloneUtil(SModel inputModel, SModel outputModel, Factory factory) { myInputModel = inputModel; myOutputModel = outputModel; myOutputModelRef = outputModel.getReference(); myFactory = factory; } /** * Record origin of copied node with TracingUtil * @return <code>this</code> for convenience */ public CloneUtil traceOriginalInput() { myTraceOriginalInput = true; return this; } /** * Creates cloned model, each node in target model has the same nodeId that corresponding node in source model * it allows to resolve internal references much faster */ public void cloneModelWithImports() { //copy model with imports, used languages and devkits cloneModel(); final ModelImports modelImports = new ModelImports(myOutputModel); modelImports.copyImportedModelsFrom(myInputModel); modelImports.copyEmployedDevKitsFrom(myInputModel); modelImports.copyUsedLanguagesFrom(myInputModel); } /** * The same as above, but also clones all module imports */ public void cloneModelWithAllImports() { cloneModelWithImports(); AbstractModule inputModule = (AbstractModule) myInputModel.getModule(); assert !(inputModule instanceof Language); ModuleDescriptor moduleDescriptor = inputModule.getModuleDescriptor(); AbstractModule outputModule = (AbstractModule) myOutputModel.getModule(); for (Dependency dependency : moduleDescriptor.getDependencies()) { outputModule.addDependency(dependency.getModuleRef(), dependency.isReexport()); } } public void cloneModel() { for (SNode node : myInputModel.getRootNodes()) { SNode outputNode = clone(node); myOutputModel.addRootNode(outputNode); } } // FIXME CopyUtil.copy() respects references within cloned sub-tree, but doesn't work with node factory. Shall combine both into single utility public SNode clone(SNode inputNode) { SNode outputNode = myFactory.create(inputNode); CopyUtil.copyProperties(inputNode, outputNode); CopyUtil.copyUserObjects(inputNode, outputNode); // keep track of 'original input node' if (myTraceOriginalInput) { TracingUtil.putInputNode(outputNode, inputNode); } for (SReference reference : inputNode.getReferences()) { boolean ext = inputNode.getModel() == null || !inputNode.getModel().getReference().equals(reference.getTargetSModelReference()); SModelReference targetModelReference = ext ? reference.getTargetSModelReference() : myOutputModelRef; SReference outRef = myFactory.create(reference, outputNode, targetModelReference); if (outRef != null) { outputNode.setReference(outRef.getLink(), outRef); } } for (SNode child : inputNode.getChildren()) { SContainmentLink role = child.getContainmentLink(); assert role != null; outputNode.addChild(role, clone(child)); } return outputNode; } public static DynamicReference create(SNode outputNode, SModelReference targetModelRef, DynamicReference prototype) { DynamicReference outputReference = new DynamicReference( prototype.getLink(), outputNode, targetModelRef, prototype.getResolveInfo()); outputReference.setOrigin(prototype.getOrigin()); return outputReference; } public interface Factory { SNode create(SNode prototype); SReference create(SReference prototype, SNode outputNode, SModelReference targetModelRef); } public static class RegularSModelFactory implements Factory { @Override public SNode create(SNode prototype) { return new jetbrains.mps.smodel.SNode(prototype.getConcept(), prototype.getNodeId()); } @Override public SReference create(SReference prototype, SNode outputNode, SModelReference targetModelRef) { // [model] clone mechanism in smodel.SReference or elsewhere not to perform instanceof // Besides, what if there's custom openapi.SReference impl (GenSReference) I'm not aware of? How am I supposed to clone it here? if (prototype instanceof StaticReference) { if (targetModelRef == null) { LOG.warning("broken reference '" + prototype.getLink().getName() + "' in " + SNodeOperations.getDebugText(prototype.getSourceNode()), prototype.getSourceNode()); } else { StaticReference outputReference = new StaticReference( prototype.getLink(), outputNode, targetModelRef, prototype.getTargetNodeId(), ((StaticReference) prototype).getResolveInfo()); return outputReference; } } else if (prototype instanceof DynamicReference) { return CloneUtil.create(outputNode, targetModelRef, (DynamicReference) prototype); } else { LOG.error("internal error: can't clone reference '" + prototype.getLink().getName() + "' in " + SNodeOperations.getDebugText(prototype.getSourceNode()), prototype.getSourceNode()); LOG.error(" -- was reference class : " + prototype.getClass().getName()); } return null; } } }