Multimethod.java (5166B)
1 // Copyright (c) 2009, 2015 Tomas Hlavaty All rights reserved. 2 3 // Redistribution and use in source and binary forms, with or without 4 // modification, are permitted provided that the following conditions 5 // are met: 6 7 // Redistributions of source code must retain the above copyright 8 // notice, this list of conditions and the following 9 // disclaimer. Redistributions in binary form must reproduce the above 10 // copyright notice, this list of conditions and the following 11 // disclaimer in the documentation and/or other materials provided 12 // with the distribution. 13 14 // Neither the name of jmultimethod nor the names of its contributors 15 // may be used to endorse or promote products derived from this 16 // software without specific prior written permission. 17 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 // OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package jmultimethod; 32 33 import java.lang.annotation.Annotation; 34 import java.lang.reflect.Method; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Comparator; 38 39 public class Multimethod { 40 41 protected String name; 42 protected final ArrayList<Method> methods = new ArrayList<Method>(); 43 protected final MethodComparator methodComparator = new MethodComparator(); 44 45 public Multimethod(String name, Class... classes) { 46 this.name = name; 47 for(Class c: classes) { 48 add(c); 49 } 50 } 51 52 public void add(Class c) { 53 for(Method m: c.getMethods()) { 54 for(Annotation ma: m.getAnnotations()) { 55 if(ma instanceof Multi) { 56 Multi g = (Multi) ma; 57 if(this.name.equals(g.value())) { 58 methods.add(m); 59 } 60 } 61 } 62 } 63 sort(); 64 } 65 66 protected void sort() { 67 Method[] a = new Method[methods.size()]; 68 methods.toArray(a); 69 Arrays.sort(a, methodComparator); 70 methods.clear(); 71 for(Method m: a) { 72 methods.add(m); 73 } 74 } 75 76 protected class MethodComparator implements Comparator<Method> { 77 @Override 78 public int compare(Method l, Method r) { 79 if(l.equals(r)) { 80 return 0; 81 } 82 // most specific methods first 83 Class[] lc = l.getParameterTypes(); 84 Class[] rc = r.getParameterTypes(); 85 for(int i = 0; i < lc.length; i++) { 86 String lv = value(l, i); 87 String rv = value(r, i); 88 if(lv == null) { 89 if(rv == null) { 90 if(lc[i].isAssignableFrom(rc[i])) { 91 return 1; 92 } 93 } else { 94 return 1; 95 } 96 } else { 97 if(rv == null) { 98 return -1; 99 } 100 } 101 } 102 return -1; 103 } 104 } 105 106 protected String value(Method method, int arg) { 107 Annotation[] a = method.getParameterAnnotations()[arg]; 108 for(Annotation p: a) { 109 if(p instanceof V) { 110 V v = (V) p; 111 return v.value(); 112 } 113 } 114 return null; 115 } 116 117 protected boolean isApplicable(Method method, Object... args) { 118 Class[] c = method.getParameterTypes(); 119 for(int i = 0; i < c.length; i++) { 120 // must be instanceof and equal to annotated value if present 121 if(c[i].isInstance(args[i])) { 122 String v = value(method, i); 123 if(v != null && !v.equals(args[i])) { 124 return false; 125 } 126 } else { 127 if(args[i] != null || !Object.class.equals(c[i])) { 128 return false; 129 } 130 } 131 } 132 return true; 133 } 134 135 public Object invoke(Object self, Object... args) { 136 Method m = null; // first applicable method (most specific) 137 for(Method method: methods) { 138 if(isApplicable(method, args)) { 139 m = method; 140 break; 141 } 142 } 143 if(m == null) { 144 throw new RuntimeException("No applicable method '" + name + "'."); 145 } 146 try { 147 return m.invoke(self, args); 148 } catch (Exception e) { 149 throw new RuntimeException("Method invocation failed '" + name + "'."); 150 } 151 } 152 }