/* * Copyright 2003-2015 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.core.aspects.behaviour; import jetbrains.mps.core.aspects.behaviour.api.BHDescriptor; import jetbrains.mps.core.aspects.behaviour.api.BehaviorRegistry; import jetbrains.mps.core.aspects.behaviour.api.SAbstractType; import jetbrains.mps.core.aspects.behaviour.api.SMethod; import jetbrains.mps.core.aspects.behaviour.api.SMethodId; import jetbrains.mps.core.aspects.behaviour.api.SModifiers; import jetbrains.mps.core.aspects.behaviour.api.SParameter; import jetbrains.mps.core.aspects.behaviour.api.SThrowable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.annotations.Immutable; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.model.SNode; import java.util.List; /** * As for 3.2 -- we still have the behavior language which allows several methods with identical signature. * Moreover it urges us to point to the overridden method explicitly. * * Pro & contra * 1. The good part is a possibility to resolve a simple case: * abstract I1#foo(); * abstract I2#foo(); * concept C extends I1, I2. * We are able to deliver up two separate methods (with the same java signature) overriding each foo separately. * 2. The bad part is that a user of the system can easily make a mess: methods are resolved by id. * Also the generated code is forced to make the most of id, because string name resolving is simply not enough here. * Besides it is not that easy to override method -- one needs to reference the method he wants to override. * * Plan for 3.3: * 1. Make the 'overrides' reference optional: * simply rename all the methods so that overridden_method.name.equals(this.name) * 'overrides' is required only two virtual method hierarchies clash in the concept. * 2. Grant the 'virtual' keyword new semantics: automatic method resolve * must happen in the case of virtual methods with overridden method == null. * 3. Give out an error about methods' name collision, forbid generation if that is the case. * * Only there is no conflict for virtual methods' names throughout in the concept hierarchy * it might be possible to resolve two overriding methods by name. */ @SuppressWarnings("Duplicates") @Immutable public final class SMethodImpl<T> implements SMethod<T> { public static final String METHOD_NAME_ID_SEPARATOR ="_"; // used in the behavior generator private final String myName; private final SModifiers myMethodModifiers; private final SAbstractType myReturnType; private final SAbstractConcept myConcept; private final List<SParameter> myParameters; private final SMethodId myId; // in the case of virtual methods the id is always the id of the base method (topmost) private final BehaviorRegistry myRegistry; private SMethodImpl(@NotNull String name, @NotNull SModifiers modifiers, @NotNull SAbstractType returnType, @NotNull SAbstractConcept concept, @NotNull SMethodId methodId, @NotNull BehaviorRegistry registry, List<SParameter> parameters) { myName = name; myMethodModifiers = modifiers; myReturnType = returnType; myConcept = concept; myParameters = parameters; myRegistry = registry; myId = methodId; } /** * NB: there is a cozy {@link SMethodBuilder} to create SMethod * * @param <T> -- parametrized by return type * @param methodName -- usual methodName * @param modifiers -- could be virtual or (and) static. @see SMethodModifiers * @param returnType -- return type * @param concept -- the concept, which contains the method declaration. * we need it to distinguish two identically named non-virtual methods in the parent and the child classes. * @param parameters -- the types of method's arguments * @param id -- method string id which must uniquely identify the method throughout the concept hierarchy. * NB: for the virtual methods which have the same base method, the id must be the same. * * @param registry -- BehaviorRegistry to get the mapping concept <-> behavior descriptor * @return new SMethod */ public static <T> SMethod<T> create(@NotNull String methodName, @NotNull SModifiers modifiers, @NotNull SAbstractType returnType, @NotNull SAbstractConcept concept, @NotNull SMethodId id, @NotNull BehaviorRegistry registry, List<SParameter> parameters) { return new SMethodImpl<T>(methodName, modifiers, returnType, concept, id, registry, parameters); } private void checkForConcept(@NotNull SAbstractConcept concept) { if (!concept.isSubConceptOf(myConcept)) { throw new IllegalArgumentException("Illegal parameter : " + concept + " is not a subconcept of " + myConcept); } } @Override public T invoke(@Nullable SNode operand, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } SConcept concept = operand.getConcept(); return invoke0(operand, concept, parameters); } @Override public T invoke(@Nullable SAbstractConcept operand, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } return invoke0(operand, operand, parameters); } @Override public T invoke0(@Nullable SNode operand, @NotNull SAbstractConcept concreteConcept, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(concreteConcept); if (myMethodModifiers.isPrivate()) { return invokeSpecial(operand, parameters); } BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(concreteConcept); return bhDescriptor.invoke(operand, this, parameters); } @Override public T invoke0(@Nullable SAbstractConcept operand, @NotNull SAbstractConcept concreteConcept, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(operand); if (myMethodModifiers.isPrivate()) { return invokeSpecial(operand, parameters); } BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(concreteConcept); return bhDescriptor.invoke(operand, this, parameters); } @Override public T invokeSpecial(@Nullable SNode operand, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(operand.getConcept()); BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(myConcept); return bhDescriptor.invokeSpecial(operand, this, parameters); } @Override public T invokeSpecial(@Nullable SAbstractConcept operand, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(operand); BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(myConcept); return bhDescriptor.invokeSpecial(operand, this, parameters); } @Override public T invokeSuper(@Nullable SNode operand, @NotNull SAbstractConcept concept, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(concept); if (myMethodModifiers.isPrivate()) { throw new IllegalArgumentException("Cannot invoke super method which is declared private" + this); } if (!isVirtual()) { throw new IllegalArgumentException("Method must be virtual"); } BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(concept); return bhDescriptor.invokeSuper(operand, this, parameters); } @Override public T invokeSuper(@Nullable SAbstractConcept operand, @NotNull SAbstractConcept concept, Object... parameters) { if (operand == null) { //noinspection unchecked return (T) getReturnType().getDefaultValue(); } checkForConcept(concept); if (myMethodModifiers.isPrivate()) { throw new IllegalArgumentException("Cannot invoke super method which is declared private" + this); } if (!isVirtual()) { throw new IllegalArgumentException("Method must be virtual"); } BHDescriptor bhDescriptor = myRegistry.getBHDescriptor(concept); return bhDescriptor.invokeSuper(operand, this, parameters); } @NotNull @Override public SAbstractType getReturnType() { return myReturnType; } @Override public List<SParameter> getParameters() { return myParameters; } @Override public List<SThrowable> getExceptions() { return null; // fixme } @NotNull @Override public SModifiers getModifiers() { return myMethodModifiers; } public boolean isVirtual() { return myMethodModifiers.isVirtual(); } @Override public boolean isAbstract() { return myMethodModifiers.isAbstract(); } @Override public boolean isStatic() { return myMethodModifiers.isStatic(); } @NotNull @Override public SAbstractConcept getConcept() { return myConcept; } @NotNull @Override public SMethodId getId() { return myId; } @NotNull @Override public String getName() { return myName; } public boolean isPublic() { return myMethodModifiers.isPublic(); } @Override public String toString() { return String.format("%s:%s(%s)%s", myReturnType.toString(), myName, myParameters, myMethodModifiers.toString()); } @Override public boolean equals(Object o) { if (o instanceof SMethod) { SMethod another = (SMethod) o; return getId().equals(another.getId()); } return false; } @Override public int hashCode() { return myId.hashCode(); } }