/* * Copyright (C) 2011 Red Hat, Inc. and/or its affiliates. * * 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 org.jboss.errai.codegen; import java.util.HashMap; import java.util.Map; import org.jboss.errai.codegen.builder.callstack.CallWriter; import org.jboss.errai.codegen.literal.TypeLiteral; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaClassFactory; import org.jboss.errai.codegen.meta.MetaMethod; import org.jboss.errai.codegen.meta.MetaParameterizedType; import org.jboss.errai.codegen.meta.MetaType; import org.jboss.errai.codegen.meta.MetaTypeVariable; import org.jboss.errai.codegen.meta.MetaWildcardType; import org.mvel2.util.NullType; /** * Represents a method invocation statement. * * @author Christian Sadilek <csadilek@redhat.com> * @author Mike Brock */ public class MethodInvocation extends AbstractStatement { private final MetaClass inputType; private final MetaMethod method; private final CallParameters callParameters; private Map<String, MetaClass> typeVariables; private final CallWriter writer; public MethodInvocation(final CallWriter writer, final MetaClass inputType, final MetaMethod method, final CallParameters callParameters) { this.inputType = inputType; this.method = method; this.callParameters = callParameters; this.writer = writer; } String generatedCache; @Override public String generate(final Context context) { if (generatedCache != null) return generatedCache; return generatedCache = method.getName().concat(callParameters.generate(context)); } @Override public MetaClass getType() { MetaClass returnType = method.getReturnType(); if (method.getGenericReturnType() != null && method.getGenericReturnType() instanceof MetaTypeVariable) { typeVariables = new HashMap<String, MetaClass>(); resolveTypeVariables(); final MetaTypeVariable typeVar = (MetaTypeVariable) method.getGenericReturnType(); if (typeVariables.containsKey(typeVar.getName())) { returnType = typeVariables.get(typeVar.getName()); } else if (writer.getTypeParm(typeVar.getName()) != null) { returnType = writer.getTypeParm(typeVar.getName()); } else { // returning NullType as a stand-in for an unbounded wildcard type since this is a parameterized method // and there is not RHS qualification for the parameter. // // ie when calling GWT.create() and assigning it to a concrete type. // // TODO: might be worth flushing this out for clarify in the future. return MetaClassFactory.get(NullType.class); } } assert returnType != null; return returnType; } // Resolves type variables by inspecting call parameters private void resolveTypeVariables() { final MetaParameterizedType gSuperClass = inputType.getGenericSuperClass(); final MetaClass superClass = inputType.getSuperClass(); if (superClass != null && superClass.getTypeParameters() != null && superClass.getTypeParameters().length > 0 && gSuperClass != null && gSuperClass.getTypeParameters().length > 0) { for (int i = 0; i < superClass.getTypeParameters().length; i++) { final String varName = superClass.getTypeParameters()[i].getName(); if (gSuperClass.getTypeParameters()[i] instanceof MetaClass) { typeVariables.put(varName, (MetaClass) gSuperClass.getTypeParameters()[i]); } else if (gSuperClass.getTypeParameters()[i] instanceof MetaWildcardType) { typeVariables.put(varName, MetaClassFactory.get(Object.class)); } else { final MetaClass clazz = writer.getTypeParm(varName); if (clazz != null) { typeVariables.put(varName, clazz); } } } } int methodParmIndex = 0; for (final MetaType methodParmType : method.getGenericParameterTypes()) { final Statement parm = callParameters.getParameters().get(methodParmIndex); final MetaType callParmType; if (parm instanceof TypeLiteral) { callParmType = ((TypeLiteral) parm).getActualType(); } else { callParmType = parm.getType(); } resolveTypeVariable(methodParmType, callParmType); methodParmIndex++; } } private void resolveTypeVariable(final MetaType methodParmType, final MetaType callParmType) { if (methodParmType instanceof MetaTypeVariable) { final MetaTypeVariable typeVar = (MetaTypeVariable) methodParmType; final MetaClass resolvedType; if (callParmType instanceof MetaClass) { resolvedType = (MetaClass) callParmType; } else if (callParmType instanceof MetaWildcardType) { final MetaType[] upperBounds = ((MetaWildcardType) callParmType).getUpperBounds(); if (upperBounds != null && upperBounds.length == 1 && upperBounds[0] instanceof MetaClass) { resolvedType = (MetaClass) upperBounds[0]; } else { // it's either unbounded (? or ? super X) or has fancy bounds like ? extends X & Y // so we'll fall back on good old java.lang.Object resolvedType = MetaClassFactory.get(Object.class); } } else { throw new IllegalArgumentException("Call parameter \"" + callParmType + "\" is of unexpected metatype " + callParmType.getClass()); } typeVariables.put(typeVar.getName(), resolvedType); } else if (methodParmType instanceof MetaParameterizedType) { final MetaType parameterizedCallParmType; if (callParmType instanceof MetaParameterizedType) { parameterizedCallParmType = callParmType; } else { parameterizedCallParmType = ((MetaClass) callParmType).getParameterizedType(); } final MetaParameterizedType parameterizedMethodParmType = (MetaParameterizedType) methodParmType; int typeParmIndex = 0; for (final MetaType typeParm : parameterizedMethodParmType.getTypeParameters()) { if (parameterizedCallParmType != null) { resolveTypeVariable(typeParm, ((MetaParameterizedType) parameterizedCallParmType).getTypeParameters()[typeParmIndex++]); } } } } }