Skip to content

Commit a99ce97

Browse files
committed
Initial commit.
1 parent e47a365 commit a99ce97

33 files changed

+3389
-1
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,30 @@
11
# string-template-utils
2-
General utilities for use with String Template ST4 jar, with utilities for my rebuild of string-template-maven-plugin
2+
General utilities for use with [StringTemplate](http://www.stringtemplate.org/) v4 jar, with utilities for my rebuild of string-template-maven-plugin
3+
4+
The core is allows far more powerful MethodHandle based replacements for the original Method based ObjectModelAdapter
5+
and StringRenderer, the ability to use parameterised methods in both the model class and compatible static methods in
6+
other class, thus allows easy bypass of the stupid no-null and boolean conditional restrictions of the StringTemplate
7+
interpreter.
8+
9+
Functionality includes:
10+
- A TypeConverter class, which make matching/conversion of property values to method parameter types easy.
11+
- A MemberInvoker class, which makes matching and invoking Members easy, with MethodHandles for much faster invoke.
12+
- A MethodInvokers class, which can be fast searched for both instance and static MemberInvoker objects.
13+
- A TypeFunctions class, which allow registration, for a type, of instance fields and methods, and static methods
14+
methods, and request of a MethodInvokers object for each a Member name(s).
15+
- An ObjectFunctions class, designed to be registered with TypeFunction, providing some useful Object static methods.
16+
- A StringFunctions class, designed to be registered with TypeFunction, providing some useful String static methods.
17+
- An AbstractInvokeAdapter class, which extends ModelAdapter providing an abstract base for calling fields,
18+
and parameterised Method, with parameters in chained properties, for a type. Parameters are joined/matched using a
19+
hidden Composite Object driven by it's own hidden ModelAdapter, or a toString() call. Chaining different types is
20+
possible, via lookup of the appropriate ModelHandler from the used STGroup.
21+
in the same property chain, via embedded ModelAdapter lookup.
22+
- A StringInvokeAdapter class, which extends InvokeAdapter for String type use.
23+
- A StringInvokeRender class, which extends AttributeRenderer and uses TypeFunctions to access instance String methods
24+
with no parameters and static methods accepting a String parameter, and possibly a Locale parameter.
25+
- Most of the classes are public for directly use or protected so that other libraries can extend them.
26+
27+
The code was designed to be Thread-safe too, with minimal locking.
28+
29+
### TODO
30+
- Consider adding more to the Javadocs or provide examples in Markdown files.

pom.xml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<properties>
7+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
8+
<!-- The professional way to specify a specific JDK -->
9+
<toolchain.jdk.version>1.8</toolchain.jdk.version>
10+
<toolchain.jdk.vendor>AdoptOpenJDK</toolchain.jdk.vendor>
11+
<maven.compiler.source>${toolchain.jdk.version}</maven.compiler.source>
12+
<maven.compiler.target>${toolchain.jdk.version}</maven.compiler.target>
13+
<doclint>none</doclint>
14+
</properties>
15+
16+
<groupId>rwperrott</groupId>
17+
<artifactId>string-template-utils</artifactId>
18+
<version>2.2.0</version>
19+
<packaging>jar</packaging>
20+
21+
<name>StringTemplate Utils</name>
22+
23+
<dependencies>
24+
<dependency>
25+
<groupId>org.antlr</groupId>
26+
<artifactId>ST4</artifactId>
27+
<version>4.3.1</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>it.unimi.dsi</groupId>
31+
<artifactId>fastutil</artifactId>
32+
<version>8.4.3</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.apache.commons</groupId>
36+
<artifactId>commons-lang3</artifactId>
37+
<version>3.11</version>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.apache.commons</groupId>
41+
<artifactId>commons-text</artifactId>
42+
<version>1.9</version>
43+
</dependency>
44+
45+
<dependency>
46+
<scope>test</scope>
47+
<groupId>org.testng</groupId>
48+
<artifactId>testng</artifactId>
49+
<version>7.3.0</version>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.projectlombok</groupId>
53+
<artifactId>lombok</artifactId>
54+
<version>1.18.12</version>
55+
<type>jar</type>
56+
</dependency>
57+
</dependencies>
58+
59+
<build>
60+
<plugins>
61+
<plugin>
62+
<groupId>org.apache.maven.plugins</groupId>
63+
<artifactId>maven-clean-plugin</artifactId>
64+
<version>3.1.0</version>
65+
</plugin>
66+
<plugin>
67+
<groupId>org.apache.maven.plugins</groupId>
68+
<artifactId>maven-source-plugin</artifactId>
69+
<version>3.2.1</version>
70+
<executions>
71+
<execution>
72+
<id>attach-sources</id>
73+
<goals>
74+
<goal>jar-no-fork</goal>
75+
</goals>
76+
</execution>
77+
</executions>
78+
</plugin>
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-compiler-plugin</artifactId>
82+
<version>3.8.1</version>
83+
</plugin>
84+
<plugin>
85+
<artifactId>maven-surefire-plugin</artifactId>
86+
<version>2.22.2</version>
87+
</plugin>
88+
<plugin>
89+
<groupId>org.apache.maven.plugins</groupId>
90+
<artifactId>maven-jar-plugin</artifactId>
91+
<version>3.2.0</version>
92+
</plugin>
93+
<plugin>
94+
<groupId>org.apache.maven.plugins</groupId>
95+
<artifactId>maven-javadoc-plugin</artifactId>
96+
<version>3.2.0</version>
97+
<configuration>
98+
<doctitle>StringTemplate Utils - ${project.version}</doctitle>
99+
<windowtitle>StringTemplate Utils - ${project.version}</windowtitle>
100+
<show>protected</show>
101+
<!--
102+
<links>
103+
A supported Java version unlike 1.6!
104+
<link>https://docs.oracle.com/javase/8/docs/api/</link>
105+
</links>
106+
-->
107+
</configuration>
108+
<executions>
109+
<execution>
110+
<id>attach-javadocs</id>
111+
<goals>
112+
<goal>jar</goal>
113+
</goals>
114+
</execution>
115+
</executions>
116+
</plugin>
117+
</plugins>
118+
</build>
119+
</project>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.stringtemplate.v4.misc;
2+
3+
/**
4+
* Converts relative template line numbers from STRuntimeMessages, to absolute line numbers
5+
*/
6+
public class STRuntimeMessagePatch extends STRuntimeMessage {
7+
final int absoluteStartLineNumber;
8+
9+
public STRuntimeMessagePatch(final STRuntimeMessage of, final int absoluteStartLineNumber) {
10+
super(of.interp, of.error, of.ip, of.scope, of.cause, of.arg, of.arg2, of.arg3);
11+
this.absoluteStartLineNumber = absoluteStartLineNumber;
12+
}
13+
14+
@Override
15+
public String getSourceLocation() {
16+
if ( ip<0 || self==null || self.impl==null ) return null;
17+
Interval I = self.impl.sourceMap[ip];
18+
if ( I==null ) return null;
19+
// Count line and charPos to I.a position
20+
final String s = self.impl.template;
21+
int p = 0, index = I.a, line = absoluteStartLineNumber, charPos = 0;
22+
while (p < index ) {
23+
switch (s.charAt(p++)) {
24+
case '\r':
25+
if (p < index && s.charAt(p) == '\n')
26+
p++;
27+
case '\n':
28+
charPos = 0;
29+
line++;
30+
break;
31+
default:
32+
charPos++;
33+
break;
34+
}
35+
}
36+
37+
return new Coordinate(line,charPos).toString();
38+
}
39+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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

Comments
 (0)