| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.heron.eco.builder; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.apache.heron.eco.definition.ConfigurationMethodDefinition; |
| import org.apache.heron.eco.definition.EcoExecutionContext; |
| import org.apache.heron.eco.definition.ObjectDefinition; |
| |
| public class ObjectBuilder { |
| private static final Logger LOG = LoggerFactory.getLogger(ObjectBuilder.class); |
| |
| private BuilderUtility builderUtility; |
| |
| public void setBuilderUtility(BuilderUtility builderUtility) { |
| this.builderUtility = builderUtility; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| public Object buildObject(ObjectDefinition def, EcoExecutionContext context) |
| throws ClassNotFoundException, IllegalAccessException, InstantiationException, |
| InvocationTargetException, NoSuchFieldException { |
| Class clazz = builderUtility.classForName(def.getClassName()); |
| |
| Object obj; |
| if (def.hasConstructorArgs()) { |
| LOG.debug("Found constructor arguments in definition "); |
| List<Object> cArgs = def.getConstructorArgs(); |
| |
| if (def.hasReferences()) { |
| LOG.debug("The definition has references"); |
| cArgs = builderUtility.resolveReferences(cArgs, context); |
| } else { |
| LOG.debug("The definition does not have references"); |
| } |
| LOG.debug("finding compatible constructor for : " + clazz.getName()); |
| Constructor con = findCompatibleConstructor(cArgs, clazz); |
| if (con != null) { |
| LOG.debug("Found something seemingly compatible, attempting invocation..."); |
| obj = con.newInstance(getArgsWithListCoercian(cArgs, con.getParameterTypes())); |
| } else { |
| String msg = String |
| .format("Couldn't find a suitable constructor for class '%s' with arguments '%s'.", |
| clazz.getName(), |
| cArgs); |
| throw new IllegalArgumentException(msg); |
| } |
| } else { |
| obj = clazz.newInstance(); |
| } |
| builderUtility.applyProperties(def, obj, context); |
| invokeConfigMethods(def, obj, context); |
| return obj; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected Constructor findCompatibleConstructor(List<Object> args, Class target) { |
| Constructor retval = null; |
| int eligibleCount = 0; |
| |
| LOG.debug("Target class: " + target.getName() + ", constructor args: " + args); |
| Constructor[] cons = target.getDeclaredConstructors(); |
| |
| for (Constructor con : cons) { |
| Class[] paramClasses = con.getParameterTypes(); |
| |
| if (paramClasses.length == args.size()) { |
| LOG.debug("found constructor with same number of args.."); |
| boolean invokable = canInvokeWithArgs(args, con.getParameterTypes()); |
| if (invokable) { |
| retval = con; |
| eligibleCount++; |
| } |
| LOG.debug("** invokable --> {}" + invokable); |
| } else { |
| LOG.debug("Skipping constructor with wrong number of arguments."); |
| } |
| } |
| if (eligibleCount > 1) { |
| LOG.error("Found multiple invokable constructors for class: " |
| + target + ", given arguments " + args + ". Using the last one found."); |
| } |
| return retval; |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| protected boolean canInvokeWithArgs(List<Object> args, Class[] parameterTypes) { |
| if (parameterTypes.length != args.size()) { |
| LOG.warn("parameter types were the wrong size"); |
| return false; |
| } |
| |
| for (int i = 0; i < args.size(); i++) { |
| Object obj = args.get(i); |
| if (obj == null) { |
| throw new IllegalArgumentException("argument shouldn't be null - index: " + i); |
| } |
| Class paramType = parameterTypes[i]; |
| Class objectType = obj.getClass(); |
| LOG.debug("Comparing parameter class " + paramType + " to object class " |
| + objectType + "to see if assignment is possible."); |
| if (paramType.equals(objectType)) { |
| LOG.debug("Yes, they are the same class."); |
| } else if (paramType.isAssignableFrom(objectType)) { |
| LOG.debug("Yes, assignment is possible."); |
| } else if (isPrimitiveBoolean(paramType) && Boolean.class.isAssignableFrom(objectType)) { |
| LOG.debug("Yes, assignment is possible."); |
| } else if (isPrimitiveNumber(paramType) && Number.class.isAssignableFrom(objectType)) { |
| LOG.debug("Yes, assignment is possible."); |
| } else if (paramType.isEnum() && objectType.equals(String.class)) { |
| LOG.debug("Yes, will convert a String to enum"); |
| } else if (paramType.isArray() && List.class.isAssignableFrom(objectType)) { |
| LOG.debug("Assignment is possible if we convert a List to an array."); |
| LOG.debug("Array Type: " + paramType.getComponentType() + ", List type: " |
| + ((List) obj).get(0).getClass()); |
| } else { |
| LOG.debug("returning false"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected boolean isPrimitiveNumber(Class clazz) { |
| return clazz.isPrimitive() && !clazz.equals(boolean.class); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| protected boolean isPrimitiveBoolean(Class clazz) { |
| return clazz.isPrimitive() && clazz.equals(boolean.class); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| public void invokeConfigMethods(ObjectDefinition bean, |
| Object instance, EcoExecutionContext context) |
| throws InvocationTargetException, IllegalAccessException { |
| |
| List<ConfigurationMethodDefinition> methodDefs = bean.getConfigMethods(); |
| if (methodDefs == null || methodDefs.size() == 0) { |
| return; |
| } |
| Class clazz = instance.getClass(); |
| for (ConfigurationMethodDefinition methodDef : methodDefs) { |
| List<Object> args = methodDef.getArgs(); |
| if (args == null) { |
| args = new ArrayList<Object>(); |
| } |
| if (methodDef.hasReferences()) { |
| args = builderUtility.resolveReferences(args, context); |
| } |
| String methodName = methodDef.getName(); |
| LOG.debug("method name: " + methodName); |
| Method method = findCompatibleMethod(args, clazz, methodName); |
| if (method != null) { |
| Object[] methodArgs = getArgsWithListCoercian(args, method.getParameterTypes()); |
| method.invoke(instance, methodArgs); |
| } else { |
| String msg = String |
| .format("Unable to find configuration method '%s' in class '%s' with arguments %s.", |
| new Object[]{methodName, clazz.getName(), args}); |
| throw new IllegalArgumentException(msg); |
| } |
| } |
| } |
| |
| @SuppressWarnings("rawtypes") |
| private Method findCompatibleMethod(List<Object> args, Class target, String methodName) { |
| Method retval = null; |
| int eligibleCount = 0; |
| LOG.debug("Target class: " + target.getName() + ", methodName: " |
| + methodName + ", args: " + args); |
| Method[] methods = target.getMethods(); |
| LOG.debug("methods count: " + methods.length); |
| for (Method method : methods) { |
| Class[] paramClasses = method.getParameterTypes(); |
| if (paramClasses.length == args.size() && method.getName().equals(methodName)) { |
| LOG.debug("found constructor with same number of args.."); |
| boolean invokable = false; |
| if (args.size() == 0) { |
| // it's a method with zero args |
| invokable = true; |
| } else { |
| invokable = canInvokeWithArgs(args, method.getParameterTypes()); |
| } |
| if (invokable) { |
| retval = method; |
| eligibleCount++; |
| } |
| LOG.debug("** invokable --> " + invokable); |
| } else { |
| LOG.debug("Skipping method with wrong number of arguments."); |
| } |
| } |
| if (eligibleCount > 1) { |
| LOG.warn("Found multiple invokable methods for class, method, given arguments {} " |
| + new Object[]{target, methodName, args}); |
| } |
| return retval; |
| } |
| |
| |
| |
| /** |
| * Given a java.util.List of contructor/method arguments, and a list of parameter types, |
| * attempt to convert the |
| * list to an java.lang.Object array that can be used to invoke the constructor. |
| * If an argument needs |
| * to be coerced from a List to an Array, do so. |
| */ |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private Object[] getArgsWithListCoercian(List<Object> args, Class[] parameterTypes) { |
| // Class[] parameterTypes = constructor.getParameterTypes(); |
| if (parameterTypes.length != args.size()) { |
| throw new IllegalArgumentException("Contructor parameter count does not " |
| + "egual argument size."); |
| } |
| Object[] constructorParams = new Object[args.size()]; |
| |
| // loop through the arguments, if we hit a list that has to be convered to an array, |
| // perform the conversion |
| for (int i = 0; i < args.size(); i++) { |
| Object obj = args.get(i); |
| Class paramType = parameterTypes[i]; |
| Class objectType = obj.getClass(); |
| LOG.debug("Comparing parameter class " + paramType.getName() + " to object class " |
| + objectType.getName() + " to see if assignment is possible."); |
| if (paramType.equals(objectType)) { |
| LOG.debug("They are the same class."); |
| constructorParams[i] = args.get(i); |
| continue; |
| } |
| if (paramType.isAssignableFrom(objectType)) { |
| LOG.debug("Assignment is possible."); |
| constructorParams[i] = args.get(i); |
| continue; |
| } |
| if (isPrimitiveBoolean(paramType) && Boolean.class.isAssignableFrom(objectType)) { |
| LOG.debug("Its a primitive boolean."); |
| Boolean bool = (Boolean) args.get(i); |
| constructorParams[i] = bool.booleanValue(); |
| continue; |
| } |
| if (isPrimitiveNumber(paramType) && Number.class.isAssignableFrom(objectType)) { |
| LOG.debug("Its a primitive number."); |
| Number num = (Number) args.get(i); |
| if (paramType == Float.TYPE) { |
| constructorParams[i] = num.floatValue(); |
| } else if (paramType == Double.TYPE) { |
| constructorParams[i] = num.doubleValue(); |
| } else if (paramType == Long.TYPE) { |
| constructorParams[i] = num.longValue(); |
| } else if (paramType == Integer.TYPE) { |
| constructorParams[i] = num.intValue(); |
| } else if (paramType == Short.TYPE) { |
| constructorParams[i] = num.shortValue(); |
| } else if (paramType == Byte.TYPE) { |
| constructorParams[i] = num.byteValue(); |
| } else { |
| constructorParams[i] = args.get(i); |
| } |
| continue; |
| } |
| |
| // enum conversion |
| if (paramType.isEnum() && objectType.equals(String.class)) { |
| LOG.debug("Yes, will convert a String to enum"); |
| constructorParams[i] = Enum.valueOf(paramType, (String) args.get(i)); |
| continue; |
| } |
| |
| // List to array conversion |
| if (paramType.isArray() && List.class.isAssignableFrom(objectType)) { |
| LOG.debug("Conversion appears possible..."); |
| List list = (List) obj; |
| LOG.debug("Array Type: {}, List type: {}" + paramType.getComponentType() |
| + list.get(0).getClass()); |
| |
| // create an array of the right type |
| Object newArrayObj = Array.newInstance(paramType.getComponentType(), list.size()); |
| for (int j = 0; j < list.size(); j++) { |
| Array.set(newArrayObj, j, list.get(j)); |
| |
| } |
| constructorParams[i] = newArrayObj; |
| LOG.debug("After conversion: {}" + constructorParams[i]); |
| } |
| } |
| return constructorParams; |
| } |
| } |