/*
* 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.scope.Scope;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.smodel.adapter.structure.link.InvalidContainmentLink;
import jetbrains.mps.smodel.adapter.structure.ref.InvalidReferenceLink;
import jetbrains.mps.smodel.constraints.ReferenceDescriptor.ErrorReferenceDescriptor;
import jetbrains.mps.smodel.constraints.ReferenceDescriptor.OkReferenceDescriptor;
import jetbrains.mps.smodel.language.ConceptRegistry;
import jetbrains.mps.smodel.language.ConceptRegistryUtil;
import jetbrains.mps.smodel.legacy.ConceptMetaInfoConverter;
import jetbrains.mps.smodel.presentation.ReferenceConceptUtil;
import jetbrains.mps.smodel.runtime.CheckingNodeContext;
import jetbrains.mps.smodel.runtime.ConstraintContext_CanBeAncestor;
import jetbrains.mps.smodel.runtime.ConstraintContext_CanBeChild;
import jetbrains.mps.smodel.runtime.ConstraintContext_CanBeParent;
import jetbrains.mps.smodel.runtime.ConstraintsDescriptor;
import jetbrains.mps.util.annotation.ToRemove;
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.SRepository;
import static jetbrains.mps.smodel.constraints.ModelConstraintsUtils.getModule;
import static jetbrains.mps.smodel.constraints.ModelConstraintsUtils.getOperationContext;
/**
* API for model constraints
* All methods require read action
* If you don't need breaking node set checkingNodeContext parameter to null
* <p/>
* If you need Scope use 1) getScope(SReference) or 2) getReferenceDescriptor(...).getScope()
* If you need reference presentation use getReferenceDescriptor(...).getText(...)
* <p/>
* Possible parameters for getReferenceDescriptor method:
* getReferenceDescriptor(reference) gets ref descriptor for existing reference
* getReferenceDescriptor(node, role) gets ref descriptor for reference being created at the location, role (cannot be null) should be "reference" link
* getReferenceDescriptor(node, role, index, smartConcept) gets ref descriptor for smartReference being created in "aggregation" role
*/
public class ModelConstraints {
// todo: make ModelConstraints project component? Concept and Language registry too?
// public canBe* section
public static boolean canBeAncestor(@NotNull SNode parentNode, @NotNull SAbstractConcept childConcept,/*TODO @NotNull*/ SContainmentLink containmentLink,
@Nullable CheckingNodeContext checkingNodeContext) {
// TODO: make containmentLink @NotNull and expose this parameter inside canBeAncestor constraint in MPS DSL.
// TODO: For now I did not expose it because editor is calling this method with null containmentLink from time
// TODO: to time -> additional refactoring is required in editor framework in order to achieve it.
return canBeAncestor(new ConstraintContext_CanBeAncestor(parentNode, childConcept, parentNode, containmentLink), checkingNodeContext);
}
public static boolean canBeAncestor(@NotNull SNode childNode, @Nullable CheckingNodeContext checkingNodeContext) {
if (childNode.getParent() == null) {
// for root nodes it should return true
return true;
}
return canBeAncestor(new ConstraintContext_CanBeAncestor(childNode, childNode.getParent()), checkingNodeContext);
}
public static boolean canBeAncestorDirect(@NotNull SNode ancestor, @NotNull SAbstractConcept childConcept, @NotNull SNode parent,
/*TODO @NotNull*/ SContainmentLink containmentLink, @Nullable CheckingNodeContext checkingNodeContext) {
return canBeAncestorDirect(new ConstraintContext_CanBeAncestor(ancestor, childConcept, parent, containmentLink), checkingNodeContext);
}
public static boolean canBeAncestorDirect(@NotNull SNode ancestor, @NotNull SNode descendant, @Nullable CheckingNodeContext checkingNodeContext) {
return canBeAncestorDirect(new ConstraintContext_CanBeAncestor(ancestor, descendant), checkingNodeContext);
}
public static boolean canBeParent(@NotNull SNode node, @NotNull SAbstractConcept childConcept, /*TODO @NotNull*/ SContainmentLink link,
@Nullable CheckingNodeContext checkingNodeContext) {
return canBeParent(new ConstraintContext_CanBeParent(childConcept, node, link), checkingNodeContext);
}
public static boolean canBeParent(@NotNull SNode childNode, @Nullable CheckingNodeContext checkingNodeContext) {
if (childNode.getParent() == null) {
// for root nodes it should return true
return true;
}
return canBeParent(new ConstraintContext_CanBeParent(childNode), checkingNodeContext);
}
public static boolean canBeChild(@NotNull SNode parentNode, @NotNull SAbstractConcept childConcept, /*TODO @NotNull*/ SContainmentLink link,
@Nullable CheckingNodeContext checkingNodeContext) {
return canBeChild(new ConstraintContext_CanBeChild(childConcept, parentNode, link), checkingNodeContext);
}
public static boolean canBeChild(@NotNull SNode node, @Nullable CheckingNodeContext checkingNodeContext) {
return canBeChild(new ConstraintContext_CanBeChild(node), checkingNodeContext);
}
public static boolean canBeRoot(@NotNull SAbstractConcept concept, @NotNull SModel model) {
if (concept.isAbstract()) {
return false;
}
assert concept instanceof SConcept : "non-abstract SAbstractConcept should be an instanceof SConcept";
if (!((SConcept) concept).isRootable()) {
return false;
}
ConstraintsDescriptor descriptor = ConceptRegistryUtil.getConstraintsDescriptor(concept);
return descriptor.canBeRoot(model, getOperationContext(getModule(model)), null);
}
// private canBe* section
private static boolean canBeAncestor(@NotNull ConstraintContext_CanBeAncestor context, @Nullable CheckingNodeContext checkingNodeContext) {
SNode currentNode = context.getNode();
while (currentNode != null) {
context.setNode(currentNode);
ConstraintsDescriptor descriptor = ConceptRegistryUtil.getConstraintsDescriptor(currentNode.getConcept());
if (!descriptor.canBeAncestor(context, checkingNodeContext)) {
return false;
}
currentNode = currentNode.getParent();
}
return true;
}
private static boolean canBeAncestorDirect(@NotNull ConstraintContext_CanBeAncestor context, @Nullable CheckingNodeContext checkingNodeContext) {
ConstraintsDescriptor descriptor = ConceptRegistryUtil.getConstraintsDescriptor(context.getNode().getConcept());
return descriptor.canBeAncestor(context, checkingNodeContext);
}
private static boolean canBeParent(@NotNull ConstraintContext_CanBeParent context, @Nullable CheckingNodeContext checkingNodeContext) {
ConstraintsDescriptor descriptor = ConceptRegistryUtil.getConstraintsDescriptor(context.getNode().getConcept());
return descriptor.canBeParent(context, checkingNodeContext);
}
private static boolean canBeChild(@NotNull ConstraintContext_CanBeChild context, @Nullable CheckingNodeContext checkingNodeContext) {
ConstraintsDescriptor descriptor = ConceptRegistry.getInstance().getConstraintsDescriptor(context.getConcept());
return descriptor.canBeChild(context, checkingNodeContext);
}
// deprecated canBe* section
/**
* @deprecated use {@link #canBeAncestor(SNode, CheckingNodeContext)}
* or {@link #canBeAncestor(SNode, SAbstractConcept, SContainmentLink, CheckingNodeContext)} instead
*/
@Deprecated
@ToRemove(version = 3.5)
public static boolean canBeAncestor(@NotNull SNode node, @Nullable SNode childNode, @NotNull SNode childConcept, SNode containmentLink,
@Nullable CheckingNodeContext checkingNodeContext) {
return canBeAncestor(new ConstraintContext_CanBeAncestor(node, childNode, childConcept, node, containmentLink), checkingNodeContext);
}
/**
* @deprecated use {@link #canBeAncestorDirect(SNode, SNode, CheckingNodeContext)}
* or {@link #canBeAncestorDirect(SNode, SAbstractConcept, SNode, SContainmentLink, CheckingNodeContext)} instead
*/
@Deprecated
@ToRemove(version = 3.5)
public static boolean canBeAncestorDirect(@NotNull SNode ancestor, @Nullable SNode descendant, @NotNull SNode childConcept, SNode parent,
SNode containmentLink, @Nullable CheckingNodeContext checkingNodeContext) {
return canBeAncestorDirect(new ConstraintContext_CanBeAncestor(ancestor, descendant, childConcept, parent, containmentLink), checkingNodeContext);
}
/**
* @deprecated use {@link #canBeParent(SNode, CheckingNodeContext)}
* or {@link #canBeParent(SNode, SAbstractConcept, SContainmentLink, CheckingNodeContext)} instead
*/
@Deprecated
@ToRemove(version = 3.5)
public static boolean canBeParent(@NotNull SNode parentNode, @NotNull SNode childConcept, @NotNull SNode link, @Nullable SNode childNode,
@Nullable CheckingNodeContext checkingNodeContext) {
return canBeParent(new ConstraintContext_CanBeParent(parentNode, childNode, childConcept, link), checkingNodeContext);
}
/**
* @deprecated use {@link #canBeChild(SNode, CheckingNodeContext)}
* or {{@link #canBeChild(SNode, SAbstractConcept, SContainmentLink, CheckingNodeContext)}} instead
*/
@Deprecated
@ToRemove(version = 3.5)
public static boolean canBeChild(SAbstractConcept childConcept, SNode parentNode, SNode link, @Nullable SNode childNode,
@Nullable CheckingNodeContext checkingNodeContext) {
return canBeChild(new ConstraintContext_CanBeChild(childNode, childConcept.getDeclarationNode(), parentNode, link), checkingNodeContext);
}
// scopes part
@NotNull
public static Scope getScope(@NotNull SReference reference) {
return getReferenceDescriptor(reference).getScope();
}
@NotNull
public static ReferenceDescriptor getReferenceDescriptor(@NotNull SReference reference) {
return new OkReferenceDescriptor(reference);
}
@NotNull
public static ReferenceDescriptor getReferenceDescriptor(@NotNull SNode sourceNode, @NotNull SReferenceLink association) {
return new OkReferenceDescriptor(association, sourceNode);
}
/**
* @deprecated shall use {@link #getReferenceDescriptor(SReference)} or {@link #getReferenceDescriptor(SNode, SReferenceLink)} instead.
*/
@NotNull
@Deprecated
@ToRemove(version = 3.3)
public static ReferenceDescriptor getReferenceDescriptor(@NotNull SNode referenceNode, @NotNull String role) {
// TODO: this method first argument before is enclosingNode, it's wrong - it's referenceNode. check usages of method
SConcept concept = referenceNode.getConcept();
SReferenceLink referenceLink = ((ConceptMetaInfoConverter) concept).convertAssociation(role);
if (referenceLink instanceof InvalidReferenceLink) {
return new ErrorReferenceDescriptor("can't find reference link for role '" + role + "' in '" + concept + "'");
}
return new OkReferenceDescriptor(referenceLink, referenceNode);
}
public static ReferenceDescriptor getReferenceDescriptor(@NotNull SNode contextNode, /*TODO should be @NotNull*/ @Nullable SContainmentLink containmentLink,
int position, @NotNull SReferenceLink association) {
return new OkReferenceDescriptor(association.getOwner(), association, contextNode, containmentLink, position);
}
public static ReferenceDescriptor getSmartReferenceDescriptor(@NotNull SNode contextNode,
/*TODO should be @NotNull*/ @Nullable SContainmentLink containmentLink, int position,
@NotNull SAbstractConcept smartConcept) {
SReferenceLink smartReferenceLink = ReferenceConceptUtil.getCharacteristicReference(smartConcept);
if (smartReferenceLink == null) {
return new ErrorReferenceDescriptor("smart concept '" + smartConcept.getName() + "' has no characteristic reference");
}
return new OkReferenceDescriptor(smartConcept, smartReferenceLink, contextNode, containmentLink, position);
}
/**
* @deprecated Use {@link #getSmartReferenceDescriptor(SNode, SContainmentLink, int, SAbstractConcept)} instead.
* It doesn't work for specialized links.
*/
@NotNull
@Deprecated
@ToRemove(version = 3.5)
public static ReferenceDescriptor getSmartReferenceDescriptor(@NotNull SNode enclosingNode, @Nullable String role, int index, @NotNull SNode smartConcept) {
SConcept concept = enclosingNode.getConcept();
SContainmentLink containmentLink = ((ConceptMetaInfoConverter) concept).convertAggregation(role);
if (containmentLink instanceof InvalidContainmentLink) {
return new ErrorReferenceDescriptor("can't find containment link for role '" + role + "' in '" + concept + "'");
}
return getSmartReferenceDescriptor(enclosingNode, containmentLink, index, MetaAdapterByDeclaration.getConcept(smartConcept));
}
/**
* @deprecated Use {@link #getSmartReferenceDescriptor(SNode, SContainmentLink, int, SAbstractConcept)} instead.
*/
@NotNull
@Deprecated
@ToRemove(version = 3.5)
public static ReferenceDescriptor getSmartReferenceDescriptor(@NotNull SNode enclosingNode, @Nullable SContainmentLink link, int index,
@NotNull SConcept smartConcept, SRepository repository) {
return getSmartReferenceDescriptor(enclosingNode, link, index, smartConcept);
}
public static SConcept getDefaultConcreteConcept(SAbstractConcept concept) {
if (!concept.isValid()) {
return MetaAdapterByDeclaration.asInstanceConcept(concept);
}
SAbstractConcept cc = ConceptRegistryUtil.getConstraintsDescriptor(concept).getDefaultConcreteConcept();
// FIXME see ConstraintsDescriptor#getDefaultConcreteConcept() which shall return SConcept right away
return MetaAdapterByDeclaration.asInstanceConcept(cc);
}
}