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