8329948: Remove string template feature

Reviewed-by: jlahoda
This commit is contained in:
Maurizio Cimadamore 2024-04-17 14:10:28 +00:00
parent ff3e76fd0c
commit 03e84178eb
66 changed files with 183 additions and 7278 deletions

View file

@ -1,134 +0,0 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.runtime;
import java.lang.invoke.MethodHandle;
import java.util.List;
import java.util.Objects;
/**
* This class implements specialized {@link StringTemplate StringTemplates} produced by
* string template bootstrap method callsites generated by the compiler. Instances of this
* class are produced by {@link StringTemplateImplFactory}.
* <p>
* Values are stored by subclassing {@link Carriers.CarrierObject}. This allows specializations
* and sharing of value shapes without creating a new class for each shape.
* <p>
* {@link StringTemplate} fragments are shared via binding to the
* {@link java.lang.invoke.CallSite CallSite's} {@link MethodHandle}.
* <p>
* The {@link StringTemplateImpl} instance also carries
* specialized {@link MethodHandle MethodHandles} for producing the values list and interpolation.
* These {@link MethodHandle MethodHandles} are also shared by binding to the
* {@link java.lang.invoke.CallSite CallSite}.
*
* @since 21
*
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
* Do not rely on its availability.
*/
final class StringTemplateImpl extends Carriers.CarrierObject implements StringTemplate {
/**
* List of string fragments for the string template. This value of this list is shared by
* all instances created at the {@link java.lang.invoke.CallSite CallSite}.
*/
private final List<String> fragments;
/**
* Specialized {@link MethodHandle} used to implement the {@link StringTemplate StringTemplate's}
* {@code values} method. This {@link MethodHandle} is shared by all instances created at the
* {@link java.lang.invoke.CallSite CallSite}.
*/
private final MethodHandle valuesMH;
/**
* Specialized {@link MethodHandle} used to implement the {@link StringTemplate StringTemplate's}
* {@code interpolate} method. This {@link MethodHandle} is shared by all instances created at the
* {@link java.lang.invoke.CallSite CallSite}.
*/
private final MethodHandle interpolateMH;
/**
* Constructor.
*
* @param primitiveCount number of primitive slots required (bound at callsite)
* @param objectCount number of object slots required (bound at callsite)
* @param fragments list of string fragments (bound in (bound at callsite)
* @param valuesMH {@link MethodHandle} to produce list of values (bound at callsite)
* @param interpolateMH {@link MethodHandle} to produce interpolation (bound at callsite)
*/
StringTemplateImpl(int primitiveCount, int objectCount,
List<String> fragments, MethodHandle valuesMH, MethodHandle interpolateMH) {
super(primitiveCount, objectCount);
this.fragments = fragments;
this.valuesMH = valuesMH;
this.interpolateMH = interpolateMH;
}
@Override
public List<String> fragments() {
return fragments;
}
@Override
public List<Object> values() {
try {
return (List<Object>)valuesMH.invokeExact(this);
} catch (RuntimeException | Error ex) {
throw ex;
} catch (Throwable ex) {
throw new RuntimeException("string template values failure", ex);
}
}
@Override
public String interpolate() {
try {
return (String)interpolateMH.invokeExact(this);
} catch (RuntimeException | Error ex) {
throw ex;
} catch (Throwable ex) {
throw new RuntimeException("string template interpolate failure", ex);
}
}
@Override
public boolean equals(Object other) {
return other instanceof StringTemplate st &&
Objects.equals(fragments(), st.fragments()) &&
Objects.equals(values(), st.values());
}
@Override
public int hashCode() {
return Objects.hash(fragments(), values());
}
@Override
public String toString() {
return StringTemplate.toString(this);
}
}

View file

@ -1,204 +0,0 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.runtime;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.StringConcatException;
import java.lang.invoke.StringConcatFactory;
import java.util.Arrays;
import java.util.List;
/**
* This class synthesizes {@link StringTemplate StringTemplates} based on
* fragments and bootstrap method type. Usage is primarily from
* {@link java.lang.runtime.TemplateRuntime}.
*
* @since 21
*
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
* Do not rely on its availability.
*/
final class StringTemplateImplFactory {
/**
* Private constructor.
*/
StringTemplateImplFactory() {
throw new AssertionError("private constructor");
}
/*
* {@link StringTemplateImpl} constructor MethodHandle.
*/
private static final MethodHandle CONSTRUCTOR;
/*
* Frequently used method types.
*/
private static final MethodType MT_STRING_STIMPL =
MethodType.methodType(String.class, StringTemplateImpl.class);
private static final MethodType MT_LIST_STIMPL =
MethodType.methodType(List.class, StringTemplateImpl.class);
/**
* List (for nullable) of MethodHandle;
*/
private static final MethodHandle TO_LIST;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(void.class, int.class, int.class, List.class,
MethodHandle.class, MethodHandle.class);
CONSTRUCTOR = lookup.findConstructor(StringTemplateImpl.class, mt)
.asType(mt.changeReturnType(Carriers.CarrierObject.class));
mt = MethodType.methodType(List.class, Object[].class);
TO_LIST = lookup.findStatic(StringTemplateImplFactory.class, "toList", mt);
} catch(ReflectiveOperationException ex) {
throw new AssertionError("carrier static init fail", ex);
}
}
/**
* Create a new {@link StringTemplateImpl} constructor.
*
* @param fragments string template fragments
* @param type values types with StringTemplate return
*
* @return {@link MethodHandle} that can construct a {@link StringTemplateImpl} with arguments
* used as values.
*/
static MethodHandle createStringTemplateImplMH(List<String> fragments, MethodType type) {
Carriers.CarrierElements elements = Carriers.CarrierFactory.of(type);
MethodHandle[] components = elements
.components()
.stream()
.map(c -> c.asType(c.type().changeParameterType(0, StringTemplateImpl.class)))
.toArray(MethodHandle[]::new);
Class<?>[] ptypes = elements
.components()
.stream()
.map(c -> c.type().returnType())
.toArray(Class<?>[]::new);
int[] permute = new int[ptypes.length];
MethodHandle interpolateMH;
MethodType mt;
try {
interpolateMH = StringConcatFactory.makeConcatWithTemplate(fragments, List.of(ptypes));
} catch (StringConcatException ex) {
throw new RuntimeException("constructing internal string template", ex);
}
interpolateMH = MethodHandles.filterArguments(interpolateMH, 0, components);
interpolateMH = MethodHandles.permuteArguments(interpolateMH, MT_STRING_STIMPL, permute);
mt = MethodType.methodType(List.class, ptypes);
MethodHandle valuesMH = TO_LIST.asCollector(Object[].class, components.length).asType(mt);
valuesMH = MethodHandles.filterArguments(valuesMH, 0, components);
valuesMH = MethodHandles.permuteArguments(valuesMH, MT_LIST_STIMPL, permute);
MethodHandle constructor = MethodHandles.insertArguments(CONSTRUCTOR, 0,
elements.primitiveCount(), elements.objectCount(),
fragments, valuesMH, interpolateMH);
constructor = MethodHandles.foldArguments(elements.initializer(), 0, constructor);
mt = MethodType.methodType(StringTemplate.class, ptypes);
constructor = constructor.asType(mt);
return constructor;
}
/**
* Generic {@link StringTemplate}.
*
* @param fragments immutable list of string fragments from string template
* @param values immutable list of expression values
*/
private record SimpleStringTemplate(List<String> fragments, List<Object> values)
implements StringTemplate {
@Override
public String toString() {
return StringTemplate.toString(this);
}
}
/**
* Returns a new StringTemplate composed from fragments and values.
*
* @param fragments array of string fragments
* @param values array of expression values
*
* @return StringTemplate composed from fragments and values
*/
static StringTemplate newTrustedStringTemplate(String[] fragments, Object[] values) {
return new SimpleStringTemplate(List.of(fragments), toList(values));
}
/**
* Returns a new StringTemplate composed from fragments and values.
*
* @param fragments list of string fragments
* @param values array of expression values
*
* @return StringTemplate composed from fragments and values
*/
static StringTemplate newTrustedStringTemplate(List<String> fragments, Object[] values) {
return new SimpleStringTemplate(List.copyOf(fragments), toList(values));
}
/**
* Returns a new StringTemplate composed from fragments and values.
*
* @param fragments list of string fragments
* @param values list of expression values
*
* @return StringTemplate composed from fragments and values
*/
static StringTemplate newStringTemplate(List<String> fragments, List<?> values) {
@SuppressWarnings("unchecked")
List<Object> copy = (List<Object>)values.stream().toList();
return new SimpleStringTemplate(List.copyOf(fragments), copy);
}
/**
* Collect nullable elements from an array into a unmodifiable list.
* Elements are guaranteed to be safe.
*
* @param elements elements to place in list
*
* @return unmodifiable list.
*/
private static List<Object> toList(Object[] elements) {
return Arrays.stream(elements).toList();
}
}

View file

@ -1,269 +0,0 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.runtime;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.StringTemplate.Processor;
import java.lang.StringTemplate.Processor.Linkage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import jdk.internal.access.JavaTemplateAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.javac.PreviewFeature;
/**
* Manages string template bootstrap methods. These methods may be used, for example,
* by Java compiler implementations to create {@link StringTemplate} instances. For example,
* the java compiler will translate the following code;
* {@snippet :
* int x = 10;
* int y = 20;
* StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
* }
* to byte code that invokes the {@link java.lang.runtime.TemplateRuntime#newStringTemplate}
* bootstrap method to construct a {@link CallSite} that accepts two integers and produces a new
* {@link StringTemplate} instance.
* {@snippet :
* MethodHandles.Lookup lookup = MethodHandles.lookup();
* MethodType mt = MethodType.methodType(StringTemplate.class, int.class, int.class);
* CallSite cs = TemplateRuntime.newStringTemplate(lookup, "", mt, "", " + ", " = ", "");
* ...
* int x = 10;
* int y = 20;
* StringTemplate st = (StringTemplate)cs.getTarget().invokeExact(x, y);
* }
* If the string template requires more than
* {@link java.lang.invoke.StringConcatFactory#MAX_INDY_CONCAT_ARG_SLOTS} value slots,
* then the java compiler will use the
* {@link java.lang.runtime.TemplateRuntime#newLargeStringTemplate} bootstrap method
* instead. For example, the java compiler will translate the following code;
* {@snippet :
* int[] a = new int[1000], b = new int[1000];
* ...
* StringTemplate st = """
* \{a[0]} - \{b[0]}
* \{a[1]} - \{b[1]}
* ...
* \{a[999]} - \{b[999]}
* """;
* }
* to byte code that invokes the {@link java.lang.runtime.TemplateRuntime#newLargeStringTemplate}
* bootstrap method to construct a {@link CallSite} that accepts an array of integers and produces a new
* {@link StringTemplate} instance.
* {@snippet :
* MethodType mt = MethodType.methodType(StringTemplate.class, String[].class, Object[].class);
* CallSite cs = TemplateRuntime.newStringTemplate(lookup, "", mt);
* ...
* int[] a = new int[1000], b = new int[1000];
* ...
* StringTemplate st = (StringTemplate)cs.getTarget().invokeExact(
* new String[] { "", " - ", "\n", " - ", "\n", ... " - ", "\n" },
* new Object[] { a[0], b[0], a[1], b[1], ..., a[999], b[999]}
* );
* }
*
* @since 21
*/
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
public final class TemplateRuntime {
private static final JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
/**
* {@link MethodHandle} to {@link TemplateRuntime#defaultProcess}.
*/
private static final MethodHandle DEFAULT_PROCESS_MH;
/**
* {@link MethodHandle} to {@link TemplateRuntime#newTrustedStringTemplate}.
*/
private static final MethodHandle NEW_TRUSTED_STRING_TEMPLATE;
/**
* Initialize {@link MethodHandle MethodHandles}.
*/
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(Object.class,
List.class, Processor.class, Object[].class);
DEFAULT_PROCESS_MH =
lookup.findStatic(TemplateRuntime.class, "defaultProcess", mt);
mt = MethodType.methodType(StringTemplate.class, String[].class, Object[].class);
NEW_TRUSTED_STRING_TEMPLATE =
lookup.findStatic(StringTemplateImplFactory.class, "newTrustedStringTemplate", mt);
} catch (ReflectiveOperationException ex) {
throw new AssertionError("string bootstrap fail", ex);
}
}
/**
* Private constructor.
*/
private TemplateRuntime() {
throw new AssertionError("private constructor");
}
/**
* String template bootstrap method for creating string templates.
* The static arguments include the fragments list.
* The non-static arguments are the values.
*
* @param lookup method lookup from call site
* @param name method name - not used
* @param type method type
* (ptypes...) -> StringTemplate
* @param fragments fragment array for string template
*
* @return {@link CallSite} to handle create string template
*
* @throws NullPointerException if any of the arguments is null
* @throws Throwable if linkage fails
*/
public static CallSite newStringTemplate(MethodHandles.Lookup lookup,
String name,
MethodType type,
String... fragments) throws Throwable {
Objects.requireNonNull(lookup, "lookup is null");
Objects.requireNonNull(name, "name is null");
Objects.requireNonNull(type, "type is null");
Objects.requireNonNull(fragments, "fragments is null");
MethodHandle mh = StringTemplateImplFactory
.createStringTemplateImplMH(List.of(fragments), type).asType(type);
return new ConstantCallSite(mh);
}
/**
* String template bootstrap method for creating large string templates,
* i.e., when the number of value slots exceeds
* {@link java.lang.invoke.StringConcatFactory#MAX_INDY_CONCAT_ARG_SLOTS}.
* The non-static arguments are the fragments array and values array.
*
* @param lookup method lookup from call site
* @param name method name - not used
* @param type method type
* (String[], Object[]) -> StringTemplate
*
* @return {@link CallSite} to handle create large string template
*
* @throws NullPointerException if any of the arguments is null
* @throws Throwable if linkage fails
*/
public static CallSite newLargeStringTemplate(MethodHandles.Lookup lookup,
String name,
MethodType type) throws Throwable {
Objects.requireNonNull(lookup, "lookup is null");
Objects.requireNonNull(name, "name is null");
Objects.requireNonNull(type, "type is null");
return new ConstantCallSite(NEW_TRUSTED_STRING_TEMPLATE.asType(type));
}
/**
* String template bootstrap method for static final processors.
* The static arguments include the fragments array and a {@link MethodHandle}
* to retrieve the value of the static final processor.
* The non-static arguments are the values.
*
* @param lookup method lookup from call site
* @param name method name - not used
* @param type method type
* (ptypes...) -> Object
* @param processorGetter {@link MethodHandle} to get static final processor
* @param fragments fragments from string template
*
* @return {@link CallSite} to handle string template processing
*
* @throws NullPointerException if any of the arguments is null
* @throws Throwable if linkage fails
*
* @implNote this method is likely to be revamped before exiting preview.
*/
public static CallSite processStringTemplate(MethodHandles.Lookup lookup,
String name,
MethodType type,
MethodHandle processorGetter,
String... fragments) throws Throwable {
Objects.requireNonNull(lookup, "lookup is null");
Objects.requireNonNull(name, "name is null");
Objects.requireNonNull(type, "type is null");
Objects.requireNonNull(processorGetter, "processorGetter is null");
Objects.requireNonNull(fragments, "fragments is null");
Processor<?, ?> processor = (Processor<?, ?>)processorGetter.invoke();
MethodHandle mh = processor instanceof Linkage linkage
? linkage.linkage(List.of(fragments), type)
: defaultProcessMethodHandle(type, processor, List.of(fragments));
return new ConstantCallSite(mh);
}
/**
* Creates a simple {@link StringTemplate} and then invokes the processor's process method.
*
* @param fragments fragments from string template
* @param processor {@link Processor} to process
* @param values array of expression values
*
* @return result of processing the string template
*
* @throws Throwable when {@link Processor#process(StringTemplate)} throws
*/
private static Object defaultProcess(
List<String> fragments,
Processor<?, ?> processor,
Object[] values
) throws Throwable {
return processor.process(StringTemplate.of(fragments, Arrays.stream(values).toList()));
}
/**
* Generate a {@link MethodHandle} which is effectively invokes
* {@code processor.process(new StringTemplate(fragments, values...)}.
*
* @return default process {@link MethodHandle}
*/
private static MethodHandle defaultProcessMethodHandle(
MethodType type,
Processor<?, ?> processor,
List<String> fragments
) {
MethodHandle mh = MethodHandles.insertArguments(DEFAULT_PROCESS_MH, 0, fragments, processor);
return mh.asCollector(Object[].class, type.parameterCount()).asType(type);
}
}

View file

@ -1,152 +0,0 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.runtime;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaTemplateAccess;
import jdk.internal.access.SharedSecrets;
/**
* This class provides runtime support for string templates. The methods within
* are intended for internal use only.
*
* @since 21
*
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
* Do not rely on its availability.
*/
final class TemplateSupport implements JavaTemplateAccess {
/**
* Private constructor.
*/
private TemplateSupport() {
}
static {
SharedSecrets.setJavaTemplateAccess(new TemplateSupport());
}
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
/**
* Returns a StringTemplate composed from fragments and values.
*
* @implSpec The {@code fragments} list size must be one more that the
* {@code values} list size.
*
* @param fragments list of string fragments
* @param values list of expression values
*
* @return StringTemplate composed from fragments and values
*
* @throws IllegalArgumentException if fragments list size is not one more
* than values list size
* @throws NullPointerException if fragments is null or values is null or if any fragment is null.
*
* @implNote Contents of both lists are copied to construct immutable lists.
*/
@Override
public StringTemplate of(List<String> fragments, List<?> values) {
return StringTemplateImplFactory.newStringTemplate(fragments, values);
}
/**
* Creates a string that interleaves the elements of values between the
* elements of fragments.
*
* @param fragments list of String fragments
* @param values list of expression values
*
* @return String interpolation of fragments and values
*/
@Override
public String interpolate(List<String> fragments, List<?> values) {
int fragmentsSize = fragments.size();
int valuesSize = values.size();
if (fragmentsSize == 1) {
return fragments.get(0);
}
int size = fragmentsSize + valuesSize;
String[] strings = new String[size];
int i = 0, j = 0;
for (; j < valuesSize; j++) {
strings[i++] = fragments.get(j);
strings[i++] = String.valueOf(values.get(j));
}
strings[i] = fragments.get(j);
return JLA.join("", "", "", strings, size);
}
/**
* Combine one or more {@link StringTemplate StringTemplates} to produce a combined {@link StringTemplate}.
* {@snippet :
* StringTemplate st = StringTemplate.combine("\{a}", "\{b}", "\{c}");
* assert st.interpolate().equals("\{a}\{b}\{c}");
* }
*
* @param sts zero or more {@link StringTemplate}
*
* @return combined {@link StringTemplate}
*
* @throws NullPointerException if sts is null or if any element of sts is null
*/
@Override
public StringTemplate combine(StringTemplate... sts) {
Objects.requireNonNull(sts, "sts must not be null");
if (sts.length == 0) {
return StringTemplate.of("");
} else if (sts.length == 1) {
return Objects.requireNonNull(sts[0], "string templates should not be null");
}
int size = 0;
for (StringTemplate st : sts) {
Objects.requireNonNull(st, "string templates should not be null");
size += st.values().size();
}
String[] combinedFragments = new String[size + 1];
Object[] combinedValues = new Object[size];
combinedFragments[0] = "";
int fragmentIndex = 1;
int valueIndex = 0;
for (StringTemplate st : sts) {
Iterator<String> iterator = st.fragments().iterator();
combinedFragments[fragmentIndex - 1] += iterator.next();
while (iterator.hasNext()) {
combinedFragments[fragmentIndex++] = iterator.next();
}
for (Object value : st.values()) {
combinedValues[valueIndex++] = value;
}
}
return StringTemplateImplFactory.newTrustedStringTemplate(combinedFragments, combinedValues);
}
}