/*
* 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;
}
}
}