/** * Copyright 2014 * SMEdit https://github.com/StarMade/SMEdit * SMTools https://github.com/StarMade/SMTools * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. **/ package jo.sm.logic.macro; import java.beans.PropertyDescriptor; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.Bindings; import javax.script.ScriptContext; import jo.sm.data.SparseMatrix; import jo.sm.data.StarMade; import jo.sm.logic.StarMadeLogic; import jo.sm.logic.utils.MapUtils; import jo.sm.logic.utils.StringUtils; import jo.sm.mods.IBlocksPlugin; import jo.sm.mods.IPluginCallback; import jo.sm.mods.IStarMadePlugin; import jo.sm.ship.data.Block; import jo.sm.ui.act.plugin.DescribedBeanInfo; public class MacroFunctionOpLogic { private static Map<String, IStarMadePlugin> FUNCTIONS = null; private static Map<String, JavascriptWrapper> JAVASCRIPT_FUNCTIONS = null; private static Map<Thread, Stack<ScriptContext>> mInstanceProps = new HashMap<>(); private static final Logger log = Logger.getLogger(MacroFunctionOpLogic.class.getName()); private static synchronized void init() { if (FUNCTIONS != null) { return; } FUNCTIONS = new HashMap<>(); register(IBlocksPlugin.SUBTYPE_FILE); register(IBlocksPlugin.SUBTYPE_EDIT); register(IBlocksPlugin.SUBTYPE_GENERATE); register(IBlocksPlugin.SUBTYPE_MODIFY); register(IBlocksPlugin.SUBTYPE_PAINT); register(IBlocksPlugin.SUBTYPE_VIEW); } public static String getID(IStarMadePlugin plugin) { int[] classification = plugin.getClassifications()[0]; return getPrefix(classification[1]) + getBaseID(plugin); } private static String getPrefix(int subtype) { switch (subtype) { case IBlocksPlugin.SUBTYPE_FILE: return "file"; case IBlocksPlugin.SUBTYPE_EDIT: return "edit"; case IBlocksPlugin.SUBTYPE_GENERATE: return "gen"; case IBlocksPlugin.SUBTYPE_MODIFY: return "mod"; case IBlocksPlugin.SUBTYPE_PAINT: return "paint"; case IBlocksPlugin.SUBTYPE_VIEW: return "view"; } throw new IllegalStateException("Unknown subtype=" + subtype); } private static void register(int subtype) { String prefix = getPrefix(subtype); List<IBlocksPlugin> plugins = StarMadeLogic.getBlocksPlugins(IBlocksPlugin.TYPE_ALL, subtype); for (IBlocksPlugin plugin : plugins) { String name = prefix + getBaseID(plugin); log.log(Level.INFO, "Registering '"+name+"'"); FUNCTIONS.put(name.toLowerCase(), plugin); } } private static String getBaseID(IStarMadePlugin plugin) { String name = plugin.getName(); name = StringUtils.substitute(name, "/", ""); name = StringUtils.substitute(name, ".", ""); name = StringUtils.substitute(name, " ", ""); return name; } public static String expandFunctions(String expr, Map<String, Object> props) { init(); for (int start = expr.indexOf('@'); start >= 0; start = expr.indexOf('@', start + 1)) { int end = start + 1; while ((end < expr.length()) && Character.isJavaIdentifierPart(expr.charAt(end))) { end++; } String func = expr.substring(start + 1, end).toLowerCase(); if (FUNCTIONS.containsKey(func)) { expr = expr.substring(0, start) + "func_" + func.toLowerCase() + ".run" + expr.substring(end); } } return expr; } public static ScriptContext getInstanceProps() { Stack<ScriptContext> stack = mInstanceProps.get(Thread.currentThread()); if ((stack != null) && (stack.size() > 0)) { return stack.get(stack.size() - 1); } else { return null;//throw new IllegalStateException("Tried to get instance props and none set!"); } } public static void setInstanceProps(ScriptContext props) { Stack<ScriptContext> stack = mInstanceProps.get(Thread.currentThread()); if (stack == null) { stack = new Stack<>(); mInstanceProps.put(Thread.currentThread(), stack); } stack.push(props); } public static void clearInstanceProps() { Stack<ScriptContext> stack = mInstanceProps.get(Thread.currentThread()); if (stack != null) { stack.pop(); if (stack.size() == 0) { mInstanceProps.remove(Thread.currentThread()); } } } public static void makeJavascriptBindings(Bindings b, Map<String, Object> props) { init(); if (JAVASCRIPT_FUNCTIONS == null) { JAVASCRIPT_FUNCTIONS = new HashMap<>(); for (String name : FUNCTIONS.keySet()) { JavascriptWrapper wrapper = new JavascriptWrapper(name); JAVASCRIPT_FUNCTIONS.put("func_" + name, wrapper); } } MapUtils.copy(b, JAVASCRIPT_FUNCTIONS); } static Object evaluateFunction(String name, Object[] args, Map<String, Object> props) { init(); IStarMadePlugin func = FUNCTIONS.get(name.toLowerCase()); if (func instanceof IBlocksPlugin) { return evaluateBlocksPlugin((IBlocksPlugin) func, props, args); } throw new IllegalStateException("Unknown function " + name); } static Object evaluateBlocksPlugin(IBlocksPlugin plugin, Map<String, Object> props, Object[] args) { @SuppressWarnings("unchecked") SparseMatrix<Block> grid = (SparseMatrix<Block>) find(SparseMatrix.class, "grid", props, args); if (grid == null) { throw new IllegalArgumentException("Cannot find grid argument"); } StarMade sm = (StarMade) find(StarMade.class, "sm", props, args); if (sm == null) { sm = StarMadeLogic.getInstance(); } IPluginCallback cb = (IPluginCallback) find(IPluginCallback.class, "cb", props, args); if (cb == null) { cb = new NullPluginCallback(); } Object params = plugin.newParameterBean(); if (params != null) { plugin.initParameterBean(grid, params, sm, cb); } DescribedBeanInfo info = new DescribedBeanInfo(params); int off = 0; if ((args.length > 0) && (args[0] instanceof SparseMatrix)) { off++; } for (int i = 0; i < info.getOrderedProps().size(); i++) { PropertyDescriptor prop = info.getOrderedProps().get(i); Object value = find(prop, i + off, args); if (value instanceof String) { info.setAsText(prop.getName(), (String) value); } else if (value != null) { info.setAsValue(prop.getName(), value); } } return plugin.modify(grid, params, sm, cb); } private static Object find(PropertyDescriptor prop, int i, Object[] args) { Class<?> propType = prop.getPropertyType(); propType = promoteType(propType); if (i < args.length) { Class<?> argType = args[i].getClass(); if ((args[i] instanceof String) || propType.isAssignableFrom(argType)) { return args[i]; } else if (Number.class.isAssignableFrom(propType) && Number.class.isAssignableFrom(argType)) { Number arg = (Number) args[i]; if (propType == Double.class) { return arg.doubleValue(); } else if (propType == Float.class) { return arg.floatValue(); } else if (propType == Long.class) { return arg.longValue(); } else if (propType == Integer.class) { return arg.intValue(); } else if (propType == Short.class) { return arg.shortValue(); } else if (propType == Byte.class) { return arg.byteValue(); } } } // TODO: search for javascript struct return null; } private static Class<?> promoteType(Class<?> type) { if (type == boolean.class) { return Boolean.class; } if (type == int.class) { return Integer.class; } if (type == short.class) { return Short.class; } if (type == byte.class) { return Byte.class; } if (type == long.class) { return Long.class; } if (type == double.class) { return Double.class; } if (type == float.class) { return Float.class; } if (type == char.class) { return Character.class; } return type; } private static Object find(Class<?> theClass, String theName, Map<String, Object> props, Object[] args) { if (args != null) { for (Object o : args) { if (theClass.isInstance(o)) { return o; } } } if (props != null) { if (props.containsKey(theName)) { return props.get(theName); } for (String key : props.keySet()) { if (key.equalsIgnoreCase(theName)) { return props.get(key); } } } return null; } }