|
| 1 | +package rwperrott.stringtemplate.v4; |
| 2 | + |
| 3 | +import org.stringtemplate.v4.Interpreter; |
| 4 | +import org.stringtemplate.v4.ModelAdaptor; |
| 5 | +import org.stringtemplate.v4.ST; |
| 6 | +import org.stringtemplate.v4.STGroup; |
| 7 | +import org.stringtemplate.v4.misc.STNoSuchPropertyException; |
| 8 | + |
| 9 | +import java.util.ArrayList; |
| 10 | +import java.util.Collections; |
| 11 | +import java.util.List; |
| 12 | +import java.util.StringJoiner; |
| 13 | +import java.util.function.UnaryOperator; |
| 14 | + |
| 15 | +/** |
| 16 | + * This provides most/all the functionality to create fra more powerful ModelAdapters which can use an objects fields |
| 17 | + * and make parameterised use of it's instance/static methods and static methods of other registered class. |
| 18 | + * <p> |
| 19 | + * Uses unreflected and cached HethodHandles to invoke Field getters and Methods, which is a lot faster than the |
| 20 | + * redundant Security costs of calling invoke on Field or Method objects. |
| 21 | + * |
| 22 | + * @param <T> type to be adapted |
| 23 | + */ |
| 24 | +public class AbstractInvokeAdaptor<T> implements ModelAdaptor<T> { |
| 25 | + private final boolean onlyPublic; |
| 26 | + |
| 27 | + protected AbstractInvokeAdaptor(final boolean onlyPublic) { |
| 28 | + this.onlyPublic = onlyPublic; |
| 29 | + } |
| 30 | + |
| 31 | + @Override |
| 32 | + public final Object getProperty(Interpreter interp, ST self, T model, Object property, String propertyName) |
| 33 | + throws STNoSuchPropertyException { |
| 34 | + if (model == null) |
| 35 | + throw new NullPointerException("o"); |
| 36 | + |
| 37 | + final Class<?> cls = model.getClass(); |
| 38 | + if (property == null) |
| 39 | + throw STExceptions.noSuchPropertyInClass(cls, propertyName, null); |
| 40 | + |
| 41 | + propertyName = toAlias(propertyName); |
| 42 | + |
| 43 | + try { |
| 44 | + final MemberInvokers mis = TypeFunctions.get(cls, propertyName); |
| 45 | + if (mis.maxTypeConverterCount() == 0) { |
| 46 | + final List<Object> args = Collections.emptyList(); |
| 47 | + final MemberInvoker invoker = mis.find(onlyPublic, Object.class, args); |
| 48 | + if (null == invoker) { |
| 49 | + TypeFunctions.get(cls, propertyName); |
| 50 | + throw new IllegalArgumentException("No matching field, method, or static method found"); |
| 51 | + } |
| 52 | + return invoker.invoke(model, args); |
| 53 | + } |
| 54 | + |
| 55 | + final Composite r = new Composite(interp, self, model, propertyName, onlyPublic, Object.class, mis); |
| 56 | + |
| 57 | + // Ensure that COMPOSITE_ADAPTER is registered to handle Composite objects. |
| 58 | + final STGroup stg = self.groupThatCreatedThisInstance; |
| 59 | + if (COMPOSITE_ADAPTER != stg.getModelAdaptor(Composite.class)) |
| 60 | + stg.registerModelAdaptor(Composite.class, COMPOSITE_ADAPTER); |
| 61 | + |
| 62 | + return r; |
| 63 | + } catch (Throwable t) { |
| 64 | + throw STExceptions.noSuchPropertyInObject(model, propertyName, t); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + protected String toAlias(String name) { |
| 69 | + return name; |
| 70 | + } |
| 71 | + |
| 72 | + /** |
| 73 | + * Only created if memberInvokers.maxTypeConverterCount() more than zero. |
| 74 | + * <p> |
| 75 | + * Caries Interpreter and ST, so can resolve excess properties. |
| 76 | + */ |
| 77 | + private static final class Composite implements UnaryOperator<Object> { |
| 78 | + private final Interpreter interp; |
| 79 | + private final ST self; |
| 80 | + private final boolean onlyPublic; |
| 81 | + private final Class<?> returnType; |
| 82 | + private final Object value; |
| 83 | + private final String propertyName; |
| 84 | + private final MemberInvokers memberInvokers; |
| 85 | + private final List<Object> args = new ArrayList<>(); |
| 86 | + private MemberInvoker invoker; |
| 87 | + |
| 88 | + public Composite(final Interpreter interp, |
| 89 | + final ST self, |
| 90 | + final Object value, |
| 91 | + final String propertyName, |
| 92 | + final boolean onlyPublic, |
| 93 | + final Class<?> returnType, |
| 94 | + final MemberInvokers memberInvokers) { |
| 95 | + this.interp = interp; |
| 96 | + this.self = self; |
| 97 | + this.onlyPublic = onlyPublic; |
| 98 | + this.returnType = returnType; |
| 99 | + this.value = value; |
| 100 | + this.propertyName = propertyName; |
| 101 | + this.memberInvokers = memberInvokers; |
| 102 | + |
| 103 | + // Allow for 0..n args methods |
| 104 | + final MemberInvoker test = memberInvokers.find(onlyPublic, returnType, args); |
| 105 | + if (null != test) |
| 106 | + invoker = test; |
| 107 | + } |
| 108 | + |
| 109 | + @Override |
| 110 | + public Object apply(final Object o) { |
| 111 | + args.add(o); |
| 112 | + MemberInvoker test = memberInvokers.find(onlyPublic, returnType, args); |
| 113 | + if (null != test) |
| 114 | + invoker = test; // The latest best match |
| 115 | + return args.size() < memberInvokers.maxTypeConverterCount() |
| 116 | + ? this |
| 117 | + : invoke(); |
| 118 | + } |
| 119 | + |
| 120 | + @SuppressWarnings({"rawtypes", "unchecked"}) |
| 121 | + Object invoke() { |
| 122 | + Object r = null; |
| 123 | + int i = 0; |
| 124 | + final int n = args.size(); |
| 125 | + try { |
| 126 | + if (null == invoker) |
| 127 | + throw new IllegalArgumentException("No suitable field, method, or static method found"); |
| 128 | + r = invoker.invoke(value, args); |
| 129 | + // Resolve each excess property, using the appropriate model adapter. |
| 130 | + final STGroup stg = self.groupThatCreatedThisInstance; |
| 131 | + i = invoker.typeConverterCount(); |
| 132 | + while (i < n) { |
| 133 | + final Object arg = args.get(i++); |
| 134 | + final ModelAdaptor ma = stg.getModelAdaptor(arg.getClass()); |
| 135 | + if (null == ma) |
| 136 | + throw new IllegalStateException("No ModelAdapter for " + arg.getClass().getTypeName()); |
| 137 | + r = ma.getProperty(interp, self, r, arg, arg.toString()); |
| 138 | + } |
| 139 | + return r; |
| 140 | + } catch (Throwable t) { |
| 141 | + // Must use a copy of args to avoid damage by toString(), during debug! |
| 142 | + final StringJoiner sj = new StringJoiner("."); |
| 143 | + if (i == 0) { |
| 144 | + add(sj, propertyName); |
| 145 | + args.forEach(arg -> add(sj, arg)); |
| 146 | + } else { |
| 147 | + add(sj, r); |
| 148 | + args.subList(i, n).forEach(arg -> add(sj, arg)); |
| 149 | + } |
| 150 | + throw STExceptions.noSuchPropertyInObject(value, sj.toString(), t); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + private static void add(StringJoiner sj, Object o) { |
| 155 | + if (o instanceof CharSequence) |
| 156 | + sj.add(o.toString()); |
| 157 | + else |
| 158 | + sj.add(String.format("%s{%s}", o.getClass().getSimpleName(), o)); |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Must call invoke, in case no more properties after last one. |
| 163 | + */ |
| 164 | + public String toString() { |
| 165 | + return invoke().toString(); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + private static final ModelAdaptor<Composite> COMPOSITE_ADAPTER = |
| 170 | + (interp, self, model, property, propertyName) -> model.apply(property); |
| 171 | +} |
0 commit comments