/*
* 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.ignite.testframework.configvariations;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.cache.configuration.Factory;
import org.apache.ignite.IgniteException;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.jetbrains.annotations.Nullable;
/**
* Parameters utils.
*/
public class Parameters {
/**
* Private constructor.
*/
private Parameters() {
// No-op.
}
/**
* @return Array of configuration processors for given enum.
*/
@SuppressWarnings("unchecked")
public static <T> ConfigParameter<T>[] enumParameters(String mtdName, Class<?> enumCls) {
return enumParameters(false, mtdName, enumCls);
}
/**
* @return Array of configuration processors for given enum.
*/
@SuppressWarnings("unchecked")
public static <T> ConfigParameter<T>[] enumParameters(boolean withNull, String mtdName, Class<?> enumCls) {
return parameters0(mtdName, withNull, enumCls.getEnumConstants());
}
/**
* @param mtdName Method name.
* @param values Values.
* @return Array of configuration paramethers.
*/
@SuppressWarnings("unchecked")
private static <T> ConfigParameter<T>[] parameters0(String mtdName, boolean withNull, Object[] values) {
for (Object val : values) {
if (!isPrimitiveOrEnum(val) && !(val instanceof Factory))
throw new IllegalArgumentException("Value have to be primite, enum or factory: " + val);
}
if (withNull) {
Object[] valuesWithNull = new Object[values.length + 1];
valuesWithNull[0] = null;
System.arraycopy(values, 0, valuesWithNull, 1, valuesWithNull.length - 1);
values = valuesWithNull;
}
assert values != null && values.length > 0 : "MtdName:" + mtdName;
ConfigParameter<T>[] resArr = new ConfigParameter[values.length];
for (int i = 0; i < resArr.length; i++)
resArr[i] = new ReflectionParameter<>(mtdName, values[i]);
return resArr;
}
/**
* @param val Value.
* @return Primitive or enum or not.
*/
private static boolean isPrimitiveOrEnum(Object val) {
return val.getClass().isPrimitive()
|| val.getClass().equals(Boolean.class)
|| val.getClass().equals(Byte.class)
|| val.getClass().equals(Short.class)
|| val.getClass().equals(Character.class)
|| val.getClass().equals(Integer.class)
|| val.getClass().equals(Long.class)
|| val.getClass().equals(Float.class)
|| val.getClass().equals(Double.class)
|| val.getClass().isEnum();
}
/**
* @return Array of configuration processors for given enum.
*/
@SuppressWarnings("unchecked")
public static <T> ConfigParameter<T>[] booleanParameters(String mtdName) {
return parameters0(mtdName, false, new Boolean[] {true, false});
}
/**
* @return Array of configuration processors for given enum.
*/
@SuppressWarnings("unchecked")
public static <T> ConfigParameter<T>[] booleanParameters(boolean withNull, String mtdName) {
return parameters0(mtdName, withNull, new Boolean[] {true, false});
}
/**
* @param mtdName Method name.
* @param values Values.
* @return Array of configuration processors for given classes.
*/
public static ConfigParameter[] objectParameters(String mtdName, Object... values) {
return objectParameters(false, mtdName, values);
}
/**
* @param mtdName Method name.
* @param values Values.
* @return Array of configuration processors for given classes.
*/
public static ConfigParameter[] objectParameters(boolean withNull, String mtdName, Object... values) {
return parameters0(mtdName, withNull, values);
}
/**
* @param mtdName Method name.
* @param val Value.
* @return Configuration parameter.
*/
public static <T> ConfigParameter<T> parameter(String mtdName, Object val) {
return new ReflectionParameter<>(mtdName, val);
}
/**
* @return Complex parameter.
*/
@SuppressWarnings("unchecked")
public static <T> ConfigParameter<T> complexParameter(ConfigParameter<T>... params) {
return new ComplexParameter<T>(params);
}
/**
* @param cls Class.
* @return Factory that uses default constructor to initiate object by given class.
*/
public static <T> Factory<T> factory(Class<?> cls) {
return new ReflectionFactory<>(cls);
}
/**
* Reflection configuration applier.
*/
@SuppressWarnings("serial")
private static class ReflectionParameter<T> implements ConfigParameter<T> {
/** Classes of marameters cache. */
private static final ConcurrentMap<T2<Class, String>, Class> paramClassesCache = new ConcurrentHashMap();
/** */
private final String mtdName;
/** Primitive, enum or factory. */
private final Object val;
/**
* @param mtdName Method name.
*/
ReflectionParameter(String mtdName, @Nullable Object val) {
if (val != null && !isPrimitiveOrEnum(val) && !(val instanceof Factory))
throw new IllegalArgumentException("Value have to be primite, enum or factory: " + val);
this.mtdName = mtdName;
this.val = val;
}
/** {@inheritDoc} */
@Override public String name() {
String mtdName0 = mtdName;
if (mtdName0.startsWith("set") && mtdName0.length() > 3)
mtdName0 = mtdName0.substring(3, mtdName0.length());
String val0;
if (val == null)
val0 = "null";
else if (val instanceof Factory)
val0 = ((Factory)val).create().toString();
else
val0 = val.toString();
return mtdName0 + "=" + val0;
}
/** {@inheritDoc} */
@Override public T apply(T cfg) {
if (val == null)
return null;
try {
Object val0 = val;
if (!isPrimitiveOrEnum(val))
val0 = ((Factory)val0).create();
Class<?> paramCls = paramClassesCache.get(new T2<Class, String>(cfg.getClass(), mtdName));
if (paramCls == null)
paramCls = val0.getClass();
else if (!paramCls.isInstance(val0))
throw new IgniteException("Class parameter from cache does not match value argument class " +
"[paramCls=" + paramCls + ", val=" + val0 + "]");
if (val0.getClass().equals(Boolean.class))
paramCls = Boolean.TYPE;
else if (val0.getClass().equals(Byte.class))
paramCls = Byte.TYPE;
else if (val0.getClass().equals(Short.class))
paramCls = Short.TYPE;
else if (val0.getClass().equals(Character.class))
paramCls = Character.TYPE;
else if (val0.getClass().equals(Integer.class))
paramCls = Integer.TYPE;
else if (val0.getClass().equals(Long.class))
paramCls = Long.TYPE;
else if (val0.getClass().equals(Float.class))
paramCls = Float.TYPE;
else if (val0.getClass().equals(Double.class))
paramCls = Double.TYPE;
Method mtd;
Queue<Class> queue = new ArrayDeque<>();
boolean failed = false;
while (true) {
try {
mtd = cfg.getClass().getMethod(mtdName, paramCls);
if (failed)
paramClassesCache.put(new T2<Class, String>(cfg.getClass(), mtdName), paramCls);
break;
}
catch (NoSuchMethodException e) {
failed = true;
U.warn(null, "Method not found [cfgCls=" + cfg.getClass() + ", mtdName=" + mtdName
+ ", paramCls=" + paramCls + "]");
Class<?>[] interfaces = paramCls.getInterfaces();
Class<?> superclass = paramCls.getSuperclass();
if (superclass != null)
queue.add(superclass);
if (!F.isEmpty(interfaces))
queue.addAll(Arrays.asList(interfaces));
if (queue.isEmpty())
throw new IgniteException("Method not found [cfgCls=" + cfg.getClass() + ", mtdName="
+ mtdName + ", paramCls=" + val0.getClass() + "]", e);
paramCls = queue.remove();
}
}
mtd.invoke(cfg, val0);
}
catch (InvocationTargetException | IllegalAccessException e) {
throw new IgniteException(e);
}
return null;
}
}
/**
*
*/
private static class ReflectionFactory<T> implements Factory<T> {
/** */
private static final long serialVersionUID = 0;
/** */
private Class<?> cls;
/**
* @param cls Class.
*/
ReflectionFactory(Class<?> cls) {
this.cls = cls;
}
/** {@inheritDoc} */
@Override public T create() {
try {
Constructor<?> constructor = cls.getConstructor();
return (T)constructor.newInstance();
}
catch (NoSuchMethodException | InstantiationException | InvocationTargetException |
IllegalAccessException e) {
throw new IgniteException("Failed to create object using default constructor: " + cls, e);
}
}
}
/**
*
*/
private static class ComplexParameter<T> implements ConfigParameter<T> {
/** */
private final String name;
/** */
private ConfigParameter<T>[] params;
/**
* @param params Params
*/
@SafeVarargs
ComplexParameter(ConfigParameter<T>... params) {
A.notEmpty(params, "params");
this.params = params;
if (params.length == 1)
name = params[0].name();
else {
SB sb = new SB(params[0].name());
for (int i = 1; i < params.length; i++)
sb.a('-').a(params[i]);
name = sb.toString();
}
}
/** {@inheritDoc} */
@Override public String name() {
return name;
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override public T apply(T cfg) {
for (ConfigParameter param : params)
param.apply(cfg);
return cfg;
}
}
}