/*
* 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();
}
}