/** * Serene (simple) - A PoC lisp to collect data on Serenes concepts * Copyright (C) 2019-2020 Sameer Rahmani * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package serene.simple.builtin; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import serene.simple.BaseScope; import serene.simple.SereneException; import static serene.simple.Utils.getClassOf; public class NewFn extends AFn { public String fnName() { return "new"; }; public Object eval(BaseScope scope) throws SereneException { List args = this.arguments(); if (args.size() == 0) { throw new SereneException("Needs a class to instantiate."); } else if(args.size() == 1) { return this.createObject((Class) args.get(0)); } return this.createObject((Class) args.get(0), args.subList(1, args.size())); } private Object createObject(Class c) { return this.createObject(c, (List) new ArrayList()); } private Object createObject(Class c, List args) { Constructor constructor = this.getConstructor(c, args); //Object[] argvals = new Object[args.size()]; Object[] argvals = (Object[]) args.toArray(); for(int i = 0; i < args.size(); i++) if(constructor != null) { try { return constructor.newInstance( Reflector.boxArgs(constructor.getParameterTypes(), argvals)); } catch(Exception e) { throw new SereneException( String.format("Can't instantiate class '%s' because: %s", c.getName(), e.getMessage())); } } return Reflector.invokeConstructor(c, argvals); } private Constructor getConstructor(Class c, List args) { ArrayList> possibleInitFns = new ArrayList>(); Constructor[] inits = c.getConstructors(); ArrayList[]> params = new ArrayList[]>(); ArrayList> rets = new ArrayList>(); // Constructor index on the possibleInitFns List int id = 0; for (int i = 0; i < inits.length; i++){ Constructor initFn = inits[i]; if(initFn.getParameterTypes().length == args.size()) { possibleInitFns.add(initFn); params.add(initFn.getParameterTypes()); rets.add(c); } } if (possibleInitFns.isEmpty()) { throw new IllegalArgumentException("No matching constructor found for " + c); } if (possibleInitFns.size() > 1) { id = findMatchingConstructor(c.getName(), params, args, rets); } return id == 0 ? null : (Constructor) possibleInitFns.get(id); } public int findMatchingConstructor(String methodName, ArrayList[]> paramlists, List args, List> rets) { // presumes matching lengths int matchIdx = -1; boolean tied = false; boolean foundExact = false; for (int i = 0; i < paramlists.size(); i++) { boolean match = true; int exact = 0; for (int p = 0; match && p < args.size(); ++p) { Object arg = args.get(p); Class aclass = getClassOf(arg); Class pclass = paramlists.get(i)[p]; // TODO: handle null aclass here here if (aclass == pclass) exact++; else match = Reflector.paramArgTypeMatch(pclass, aclass); } if (exact == args.size()) { if (!foundExact || matchIdx == -1 || rets.get(matchIdx).isAssignableFrom(rets.get(i))) matchIdx = i; tied = false; foundExact = true; } else if (match && !foundExact) { if (matchIdx == -1) matchIdx = i; else { if (subsumes(paramlists.get(i), paramlists.get(matchIdx))) { matchIdx = i; tied = false; } else if (Arrays.equals(paramlists.get(matchIdx), paramlists.get(i))) { if (rets.get(matchIdx).isAssignableFrom(rets.get(i))) matchIdx = i; } else if (!(subsumes(paramlists.get(matchIdx), paramlists.get(i)))) tied = true; } } } if (tied) throw new IllegalArgumentException( "More than one matching method found: " + methodName); return matchIdx; } static public boolean subsumes(Class[] c1, Class[] c2){ //Presumes matching lengths Boolean better = false; for(int i = 0; i < c1.length; i++) { if(c1[i] != c2[i]) { if(!c1[i].isPrimitive() && c2[i].isPrimitive() || c2[i].isAssignableFrom(c1[i])) better = true; else return false; } } return better; } }