StackOverflowError from Custom Moshi Adapter - java

I created a moshi adapter to address null String values:
public class NullStringAdapter {
#FromJson
public String stringFromJson(#Nullable String value) {
if (value.equals(null)) {
return "nulled";
}
return value;
}
}
I created a Moshi instance with it and added it to my retrofit:
Moshi moshi = new Moshi.Builder().add(new NullStringAdapter()).build();
Retrofit.Builder().baseUrl(baseURL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
At runtime, I get a StackOverflowError from a repetitive method in the MoshiAdapterMethodsFactory:
Caused by: java.lang.StackOverflowError: stack size 1038KB
at com.squareup.moshi.Moshi.cacheKey(Moshi.java:140)
at com.squareup.moshi.Moshi.adapter(Moshi.java:69)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
at com.squareup.moshi.AdapterMethodsFactory$5.fromJson(AdapterMethodsFactory.java:212)
at com.squareup.moshi.AdapterMethodsFactory$1.fromJson(AdapterMethodsFactory.java:81)
.... and so on.
The two problem code areas are line 212:
#Override public Object fromJson(Moshi moshi, JsonReader reader)
throws IOException, IllegalAccessException, InvocationTargetException {
JsonAdapter<Object> delegate = moshi.adapter(parameterTypes[0], qualifierAnnotations);
*****Object intermediate = delegate.fromJson(reader);*****
return method.invoke(adapter, intermediate);
}
And line 81:
#Override public Object fromJson(JsonReader reader) throws IOException {
if (fromAdapter == null) {
return delegate.fromJson(reader);
} else if (!fromAdapter.nullable && reader.peek() == JsonReader.Token.NULL) {
reader.nextNull();
return null;
} else {
try {
*****return fromAdapter.fromJson(moshi, reader);*****
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
throw new JsonDataException(e.getCause() + " at " + reader.getPath());
}
}
}
Why is the method factory endlessly generating these methods?

The problem is that you are trying to adapt String to String, that's what creates the loop thus resulting in a StackOverflowError. Create a custom annotation for all the strings that you expect to have the "nulled" value, smth like this:
#JsonQualifier
#Retention(RetentionPolicy.RUNTIME)
public #interface NullableString { }
And then change your adapter to:
public class NullStringAdapter {
#FromJson #NullableString
public String stringFromJson(#Nullable String value) {
if (value.equals(null)) {
return "nulled";
}
return value;
}
}
Or if you want to enforce this type or deserializing to every String in you parse from json (altho I would not advice for that) you can declare your adapter using a factory:
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
#Override public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
if (!annotations.isEmpty()) return null;
Class<?> rawType = Types.getRawType(type);
if (rawType.equals(String.class) return /** your custom JsonAdapter here. */;
return null;
}
};

Related

How can i deserialize complex List like List<List<Object>> using Java reflection and Jackson like SpringMVC #RequestBody do

I don't know if it's clear for you, I want to implement a method like SpringMVC's #RequestBody(), this method can deserialize input json string to object, and this method should support complex object type(like List<List<Object>>)
Now using Java reflection and Jackson, we can easily deserialize List<Object>, such as "[1,100]" to List<Long>, but if the input is "[[1,100]]", there is nested list in another list.
Here is my code:
// there is a class and method's parameter has annotation #Param
class Code{
public void getName(#Param("fooList") List<List<Long>> fooList) {
System.out.println("getName run");
}
}
// reflection Code#getName and deserialize method
public Object inject(Parameter param) {
Object object = null;
try {
Class<?> paramType = param.getType();
Param paramAnnotation = param.getAnnotation(Param.class);
// for unit test purpose, hard code json string here
object = "[[1,3]]";
if (object != null) {
if (Collection.class == paramType || List.class.isAssignableFrom(paramType)) {
// basic type
if (clazz != null) {
// todo: string to list, need support complex List
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(text,
objectMapper.getTypeFactory().constructCollectionType(List.class,
clazz));
} catch (Exception e) {
throw new SDKException("");
}
List list = JSONs.toList(object.toString(), clazz);
List converterArgs = new ArrayList<>(list.size());
for (Object arg : list) {
try {
arg = Casts.cast(clazz, arg);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
converterArgs.add(arg);
}
object = converterArgs;
}
}
if (String.class.isAssignableFrom(paramType)) {
object = JSONs.string(object);
} else {
if (!object.getClass().equals(paramType)) {
try {
object = Casts.cast(paramType, object);
} catch (Exception e) {
throw new InvalidParametersException("type error");
}
}
}
}
} catch (Exception e) {
throw new SDKException("", e);
}
return object;
}
#Test
public void testReflection() {
try {
Method getName = Code.class.getMethod("getName", List.class);
Parameter[] parameters = getName.getParameters();
AnnotationParamInjector injector = new AnnotationParamInjector();
Object inject = injector.inject(parameters[0]);
System.out.println("inject = " + inject);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
Backgroud: I want to implement a method like SpringMVC's controller, so I need to deserialize a method parameter to an object. Right now there is only support for a normal class and a List but none for a complex List.
Questions:
Java reflection can parse generic class, even complex List, we can get reflect List from outer to inner, but how can I parse say a json string "[[1, 100]]"? This is the problem that I'm trying to solve.
Here is my pseudocode, I recursively call constructClass() to get its generic row type, after calling constructClass(), the json string should be parsed, ie something like "[[1,100]]" should change to "[1,100]"
public void constructClass(String text, Class<?> clazz) {
Type type = clazz.getGenericSuperclass();
if (Collection.class == clazz || List.class.isAssignableFrom(clazz)) {
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type genericType = pType.getActualTypeArguments()[0];
this.constructClass(text, genericType.getClass());
}
} else if (Map.class == clazz) {
} else {
}
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
pType.getActualTypeArguments()[0]
}
}
Could someone provide a good way to do this?

Jackson Deserialize Integer Converting Error

I am trying to handle if required filed comes with a String value while I expect it as Integer. For example;
{
"transactionTimeMilliseconds": "asd"
}
but it defined as int in Java code.
private int transactionTimeMilliseconds;
#JsonCreator
public Channel(#JsonProperty("transactionTimeMilliseconds") int transactionTimeMilliseconds) {
this.transactionTimeMilliseconds = transactionTimeMilliseconds;
}
I have an exception informer class.
CLASS
#ControllerAdvice
public class ExceptionConfiguration extends ResponseEntityExceptionHandler {
#ExceptionHandler(MismatchedInputException.class) // Or whatever exception type you want to handle
public ResponseEntity<JsonException> handleMissingFieldError(MismatchedInputException exception) { // Or whatever exception type you want to handle
int code = 601;
String message = exception.getMessage().split("\n")[0] + exception.getMessage().split(";")[1].replace("]", "");
JsonException jsonException = new JsonException(code,message);
return ResponseEntity.status(jsonException.getCode()).body(jsonException);
}
#ExceptionHandler(UnrecognizedPropertyException.class) // Or whatever exception type you want to handle
public ResponseEntity<JsonException> handleUnrecognizedFieldError(UnrecognizedPropertyException exception) { // Or whatever exception type you want to handle
int code = 602;
String message = exception.getMessage().split(",")[0] + exception.getMessage().split(";")[1].replace("]", "");
JsonException jsonException = new JsonException(code,message);
return ResponseEntity.status(jsonException.getCode()).body(jsonException);
}
#ExceptionHandler(JsonParseException.class) // Or whatever exception type you want to handle
public ResponseEntity<JsonException> handleJsonParseError(JsonParseException exception) {
int code = 603;
String message = exception.getMessage().split(":")[0] + exception.getMessage().split(";")[1].replace("]", "");
JsonException jsonException = new JsonException(code,message);
return ResponseEntity.status(jsonException.getCode()).body(jsonException);
}
#ExceptionHandler(InvalidFormatException.class) // Or whatever exception type you want to handle
public ResponseEntity<JsonException> handleJsonInvalidFormatError(InvalidFormatException exception) {
int code = 604;
String message = exception.getMessage().split(":")[0] + exception.getMessage().split(";")[1].replace("]", "");
JsonException jsonException = new JsonException(code,message);
return ResponseEntity.status(jsonException.getCode()).body(jsonException);
}
#ExceptionHandler(JsonMappingException.class) // Or whatever exception type you want to handle
public ResponseEntity<JsonException> handleNullFieldError(JsonMappingException exception) {
int code = 605;
String message = exception.getMessage().split(":")[0] + exception.getMessage().split(";")[1].replace("]", "");
JsonException jsonException = new JsonException(code,message);
return ResponseEntity.status(jsonException.getCode()).body(jsonException);
}
}
I have to recognize that value, and if this field is wrong as written in above, set it default value as 0.
Should I write a custom deserializer to solve this problem? Thanks.
something like this worked for me :
class Val {
private int v;
public int getV() {
return v;
}
#JsonSetter // or #JsonProperty("v")
public void setV(String v) {
System.out.println("in setter");
try {
this.v = Integer.parseInt(v);
} catch (Exception e) {
this.v = 0;
}
}
}
Test:
#Test
public void test() throws IOException {
String json = " { \"v\" : 1 } ";
Val v = new ObjectMapper().readValue(json, Val.class);
System.out.println(v.getV()); // prints 1
json = " { \"v\" : \"asd\" } ";
v = new ObjectMapper().readValue(json, Val.class);
System.out.println(v.getV()); // prints 0
}
I tried something like this but couldn't get it to work so far.
class Val {
private int v;
#JsonCreator
public Val(#JsonProperty("v") String v) {
System.out.println("in setter");
try {
this.v = Integer.parseInt(v);
} catch (Exception e) {
this.v = 0;
}
}
public int getV() {
return v;
}
}

Catching exception while converting HTTP request data into enum

I have a problem while deserialization of HTTP request value into enum with custom com.fasterxml.jackson.databind.JsonDeserializer deserializer implementation:
public class EnvelopeColorJsonDeserializer extends JsonDeserializer<EnvelopeColor> {
#Override
public EnvelopeColor deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String val = p.getValueAsString();
return EnvelopeColor.fromCode(val);
}
}
This is how I convert value into enum:
public static EnvelopeColor fromCode(String code) {
Assert.notNull(code, "code");
for (EnvelopeColor type : values()) {
if (code.equals(type.code)) {
return type;
}
}
throw new RuntimeException("Not supported color: " + code);
}
Endpoint:
#PostMapping("/")
public #ResponseBody
ResponseEntity add(#RequestBody EnvelopeDto envelope) {
// some stuff goes here...
}
Question
Is there some way how to a) check if HTTP request value is valid enum constant value before going to deserialization process or b) how to catch exception in #ControllerAdvice exception handler? (I would like to avoid throwing custom exception in fromCode() method).
You can add an exception handler for class HttpMessageNotReadableException.
This exception is thrown when Spring is not able to deserialize the payload into the DTO.
#ExceptionHandler(org.springframework.http.converter.HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected #ResponseBody handleIncorrectData(HttpMessageNotReadableException ex,
HttpServletRequest request, HttpServletResponse response){
....
}
Moreover you can define a custom EnumConverter which will give the exact details to user which are the correct enum values.
public class CustomEnumConverter extends EnumConverter {
#Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
try {
return super.unmarshal(reader, context);
} catch (IllegalArgumentException e) {
String inputValue = reader.getValue();
Class contextType = context.getRequiredType();
StringBuilder sb = new StringBuilder();
Object[] enumConstants = contextType.getEnumConstants();
for (Object o : enumConstants) {
sb.append(o + ",");
}
if (sb.length() > 0)
sb.deleteCharAt(sb.length() - 1);
throw new InvalidArgumentException(ErrorCode.INVALID_ARGUMENT, inputValue,
reader.getNodeName(), sb.toString());
}
}
}
for catch exception in #ControllerAdvice exception handler, try this :
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(ApplicationExceptionHandler.class);
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({RuntimeException.class})
public #ResponseBody Response<Error> handleIllegalArgumentException(Exception e) {
logger.error("BadRequestException :: ", e);
return new Response<Error>(new Error("BadRequestException", e.getMessage()));
}
}

Combining several methods into a single generic method

If I have 'n' number of methods like this, is there a way out by which I can optimize this and make it a single function?
Or are there other better options by which I can make this more generic?
public List<Address> getAddressList(String response) {
List<Address> AddressList = new ArrayList<Address>();
if (response != null && response.length() > 0) {
try {
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Address>>(){}.getType();
AddressList = gson.fromJson(response, collectionType);
} catch (IllegalStateException ex) {
} catch (Exception ex) {
}
}
return AddressList;
}
public List<Tweet> getTweetList(String response) {
List<Tweet> tweetList = new ArrayList<Tweet>();
if (response != null && response.length() > 0) {
try {
Gson gson = new Gson();
Type collectionType = new TypeToken<List<Tweet>>(){}.getType();
tweetList = gson.fromJson(response, collectionType);
} catch (IllegalStateException ex) {
} catch (Exception ex) {
}
}
return tweetList;
}
To duplicate axtavt's answer from this here question:
There is no way to do it without passing actual type of T (as Class<T>) to your method.
But if you pass it explicitly, you can create a TypeToken for List<T> as follows:
private <T> List<T> GetListFromFile(String filename, Class<T> elementType) {
...
TypeToken<List<T>> token = new TypeToken<List<T>>() {}
.where(new TypeParameter<T>() {}, elementType);
List<T> something = gson.fromJson(data, token);
...
}
See also:
TypeToken
So, to answer your question, you could do something like this:
public List<Address> getAddressList(final String response) {
return getGenericList(response, Address.class);
}
public List<Tweet> getTweetList(final String response) {
return getGenericList(response, Tweet.class);
}
#SuppressWarnings("serial")
private <T> List<T> getGenericList(final String response, final Class<T> elementType) {
List<T> list = new ArrayList<T>();
if (response != null && response.length() > 0) {
try {
final Gson gson = new Gson();
final Type collectionType =
new TypeToken<List<T>>(){}.where(new TypeParameter<T>() {}, elementType).getType();
list = gson.fromJson(response, collectionType);
}
catch (final IllegalStateException ex) {
}
catch (final Exception ex) {
}
}
return list;
}
EDIT: Tried the code out
I tried this code out with the following small test that should just create a list of a couple of addresses:
public static void main(final String[] args) {
final List<Address> addressList = getAddressList("[{}, {}]");
System.out.println(addressList);
}
And the output was:
[gson.Address#6037fb1e, gson.Address#7b479feb]
I made my own Address class in my test project, hence the gson.Address in the above output.

How can I introspect a freemarker template to find out what variables it uses?

I'm not at all sure that this is even a solvable problem, but supposing that I have a freemarker template, I'd like to be able to ask the template what variables it uses.
For my purposes, we can assume that the freemarker template is very simple - just "root level" entries (the model for such a template could be a simple Map). In other words, I don't need to handle templates that call for nested structures, etc.
one other way to get the variables from java. This just tries to process the template and catch the InvalidReferenceException to find all the variables in a freemarker-template
/**
* Find all the variables used in the Freemarker Template
* #param templateName
* #return
*/
public Set<String> getTemplateVariables(String templateName) {
Template template = getTemplate(templateName);
StringWriter stringWriter = new StringWriter();
Map<String, Object> dataModel = new HashMap<>();
boolean exceptionCaught;
do {
exceptionCaught = false;
try {
template.process(dataModel, stringWriter);
} catch (InvalidReferenceException e) {
exceptionCaught = true;
dataModel.put(e.getBlamedExpressionString(), "");
} catch (IOException | TemplateException e) {
throw new IllegalStateException("Failed to Load Template: " + templateName, e);
}
} while (exceptionCaught);
return dataModel.keySet();
}
private Template getTemplate(String templateName) {
try {
return configuration.getTemplate(templateName);
} catch (IOException e) {
throw new IllegalStateException("Failed to Load Template: " + templateName, e);
}
}
This is probably late, but in case anyone else encountered this problem : you can use 'data_model' and 'globals' to inspect the model - data_model will only contain values provided by the model while globals will also contain any variables defined in the template.
You need to prepend the special variables with a dot - so to access globals, use ${.globals}
For other special variables see http://freemarker.sourceforge.net/docs/ref_specvar.html
I had the same task to get the list of variables from template on java side and don't found any good approaches to that except using reflection. I'm not sure whether there is a better way to get this data or not but here's my approach:
public Set<String> referenceSet(Template template) throws TemplateModelException {
Set<String> result = new HashSet<>();
TemplateElement rootTreeNode = template.getRootTreeNode();
for (int i = 0; i < rootTreeNode.getChildCount(); i++) {
TemplateModel templateModel = rootTreeNode.getChildNodes().get(i);
if (!(templateModel instanceof StringModel)) {
continue;
}
Object wrappedObject = ((StringModel) templateModel).getWrappedObject();
if (!"DollarVariable".equals(wrappedObject.getClass().getSimpleName())) {
continue;
}
try {
Object expression = getInternalState(wrappedObject, "expression");
switch (expression.getClass().getSimpleName()) {
case "Identifier":
result.add(getInternalState(expression, "name").toString());
break;
case "DefaultToExpression":
result.add(getInternalState(expression, "lho").toString());
break;
case "BuiltinVariable":
break;
default:
throw new IllegalStateException("Unable to introspect variable");
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new TemplateModelException("Unable to reflect template model");
}
}
return result;
}
private Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = o.getClass().getDeclaredField(fieldName);
boolean wasAccessible = field.isAccessible();
try {
field.setAccessible(true);
return field.get(o);
} finally {
field.setAccessible(wasAccessible);
}
}
Sample project that I made for demonstrating template introspection can be found on github: https://github.com/SimY4/TemplatesPOC.git
I solved this for my very simple usecase (only using flat datastructure, no nesting (for example: ${parent.child}), lists or more specific) with dummy data provider:
public class DummyDataProvider<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1;
public final Set<String> variables = new HashSet<>();
#SuppressWarnings("unchecked")
#Override
public V get(Object key) {
variables.add(key.toString());
return (V) key;
}
}
You can give this for processing to a template and when it finishes Set variables contains your variables.
This is very simplistic approach, which certainly needs improvement, but you get the idea.
public static Set<String> getNecessaryTemplateVariables(String templateName) throws TemplateModelException {
Set<String> result = new HashSet<>();
TemplateElement rootTreeNode = getTemplate(templateName).getRootTreeNode();
if ("IteratorBlock".equals(rootTreeNode.getClass().getSimpleName())) {
introspectFromIteratorBlock(result, rootTreeNode);
return result;
}
for (int i = 0; i < rootTreeNode.getChildCount(); i++) {
TemplateModel templateModel = rootTreeNode.getChildNodes().get(i);
if (!(templateModel instanceof StringModel)) {
continue;
}
Object wrappedObject = ((StringModel) templateModel).getWrappedObject();
if ("DollarVariable".equals(wrappedObject.getClass().getSimpleName())) {
introspectFromDollarVariable(result, wrappedObject);
} else if ("ConditionalBlock".equals(wrappedObject.getClass().getSimpleName())) {
introspectFromConditionalBlock(result, wrappedObject);
} else if ("IfBlock".equals(wrappedObject.getClass().getSimpleName())) {
introspectFromIfBlock(result, wrappedObject);
} else if ("IteratorBlock".equals(wrappedObject.getClass().getSimpleName())) {
introspectFromIteratorBlock(result, wrappedObject);
}
}
return result;
}
private static void introspectFromIteratorBlock(Set<String> result, Object wrappedObject) {
try {
Object expression = getInternalState(wrappedObject, "listExp");
result.add(getInternalState(expression, "name").toString());
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
}
private static void introspectFromConditionalBlock(Set<String> result, Object wrappedObject)
throws TemplateModelException {
try {
Object expression = getInternalState(wrappedObject, "condition");
if (expression == null) {
return;
}
result.addAll(dealCommonExpression(expression));
String nested = getInternalState(wrappedObject, "nestedBlock").toString();
result.addAll(getNecessaryTemplateVariables(nested));
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
}
private static Set<String> dealCommonExpression(Object expression)
throws NoSuchFieldException, IllegalAccessException {
Set<String> ret = Sets.newHashSet();
switch (expression.getClass().getSimpleName()) {
case "ComparisonExpression":
String reference = dealComparisonExpression(expression);
ret.add(reference);
break;
case "ExistsExpression":
reference = dealExistsExpression(expression);
ret.add(reference);
break;
case "AndExpression":
ret.addAll(dealAndExpression(expression));
default:
break;
}
return ret;
}
private static String dealComparisonExpression(Object expression)
throws NoSuchFieldException, IllegalAccessException {
Object left = getInternalState(expression, "left");
Object right = getInternalState(expression, "right");
String reference;
if ("Identifier".equals(left.getClass().getSimpleName())) {
reference = getInternalState(left, "name").toString();
} else {
reference = getInternalState(right, "name").toString();
}
return reference;
}
private static String dealExistsExpression(Object expression) throws NoSuchFieldException, IllegalAccessException {
Object exp = getInternalState(expression, "exp");
return getInternalState(exp, "name").toString();
}
private static Set<String> dealAndExpression(Object expression) throws NoSuchFieldException,
IllegalAccessException{
Set<String> ret = Sets.newHashSet();
Object lho = getInternalState(expression, "lho");
ret.addAll(dealCommonExpression(lho));
Object rho = getInternalState(expression, "rho");
ret.addAll(dealCommonExpression(rho));
return ret;
}
private static void introspectFromIfBlock(Set<String> result, Object wrappedObject)
throws TemplateModelException {
result.addAll(getNecessaryTemplateVariables(wrappedObject.toString()));
}
private static void introspectFromDollarVariable(Set<String> result, Object wrappedObject)
throws TemplateModelException {
try {
Object expression = getInternalState(wrappedObject, "expression");
switch (expression.getClass().getSimpleName()) {
case "Identifier":
result.add(getInternalState(expression, "name").toString());
break;
case "DefaultToExpression":
result.add(getInternalState(expression, "lho").toString());
break;
case "BuiltinVariable":
break;
default:
break;
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new TemplateModelException("Unable to reflect template model");
}
}
private static Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field [] fieldArray = o.getClass().getDeclaredFields();
for (Field field : fieldArray) {
if (!field.getName().equals(fieldName)) {
continue;
}
boolean wasAccessible = field.isAccessible();
try {
field.setAccessible(true);
return field.get(o);
} finally {
field.setAccessible(wasAccessible);
}
}
throw new NoSuchFieldException();
}
private static Template getTemplate(String templateName) {
try {
StringReader stringReader = new StringReader(templateName);
return new Template(null, stringReader, null);
} catch (IOException e) {
throw new IllegalStateException("Failed to Load Template: " + templateName, e);
}
}
I optimized SimY4's answer, and supported <#list> and <#if> block. Code is not fully tested
Execute following regex on the template:
(?<=\$\{)([^\}]+)(?=\})
(?<=\$\{) Matches everything followed by ${
([^\}]+) Matches any string not containing }
(?=\}) Matches everything before }
I had the same problem and none of posted solution made sense to me. What I came with is plugging custom implementation of
TemplateExceptionHandler. Example:
final private List<String> missingReferences = Lists.newArrayList();
final Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setTemplateExceptionHandler(new TemplateExceptionHandler() {
#Override
public void handleTemplateException(TemplateException arg0, Environment arg1, Writer arg2) throws TemplateException {
if (arg0 instanceof InvalidReferenceException) {
missingReferences.add(arg0.getBlamedExpressionString());
return;
}
throw arg0;
}
}
Template template = loadTemplate(cfg, templateId, templateText);
StringWriter out = new StringWriter();
try {
template.process(templateData, out);
} catch (TemplateException | IOException e) {
throw new RuntimeException("oops", e);
}
System.out.println("Missing references: " + missingReferences);
Read more here: https://freemarker.apache.org/docs/pgui_config_errorhandling.html

Categories

Resources