/*
* 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 <http://www.gnu.org/licenses/>.
*
*
* Copyright 2011-2014 Peter Güttinger
*
*/
package ch.njol.skript;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.eclipse.jdt.annotation.Nullable;
import ch.njol.skript.Metrics.Graph;
import ch.njol.skript.Metrics.Plotter;
import ch.njol.skript.Updater.UpdateState;
import ch.njol.skript.aliases.Aliases;
import ch.njol.skript.bukkitutil.Workarounds;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.classes.Comparator;
import ch.njol.skript.classes.Converter;
import ch.njol.skript.classes.data.BukkitClasses;
import ch.njol.skript.classes.data.BukkitEventValues;
import ch.njol.skript.classes.data.DefaultComparators;
import ch.njol.skript.classes.data.DefaultConverters;
import ch.njol.skript.classes.data.DefaultFunctions;
import ch.njol.skript.classes.data.JavaClasses;
import ch.njol.skript.classes.data.SkriptClasses;
import ch.njol.skript.command.Commands;
import ch.njol.skript.doc.Documentation;
import ch.njol.skript.events.EvtSkript;
import ch.njol.skript.hooks.Hook;
import ch.njol.skript.lang.Condition;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ExpressionInfo;
import ch.njol.skript.lang.ExpressionType;
import ch.njol.skript.lang.SkriptEvent;
import ch.njol.skript.lang.SkriptEventInfo;
import ch.njol.skript.lang.Statement;
import ch.njol.skript.lang.SyntaxElementInfo;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.lang.VariableString;
import ch.njol.skript.lang.function.Functions;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Message;
import ch.njol.skript.log.BukkitLoggerFilter;
import ch.njol.skript.log.CountingLogHandler;
import ch.njol.skript.log.ErrorDescLogHandler;
import ch.njol.skript.log.ErrorQuality;
import ch.njol.skript.log.LogEntry;
import ch.njol.skript.log.LogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.log.Verbosity;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.registrations.Comparators;
import ch.njol.skript.registrations.Converters;
import ch.njol.skript.registrations.EventValues;
import ch.njol.skript.util.ExceptionUtils;
import ch.njol.skript.util.FileUtils;
import ch.njol.skript.util.Getter;
import ch.njol.skript.util.Task;
import ch.njol.skript.util.Utils;
import ch.njol.skript.util.Version;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Closeable;
import ch.njol.util.Kleenean;
import ch.njol.util.NullableChecker;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.CollectionUtils;
import ch.njol.util.coll.iterator.CheckedIterator;
import ch.njol.util.coll.iterator.EnumerationIterable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* <b>Skript</b> - A Bukkit plugin to modify how Minecraft behaves without having to write a single line of code (You'll likely be writing some code though if you're reading this
* =P)
* <p>
* Use this class to extend this plugin's functionality by adding more {@link Condition conditions}, {@link Effect effects}, {@link SimpleExpression expressions}, etc.
* <p>
* If your plugin.yml contains <tt>'depend: [Skript]'</tt> then your plugin will not start at all if Skript is not present. Add <tt>'softdepend: [Skript]'</tt> to your plugin.yml
* if you want your plugin to work even if Skript isn't present, but want to make sure that Skript gets loaded before your plugin.
* <p>
* If you use 'softdepend' you can test whether Skript is loaded with <tt>'Bukkit.getPluginManager().getPlugin("Skript") != null'</tt>
* <p>
* Once you made sure that Skript is loaded you can use <code>Skript.getInstance()</code> whenever you need a reference to the plugin, but you likely won't need it since all API
* methods are static.
*
* @author Peter Güttinger
* @see #registerAddon(JavaPlugin)
* @see #registerCondition(Class, String...)
* @see #registerEffect(Class, String...)
* @see #registerExpression(Class, Class, ExpressionType, String...)
* @see #registerEvent(String, Class, Class, String...)
* @see EventValues#registerEventValue(Class, Class, Getter, int)
* @see Classes#registerClass(ClassInfo)
* @see Comparators#registerComparator(Class, Class, Comparator)
* @see Converters#registerConverter(Class, Class, Converter)
*/
@SuppressFBWarnings("REC_CATCH_EXCEPTION")
public final class Skript extends JavaPlugin implements Listener {
// ================ PLUGIN ================
@Nullable
private static Skript instance = null;
private static boolean disabled = false;
public static Skript getInstance() {
final Skript i = instance;
if (i == null)
throw new IllegalStateException();
return i;
}
public Skript() throws IllegalStateException {
if (instance != null)
throw new IllegalStateException("Cannot create multiple instances of Skript!");
instance = this;
}
@Nullable
private static Version version = null;
public static Version getVersion() {
final Version v = version;
if (v == null)
throw new IllegalStateException();
return v;
}
public final static Message m_invalid_reload = new Message("skript.invalid reload"),
m_finished_loading = new Message("skript.finished loading");
@Override
public void onEnable() {
if (disabled) {
Skript.error(m_invalid_reload.toString());
setEnabled(false);
return;
}
Language.loadDefault(getAddonInstance());
Workarounds.init();
version = new Version("" + getDescription().getVersion());
runningCraftBukkit = Bukkit.getServer().getClass().getName().equals("org.bukkit.craftbukkit.CraftServer");
final String bukkitV = Bukkit.getBukkitVersion();
final Matcher m = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?").matcher(bukkitV);
if (!m.find()) {
Skript.error("The Bukkit version '" + bukkitV + "' does not contain a version number which is required for Skript to enable or disable certain features. " +
"Skript will still work, but you might get random errors if you use features that are not available in your version of Bukkit.");
minecraftVersion = new Version(666, 0, 0);
} else {
minecraftVersion = new Version("" + m.group());
}
if (!getDataFolder().isDirectory())
getDataFolder().mkdirs();
final File scripts = new File(getDataFolder(), SCRIPTSFOLDER);
if (!scripts.isDirectory()) {
ZipFile f = null;
try {
if (!scripts.mkdirs())
throw new IOException("Could not create the directory " + scripts);
f = new ZipFile(getFile());
for (final ZipEntry e : new EnumerationIterable<ZipEntry>(f.entries())) {
if (e.isDirectory())
continue;
File saveTo = null;
if (e.getName().startsWith(SCRIPTSFOLDER + "/")) {
final String fileName = e.getName().substring(e.getName().lastIndexOf('/') + 1);
saveTo = new File(scripts, (fileName.startsWith("-") ? "" : "-") + fileName);
} else if (e.getName().equals("config.sk")) {
final File cf = new File(getDataFolder(), e.getName());
if (!cf.exists())
saveTo = cf;
} else if (e.getName().startsWith("aliases-") && e.getName().endsWith(".sk") && !e.getName().contains("/")) {
final File af = new File(getDataFolder(), e.getName());
if (!af.exists())
saveTo = af;
}
if (saveTo != null) {
final InputStream in = f.getInputStream(e);
try {
assert in != null;
FileUtils.save(in, saveTo);
} finally {
in.close();
}
}
}
info("Successfully generated the config, the example scripts and the aliases files.");
} catch (final ZipException e) {} catch (final IOException e) {
error("Error generating the default files: " + ExceptionUtils.toString(e));
} finally {
if (f != null) {
try {
f.close();
} catch (final IOException e) {}
}
}
}
getCommand("skript").setExecutor(new SkriptCommand());
new JavaClasses();
new BukkitClasses();
new BukkitEventValues();
new SkriptClasses();
new DefaultComparators();
new DefaultConverters();
new DefaultFunctions();
try {
getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity");
} catch (final Exception e) {
exception(e, "Could not load required .class files: " + e.getLocalizedMessage());
setEnabled(false);
return;
}
SkriptConfig.load();
Language.setUseLocal(true);
Updater.start();
Aliases.load();
Commands.registerListeners();
if (logNormal())
info(" " + Language.get("skript.copyright"));
final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0;
Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
@SuppressWarnings("synthetic-access")
@Override
public void run() {
assert Bukkit.getWorlds().get(0).getFullTime() == tick;
// load hooks
try {
final JarFile jar = new JarFile(getFile());
try {
for (final JarEntry e : new EnumerationIterable<JarEntry>(jar.entries())) {
if (e.getName().startsWith("ch/njol/skript/hooks/") && e.getName().endsWith("Hook.class") && StringUtils.count("" + e.getName(), '/') <= 5) {
final String c = e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length());
try {
final Class<?> hook = Class.forName(c, true, getClassLoader());
if (hook != null && Hook.class.isAssignableFrom(hook) && !hook.isInterface() && Hook.class != hook) {
hook.getDeclaredConstructor().setAccessible(true);
hook.getDeclaredConstructor().newInstance();
}
} catch (final ClassNotFoundException ex) {
Skript.exception(ex, "Cannot load class " + c);
} catch (final ExceptionInInitializerError err) {
Skript.exception(err.getCause(), "Class " + c + " generated an exception while loading");
}
continue;
// } else if (Documentation.generate) {
// try {
// Class.forName(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()), true, getClassLoader());
// } catch (final Exception ex) {}
}
}
} finally {
try {
jar.close();
} catch (final IOException e) {}
}
} catch (final Exception e) {
error("Error while loading plugin hooks" + (e.getLocalizedMessage() == null ? "" : ": " + e.getLocalizedMessage()));
if (testing())
e.printStackTrace();
}
Language.setUseLocal(false);
stopAcceptingRegistrations();
Documentation.generate(); // TODO move to test classes?
if (logNormal())
info("Loading variables...");
final long vls = System.currentTimeMillis();
final LogHandler h = SkriptLogger.startLogHandler(new ErrorDescLogHandler() {
private final List<LogEntry> log = new ArrayList<LogEntry>();
@Override
public LogResult log(final LogEntry entry) {
super.log(entry);
if (entry.level.intValue() >= Level.SEVERE.intValue()) {
logEx(entry.message); // no [Skript] prefix
return LogResult.DONT_LOG;
} else {
log.add(entry);
return LogResult.CACHED;
}
}
@Override
protected void beforeErrors() {
logEx();
logEx("===!!!=== Skript variable load error ===!!!===");
logEx("Unable to load (all) variables:");
}
@Override
protected void afterErrors() {
logEx();
logEx("Skript will work properly, but old variables might not be available at all and new ones may or may not be saved until Skript is able to create a backup of the old file and/or is able to connect to the database (which requires a restart of Skript)!");
logEx();
}
@Override
protected void onStop() {
super.onStop();
SkriptLogger.logAll(log);
}
});
final CountingLogHandler c = SkriptLogger.startLogHandler(new CountingLogHandler(SkriptLogger.SEVERE));
try {
if (!Variables.load())
if (c.getCount() == 0)
error("(no information available)");
} finally {
c.stop();
h.stop();
}
final long vld = System.currentTimeMillis() - vls;
if (logNormal())
info("Loaded " + Variables.numVariables() + " variables in " + ((vld / 100) / 10.) + " seconds");
ScriptLoader.loadScripts();
Skript.info(m_finished_loading.toString());
EvtSkript.onSkriptStart();
final Metrics metrics = new Metrics(Skript.this);
final Graph scriptData = metrics.createGraph("data");
scriptData.addPlotter(new Plotter("scripts") {
@Override
public int getValue() {
return ScriptLoader.loadedScripts();
}
});
scriptData.addPlotter(new Plotter("triggers") {
@Override
public int getValue() {
return ScriptLoader.loadedTriggers();
}
});
scriptData.addPlotter(new Plotter("commands") {
@Override
public int getValue() {
return ScriptLoader.loadedCommands();
}
});
scriptData.addPlotter(new Plotter("functions") {
@Override
public int getValue() {
return ScriptLoader.loadedFunctions();
}
});
scriptData.addPlotter(new Plotter("variables") {
@Override
public int getValue() {
return Variables.numVariables();
}
});
final Graph language = metrics.createGraph("language");
language.addPlotter(new Plotter() {
@Override
public int getValue() {
return 1;
}
@Override
public String getColumnName() {
return Language.getName();
}
});
final Graph similarPlugins = metrics.createGraph("similar plugins");
for (final String plugin : new String[] {"VariableTriggers", "CommandHelper", "Denizen", "rTriggers", "kTriggers", "TriggerCmds", "BlockScripts", "ScriptBlock", "buscript", "BukkitScript"}) {
assert plugin != null;
similarPlugins.addPlotter(new Plotter(plugin) {
@Override
public int getValue() {
return Bukkit.getPluginManager().getPlugin(plugin) != null ? 1 : 0;
}
});
}
metrics.start();
Skript.metrics = metrics;
// suppresses the "can't keep up" warning after loading all scripts
final Filter f = new Filter() {
@Override
public boolean isLoggable(final @Nullable LogRecord record) {
if (record == null)
return false;
if (record.getMessage() != null && record.getMessage().toLowerCase().startsWith("can't keep up!"))
return false;
return true;
}
};
BukkitLoggerFilter.addFilter(f);
Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.this, new Runnable() {
@Override
public void run() {
BukkitLoggerFilter.removeFilter(f);
}
}, 1);
}
});
Bukkit.getPluginManager().registerEvents(new Listener() {
@EventHandler
public void onJoin(final PlayerJoinEvent e) {
if (e.getPlayer().hasPermission("skript.admin")) {
new Task(Skript.this, 0) {
@Override
public void run() {
Updater.stateLock.readLock().lock();
try {
final Player p = e.getPlayer();
assert p != null;
if ((Updater.state == UpdateState.CHECKED_FOR_UPDATE || Updater.state == UpdateState.DOWNLOAD_ERROR) && Updater.latest.get() != null)
info(p, "" + Updater.m_update_available);
} finally {
Updater.stateLock.readLock().unlock();
}
}
};
}
}
}, this);
}
private static Version minecraftVersion = new Version(666);
private static boolean runningCraftBukkit = false;
public static Version getMinecraftVersion() {
return minecraftVersion;
}
/**
* @return Whether this server is running CraftBukkit
*/
public static boolean isRunningCraftBukkit() {
return runningCraftBukkit;
}
/**
* @return Whether this server is running Minecraft <tt>major.minor</tt> or higher
*/
public static boolean isRunningMinecraft(final int major, final int minor) {
return minecraftVersion.compareTo(major, minor) >= 0;
}
public static boolean isRunningMinecraft(final int major, final int minor, final int revision) {
return minecraftVersion.compareTo(major, minor, revision) >= 0;
}
public static boolean isRunningMinecraft(final Version v) {
return minecraftVersion.compareTo(v) >= 0;
}
/**
* Used to test whether certain Bukkit features are supported.
*
* @param className
* @return Whether the given class exists.
*/
public final static boolean supports(final String className) {
try {
Class.forName(className);
return true;
} catch (final ClassNotFoundException e) {
return false;
}
}
@Nullable
static Metrics metrics;
@Nullable
public static Metrics getMetrics() {
return metrics;
}
/**
* Clears triggers, commands, functions and variable names
*/
final static void disableScripts() {
VariableString.variableNames.clear();
SkriptEventHandler.removeAllTriggers();
Commands.clearCommands();
Functions.clearFunctions();
}
/**
* Prints errors from reloading the config & scripts
*/
final static void reload() {
disableScripts();
reloadMainConfig();
reloadAliases();
ScriptLoader.loadScripts();
}
/**
* Prints errors
*/
final static void reloadScripts() {
disableScripts();
ScriptLoader.loadScripts();
}
/**
* Prints errors
*/
final static void reloadMainConfig() {
SkriptConfig.load();
}
/**
* Prints errors
*/
final static void reloadAliases() {
Aliases.clear();
Aliases.load();
}
@SuppressWarnings("null")
private final static Collection<Closeable> closeOnDisable = Collections.synchronizedCollection(new ArrayList<Closeable>());
/**
* Registers a Closeable that should be closed when this plugin is disabled.
* <p>
* All registered Closeables will be closed after all scripts have been stopped.
*
* @param closeable
*/
public static void closeOnDisable(final Closeable closeable) {
closeOnDisable.add(closeable);
}
@Override
public void onDisable() {
if (disabled)
return;
disabled = true;
EvtSkript.onSkriptStop(); // TODO [code style] warn user about delays in Skript stop events
disableScripts();
Bukkit.getScheduler().cancelTasks(this);
for (final Closeable c : closeOnDisable) {
try {
c.close();
} catch (final Exception e) {
Skript.exception(e, "An error occurred while shutting down.", "This might or might not cause any issues.");
}
}
// unset static fields to prevent memory leaks as Bukkit reloads the classes with a different classloader on reload
// async to not slow down server reload, delayed to not slow down server shutdown
final Thread t = newThread(new Runnable() {
@SuppressWarnings("synthetic-access")
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (final InterruptedException e) {}
try {
final Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
final JarFile jar = new JarFile(getFile());
try {
for (final JarEntry e : new EnumerationIterable<JarEntry>(jar.entries())) {
if (e.getName().endsWith(".class")) {
try {
final Class<?> c = Class.forName(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()), false, getClassLoader());
for (final Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()) && !f.getType().isPrimitive()) {
if (Modifier.isFinal(f.getModifiers())) {
modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
}
f.setAccessible(true);
f.set(null, null);
}
}
} catch (final Throwable ex) {}
}
}
} finally {
jar.close();
}
} catch (final Throwable ex) {
if (testing())
ex.printStackTrace();
}
}
}, "Skript cleanup thread");
t.setPriority(Thread.MIN_PRIORITY);
t.setDaemon(true);
t.start();
}
// ================ CONSTANTS, OPTIONS & OTHER ================
public final static String SCRIPTSFOLDER = "scripts";
public static void outdatedError() {
error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript.");
}
public static void outdatedError(final Exception e) {
outdatedError();
if (testing())
e.printStackTrace();
}
/**
* A small value, useful for comparing doubles or floats.
* <p>
* E.g. to test whether two floating-point numbers are equal:
*
* <pre>
* Math.abs(a - b) < Skript.EPSILON
* </pre>
*
* or whether a location is within a specific radius of another location:
*
* <pre>
* location.distanceSquared(center) - radius * radius < Skript.EPSILON
* </pre>
*
* @see #EPSILON_MULT
*/
public final static double EPSILON = 1e-10;
/**
* A value a bit larger than 1
*
* @see #EPSILON
*/
public final static double EPSILON_MULT = 1.00001;
/**
* The maximum ID a block can have in Minecraft.
*/
public final static int MAXBLOCKID = 255;
/**
* The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE.
*/
public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE;
public final static String toString(final double n) {
return StringUtils.toString(n, SkriptConfig.numberAccuracy.value());
}
public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() {
@Override
public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) {
Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName()));
}
};
/**
* Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically.
*/
public final static Thread newThread(final Runnable r, final String name) {
final Thread t = new Thread(r, name);
t.setUncaughtExceptionHandler(UEH);
return t;
}
// ================ REGISTRATIONS ================
private static boolean acceptRegistrations = true;
public static boolean isAcceptRegistrations() {
return acceptRegistrations;
}
public static void checkAcceptRegistrations() {
if (!acceptRegistrations)
throw new SkriptAPIException("Registering is disabled after initialisation!");
}
private static void stopAcceptingRegistrations() {
acceptRegistrations = false;
Converters.createMissingConverters();
Classes.onRegistrationsStop();
}
// ================ ADDONS ================
private final static HashMap<String, SkriptAddon> addons = new HashMap<String, SkriptAddon>();
/**
* Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements
* and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes).
*
* @param p The plugin
*/
public static SkriptAddon registerAddon(final JavaPlugin p) {
checkAcceptRegistrations();
if (addons.containsKey(p.getName()))
throw new IllegalArgumentException("The plugin " + p.getName() + " is already registered");
final SkriptAddon addon = new SkriptAddon(p);
addons.put(p.getName(), addon);
return addon;
}
@Nullable
public static SkriptAddon getAddon(final JavaPlugin p) {
return addons.get(p.getName());
}
@Nullable
public static SkriptAddon getAddon(final String name) {
return addons.get(name);
}
@SuppressWarnings("null")
public static Collection<SkriptAddon> getAddons() {
return Collections.unmodifiableCollection(addons.values());
}
@Nullable
private static SkriptAddon addon;
/**
* @return A {@link SkriptAddon} representing Skript.
*/
public static SkriptAddon getAddonInstance() {
final SkriptAddon a = addon;
if (a == null)
return addon = new SkriptAddon(Skript.getInstance())
.setLanguageFileDirectory("lang");
else
return a;
}
// ================ CONDITIONS & EFFECTS ================
private final static Collection<SyntaxElementInfo<? extends Condition>> conditions = new ArrayList<SyntaxElementInfo<? extends Condition>>(50);
private final static Collection<SyntaxElementInfo<? extends Effect>> effects = new ArrayList<SyntaxElementInfo<? extends Effect>>(50);
private final static Collection<SyntaxElementInfo<? extends Statement>> statements = new ArrayList<SyntaxElementInfo<? extends Statement>>(100);
/**
* registers a {@link Condition}.
*
* @param condition The condition's class
* @param patterns Skript patterns to match this condition
*/
public static <E extends Condition> void registerCondition(final Class<E> condition, final String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
final SyntaxElementInfo<E> info = new SyntaxElementInfo<E>(patterns, condition);
conditions.add(info);
statements.add(info);
}
/**
* Registers an {@link Effect}.
*
* @param effect The effect's class
* @param patterns Skript patterns to match this effect
*/
public static <E extends Effect> void registerEffect(final Class<E> effect, final String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
final SyntaxElementInfo<E> info = new SyntaxElementInfo<E>(patterns, effect);
effects.add(info);
statements.add(info);
}
public static Collection<SyntaxElementInfo<? extends Statement>> getStatements() {
return statements;
}
public static Collection<SyntaxElementInfo<? extends Condition>> getConditions() {
return conditions;
}
public static Collection<SyntaxElementInfo<? extends Effect>> getEffects() {
return effects;
}
// ================ EXPRESSIONS ================
private final static List<ExpressionInfo<?, ?>> expressions = new ArrayList<ExpressionInfo<?, ?>>(100);
private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length];
/**
* Registers an expression.
*
* @param c The expression's class
* @param returnType The superclass of all values returned by the expression
* @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions.
* @param patterns Skript patterns that match this expression
* @throws IllegalArgumentException if returnType is not a normal class
*/
public static <E extends Expression<T>, T> void registerExpression(final Class<E> c, final Class<T> returnType, final ExpressionType type, final String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
if (returnType.isAnnotation() || returnType.isArray() || returnType.isPrimitive())
throw new IllegalArgumentException("returnType must be a normal type");
final ExpressionInfo<E, T> info = new ExpressionInfo<E, T>(patterns, returnType, c);
for (int i = type.ordinal() + 1; i < ExpressionType.values().length; i++) {
expressionTypesStartIndices[i]++;
}
expressions.add(expressionTypesStartIndices[type.ordinal()], info);
}
@SuppressWarnings("null")
public static Iterator<ExpressionInfo<?, ?>> getExpressions() {
return expressions.iterator();
}
public static Iterator<ExpressionInfo<?, ?>> getExpressions(final Class<?>... returnTypes) {
return new CheckedIterator<ExpressionInfo<?, ?>>(getExpressions(), new NullableChecker<ExpressionInfo<?, ?>>() {
@Override
public boolean check(final @Nullable ExpressionInfo<?, ?> i) {
if (i == null || i.returnType == Object.class)
return true;
for (final Class<?> returnType : returnTypes) {
assert returnType != null;
if (Converters.converterExists(i.returnType, returnType))
return true;
}
return false;
}
});
}
// ================ EVENTS ================
private final static Collection<SkriptEventInfo<?>> events = new ArrayList<SkriptEventInfo<?>>(50);
/**
* Registers an event.
*
* @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and
* the documentation.
* @param c The event's class
* @param event The Bukkit event this event applies to
* @param patterns Skript patterns to match this event
* @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation.
*/
@SuppressWarnings("unchecked")
public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(final String name, final Class<E> c, final Class<? extends Event> event, final String... patterns) {
checkAcceptRegistrations();
final SkriptEventInfo<E> r = new SkriptEventInfo<E>(name, patterns, c, CollectionUtils.array(event));
events.add(r);
return r;
}
/**
* Registers an event.
*
* @param name The name of the event, used for error messages
* @param c The event's class
* @param events The Bukkit events this event applies to
* @param patterns Skript patterns to match this event
* @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation.
*/
public static <E extends SkriptEvent> SkriptEventInfo<E> registerEvent(final String name, final Class<E> c, final Class<? extends Event>[] events, final String... patterns) {
checkAcceptRegistrations();
final SkriptEventInfo<E> r = new SkriptEventInfo<E>(name, patterns, c, events);
Skript.events.add(r);
return r;
}
public final static Collection<SkriptEventInfo<?>> getEvents() {
return events;
}
// ================ COMMANDS ================
/**
* Dispatches a command with calling command events
*
* @param sender
* @param command
* @return Whether the command was run
*/
public final static boolean dispatchCommand(final CommandSender sender, final String command) {
try {
if (sender instanceof Player) {
final PlayerCommandPreprocessEvent e = new PlayerCommandPreprocessEvent((Player) sender, "/" + command);
Bukkit.getPluginManager().callEvent(e);
if (e.isCancelled() || e.getMessage() == null || !e.getMessage().startsWith("/"))
return false;
return Bukkit.dispatchCommand(e.getPlayer(), e.getMessage().substring(1));
} else {
final ServerCommandEvent e = new ServerCommandEvent(sender, command);
Bukkit.getPluginManager().callEvent(e);
if (e.getCommand() == null || e.getCommand().isEmpty())
return false;
return Bukkit.dispatchCommand(e.getSender(), e.getCommand());
}
} catch (final Exception ex) {
ex.printStackTrace(); // just like Bukkit
return false;
}
}
// ================ LOGGING ================
public final static boolean logNormal() {
return SkriptLogger.log(Verbosity.NORMAL);
}
public final static boolean logHigh() {
return SkriptLogger.log(Verbosity.HIGH);
}
public final static boolean logVeryHigh() {
return SkriptLogger.log(Verbosity.VERY_HIGH);
}
public final static boolean debug() {
return SkriptLogger.debug();
}
public final static boolean testing() {
return debug() || Skript.class.desiredAssertionStatus();
}
public final static boolean log(final Verbosity minVerb) {
return SkriptLogger.log(minVerb);
}
public static void debug(final String info) {
SkriptLogger.log(SkriptLogger.DEBUG, info);
}
/**
* @see SkriptLogger#log(Level, String)
*/
@SuppressWarnings("null")
public static void info(final String info) {
SkriptLogger.log(Level.INFO, info);
}
/**
* @see SkriptLogger#log(Level, String)
*/
@SuppressWarnings("null")
public static void warning(final String warning) {
SkriptLogger.log(Level.WARNING, warning);
}
/**
* @see SkriptLogger#log(Level, String)
*/
@SuppressWarnings("null")
public static void error(final @Nullable String error) {
if (error != null)
SkriptLogger.log(Level.SEVERE, error);
}
/**
* Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log
* errors with a specific {@link ErrorQuality}.
*
* @param error
* @param quality
*/
public static void error(final String error, final ErrorQuality quality) {
SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error));
}
private final static String EXCEPTION_PREFIX = "#!#! ";
/**
* Used if something happens that shouldn't happen
*
* @param info Description of the error and additional information
* @return an EmptyStackException to throw if code execution should terminate.
*/
public final static EmptyStackException exception(final String... info) {
return exception(null, info);
}
public final static EmptyStackException exception(final @Nullable Throwable cause, final String... info) {
return exception(cause, null, null, info);
}
public final static EmptyStackException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) {
return exception(cause, thread, null, info);
}
public final static EmptyStackException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) {
return exception(cause, null, item, info);
}
/**
* Used if something happens that shouldn't happen
*
* @param cause exception that shouldn't occur
* @param info Description of the error and additional information
* @return an EmptyStackException to throw if code execution should terminate.
*/
public final static EmptyStackException exception(@Nullable Throwable cause, final @Nullable Thread thread, final @Nullable TriggerItem item, final String... info) {
logEx();
logEx("[Skript] Severe Error:");
logEx(info);
logEx();
logEx("If you're developing an add-on for Skript this likely means that you have done something wrong.");
logEx("If you're a server admin however please go to http://dev.bukkit.org/server-mods/skript/tickets/");
logEx("and check whether this error has already been reported.");
logEx("If not please create a new ticket with a meaningful title, copy & paste this whole error into it,");
logEx("and describe what you did before it happened and/or what you think caused the error.");
logEx("If you think that it's a trigger that's causing the error please post the trigger as well.");
logEx("By following this guide fixing the error should be easy and done fast.");
logEx();
logEx("Stack trace:");
if (cause == null || cause.getStackTrace().length == 0) {
logEx(" warning: no/empty exception given, dumping current stack trace instead");
cause = new Exception(cause);
}
boolean first = true;
while (cause != null) {
logEx((first ? "" : "Caused by: ") + cause.toString());
for (final StackTraceElement e : cause.getStackTrace())
logEx(" at " + e.toString());
cause = cause.getCause();
first = false;
}
logEx();
logEx("Version Information:");
logEx(" Skript: " + getVersion());
logEx(" Bukkit: " + Bukkit.getBukkitVersion());
logEx(" Minecraft: " + getMinecraftVersion());
logEx(" Java: " + System.getProperty("java.version") + " (" + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version") + ")");
logEx(" OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version"));
logEx();
logEx("Running CraftBukkit: " + runningCraftBukkit);
logEx();
logEx("Current node: " + SkriptLogger.getNode());
logEx("Current item: " + (item == null ? "null" : item.toString(null, true)));
logEx();
logEx("Thread: " + (thread == null ? Thread.currentThread() : thread).getName());
logEx();
logEx("End of Error.");
logEx();
return new EmptyStackException();
}
final static void logEx() {
SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX);
}
final static void logEx(final String... lines) {
for (final String line : lines)
SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line);
}
public static String SKRIPT_PREFIX = ChatColor.GRAY + "[" + ChatColor.GOLD + "Skript" + ChatColor.GRAY + "]" + ChatColor.RESET + " ";
// static {
// Language.addListener(new LanguageChangeListener() {
// @Override
// public void onLanguageChange() {
// final String s = Language.get_("skript.prefix");
// if (s != null)
// SKRIPT_PREFIX = Utils.replaceEnglishChatStyles(s) + ChatColor.RESET + " ";
// }
// });
// }
public static void info(final CommandSender sender, final String info) {
sender.sendMessage(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(info));
}
/**
* @param message
* @param permission
* @see #adminBroadcast(String)
*/
public static void broadcast(final String message, final String permission) {
Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), permission);
}
public static void adminBroadcast(final String message) {
Bukkit.broadcast(SKRIPT_PREFIX + Utils.replaceEnglishChatStyles(message), "skript.admin");
}
/**
* Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added.
*
* @param sender
* @param info
*/
public static void message(final CommandSender sender, final String info) {
sender.sendMessage(Utils.replaceEnglishChatStyles(info));
}
public static void error(final CommandSender sender, final String error) {
sender.sendMessage(SKRIPT_PREFIX + ChatColor.DARK_RED + Utils.replaceEnglishChatStyles(error));
}
}