-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
3,389 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,30 @@ | ||
# string-template-utils | ||
General utilities for use with String Template ST4 jar, with utilities for my rebuild of string-template-maven-plugin | ||
General utilities for use with [StringTemplate](http://www.stringtemplate.org/) v4 jar, with utilities for my rebuild of string-template-maven-plugin | ||
|
||
The core is allows far more powerful MethodHandle based replacements for the original Method based ObjectModelAdapter | ||
and StringRenderer, the ability to use parameterised methods in both the model class and compatible static methods in | ||
other class, thus allows easy bypass of the stupid no-null and boolean conditional restrictions of the StringTemplate | ||
interpreter. | ||
|
||
Functionality includes: | ||
- A TypeConverter class, which make matching/conversion of property values to method parameter types easy. | ||
- A MemberInvoker class, which makes matching and invoking Members easy, with MethodHandles for much faster invoke. | ||
- A MethodInvokers class, which can be fast searched for both instance and static MemberInvoker objects. | ||
- A TypeFunctions class, which allow registration, for a type, of instance fields and methods, and static methods | ||
methods, and request of a MethodInvokers object for each a Member name(s). | ||
- An ObjectFunctions class, designed to be registered with TypeFunction, providing some useful Object static methods. | ||
- A StringFunctions class, designed to be registered with TypeFunction, providing some useful String static methods. | ||
- An AbstractInvokeAdapter class, which extends ModelAdapter providing an abstract base for calling fields, | ||
and parameterised Method, with parameters in chained properties, for a type. Parameters are joined/matched using a | ||
hidden Composite Object driven by it's own hidden ModelAdapter, or a toString() call. Chaining different types is | ||
possible, via lookup of the appropriate ModelHandler from the used STGroup. | ||
in the same property chain, via embedded ModelAdapter lookup. | ||
- A StringInvokeAdapter class, which extends InvokeAdapter for String type use. | ||
- A StringInvokeRender class, which extends AttributeRenderer and uses TypeFunctions to access instance String methods | ||
with no parameters and static methods accepting a String parameter, and possibly a Locale parameter. | ||
- Most of the classes are public for directly use or protected so that other libraries can extend them. | ||
|
||
The code was designed to be Thread-safe too, with minimal locking. | ||
|
||
### TODO | ||
- Consider adding more to the Javadocs or provide examples in Markdown files. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<!-- The professional way to specify a specific JDK --> | ||
<toolchain.jdk.version>1.8</toolchain.jdk.version> | ||
<toolchain.jdk.vendor>AdoptOpenJDK</toolchain.jdk.vendor> | ||
<maven.compiler.source>${toolchain.jdk.version}</maven.compiler.source> | ||
<maven.compiler.target>${toolchain.jdk.version}</maven.compiler.target> | ||
<doclint>none</doclint> | ||
</properties> | ||
|
||
<groupId>rwperrott</groupId> | ||
<artifactId>string-template-utils</artifactId> | ||
<version>2.2.0</version> | ||
<packaging>jar</packaging> | ||
|
||
<name>StringTemplate Utils</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.antlr</groupId> | ||
<artifactId>ST4</artifactId> | ||
<version>4.3.1</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>it.unimi.dsi</groupId> | ||
<artifactId>fastutil</artifactId> | ||
<version>8.4.3</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-lang3</artifactId> | ||
<version>3.11</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.apache.commons</groupId> | ||
<artifactId>commons-text</artifactId> | ||
<version>1.9</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<scope>test</scope> | ||
<groupId>org.testng</groupId> | ||
<artifactId>testng</artifactId> | ||
<version>7.3.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.projectlombok</groupId> | ||
<artifactId>lombok</artifactId> | ||
<version>1.18.12</version> | ||
<type>jar</type> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-clean-plugin</artifactId> | ||
<version>3.1.0</version> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-source-plugin</artifactId> | ||
<version>3.2.1</version> | ||
<executions> | ||
<execution> | ||
<id>attach-sources</id> | ||
<goals> | ||
<goal>jar-no-fork</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.8.1</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.22.2</version> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-jar-plugin</artifactId> | ||
<version>3.2.0</version> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-javadoc-plugin</artifactId> | ||
<version>3.2.0</version> | ||
<configuration> | ||
<doctitle>StringTemplate Utils - ${project.version}</doctitle> | ||
<windowtitle>StringTemplate Utils - ${project.version}</windowtitle> | ||
<show>protected</show> | ||
<!-- | ||
<links> | ||
A supported Java version unlike 1.6! | ||
<link>https://docs.oracle.com/javase/8/docs/api/</link> | ||
</links> | ||
--> | ||
</configuration> | ||
<executions> | ||
<execution> | ||
<id>attach-javadocs</id> | ||
<goals> | ||
<goal>jar</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
39 changes: 39 additions & 0 deletions
39
src/main/java/org/stringtemplate/v4/misc/STRuntimeMessagePatch.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package org.stringtemplate.v4.misc; | ||
|
||
/** | ||
* Converts relative template line numbers from STRuntimeMessages, to absolute line numbers | ||
*/ | ||
public class STRuntimeMessagePatch extends STRuntimeMessage { | ||
final int absoluteStartLineNumber; | ||
|
||
public STRuntimeMessagePatch(final STRuntimeMessage of, final int absoluteStartLineNumber) { | ||
super(of.interp, of.error, of.ip, of.scope, of.cause, of.arg, of.arg2, of.arg3); | ||
this.absoluteStartLineNumber = absoluteStartLineNumber; | ||
} | ||
|
||
@Override | ||
public String getSourceLocation() { | ||
if ( ip<0 || self==null || self.impl==null ) return null; | ||
Interval I = self.impl.sourceMap[ip]; | ||
if ( I==null ) return null; | ||
// Count line and charPos to I.a position | ||
final String s = self.impl.template; | ||
int p = 0, index = I.a, line = absoluteStartLineNumber, charPos = 0; | ||
while (p < index ) { | ||
switch (s.charAt(p++)) { | ||
case '\r': | ||
if (p < index && s.charAt(p) == '\n') | ||
p++; | ||
case '\n': | ||
charPos = 0; | ||
line++; | ||
break; | ||
default: | ||
charPos++; | ||
break; | ||
} | ||
} | ||
|
||
return new Coordinate(line,charPos).toString(); | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
src/main/java/rwperrott/stringtemplate/v4/AbstractInvokeAdaptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package rwperrott.stringtemplate.v4; | ||
|
||
import org.stringtemplate.v4.Interpreter; | ||
import org.stringtemplate.v4.ModelAdaptor; | ||
import org.stringtemplate.v4.ST; | ||
import org.stringtemplate.v4.STGroup; | ||
import org.stringtemplate.v4.misc.STNoSuchPropertyException; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.StringJoiner; | ||
import java.util.function.UnaryOperator; | ||
|
||
/** | ||
* This provides most/all the functionality to create fra more powerful ModelAdapters which can use an objects fields | ||
* and make parameterised use of it's instance/static methods and static methods of other registered class. | ||
* <p> | ||
* Uses unreflected and cached HethodHandles to invoke Field getters and Methods, which is a lot faster than the | ||
* redundant Security costs of calling invoke on Field or Method objects. | ||
* | ||
* @param <T> type to be adapted | ||
*/ | ||
public class AbstractInvokeAdaptor<T> implements ModelAdaptor<T> { | ||
private final boolean onlyPublic; | ||
|
||
protected AbstractInvokeAdaptor(final boolean onlyPublic) { | ||
this.onlyPublic = onlyPublic; | ||
} | ||
|
||
@Override | ||
public final Object getProperty(Interpreter interp, ST self, T model, Object property, String propertyName) | ||
throws STNoSuchPropertyException { | ||
if (model == null) | ||
throw new NullPointerException("o"); | ||
|
||
final Class<?> cls = model.getClass(); | ||
if (property == null) | ||
throw STExceptions.noSuchPropertyInClass(cls, propertyName, null); | ||
|
||
propertyName = toAlias(propertyName); | ||
|
||
try { | ||
final MemberInvokers mis = TypeFunctions.get(cls, propertyName); | ||
if (mis.maxTypeConverterCount() == 0) { | ||
final List<Object> args = Collections.emptyList(); | ||
final MemberInvoker invoker = mis.find(onlyPublic, Object.class, args); | ||
if (null == invoker) { | ||
TypeFunctions.get(cls, propertyName); | ||
throw new IllegalArgumentException("No matching field, method, or static method found"); | ||
} | ||
return invoker.invoke(model, args); | ||
} | ||
|
||
final Composite r = new Composite(interp, self, model, propertyName, onlyPublic, Object.class, mis); | ||
|
||
// Ensure that COMPOSITE_ADAPTER is registered to handle Composite objects. | ||
final STGroup stg = self.groupThatCreatedThisInstance; | ||
if (COMPOSITE_ADAPTER != stg.getModelAdaptor(Composite.class)) | ||
stg.registerModelAdaptor(Composite.class, COMPOSITE_ADAPTER); | ||
|
||
return r; | ||
} catch (Throwable t) { | ||
throw STExceptions.noSuchPropertyInObject(model, propertyName, t); | ||
} | ||
} | ||
|
||
protected String toAlias(String name) { | ||
return name; | ||
} | ||
|
||
/** | ||
* Only created if memberInvokers.maxTypeConverterCount() more than zero. | ||
* <p> | ||
* Caries Interpreter and ST, so can resolve excess properties. | ||
*/ | ||
private static final class Composite implements UnaryOperator<Object> { | ||
private final Interpreter interp; | ||
private final ST self; | ||
private final boolean onlyPublic; | ||
private final Class<?> returnType; | ||
private final Object value; | ||
private final String propertyName; | ||
private final MemberInvokers memberInvokers; | ||
private final List<Object> args = new ArrayList<>(); | ||
private MemberInvoker invoker; | ||
|
||
public Composite(final Interpreter interp, | ||
final ST self, | ||
final Object value, | ||
final String propertyName, | ||
final boolean onlyPublic, | ||
final Class<?> returnType, | ||
final MemberInvokers memberInvokers) { | ||
this.interp = interp; | ||
this.self = self; | ||
this.onlyPublic = onlyPublic; | ||
this.returnType = returnType; | ||
this.value = value; | ||
this.propertyName = propertyName; | ||
this.memberInvokers = memberInvokers; | ||
|
||
// Allow for 0..n args methods | ||
final MemberInvoker test = memberInvokers.find(onlyPublic, returnType, args); | ||
if (null != test) | ||
invoker = test; | ||
} | ||
|
||
@Override | ||
public Object apply(final Object o) { | ||
args.add(o); | ||
MemberInvoker test = memberInvokers.find(onlyPublic, returnType, args); | ||
if (null != test) | ||
invoker = test; // The latest best match | ||
return args.size() < memberInvokers.maxTypeConverterCount() | ||
? this | ||
: invoke(); | ||
} | ||
|
||
@SuppressWarnings({"rawtypes", "unchecked"}) | ||
Object invoke() { | ||
Object r = null; | ||
int i = 0; | ||
final int n = args.size(); | ||
try { | ||
if (null == invoker) | ||
throw new IllegalArgumentException("No suitable field, method, or static method found"); | ||
r = invoker.invoke(value, args); | ||
// Resolve each excess property, using the appropriate model adapter. | ||
final STGroup stg = self.groupThatCreatedThisInstance; | ||
i = invoker.typeConverterCount(); | ||
while (i < n) { | ||
final Object arg = args.get(i++); | ||
final ModelAdaptor ma = stg.getModelAdaptor(arg.getClass()); | ||
if (null == ma) | ||
throw new IllegalStateException("No ModelAdapter for " + arg.getClass().getTypeName()); | ||
r = ma.getProperty(interp, self, r, arg, arg.toString()); | ||
} | ||
return r; | ||
} catch (Throwable t) { | ||
// Must use a copy of args to avoid damage by toString(), during debug! | ||
final StringJoiner sj = new StringJoiner("."); | ||
if (i == 0) { | ||
add(sj, propertyName); | ||
args.forEach(arg -> add(sj, arg)); | ||
} else { | ||
add(sj, r); | ||
args.subList(i, n).forEach(arg -> add(sj, arg)); | ||
} | ||
throw STExceptions.noSuchPropertyInObject(value, sj.toString(), t); | ||
} | ||
} | ||
|
||
private static void add(StringJoiner sj, Object o) { | ||
if (o instanceof CharSequence) | ||
sj.add(o.toString()); | ||
else | ||
sj.add(String.format("%s{%s}", o.getClass().getSimpleName(), o)); | ||
} | ||
|
||
/** | ||
* Must call invoke, in case no more properties after last one. | ||
*/ | ||
public String toString() { | ||
return invoke().toString(); | ||
} | ||
} | ||
|
||
private static final ModelAdaptor<Composite> COMPOSITE_ADAPTER = | ||
(interp, self, model, property, propertyName) -> model.apply(property); | ||
} |
Oops, something went wrong.