package org.bimserver.shared.meta;
/******************************************************************************
* Copyright (C) 2009-2014 BIMserver.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import org.bimserver.shared.exceptions.ServiceException;
import org.bimserver.shared.interfaces.PublicInterface;
import org.bimserver.shared.json.ReflectorException;
import org.bimserver.shared.reflector.KeyValuePair;
import org.bimserver.shared.reflector.Reflector;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
public class SMethod {
private static final Logger LOGGER = LoggerFactory.getLogger(SMethod.class);
private String doc = "";
private final List<SParameter> parameters = new ArrayList<SParameter>();
private final Method method;
private SClass returnType;
private SClass genericReturnType;
private String returnDoc;
private String name;
private SService service;
@SuppressWarnings("rawtypes")
public SMethod(SService service, Method method) {
this.service = service;
this.method = method;
WebMethod webMethod = method.getAnnotation(WebMethod.class);
if (webMethod == null) {
this.name = method.getName();
LOGGER.warn("Method " + method.getName() + " has no @WebMethod annotation");
} else {
this.name = webMethod.action();
}
int parameterCounter = 0;
for (Class<?> parameterType : method.getParameterTypes()) {
String paramName = "arg" + parameterCounter;
WebParam webParam = extractAnnotation(parameterCounter, WebParam.class);
if (webParam != null) {
paramName = webParam.name();
} else {
LOGGER.warn("Method " + method.getName() + " parameter " + parameterCounter + " has no @WebParam annotation");
}
Class<?> genericType = getGenericReturnType(parameterCounter);
parameters.add(new SParameter(this, service.getServicesMap().getSType(parameterType.getName()), genericType == null ? null : service.getServicesMap().getSType(genericType.getName()), paramName));
parameterCounter++;
}
this.returnType = service.getServicesMap().getSType(method.getReturnType().getName());
if (method.getReturnType() == List.class || method.getReturnType() == Set.class) {
Type genericReturnType = method.getGenericReturnType();
ParameterizedType parameterizedType = (ParameterizedType)genericReturnType;
Type type = parameterizedType.getActualTypeArguments()[0];
if (type instanceof Class) {
this.genericReturnType = service.getServicesMap().getSType(((Class)type).getName());
} else if (type instanceof GenericArrayTypeImpl) {
// Somehow this only happens on Java 6 JVMs
GenericArrayTypeImpl genericArrayTypeImpl = (GenericArrayTypeImpl)type;
this.genericReturnType = service.getServicesMap().getSType(((Class)genericArrayTypeImpl.getGenericComponentType()).getName());
}
}
}
@SuppressWarnings("unchecked")
private <T extends Annotation> T extractAnnotation(int parameterIndex, Class<T> annotationClass) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Annotation[] annotations = parameterAnnotations[parameterIndex];
for (Annotation annotation : annotations) {
if (annotationClass.isInstance(annotation)) {
return (T) annotation;
}
}
return null;
}
private Class<?> getGenericReturnType(int parameterCounter) {
Class<?> parameterType = null;
Type genericReturnType = method.getGenericParameterTypes()[parameterCounter];
if (genericReturnType instanceof ParameterizedType) {
ParameterizedType parameterizedTypeImpl = (ParameterizedType)genericReturnType;
Type type2 = parameterizedTypeImpl.getActualTypeArguments()[0];
parameterType = ((Class<?>)type2);
}
return parameterType;
}
public String getName() {
return name;
}
public SParameter getParameter(int index) {
return parameters.get(index);
}
public Method getMethod() {
return method;
}
public SClass getReturnType() {
return returnType;
}
public List<SParameter> getParameters() {
return parameters;
}
public boolean returnsVoid() {
return method.getReturnType() == Void.class || method.getReturnType() == void.class;
}
public boolean isAggregateReturnType() {
return List.class.isAssignableFrom(method.getReturnType()) || Set.class.isAssignableFrom(method.getReturnType());
}
public SClass getGenericReturnType() {
return genericReturnType;
}
public SClass getBestReturnType() {
return genericReturnType != null ? genericReturnType : returnType;
}
public String returnTypeToJavaCode() {
StringBuilder sb = new StringBuilder();
if (getGenericReturnType() != null) {
sb.append(getReturnType().toJavaCode());
sb.append("<");
sb.append(getGenericReturnType().toJavaCode());
sb.append(">");
} else {
sb.append(getReturnType().toJavaCode());
}
return sb.toString();
}
public String signatureToJavaCode() {
StringBuilder sb = new StringBuilder();
for (SParameter parameter : getParameters()) {
sb.append((parameter.getGenericType() != null ? (parameter.getGenericType().toJavaCode() + "<" + parameter.getType() + ">") : parameter.getType().toJavaCode()) + parameter.getName());
if (!parameter.isLast()) {
sb.append(", ");
}
}
return sb.toString();
}
public boolean isListReturnType() {
return List.class.isAssignableFrom(method.getReturnType());
}
public String getPrintableName() {
String r = getReturnType().getPrintableName();
if (getGenericReturnType() != null) {
r += "<" + getGenericReturnType().getPrintableName() + ">";
}
return r;
}
public SParameter getParameter(String name) {
for (SParameter parameter : this.parameters) {
if (parameter.getName().equals(name)) {
return parameter;
}
}
return null;
}
public void setDoc(String doc) {
this.doc = doc;
}
public String getDoc() {
return doc;
}
public void setReturnDoc(String returnDoc) {
this.returnDoc = returnDoc;
}
public String getReturnDoc() {
return returnDoc;
}
public <T extends PublicInterface, K extends PublicInterface> Object invoke(Class<K> clazz, T service, KeyValuePair[] keyValuePairs) throws ServiceException, ReflectorException {
Reflector reflector = this.service.getServicesMap().getReflectorFactory().createReflector(clazz, service);
return reflector.callMethod(clazz.getName(), getName(), getReturnType().getInstanceClass(), keyValuePairs);
}
public SService getService() {
return service;
}
public JSONObject toJson() throws JSONException {
JSONObject methodJson = new JSONObject();
methodJson.put("name", getName());
methodJson.put("doc", getDoc());
methodJson.put("returnDoc", getReturnDoc());
JSONArray parametersJson = new JSONArray();
methodJson.put("parameters", parametersJson);
for (SParameter parameter : parameters) {
parametersJson.put(parameter.toJson());
}
return methodJson;
}
}