/*
* Copyright 2003-2014 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.BHMethodNotFoundException;
import jetbrains.mps.core.aspects.behaviour.api.BehaviorRegistry;
import jetbrains.mps.core.aspects.behaviour.api.SAbstractType;
import jetbrains.mps.core.aspects.behaviour.api.SConstructor;
import jetbrains.mps.core.aspects.behaviour.api.SMethod;
import jetbrains.mps.core.aspects.behaviour.api.SMethodId;
import jetbrains.mps.core.aspects.behaviour.api.SParameter;
import jetbrains.mps.smodel.SModelUtil_new;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.*;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Common ancestor for all the generated behavior aspects (per concept).
* Exploiting the idea of virtual table to yield the dynamic dispatch for behavior methods' invocation.
*/
/**
* TODO
* Features:
* Multiple dispatch?
* Default parameter values?
*/
public abstract class BaseBHDescriptor implements BHDescriptor {
private static final Logger LOG = LogManager.getLogger(BaseBHDescriptor.class);
private SAbstractConcept myConcept;
private boolean myInitialized = false;
private SMethodVirtualTable myVTable;
private final SMethodVirtualTable mySuperVTable = new SMethodVirtualTable();
private final BehaviorRegistry myBehaviorRegistry;
private AncestorCache myAncestorCache;
protected BaseBHDescriptor(BehaviorRegistry behaviorRegistry) {
myBehaviorRegistry = behaviorRegistry;
}
/**
* Intended to be executed during concept behavior construction
*
* @see BehaviorRegistry#getBHDescriptor
*/
public synchronized void init() {
if (!myInitialized) {
myConcept = getConcept();
myAncestorCache = new AncestorCache(myConcept, myBehaviorRegistry);
initVirtualTables();
myInitialized = true;
}
}
private void checkInitialized() {
if (!myInitialized) {
throw new BHNotInitializedException(myConcept);
}
}
private void initVirtualTables() {
myVTable = new SMethodVirtualTable(this, getDeclaredMethods());
List<SAbstractConcept> ancestors = myAncestorCache.getAncestorsInvocationOrder();
for (SAbstractConcept ancestor : ancestors) {
if (ancestor != myConcept) {
BHDescriptor bhDescriptor = getBHDescriptor(ancestor);
// now its vtable is initialized
if (bhDescriptor instanceof BaseBHDescriptor) {
SMethodVirtualTable vTable = ((BaseBHDescriptor) bhDescriptor).myVTable;
myVTable.merge(vTable);
mySuperVTable.merge(vTable);
}
}
}
}
@NotNull
private BHDescriptor getBHDescriptor(@NotNull SAbstractConcept concept) {
if (concept.equals(myConcept)) {
return this;
}
return myBehaviorRegistry.getBHDescriptor(concept);
}
static class ParametersTypeConverter {
private final List<SParameter> myMethodParameters;
ParametersTypeConverter(@NotNull List<SParameter> methodParameters) {
myMethodParameters = methodParameters;
}
private SParameter getLastParameter() {
return myMethodParameters.get(myMethodParameters.size() - 1);
}
public Object[] convertParameters(Object... parameters) {
if (parameters == null) {
return new Object[]{null};
}
if (myMethodParameters.isEmpty()) {
return new Object[0];
}
Object[] newParameters;
SParameter lastPrm = getLastParameter();
if (lastPrm instanceof SVarArgParameter) {
newParameters = resolveVarArg(parameters);
} else {
newParameters = resolveSingleArrayArgumentProblem(parameters);
}
return newParameters;
}
@NotNull
private Object[] resolveVarArg(@NotNull Object[] parameters) {
SVarArgParameter lastPrm = (SVarArgParameter) getLastParameter();
Object[] newParameters = new Object[myMethodParameters.size()];
SAbstractType componentType = lastPrm.getComponentType();
Class<?> javaComponentType = Object.class;
if (componentType instanceof SJavaCompoundType) {
javaComponentType = ((SJavaCompoundType) componentType).getJavaType();
}
newParameters[myMethodParameters.size() - 1] = Array.newInstance(javaComponentType, parameters.length - myMethodParameters.size() + 1);
for (int i = 0; i < parameters.length; ++i) {
if (i < myMethodParameters.size() - 1) {
newParameters[i] = parameters[i];
} else {
Array.set(newParameters[myMethodParameters.size() - 1], i - myMethodParameters.size() + 1, parameters[i]);
}
}
return newParameters;
}
@NotNull
private Object[] resolveSingleArrayArgumentProblem(@NotNull Object[] parameters) {
SParameter lastPrm = getLastParameter();
if (myMethodParameters.size() == 1) { // that means that we could be passing a single array
if (lastPrm.getType() instanceof SJavaCompoundType) {
Class<?> javaType = ((SJavaCompoundType) lastPrm.getType()).getJavaType();
if (javaType.isArray()) {
Class<?> componentType = javaType.getComponentType();
for (int i = 0; i < parameters.length; ++i) {
if (parameters[i] == null) {
continue;
}
if (!componentType.isAssignableFrom(parameters[i].getClass())) {
return parameters;
}
}
parameters = new Object[]{parameters};
}
}
}
return parameters;
}
}
/**
* in the case of the last vararg argument converts all arguments into arguments + separate array for the vararg arguments
* also used against a single null in the varargs arguments
*/
@Nullable
private Object[] getParametersArray(@NotNull List<SParameter> methodParameters, Object... parameters) {
return new ParametersTypeConverter(methodParameters).convertParameters(parameters);
}
@NotNull
@Override
public SNode newNode(@Nullable SModel model, @NotNull SConstructor constructor, Object... parameters) {
if (parameters.length > 0) {
throw new IllegalArgumentException("For now one cannot pass arguments to a behavior constructor");
}
SNode node = SModelUtil_new.instantiateConceptDeclaration(myConcept, model, null, false);
new ConstructionHandler(myAncestorCache, myConcept).initNode(node, constructor, getParametersArray(Collections.<SParameter>emptyList(), parameters));
return node;
}
public void initNode(@NotNull SNode node) {
SConstructor defaultConstructor = new SDefaultConstructorImpl(this, AccessPrivileges.PUBLIC);
Object[] emptyParameters = new Object[0];
new ConstructionHandler(myAncestorCache, myConcept).initNode(node, defaultConstructor,
getParametersArray(Collections.<SParameter>emptyList(), emptyParameters));
}
@Override
public final <T> T invoke(@NotNull SNode operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkNotStatic(method);
checkForConcept(operand.getConcept());
if (method.isVirtual()) {
return invokeVirtual(operand, method, parameters);
} else {
return invokeNonVirtual(operand, method, parameters);
}
}
@Override
public final <T> T invoke(@NotNull SAbstractConcept operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkStatic(method);
checkForConcept(operand);
if (method.isVirtual()) {
return invokeVirtual(operand, method, parameters);
} else {
return invokeNonVirtual(operand, method, parameters);
}
}
@Override
public final <T> T invokeSuper(@NotNull SNode operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkNotStatic(method);
checkForConcept(operand.getConcept());
assert method.isVirtual();
return invokeVirtualSuper(operand, method, parameters);
}
@Override
public final <T> T invokeSuper(@NotNull SAbstractConcept operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkStatic(method);
checkForConcept(operand);
assert method.isVirtual();
return invokeVirtualSuper(operand, method, parameters);
}
private void checkForConcept(@NotNull SAbstractConcept concept) {
if (!concept.isSubConceptOf(myConcept)) {
throw new IllegalArgumentException("Illegal parameter : " + concept + " is not a subconcept of " + myConcept);
}
}
private <T> void checkParameters(@NotNull SMethod<T> method, @NotNull Object[] parameters) {
List<SParameter> declaredParameters = method.getParameters();
boolean hasVarArg = !declaredParameters.isEmpty() && declaredParameters.get(declaredParameters.size() - 1) instanceof SVarArgParameter;
if (!hasVarArg) {
if (declaredParameters.size() != parameters.length) {
throw new BHMethodArgumentsCountDoNotMatch(method, parameters.length);
}
}
for (int i = 0; i < parameters.length; ++i) {
if (parameters[i] != null) {
Class<?> aClass = parameters[i].getClass();
SJavaCompoundTypeImpl passedObjectType = new SJavaCompoundTypeImpl(aClass);
if (hasVarArg && (i >= declaredParameters.size() - 1)) { // that lies in a vararg argument
SArrayType varArgType = (SArrayType) declaredParameters.get(declaredParameters.size() - 1).getType();
if (parameters.length == declaredParameters.size()) { // an array could be passed
if (varArgType.isAssignableFrom(passedObjectType)) {
continue;
}
}
if (!varArgType.getInternalType().isAssignableFrom(passedObjectType)) {
throw new BHArgumentsDoNotMatch(method, parameters, declaredParameters, i);
}
} else {
if (!declaredParameters.get(i).getType().isAssignableFrom(passedObjectType)) {
throw new BHArgumentsDoNotMatch(method, parameters, declaredParameters, i);
}
}
}
}
}
private <T> T invokeNonVirtual(@NotNull SNode node, @NotNull SMethod<T> method, Object... parameters) {
checkNotStatic(method);
return invokeNonVirtualCommon(NodeOrConcept.create(node), method, parameters);
}
private <T> T invokeNonVirtual(@NotNull SAbstractConcept concept, @NotNull SMethod<T> method, Object... parameters) {
checkStatic(method);
return invokeNonVirtualCommon(NodeOrConcept.create(concept), method, parameters);
}
private <T> T invokeNonVirtualCommon(@NotNull NodeOrConcept nodeOrConcept, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkForConcept(nodeOrConcept.getConcept());
if (method.getModifiers().isPrivate()) {
if (nodeOrConcept.getNode() != null) {
return invokeSpecial(nodeOrConcept.getNode(), method, parameters);
} else {
return invokeSpecial(nodeOrConcept.getConcept(), method, parameters);
}
}
Iterable<SAbstractConcept> ancestors = myAncestorCache.getAncestorsInvocationOrder();
for (SAbstractConcept ancestor : ancestors) {
BHDescriptor bhDescriptor = getBHDescriptor(ancestor);
if (bhDescriptor instanceof BaseBHDescriptor) {
BaseBHDescriptor baseBHDescriptor = (BaseBHDescriptor) bhDescriptor;
if (baseBHDescriptor.hasDeclaredMethod(method)) {
if (nodeOrConcept.getNode() != null) {
return baseBHDescriptor.invokeSpecial(nodeOrConcept.getNode(), method, parameters);
} else {
return baseBHDescriptor.invokeSpecial(nodeOrConcept.getConcept(), method, parameters);
}
}
} else {
throw new IllegalStateException("Unknown behavior descriptor in the '" + nodeOrConcept.getConcept() + "' ancestor tree : '" + bhDescriptor + "'");
}
}
throw new BHMethodNotFoundException(this, method);
}
private <T> void checkStatic(@NotNull SMethod<T> method) {
if (!method.isStatic()) {
throw new IllegalArgumentException("Method must be static");
}
}
private <T> void checkNotStatic(@NotNull SMethod<T> method) {
if (method.isStatic()) {
throw new IllegalArgumentException("Method must be static");
}
}
private <T> T invokeVirtual(@NotNull SNode operand, @NotNull SMethod<T> method, Object... parameters) {
BaseBHDescriptor baseBHDescriptor = findDescriptorByVirtualMethod(method, false);
return baseBHDescriptor.invokeSpecial(operand, method, parameters);
}
private <T> T invokeVirtual(@NotNull SAbstractConcept operand, @NotNull SMethod<T> method, Object... parameters) {
BaseBHDescriptor baseBHDescriptor = findDescriptorByVirtualMethod(method, false);
return baseBHDescriptor.invokeSpecial(operand, method, parameters);
}
private <T> T invokeVirtualSuper(SNode operand, SMethod<T> method, Object... parameters) {
BaseBHDescriptor baseBHDescriptor = findDescriptorByVirtualMethod(method, true);
return baseBHDescriptor.invokeSpecial(operand, method, parameters);
}
private <T> T invokeVirtualSuper(SAbstractConcept operand, SMethod<T> method, Object... parameters) {
BaseBHDescriptor baseBHDescriptor = findDescriptorByVirtualMethod(method, true);
return baseBHDescriptor.invokeSpecial(operand, method, parameters);
}
@NotNull
private <T> BaseBHDescriptor findDescriptorByVirtualMethod(SMethod<T> method, boolean superOnly) {
assert method.isVirtual();
BHDescriptor bhDescriptor = superOnly ? mySuperVTable.get(method)
: myVTable.get(method);
if (bhDescriptor == null) {
throw new BHMethodNotFoundException(this, method);
}
assert bhDescriptor instanceof BaseBHDescriptor;
return (BaseBHDescriptor) bhDescriptor;
}
@Override
public <T> T invokeSpecial(@NotNull SNode operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkNotStatic(method);
checkForConcept(operand.getConcept());
@Nullable Object[] parametersArray = getParametersArray(method.getParameters(), parameters);
if (parametersArray != null) {
checkParameters(method, parametersArray);
}
return invokeSpecial0(operand, method, parametersArray);
}
@Override
public <T> T invokeSpecial(@NotNull SAbstractConcept operand, @NotNull SMethod<T> method, Object... parameters) {
checkInitialized();
checkStatic(method);
checkForConcept(operand);
@Nullable Object[] parametersArray = getParametersArray(method.getParameters(), parameters);
if (parametersArray != null) {
checkParameters(method, parametersArray);
}
return invokeSpecial0(operand, method, parametersArray);
}
@Nullable
@Override
public SMethod<?> getMethod(@NotNull SMethodId methodId) {
List<SMethod<?>> methods = getMethods();
for (SMethod<?> method : methods) {
if (method.getId().equals(methodId)) {
return method;
}
}
return null;
}
@NotNull
@Override
public List<SMethod<?>> getMethods() {
Set<SMethod<?>> result = new HashSet<SMethod<?>>();
for (SAbstractConcept concept : myAncestorCache.getAncestorsConstructionOrder()) {
BHDescriptor bhDescriptor = getBHDescriptor(concept);
List<SMethod<?>> conceptMethods = bhDescriptor.getDeclaredMethods();
for (SMethod<?> method : conceptMethods) {
if (method.getModifiers().isPublic() && !method.getModifiers().isVirtual()) {
result.add(method);
}
}
}
for (SMethod<?> virtualMethod : myVTable.getMethods()) {
result.add(virtualMethod);
}
return new ArrayList<SMethod<?>>(result);
}
/**
* @generated : listing all the declared methods
* NB: must be fast
**/
@NotNull
@Override
public abstract List<SMethod<?>> getDeclaredMethods();
/**
* @param node -- the new node to initialize
* @param constructor -- constructor to invoke
* @param parameters -- parameters to pass to the constructor
* @generated : switch by constructor; invoking without calling supers
*/
protected abstract void initNode(@NotNull SNode node, @NotNull SConstructor constructor, @Nullable Object[] parameters);
/**
* invokes a method without dynamic resolution
*
* @param parameters is an array of arguments.
* NB: in the case of the last var arg parameter, the last array member is actually packed into another array
* @throws BHMethodNotFoundException if the method has not been found
* @generated : switch by the method; direct invocation in each case
**/
protected abstract <T> T invokeSpecial0(@NotNull SNode node, @NotNull SMethod<T> method, @Nullable Object[] parameters);
/**
* invokes a static method without dynamic resolution
*
* @throws BHMethodNotFoundException if the method has not been found
* @generated : switch by the method; direct invocation in each case
**/
protected abstract <T> T invokeSpecial0(@NotNull SAbstractConcept concept, @NotNull SMethod<T> method, @Nullable Object[] parameters);
/**
* @return true iff the method exists (constructor is not a method here)
**/
private <T> boolean hasDeclaredMethod(@NotNull SMethod<T> method) {
return getDeclaredMethods().contains(method);
}
@Override
public String toString() {
return getConcept() + " BHDescriptor";
}
private final class ConstructionHandler {
private final AncestorCache myAncestorCache;
private final SAbstractConcept myConcept;
public ConstructionHandler(AncestorCache ancestorCache, SAbstractConcept concept) {
myAncestorCache = ancestorCache;
myConcept = concept;
}
public void initNode(@NotNull SNode node, @NotNull SConstructor constructor, @Nullable Object[] parameters) {
//Qualified name is used just because we have instances of interfaces, and instance.getConcept() returns SConcept.
//This should be considered a hack and removed when possible
assert myConcept.getQualifiedName().equals(node.getConcept().getQualifiedName()) : "myConcept=" + myConcept + "; node.concept=" + node.getConcept();
for (SAbstractConcept ancestor : myAncestorCache.getAncestorsConstructionOrder()) {
BHDescriptor ancestorDescriptor = BaseBHDescriptor.this.getBHDescriptor(ancestor);
if (ancestorDescriptor instanceof BaseBHDescriptor) {
((BaseBHDescriptor) ancestorDescriptor).initNode(node, constructor, parameters);
}
}
}
}
private class BHMethodArgumentsCountDoNotMatch extends RuntimeException {
public BHMethodArgumentsCountDoNotMatch(SMethod method, int length) {
super("Method " + method + " has " + method.getParameters().size() + " parameters in the declaration while " + length + " have been passed");
}
}
private class BHArgumentsDoNotMatch extends RuntimeException {
public BHArgumentsDoNotMatch(SMethod<?> method, Object[] parameters, List<SParameter> sParameters, int i) {
super("The parameter " + parameters[i] + " of the type " + parameters[i].getClass() + " does not match " + sParameters.get(i) +
" while calling " + method + " in the " + BaseBHDescriptor.this + " descriptor");
}
}
private class BHNotInitializedException extends RuntimeException {
public BHNotInitializedException(SAbstractConcept concept) {
super("Behavior descriptor has not been initialized; concept : " + concept);
}
}
}