Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
rwperrott committed Dec 11, 2020
1 parent e47a365 commit a99ce97
Show file tree
Hide file tree
Showing 33 changed files with 3,389 additions and 1 deletion.
30 changes: 29 additions & 1 deletion README.md
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.
119 changes: 119 additions & 0 deletions pom.xml
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>
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 src/main/java/rwperrott/stringtemplate/v4/AbstractInvokeAdaptor.java
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);
}
Loading

0 comments on commit a99ce97

Please sign in to comment.