001package jmri.jmrit.logixng.util.parser.functions;
002
003import java.lang.reflect.InvocationTargetException;
004import java.lang.reflect.Constructor;
005import java.util.ArrayList;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009
010import jmri.JmriException;
011import jmri.jmrit.logixng.SymbolTable;
012import jmri.jmrit.logixng.util.parser.*;
013import jmri.util.TypeConversionUtil;
014
015import org.openide.util.lookup.ServiceProvider;
016
017/**
018 * Implementation of string functions.
019 *
020 * @author Daniel Bergqvist 2020
021 */
022@ServiceProvider(service = FunctionFactory.class)
023public class JavaFunctions implements FunctionFactory {
024
025    @Override
026    public String getModule() {
027        return "Java";
028    }
029
030    @Override
031    public Set<Function> getFunctions() {
032        Set<Function> functionClasses = new HashSet<>();
033
034        addNewFunction(functionClasses);
035
036        return functionClasses;
037    }
038
039    @Override
040    public Set<Constant> getConstants() {
041        Set<Constant> constantClasses = new HashSet<>();
042        constantClasses.add(new Constant(getModule(), "null", null));
043        constantClasses.add(new Constant(getModule(), "None", null));
044        return constantClasses;
045    }
046
047    @Override
048    public String getConstantDescription() {
049        return Bundle.getMessage("Java.ConstantDescriptions");
050    }
051
052    private void addNewFunction(Set<Function> functionClasses) {
053        functionClasses.add(new AbstractFunction(this, "new", Bundle.getMessage("Java.new_Descr")) {
054            private boolean isAssignableFrom(Class<?> type, Object param) {
055                if (param == null) return true;
056                if (type.isAssignableFrom(param.getClass())) return true;
057                if ((type == Byte.TYPE) && (param instanceof Long)) return true;
058                if ((type == Short.TYPE) && (param instanceof Long)) return true;
059                if ((type == Integer.TYPE) && (param instanceof Long)) return true;
060                return ((type == Float.TYPE) && (param instanceof Double));
061            }
062
063            private boolean canCall(Constructor<?> constructor, Object[] params) {
064                Class<?>[] paramTypes = constructor.getParameterTypes();
065                if (paramTypes.length != params.length) return false;
066                for (int i=0; i < paramTypes.length; i++) {
067                    if (!isAssignableFrom(paramTypes[i], params[i])) return false;
068                }
069                return true;
070            }
071
072            private Object callConstructor(Constructor<?> constructor, Object[] params)
073                    throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
074
075                Class<?>[] paramTypes = constructor.getParameterTypes();
076                Object[] newParams = new Object[params.length];
077                for (int i=0; i < params.length; i++) {
078                    Object newParam;
079                    if ((params[i] == null) || (paramTypes[i].isAssignableFrom(params[i].getClass()))) newParam = params[i];
080                    else if ((paramTypes[i] == Byte.TYPE) && (params[i] instanceof Long)) newParam = (byte)(long)params[i];
081                    else if ((paramTypes[i] == Short.TYPE) && (params[i] instanceof Long)) newParam = (short)(long)params[i];
082                    else if ((paramTypes[i] == Integer.TYPE) && (params[i] instanceof Long)) newParam = (int)(long)params[i];
083                    else if ((paramTypes[i] == Float.TYPE) && (params[i] instanceof Double)) newParam = (float)(double)params[i];
084                    else throw new RuntimeException(String.format("%s cannot be assigned to %s", params[i].getClass().getName(), paramTypes[i].getName()));
085                    newParams[i] = newParam;
086                }
087                return constructor.newInstance(newParams);
088            }
089
090            public Object createInstance(String className, List<Object> parameters)
091                    throws JmriException, ClassNotFoundException, InstantiationException {
092
093                if (className == null) throw new NullPointerException("Class name is null");
094
095                Class<?> clazz = Class.forName(className);
096                Constructor<?>[] constructors = clazz.getConstructors();
097                Object[] params = parameters.toArray();
098
099                Exception exception = null;
100                for (Constructor<?> c : constructors) {
101                    try {
102                        if (canCall(c, params)) return callConstructor(c, params);
103                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
104                        exception = ex;
105                    }
106                }
107
108                if (exception != null) throw new ReflectionException("Reflection exception", exception);
109
110                List<String> paramList = new ArrayList<>();
111                for (Object o : params) paramList.add(String.format("%s:%s", o, o != null ? o.getClass().getName() : "null"));
112                throw new CannotCreateInstanceException(String.format("Can not create new instance of class %s with parameters %s", clazz.getName(), String.join(", ", paramList)), clazz.getName());
113            }
114
115            @Override
116            public Object calculate(SymbolTable symbolTable, List<ExpressionNode> parameterList)
117                    throws CalculateException, JmriException {
118                if (parameterList.isEmpty()) {
119                    throw new WrongNumberOfParametersException(Bundle.getMessage("WrongNumberOfParameters1", getName(), 1));
120                }
121
122                String className = TypeConversionUtil.convertToString(
123                        parameterList.get(0).calculate(symbolTable), false);
124
125                List<Object> list = new ArrayList<>();
126                for (int i=1; i < parameterList.size(); i++) {
127                    list.add(parameterList.get(i).calculate(symbolTable));
128                }
129
130                try {
131                    return createInstance(className, list);
132                } catch (ClassNotFoundException e) {
133                    throw new ClassIsNotFoundException(String.format("The class %s is not found", className), className);
134                } catch (InstantiationException e) {
135                    throw new ReflectionException("Reflection exception", e);
136                }
137            }
138        });
139    }
140
141}