package com.bergerkiller.bukkit.common.reflection; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.logging.Level; import org.bukkit.Bukkit; import com.bergerkiller.bukkit.common.Common; import com.bergerkiller.bukkit.common.conversion.ConverterPair; import com.bergerkiller.bukkit.common.internal.CommonPlugin; import com.bergerkiller.bukkit.common.utils.LogicUtil; import com.bergerkiller.bukkit.common.utils.StringUtil; /** * Wraps around the java.lang.reflect.Field class to provide an error-free alternative<br> * Exceptions are logged, isValid can be used to check if the Field is actually working * * @param <T> type of the Field */ public class SafeField<T> implements FieldAccessor<T> { private Field field; public SafeField(Field field) { if (!field.isAccessible()) { try { field.setAccessible(true); } catch (SecurityException ex) { ex.printStackTrace(); field = null; } } this.field = field; } public SafeField(String fieldPath) { if (LogicUtil.nullOrEmpty(fieldPath) || !fieldPath.contains(".")) { Bukkit.getLogger().log(Level.SEVERE, "Field path contains no class: " + fieldPath); return; } try { String className = StringUtil.getLastBefore(fieldPath, "."); String fieldName = fieldPath.substring(className.length() + 1); Class<?> type = Class.forName(Common.SERVER.getClassName(className)); load(type, fieldName); } catch (Throwable t) { System.out.println("Failed to load field '" + fieldPath + "':"); t.printStackTrace(); } } public SafeField(Object value, String name) { load(value == null ? null : value.getClass(), name); } public SafeField(Class<?> source, String name) { load(source, name); } private void load(Class<?> source, String name) { if (source == null) { new Exception("Can not load field '" + name + "' because the class is null!").printStackTrace(); return; } // try to find the field String fixedName = Common.SERVER == null ? name : Common.SERVER.getFieldName(source, name); String dispName = name.equals(fixedName) ? name : (name + "[" + fixedName + "]"); try { this.field = findRaw(source, fixedName); if (this.field != null) { this.field.setAccessible(true); return; } } catch (SecurityException ex) { new Exception("No permission to access field '" + dispName + "' in class file '" + source.getSimpleName() + "'").printStackTrace(); return; } CommonPlugin.getInstance().handleReflectionMissing("Field", dispName, source); } @Override public boolean isValid() { return this.field != null; } /** * Gets whether this Field is a static Field * * @return True if static, False if not */ public boolean isStatic() { return this.field == null ? false : Modifier.isStatic(this.field.getModifiers()); } @Override public T transfer(Object from, Object to) { if (this.field == null) { return null; } T old = get(to); set(to, get(from)); return old; } @Override @SuppressWarnings("unchecked") public T get(Object object) { if (this.field == null) { return null; } try { return (T) this.field.get(object); } catch (Throwable t) { if (!this.isStatic() && object == null) { throw new IllegalArgumentException("Non-static field requires a non-null instance"); } t.printStackTrace(); this.field = null; return null; } } @Override public boolean set(Object object, T value) { if (this.field != null) { try { this.field.set(object, value); return true; } catch (Throwable t) { if (!this.isStatic() && object == null) { throw new IllegalArgumentException("Non-static field requires a non-null instance"); } t.printStackTrace(); this.field = null; } } return false; } @Override public String toString() { StringBuilder text = new StringBuilder(20); final int mod = field.getModifiers(); if (Modifier.isPublic(mod)) { text.append("public "); } else if (Modifier.isPrivate(mod)) { text.append("private "); } else if (Modifier.isProtected(mod)) { text.append("protected "); } if (Modifier.isStatic(mod)) { text.append("static "); } return text.append(field.getType().getName()).append(" ").append(field.getName()).toString(); } /** * Gets the name of this field as declared in the Class * * @return Field name */ public String getName() { return field.getName(); } /** * Gets the Class type of this field as declared inthe Class * * @return Field type */ public Class<?> getType() { return field.getType(); } @Override public <K> TranslatorFieldAccessor<K> translate(ConverterPair<?, K> converterPair) { return new TranslatorFieldAccessor<K>(this, converterPair); } /** * Tries to set a Field for a certain Object * * @param source to set a Field for * @param fieldname to set * @param value to set to */ public static <T> void set(Object source, String fieldname, T value) { new SafeField<T>(source, fieldname).set(source, value); } /** * Tries to set a static Field in a certain Class * * @param clazz to set the static field in * @param fieldname of the static field * @param value to set to */ public static <T> void setStatic(Class<?> clazz, String fieldname, T value) { new SafeField<T>(clazz, fieldname).set(null, value); } /** * Tries to get a Field from a certain Object * * @param source to get the Field from * @param fieldname to get * @return The Field value, or null if not possible */ public static <T> T get(Object source, String fieldname) { return new SafeField<T>(source, fieldname).get(source); } /** * Tries to get a static Field from a class * * @param clazz to get the field value for * @param fieldname of the field value * @return The Field value, or null if not possible */ public static <T> T get(Class<?> clazz, String fieldname) { return new SafeField<T>(clazz, fieldname).get(null); } /** * Creates a new SafeField instance pointing to the field found in the given class type * * @param type - class type to find the field in * @param name - field name * @return new SafeField */ public static <T> SafeField<T> create(Class<?> type, String name) { return new SafeField<T>(type, name); } /** * Creates a new SafeField instance pointing to the field found in the given class type. * Exposes to the outside world using a translator. * * @param type - class type to find the field in * @param name - field name * @param converterPair - used to convert between exposed and stored types * @return new TranslatorFieldAccessor backed by a SafeField */ public static <T> TranslatorFieldAccessor<T> create(Class<?> type, String name, ConverterPair<?, T> converterPair) { return create(type, name).translate(converterPair); } /** * Checks whether a certain field is available in a Class * * @param type of Class * @param name of the field * @return True if available, False if not */ public static boolean contains(Class<?> type, String name) { return findRaw(type, Common.SERVER.getFieldName(type, name)) != null; } /** * Tries to recursively find a field in a Class * * @param type of Class * @param name of the field * @return the Field, or null if not found */ private static Field findRaw(Class<?> type, String name) { Class<?> tmp = type; // Try to find the field in the current and all Super Classes while (tmp != null) { try { return tmp.getDeclaredField(name); } catch (NoSuchFieldException ex) { tmp = tmp.getSuperclass(); } } // Interfaces don't contain fields, so nothing found at this point return null; } }