/*
* 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.smodel.constraints;
import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.scope.ErrorScope;
import jetbrains.mps.scope.ModelPlusImportedScope;
import jetbrains.mps.scope.Scope;
import jetbrains.mps.smodel.DynamicReference;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.smodel.language.ConceptRegistryUtil;
import jetbrains.mps.smodel.runtime.ReferenceConstraintsDescriptor;
import jetbrains.mps.smodel.runtime.ReferenceScopeProvider;
import jetbrains.mps.smodel.runtime.base.BaseReferenceScopeProvider;
import jetbrains.mps.smodel.search.ConceptAndSuperConceptsScope;
import jetbrains.mps.smodel.search.ISearchScope.Adapter;
import jetbrains.mps.smodel.search.ISearchScope.RefAdapter;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SReference;
import org.jetbrains.mps.openapi.module.SModule;
import static jetbrains.mps.smodel.constraints.ModelConstraintsUtils.getOperationContext;
/**
* Abstraction to capture constraints-related stuff about references.
* Is a consumer of what ConstraintsAspectDescriptor->ConstraintsDescriptor->ReferenceConstraintsDescriptor
* provides, and is sort of facade for these classes to the rest of MPS.
* I'm not quite sure it deserves to stay, as we could use descriptors directly, however, descriptors need
* a change as well, and it might be reasonable to keep both for a while, to facilitate step by step refactoring
* (first, uses of this class, then new descriptors). API this class provides is of dubious quality
* (e.g. #getReferencePresentation() with booleans)
*/
public abstract class ReferenceDescriptor {
private static final Logger LOG = Logger.wrap(LogManager.getLogger(ReferenceDescriptor.class));
private static final BaseReferenceScopeProvider EMPTY_REFERENCE_SCOPE_PROVIDER = new BaseReferenceScopeProvider();
// can be ErrorScope
@NotNull
abstract public Scope getScope();
/**
* @return null if there is no presentation for reference
* @deprecated uses only in the editor
*/
@Nullable
@Deprecated
@ToRemove(version = 3.5)
abstract public String getReferencePresentation(SNode targetNode, boolean visible, boolean smartRef, boolean inEditor);
/**
* @deprecated this class shall not expose its implementation detail, otherwise there's no point in its presence.
* refactor the single use and remove this method, it's our internal api.
* Perhaps, we need a distinct validator object?
*/
@Nullable
@Deprecated
@ToRemove(version = 3.5)
public ReferenceScopeProvider getScopeProvider() {
return null;
}
static class OkReferenceDescriptor extends ReferenceDescriptor {
// main parameters for ScopeProvider calculating
@NotNull
private final SAbstractConcept myNodeConcept;
@NotNull
private final SReferenceLink myReferenceLink;
private final SReference myReference;
private final SNode myReferenceNode;
// parameters that used for describing context when myReferenceNode is null
@NotNull
private final SNode myContextNode;
private final SContainmentLink myContainmentLink;
private final int myPosition;
//for specialized links
@NotNull
private final SAbstractConcept myLinkTarget;
// calculated scope provider
@Nullable
private final ReferenceScopeProvider myScopeProvider;
OkReferenceDescriptor(@NotNull SAbstractConcept nodeConcept, @NotNull SReferenceLink referenceLink, @NotNull SNode contextNode,
/*TODO should be @NotNull*/ @Nullable SContainmentLink containmentLink, int position) {
myReference = null;
myReferenceNode = null;
myNodeConcept = nodeConcept;
myReferenceLink = referenceLink;
myContextNode = contextNode;
myLinkTarget = getLinkTarget(myReferenceLink, myNodeConcept);
myContainmentLink = containmentLink;
myPosition = position;
myScopeProvider = getScopeProvider(myNodeConcept, myReferenceLink);
}
OkReferenceDescriptor(@NotNull SReferenceLink referenceLink, @NotNull SNode referenceNode) {
myReference = null;
myReferenceNode = referenceNode;
myNodeConcept = myReferenceNode.getConcept();
myReferenceLink = referenceLink;
myContextNode = myReferenceNode;
myLinkTarget = getLinkTarget(myReferenceLink, myNodeConcept);
myContainmentLink = null;
myPosition = 0;
myScopeProvider = getScopeProvider(myNodeConcept, myReferenceLink);
}
OkReferenceDescriptor(@NotNull SReference reference) {
myReference = reference;
myReferenceNode = myReference.getSourceNode();
myNodeConcept = myReferenceNode.getConcept();
myReferenceLink = myReference.getLink();
myContextNode = myReferenceNode;
myLinkTarget = getLinkTarget(myReferenceLink, myNodeConcept);
myContainmentLink = null;
myPosition = 0;
myScopeProvider = getScopeProvider(myNodeConcept, myReferenceLink);
}
@Override
@NotNull
public Scope getScope() {
final ReferentConstraintsContextImpl context =
new ReferentConstraintsContextImpl(myContextNode, myContainmentLink, myPosition, myReferenceNode, myReference != null, myLinkTarget);
try {
if (myScopeProvider != null) {
Scope searchScope = myScopeProvider.createScope(getOperationContext(getModule()), context);
if (searchScope != null) {
if (myReference != null && searchScope instanceof Adapter) {
return new RefAdapter(((Adapter) searchScope).getSearchScope(), myReference);
}
return searchScope;
}
}
// global search scope
return new ModelPlusImportedScope(getModel(), false, myLinkTarget);
} catch (Exception t) {
LOG.error(t, myContextNode);
return new ErrorScope("can't create search scope for link `" + myReferenceLink + "' in '" + myNodeConcept.getName() + "'");
}
}
@Override
@Nullable
@Deprecated
@ToRemove(version = 3.5)
public String getReferencePresentation(SNode targetNode, boolean visible, boolean smartRef, boolean inEditor) {
if (myScopeProvider == null || !myScopeProvider.hasPresentation()) {
return null;
}
return myScopeProvider.getPresentation(
getOperationContext(getModule()),
new PresentationReferentConstraintsContextImpl(myContextNode, myContainmentLink, myPosition, myReferenceNode, myReference != null, myLinkTarget,
targetNode, visible, smartRef, inEditor)
);
}
@Override
@Nullable
public ReferenceScopeProvider getScopeProvider() {
return myScopeProvider;
}
@Nullable
private static ReferenceScopeProvider getScopeProvider(SAbstractConcept nodeConcept, SReferenceLink associationLink) {
ReferenceConstraintsDescriptor refConstraintsDescriptor =
ConceptRegistryUtil.getConstraintsDescriptor(nodeConcept).getReference(associationLink);
if (refConstraintsDescriptor != null) {
ReferenceScopeProvider result = refConstraintsDescriptor.getScopeProvider();
if (result != null) {
return result;
}
}
SAbstractConcept conceptForDefaultSearchScope = associationLink.getTargetConcept();
return ConceptRegistryUtil.getConstraintsDescriptor(conceptForDefaultSearchScope).getDefaultScopeProvider();
}
@NotNull
private static SAbstractConcept getLinkTarget(@NotNull SReferenceLink genuineLink, @NotNull SAbstractConcept concreteConcept) {
// TODO for now, link target is calculated using language sources.
// it will be possible to do it without sources when information about link specialization will be generated.
SNode conceptDeclaration = concreteConcept.getDeclarationNode();
SNode linkDeclaration = new ConceptAndSuperConceptsScope(conceptDeclaration).getMostSpecificLinkDeclarationByRole(genuineLink.getName());
final SNode linkDeclarationTarget = SModelUtil.getLinkDeclarationTarget(linkDeclaration);
if (linkDeclarationTarget != null) {
SAbstractConcept concept = MetaAdapterByDeclaration.getConcept(linkDeclarationTarget);
if (concept != null) {
return concept;
}
}
return genuineLink.getTargetConcept();
}
private SModel getModel() {
return myContextNode.getModel();
}
private SModule getModule() {
return ModelConstraintsUtils.getModule(getModel());
}
}
static class ErrorReferenceDescriptor extends ReferenceDescriptor {
private final String message;
public ErrorReferenceDescriptor(String message) {
this.message = message;
}
@NotNull
@Override
public Scope getScope() {
return new ErrorScope(message);
}
@Override
@Nullable
@Deprecated
@ToRemove(version = 3.5)
public String getReferencePresentation(SNode targetNode, boolean visible, boolean smartRef, boolean inEditor) {
return null;
}
@Override
public ReferenceScopeProvider getScopeProvider() {
return null;
}
}
}