package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.Common.OSUtils;
import com.laytonsmith.PureUtilities.Common.ReflectionUtils;
import com.laytonsmith.PureUtilities.Common.StreamUtils;
import com.laytonsmith.abstraction.MCPlayer;
import com.laytonsmith.abstraction.MCServer;
import com.laytonsmith.commandhelper.CommandHelperPlugin;
import com.laytonsmith.core.AliasCore;
import com.laytonsmith.core.MethodScriptCompiler;
import com.laytonsmith.core.MethodScriptComplete;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.Profiles;
import com.laytonsmith.core.Script;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.Variable;
import com.laytonsmith.core.environments.CommandHelperEnvironment;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.CRE.AbstractCREException;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.exceptions.ConfigCompileGroupException;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.persistence.DataSourceException;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URISyntaxException;
import java.util.ArrayList;
/**
* An example script is a self contained script that runs itself, and returns
* the output. This is used in documentation to show examples, and furthermore, since
* the code can be ACTUALLY run, examples are more guaranteed to show actual output
* (bugs and all). These somewhat function as unit tests as well, but are not necessarily
* meant to cover all possible use cases. The environment is mocked up in several cases,
* but there is no guarantee that functions will work. Since this is the case,
* output can be mocked, by using the appropriate constructor. This will bypass actually
* running the code, but will still output the information in the standard format.
*/
public class ExampleScript {
String description;
String originalScript;
ParseTree script;
String output;
StringBuilder playerOutput = null;
MCPlayer fakePlayer;
static MCServer fakeServer;
static Plugin fakePlugin;
static AliasCore fakeCore;
static boolean init = false;
/**
* Creates a new example script, where the output will come from the
* script itself.
* @param description
* @param script
*/
public ExampleScript(String description, String script) throws ConfigCompileException{
this(description, script, null, false);
}
public ExampleScript(String description, String script, boolean intentionalCompileError) throws ConfigCompileException{
this(description, script, null, intentionalCompileError);
}
/**
* Creates a new example script, but the output is also specified. Use
* this in cases where the script cannot be run.
* @param description
* @param script
* @param output
* @throws ConfigCompileException
*/
public ExampleScript(String description, String script, String output) throws ConfigCompileException{
this(description, script, output, false);
}
/**
* Works like {@link #ExampleScript(java.lang.String, java.lang.String, java.lang.String)} but prints
* a message in the normal output when a compile error is encountered, instead of triggering the exceptional
* handling.
* @param description
* @param script
* @param output
* @param intentionalCompileError
* @throws ConfigCompileException
*/
private ExampleScript(String description, String script, String output, boolean intentionalCompileError) throws ConfigCompileException{
this.description = description;
this.originalScript = script;
try{
this.script = MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, new File((OSUtils.GetOS() == OSUtils.OS.WINDOWS ? "C:\\" : "/") + "Examples.ms"), true));
this.output = output;
} catch(ConfigCompileException e){
if(intentionalCompileError){
this.output = "Causes compile error: " + e.getMessage();
}
} catch(ConfigCompileGroupException ex){
if(intentionalCompileError){
StringBuilder b = new StringBuilder();
b.append("Causes compile errors:\n");
for(ConfigCompileException e : ex.getList()){
b.append(e.getMessage()).append("\n");
}
this.output = b.toString();
}
}
playerOutput = new StringBuilder();
fakePlayer = (MCPlayer)Proxy.newProxyInstance(ExampleScript.class.getClassLoader(), new Class[]{MCPlayer.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("getName") || method.getName().equals("getDisplayName")){
return "Player";
}
if(method.getName().equals("getServer")){
return fakeServer;
}
if(method.getName().equals("sendMessage")){
playerOutput.append(args[0].toString()).append("\n");
}
if(method.getName().equals("isOnline")){
return true;
}
return genericReturn(method.getReturnType());
}
});
if(!init){
init = true;
fakeServer = (MCServer)Proxy.newProxyInstance(ExampleScript.class.getClassLoader(), new Class[]{MCServer.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return genericReturn(method.getReturnType());
}
});
final PluginManager bukkitPluginManager = (PluginManager)Proxy.newProxyInstance(ExampleScript.class.getClassLoader(), new Class[]{PluginManager.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StreamUtils.GetSystemOut().println(method.getReturnType().getSimpleName() + " " + method.getName());
return genericReturn(method.getReturnType());
}
});
final Server bukkitServer = (Server)Proxy.newProxyInstance(ExampleScript.class.getClassLoader(), new Class[]{Server.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StreamUtils.GetSystemOut().println(method.getReturnType().getSimpleName() + " " + method.getName());
if(method.getName().equals("getPluginManager")){
return bukkitPluginManager;
}
return genericReturn(method.getReturnType());
}
});
fakePlugin = (Plugin)Proxy.newProxyInstance(ExampleScript.class.getClassLoader(), new Class[]{Plugin.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
StreamUtils.GetSystemOut().println(method.getReturnType().getSimpleName() + " " + method.getName());
if(method.getName().equals("getServer")){
return bukkitServer;
}
return genericReturn(method.getReturnType());
}
});
fakeCore = new FakeCore();
ReflectionUtils.set(CommandHelperPlugin.class, "ac", fakeCore);
}
}
public String getDescription() {
return description;
}
public boolean isAutomatic(){
return output == null;
}
private class FakeCore extends AliasCore{
public FakeCore(){
super(null, null, null, null, null);
this.autoIncludes = new ArrayList<>();
}
}
private Object genericReturn(Class r){
if(r.isPrimitive()){
if(r == int.class){
return 0;
} else if(r == byte.class){
return (byte)0;
} else if(r == double.class){
return 0.0;
} else if(r == float.class){
return 0.0f;
} else if(r == char.class){
return '\0';
} else if(r == short.class){
return (short)0;
} else if(r == boolean.class){
return false;
} else { //long
return 0L;
}
} else {
if(r == String.class){
return "";
}
return null;
}
}
public String getScript(){
return originalScript;
}
public String getOutput() throws IOException, DataSourceException, URISyntaxException{
if(output != null){
return output;
}
Script s = Script.GenerateScript(script, Static.GLOBAL_PERMISSION);
Environment env;
try {
env = Static.GenerateStandaloneEnvironment();
} catch (Profiles.InvalidProfileException ex) {
throw new RuntimeException(ex);
}
env.getEnv(CommandHelperEnvironment.class).SetPlayer(fakePlayer);
final StringBuilder finalOutput = new StringBuilder();
String thrown = null;
try{
s.run(new ArrayList<Variable>(), env, new MethodScriptComplete() {
@Override
public void done(String output) {
if(output != null){
finalOutput.append(output);
}
}
});
} catch(ConfigRuntimeException e){
String name = e.getClass().getName();
if(e instanceof AbstractCREException){
name = ((AbstractCREException)e).getName();
}
thrown = "\n(Throws " + name + ": " + e.getMessage() + ")";
}
String playerOut = playerOutput.toString().trim();
String finalOut = finalOutput.toString().trim();
String out = (playerOut.equals("")?"":playerOut) + (finalOut.equals("")||!playerOut.trim().equals("") ?"":":" + finalOut);
if(thrown != null){
out += thrown;
}
return out;
}
}