1 package gnu.kawa.format; 2 3 import gnu.lists.Consumer; 4 import java.util.*; 5 6 /** A format that delegates to a set of type-specific handlers. */ 7 8 public class GenericFormat extends AbstractFormat { 9 protected AbstractFormat next; 10 11 List<Entry> entries = new ArrayList<Entry>(); 12 13 Map<Class, Object[]> map = new HashMap<Class,Object[]>(); 14 GenericFormat()15 public GenericFormat() { } GenericFormat(AbstractFormat next)16 public GenericFormat(AbstractFormat next) { this.next = next; } 17 18 /** Add an entry. 19 * More recently-added entries take precedence. 20 */ add(Entry entry)21 public void add(Entry entry) { entries.add(entry); } 22 add(Class cls, String mname)23 public void add(Class cls, String mname) { 24 add(Entry.valueOf(cls, mname)); } 25 26 /** Same as plain add, but invalidates class-based lookup cache 27 * for subtypes of cls. 28 */ addInvalidatingCache(Entry entry, Class cls)29 public void addInvalidatingCache(Entry entry, Class cls) { 30 invalidateCache(cls); 31 entries.add(entry); 32 } 33 invalidateCache(Class cls)34 public void invalidateCache(Class cls) { 35 for (Iterator<Class> it = map.keySet().iterator(); 36 it.hasNext(); ) { 37 Class key = it.next(); 38 if (cls.isAssignableFrom(key)) 39 it.remove(); 40 } 41 } 42 writeObject(Object value, Consumer out)43 public void writeObject(Object value, Consumer out) { 44 GenericFormat curFormat = this; 45 for (;;) { 46 if (curFormat.tryFormat(value, this, out)) 47 return; 48 AbstractFormat next = curFormat.next; 49 if (next instanceof GenericFormat) 50 curFormat = (GenericFormat) next; 51 else { 52 if (next != null) 53 next.writeObject(value, out); 54 else 55 out.write(value == null ? "(null)" : value.toString()); 56 return; 57 } 58 } 59 } 60 tryFormat(Object value, AbstractFormat format, Consumer out)61 public boolean tryFormat(Object value, AbstractFormat format, Consumer out) { 62 Class cls = value == null ? Object.class : value.getClass(); 63 Object[] cache = map.get(cls); 64 // index in cache - increasing from newest entry to oldest 65 // The cache is an array of potential entries (a subset of 66 // the entries list) in reverse order, followed by an Integer, 67 // which is the index of the last entry tested. 68 int j = 0; 69 // index in entries - oldest entry (lowest index) 70 // tested (so far) with tryFormat 71 int oldestEntry; 72 if (cache != null) { 73 for (;;) { 74 Object entry = cache[j]; 75 if (entry instanceof Entry) { 76 TryFormatResult res = 77 ((Entry) entry).tryFormat(value, format, out); 78 if (res == TryFormatResult.HANDLED) 79 return true; 80 j++; 81 } else { 82 oldestEntry = (Integer) entry; 83 break; 84 } 85 } 86 } else { 87 oldestEntry = entries.size(); 88 cache = new Object[8]; 89 j = 0; 90 } 91 for (int i = oldestEntry; --i >= 0;) { 92 Entry entry = entries.get(i); 93 TryFormatResult res = entry.tryFormat(value, format, out); 94 if (res == TryFormatResult.INVALID_CLASS) 95 continue; 96 if (j + 2 >= cache.length) { 97 Object[] tmp = new Object[(3 * cache.length) >> 1]; 98 System.arraycopy(cache, 0, tmp, 0, cache.length); 99 cache = tmp; 100 } 101 cache[j++] = entry; 102 if (res == TryFormatResult.HANDLED) { 103 cache[j++] = i; 104 map.put(cls, cache); 105 return true; 106 } 107 } 108 cache[j++] = 0; 109 map.put(cls, cache); 110 return false; 111 } 112 113 public enum TryFormatResult { 114 /** Returned when the handler does not accept any element of the 115 * value's class. */ 116 INVALID_CLASS, 117 INVALID, 118 HANDLED 119 }; 120 121 public static class Entry { 122 public static Entry defaultInstance = new Entry(); 123 124 /** Try to print value on out. 125 * Assume the result only depends of the *class* of value and the format. 126 */ tryFormat(Object value, AbstractFormat format, Consumer out)127 public TryFormatResult tryFormat(Object value, AbstractFormat format, Consumer out) { 128 out.write(value == null ? "(null)" : value.toString()); 129 return TryFormatResult.HANDLED; 130 } valueOf(Class cls, String mname)131 public static Entry valueOf(Class cls, String mname) { 132 MethodEntry entry = new MethodEntry(); 133 try { 134 /* #ifdef use:java.lang.invoke */ 135 entry.method = java.lang.invoke.MethodHandles.lookup() 136 .findStatic(cls, mname, MethodEntry.mtype); 137 /* #else */ 138 // entry.method = cls.getDeclaredMethod(mname, MethodEntry.mtype); 139 /* #endif */ 140 } catch (Exception ex) { 141 throw new RuntimeException(ex); 142 } 143 return entry; 144 } 145 } 146 147 public static class MethodEntry extends Entry { 148 /* #ifdef use:java.lang.invoke */ 149 java.lang.invoke.MethodHandle method; 150 static final java.lang.invoke.MethodType mtype = 151 java.lang.invoke.MethodType.methodType(TryFormatResult.class, 152 Object.class, 153 AbstractFormat.class, 154 Consumer.class); 155 /* #else */ 156 // java.lang.reflect.Method method; 157 // private static final Class[] mtype = 158 // new Class[] { Object.class, 159 // AbstractFormat.class, 160 // Consumer.class }; 161 /* #endif */ tryFormat(Object value, AbstractFormat format, Consumer out)162 public TryFormatResult tryFormat(Object value, AbstractFormat format, Consumer out) { 163 try { 164 /* #ifdef use:java.lang.invoke */ 165 return (TryFormatResult) method.invokeExact(value, format, out); 166 /* #else */ 167 // return (TryFormatResult) 168 // method.invoke(null, new Object[] { value, format, out }); 169 /* #endif */ 170 } catch (Throwable ex) { 171 throw new RuntimeException(ex); 172 } 173 } 174 } 175 } 176