Bukkit-API  1.7.9-R0.2
The inofficial Bukkit-API
JavaPluginLoader.java
1 package org.bukkit.plugin.java;
2 
3 import java.io.File;
4 import java.io.FileNotFoundException;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.lang.reflect.InvocationTargetException;
8 import java.lang.reflect.Method;
9 import java.util.Arrays;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.LinkedHashMap;
13 import java.util.Map;
14 import java.util.Set;
15 import java.util.jar.JarEntry;
16 import java.util.jar.JarFile;
17 import java.util.logging.Level;
18 import java.util.regex.Pattern;
19 
20 import org.apache.commons.lang.Validate;
21 import org.bukkit.Server;
22 import org.bukkit.Warning;
26 import org.bukkit.event.Event;
29 import org.bukkit.event.Listener;
36 import org.bukkit.plugin.Plugin;
42 import org.yaml.snakeyaml.error.YAMLException;
43 
44 /**
45  * Represents a Java plugin loader, allowing plugins in the form of .jar
46  */
47 public final class JavaPluginLoader implements PluginLoader {
48  final Server server;
49  private final Pattern[] fileFilters = new Pattern[] { Pattern.compile("\\.jar$"), };
50  private final Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
51  private final Map<String, PluginClassLoader> loaders = new LinkedHashMap<String, PluginClassLoader>();
52 
53  /**
54  * This class was not meant to be constructed explicitly
55  */
56  @Deprecated
57  public JavaPluginLoader(Server instance) {
58  Validate.notNull(instance, "Server cannot be null");
59  server = instance;
60  }
61 
62  public Plugin loadPlugin(final File file) throws InvalidPluginException {
63  Validate.notNull(file, "File cannot be null");
64 
65  if (!file.exists()) {
66  throw new InvalidPluginException(new FileNotFoundException(file.getPath() + " does not exist"));
67  }
68 
69  final PluginDescriptionFile description;
70  try {
71  description = getPluginDescription(file);
72  } catch (InvalidDescriptionException ex) {
73  throw new InvalidPluginException(ex);
74  }
75 
76  final File parentFile = file.getParentFile();
77  final File dataFolder = new File(parentFile, description.getName());
78  @SuppressWarnings("deprecation")
79  final File oldDataFolder = new File(parentFile, description.getRawName());
80 
81  // Found old data folder
82  if (dataFolder.equals(oldDataFolder)) {
83  // They are equal -- nothing needs to be done!
84  } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) {
85  server.getLogger().warning(String.format(
86  "While loading %s (%s) found old-data folder: `%s' next to the new one `%s'",
87  description.getFullName(),
88  file,
89  oldDataFolder,
90  dataFolder
91  ));
92  } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) {
93  if (!oldDataFolder.renameTo(dataFolder)) {
94  throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'");
95  }
96  server.getLogger().log(Level.INFO, String.format(
97  "While loading %s (%s) renamed data folder: `%s' to `%s'",
98  description.getFullName(),
99  file,
100  oldDataFolder,
101  dataFolder
102  ));
103  }
104 
105  if (dataFolder.exists() && !dataFolder.isDirectory()) {
106  throw new InvalidPluginException(String.format(
107  "Projected datafolder: `%s' for %s (%s) exists and is not a directory",
108  dataFolder,
109  description.getFullName(),
110  file
111  ));
112  }
113 
114  for (final String pluginName : description.getDepend()) {
115  if (loaders == null) {
116  throw new UnknownDependencyException(pluginName);
117  }
118  PluginClassLoader current = loaders.get(pluginName);
119 
120  if (current == null) {
121  throw new UnknownDependencyException(pluginName);
122  }
123  }
124 
125  final PluginClassLoader loader;
126  try {
127  loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file);
128  } catch (InvalidPluginException ex) {
129  throw ex;
130  } catch (Throwable ex) {
131  throw new InvalidPluginException(ex);
132  }
133 
134  loaders.put(description.getName(), loader);
135 
136  return loader.plugin;
137  }
138 
140  Validate.notNull(file, "File cannot be null");
141 
142  JarFile jar = null;
143  InputStream stream = null;
144 
145  try {
146  jar = new JarFile(file);
147  JarEntry entry = jar.getJarEntry("plugin.yml");
148 
149  if (entry == null) {
150  throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml"));
151  }
152 
153  stream = jar.getInputStream(entry);
154 
155  return new PluginDescriptionFile(stream);
156 
157  } catch (IOException ex) {
158  throw new InvalidDescriptionException(ex);
159  } catch (YAMLException ex) {
160  throw new InvalidDescriptionException(ex);
161  } finally {
162  if (jar != null) {
163  try {
164  jar.close();
165  } catch (IOException e) {
166  }
167  }
168  if (stream != null) {
169  try {
170  stream.close();
171  } catch (IOException e) {
172  }
173  }
174  }
175  }
176 
177  public Pattern[] getPluginFileFilters() {
178  return fileFilters.clone();
179  }
180 
181  Class<?> getClassByName(final String name) {
182  Class<?> cachedClass = classes.get(name);
183 
184  if (cachedClass != null) {
185  return cachedClass;
186  } else {
187  for (String current : loaders.keySet()) {
188  PluginClassLoader loader = loaders.get(current);
189 
190  try {
191  cachedClass = loader.findClass(name, false);
192  } catch (ClassNotFoundException cnfe) {}
193  if (cachedClass != null) {
194  return cachedClass;
195  }
196  }
197  }
198  return null;
199  }
200 
201  void setClass(final String name, final Class<?> clazz) {
202  if (!classes.containsKey(name)) {
203  classes.put(name, clazz);
204 
205  if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
206  Class<? extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
207  ConfigurationSerialization.registerClass(serializable);
208  }
209  }
210  }
211 
212  private void removeClass(String name) {
213  Class<?> clazz = classes.remove(name);
214 
215  try {
216  if ((clazz != null) && (ConfigurationSerializable.class.isAssignableFrom(clazz))) {
217  Class<? extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
218  ConfigurationSerialization.unregisterClass(serializable);
219  }
220  } catch (NullPointerException ex) {
221  // Boggle!
222  // (Native methods throwing NPEs is not fun when you can't stop it before-hand)
223  }
224  }
225 
226  public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(Listener listener, final Plugin plugin) {
227  Validate.notNull(plugin, "Plugin can not be null");
228  Validate.notNull(listener, "Listener can not be null");
229 
230  boolean useTimings = server.getPluginManager().useTimings();
231  Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();
232  Set<Method> methods;
233  try {
234  Method[] publicMethods = listener.getClass().getMethods();
235  methods = new HashSet<Method>(publicMethods.length, Float.MAX_VALUE);
236  for (Method method : publicMethods) {
237  methods.add(method);
238  }
239  for (Method method : listener.getClass().getDeclaredMethods()) {
240  methods.add(method);
241  }
242  } catch (NoClassDefFoundError e) {
243  plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
244  return ret;
245  }
246 
247  for (final Method method : methods) {
248  final EventHandler eh = method.getAnnotation(EventHandler.class);
249  if (eh == null) continue;
250  final Class<?> checkClass;
251  if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
252  plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
253  continue;
254  }
255  final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
256  method.setAccessible(true);
257  Set<RegisteredListener> eventSet = ret.get(eventClass);
258  if (eventSet == null) {
259  eventSet = new HashSet<RegisteredListener>();
260  ret.put(eventClass, eventSet);
261  }
262 
263  for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
264  // This loop checks for extending deprecated events
265  if (clazz.getAnnotation(Deprecated.class) != null) {
266  Warning warning = clazz.getAnnotation(Warning.class);
267  WarningState warningState = server.getWarningState();
268  if (!warningState.printFor(warning)) {
269  break;
270  }
271  plugin.getLogger().log(
272  Level.WARNING,
273  String.format(
274  "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." +
275  " \"%s\"; please notify the authors %s.",
276  plugin.getDescription().getFullName(),
277  clazz.getName(),
278  method.toGenericString(),
279  (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
280  Arrays.toString(plugin.getDescription().getAuthors().toArray())),
281  warningState == WarningState.ON ? new AuthorNagException(null) : null);
282  break;
283  }
284  }
285 
286  EventExecutor executor = new EventExecutor() {
287  public void execute(Listener listener, Event event) throws EventException {
288  try {
289  if (!eventClass.isAssignableFrom(event.getClass())) {
290  return;
291  }
292  method.invoke(listener, event);
293  } catch (InvocationTargetException ex) {
294  throw new EventException(ex.getCause());
295  } catch (Throwable t) {
296  throw new EventException(t);
297  }
298  }
299  };
300  if (useTimings) {
301  eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
302  } else {
303  eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
304  }
305  }
306  return ret;
307  }
308 
309  public void enablePlugin(final Plugin plugin) {
310  Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
311 
312  if (!plugin.isEnabled()) {
313  plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName());
314 
315  JavaPlugin jPlugin = (JavaPlugin) plugin;
316 
317  String pluginName = jPlugin.getDescription().getName();
318 
319  if (!loaders.containsKey(pluginName)) {
320  loaders.put(pluginName, (PluginClassLoader) jPlugin.getClassLoader());
321  }
322 
323  try {
324  jPlugin.setEnabled(true);
325  } catch (Throwable ex) {
326  server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
327  }
328 
329  // Perhaps abort here, rather than continue going, but as it stands,
330  // an abort is not possible the way it's currently written
331  server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
332  }
333  }
334 
335  public void disablePlugin(Plugin plugin) {
336  Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
337 
338  if (plugin.isEnabled()) {
339  String message = String.format("Disabling %s", plugin.getDescription().getFullName());
340  plugin.getLogger().info(message);
341 
342  server.getPluginManager().callEvent(new PluginDisableEvent(plugin));
343 
344  JavaPlugin jPlugin = (JavaPlugin) plugin;
345  ClassLoader cloader = jPlugin.getClassLoader();
346 
347  try {
348  jPlugin.setEnabled(false);
349  } catch (Throwable ex) {
350  server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
351  }
352 
353  loaders.remove(jPlugin.getDescription().getName());
354 
355  if (cloader instanceof PluginClassLoader) {
356  PluginClassLoader loader = (PluginClassLoader) cloader;
357  Set<String> names = loader.getClasses();
358 
359  for (String name : names) {
360  removeClass(name);
361  }
362  }
363  }
364  }
365 }
final void setEnabled(final boolean enabled)
PluginManager getPluginManager()
PluginDescriptionFile getPluginDescription(File file)
PluginDescriptionFile getDescription()
final ClassLoader getClassLoader()
final PluginDescriptionFile getDescription()
WarningState getWarningState()
Logger getLogger()