/* * 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.runtime.base; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.smodel.IOperationContext; import jetbrains.mps.smodel.adapter.structure.concept.SAbstractConceptAdapter; import jetbrains.mps.smodel.language.ConceptRegistry; 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.ConstraintContext_CanBeRoot; import jetbrains.mps.smodel.runtime.ConstraintContext_DefaultScopeProvider; import jetbrains.mps.smodel.runtime.ConstraintFunction; import jetbrains.mps.smodel.runtime.ConstraintFunctions; import jetbrains.mps.smodel.runtime.ConstraintsDescriptor; import jetbrains.mps.smodel.runtime.ConstraintsDispatchable; import jetbrains.mps.smodel.runtime.IconResource; import jetbrains.mps.smodel.runtime.InheritanceIterable; import jetbrains.mps.smodel.runtime.PropertyConstraintsDescriptor; import jetbrains.mps.smodel.runtime.ReferenceConstraintsDescriptor; import jetbrains.mps.smodel.runtime.ReferenceScopeProvider; import jetbrains.mps.util.MacrosFactory; import jetbrains.mps.vfs.FileSystem; import jetbrains.mps.vfs.IFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.language.SProperty; import org.jetbrains.mps.openapi.language.SReferenceLink; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; public class BaseConstraintsDescriptor implements ConstraintsDispatchable, ConstraintsDescriptor { private final SAbstractConcept myConcept; private ConstraintFunction<ConstraintContext_CanBeChild, Boolean> myCanBeChildConstraint; private ConstraintFunction<ConstraintContext_CanBeRoot, Boolean> myCanBeRootConstraint; private ConstraintFunction<ConstraintContext_CanBeParent, Boolean> myCanBeParentConstraint; private ConstraintFunction<ConstraintContext_CanBeAncestor, Boolean> myCanBeAncestorConstraint; private ConstraintFunction<ConstraintContext_DefaultScopeProvider, ReferenceScopeProvider> myDefaultScopeConstraint; private final ConcurrentHashMap<SProperty, PropertyConstraintsDescriptor> propertiesConstraints = new ConcurrentHashMap<>(); private final ConcurrentHashMap<SReferenceLink, ReferenceConstraintsDescriptor> referencesConstraints = new ConcurrentHashMap<>(); public BaseConstraintsDescriptor(SAbstractConcept concept) { this.myConcept = concept; calcInheritance(); } protected Map<SProperty, PropertyConstraintsDescriptor> getSpecifiedProperties() { // XXX not sure whether shall make the method abstract or return an empty map. return Collections.emptyMap(); } protected Map<SReferenceLink, ReferenceConstraintsDescriptor> getSpecifiedReferences() { // XXX not sure whether shall make the method abstract or return an empty map. return Collections.emptyMap(); } protected ConstraintFunction<ConstraintContext_CanBeChild, Boolean> calculateCanBeChildConstraint() { if (hasOwnCanBeChildMethod()) { // branch for interoperability with legacy non-regenerated code // remove after 3.5 return (context, checkingNodeContext) -> canBeChild( context.getNode(), context.getParentNode(), context.getLink() == null ? null : context.getLink().getDeclarationNode(), context.getConcept().getDeclarationNode(), null, checkingNodeContext ); } return ConstraintFunctions.createBooleanComposition(collectParents(ConstraintFunctions::getCanBeChildConstraintFunction)); } protected ConstraintFunction<ConstraintContext_CanBeRoot, Boolean> calculateCanBeRootConstraint() { if (hasOwnCanBeRootMethod()) { // branch for interoperability with legacy non-regenerated code // remove after 3.5 return (context, checkingNodeContext) -> canBeRoot(context.getModel(), null, checkingNodeContext); } return ConstraintFunctions.createBooleanComposition(collectParents(ConstraintFunctions::getCanBeRootConstraintFunction)); } protected ConstraintFunction<ConstraintContext_CanBeParent, Boolean> calculateCanBeParentConstraint() { if (hasOwnCanBeParentMethod()) { // branch for interoperability with legacy non-regenerated code // remove after 3.5 return (context, checkingNodeContext) -> canBeParent( context.getNode(), context.getChildNode(), context.getChildConcept().getDeclarationNode(), context.getLink() == null ? null : context.getLink().getDeclarationNode(), null, checkingNodeContext ); } return ConstraintFunctions.createBooleanComposition(collectParents(ConstraintFunctions::getCanBeParentConstraintFunction)); } protected ConstraintFunction<ConstraintContext_CanBeAncestor, Boolean> calculateCanBeAncestorConstraint() { if (hasOwnCanBeAncestorMethod()) { // branch for interoperability with legacy non-regenerated code // remove after 3.5 return (context, checkingNodeContext) -> canBeAncestor( context.getNode(), context.getChildNode(), context.getChildConcept().getDeclarationNode(), context.getParentNode(), context.getLink() == null ? null : context.getLink().getDeclarationNode(), null, checkingNodeContext ); } return ConstraintFunctions.createBooleanComposition(collectParents(ConstraintFunctions::getCanBeAncestorConstraintFunction)); } protected ConstraintFunction<ConstraintContext_DefaultScopeProvider, ReferenceScopeProvider> calculateDefaultScopeConstraint() { if (hasOwnDefaultScopeProvider()) { // branch for interoperability with legacy non-regenerated code // remove after 3.5 return (context, checkingNodeContext) -> getDefaultScopeProvider(); } return ConstraintFunctions.createScopeProviderComposition(collectParents(ConstraintFunctions::getDefaultScopeConstraintFunction)); } public ConstraintFunction<ConstraintContext_CanBeChild, Boolean> getCanBeChildConstraint() { return myCanBeChildConstraint; } public ConstraintFunction<ConstraintContext_CanBeRoot, Boolean> getCanBeRootConstraint() { return myCanBeRootConstraint; } public ConstraintFunction<ConstraintContext_CanBeParent, Boolean> getCanBeParentConstraint() { return myCanBeParentConstraint; } public ConstraintFunction<ConstraintContext_CanBeAncestor, Boolean> getCanBeAncestorConstraint() { return myCanBeAncestorConstraint; } public ConstraintFunction<ConstraintContext_DefaultScopeProvider, ReferenceScopeProvider> getDefaultScopeConstraint() { return myDefaultScopeConstraint; } protected void calcInheritance() { propertiesConstraints.putAll(getSpecifiedProperties()); referencesConstraints.putAll(getSpecifiedReferences()); myCanBeChildConstraint = calculateCanBeChildConstraint(); myCanBeRootConstraint = calculateCanBeRootConstraint(); myCanBeParentConstraint = calculateCanBeParentConstraint(); myCanBeAncestorConstraint = calculateCanBeAncestorConstraint(); myDefaultScopeConstraint = calculateDefaultScopeConstraint(); } private <C, R> List<ConstraintFunction<C, R>> collectParents( Function<BaseConstraintsDescriptor, ConstraintFunction<C, R>> mapper ) { return new InheritanceIterable(myConcept).stream() .map(BaseConstraintsDescriptor::getDescriptor) .filter(Objects::nonNull) .map(mapper) .collect(Collectors.toList()); } protected static BaseConstraintsDescriptor getDescriptor(SAbstractConcept concept) { ConstraintsDescriptor cd = ConceptRegistry.getInstance().getConstraintsDescriptor(concept); if (cd instanceof BaseConstraintsDescriptor) { return (BaseConstraintsDescriptor) cd; } return null; } @Override @Deprecated public boolean hasOwnCanBeChildMethod() { return false; } @Override @Deprecated public boolean hasOwnCanBeRootMethod() { return false; } @Override @Deprecated public boolean hasOwnCanBeParentMethod() { return false; } @Override @Deprecated public boolean hasOwnCanBeAncestorMethod() { return false; } @Override @Deprecated public boolean hasOwnDefaultScopeProvider() { return false; } @Override public SAbstractConcept getConcept() { return myConcept; } @Deprecated @Override public boolean canBeChild(@Nullable SNode node, SNode parentNode, SNode link, SNode childConcept, IOperationContext operationContext, @Nullable CheckingNodeContext checkingNodeContext) { return canBeChild(new ConstraintContext_CanBeChild(node, childConcept, parentNode, link), checkingNodeContext); } @Override public boolean canBeChild(@NotNull ConstraintContext_CanBeChild context, @Nullable CheckingNodeContext checkingNodeContext) { return myCanBeChildConstraint.invoke(context, checkingNodeContext); } @Override public boolean canBeRoot(@NotNull ConstraintContext_CanBeRoot context, @Nullable CheckingNodeContext checkingNodeContext) { return myCanBeRootConstraint.invoke(context, checkingNodeContext); } @Deprecated @Override public boolean canBeRoot(@NotNull SModel model, IOperationContext operationContext, @Nullable CheckingNodeContext checkingNodeContext) { return canBeRoot(new ConstraintContext_CanBeRoot(model), checkingNodeContext); } @Deprecated @Override public boolean canBeParent(SNode node, @Nullable SNode childNode, SNode childConcept, SNode link, IOperationContext operationContext, @Nullable CheckingNodeContext checkingNodeContext) { return canBeParent(new ConstraintContext_CanBeParent(node, childNode, childConcept, link), checkingNodeContext); } @Override public boolean canBeParent(@NotNull ConstraintContext_CanBeParent context, @Nullable CheckingNodeContext checkingNodeContext) { return myCanBeParentConstraint.invoke(context, checkingNodeContext); } @Deprecated @Override public boolean canBeAncestor(SNode node, @Nullable SNode childNode, SNode childConcept, SNode parentNode, SNode link, IOperationContext operationContext, @Nullable CheckingNodeContext checkingNodeContext) { return canBeAncestor(new ConstraintContext_CanBeAncestor(node, childNode, childConcept, parentNode, link), checkingNodeContext); } @Override public boolean canBeAncestor(@NotNull ConstraintContext_CanBeAncestor context, @Nullable CheckingNodeContext checkingNodeContext) { return myCanBeAncestorConstraint.invoke(context, checkingNodeContext); } public PropertyConstraintsDescriptor getProperty(SProperty property) { if (propertiesConstraints.containsKey(property)) { return propertiesConstraints.get(property); } if (!((SAbstractConceptAdapter) myConcept).hasProperty(property)) { return null; } propertiesConstraints.put(property, new BasePropertyConstraintsDescriptor(property, this)); return propertiesConstraints.get(property); } public ReferenceConstraintsDescriptor getReference(SReferenceLink ref) { if (referencesConstraints.containsKey(ref)) { return referencesConstraints.get(ref); } if (!((SAbstractConceptAdapter) myConcept).hasReference(ref)) { return null; } referencesConstraints.put(ref, new BaseReferenceConstraintsDescriptor(ref, this)); return referencesConstraints.get(ref); } @Override public ReferenceScopeProvider getDefaultScopeProvider() { return myDefaultScopeConstraint.invoke(ConstraintContext_DefaultScopeProvider.getInstance(), null); } @Override public String getAlternativeIcon(SNode node) { return null; } @Nullable @Override public IconResource getInstanceIcon(SNode node) { //compatibility code introduced before 3.4 //we can remove this code when users migrate to new method in constraints return new MyIconResource(node); } public SAbstractConcept getDefaultConcreteConcept() { return myConcept; } private class MyIconResource extends IconResource { private final SNode myNode; public MyIconResource(SNode node) { super("", null); myNode = node; } @Override public InputStream getResource() { String iconPath = MacrosFactory.forModule((AbstractModule) myNode.getConcept().getDeclarationNode().getModel().getModule()).expandPath( getAlternativeIcon(myNode)); if (iconPath == null) { return null; } IFile file = FileSystem.getInstance().getFileByPath(iconPath); if (!file.exists()) { return null; } try { return file.openInputStream(); } catch (IOException e) { return null; } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } MyIconResource that = (MyIconResource) o; return myNode == that.myNode; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (myNode != null ? myNode.hashCode() : 0); return result; } } }