classNames = new ArrayList<>();
+ for (JarEntry entry : entryCache) {
+ if (entry == null) // This entry has already been loaded before
+ continue;
+
+ String name = entry.getName();
+ if (name.startsWith(basePackage) && name.endsWith(".class")) {
+ boolean load = subPackages.length == 0;
+
+ if (load) { // No subpackages provided
+ load = recursive || StringUtils.count(name, '/') <= initialDepth;
+ } else {
+ for (String subPackage : subPackages) {
+ if (
+ // We also need to account for subpackage depths when not doing a recursive search
+ (recursive || StringUtils.count(name, '/') <= initialDepth + StringUtils.count(subPackage, '/'))
+ && name.startsWith(subPackage, basePackage.length())
+ ) {
load = true;
break;
}
}
-
- if (load)
- classNames.add(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()));
}
+
+ if (load)
+ classNames.add(name.replace('/', '.').substring(0, name.length() - ".class".length()));
}
+ }
- classNames.sort(String::compareToIgnoreCase);
+ classNames.sort(String::compareToIgnoreCase);
- for (String c : classNames) {
- try {
- Class.forName(c, true, plugin.getClass().getClassLoader());
- } catch (ClassNotFoundException ex) {
- Skript.exception(ex, "Cannot load class " + c + " from " + this);
- } catch (ExceptionInInitializerError err) {
- Skript.exception(err.getCause(), this + "'s class " + c + " generated an exception while loading");
- }
- }
- } finally {
+ for (String className : classNames) {
try {
- jar.close();
- } catch (IOException e) {}
+ Class> clazz = Class.forName(className, initialize, plugin.getClass().getClassLoader());
+ if (withClass != null)
+ withClass.accept(clazz);
+ } catch (ClassNotFoundException ex) {
+ Skript.exception(ex, "Cannot load class " + className);
+ } catch (ExceptionInInitializerError err) {
+ Skript.exception(err.getCause(), this + "'s class " + className + " generated an exception while loading");
+ }
}
+
return this;
}
+
+ /**
+ * Loads all module classes found in the package search.
+ * @param basePackage The base package to start searching in (e.g. 'ch.njol.skript').
+ * @param subPackages Specific subpackages to search in (e.g. 'conditions').
+ * If no subpackages are provided, all subpackages will be searched.
+ * Note that the search will go no further than the first layer of subpackages.
+ * @return This SkriptAddon.
+ */
+ @SuppressWarnings("ThrowableNotThrown")
+ public SkriptAddon loadModules(String basePackage, String... subPackages) {
+ return loadClasses(basePackage, false, false, c -> {
+ if (Module.class.isAssignableFrom(c) && !c.isInterface() && !Modifier.isAbstract(c.getModifiers())) {
+ try {
+ ((Module) c.getConstructor().newInstance()).register(this);
+ } catch (Exception e) {
+ Skript.exception(e, "Failed to load module " + c);
+ }
+ }
+ }, subPackages);
+ }
@Nullable
private String languageFileDirectory = null;
/**
- * Makes Skript load language files from the specified directory, e.g. "lang" or "skript lang" if you have a lang folder yourself. Localised files will be read from the
- * plugin's jar and the plugin's data folder, but the default English file is only taken from the jar and must exist!
+ * Loads language files from the specified directory (e.g. "lang") into Skript.
+ * Localized files will be read from the plugin's jar and the plugin's data file,
+ * but the default.lang file is only taken from the jar and must exist!
*
- * @param directory Directory name
- * @return This SkriptAddon
+ * @param directory The directory containing language files.
+ * @return This SkriptAddon.
*/
public SkriptAddon setLanguageFileDirectory(String directory) {
if (languageFileDirectory != null)
- throw new IllegalStateException();
+ throw new IllegalStateException("The language file directory may only be set once.");
directory = "" + directory.replace('\\', '/');
if (directory.endsWith("/"))
directory = "" + directory.substring(0, directory.length() - 1);
@@ -149,7 +226,11 @@ public SkriptAddon setLanguageFileDirectory(String directory) {
Language.loadDefault(this);
return this;
}
-
+
+ /**
+ * @return The language file directory set for this addon.
+ * Null if not yet set using {@link #setLanguageFileDirectory(String)}.
+ */
@Nullable
public String getLanguageFileDirectory() {
return languageFileDirectory;
@@ -159,27 +240,25 @@ public String getLanguageFileDirectory() {
private File file = null;
/**
- * @return The jar file of the plugin. The first invocation of this method uses reflection to invoke the protected method {@link JavaPlugin#getFile()} to get the plugin's jar
- * file. The file is then cached and returned upon subsequent calls to this method to reduce usage of reflection.
+ * @return The jar file of the plugin.
+ * After this method is first called, the file will be cached for future use.
*/
@Nullable
public File getFile() {
if (file != null)
return file;
try {
- final Method getFile = JavaPlugin.class.getDeclaredMethod("getFile");
+ Method getFile = JavaPlugin.class.getDeclaredMethod("getFile");
getFile.setAccessible(true);
file = (File) getFile.invoke(plugin);
return file;
- } catch (final NoSuchMethodException e) {
- Skript.outdatedError(e);
- } catch (final IllegalArgumentException e) {
+ } catch (NoSuchMethodException | IllegalArgumentException e) {
Skript.outdatedError(e);
- } catch (final IllegalAccessException e) {
+ } catch (IllegalAccessException e) {
assert false;
- } catch (final SecurityException e) {
+ } catch (SecurityException e) {
throw new RuntimeException(e);
- } catch (final InvocationTargetException e) {
+ } catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
return null;
diff --git a/src/main/java/org/skriptlang/skript/registration/Module.java b/src/main/java/org/skriptlang/skript/registration/Module.java
new file mode 100644
index 00000000000..158deffc009
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/registration/Module.java
@@ -0,0 +1,61 @@
+/**
+ * This file is part of Skript.
+ *
+ * Skript is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Skript is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Skript. If not, see .
+ *
+ * Copyright Peter Güttinger, SkriptLang team and contributors
+ */
+package org.skriptlang.skript.registration;
+
+import ch.njol.skript.SkriptAddon;
+
+/**
+ * A module is a part of a {@link SkriptAddon} containing related syntax, classinfos, converters, etc.
+ * They are intended for providing organization and structure.
+ * Modules can be loaded using {@link SkriptAddon#loadModules(String, String...)}.
+ * Note that when loading 'org.skriptlang.skript.X', the module class should be placed at 'org.skriptlang.skript.X.ModuleClassHere'
+ * as the mentioned method will not search deeper than the provided subpackages.
+ * The example below is a possible organization structure that a project using Modules could use.
+ *
+ * potions
+ * |- elements
+ * |- PotionsExpr.java
+ * |- PotionsModule.java
+ * math
+ * |- elements
+ * |- MathExpr.java
+ * |- MathModule.java
+ * MyPlugin.java
+ *
+ */
+public interface Module {
+
+ /**
+ * @param addon The addon responsible for registering this module.
+ * To be used for registering syntax, classinfos, etc.
+ */
+ void register(SkriptAddon addon);
+
+ /**
+ * Loads syntax elements for this module.
+ * @param loader The SkriptAddon to load syntax with.
+ * @param subPackageName The location of syntax elements (ex: "elements")
+ * Elements should not be contained within the main module package.
+ * They should be within a subpackage of the package containing the Module class.
+ */
+ default void loadSyntax(SkriptAddon loader, String subPackageName) {
+ loader.loadClasses(getClass().getPackage().getName() + "." + subPackageName);
+ }
+
+}