Last active
September 28, 2018 08:25
-
-
Save maxymania/4280f94381c2a9878e0fd26697247c0b to your computer and use it in GitHub Desktop.
Reflection, Method selector like the java compiler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: insert your package name here! | |
package your.packag.e.name.here; | |
/* | |
* The ZLIB license (with two restrictions removed). | |
* | |
* Copyright 2018 Simon Schmidt. | |
* | |
* This software is provided 'as-is', without any express or implied | |
* warranty. In no event will the authors be held liable for any damages | |
* arising from the use of this software. | |
* | |
* Permission is granted to anyone to use this software for any purpose, | |
* including commercial applications, and to alter it and redistribute it | |
* freely, subject to the following restrictions: | |
* | |
* 1. This notice may not be removed or altered from any source distribution. | |
*/ | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.ListIterator; | |
import java.util.Map; | |
/** | |
* | |
* @author Simon Schmidt | |
*/ | |
public class MethodSelector { | |
public static Class<?> box(Class<?> c){ | |
if(c==void.class)c = Void.class; | |
if(c==boolean.class)c = Boolean.class; | |
if(c==byte.class)c = Byte.class; | |
if(c==short.class)c = Short.class; | |
if(c==char.class)c = Character.class; | |
if(c==int.class)c = Integer.class; | |
if(c==long.class)c = Long.class; | |
if(c==float.class)c = Float.class; | |
if(c==double.class)c = Double.class; | |
return c; | |
} | |
public static class CachedMethod{ | |
public final Method method; | |
public final Class<?>[] parameters,parametersBoxed; | |
public CachedMethod(Method method) { | |
this.method = method; | |
parameters = method.getParameterTypes(); | |
parametersBoxed = parameters.clone(); | |
for(int i=0,n=parametersBoxed.length;i<n;++i) parametersBoxed[i] = box(parametersBoxed[i]); | |
} | |
public CachedMethod(Class<?> ...args) { | |
this.method = null; | |
parameters = args; | |
parametersBoxed = parameters.clone(); | |
for(int i=0,n=parametersBoxed.length;i<n;++i) parametersBoxed[i] = box(parametersBoxed[i]); | |
} | |
public Class<?>[] getParameterTypes(boolean box){ return box?parametersBoxed:parameters; } | |
public boolean isAssignableFrom(CachedMethod other,boolean box){ | |
final Class<?>[] a = getParameterTypes(box),b = other.getParameterTypes(box); | |
if(a.length!=b.length) return false; | |
for(int i=0,n=a.length;i<n;++i) | |
if(!a[i].isAssignableFrom(b[i])) return false; | |
return true; | |
} | |
/* | |
bit & 1 if this := other | |
bit & 2 if other := this | |
*/ | |
public int compareBit(CachedMethod other,boolean box){ | |
final Class<?>[] a = getParameterTypes(box),b = other.getParameterTypes(box); | |
if(a.length!=b.length) return 0; | |
boolean afromb = true,bfroma = true; | |
for(int i=0,n=a.length;i<n;++i){ | |
if(!a[i].isAssignableFrom(b[i])) afromb = false; | |
if(!b[i].isAssignableFrom(a[i])) bfroma = false; | |
} | |
int bit = 0; | |
if(afromb) bit|=1; | |
if(bfroma) bit|=2; | |
return bit; | |
} | |
} | |
public static void insert(List<CachedMethod> meth,CachedMethod inst,boolean box){ | |
ListIterator<CachedMethod> iter = meth.listIterator(); | |
while(iter.hasNext()){ | |
CachedMethod cm = iter.next(); | |
switch(cm.compareBit(inst, box)){ | |
case 0: /* fully incompatible */ continue; | |
case 1: /* inst SC of cm. */ iter.previous(); break; | |
case 2: /* cm SC of inst. */ | |
case 3: /* fully equivalent */ continue; | |
} | |
break; | |
} | |
iter.add(inst); | |
} | |
public static String methodId(Method m){ | |
return m.getName()+"/"+m.getParameterCount(); | |
} | |
private static List<CachedMethod> idealList(String unused){ return new LinkedList(); } | |
private static final CachedMethod[] C = {}; | |
private final boolean useBoxing; | |
private final Map<String,CachedMethod[]> dyn = new HashMap(),stat = new HashMap(); | |
private MethodSelector(Iterable<Method> meths,boolean box){ | |
useBoxing = box; | |
Map<String,List<CachedMethod>> ldyn = new HashMap<>(),lstat = new HashMap<>(); | |
for(Method meth:meths) { | |
int m = meth.getModifiers(); | |
if(!Modifier.isPublic(m)) continue; | |
String id = methodId(meth); | |
CachedMethod cm = new CachedMethod(meth); | |
insert((Modifier.isStatic(m)?lstat:ldyn).computeIfAbsent(id, MethodSelector::idealList),cm,box); | |
} | |
ldyn.forEach((k,v)->dyn.put(k, v.toArray(C))); | |
lstat.forEach((k,v)->stat.put(k, v.toArray(C))); | |
} | |
public static MethodSelector getSelectorForClassEx(Class<?> itself,boolean declared,boolean useBoxing){ | |
return new MethodSelector(Arrays.asList(declared?itself.getDeclaredMethods():itself.getMethods()),useBoxing); | |
} | |
public Method lookupEx(boolean dynamic,String name,Class<?> ...args) throws NoSuchMethodException{ | |
String id = name+"/"+(args.length); | |
CachedMethod cm = new CachedMethod(args); | |
for(CachedMethod other:(dynamic?dyn:stat).getOrDefault(id, C)){ | |
if(other.isAssignableFrom(cm, useBoxing)) | |
return other.method; | |
} | |
throw new NoSuchMethodException(""+id+" with the given types "+Arrays.toString(args)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment