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