package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.abstraction.Implementation;
import com.laytonsmith.annotations.api;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.Optimizable;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CBoolean;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.events.Event;
import com.laytonsmith.core.events.EventList;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.extensions.ExtensionManager;
import com.laytonsmith.core.extensions.ExtensionTracker;
import java.net.URL;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Jason Unger <entityreborn@gmail.com>
*/
public class ExtensionMeta {
public static String docs() {
return "Provides the ability for finding out information about installed"
+ " extensions, including events and functions.";
}
@api
public static class function_exists extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
try {
FunctionList.getFunction(args[0].val().toLowerCase(), t);
} catch (ConfigCompileException ex) {
return CBoolean.FALSE;
}
return CBoolean.TRUE;
}
@Override
public String getName() {
return "function_exists";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "boolean {name} Returns true if the function is known to "
+ Implementation.GetServerType().getBranding() + ". This is a special function; it"
+ " is resolved at compile time, and allows for conditional uses of functions that"
+ " may or may not exist, such as functions that might or might not be loaded in an extension,"
+ " or from different versions."
+ " This is useful for shared code in environments where an extension may or may not"
+ " be available, or an older version of " + Implementation.GetServerType().getBranding()
+ ". if(function_exists('my_extension_function')){ my_extension_function() } can"
+ " then be used to selectively \"bypass\" the compiler restrictions that would normally cause a fatal"
+ " compile error, since that function is missing. Therefore, you can wrap extension related code"
+ " around extension specific blocks, and make that code portable to other installations that"
+ " may not have the extension installed.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
if (children.size() != 1) {
throw new ConfigCompileException(getName() + " can only accept one argument", t);
}
if (!(children.get(0).getData() instanceof CString)) {
throw new ConfigCompileException(getName() + " can only accept hardcoded string values", t);
}
return new ParseTree(this.exec(t, null, children.get(0).getData()), children.get(0).getFileOptions());
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return new ExampleScript[]{
new ExampleScript("Wrapping a block of code that uses extension functions",
"if(function_exists('my_function')){\n"
+ "\tmy_function()\n"
+ "}\n", "<No errors will occur if the extension that contains my_function() isn't loaded>")
};
}
}
@api
@seealso(function_exists.class)
public static class event_exists extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
return CBoolean.get(EventList.getEvent(args[0].val().toLowerCase()) != null);
}
@Override
public String getName() {
return "event_exists";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "boolean {name} Returns true if the event is known to "
+ Implementation.GetServerType().getBranding() + "."
+ " Like function_exists(), this function is resolved at compile time,"
+ " and allows for conditional uses of events that may or may not exist.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
if (children.size() != 1) {
throw new ConfigCompileException(getName() + " can only accept one argument", t);
}
if (!(children.get(0).getData() instanceof CString)) {
throw new ConfigCompileException(getName() + " can only accept hardcoded string values", t);
}
return new ParseTree(this.exec(t, null, children.get(0).getData()), children.get(0).getFileOptions());
}
}
@api
@seealso(function_exists.class)
public static class extension_exists extends AbstractFunction implements Optimizable {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
Map<URL, ExtensionTracker> trackers = ExtensionManager.getTrackers();
for (ExtensionTracker tracker : trackers.values()) {
String identifier = tracker.getIdentifier();
if ((identifier != null) && identifier.equalsIgnoreCase(args[0].val())) {
return CBoolean.TRUE;
}
}
return CBoolean.FALSE;
}
@Override
public String getName() {
return "extension_exists";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "boolean {name} Returns true if the extention is known to "
+ Implementation.GetServerType().getBranding() + " and loaded."
+ " Like function_exists(), this function is resolved at compile time.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
@Override
public Set<OptimizationOption> optimizationOptions() {
return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC);
}
@Override
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
if (children.size() != 1) {
throw new ConfigCompileException(getName() + " can only accept one argument", t);
} else if (!(children.get(0).getData() instanceof CString)) {
throw new ConfigCompileException(getName() + " can only accept hardcoded string values", t);
} else {
return new ParseTree(this.exec(t, null, children.get(0).getData()), children.get(0).getFileOptions());
}
}
}
@api
public static class extension_info extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return false;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
Map<URL, ExtensionTracker> trackers = ExtensionManager.getTrackers();
CArray retn = CArray.GetAssociativeArray(t);
if (args.length == 0) {
for (URL url: trackers.keySet()) {
ExtensionTracker trk = trackers.get(url);
CArray trkdata;
if (!retn.containsKey(trk.getIdentifier())) {
trkdata = CArray.GetAssociativeArray(t);
} else {
trkdata = (CArray) retn.get(trk.getIdentifier(), t);
}
// For both events and functions, make sure we don't overwrite
// in cases of extensions with the same name. Shouldn't happen,
// but lets handle it nicely. This will ALWAYS happen for old
// style extensions, as they don't have an identifier.
CArray funcs;
if (!trkdata.containsKey("functions")) {
funcs = new CArray(t);
} else {
funcs = (CArray) trkdata.get("functions", t);
}
for (FunctionBase func: trk.getFunctions()) {
if (!funcs.contains(func.getName())) {
funcs.push(new CString(func.getName(), t), t);
}
}
funcs.sort(CArray.SortType.STRING_IC);
trkdata.set("functions", funcs, t);
CArray events;
if (!trkdata.containsKey("events")) {
events = new CArray(t);
} else {
events = (CArray) trkdata.get("events", t);
}
for (Event event: trk.getEvents()) {
events.push(new CString(event.getName(), t), t);
}
events.sort(CArray.SortType.STRING_IC);
trkdata.set("events", events, t);
trkdata.set("version", trk.getVersion().toString());
if (trk.getIdentifier() != null) {
retn.set(trk.getIdentifier(), trkdata, t);
} else {
retn.set("__unidentified__", trkdata, t);
}
}
} else {
for (ExtensionTracker tracker : trackers.values()) {
String identifier = tracker.getIdentifier();
if (identifier == null) {
identifier = "__unidentified__";
}
if (identifier.equals(args[0].val())) {
CArray functions = (retn.containsKey("functions")) ? (CArray) retn.get("functions", t) : new CArray(t);
for (FunctionBase function : tracker.getFunctions()) {
if (!functions.contains(function.getName())) {
functions.push(new CString(function.getName(), t), t);
}
}
functions.sort(CArray.SortType.STRING_IC);
retn.set("functions", functions, t);
CArray events = (retn.containsKey("events")) ? (CArray) retn.get("events", t) : new CArray(t);
for (Event event : tracker.getEvents()) {
if (!events.contains(event.getName())) {
events.push(new CString(event.getName(), t), t);
}
}
events.sort(CArray.SortType.STRING_IC);
retn.set("events", events, t);
retn.set("version", tracker.getVersion().toString(), t);
}
}
}
return retn;
}
@Override
public String getName() {
return "extension_info";
}
@Override
public Integer[] numArgs() {
return new Integer[]{0, 1};
}
@Override
public String docs() {
return "array {[extensionName]} Returns extension info for the extensions"
+ " the system has loaded, or the given extension if extensionName is specified. Included data will be events,"
+ " functions and version, keyed by the name of the extension"
+ " (or __unidentified__ if it's an old-style extension).";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
}