/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.managed.reflection;
import com.jcwhatever.nucleus.managed.reflection.IReflectedArray;
import com.jcwhatever.nucleus.managed.reflection.IReflectedInstance;
import com.jcwhatever.nucleus.managed.reflection.IReflectedType;
import com.jcwhatever.nucleus.managed.reflection.Reflection;
import com.jcwhatever.nucleus.utils.PreCon;
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.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Internal implementation of {@link IReflectedType}.
*/
class ReflectedType implements IReflectedType {
private final CachedReflectedType _cached;
private final Collection<Constructor<?>> _constructorOutput = new ArrayList<>(5);
private final Collection<Method> _methodOutput = new ArrayList<>(5);
private Map<String, Method> _aliasMethods;
private Map<String, ReflectedField> _aliasFields;
private Map<String, Constructor<?>> _aliasConstructors;
private Map<String, Object> _aliasEnum;
/**
* Constructor.
*
* <p>The reflected types cache.</p>
*/
ReflectedType(CachedReflectedType cached) {
PreCon.notNull(cached);
_cached = cached;
}
/**
* Get the cached reflected type.
*/
public CachedReflectedType getCachedType() {
return _cached;
}
@Override
public ReflectedTypeFields getFields() {
return getFields(Object.class);
}
@Override
public ReflectedTypeFields getFields(Class<?> fieldType) {
PreCon.notNull(fieldType, "fieldType");
return new ReflectedTypeFields(this, _cached.fieldsByType(fieldType));
}
@Override
public ReflectedField getField(String name) {
PreCon.notNullOrEmpty(name, "name");
if (_aliasFields != null) {
ReflectedField field = _aliasFields.get(name);
if (field != null)
return field;
}
ReflectedField field = _cached.fieldByName(name);
if (field == null) {
throw new RuntimeException("Field " + name + " not found in type " + getHandle().getName());
}
return field;
}
@Override
public Object getEnum(String constantName) {
PreCon.notNullOrEmpty(constantName, "constantName");
if (_aliasEnum != null) {
Object constant = _aliasEnum.get(constantName);
if (constant != null)
return constant;
}
return getEnumConstant(constantName);
}
@Override
public Object get(String fieldName) {
ReflectedField field = getField(fieldName);
if (!field.isStatic()) {
throw new RuntimeException("Field " + fieldName + " is not static.");
}
return field.get(null);
}
@Override
public void set(String fieldName, Object value) {
ReflectedField field = getField(fieldName);
if (!field.isStatic()) {
throw new RuntimeException("Field " + fieldName + " is not static.");
}
field.set(null, value);
}
@Override
public Object construct(String alias, Object... arguments) {
PreCon.notNullOrEmpty(alias, "alias");
PreCon.notNull(arguments, "arguments");
if (_aliasConstructors == null)
throw new RuntimeException("No constructor aliases registered.");
Constructor<?> constructor = _aliasConstructors.get(alias);
if (constructor == null)
throw new RuntimeException("Constructor alias not found : " + alias);
try {
return constructor.newInstance(arguments);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException("Failed to instantiate constructor.");
}
}
@Override
public IReflectedInstance constructReflect(String alias, Object... arguments) {
PreCon.notNullOrEmpty(alias, "alias");
PreCon.notNull(arguments, "arguments");
Object instance = construct(alias, arguments);
assert instance != null;
return new ReflectedInstance(this, instance);
}
/**
* Create a new instance of the encapsulated class.
* Searches for the correct constructor based on
* provided arguments.
*
* @param arguments The constructor arguments.
*
* @return The new instance.
*/
public Object newInstance(Object... arguments) {
PreCon.notNull(arguments, "arguments");
_constructorOutput.clear();
Collection<Constructor<?>> constructors = _cached.constructorsByCount(arguments.length, _constructorOutput);
Constructor<?> constructor;
if (constructors.size() == 0) {
throw new RuntimeException("No constructors to instantiate.");
}
else {
constructor = constructors.size() == 1
? constructors.iterator().next()
: Reflection.findConstructorByArgs(constructors, arguments);
}
if (constructor == null)
throw new RuntimeException("Failed to find a matching constructor.");
Object instance;
try {
instance = constructor.newInstance(arguments);
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException("Failed to instantiate constructor.");
}
return instance;
}
/**
* Create a new instance of the encapsulated class.
* Searches for the correct constructor based on
* provided arguments.
*
* @param arguments The constructor arguments.
*
* @return The new instance.
*/
public ReflectedInstance newReflectedInstance(Object... arguments) {
PreCon.notNull(arguments, "arguments");
Object instance = newInstance(arguments);
return new ReflectedInstance(this, instance);
}
@Override
public IReflectedArray newArray(int size) {
PreCon.positiveNumber(size);
Object array = Array.newInstance(_cached.getHandle(), size);
return new ReflectedArray(this, array);
}
@Override
public IReflectedArray newArray(int... sizes) {
PreCon.greaterThanZero(sizes.length, "At least one array dimension must be specified.");
Object array = Array.newInstance(_cached.getHandle(), sizes);
return new ReflectedArray(this, array);
}
@Override
public ReflectedInstance reflect(Object instance) {
PreCon.notNull(instance, "instance");
return new ReflectedInstance(this, instance);
}
@Override
public ReflectedArray reflectArray(Object instance) {
PreCon.notNull(instance, "instance");
return new ReflectedArray(this, instance);
}
@Override
public ReflectedType constructorAlias(String alias, Class<?>... signature) {
PreCon.notNullOrEmpty(alias, "alias");
PreCon.notNull(signature, "signature");
if (_aliasConstructors == null)
_aliasConstructors = new HashMap<>(10);
if (_aliasConstructors.containsKey(alias))
throw new RuntimeException("Constructor alias already registered: " + alias);
Constructor<?> constructor;
try {
constructor = getHandle().getDeclaredConstructor(signature);
constructor.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException("Constructor not found.");
}
constructor.setAccessible(true);
_aliasConstructors.put(alias, constructor);
return this;
}
@Override
public ReflectedType fieldAlias(String fieldAlias, String fieldName) {
PreCon.notNullOrEmpty(fieldAlias, "fieldAlias");
PreCon.notNullOrEmpty(fieldName, "fieldName");
if (_aliasFields == null)
_aliasFields = new HashMap<>(10);
if (_aliasFields.containsKey(fieldAlias))
throw new RuntimeException("Field alias already registered: " + fieldAlias);
ReflectedField field = getField(fieldName);
if (field == null)
throw new RuntimeException("Field " + fieldName + " not found in type " + getHandle().getName());
_aliasFields.put(fieldAlias, field);
return this;
}
@Override
public ReflectedType enumAlias(String constantAlias, String constantName) {
PreCon.notNullOrEmpty(constantAlias, "constantAlias");
PreCon.notNullOrEmpty(constantName, "constantName");
if (_aliasEnum == null)
_aliasEnum = new HashMap<>(5);
if (_aliasEnum.containsKey(constantAlias))
throw new RuntimeException("Enum alias already registered: " + constantAlias);
_aliasEnum.put(constantAlias, getEnumConstant(constantName));
return this;
}
@Override
public ReflectedType enumConst(String constantName) {
PreCon.notNullOrEmpty(constantName, "constantName");
if (_aliasEnum == null)
_aliasEnum = new HashMap<>(5);
if (_aliasEnum.containsKey(constantName))
throw new RuntimeException("Enum constant already registered: " + constantName);
_aliasEnum.put(constantName, getEnumConstant(constantName));
return this;
}
@Override
public ReflectedType method(String methodName, Class<?>... signature) {
PreCon.notNullOrEmpty(methodName, "methodName");
PreCon.notNull(signature, "argTypes");
if (_aliasMethods == null) {
_aliasMethods = new HashMap<>(10);
}
if (_aliasMethods.containsKey(methodName))
throw new RuntimeException("Method already registered: " + methodName);
Method method;
try {
method = getHandle().getDeclaredMethod(methodName, signature);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException("Method not found.");
}
method.setAccessible(true);
_aliasMethods.put(methodName, method);
return this;
}
@Override
public ReflectedType methodAlias(String alias, String methodName, Class<?>... signature) {
PreCon.notNullOrEmpty(alias, "alias");
PreCon.notNullOrEmpty(methodName, "methodName");
PreCon.notNull(signature, "argTypes");
if (_aliasMethods == null) {
_aliasMethods = new HashMap<>(10);
}
if (_aliasMethods.containsKey(alias))
throw new RuntimeException("Alias already registered: " + alias);
Method method;
try {
method = getHandle().getDeclaredMethod(methodName, signature);
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException("Method not found.");
}
method.setAccessible(true);
_aliasMethods.put(alias, method);
return this;
}
@Override
@Nullable
public <V> V invokeStatic(String staticMethodName, Object... arguments) {
PreCon.notNull(staticMethodName, "staticMethodName");
PreCon.notNull(arguments, "arguments");
return invoke(null, staticMethodName, arguments);
}
@Override
@Nullable
public <V> V invoke(@Nullable Object instance, String methodName, Object... arguments) {
PreCon.notNullOrEmpty(methodName, "methodName");
PreCon.notNull(arguments, "arguments");
Method method = _aliasMethods != null ? _aliasMethods.get(methodName) : null;
if (method == null) {
_methodOutput.clear();
Collection<Method> methods = _cached.methodsByName(methodName, _methodOutput);
// get method definition
if (methods.size() == 1) {
method = methods.iterator().next();
} else {
method = Reflection.findMethodByArgs(methods, methodName, arguments);
if (method == null)
throw new RuntimeException("Method '" + methodName +
"' not found in type " + _cached.getHandle().getCanonicalName());
}
}
if (instance == null && !Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException("Method " + methodName + " is not static.");
}
else if (instance != null && Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException("Method " + methodName + " is static.");
}
Object result;
try {
result = method.invoke(instance, arguments);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
@SuppressWarnings("unchecked")
V castedResult = (V)result;
return castedResult;
}
@Override
public Class<?> getHandle() {
return _cached.getHandle();
}
private Object getEnumConstant(String constantName) {
if (!getHandle().isEnum())
throw new RuntimeException("Type '" + getHandle().getName() + "' is not an enum.");
Object[] constants = getHandle().getEnumConstants();
Method nameMethod;
try {
nameMethod = Enum.class.getMethod("name");
} catch (NoSuchMethodException e) {
throw new AssertionError();
}
nameMethod.setAccessible(true);
for (Object constant : constants) {
String name;
try {
name = (String) nameMethod.invoke(constant);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new AssertionError();
}
if (constantName.equals(name)) {
return constant;
}
}
throw new RuntimeException("Failed to find enum constant named " +
constantName + " in type " + getHandle().getName());
}
}