package com.bergerkiller.bukkit.common.reflection; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import org.objenesis.ObjenesisHelper; import org.objenesis.instantiator.ObjectInstantiator; import com.bergerkiller.bukkit.common.conversion.Converter; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.reflection.SafeField; import com.bergerkiller.bukkit.common.utils.CommonUtil; /** * Uses reflection to transfer/copy all the fields of a class */ public class ClassTemplate<T> { private Class<T> type; private List<SafeField<?>> fields; private ObjectInstantiator instantiator; /** * Initializes a new ClassTemplate not pointing to any Class<br> * Call setClass to define the Class to use */ public ClassTemplate() { } /** * Initializes a new ClassTemplate pointing to the Class specified * * @param type of Class */ public ClassTemplate(Class<T> type) { setClass(type); } /** * Initializes this Class Template to represent the Class and fields of the type specified * * @param type to set the Class to */ protected void setClass(Class<T> type) { this.type = type; this.fields = null; if (this.type == null) { this.instantiator = null; } else { this.instantiator = ObjenesisHelper.getInstantiatorOf(type); } } /** * Gets the Class type represented by this Template * * @return Class type */ public Class<T> getType() { return this.type; } /** * Gets all the fields declared in this Class * * @return Declared fields */ public List<SafeField<?>> getFields() { if (fields == null) { if (type == null) { fields = Collections.emptyList(); } else { fields = Collections.unmodifiableList(fillFields(new ArrayList<SafeField<?>>(), type)); } } return fields; } /** * Gets the field set at a specific index * * @param index to get the field at * @return field at the index * @throws IllegalArgumentException - If no field is at the index */ public SafeField<?> getFieldAt(int index) { List<SafeField<?>> fields = getFields(); if (index < 0 || index >= fields.size()) { throw new IllegalArgumentException("No field exists at index " + index); } return fields.get(index); } private static List<SafeField<?>> fillFields(List<SafeField<?>> fields, Class<?> clazz) { if (clazz == null) { return fields; } Field[] declared = clazz.getDeclaredFields(); ArrayList<SafeField<?>> newFields = new ArrayList<SafeField<?>>(declared.length); for (Field field : declared) { if (!Modifier.isStatic(field.getModifiers())) { newFields.add(new SafeField<Object>(field)); } } fields.addAll(0, newFields); return fillFields(fields, clazz.getSuperclass()); } /** * Gets a new instance of this Class, using the empty constructor * * @return A new instance, or null if an error occurred/empty constructor is not available * @throws IllegalStateException if this ClassTemplate has no (valid) Class set */ public T newInstance() { if (this.type == null) { throw new IllegalStateException("Class was not found or is not set"); } try { return this.type.newInstance(); } catch (Throwable t) { t.printStackTrace(); } return null; } /** * Gets a new instance of this Class without calling the Class Constructors. * Calling this method will result in a new instance with all fields set to the default. * That is, all fields will have 'NULL' values, or for primitives, 0/false/etc. * * @return a new Class Instance, or null upon failure * @throws IllegalStateException if this ClassTemplate has no (valid) Class set */ @SuppressWarnings("unchecked") public T newInstanceNull() { if (this.instantiator == null) { throw new IllegalStateException("Class was not found or is not set"); } try { return (T) this.instantiator.newInstance(); } catch (Throwable t) { t.printStackTrace(); } return null; } /** * Checks whether a given object is an instance of the class represented by this Template * * @param object to check * @return True if the object is an instance, False if not */ public boolean isInstance(Object object) { return this.type.isInstance(object); } /** * Checks whether the specified clazz object can be assigned to the type represented * by this ClassTemplate. This template taken as A, and clazz as B, this is equivalent to:<br> * <b>B instanceof A</b> * * @param clazz to check * @return True if the clazz can be assigned to this template type, False if not */ public boolean isAssignableFrom(Class<?> clazz) { return this.type.isAssignableFrom(clazz); } /** * Checks whether the object class equals the class represented by this Template * * @param object to check * @return True if the object is a direct instance, False if not */ public boolean isType(Object object) { return object != null && isType(object.getClass()); } /** * Checks whether a given class instance equals the class represented by this Template * * @param clazz to check * @return True if the clazz is not null and equals the class of this template */ public boolean isType(Class<?> clazz) { return clazz != null && this.type.equals(clazz); } /** * Transfers all the fields from one class instance to the other * * @param from instance * @param to instance */ public void transfer(Object from, Object to) { for (FieldAccessor<?> field : this.getFields()) { field.transfer(from, to); } } /** * Checks whether this Class Template is properly initialized and can be used * * @return True if this template is valid for use, False if not */ public boolean isValid() { return this.type != null; } @Override public String toString() { StringBuilder builder = new StringBuilder(500); builder.append("Class path: ").append(this.getType().getName()).append('\n'); builder.append("Fields (").append(this.getFields().size()).append("):"); for (FieldAccessor<?> field : this.getFields()) { builder.append("\n ").append(field.toString()); } return builder.toString(); } /** * Attempts to find the constructor for this Class using the parameter types * * @param parameterTypes of the constructor * @return Constructor */ public SafeConstructor<T> getConstructor(Class<?>... parameterTypes) { return new SafeConstructor<T>(this.getType(), parameterTypes); } /** * Attempts to find the field by name * * @param name of the field * @return field */ public <K> SafeField<K> getField(String name) { return new SafeField<K>(this.getType(), name); } /** * Attempts to find the method by name * * @param name of the method * @param parameterTypes of the method * @return method */ public <K> SafeMethod<K> getMethod(String name, Class<?>... parameterTypes) { return new SafeMethod<K>(this.getType(), name, parameterTypes); } /** * Gets a statically declared field value * * @param name of the static field * @return Static field value */ public <K> K getStaticFieldValue(String name) { return SafeField.get(getType(), name); } /** * Gets a statically declared field value and uses the * converter to get the value. * * @param name of the static field * @param converter to use * @return Converted static field value */ public <K> K getStaticFieldValue(String name, Converter<K> converter) { return converter.convert(getStaticFieldValue(name)); } /** * Sets a statically declared field value * * @param name of the static field * @param value to set the static field to */ public <K> void setStaticFieldValue(String name, K value) { SafeField.setStatic(getType(), name, value); } /** * Attempts to create a new template for the class at the path specified. * If the class is not found, a proper warning is printed. * * @param path to the class * @return a new template, or null if the template could not be made */ public static ClassTemplate<?> create(String path) { Class<?> type = CommonUtil.getClass(path); if (type == null) { CommonPlugin.LOGGER.log(Level.WARNING, "Class not found: '" + path + "'"); } return create(type); } /** * Attempts to create a new template for the class of the class instance specified<br> * If something fails, an empty instance is returned * * @param value of the class to create the template for * @return a new template */ @SuppressWarnings("unchecked") public static <T> ClassTemplate<T> create(T value) { return create((Class<T>) value.getClass()); } /** * Attempts to create a new template for the class specified<br> * If something fails, an empty instance is returned * * @param clazz to create * @return a new template */ public static <T> ClassTemplate<T> create(Class<T> clazz) { return new ClassTemplate<T>(clazz); } }