Bukkit  1.4.7-R1.0
 All Classes Namespaces Files Functions Variables Enumerator Pages
JavaPluginLoader.java
Go to the documentation of this file.
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.Constructor;
8 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method;
10 import java.net.URL;
11 import java.util.Arrays;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.LinkedHashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.jar.JarEntry;
19 import java.util.jar.JarFile;
20 import java.util.logging.Level;
21 import java.util.regex.Pattern;
22 
23 import org.apache.commons.lang.Validate;
24 import org.bukkit.Server;
25 import org.bukkit.Warning;
26 import org.bukkit.Warning.WarningState;
27 import org.bukkit.configuration.serialization.ConfigurationSerializable;
28 import org.bukkit.configuration.serialization.ConfigurationSerialization;
29 import org.bukkit.event.Event;
30 import org.bukkit.event.EventException;
31 import org.bukkit.event.EventHandler;
32 import org.bukkit.event.Listener;
33 import org.bukkit.event.server.PluginDisableEvent;
34 import org.bukkit.event.server.PluginEnableEvent;
35 import org.bukkit.plugin.AuthorNagException;
36 import org.bukkit.plugin.EventExecutor;
37 import org.bukkit.plugin.InvalidDescriptionException;
38 import org.bukkit.plugin.InvalidPluginException;
39 import org.bukkit.plugin.Plugin;
40 import org.bukkit.plugin.PluginDescriptionFile;
41 import org.bukkit.plugin.PluginLoader;
42 import org.bukkit.plugin.RegisteredListener;
43 import org.bukkit.plugin.TimedRegisteredListener;
44 import org.bukkit.plugin.UnknownDependencyException;
45 import org.yaml.snakeyaml.error.YAMLException;
46 
47 import com.google.common.collect.ImmutableList;
48 
52 public class JavaPluginLoader implements PluginLoader {
53  final Server server;
54  final boolean extended = this.getClass() != JavaPluginLoader.class;
55  boolean warn;
56 
57  private final Pattern[] fileFilters0 = new Pattern[] { Pattern.compile("\\.jar$"), };
61  @Deprecated
62  protected final Pattern[] fileFilters = fileFilters0;
63 
64  private final Map<String, Class<?>> classes0 = new HashMap<String, Class<?>>();
68  @Deprecated
69  protected final Map<String, Class<?>> classes = classes0;
70 
71  private final Map<String, PluginClassLoader> loaders0 = new LinkedHashMap<String, PluginClassLoader>();
75  @Deprecated
76  protected final Map<String, PluginClassLoader> loaders = loaders0;
77 
81  @Deprecated
82  public JavaPluginLoader(Server instance) {
83  Validate.notNull(instance, "Server cannot be null");
84  server = instance;
85  warn = instance.getWarningState() != WarningState.OFF;
86  if (extended && warn) {
87  warn = false;
88  instance.getLogger().log(Level.WARNING, "JavaPluginLoader not intended to be extended by " + getClass() + ", and may be final in a future version of Bukkit");
89  }
90  }
91 
92  public Plugin loadPlugin(File file) throws InvalidPluginException {
93  Validate.notNull(file, "File cannot be null");
94 
95  if (!file.exists()) {
96  throw new InvalidPluginException(new FileNotFoundException(file.getPath() + " does not exist"));
97  }
98 
99  PluginDescriptionFile description;
100  try {
101  description = getPluginDescription(file);
102  } catch (InvalidDescriptionException ex) {
103  throw new InvalidPluginException(ex);
104  }
105 
106  File dataFolder = new File(file.getParentFile(), description.getName());
107  File oldDataFolder = extended ? getDataFolder(file) : getDataFolder0(file); // Don't warn on deprecation, but maintain overridability
108 
109  // Found old data folder
110  if (dataFolder.equals(oldDataFolder)) {
111  // They are equal -- nothing needs to be done!
112  } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) {
113  server.getLogger().log(Level.INFO, String.format(
114  "While loading %s (%s) found old-data folder: %s next to the new one: %s",
115  description.getName(),
116  file,
117  oldDataFolder,
118  dataFolder
119  ));
120  } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) {
121  if (!oldDataFolder.renameTo(dataFolder)) {
122  throw new InvalidPluginException("Unable to rename old data folder: '" + oldDataFolder + "' to: '" + dataFolder + "'");
123  }
124  server.getLogger().log(Level.INFO, String.format(
125  "While loading %s (%s) renamed data folder: '%s' to '%s'",
126  description.getName(),
127  file,
128  oldDataFolder,
129  dataFolder
130  ));
131  }
132 
133  if (dataFolder.exists() && !dataFolder.isDirectory()) {
134  throw new InvalidPluginException(String.format(
135  "Projected datafolder: '%s' for %s (%s) exists and is not a directory",
136  dataFolder,
137  description.getName(),
138  file
139  ));
140  }
141 
142  List<String> depend = description.getDepend();
143  if (depend == null) {
144  depend = ImmutableList.<String>of();
145  }
146 
147  for (String pluginName : depend) {
148  if (loaders0 == null) {
149  throw new UnknownDependencyException(pluginName);
150  }
151  PluginClassLoader current = loaders0.get(pluginName);
152 
153  if (current == null) {
154  throw new UnknownDependencyException(pluginName);
155  }
156  }
157 
158  PluginClassLoader loader = null;
159  JavaPlugin result = null;
160 
161  try {
162  URL[] urls = new URL[1];
163 
164  urls[0] = file.toURI().toURL();
165 
166  if (description.getClassLoaderOf() != null) {
167  loader = loaders0.get(description.getClassLoaderOf());
168  loader.addURL(urls[0]);
169  } else {
170  loader = new PluginClassLoader(this, urls, getClass().getClassLoader(), null);
171  }
172 
173  Class<?> jarClass = Class.forName(description.getMain(), true, loader);
174  Class<? extends JavaPlugin> plugin = jarClass.asSubclass(JavaPlugin.class);
175 
176  Constructor<? extends JavaPlugin> constructor = plugin.getConstructor();
177 
178  result = constructor.newInstance();
179 
180  result.initialize(this, server, description, dataFolder, file, loader);
181  } catch (InvocationTargetException ex) {
182  throw new InvalidPluginException(ex.getCause());
183  } catch (Throwable ex) {
184  throw new InvalidPluginException(ex);
185  }
186 
187  loaders0.put(description.getName(), loader);
188 
189  return result;
190  }
191 
195  @Deprecated
196  public Plugin loadPlugin(File file, boolean ignoreSoftDependencies) throws InvalidPluginException {
197  if (warn) {
198  server.getLogger().log(Level.WARNING, "Method \"public Plugin loadPlugin(File, boolean)\" is Deprecated, and may be removed in a future version of Bukkit", new AuthorNagException(""));
199  warn = false;
200  }
201  return loadPlugin(file);
202  }
203 
207  @Deprecated
208  protected File getDataFolder(File file) {
209  if (warn) {
210  server.getLogger().log(Level.WARNING, "Method \"protected File getDataFolder(File)\" is Deprecated, and may be removed in a future version of Bukkit", new AuthorNagException(""));
211  warn = false;
212  }
213  return getDataFolder0(file);
214  }
215 
216  private File getDataFolder0(File file) {
217  File dataFolder = null;
218 
219  String filename = file.getName();
220  int index = file.getName().lastIndexOf(".");
221 
222  if (index != -1) {
223  String name = filename.substring(0, index);
224 
225  dataFolder = new File(file.getParentFile(), name);
226  } else {
227  // This is if there is no extension, which should not happen
228  // Using _ to prevent name collision
229 
230  dataFolder = new File(file.getParentFile(), filename + "_");
231  }
232 
233  return dataFolder;
234  }
235 
237  Validate.notNull(file, "File cannot be null");
238 
239  JarFile jar = null;
240  InputStream stream = null;
241 
242  try {
243  jar = new JarFile(file);
244  JarEntry entry = jar.getJarEntry("plugin.yml");
245 
246  if (entry == null) {
247  throw new InvalidDescriptionException(new FileNotFoundException("Jar does not contain plugin.yml"));
248  }
249 
250  stream = jar.getInputStream(entry);
251 
252  return new PluginDescriptionFile(stream);
253 
254  } catch (IOException ex) {
255  throw new InvalidDescriptionException(ex);
256  } catch (YAMLException ex) {
257  throw new InvalidDescriptionException(ex);
258  } finally {
259  if (jar != null) {
260  try {
261  jar.close();
262  } catch (IOException e) {
263  }
264  }
265  if (stream != null) {
266  try {
267  stream.close();
268  } catch (IOException e) {
269  }
270  }
271  }
272  }
273 
274  public Pattern[] getPluginFileFilters() {
275  return fileFilters0.clone();
276  }
277 
281  @Deprecated
282  public Class<?> getClassByName(final String name) {
283  if (warn) {
284  server.getLogger().log(Level.WARNING, "Method \"public Class<?> getClassByName(String)\" is Deprecated, and may be removed in a future version of Bukkit", new AuthorNagException(""));
285  warn = false;
286  }
287  return getClassByName0(name);
288  }
289 
290  Class<?> getClassByName0(final String name) {
291  Class<?> cachedClass = classes0.get(name);
292 
293  if (cachedClass != null) {
294  return cachedClass;
295  } else {
296  for (String current : loaders0.keySet()) {
297  PluginClassLoader loader = loaders0.get(current);
298 
299  try {
300  cachedClass = loader.extended ? loader.findClass(name, false) : loader.findClass0(name, false); // Don't warn on deprecation, but maintain overridability
301  } catch (ClassNotFoundException cnfe) {}
302  if (cachedClass != null) {
303  return cachedClass;
304  }
305  }
306  }
307  return null;
308  }
309 
313  @Deprecated
314  public void setClass(final String name, final Class<?> clazz) {
315  if (warn) {
316  server.getLogger().log(Level.WARNING, "Method \"public void setClass(String, Class<?>)\" is Deprecated, and may be removed in a future version of Bukkit", new AuthorNagException(""));
317  warn = false;
318  }
319  setClass0(name, clazz);
320  }
321 
322  void setClass0(final String name, final Class<?> clazz) {
323  if (!classes0.containsKey(name)) {
324  classes0.put(name, clazz);
325 
326  if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
327  Class<? extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
329  }
330  }
331  }
332 
336  @Deprecated
337  public void removeClass(String name) {
338  if (warn) {
339  server.getLogger().log(Level.WARNING, "Method \"public void removeClass(String)\" is Deprecated, and may be removed in a future version of Bukkit", new AuthorNagException(""));
340  warn = false;
341  }
342  removeClass0(name);
343  }
344 
345  private void removeClass0(String name) {
346  Class<?> clazz = classes0.remove(name);
347 
348  try {
349  if ((clazz != null) && (ConfigurationSerializable.class.isAssignableFrom(clazz))) {
350  Class<? extends ConfigurationSerializable> serializable = clazz.asSubclass(ConfigurationSerializable.class);
352  }
353  } catch (NullPointerException ex) {
354  // Boggle!
355  // (Native methods throwing NPEs is not fun when you can't stop it before-hand)
356  }
357  }
358 
359  public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(Listener listener, final Plugin plugin) {
360  Validate.notNull(plugin, "Plugin can not be null");
361  Validate.notNull(listener, "Listener can not be null");
362 
363  boolean useTimings = server.getPluginManager().useTimings();
364  Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();
365  Set<Method> methods;
366  try {
367  Method[] publicMethods = listener.getClass().getMethods();
368  methods = new HashSet<Method>(publicMethods.length, Float.MAX_VALUE);
369  for (Method method : publicMethods) {
370  methods.add(method);
371  }
372  for (Method method : listener.getClass().getDeclaredMethods()) {
373  methods.add(method);
374  }
375  } catch (NoClassDefFoundError e) {
376  plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
377  return ret;
378  }
379 
380  for (final Method method : methods) {
381  final EventHandler eh = method.getAnnotation(EventHandler.class);
382  if (eh == null) continue;
383  final Class<?> checkClass = method.getParameterTypes()[0];
384  if (!Event.class.isAssignableFrom(checkClass) || method.getParameterTypes().length != 1) {
385  plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
386  continue;
387  }
388  final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
389  method.setAccessible(true);
390  Set<RegisteredListener> eventSet = ret.get(eventClass);
391  if (eventSet == null) {
392  eventSet = new HashSet<RegisteredListener>();
393  ret.put(eventClass, eventSet);
394  }
395 
396  for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
397  // This loop checks for extending deprecated events
398  if (clazz.getAnnotation(Deprecated.class) != null) {
399  Warning warning = clazz.getAnnotation(Warning.class);
400  WarningState warningState = server.getWarningState();
401  if (!warningState.printFor(warning)) {
402  break;
403  }
404  plugin.getLogger().log(
405  Level.WARNING,
406  String.format(
407  "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated." +
408  " \"%s\"; please notify the authors %s.",
409  plugin.getDescription().getFullName(),
410  clazz.getName(),
411  method.toGenericString(),
412  (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
413  Arrays.toString(plugin.getDescription().getAuthors().toArray())),
414  warningState == WarningState.ON ? new AuthorNagException(null) : null);
415  break;
416  }
417  }
418 
419  EventExecutor executor = new EventExecutor() {
420  public void execute(Listener listener, Event event) throws EventException {
421  try {
422  if (!eventClass.isAssignableFrom(event.getClass())) {
423  return;
424  }
425  method.invoke(listener, event);
426  } catch (InvocationTargetException ex) {
427  throw new EventException(ex.getCause());
428  } catch (Throwable t) {
429  throw new EventException(t);
430  }
431  }
432  };
433  if (useTimings) {
434  eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
435  } else {
436  eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
437  }
438  }
439  return ret;
440  }
441 
442  public void enablePlugin(final Plugin plugin) {
443  Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
444 
445  if (!plugin.isEnabled()) {
446  plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName());
447 
448  JavaPlugin jPlugin = (JavaPlugin) plugin;
449 
450  String pluginName = jPlugin.getDescription().getName();
451 
452  if (!loaders0.containsKey(pluginName)) {
453  loaders0.put(pluginName, (PluginClassLoader) jPlugin.getClassLoader());
454  }
455 
456  try {
457  jPlugin.setEnabled(true);
458  } catch (Throwable ex) {
459  server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
460  }
461 
462  // Perhaps abort here, rather than continue going, but as it stands,
463  // an abort is not possible the way it's currently written
464  server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
465  }
466  }
467 
468  public void disablePlugin(Plugin plugin) {
469  Validate.isTrue(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
470 
471  if (plugin.isEnabled()) {
472  String message = String.format("Disabling %s", plugin.getDescription().getFullName());
473  plugin.getLogger().info(message);
474 
475  server.getPluginManager().callEvent(new PluginDisableEvent(plugin));
476 
477  JavaPlugin jPlugin = (JavaPlugin) plugin;
478  ClassLoader cloader = jPlugin.getClassLoader();
479 
480  try {
481  jPlugin.setEnabled(false);
482  } catch (Throwable ex) {
483  server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
484  }
485 
486  loaders0.remove(jPlugin.getDescription().getName());
487 
488  if (cloader instanceof PluginClassLoader) {
489  PluginClassLoader loader = (PluginClassLoader) cloader;
490  Set<String> names = loader.extended ? loader.getClasses() : loader.getClasses0(); // Don't warn on deprecation, but maintain overridability
491 
492  for (String name : names) {
493  if (extended) {
494  removeClass(name);
495  } else {
496  removeClass0(name);
497  }
498  }
499  }
500  }
501  }
502 }