package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery;
import com.laytonsmith.PureUtilities.Common.StreamUtils;
import com.laytonsmith.annotations.core;
import com.laytonsmith.annotations.hide;
import com.laytonsmith.annotations.noprofile;
import com.laytonsmith.annotations.seealso;
import com.laytonsmith.core.Documentation;
import com.laytonsmith.core.LogLevel;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CClosure;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.CVoid;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.IVariable;
import com.laytonsmith.core.constructs.IVariableList;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.snapins.PackagePermission;
import com.laytonsmith.tools.docgen.DocGenTemplates;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
public abstract class AbstractFunction implements Function {
private boolean shouldProfile = true;
protected AbstractFunction() {
//If we have the noprofile annotation, cache that we don't want to profile.
shouldProfile = !this.getClass().isAnnotationPresent(noprofile.class);
}
/**
* By default, we return CVoid.
*
* @param t
* @param env
* @param nodes
* @return
*/
@Override
public Construct execs(Target t, Environment env, Script parent, ParseTree... nodes) {
return CVoid.VOID;
}
/**
* By default, we return false, because most functions do not need this
*
* @return
*/
@Override
public boolean useSpecialExec() {
return false;
}
/**
* Most functions should show up in the normal documentation. However,
* if this function shouldn't show up in the documentation, it should
* mark itself with the @hide annotation.
*
* @return
*/
@Override
public final boolean appearInDocumentation() {
return this.getClass().getAnnotation(hide.class) == null;
}
/**
* Just return null by default. Most functions won't get to this anyways,
* since canOptimize is returning false.
*
* @param t
* @param args
* @return
*/
public Construct optimize(Target t, Construct... args) throws ConfigCompileException {
return null;
}
/**
* It may be that a function can simply check for compile errors, but not
* optimize. In this case, it is appropriate to use this definition of
* optimizeDynamic, to return a value that will essentially make no changes,
* or in the case where it can optimize anyways, even if some values are
* undetermined at the moment.
*
* @param t
* @param children
* @return
*/
public ParseTree optimizeDynamic(Target t, List<ParseTree> children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException {
return null;
}
/**
* Most functions don't need the varlist.
*
* @param varList
*/
public void varList(IVariableList varList) {
}
/**
* Most functions want the atomic values, not the variable itself.
*
* @return
*/
@Override
public boolean preResolveVariables() {
return true;
}
@Override
public ExampleScript[] examples() throws ConfigCompileException {
return null;
}
@Override
public boolean shouldProfile() {
return shouldProfile;
}
@Override
public LogLevel profileAt() {
return LogLevel.VERBOSE;
}
@Override
public String profileMessage(Construct... args) {
StringBuilder b = new StringBuilder();
boolean first = true;
for (Construct ccc : args) {
if (!first) {
b.append(", ");
}
first = false;
if (ccc instanceof CArray) {
//Arrays take too long to toString, so we don't want to actually toString them here if
//we don't need to.
b.append("<arrayNotShown size:" + ((CArray)ccc).size() + ">");
} else if (ccc instanceof CClosure) {
//The toString of a closure is too long, so let's not output them either.
b.append("<closureNotShown>");
} else if (ccc instanceof CString) {
String val = ccc.val().replace("\\", "\\\\").replace("'", "\\'");
int max = 1000;
if(val.length() > max){
val = val.substring(0, max) + "... (" + (val.length() - max) + " more characters hidden)";
}
b.append("'").append(val).append("'");
} else if (ccc instanceof IVariable) {
b.append(((IVariable) ccc).getVariableName());
} else {
b.append(ccc.val());
}
}
return "Executing function: " + this.getName() + "(" + b.toString() + ")";
}
/**
* Returns the documentation for this function that is provided as an external resource.
* This is useful for functions that have especially long or complex documentation, and adding
* it as a string directly in code would be cumbersome.
* @return
*/
protected String getBundledDocs(){
return getBundledDocs(null);
}
/**
* Returns the documentation for this function that is provided as an external resource.
* This is useful for functions that have especially long or complex documentation, and adding
* it as a string directly in code would be cumbersome. To facilitate dynamic docs, templates
* can be provided, which will be replaced for you.
* @param map
* @return
*/
protected String getBundledDocs(Map<String, DocGenTemplates.Generator> map){
String template = StreamUtils.GetString(AbstractFunction.class.getResourceAsStream("/functionDocs/" + getName()));
if(map == null){
map = new HashMap<>();
}
return DocGenTemplates.DoTemplateReplacement(template, map);
}
@Override
public String profileMessageS(List<ParseTree> args) {
return "Executing function: " + this.getName() + "(<" + args.size() + " child"
+ (args.size() == 1 ? "" : "ren") + " not shown>)";
}
@Override
public PackagePermission getPermission() {
return PackagePermission.NO_PERMISSIONS_NEEDED;
}
@Override
public URL getSourceJar() {
return ClassDiscovery.GetClassContainer(this.getClass());
}
private final static Class[] EMPTY_CLASS = new Class[0];
/**
* Checks for the @seealso annotation on this class, and returns
* the value listed there. This is to prevent subclasses from inheriting
* the list from super classes.
* @return
*/
@Override
public final Class<? extends Documentation>[] seeAlso() {
seealso see = this.getClass().getAnnotation(seealso.class);
if(see == null){
return EMPTY_CLASS;
} else {
return see.value();
}
}
@Override
public final boolean isCore() {
Class c = this.getClass();
do{
if(c.getAnnotation(core.class) != null){
return true;
}
c = c.getDeclaringClass();
} while(c != null);
return false;
}
public void link(Target t, List<ParseTree> children) throws ConfigCompileException {
// Do nothing, as a default
}
}