/*
* 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.reference;
import jetbrains.mps.InternalFlag;
import jetbrains.mps.generator.impl.TemplateGenerator;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SReference;
/**
* These references are created in transient models.
* They are always internal.
*/
public class PostponedReference extends jetbrains.mps.smodel.SReference {
private ReferenceInfo myReferenceInfo;
private SReference myReplacementReference;
private TemplateGenerator myGenerator;
public PostponedReference(@NotNull SReferenceLink role, @NotNull SNode sourceNode, @NotNull ReferenceInfo referenceInfo) {
super(role, sourceNode);
myReferenceInfo = referenceInfo;
}
/*
* We used to set postponed references in output model nodes right away. With in-place transformation,
* however, when input model is the same as output, certain scenarios might lead to ordering dependency
* between postponed references.
* E.g. when DeltaBuilder.prepareReferences() replaces a reference in input-output model
* with PostponedReference, and one of ReferenceMacro resolvers already in the list of
* PostponedReferenceUpdate depends on that reference (i.e. ref1 in <code>genContext.get output LABEL for (node.ref1.value)</code>)
* During PostponedReferenceUpdate, RM has no chance to resolve its target as it first needs a PostponedReference far behind it in the
* list to get resolved.
*
* Therefore, we no longer change references of a source node, but merely collect references for later processing. Of course,
* we could have done this conditionally for in-place transformation only, but generally I don't see a reason not to do the
* same in a separate input/output models case.
*
* This change has an amusing side affect that no PostponedReference could ever stay in a model. On one hand, this avoids
* cryptic errors about unexpected PostponedReference and helps to eliminate checks for PR in a transient model. OTOH, potential
* problems are easy to overlook, and hard to trace down (i.e. no reference is created where previous generator would fail)
*
* @see https://youtrack.jetbrains.com/issue/MPS-22271
*/
public void registerWith(@NotNull TemplateGenerator generator) {
myGenerator = generator;
generator.register(this);
}
/*package*/ TemplateGenerator getGenerator() {
return myGenerator;
}
@Override
@Nullable
public synchronized SModelReference getTargetSModelReference() {
if (myReplacementReference != null) {
return myReplacementReference.getTargetSModelReference();
}
// ok, reference is unresolved and not required
return null;
}
@Override
@Deprecated
/**
* Use method in SReferenceBase class, as when you change ref, you know what ref it is
* @Deprecated in 3.0
*/
public void setTargetSModelReference(@NotNull SModelReference modelReference) {
if (InternalFlag.isInternalMode()) {
throw new UnsupportedOperationException();
}
// I don't throw exception here as it might obscure any other error that lead
// to model reference change, e.g. if a reference of incomplete transient model
// is changed from finally{} block of GenerationSession (see MPS-21983)
// Generator code doesn't change reference's target model directly.
Logger.getLogger(PostponedReference.class).error("ATTEMPT TO CHANGE TARGET MODEL of PostponedReference", new UnsupportedOperationException());
}
@Override
protected SNode getTargetNode_internal() {
if (myReplacementReference == null) {
return null;
}
return myReplacementReference.getTargetNode();
}
/**
* @return null is not resolved and not required.
*/
public SReference initReplacementReference() {
if (myReplacementReference != null) {
return myReplacementReference;
}
synchronized (this) {
if (myReferenceInfo == null) {
return myReplacementReference; // already processed
}
myReplacementReference = myReferenceInfo.create(this);
// release resources
myReferenceInfo = null;
}
return myReplacementReference;
}
/**
* replaces this instance with ether StaticReference or with DynamicReference. (only static so far)
* removes reference in case of error.
*/
public void replace() {
getSourceNode().setReference(getLink(), myReplacementReference);
}
}