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