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 }