package org.apache.commons.vfs2.util;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs2.FileSystemConfigBuilder;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemManager;
import org.apache.commons.vfs2.FileSystemOptions;
public class DelegatingFileSystemOptionsBuilder {
@SuppressWarnings("unchecked")
private static final Class<String>[] STRING_PARAM = new Class[] { String.class };
private static final Map<String, Class<?>> PRIMATIVE_TO_OBJECT = new TreeMap<>();
private static final Log log = LogFactory.getLog(DelegatingFileSystemOptionsBuilder.class);
private final FileSystemManager manager;
private final Map<String, Map<String, List<Method>>> beanMethods = new TreeMap<>();
static {
PRIMATIVE_TO_OBJECT.put(Void.TYPE.getName(), Void.class);
PRIMATIVE_TO_OBJECT.put(Boolean.TYPE.getName(), Boolean.class);
PRIMATIVE_TO_OBJECT.put(Byte.TYPE.getName(), Byte.class);
PRIMATIVE_TO_OBJECT.put(Character.TYPE.getName(), Character.class);
PRIMATIVE_TO_OBJECT.put(Short.TYPE.getName(), Short.class);
PRIMATIVE_TO_OBJECT.put(Integer.TYPE.getName(), Integer.class);
PRIMATIVE_TO_OBJECT.put(Long.TYPE.getName(), Long.class);
PRIMATIVE_TO_OBJECT.put(Double.TYPE.getName(), Double.class);
PRIMATIVE_TO_OBJECT.put(Float.TYPE.getName(), Float.class);
}
private static final class Context {
private final FileSystemOptions fso;
private final String scheme;
private final String name;
private final Object[] values;
private List<Method> configSetters;
private FileSystemConfigBuilder fileSystemConfigBuilder;
private Context(final FileSystemOptions fso, final String scheme, final String name, final Object[] values) {
this.fso = fso;
this.scheme = scheme;
this.name = name;
this.values = values;
}
}
public DelegatingFileSystemOptionsBuilder(final FileSystemManager manager) {
this.manager = manager;
}
protected FileSystemManager getManager() {
return manager;
}
public void setConfigString(final FileSystemOptions fso, final String scheme, final String name, final String value)
throws FileSystemException {
setConfigStrings(fso, scheme, name, new String[] { value });
}
public void setConfigStrings(final FileSystemOptions fso, final String scheme, final String name,
final String[] values) throws FileSystemException {
final Context ctx = new Context(fso, scheme, name, values);
setValues(ctx);
}
public void setConfigClass(final FileSystemOptions fso, final String scheme, final String name,
final Class<?> className) throws FileSystemException, IllegalAccessException, InstantiationException {
setConfigClasses(fso, scheme, name, new Class[] { className });
}
public void setConfigClasses(final FileSystemOptions fso, final String scheme, final String name,
final Class<?>[] classNames) throws FileSystemException, IllegalAccessException, InstantiationException {
final Object[] values = new Object[classNames.length];
for (int iterClassNames = 0; iterClassNames < values.length; iterClassNames++) {
values[iterClassNames] = classNames[iterClassNames].newInstance();
}
final Context ctx = new Context(fso, scheme, name, values);
setValues(ctx);
}
private void setValues(final Context ctx) throws FileSystemException {
if (!fillConfigSetters(ctx)) {
throw new FileSystemException("vfs.provider/config-key-invalid.error", ctx.scheme, ctx.name);
}
ctx.fileSystemConfigBuilder = getManager().getFileSystemConfigBuilder(ctx.scheme);
final Iterator<Method> iterConfigSetters = ctx.configSetters.iterator();
while (iterConfigSetters.hasNext()) {
final Method configSetter = iterConfigSetters.next();
if (convertValuesAndInvoke(configSetter, ctx)) {
return;
}
}
throw new FileSystemException("vfs.provider/config-value-invalid.error", ctx.scheme, ctx.name, ctx.values);
}
private boolean convertValuesAndInvoke(final Method configSetter, final Context ctx) throws FileSystemException {
final Class<?>[] parameters = configSetter.getParameterTypes();
if (parameters.length < 2) {
return false;
}
if (!parameters[0].isAssignableFrom(FileSystemOptions.class)) {
return false;
}
final Class<?> valueParameter = parameters[1];
Class<?> type;
if (valueParameter.isArray()) {
type = valueParameter.getComponentType();
} else {
if (ctx.values.length > 1) {
return false;
}
type = valueParameter;
}
if (type.isPrimitive()) {
final Class<?> objectType = PRIMATIVE_TO_OBJECT.get(type.getName());
if (objectType == null) {
log.warn(Messages.getString("vfs.provider/config-unexpected-primitive.error", type.getName()));
return false;
}
type = objectType;
}
final Class<? extends Object> valueClass = ctx.values[0].getClass();
if (type.isAssignableFrom(valueClass)) {
invokeSetter(valueParameter, ctx, configSetter, ctx.values);
return true;
}
if (valueClass != String.class) {
log.warn(Messages.getString("vfs.provider/config-unexpected-value-class.error", valueClass.getName(),
ctx.scheme, ctx.name));
return false;
}
final Object convertedValues = Array.newInstance(type, ctx.values.length);
Constructor<?> valueConstructor;
try {
valueConstructor = type.getConstructor(STRING_PARAM);
} catch (final NoSuchMethodException e) {
valueConstructor = null;
}
if (valueConstructor != null) {
for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) {
try {
Array.set(convertedValues, iterValues,
valueConstructor.newInstance(new Object[] { ctx.values[iterValues] }));
} catch (final InstantiationException e) {
throw new FileSystemException(e);
} catch (final IllegalAccessException e) {
throw new FileSystemException(e);
} catch (final InvocationTargetException e) {
throw new FileSystemException(e);
}
}
invokeSetter(valueParameter, ctx, configSetter, convertedValues);
return true;
}
Method valueFactory;
try {
valueFactory = type.getMethod("valueOf", STRING_PARAM);
if (!Modifier.isStatic(valueFactory.getModifiers())) {
valueFactory = null;
}
} catch (final NoSuchMethodException e) {
valueFactory = null;
}
if (valueFactory != null) {
for (int iterValues = 0; iterValues < ctx.values.length; iterValues++) {
try {
Array.set(convertedValues, iterValues,
valueFactory.invoke(null, new Object[] { ctx.values[iterValues] }));
} catch (final IllegalAccessException e) {
throw new FileSystemException(e);
} catch (final InvocationTargetException e) {
throw new FileSystemException(e);
}
}
invokeSetter(valueParameter, ctx, configSetter, convertedValues);
return true;
}
return false;
}
private void invokeSetter(final Class<?> valueParameter, final Context ctx, final Method configSetter,
final Object values) throws FileSystemException {
Object[] args;
if (valueParameter.isArray()) {
args = new Object[] { ctx.fso, values };
} else {
args = new Object[] { ctx.fso, Array.get(values, 0) };
}
try {
configSetter.invoke(ctx.fileSystemConfigBuilder, args);
} catch (final IllegalAccessException e) {
throw new FileSystemException(e);
} catch (final InvocationTargetException e) {
throw new FileSystemException(e);
}
}
private boolean fillConfigSetters(final Context ctx) throws FileSystemException {
final Map<String, List<Method>> schemeMethods = getSchemeMethods(ctx.scheme);
final List<Method> configSetters = schemeMethods.get(ctx.name.toLowerCase());
if (configSetters == null) {
return false;
}
ctx.configSetters = configSetters;
return true;
}
private Map<String, List<Method>> getSchemeMethods(final String scheme) throws FileSystemException {
Map<String, List<Method>> schemeMethods = beanMethods.get(scheme);
if (schemeMethods == null) {
schemeMethods = createSchemeMethods(scheme);
beanMethods.put(scheme, schemeMethods);
}
return schemeMethods;
}
private Map<String, List<Method>> createSchemeMethods(final String scheme) throws FileSystemException {
final FileSystemConfigBuilder fscb = getManager().getFileSystemConfigBuilder(scheme);
FileSystemException.requireNonNull(fscb, "vfs.provider/no-config-builder.error", scheme);
final Map<String, List<Method>> schemeMethods = new TreeMap<>();
final Method[] methods = fscb.getClass().getMethods();
for (final Method method : methods) {
if (!Modifier.isPublic(method.getModifiers())) {
continue;
}
final String methodName = method.getName();
if (!methodName.startsWith("set")) {
continue;
}
final String key = methodName.substring(3).toLowerCase();
List<Method> configSetter = schemeMethods.get(key);
if (configSetter == null) {
configSetter = new ArrayList<>(2);
schemeMethods.put(key, configSetter);
}
configSetter.add(method);
}
return schemeMethods;
}
}