/* * 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.classes; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.Debuggable; import ch.njol.skript.lang.DefaultExpression; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.localization.Noun; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * @author Peter Güttinger * @param <T> The class this info is for */ @SuppressFBWarnings("DM_STRING_VOID_CTOR") public class ClassInfo<T> implements Debuggable { private final Class<T> c; private final String codeName; private final Noun name; @Nullable private DefaultExpression<T> defaultExpression = null; @Nullable private Parser<? extends T> parser = null; @Nullable private Pattern[] userInputPatterns = null; @Nullable private Changer<? super T> changer = null; @Nullable private Serializer<? super T> serializer = null; @Nullable private Class<?> serializeAs = null; @Nullable private Arithmetic<? super T, ?> math = null; @Nullable private Class<?> mathRelativeType = null; @Nullable private String docName = null; @Nullable private String[] description = null; @Nullable private String[] usage = null; @Nullable private String[] examples = null; @Nullable private String since = null; /** * @param c The class * @param codeName The name used in patterns */ public ClassInfo(final Class<T> c, final String codeName) { this.c = c; if (!isVaildCodeName(codeName)) throw new IllegalArgumentException("Code names for classes must be lowercase and only consist of latin letters and arabic numbers"); this.codeName = codeName; name = new Noun("types." + codeName); } public final static boolean isVaildCodeName(final String name) { return name.matches("[a-z0-9]+"); } // === FACTORY METHODS === /** * @param parser A parser to parse values of this class or null if not applicable */ public ClassInfo<T> parser(final Parser<? extends T> parser) { assert this.parser == null; this.parser = parser; return this; } /** * @param userInputPatterns <u>Regex</u> patterns to match this class, e.g. in the expressions loop-[type], random [type] out of ..., or as command arguments. These patterns * must be english and match singular and plural. * @throws PatternSyntaxException If any of the patterns' syntaxes is invalid */ public ClassInfo<T> user(final String... userInputPatterns) throws PatternSyntaxException { assert this.userInputPatterns == null; this.userInputPatterns = new Pattern[userInputPatterns.length]; for (int i = 0; i < userInputPatterns.length; i++) { this.userInputPatterns[i] = Pattern.compile(userInputPatterns[i]); } return this; } /** * @param defaultExpression The default (event) value of this class or null if not applicable * @see EventValueExpression * @see SimpleLiteral */ public ClassInfo<T> defaultExpression(final DefaultExpression<T> defaultExpression) { assert this.defaultExpression == null; if (!defaultExpression.isDefault()) throw new IllegalArgumentException("defaultExpression.isDefault() must return true for the default expression of a class"); this.defaultExpression = defaultExpression; return this; } public ClassInfo<T> serializer(final Serializer<? super T> serializer) { assert this.serializer == null; if (serializeAs != null) throw new IllegalStateException("Can't set a serializer if this class is set to be serialized as another one"); this.serializer = serializer; serializer.register(this); return this; } public ClassInfo<T> serializeAs(final Class<?> serializeAs) { assert this.serializeAs == null; if (serializer != null) throw new IllegalStateException("Can't set this class to be serialized as another one if a serializer is already set"); this.serializeAs = serializeAs; return this; } @Deprecated public ClassInfo<T> changer(final SerializableChanger<? super T> changer) { return changer((Changer<? super T>) changer); } public ClassInfo<T> changer(final Changer<? super T> changer) { assert this.changer == null; this.changer = changer; return this; } public <R> ClassInfo<T> math(final Class<R> relativeType, final Arithmetic<? super T, R> math) { assert this.math == null; this.math = math; mathRelativeType = relativeType; return this; } /** * Use this as {@link #name(String)} to suppress warnings about missing documentation. */ public final static String NO_DOC = new String(); /** * Only used for Skript's documentation. * * @param name * @return This ClassInfo object */ public ClassInfo<T> name(final String name) { assert this.docName == null; this.docName = name; return this; } /** * Only used for Skript's documentation. * * @param description * @return This ClassInfo object */ public ClassInfo<T> description(final String... description) { assert this.description == null; this.description = description; return this; } /** * Only used for Skript's documentation. * * @param usage * @return This ClassInfo object */ public ClassInfo<T> usage(final String... usage) { assert this.usage == null; this.usage = usage; return this; } /** * Only used for Skript's documentation. * * @param examples * @return This ClassInfo object */ public ClassInfo<T> examples(final String... examples) { assert this.examples == null; this.examples = examples; return this; } /** * Only used for Skript's documentation. * * @param since * @return This ClassInfo object */ public ClassInfo<T> since(final String since) { assert this.since == null; this.since = since; return this; } // === GETTERS === public Class<T> getC() { return c; } public Noun getName() { return name; } public String getCodeName() { return codeName; } @Nullable public DefaultExpression<T> getDefaultExpression() { return defaultExpression; } @Nullable public Parser<? extends T> getParser() { return parser; } @Nullable public Pattern[] getUserInputPatterns() { return userInputPatterns; } @Nullable public Changer<? super T> getChanger() { return changer; } @Nullable public Serializer<? super T> getSerializer() { return serializer; } @Nullable public Class<?> getSerializeAs() { return serializeAs; } @Nullable public Arithmetic<? super T, ?> getMath() { return math; } @Nullable public Class<?> getMathRelativeType() { return mathRelativeType; } @Nullable public String[] getDescription() { return description; } @Nullable public String[] getUsage() { return usage; } @Nullable public String[] getExamples() { return examples; } @Nullable public String getSince() { return since; } @Nullable public String getDocName() { return docName; } // === ORDERING === @Nullable private Set<String> before; private final Set<String> after = new HashSet<String>(); /** * Sets one or more classes that this class should occur before in the class info list. This only affects the order in which classes are parsed if it's unknown of which type * the parsed string is. * <p> * Please note that subclasses will always be registered before superclasses, no matter what is defined here or in {@link #after(String...)}. * <p> * This list can safely contain classes that may not exist. * * @param before * @return this ClassInfo */ public ClassInfo<T> before(final String... before) { assert this.before == null; this.before = new HashSet<String>(Arrays.asList(before)); return this; } /** * Sets one or more classes that this class should occur after in the class info list. This only affects the order in which classes are parsed if it's unknown of which type * the parsed string is. * <p> * Please note that subclasses will always be registered before superclasses, no matter what is defined here or in {@link #before(String...)}. * <p> * This list can safely contain classes that may not exist. * * @param after * @return this ClassInfo */ public ClassInfo<T> after(final String... after) { this.after.addAll(Arrays.asList(after)); return this; } /** * @return Set of classes that should be after this one. May return null. */ @Nullable public Set<String> before() { return before; } /** * @return Set of classes that should be before this one. Never returns null. */ public Set<String> after() { return after; } // === GENERAL === @Override @NonNull public String toString() { return getName().getSingular(); } public String toString(final int flags) { return getName().toString(flags); } @Override @NonNull public String toString(final @Nullable Event e, final boolean debug) { if (debug) return codeName + " (" + c.getCanonicalName() + ")"; return getName().getSingular(); } }