serene-simple/src/main/java/serene/simple/builtin/NewFn.java

178 lines
5.4 KiB
Java

/**
* Serene (simple) - A PoC lisp to collect data on Serenes concepts
* Copyright (C) 2019-2020 Sameer Rahmani <lxsameer@gnu.org>
*
* 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<Object> 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<Object>) new ArrayList<Object>());
}
private Object createObject(Class c, List<Object> 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<Object> args) {
ArrayList<Constructor<?>> possibleInitFns = new ArrayList<Constructor<?>>();
Constructor<?>[] inits = c.getConstructors();
ArrayList<Class<?>[]> params = new ArrayList<Class<?>[]>();
ArrayList<Class<?>> rets = new ArrayList<Class<?>>();
// 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<Class<?>[]> paramlists,
List<Object> args,
List<Class<?>> 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;
}
}