/* * Copyright 2003-2014 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.smodel; import jetbrains.mps.RuntimeFlags; import jetbrains.mps.extapi.model.ModelWithDisposeInfo; import jetbrains.mps.logging.Logger; import jetbrains.mps.smodel.references.UnregisteredNodes; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SReferenceLink; 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.SNodeId; import org.jetbrains.mps.openapi.module.SModuleReference; //final used by find usages public final class StaticReference extends SReferenceBase { private SNodeId myTargetNodeId; // mature /** * create 'young' reference */ public StaticReference(@NotNull SReferenceLink role, @NotNull SNode sourceNode, @NotNull SNode immatureTargetNode) { super(role, sourceNode, null, immatureTargetNode); } /** * create 'mature' reference */ public StaticReference(@NotNull SReferenceLink role, @NotNull SNode sourceNode, @Nullable SModelReference targetModelReference, @Nullable SNodeId nodeId, @Nullable String resolveInfo) { // 'targetModelReference' can be null only if it is broken external reference super(role, sourceNode, targetModelReference, null); setResolveInfo(resolveInfo); myTargetNodeId = nodeId; } /** * create 'young' reference */ @Deprecated public StaticReference(@NotNull String role, @NotNull SNode sourceNode, @NotNull SNode immatureTargetNode) { super(role, sourceNode, null, immatureTargetNode); } /** * create 'mature' reference */ @Deprecated public StaticReference(@NotNull String role, @NotNull SNode sourceNode, @Nullable SModelReference targetModelReference, @Nullable SNodeId nodeId, @Nullable String resolveInfo) { // 'targetModelReference' can be null only if it is broken external reference super(role, sourceNode, targetModelReference, null); setResolveInfo(resolveInfo); myTargetNodeId = nodeId; } @Override @Nullable public SNodeId getTargetNodeId() { SNode immatureNode = myImmatureTargetNode; if (immatureNode == null || makeIndirect()) return myTargetNodeId; return immatureNode.getNodeId(); } public synchronized void setTargetNodeId(SNodeId nodeId) { if (!makeIndirect()) makeMature(); myTargetNodeId = nodeId; } @Override protected SNode getTargetNode_internal() { SModelReference mr = getTargetSModelReference(); if (mr != null) { NodeReadAccessCasterInEditor.fireReferenceTargetReadAccessed(getSourceNode(), mr, getTargetNodeId()); } if (myImmatureTargetNode != null) { synchronized (this) { if (!makeIndirect()) { return myImmatureTargetNode; } } } SNodeId targetNodeId = getTargetNodeId(); if (targetNodeId == null) { // 'unresolved' actually. // It can be tmp reference created while copy/pasting a node return null; } SModel targetModel = getTargetSModel(); if (targetModel == null) return null; if (targetModel instanceof ModelWithDisposeInfo && ((ModelWithDisposeInfo) targetModel).isDisposed()) { Logger log = Logger.wrap(LogManager.getLogger(this.getClass())); StringBuilder sb = new StringBuilder(); sb.append("target model "); sb.append(targetModel.toString()); sb.append(" is disposed\n"); SNode sourceNode = getSourceNode(); sb.append("source node is: name = "); sb.append(sourceNode.getName()); sb.append(", model = "); sb.append(sourceNode.getModel()); sb.append(", id = "); sb.append(sourceNode.getNodeId().toString()); sb.append("\ntarget node id = "); sb.append(targetNodeId); // sourceNode.getName() above ensures ModelAccess.instance().canRead() == true sb.append("\nstack trace of model disposing is: "); for (StackTraceElement ste : ((ModelWithDisposeInfo) targetModel).getDisposedStacktrace()) { sb.append(ste); sb.append("\n"); } log.error(sb.toString()); log.errorWithTrace("=============current trace:============="); return null; } SNode targetNode = targetModel.getNode(targetNodeId); if (targetNode != null) return targetNode; targetNode = UnregisteredNodes.instance().get(targetModel.getReference(), targetNodeId); if (targetNode == null) { error("target model '" + targetModel.getReference() + "' doesn't contain node with id=" + getTargetNodeId(), true); } return targetNode; } public SModel getTargetSModel() { SModel current = getSourceNode().getModel(); if (current != null && current.getReference().equals(getTargetSModelReference())) return current; // external SModelReference targetModelReference = getTargetSModelReference(); // 'unresolved' actually. // It can be tmp reference created while copy/pasting a node if (targetModelReference == null) return null; SModel modelDescriptor = null; if (current != null) { // indeed, repository might ne null, and present resolve() implementation tolerates null, see below. // likely, shall change once SRepository story is complete modelDescriptor = targetModelReference.resolve(current.getRepository()); if (modelDescriptor == null && current.getModule() != null) { // FIXME this hack is a replacement for deprecated SModule.resolveInDependencies // which used to help in resolution of transient proxy models. Transient models are not // available in a repository unless published, and regular model id we use for them are // globally unique, thus resolution through SModelReference.resolve() fails. // For regular transient models, resolution works as we use transient module id as part of the reference, // while for proxy models we use ModelFactory.create API which doesn't provide mechanism to specify model reference yet, // and generates one without module id. // Even if there's mechanism to specify module id for proxy model, shall decide how to approach greater control of a module // over resolution of its models, whether it should be new resolveInDependencies(SModelReference) or a dedicated SRepository with // transient/proxy models. // Perhaps, immature references in transient models would be even better way to go. // In fact, that's how proxy resolution works in in-place == true mode, as source nodes the moment their references got replaced // are free-floating (without in-place, they are part of output model), and references get created with immature target node. modelDescriptor = current.getModule().getModel(targetModelReference.getModelId()); } } else if (!RuntimeFlags.isMergeDriverMode()) { // [artem] here comes essential piece of MPS functionality - one can create node hanging in the thin air // set reference using string for model name and node id, and then magically resolve this simply navigating the reference // Why not e.g. nodePointer.resolve(repo) - I have no idea. Try to remove once RuntimeUtils got fixed to see if there are a lot of assumptions like that. modelDescriptor = targetModelReference.resolve(null); } return modelDescriptor; } @Override protected void adjustMature(SNode immatureTarget) { myTargetNodeId = immatureTarget.getNodeId(); } }