/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
/*
GRANITE DATA SERVICES
Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
This file is part of Granite Data Services.
Granite Data Services is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
Granite Data Services is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, see <http://www.gnu.org/licenses/>.
SLSB: This class and all the modifications to use it are marked with the 'SLSB' tag.
*/
package org.granite.generator.as3.reflect;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.granite.generator.as3.ClientType;
import org.granite.generator.as3.reflect.JavaMethod.MethodType;
import org.granite.generator.util.GenericTypeUtil;
import org.granite.messaging.service.annotations.IgnoredMethod;
import org.granite.messaging.service.annotations.RemoteDestination;
import org.granite.util.ClassUtil;
/**
* @author Franck WOLFF
*/
public class JavaRemoteDestination extends JavaAbstractType {
// /////////////////////////////////////////////////////////////////////////
// Fields.
protected final Set<JavaImport> imports = new HashSet<JavaImport>();
protected final JavaType superclass;
protected final List<JavaMethod> methods;
protected final Map<String, JavaMethodProperty> properties;
protected final String destinationName;
protected final String channelId;
// /////////////////////////////////////////////////////////////////////////
// Constructor.
public JavaRemoteDestination(JavaTypeFactory provider, Class<?> type, URL url) {
super(provider, type, url);
// Find superclass (controller filtered).
this.superclass = provider.getJavaTypeSuperclass(type);
// Collect methods.
this.methods = Collections.unmodifiableList(initMethods());
// Collect bean properties.
this.properties = Collections.unmodifiableMap(initProperties());
// Collect imports.
if (superclass != null)
addToImports(provider.getJavaImport(superclass.getType()));
RemoteDestination rd = type.getAnnotation(RemoteDestination.class);
if (rd != null) {
destinationName = rd.id();
channelId = rd.channel();
}
else {
destinationName = null;
channelId = null;
}
}
// /////////////////////////////////////////////////////////////////////////
// Properties.
public Set<JavaImport> getImports() {
return imports;
}
protected void addToImports(JavaImport javaImport) {
if (javaImport != null)
imports.add(javaImport);
}
protected void addToImports(Set<JavaImport> javaImports) {
if (javaImports != null)
imports.addAll(javaImports);
}
public boolean hasSuperclass() {
return superclass != null;
}
public JavaType getSuperclass() {
return superclass;
}
public Collection<JavaMethod> getMethods() {
return methods;
}
public Collection<JavaMethodProperty> getProperties() {
return properties.values();
}
public String getDestinationName() {
return destinationName;
}
public String getChannelId() {
return channelId;
}
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
return type.isAnnotationPresent(annotation);
}
// /////////////////////////////////////////////////////////////////////////
// Utilities.
protected List<JavaMethod> initMethods() {
List<JavaMethod> methodList = new ArrayList<JavaMethod>();
// Get all methods for interfaces: normally, even if it is possible in Java
// to override a method into a inherited interface, there is no meaning
// to do so (we just ignore potential compilation issues with generated AS3
// classes for this case since it is always possible to remove the method
// re-declaration in the child interface).
Method[] methods = null;
ParameterizedType[] declaringTypes = GenericTypeUtil.getDeclaringTypes(type);
if (type.isInterface())
methods = filterOverrides(type.getMethods(), declaringTypes);
else
methods = type.getDeclaredMethods();
for (Method method : methods) {
if (shouldGenerateMethod(method)) {
JavaMethod javaMethod = new JavaMethod(method, MethodType.OTHER, this.provider, declaringTypes);
for (Class<?> clazz : javaMethod.getParameterTypes()) {
if (clazz.isMemberClass() && !clazz.isEnum()) {
throw new UnsupportedOperationException(
"Inner classes are not supported (except enums): " + clazz
);
}
}
for (ClientType paramType : javaMethod.getClientParameterTypes())
addToImports(provider.getJavaImports(paramType, false));
for (ClientType annotationType : javaMethod.getClientAnnotationTypes())
addToImports(provider.getJavaImports(annotationType, false));
Class<?> clazz = javaMethod.getReturnType();
if (clazz.isMemberClass() && !clazz.isEnum()) {
throw new UnsupportedOperationException(
"Inner classes are not supported (except enums): " + clazz
);
}
if (!clazz.equals(Void.class) && !clazz.equals(void.class))
addToImports(provider.getJavaImports(javaMethod.getClientReturnType(), false));
methodList.add(javaMethod);
}
}
Collections.sort(methodList, new Comparator<JavaMethod>() {
@Override
public int compare(JavaMethod m1, JavaMethod m2) {
if (m1.getName().equals(m2.getName())) {
if (m1.getMember().getDeclaringClass() != m2.getMember().getDeclaringClass()) {
if (m1.getMember().getDeclaringClass().isAssignableFrom(m2.getMember().getDeclaringClass()))
return -1;
if (m2.getMember().getDeclaringClass().isAssignableFrom(m1.getMember().getDeclaringClass()))
return 1;
}
if (m1.getParameterTypes().length < m2.getParameterTypes().length)
return -1;
else if (m1.getParameterTypes().length > m2.getParameterTypes().length)
return 1;
for (int i = 0; i < m1.getParameterTypes().length; i++) {
if (m1.getParameterTypes()[i] == m2.getParameterTypes()[i])
continue;
if (m1.getParameterTypes()[i].isAssignableFrom(m2.getParameterTypes()[i]))
return -1;
else if (m2.getParameterTypes()[i].isAssignableFrom(m1.getParameterTypes()[i]))
return 1;
return m1.getParameterTypes()[i].toString().compareTo(m2.getParameterTypes()[i].toString());
}
}
return m1.getName().compareTo(m2.getName());
}
});
return methodList;
}
protected boolean shouldGenerateMethod(Method method) {
if (!Modifier.isPublic(method.getModifiers())
|| Modifier.isStatic(method.getModifiers())
|| method.isAnnotationPresent(IgnoredMethod.class))
return false;
return true;
}
protected Map<String, JavaMethodProperty> initProperties() {
Map<String, JavaMethodProperty> propertyMap = new LinkedHashMap<String, JavaMethodProperty>();
// Get all methods for interfaces: normally, even if it is possible in Java
// to override a method into a inherited interface, there is no meaning
// to do so (we just ignore potential compilation issues with generated AS3
// classes for this case since it is always possible to remove the method
// re-declaration in the child interface).
Method[] methods = null;
if (type.isInterface())
methods = type.getMethods();
else
methods = type.getDeclaredMethods();
List<Object[]> tmp = new ArrayList<Object[]>();
for (Method method : methods) {
if (shouldGenerateProperty(method)) {
for (Class<?> clazz : method.getParameterTypes())
addToImports(provider.getJavaImport(clazz));
addToImports(provider.getJavaImport(method.getReturnType()));
String propertyName = Introspector.decapitalize(method.getName().startsWith("is") ? method.getName().substring(2) : method.getName().substring(3));
Object[] property = null;
for (Object[] mp : tmp) {
if (mp[0].equals(propertyName)) {
property = mp;
break;
}
}
if (property == null) {
property = new Object[] { propertyName, null, null };
tmp.add(property);
}
if (method.getName().startsWith("set"))
property[2] = method;
else
property[1] = method;
}
}
for (Object[] property : tmp) {
JavaMethod readMethod = property[1] != null ? new JavaMethod((Method)property[1], MethodType.GETTER) : null;
JavaMethod writeMethod = property[2] != null ? new JavaMethod((Method)property[2], MethodType.SETTER) : null;
propertyMap.put((String)property[0], new JavaMethodProperty(provider, (String)property[0], readMethod, writeMethod));
}
return propertyMap;
}
protected boolean shouldGenerateProperty(Method method) {
return false;
}
public JavaInterface convertToJavaInterface() {
return new JavaInterface(getProvider(), getType(), getUrl());
}
public static Method[] filterOverrides(Method[] methods, ParameterizedType[] declaringTypes) {
List<Method> filteredMethods = new ArrayList<Method>();
for (Method method : methods) {
// Apply generic information
Type[] paramTypes = new Type[method.getGenericParameterTypes().length];
for (int i = 0; i < method.getGenericParameterTypes().length; i++)
paramTypes[i] = GenericTypeUtil.resolveTypeVariable(method.getGenericParameterTypes()[i], method.getDeclaringClass(), declaringTypes);
// Lookup an override in subinterfaces
boolean foundOverride = false;
for (Method m : methods) {
if (method == m)
continue;
if (m.getName().equals(method.getName()) && m.getParameterTypes().length == paramTypes.length && method.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) {
boolean same = true;
for (int i = 0; i < paramTypes.length; i++) {
if (!ClassUtil.classOfType(paramTypes[i]).equals(ClassUtil.classOfType(m.getParameterTypes()[i]))) {
same = false;
break;
}
}
if (same) {
foundOverride = true;
break;
}
}
}
if (!foundOverride)
filteredMethods.add(method);
}
return filteredMethods.toArray(new Method[filteredMethods.size()]);
}
}