/* * Copyright 2003-2017 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.reference; import jetbrains.mps.generator.impl.GenerationFailureException; import jetbrains.mps.generator.impl.GeneratorUtil; import jetbrains.mps.smodel.DynamicReference.DynamicReferenceOrigin; import jetbrains.mps.util.SNodeOperations; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.model.SReference; /** * @author Artem Tikhomirov * @since 2017.2 */ public abstract class ReferenceInfo_MacroBase extends ReferenceInfo { protected ReferenceInfo_MacroBase() { } @Nullable @Override public final SReference create(@NotNull PostponedReference ref) { try { Object result = expandReferenceMacro(ref); if (result instanceof SNode) { final SNode outputTargetNode = checkOutputNode(ref, (SNode) result); return createStaticReference(ref, outputTargetNode); } else if (result instanceof String) { final String resolveInfoForDynamicResolve = (String) result; final SReference dr = createDynamicReference(ref, resolveInfoForDynamicResolve, new DynamicReferenceOrigin(getMacroNodeRef(), null)); ref.getGenerator().registerDynamicReference(dr); return dr; } else if (result instanceof SNodeReference) { SNodeReference refTarget = (SNodeReference) result; return jetbrains.mps.smodel.SReference.create(ref.getLink(), ref.getSourceNode(), refTarget.getModelReference(), refTarget.getNodeId()); } if (!ref.getLink().isOptional()) { return createInvalidReference(ref, getInvalidReferenceResolveInfo()); } return null; // why not always invalid reference? Is there a convention that RM with null value means "forget it"? } catch (GenerationFailureException ex) { // when there's an error, it's better to see broken reference than no reference at all. // XXX shall I log the error? It's likely already logged in the query.evaluate(). Perhaps, shall not log it there but rather propagate // up and log it here? return createInvalidReference(ref, getInvalidReferenceResolveInfo()); // perhaps, "failure" to indicate there's an error? } } @Nullable protected abstract Object expandReferenceMacro(PostponedReference ref) throws GenerationFailureException; @Nullable protected abstract SNodeReference getMacroNodeRef(); @Nullable protected abstract String getInvalidReferenceResolveInfo(); private SNode checkOutputNode(PostponedReference ref, SNode outputTargetNode) { // check referent because it's manual and thus error prone mapping if (outputTargetNode.getModel() == ref.getGenerator().getInputModel()) { // There are RM that return input node from getReferent (e.g. in closures). The code below handles these cases, although I'm not // quite confident it's a nice idea in the first place (getReferent shall not return input nodes, imo) // try to find copy in output model and replace target SNode outputTargetNode_output = ref.getGenerator().findCopiedOutputNodeForInputNode(outputTargetNode); if (outputTargetNode_output != null) { return outputTargetNode_output; } else { // FIXME showErrorIfStrict final String msg = "reference macro returned node from input model; role: %s in %s"; ref.getGenerator().getLogger().warning(getMacroNodeRef(), String.format(msg, ref.getLink(), SNodeOperations.getDebugText(ref.getSourceNode())), GeneratorUtil.describe(ref.getSourceNode(), "source node"), GeneratorUtil.describeIfExists(outputTargetNode, "target node in input model")); } } return outputTargetNode; } }