ListPlaceholder.java
package com.timtrense.template.lang.std;
import com.timtrense.template.Context;
import com.timtrense.template.TemplatePart;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A placeholder for injecting a list-string of some collection.
* <p>
* The actual value may be any kind of {@link Collection} or array or {@link Stream} and must be
* present in the {@link Context} under the {@link #name}.
* The actual value may be a {@link Callable} returning an {@link Collection}, array or {@link Stream}.
* If the value is not present or the {@link Callable} returns {@code null} then the {@link #defaultValue} will
* be printed (which may be an arbitrary string and will NOT be formatted).
* <p>
* The given {@link Collection}, array or {@link Stream} will be completely enumerated.
* If a {@link Stream} is supplied, it will be fully consumed by this placeholder.
* {@link Objects#toString(Object)} will be called on each element.
* <p>
* <b>Warning: Do not supply infinite streams here</b>
* <p>
* A list-string is composed like {@link Collectors#joining(CharSequence, CharSequence, CharSequence)}.
* <p>
* Example: For a collection/array/stream containing 'A', 'B', 'C' and a prefix='[' and suffix=']' and delimiter=','
* the resulting string would be "<b>[A,B,C]</b>"
*
* @author Tim Trense
* @since 1.1
*/
@Data
@RequiredArgsConstructor
@AllArgsConstructor
public class ListPlaceholder implements TemplatePart {
public static final Object DEFAULT_VALUE = null;
public static final String DEFAULT_PREFIX = "[";
public static final String DEFAULT_SUFFIX = "]";
public static final String DEFAULT_DELIMITER = ",";
private @NonNull String name;
private Object defaultValue = DEFAULT_VALUE;
private @NonNull String delimiter = DEFAULT_DELIMITER;
private @NonNull String prefix = DEFAULT_PREFIX;
private @NonNull String suffix = DEFAULT_SUFFIX;
/**
* Creates an instance with default values
*
* @param name the {@link #name}
* @param defaultValue the {@link #defaultValue}
* @see #DEFAULT_DELIMITER
* @see #DEFAULT_PREFIX
* @see #DEFAULT_SUFFIX
*/
public ListPlaceholder(@NonNull String name, Object defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}
@SneakyThrows
@Override
public Object process(Context context) {
Object value = context.get(name);
if (value instanceof Callable) {
value = ((Callable<?>) value).call();
}
if (value == null) {
return defaultValue;
}
Stream<?> stream;
if (value.getClass().isArray()) {
final Object arrayObject = value;
stream = Stream.generate(new Supplier<>() {
private int index = 0;
@Override
public Object get() {
int thisIndex = index++;
if (thisIndex >= Array.getLength(arrayObject)) {
return null;
}
return Array.get(arrayObject, thisIndex);
}
}).takeWhile(Objects::nonNull);
} else if (value instanceof Collection) {
stream = ((Collection<?>) value).stream();
} else if (value instanceof Stream) {
stream = (Stream<?>) value;
} else {
return defaultValue;
}
return stream
.map(Objects::toString)
.collect(Collectors.joining(delimiter, prefix, suffix));
}
}