SaacRpc.java
package eu.javaexperience.saac;
import java.io.PrintWriter;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import eu.javaexperience.annotation.FunctionDescription;
import eu.javaexperience.annotation.FunctionVariableDescription;
import eu.javaexperience.collection.map.NullMap;
import eu.javaexperience.collection.map.OneShotMap;
import eu.javaexperience.collection.map.SmallMap;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.functional.saac.AutocompleteProvider;
import eu.javaexperience.functional.saac.FunctionCreator;
import eu.javaexperience.functional.saac.Functions.Param;
import eu.javaexperience.functional.saac.Functions.PreparedFunction;
import eu.javaexperience.interfaces.simple.SimpleCall;
import eu.javaexperience.interfaces.simple.publish.SimplePublish1;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.log.ThreadLocalHookableLogFacility;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.resource.ReferenceCounted;
import eu.javaexperience.rpc.JavaClassRpcFunctions;
import eu.javaexperience.rpc.SimpleRpcRequest;
import eu.javaexperience.rpc.SimpleRpcSession;
import eu.javaexperience.saac.exceptions.SaacException;
import eu.javaexperience.text.Format;
import eu.javaexperience.verify.LanguageTranslatableValidationEntry;
import eu.javaexperience.verify.TranslationFriendlyValidationEntry;
/**
* TODO:
* - nice UI:
* //- horizontal/vertical agruments
* - close function containers, minimize content AND expand on hover
*
* - autocomplete:
* - Enum
* - Varaible
*
* - validation:
* - assemble_time/runtime/error
* - trace function position in hierarchy for error reporting/debug purposes
*
* - project mode usage:
* - specify project dir (browse JS API)
* - module version tracking
* - include functions
*
* - macro support:
* define small blocks and make appear during autocomplete
* (Store macros in JSON, beacuse we must be capable to restore editor state from json)
* */
public class SaacRpc
{
protected static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("SaacRpc"));
public static final JavaClassRpcFunctions<SimpleRpcRequest> DISPATCH = new JavaClassRpcFunctions<SimpleRpcRequest>(SaacRpc.class);
/******************************* RPC functions ********************************/
@FunctionDescription
(
functionDescription = "Sets the given parameters on the current Saac session, hence this values can be used int the PARAM variable."
+ "\n This feature has effect only in execution mode, not affects the stored functions.",
parameters =
{
@FunctionVariableDescription(description="Parameters in js object/map/associative array style.",mayNull=false,paramName="PARAM",type=Map.class)
},
returning = @FunctionVariableDescription(description="",mayNull=false,paramName="",type=FunctionDescriptor.class)
)
public static void setQueryParams(SimpleRpcRequest req, Map<String, Object> ps)
{
SimpleRpcSession sess = (SimpleRpcSession) req.getRpcSession();
if(null != ps)
{
sess.put("PARAM", ps);
}
}
@FunctionDescription
(
functionDescription = "List all available functions.",
parameters = {},
returning = @FunctionVariableDescription(description="Collection of function descriptors.",mayNull=false,paramName="",type=FunctionDescriptor[].class)
)
public static FunctionDescriptor[] listFunctions(SimpleRpcRequest req)
{
Map<String, PreparedFunction> fset = getSessionFunctionSet((SimpleRpcSession) req.getRpcSession());
Collection<PreparedFunction> fs = fset.values();
FunctionDescriptor[] ret = new FunctionDescriptor[fs.size()];
int i = 0;
for(PreparedFunction f:fs)
{
ret[i++] = new FunctionDescriptor(f);
}
return ret;
}
@FunctionDescription
(
functionDescription = "Offers functions/enums that can be accepted by the specified function's specified argument (by index).\n"
+ "Currently Saac RPC not provide type specifications from the returning and parameter types, and can't provide \n"
+ "type bounding instructions (eg is KeyVal<String, HashMap<Boolean,GetBy1<String,Object>>> suitable for ? extends Entry<String, ? extends Map<Boolean,GetBy1>>)\n"
+ "So in this version, you should get an offer. Eg.: if user wants to edit the 0th argument of function.aggregates.countUsers(GetBy1<Boolean,User> criteria)\n you might\n"
+ "call this function: offerForType(\\\"function.aggregates.countUsers\\\", 0, false, \\\"\\\", 0,0) to get the list of suitable functions that returns GetBy1<Boolean, User> functionObject.\n"
+ "Not that if the target type is an enum, enumeration elements and functions that return that class of enum is returned.\n"
+ "Also note that an argument might have custom AutocompleteProvider, which make possible to promt application specific strings, eg:\n"
+ "getRedisInteger(String key) [key might be prompted as string]",
parameters =
{
@FunctionVariableDescription(description="Id of the receiver function.",mayNull=false,paramName="functionId",type=String.class),
@FunctionVariableDescription(description="Index of the parent's paramter count.",mayNull=false,paramName="index",type=int.class),
@FunctionVariableDescription(description="Is the function variadic?",mayNull=false,paramName="isVariadic",type=boolean.class),
@FunctionVariableDescription(description="Search term, result may be filtered by this fraction of string.",mayNull=true,paramName="",type=String.class),
@FunctionVariableDescription(description="Where the user's cursor stands in the given term.",mayNull=true,paramName="cursorStartIndex",type=Integer.class),
@FunctionVariableDescription(description="",mayNull=true,paramName="cursorEndIndex",type=Integer.class)
},
returning = @FunctionVariableDescription(description="Collection of function descriptors.",mayNull=false,paramName="",type=FunctionDescriptor[].class)
)
public static Object offerForType
(
SimpleRpcRequest req,
String functionId,
Integer index,
Boolean variadic,//TODO determine variadic if null given
String term,
Integer cursorStartIndex,
Integer cursorEndIndex
)
{
ArrayList<Object> ret = new ArrayList<>();
Map<String, PreparedFunction> INDEX = getSessionFunctionSet((SimpleRpcSession) req.getRpcSession());
PreparedFunction parentFunction = INDEX.get(functionId);
if(null == parentFunction)
{
return null;
}
Param[] ps = parentFunction.getArgs();
if(0 == ps.length)
{
return Mirror.emptyObjectArray;
}
if(ps.length <= index)
{
index = ps.length-1;
}
Param p = ps[index];
AutocompleteProvider acp = p.getAutocompleteProvider();
if(null != acp)
{
if(null == cursorStartIndex)
{
cursorStartIndex = 0;
}
if(null == cursorEndIndex)
{
cursorEndIndex = 0;
}
if(cursorEndIndex < cursorStartIndex)
{
cursorEndIndex = cursorStartIndex;
}
acp.offerElement(term, cursorStartIndex, cursorEndIndex);
}
offerTypeV1(ret, INDEX, p, Boolean.TRUE == variadic);
return ret.toArray();
}
protected static void offerTypeV1
(
List<Object> offers,
Map<String, PreparedFunction> INDEX,
Param p,
boolean variadic
)
{
offerTypeV1(offers, INDEX, p.getType(), variadic);
}
protected static void offerTypeV1
(
List<Object> offers,
Map<String, PreparedFunction> INDEX,
Type req,
boolean variadic
)
{
Class reqType = Mirror.extracClass(req);
if(variadic)
{
req = reqType.getComponentType();
reqType = reqType.getComponentType();
}
/** TODO why
if(SimplePublish1.class == reqType)
{
return;
}
*/
if(reqType.isEnum())
{
for(Enum e:((Class<Enum>)reqType).getEnumConstants())
{
offers.add(new _enum(e));
}
}
if(req instanceof GenericArrayType)
{
req = ((GenericArrayType) req).getGenericComponentType();
}
//if(isUnbounded(toRet))
{
// return null;
}
//if parent function accepts SimplePublish, we can offer any function
//(done in client side)
//the offered function will be wrapped at assemble time
/*if(SimplePublish1.class.isAssignableFrom(toRet))
{
return null;
}*/
Collection<PreparedFunction> pps = INDEX.values();
for(PreparedFunction pp:pps)
{
Type fret = pp.getReturning().getType();
if(isAcceptable(req, fret))
{
offers.add(new FunctionDescriptor(pp));
}
}
}
@FunctionDescription
(
functionDescription = "Offers functions/enums that can be accepted by the specified function's specified argument (by index).\n"
+ "This version is take care about type "
+ "Also note that an argument might have custom AutocompleteProvider, which make possible to promt application specific strings, eg:\n"
+ "getRedisInteger(String key) [key might be prompted as string]",
parameters =
{
@FunctionVariableDescription(description="The function tree from the context.",mayNull=false,paramName="function",type=String.class),
@FunctionVariableDescription(description="UUID of the receiver function in the function tree.",mayNull=false,paramName="uuid",type=String.class),
@FunctionVariableDescription(description="Index of the parent's paramter count.",mayNull=false,paramName="index",type=int.class)
},
returning = @FunctionVariableDescription(description="Collection of function descriptors.",mayNull=false,paramName="",type=FunctionDescriptor[].class)
)
public static Object[] accurateOfferV1
(
String tree,
String functionUUID,
int index
)
{
//TODO parse tree
//TODO propagate types
//TODO select function
//TODO return original offerType
return null;
}
@FunctionDescription
(
functionDescription = "Validates every function connections (This function not implmented yet.). Because users can give any kind of functions as\n"
+ " an argument of other function, they can broke the \"connections\". \n"
+ "Warning levels:\n"
+ "\t- NOTICE: If parameter matcher exacly the requested type.\n"
+ "\t- INFO: If parameter is wrapped automatically eg.: requested function is a `void function(T p1, T p2)` and the given method is `void function()` which can be assembled by relaying the call to the requested type without parameters\n"
+ "\t- WARNING: Saac supports environment variables that can be given anywhere as a given parameter, and will be fetched and used in runtime."
+ "\t- ERROR: On incompatible function composition (Requested and given functions are incompatible types.).",
parameters =
{
@FunctionVariableDescription(description="Serialized function.",mayNull=false,paramName="function",type=DataObject.class),
},
returning = @FunctionVariableDescription(description="Validation results for every entry.",mayNull=false,paramName="",type=List.class)
)
public static List<TranslationFriendlyValidationEntry> compileAndCheck
(
SimpleRpcRequest req,
DataObject function
)
{
Map<String, PreparedFunction> INDEX = (Map<String, PreparedFunction>)((SimpleRpcSession) req.getRpcSession()).get(SAAC_FUNCTION_SET_KEY);
List<TranslationFriendlyValidationEntry> ret = new ArrayList<>();
try
{
SaacEnv.create(INDEX, function, null);
}
catch(SaacException e)
{
ret.add(new TranslationFriendlyValidationEntry("saac_error", e.toDetailedMessage(), new OneShotMap<String, String>("stack_trace", Format.getPrintedStackTrace(e))));
}
catch(Exception e)
{
ret.add(new TranslationFriendlyValidationEntry("error", Format.getPrintedStackTrace(e), NullMap.instance));
}
return ret;
}
@FunctionDescription
(
functionDescription = "Executes the constructed function.",
parameters =
{
@FunctionVariableDescription(description="Serialized function.",mayNull=false,paramName="function",type=DataObject.class),
},
returning = @FunctionVariableDescription(description="Return value of the root function",mayNull=false,paramName="",type=FunctionDescriptor[].class)
)
public static Object execute(SimpleRpcRequest req, DataObject fs)
{
SimpleRpcSession rpcSess = (SimpleRpcSession) req.getRpcSession();
SaacSession saac_sess = getOrCreateSaacSession(rpcSess);
ReferenceCounted<PrintWriter> log = saac_sess.setContextLogger();
Map<String, Object> env = new SmallMap<>();
env.put("PARAM", rpcSess.get("PARAM"));
env.put("SAAC_SESSION", saac_sess);
Map<String, PreparedFunction> INDEX = (Map<String, PreparedFunction>)((SimpleRpcSession) req.getRpcSession()).get(SAAC_FUNCTION_SET_KEY);
long t0 = System.currentTimeMillis();
try
{
LoggingTools.tryLogFormat
(
LOG,
LogLevel.INFO,
"Saac execution starting"
);
if(!execute(INDEX, fs, env, log))
{
throw new RuntimeException("The root element is not runnable.");
}
}
catch(Throwable e)
{
Throwable t = e;
while(t instanceof InvocationTargetException || t instanceof RuntimeException)
{
t = t.getCause();
}
LoggingTools.tryLogFormatException
(
LOG,
LogLevel.ERROR,
t,
"Saac execution exception:\n"
);
if(null == t)
{
throw e;
}
else
{
Mirror.throwSoftOrHardButAnyway(e);
}
}
finally
{
LoggingTools.tryLogFormat
(
LOG,
LogLevel.INFO,
"Saac execution ended after %d ms",
System.currentTimeMillis() - t0
);
}
return true;
}
/********************************* RPC classes *****************************************/
public static class FunctionDescriptor
{
public static FunctionDescriptor[] emptyFuncArray = new FunctionDescriptor[0];
public FunctionDescriptor(FunctionCreator cre)
{
this.name = cre.getName();
this.description = cre.getDescription();
}
public String id;
public String name;
public String description;
public Param returning;
public Param[] arguments;
public FunctionDescriptor(PreparedFunction f)
{
id = f.getMethod().getDeclaringClass().getName()+"."+f.getMethod().getName();
name = f.getName();
description = f.getDescription();
this.returning = f.getReturning();
this.arguments = f.getArgs();
}
@Override
public String toString()
{
return "FunctionDescriptor: "+id;
}
}
protected static class _enum
{
public String name;
public int ordinal;
public String enumClass;
public _enum(Enum e)
{
this.name = e.name();
this.ordinal = e.ordinal();
this.enumClass = e.getClass().getName();
}
}
/********************************* helper functions *****************************************/
public static boolean isAcceptable(Type targetType, Type sourceType)
{
if(isUnbounded(targetType))
{
return true;
}
if(isUnbounded(sourceType))
{
return true;
}
if
(
targetType instanceof Class
&&
sourceType instanceof Class
)
{
return targetType == sourceType;
//return ((Class) targetType).isAssignableFrom(((Class)sourceType));
}
if
(
targetType instanceof ParameterizedType
&&
sourceType instanceof ParameterizedType
)
{
ParameterizedType src = ((ParameterizedType)sourceType);
ParameterizedType target = ((ParameterizedType)targetType);
if(!isAcceptable(target.getRawType(), src.getRawType()))
{
return false;
}
Type[] sa = src.getActualTypeArguments();
Type[] ta = target.getActualTypeArguments();
if(sa.length != ta.length)
{
return false;
}
for(int i=0;i<sa.length;++i)
{
if(!isAcceptable(ta[i], sa[i]))
{
return false;
}
}
return true;
}
if
(
targetType instanceof GenericArrayType
&&
sourceType instanceof GenericArrayType
)
{
return isAcceptable
(
((GenericArrayType) targetType).getGenericComponentType(),
((GenericArrayType) sourceType).getGenericComponentType()
);
}
//type(CTX) && type(CTX)
if
(
targetType instanceof TypeVariable
)
{
Type[] ts = ((TypeVariable) targetType).getBounds();
if(ts.length == 1)
{
return isAcceptable(ts[0], sourceType);
}
else
{
//TODO foreach
}
}
if(targetType instanceof ParameterizedType)
{
Type act = ((ParameterizedType) targetType).getRawType();
return isAcceptable(act, sourceType);
}
if(targetType instanceof WildcardType)
{
WildcardType wt = (WildcardType) targetType;
Type[] b = null;
if(null != (b = wt.getLowerBounds()))
{
for(Type t:b)
{
if(!isAcceptable(t, sourceType))
{
return false;
}
}
}
if(null != (b = wt.getUpperBounds()))
{
for(Type t:b)
{
if(!isAcceptable(sourceType, t))
{
return false;
}
}
}
return true;
}
/*if(targetType instanceof TypeVariable)
{
((TypeVariable) targetType).getBounds();
}
if(sourceType instanceof TypeVariable)
{
((TypeVariable) sourceType).getBounds();
}
if(targetType instanceof WildcardType)
{
((WildcardType)targetType).getLowerBounds();
}
if(sourceType instanceof WildcardType)
{
((WildcardType)targetType).getLowerBounds();
}
*/
//((WildcardType)targetType).
//GenericArrayTypeImpl ParameterizedTypeimpl, WildcardTypeimpl
return false;
}
public static boolean isUnbounded(Type[] tt)
{
for(int i=0;i<tt.length;++i)
{
if(!isUnbounded(tt[i]))
{
return false;
}
}
return true;
}
public static boolean isUnbounded(Type t)
{
if(t instanceof Class)
{
return ((Class)t).isAssignableFrom(Object.class);
}
if(t instanceof ParameterizedType)
{
return
isUnbounded(((ParameterizedType)t).getRawType())
&&
isUnbounded(((ParameterizedType)t).getActualTypeArguments());
}
if(t instanceof GenericArrayType)
{
return isUnbounded(((GenericArrayType) t).getGenericComponentType());
}
if(t instanceof TypeVariable)
{
return isUnbounded(((TypeVariable) t).getBounds());
}
if(t instanceof WildcardType)
{
if(!isUnbounded(((WildcardType)t).getLowerBounds()))
{
return false;
}
if(!isUnbounded(((WildcardType)t).getUpperBounds()))
{
return false;
}
return true;
}
return false;
}
public static final String SAAC_FUNCTION_SET_KEY = "SAAC_FUNCTIONSET";
public static Map<String, PreparedFunction> getSessionFunctionSet(SimpleRpcSession sess)
{
Map<String, PreparedFunction> ret = (Map<String, PreparedFunction>)sess.get(SAAC_FUNCTION_SET_KEY);
if(null == ret)
{
throw new RuntimeException("SAAC functionSet not set in the session. Use SaacRpc.setSessionFunctionSet to setup the session");
}
return ret;
}
public static void setSessionFunctionSet(SimpleRpcSession sess, Map<String, PreparedFunction> map)
{
sess.put(SAAC_FUNCTION_SET_KEY, map);
}
protected static final String SAAC_SESSION_KEY = "SAAC_EXECUTION";
public static SaacSession getOrCreateSaacSession(SimpleRpcSession sess)
{
SaacSession saac_sess = (SaacSession) sess.get(SAAC_SESSION_KEY);
if(null == saac_sess)
{
sess.put(SAAC_SESSION_KEY, saac_sess = new SaacSession(sess));
}
return saac_sess;
}
public static boolean execute
(
SaacEnv se,
Map<String, Object> env,
ReferenceCounted<PrintWriter> LOGGER
)
{
LoggingTools.tryLogFormat
(
LOG,
LogLevel.INFO,
"Saac Parse started"
);
se.pushEnv(env);
try
{
if(null != LOGGER)
{
ThreadLocalHookableLogFacility.setLocalOutput(LOGGER);
}
//TODO add to session and monitor
//SaacExecution exec = new SaacExecution(se, saac_sess);
Object stage = se.root;
if(stage instanceof SimpleCall)
{
((SimpleCall)stage).call();
return true;
}
if(stage instanceof SimplePublish1)
{
((SimplePublish1)stage).publish(env);
return true;
}
}
finally
{
se.popEnv(env);
if(null != LOGGER)
{
ThreadLocalHookableLogFacility.setLocalOutput(null);
}
}
return false;
}
public static boolean execute
(
Map<String, PreparedFunction> functions,
DataObject data,
Map<String, Object> env,
ReferenceCounted<PrintWriter> LOGGER
)
{
return execute(SaacEnv.create(functions, data, null), env, LOGGER);
}
}