package org.smoothbuild.lang.function.nativ; import static org.smoothbuild.lang.function.nativ.NativeParameterFactory.createParameter; import static org.smoothbuild.lang.type.Types.jTypeToType; import static org.smoothbuild.util.ReflexiveUtils.isPublic; import static org.smoothbuild.util.ReflexiveUtils.isStatic; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import org.smoothbuild.SmoothConstants; import org.smoothbuild.db.hashed.Hash; import org.smoothbuild.lang.function.base.Name; import org.smoothbuild.lang.function.base.Parameter; import org.smoothbuild.lang.function.base.Signature; import org.smoothbuild.lang.plugin.Container; import org.smoothbuild.lang.plugin.NotCacheable; import org.smoothbuild.lang.plugin.SmoothFunction; import org.smoothbuild.lang.type.Type; import org.smoothbuild.task.exec.ContainerImpl; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.hash.HashCode; import com.google.common.hash.Hasher; import com.google.inject.TypeLiteral; public class NativeFunctionFactory { public static Set<NativeFunction> nativeFunctions(Class<?> clazz, HashCode jarHash) throws NativeFunctionImplementationException { HashSet<NativeFunction> result = new HashSet<>(); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(SmoothFunction.class)) { result.add(nativeFunction(method, jarHash)); } } return result; } public static NativeFunction nativeFunction(Method method, HashCode jarHash) throws NativeFunctionImplementationException { if (!isStatic(method)) { throw new NativeFunctionImplementationException(method, "It should be static."); } if (!isPublic(method)) { throw new NativeFunctionImplementationException(method, "It should be public."); } Signature signature = createSignature(method); boolean cacheable = !method.isAnnotationPresent(NotCacheable.class); HashCode hash = createHash(jarHash, signature); return new NativeFunction(method, signature, cacheable, hash); } private static Signature createSignature(Method method) throws NativeFunctionImplementationException { Type returnType = functionType(method); return new Signature(returnType, createName(method), createParameters(method)); } private static ImmutableList<Parameter> createParameters(Method method) throws NativeFunctionImplementationException { Class<?>[] types = method.getParameterTypes(); if (types.length == 0 || (types[0] != Container.class && types[0] != ContainerImpl.class)) { throw new NativeFunctionImplementationException(method, "Its first parameter should have '" + Container.class.getCanonicalName() + "' type."); } java.lang.reflect.Parameter[] parameters = method.getParameters(); Annotation[][] annotations = method.getParameterAnnotations(); Builder<Parameter> builder = ImmutableList.builder(); for (int i = 1; i < parameters.length; i++) { Parameter parameter = createParameter(method, parameters[i], annotations[i]); builder.add(parameter); } return builder.build(); } private static Name createName(Method method) throws NativeFunctionImplementationException { String name = method.getName(); if (Name.isLegalName(name)) { return Name.name(name); } else { throw new NativeFunctionImplementationException(method, "Its name " + name + " is illegal."); } } private static Type functionType(Method functionMethod) throws NativeFunctionImplementationException { TypeLiteral<?> jType = methodJType(functionMethod); Type type = jTypeToType(jType); if (type == null) { throw new NativeFunctionImplementationException(functionMethod, "It has is illegal result type '" + jType + "'."); } return type; } private static TypeLiteral<?> methodJType(Method paramMethod) { Class<?> paramsClass = paramMethod.getDeclaringClass(); return TypeLiteral.get(paramsClass).getReturnType(paramMethod); } private static HashCode createHash(HashCode jarHash, Signature signature) { Hasher hasher = Hash.newHasher(); hasher.putBytes(jarHash.asBytes()); hasher.putString(signature.name().value(), SmoothConstants.CHARSET); return hasher.hash(); } }