blob: 4761be4f3e3263311ea1f106374cadf4f24cd8b8 [file] [log] [blame]
/*
* 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.iceberg.common;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Map;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.base.Throwables;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
/** Copied from parquet-common */
public class DynConstructors {
private DynConstructors() {}
public static class Ctor<C> extends DynMethods.UnboundMethod {
private final Constructor<C> ctor;
private final Class<? extends C> constructed;
private Ctor(Constructor<C> constructor, Class<? extends C> constructed) {
super(null, "newInstance");
this.ctor = constructor;
this.constructed = constructed;
}
public Class<? extends C> getConstructedClass() {
return constructed;
}
public C newInstanceChecked(Object... args) throws Exception {
try {
if (args.length > ctor.getParameterCount()) {
return ctor.newInstance(Arrays.copyOfRange(args, 0, ctor.getParameterCount()));
} else {
return ctor.newInstance(args);
}
} catch (InstantiationException | IllegalAccessException e) {
throw e;
} catch (InvocationTargetException e) {
Throwables.propagateIfInstanceOf(e.getCause(), Exception.class);
Throwables.propagateIfInstanceOf(e.getCause(), RuntimeException.class);
throw Throwables.propagate(e.getCause());
}
}
public C newInstance(Object... args) {
try {
return newInstanceChecked(args);
} catch (Exception e) {
Throwables.propagateIfInstanceOf(e, RuntimeException.class);
throw Throwables.propagate(e);
}
}
@Override
@SuppressWarnings("unchecked")
public <R> R invoke(Object target, Object... args) {
Preconditions.checkArgument(
target == null, "Invalid call to constructor: target must be null");
return (R) newInstance(args);
}
@Override
@SuppressWarnings("unchecked")
public <R> R invokeChecked(Object target, Object... args) throws Exception {
Preconditions.checkArgument(
target == null, "Invalid call to constructor: target must be null");
return (R) newInstanceChecked(args);
}
@Override
public DynMethods.BoundMethod bind(Object receiver) {
throw new IllegalStateException("Cannot bind constructors");
}
@Override
public boolean isStatic() {
return true;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(constructor=" + ctor + ", class=" + constructed + ")";
}
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(Class<?> baseClass) {
return new Builder(baseClass);
}
public static class Builder {
private final Class<?> baseClass;
private ClassLoader loader = Thread.currentThread().getContextClassLoader();
private Ctor ctor = null;
private Map<String, Throwable> problems = Maps.newHashMap();
public Builder(Class<?> baseClass) {
this.baseClass = baseClass;
}
public Builder() {
this.baseClass = null;
}
/**
* Set the {@link ClassLoader} used to lookup classes by name.
*
* <p>If not set, the current thread's ClassLoader is used.
*
* @param newLoader a ClassLoader
* @return this Builder for method chaining
*/
public Builder loader(ClassLoader newLoader) {
this.loader = newLoader;
return this;
}
public Builder impl(String className, Class<?>... types) {
// don't do any work if an implementation has been found
if (ctor != null) {
return this;
}
try {
Class<?> targetClass = Class.forName(className, true, loader);
impl(targetClass, types);
} catch (NoClassDefFoundError | ClassNotFoundException e) {
// cannot load this implementation
problems.put(className, e);
}
return this;
}
public <T> Builder impl(Class<T> targetClass, Class<?>... types) {
// don't do any work if an implementation has been found
if (ctor != null) {
return this;
}
try {
ctor = new Ctor<T>(targetClass.getConstructor(types), targetClass);
} catch (NoSuchMethodException e) {
// not the right implementation
problems.put(methodName(targetClass, types), e);
}
return this;
}
public Builder hiddenImpl(Class<?>... types) {
hiddenImpl(baseClass, types);
return this;
}
@SuppressWarnings("unchecked")
public Builder hiddenImpl(String className, Class<?>... types) {
// don't do any work if an implementation has been found
if (ctor != null) {
return this;
}
try {
Class targetClass = Class.forName(className, true, loader);
hiddenImpl(targetClass, types);
} catch (NoClassDefFoundError | ClassNotFoundException e) {
// cannot load this implementation
problems.put(className, e);
}
return this;
}
public <T> Builder hiddenImpl(Class<T> targetClass, Class<?>... types) {
// don't do any work if an implementation has been found
if (ctor != null) {
return this;
}
try {
Constructor<T> hidden = targetClass.getDeclaredConstructor(types);
AccessController.doPrivileged(new MakeAccessible(hidden));
ctor = new Ctor<T>(hidden, targetClass);
} catch (SecurityException e) {
// unusable
problems.put(methodName(targetClass, types), e);
} catch (NoSuchMethodException e) {
// not the right implementation
problems.put(methodName(targetClass, types), e);
}
return this;
}
@SuppressWarnings("unchecked")
public <C> Ctor<C> buildChecked() throws NoSuchMethodException {
if (ctor != null) {
return ctor;
}
throw buildCheckedException(baseClass, problems);
}
@SuppressWarnings("unchecked")
public <C> Ctor<C> build() {
if (ctor != null) {
return ctor;
}
throw buildRuntimeException(baseClass, problems);
}
}
private static class MakeAccessible implements PrivilegedAction<Void> {
private Constructor<?> hidden;
MakeAccessible(Constructor<?> hidden) {
this.hidden = hidden;
}
@Override
public Void run() {
hidden.setAccessible(true);
return null;
}
}
private static NoSuchMethodException buildCheckedException(
Class<?> baseClass, Map<String, Throwable> problems) {
NoSuchMethodException exc =
new NoSuchMethodException(
"Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
problems.values().forEach(exc::addSuppressed);
return exc;
}
private static RuntimeException buildRuntimeException(
Class<?> baseClass, Map<String, Throwable> problems) {
RuntimeException exc =
new RuntimeException(
"Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
problems.values().forEach(exc::addSuppressed);
return exc;
}
private static String formatProblems(Map<String, Throwable> problems) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, Throwable> problem : problems.entrySet()) {
if (first) {
first = false;
} else {
sb.append("\n");
}
sb.append("\tMissing ")
.append(problem.getKey())
.append(" [")
.append(problem.getValue().getClass().getName())
.append(": ")
.append(problem.getValue().getMessage())
.append("]");
}
return sb.toString();
}
private static String methodName(Class<?> targetClass, Class<?>... types) {
StringBuilder sb = new StringBuilder();
sb.append(targetClass.getName()).append("(");
boolean first = true;
for (Class<?> type : types) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(type.getName());
}
sb.append(")");
return sb.toString();
}
}