1 /*
2  * Copyright (c) 2018 Helmut Neemann.
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.hdl.hgs.function;
7 
8 import de.neemann.digital.hdl.hgs.Context;
9 import de.neemann.digital.hdl.hgs.Expression;
10 import de.neemann.digital.hdl.hgs.HGSEvalException;
11 import de.neemann.digital.hdl.hgs.HGSMap;
12 
13 import java.lang.reflect.Array;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.Modifier;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 
20 /**
21  * Used to call a java function from the template code.
22  * Uses reflection to invoke the methods.
23  * All public methods which are declared in the given class are available via a
24  * {@link HGSMap} instance obtained by calling {@link JavaClass#createMap(Object)}.
25  * See {@link de.neemann.digital.hdl.vhdl.lib.VHDLTemplate} as an example.
26  *
27  * @param <T> the type of the instance
28  */
29 public final class JavaClass<T> {
30     private final HashMap<String, MyMethod<T>> methods;
31 
32     /**
33      * Creates a new instance
34      *
35      * @param clazz the class
36      */
JavaClass(Class<T> clazz)37     public JavaClass(Class<T> clazz) {
38         methods = new HashMap<>();
39         for (Method m : clazz.getDeclaredMethods()) {
40             int mod = m.getModifiers();
41             if (Modifier.isPublic(mod)) {
42                 final String name = m.getName();
43                 if (methods.containsKey(name))
44                     throw new RuntimeException("Method overloading ("
45                             + name + ") is not supported! Try to use the ellipsis (...) instead.");
46                 methods.put(name, new MyMethod<>(m));
47             }
48         }
49     }
50 
51     /**
52      * Creates the method map.
53      *
54      * @param instance the instance to call
55      * @return the method map
56      */
createMap(T instance)57     public HGSMap createMap(T instance) {
58         return new MethodMap<>(this, instance);
59     }
60 
61     private static final class MyMethod<T> {
62         private final Method method;
63         private final boolean isStatic;
64         private final boolean addContext;
65         private final int argCount;
66         private final int javaArgCount;
67         private final boolean isVarArgs;
68         private final Class<?> compType;
69 
MyMethod(Method method)70         private MyMethod(Method method) {
71             this.method = method;
72             this.isStatic = Modifier.isStatic(method.getModifiers());
73 
74             Class<?>[] argTypes = method.getParameterTypes();
75             javaArgCount = argTypes.length;
76             addContext = (argTypes.length > 0 && argTypes[0].isAssignableFrom(Context.class));
77 
78             isVarArgs = method.isVarArgs();
79             if (isVarArgs) {
80                 argCount = -1;
81                 compType = argTypes[argTypes.length - 1].getComponentType();
82             } else {
83                 if (addContext)
84                     argCount = argTypes.length - 1;
85                 else
86                     argCount = argTypes.length;
87                 compType = null;
88             }
89         }
90 
call(T instance, Context c, ArrayList<Expression> args)91         private Object call(T instance, Context c, ArrayList<Expression> args) throws HGSEvalException {
92             if (instance == null && !isStatic)
93                 throw new HGSEvalException("Function " + method.getName() + " is not static!");
94 
95             if (argCount >= 0 && argCount != args.size())
96                 throw new HGSEvalException("Wrong number of arguments! expected: "
97                         + argCount + ", but found:" + args.size());
98 
99             Object[] a = new Object[javaArgCount];
100             int i = 0;
101             if (addContext) {
102                 a[0] = c;
103                 i++;
104             }
105 
106             if (!isVarArgs) {
107                 for (Expression exp : args) {
108                     a[i] = exp.value(c);
109                     i++;
110                 }
111             } else {
112                 // ellipsis
113                 try {
114                     // the fixed args
115                     int fixed = javaArgCount - i - 1;
116                     for (int n = 0; n < fixed; n++) {
117                         a[i] = args.get(n).value(c);
118                         i++;
119                     }
120                     // put the var args to an array
121                     final int numVarArgs = args.size() - fixed;
122                     Object varArgs = Array.newInstance(compType, numVarArgs);
123                     for (int n = fixed; n < args.size(); n++)
124                         Array.set(varArgs, n - fixed, args.get(n).value(c));
125 
126                     // and pass the array
127                     a[i] = varArgs;
128                 } catch (RuntimeException e) {
129                     throw new HGSEvalException("type error assigning value to var array in "
130                             + method.getName() + ". Type "
131                             + compType.getSimpleName() + " is required.");
132                 }
133             }
134 
135             try {
136                 return method.invoke(instance, a);
137             } catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
138                 throw new HGSEvalException("Error invoking the java method " + method.getName() + "!", e);
139             }
140         }
141     }
142 
143     private static final class MethodMap<T> implements HGSMap {
144         private final JavaClass<T> javaClass;
145         private final T instance;
146 
MethodMap(JavaClass<T> javaClass, T instance)147         private MethodMap(JavaClass<T> javaClass, T instance) {
148             this.javaClass = javaClass;
149             this.instance = instance;
150         }
151 
152         @Override
hgsMapGet(String key)153         public Object hgsMapGet(String key) {
154             MyMethod<T> m = javaClass.methods.get(key);
155             if (m == null) return null;
156             return new MethodCall<>(m, instance);
157         }
158     }
159 
160     private static final class MethodCall<T> extends InnerFunction {
161         private final MyMethod<T> m;
162         private final T instance;
163 
MethodCall(MyMethod<T> m, T instance)164         private MethodCall(MyMethod<T> m, T instance) {
165             super(m.argCount);
166             this.m = m;
167             this.instance = instance;
168         }
169 
170         @Override
call(Context c, ArrayList<Expression> args)171         public Object call(Context c, ArrayList<Expression> args) throws HGSEvalException {
172             return m.call(instance, c, args);
173         }
174     }
175 
176 }
177