/*
* 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 gobblin.util.reflection;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.ConstructorUtils;
/**
* Helper methods to instantiate classes
*/
@Slf4j
public class GobblinConstructorUtils {
/**
* Convenience method on top of {@link ConstructorUtils#invokeConstructor(Class, Object[])} that returns a new
* instance of the <code>cls</code> based on a constructor priority order. Each {@link List} in the
* <code>constructorArgs</code> array contains the arguments for a constructor of <code>cls</code>. The first
* constructor whose signature matches the argument types will be invoked.
*
* @param cls the class to be instantiated
* @param constructorArgs An array of constructor argument list. Order defines the priority of a constructor.
* @return
*
* @throws NoSuchMethodException if no constructor matched was found
*/
@SafeVarargs
public static <T> T invokeFirstConstructor(Class<T> cls, List<Object>... constructorArgs)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
for (List<Object> args : constructorArgs) {
Class<?>[] parameterTypes = new Class[args.size()];
for (int i = 0; i < args.size(); i++) {
parameterTypes[i] = args.get(i).getClass();
}
if (ConstructorUtils.getMatchingAccessibleConstructor(cls, parameterTypes) != null) {
return ConstructorUtils.invokeConstructor(cls, args.toArray(new Object[args.size()]));
}
}
throw new NoSuchMethodException("No accessible constructor found");
}
/**
* Returns a new instance of the <code>cls</code> based on a set of arguments. The method will search for a
* constructor accepting the first k arguments in <code>args</code> for every k from args.length to 0, and will
* invoke the first constructor found.
*
* For example, {@link #invokeLongestConstructor}(cls, myString, myInt) will first attempt to create an object with
* of class <code>cls</code> with constructor <init>(String, int), if it fails it will attempt <init>(String), and
* finally <init>().
*
* @param cls the class to instantiate.
* @param args the arguments to use for instantiation.
* @throws ReflectiveOperationException
*/
public static <T> T invokeLongestConstructor(Class<T> cls, Object... args) throws ReflectiveOperationException {
Class<?>[] parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
parameterTypes[i] = args[i].getClass();
}
for (int i = args.length; i >= 0; i--) {
if (ConstructorUtils.getMatchingAccessibleConstructor(cls, Arrays.copyOfRange(parameterTypes, 0, i)) != null) {
log.info(
String.format("Found accessible constructor for class %s with parameter types %s.", cls,
Arrays.toString(Arrays.copyOfRange(parameterTypes, 0, i))));
return ConstructorUtils.invokeConstructor(cls, Arrays.copyOfRange(args, 0, i));
}
}
throw new NoSuchMethodException(String.format("No accessible constructor for class %s with parameters a subset of %s.",
cls, Arrays.toString(parameterTypes)));
}
/**
* Utility method to create an instance of <code>clsName</code> using the constructor matching the arguments, <code>args</code>
*
* @param superType of <code>clsName</code>. The new instance is cast to superType
* @param clsName complete cannonical name of the class to be instantiated
* @param args constructor args to be used
*
* @throws IllegalArgumentException if there was an issue creating the instance due to
* {@link NoSuchMethodException}, {@link InvocationTargetException},{@link InstantiationException},
* {@link ClassNotFoundException}
*
* @return A new instance of <code>clsName</code>
*/
@SuppressWarnings("unchecked")
public static <T> T invokeConstructor(final Class<T> superType, final String clsName, Object... args) {
try {
return (T) ConstructorUtils.invokeConstructor(Class.forName(clsName), args);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException
| ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}