001 package org.bukkit.configuration.serialization; 002 003 import java.lang.reflect.Constructor; 004 import java.lang.reflect.InvocationTargetException; 005 import java.lang.reflect.Method; 006 import java.lang.reflect.Modifier; 007 import java.util.HashMap; 008 import java.util.Map; 009 import java.util.logging.Level; 010 import java.util.logging.Logger; 011 012 import org.apache.commons.lang.Validate; 013 import org.bukkit.Color; 014 import org.bukkit.FireworkEffect; 015 import org.bukkit.configuration.Configuration; 016 import org.bukkit.inventory.ItemStack; 017 import org.bukkit.potion.PotionEffect; 018 import org.bukkit.util.BlockVector; 019 import org.bukkit.util.Vector; 020 021 /** 022 * Utility class for storing and retrieving classes for {@link Configuration}. 023 */ 024 public class ConfigurationSerialization { 025 public static final String SERIALIZED_TYPE_KEY = "=="; 026 private final Class<? extends ConfigurationSerializable> clazz; 027 private static Map<String, Class<? extends ConfigurationSerializable>> aliases = new HashMap<String, Class<? extends ConfigurationSerializable>>(); 028 029 static { 030 registerClass(Vector.class); 031 registerClass(BlockVector.class); 032 registerClass(ItemStack.class); 033 registerClass(Color.class); 034 registerClass(PotionEffect.class); 035 registerClass(FireworkEffect.class); 036 } 037 038 protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) { 039 this.clazz = clazz; 040 } 041 042 protected Method getMethod(String name, boolean isStatic) { 043 try { 044 Method method = clazz.getDeclaredMethod(name, Map.class); 045 046 if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) { 047 return null; 048 } 049 if (Modifier.isStatic(method.getModifiers()) != isStatic) { 050 return null; 051 } 052 053 return method; 054 } catch (NoSuchMethodException ex) { 055 return null; 056 } catch (SecurityException ex) { 057 return null; 058 } 059 } 060 061 protected Constructor<? extends ConfigurationSerializable> getConstructor() { 062 try { 063 return clazz.getConstructor(Map.class); 064 } catch (NoSuchMethodException ex) { 065 return null; 066 } catch (SecurityException ex) { 067 return null; 068 } 069 } 070 071 protected ConfigurationSerializable deserializeViaMethod(Method method, Map<String, ?> args) { 072 try { 073 ConfigurationSerializable result = (ConfigurationSerializable) method.invoke(null, args); 074 075 if (result == null) { 076 Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization: method returned null"); 077 } else { 078 return result; 079 } 080 } catch (Throwable ex) { 081 Logger.getLogger(ConfigurationSerialization.class.getName()).log( 082 Level.SEVERE, 083 "Could not call method '" + method.toString() + "' of " + clazz + " for deserialization", 084 ex instanceof InvocationTargetException ? ex.getCause() : ex); 085 } 086 087 return null; 088 } 089 090 protected ConfigurationSerializable deserializeViaCtor(Constructor<? extends ConfigurationSerializable> ctor, Map<String, ?> args) { 091 try { 092 return ctor.newInstance(args); 093 } catch (Throwable ex) { 094 Logger.getLogger(ConfigurationSerialization.class.getName()).log( 095 Level.SEVERE, 096 "Could not call constructor '" + ctor.toString() + "' of " + clazz + " for deserialization", 097 ex instanceof InvocationTargetException ? ex.getCause() : ex); 098 } 099 100 return null; 101 } 102 103 public ConfigurationSerializable deserialize(Map<String, ?> args) { 104 Validate.notNull(args, "Args must not be null"); 105 106 ConfigurationSerializable result = null; 107 Method method = null; 108 109 if (result == null) { 110 method = getMethod("deserialize", true); 111 112 if (method != null) { 113 result = deserializeViaMethod(method, args); 114 } 115 } 116 117 if (result == null) { 118 method = getMethod("valueOf", true); 119 120 if (method != null) { 121 result = deserializeViaMethod(method, args); 122 } 123 } 124 125 if (result == null) { 126 Constructor<? extends ConfigurationSerializable> constructor = getConstructor(); 127 128 if (constructor != null) { 129 result = deserializeViaCtor(constructor, args); 130 } 131 } 132 133 return result; 134 } 135 136 /** 137 * Attempts to deserialize the given arguments into a new instance of the 138 * given class. 139 * <p> 140 * The class must implement {@link ConfigurationSerializable}, including 141 * the extra methods as specified in the javadoc of 142 * ConfigurationSerializable. 143 * <p> 144 * If a new instance could not be made, an example being the class not 145 * fully implementing the interface, null will be returned. 146 * 147 * @param args Arguments for deserialization 148 * @param clazz Class to deserialize into 149 * @return New instance of the specified class 150 */ 151 public static ConfigurationSerializable deserializeObject(Map<String, ?> args, Class<? extends ConfigurationSerializable> clazz) { 152 return new ConfigurationSerialization(clazz).deserialize(args); 153 } 154 155 /** 156 * Attempts to deserialize the given arguments into a new instance of the 157 * given class. 158 * <p> 159 * The class must implement {@link ConfigurationSerializable}, including 160 * the extra methods as specified in the javadoc of 161 * ConfigurationSerializable. 162 * <p> 163 * If a new instance could not be made, an example being the class not 164 * fully implementing the interface, null will be returned. 165 * 166 * @param args Arguments for deserialization 167 * @return New instance of the specified class 168 */ 169 public static ConfigurationSerializable deserializeObject(Map<String, ?> args) { 170 Class<? extends ConfigurationSerializable> clazz = null; 171 172 if (args.containsKey(SERIALIZED_TYPE_KEY)) { 173 try { 174 String alias = (String) args.get(SERIALIZED_TYPE_KEY); 175 176 if (alias == null) { 177 throw new IllegalArgumentException("Cannot have null alias"); 178 } 179 clazz = getClassByAlias(alias); 180 if (clazz == null) { 181 throw new IllegalArgumentException("Specified class does not exist ('" + alias + "')"); 182 } 183 } catch (ClassCastException ex) { 184 ex.fillInStackTrace(); 185 throw ex; 186 } 187 } else { 188 throw new IllegalArgumentException("Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')"); 189 } 190 191 return new ConfigurationSerialization(clazz).deserialize(args); 192 } 193 194 /** 195 * Registers the given {@link ConfigurationSerializable} class by its 196 * alias 197 * 198 * @param clazz Class to register 199 */ 200 public static void registerClass(Class<? extends ConfigurationSerializable> clazz) { 201 DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); 202 203 if (delegate == null) { 204 registerClass(clazz, getAlias(clazz)); 205 registerClass(clazz, clazz.getName()); 206 } 207 } 208 209 /** 210 * Registers the given alias to the specified {@link 211 * ConfigurationSerializable} class 212 * 213 * @param clazz Class to register 214 * @param alias Alias to register as 215 * @see SerializableAs 216 */ 217 public static void registerClass(Class<? extends ConfigurationSerializable> clazz, String alias) { 218 aliases.put(alias, clazz); 219 } 220 221 /** 222 * Unregisters the specified alias to a {@link ConfigurationSerializable} 223 * 224 * @param alias Alias to unregister 225 */ 226 public static void unregisterClass(String alias) { 227 aliases.remove(alias); 228 } 229 230 /** 231 * Unregisters any aliases for the specified {@link 232 * ConfigurationSerializable} class 233 * 234 * @param clazz Class to unregister 235 */ 236 public static void unregisterClass(Class<? extends ConfigurationSerializable> clazz) { 237 while (aliases.values().remove(clazz)) { 238 ; 239 } 240 } 241 242 /** 243 * Attempts to get a registered {@link ConfigurationSerializable} class by 244 * its alias 245 * 246 * @param alias Alias of the serializable 247 * @return Registered class, or null if not found 248 */ 249 public static Class<? extends ConfigurationSerializable> getClassByAlias(String alias) { 250 return aliases.get(alias); 251 } 252 253 /** 254 * Gets the correct alias for the given {@link ConfigurationSerializable} 255 * class 256 * 257 * @param clazz Class to get alias for 258 * @return Alias to use for the class 259 */ 260 public static String getAlias(Class<? extends ConfigurationSerializable> clazz) { 261 DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); 262 263 if (delegate != null) { 264 if ((delegate.value() == null) || (delegate.value() == clazz)) { 265 delegate = null; 266 } else { 267 return getAlias(delegate.value()); 268 } 269 } 270 271 if (delegate == null) { 272 SerializableAs alias = clazz.getAnnotation(SerializableAs.class); 273 274 if ((alias != null) && (alias.value() != null)) { 275 return alias.value(); 276 } 277 } 278 279 return clazz.getName(); 280 } 281 }