All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.boot.json.JsonWriter Maven / Gradle / Ivy

There is a newer version: 3.4.3
Show newest version
/*
 * Copyright 2012-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.json;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import org.springframework.boot.json.JsonValueWriter.Series;
import org.springframework.boot.json.JsonWriter.Member.Extractor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Interface that can be used to write JSON output. Typically used to generate JSON when a
 * dependency on a fully marshalling library (such as Jackson or Gson) cannot be assumed.
 * 

* For standard Java types, the {@link #standard()} factory method may be used to obtain * an instance of this interface. It supports {@link String}, {@link Number} and * {@link Boolean} as well as {@link Collection}, {@code Array}, {@link Map} and * {@link WritableJson} types. Typical usage would be: * *

 * JsonWriter<Map<String,Object>> writer = JsonWriter.standard();
 * writer.write(Map.of("Hello", "World!"), out);
 * 
*

* More complex mappings can be created using the {@link #of(Consumer)} method with a * callback to configure the {@link Members JSON members} that should be written. Typical * usage would be: * *

 * JsonWriter<Person> writer = JsonWriter.of((members) -> {
 *     members.add("first", Person::firstName);
 *     members.add("last", Person::lastName);
 *     members.add("dob", Person::dateOfBirth)
 *         .whenNotNull()
 *         .as(DateTimeFormatter.ISO_DATE::format);
 * });
 * writer.write(person, out);
 * 
*

* The {@link #writeToString(Object)} method can be used if you want to write the JSON * directly to a {@link String}. To write to other types of output, the * {@link #write(Object)} method may be used to obtain a {@link WritableJson} instance. * * @param the type being written * @author Phillip Webb * @author Moritz Halbritter * @since 3.4.0 */ @FunctionalInterface public interface JsonWriter { /** * Write the given instance to the provided {@link Appendable}. * @param instance the instance to write (may be {@code null} * @param out the output that should receive the JSON * @throws IOException on IO error */ void write(T instance, Appendable out) throws IOException; /** * Write the given instance to a JSON string. * @param instance the instance to write (may be {@code null}) * @return the JSON string */ default String writeToString(T instance) { return write(instance).toJsonString(); } /** * Provide a {@link WritableJson} implementation that may be used to write the given * instance to various outputs. * @param instance the instance to write (may be {@code null}) * @return a {@link WritableJson} instance that may be used to write the JSON */ default WritableJson write(T instance) { return WritableJson.of((out) -> write(instance, out)); } /** * Return a new {@link JsonWriter} instance that appends a new line after the JSON has * been written. * @return a new {@link JsonWriter} instance that appends a new line after the JSON */ default JsonWriter withNewLineAtEnd() { return withSuffix("\n"); } /** * Return a new {@link JsonWriter} instance that appends the given suffix after the * JSON has been written. * @param suffix the suffix to write, if any * @return a new {@link JsonWriter} instance that appends a suffixafter the JSON */ default JsonWriter withSuffix(String suffix) { if (!StringUtils.hasLength(suffix)) { return this; } return (instance, out) -> { write(instance, out); out.append(suffix); }; } /** * Factory method to return a {@link JsonWriter} for standard Java types. See * {@link JsonValueWriter class-level javadoc} for details. * @param the type to write * @return a {@link JsonWriter} instance */ static JsonWriter standard() { return of(Members::add); } /** * Factory method to return a {@link JsonWriter} with specific {@link Members member * mapping}. See {@link JsonValueWriter class-level javadoc} and {@link Members} for * details. * @param the type to write * @param members a consumer, which should configure the members * @return a {@link JsonWriter} instance * @see Members */ static JsonWriter of(Consumer> members) { // Don't inline 'new Members' (must be outside of lambda) Members initializedMembers = new Members<>(members, false); return (instance, out) -> initializedMembers.write(instance, new JsonValueWriter(out)); } /** * Callback used to configure JSON members. Individual members can be declared using * the various {@code add(...)} methods. Typically, members are declared with a * {@code "name"} and a {@link Function} that will extract the value from the * instance. Members can also be declared using a static value or a {@link Supplier}. * The {@link #add(String)} and {@link #add()} methods may be used to access the * actual instance being written. *

* Members can be added without a {@code name} when a {@code Member.using(...)} method * is used to complete the definition. *

* Members can filtered using {@code Member.when} methods and adapted to different * types using {@link Member#as(Function) Member.as(...)}. * * @param the type that will be written */ final class Members { private final List> members = new ArrayList<>(); private final boolean contributesPair; private final Series series; private final JsonWriterFiltersAndProcessors jsonProcessors = new JsonWriterFiltersAndProcessors(); Members(Consumer> members, boolean contributesToExistingSeries) { Assert.notNull(members, "'members' must not be null"); members.accept(this); Assert.state(!this.members.isEmpty(), "No members have been added"); this.contributesPair = this.members.stream().anyMatch(Member::contributesPair); this.series = (this.contributesPair && !contributesToExistingSeries) ? Series.OBJECT : null; if (this.contributesPair || this.members.size() > 1) { this.members.forEach((member) -> Assert.state(member.contributesPair(), () -> String.format("%s does not contribute a named pair, ensure that all members have " + "a name or call an appropriate 'using' method", member))); } } /** * Add a new member with access to the instance being written. * @param name the member name * @return the added {@link Member} which may be configured further */ public Member add(String name) { return add(name, (instance) -> instance); } /** * Add a new member with a static value. * @param the value type * @param name the member name * @param value the member value * @return the added {@link Member} which may be configured further */ public Member add(String name, V value) { return add(name, (instance) -> value); } /** * Add a new member with a supplied value. * @param the value type * @param name the member name * @param supplier a supplier of the value * @return the added {@link Member} which may be configured further */ public Member add(String name, Supplier supplier) { Assert.notNull(supplier, "'supplier' must not be null"); return add(name, (instance) -> supplier.get()); } /** * Add a new member with an extracted value. * @param the value type * @param name the member name * @param extractor a function to extract the value * @return the added {@link Member} which may be configured further */ public Member add(String name, Function extractor) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(extractor, "'extractor' must not be null"); return addMember(name, extractor); } /** * Add a new member with access to the instance being written. The member is added * without a name, so one of the {@code Member.using(...)} methods must be used to * complete the configuration. * @return the added {@link Member} which may be configured further */ public Member add() { return from(Function.identity()); } /** * Add all entries from the given {@link Map} to the JSON. * @param the map type * @param the key type * @param the value type * @param extractor a function to extract the map * @return the added {@link Member} which may be configured further */ public , K, V> Member addMapEntries(Function extractor) { return from(extractor).usingPairs(Map::forEach); } /** * Add members from a static value. One of the {@code Member.using(...)} methods * must be used to complete the configuration. * @param the value type * @param value the member value * @return the added {@link Member} which may be configured further */ public Member from(V value) { return from((instance) -> value); } /** * Add members from a supplied value. One of the {@code Member.using(...)} methods * must be used to complete the configuration. * @param the value type * @param supplier a supplier of the value * @return the added {@link Member} which may be configured further */ public Member from(Supplier supplier) { Assert.notNull(supplier, "'supplier' must not be null"); return from((instance) -> supplier.get()); } /** * Add members from an extracted value. One of the {@code Member.using(...)} * methods must be used to complete the configuration. * @param the value type * @param extractor a function to extract the value * @return the added {@link Member} which may be configured further */ public Member from(Function extractor) { Assert.notNull(extractor, "'extractor' must not be null"); return addMember(null, extractor); } /** * Add a filter that will be used to restrict the members written to the JSON. * @param predicate the predicate used to filter members */ public void applyingPathFilter(Predicate predicate) { Assert.notNull(predicate, "'predicate' must not be null"); this.jsonProcessors.pathFilters().add(predicate); } /** * Add the a {@link NameProcessor} to be applied when the JSON is written. * @param nameProcessor the name processor to add */ public void applyingNameProcessor(NameProcessor nameProcessor) { Assert.notNull(nameProcessor, "'nameProcessor' must not be null"); this.jsonProcessors.nameProcessors().add(nameProcessor); } /** * Add the a {@link ValueProcessor} to be applied when the JSON is written. * @param valueProcessor the value processor to add */ public void applyingValueProcessor(ValueProcessor valueProcessor) { Assert.notNull(valueProcessor, "'valueProcessor' must not be null"); this.jsonProcessors.valueProcessors().add(valueProcessor); } private Member addMember(String name, Function extractor) { Member member = new Member<>(this.members.size(), name, Extractor.of(extractor)); this.members.add(member); return member; } /** * Writes the given instance using the configured {@link Member members}. * @param instance the instance to write * @param valueWriter the JSON value writer to use */ void write(T instance, JsonValueWriter valueWriter) { valueWriter.pushProcessors(this.jsonProcessors); valueWriter.start(this.series); for (Member member : this.members) { member.write(instance, valueWriter); } valueWriter.end(this.series); valueWriter.popProcessors(); } /** * Return if any of the members contributes a name/value pair to the JSON. * @return if a name/value pair is contributed */ boolean contributesPair() { return this.contributesPair; } } /** * A member that contributes JSON. Typically, a member will contribute a single * name/value pair based on an extracted value. They may also contribute more complex * JSON structures when configured with one of the {@code using(...)} methods. *

* The {@code when(...)} methods may be used to filter a member (omit it entirely from * the JSON). The {@link #as(Function)} method can be used to adapt to a different * type. * * @param the member type */ final class Member { private final int index; private final String name; private Extractor extractor; private BiConsumer> pairs; private Members members; Member(int index, String name, Extractor extractor) { this.index = index; this.name = name; this.extractor = extractor; } /** * Only include this member when its value is not {@code null}. * @return a {@link Member} which may be configured further */ public Member whenNotNull() { return when(Objects::nonNull); } /** * Only include this member when an extracted value is not {@code null}. * @param extractor an function used to extract the value to test * @return a {@link Member} which may be configured further */ public Member whenNotNull(Function extractor) { Assert.notNull(extractor, "'extractor' must not be null"); return when((instance) -> Objects.nonNull(extractor.apply(instance))); } /** * Only include this member when it is not {@code null} and has a * {@link Object#toString() toString()} that is not zero length. * @return a {@link Member} which may be configured further * @see StringUtils#hasLength(CharSequence) */ public Member whenHasLength() { return when((instance) -> instance != null && StringUtils.hasLength(instance.toString())); } /** * Only include this member when it is not empty (See * {@link ObjectUtils#isEmpty(Object)} for details). * @return a {@link Member} which may be configured further */ public Member whenNotEmpty() { return whenNot(ObjectUtils::isEmpty); } /** * Only include this member when the given predicate does not match. * @param predicate the predicate to test * @return a {@link Member} which may be configured further */ public Member whenNot(Predicate predicate) { Assert.notNull(predicate, "'predicate' must not be null"); return when(predicate.negate()); } /** * Only include this member when the given predicate matches. * @param predicate the predicate to test * @return a {@link Member} which may be configured further */ public Member when(Predicate predicate) { Assert.notNull(predicate, "'predicate' must not be null"); this.extractor = this.extractor.when(predicate); return this; } /** * Adapt the value by applying the given {@link Function}. * @param the result type * @param adapter a {@link Function} to adapt the value * @return a {@link Member} which may be configured further */ @SuppressWarnings("unchecked") public Member as(Function adapter) { Assert.notNull(adapter, "'adapter' must not be null"); Member result = (Member) this; result.extractor = this.extractor.as(adapter); return result; } /** * Add JSON name/value pairs by extracting values from a series of elements. * Typically used with a {@link Iterable#forEach(Consumer)} call, for example: * *

		 * members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, pairExtractor);
		 * 
*

* When used with a named member, the pairs will be added as a new JSON value * object: * *

		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * 
* * When used with an unnamed member the pairs will be added to the existing JSON * object: * *
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * 
* @param the element type * @param elements callback used to provide the elements * @param extractor a {@link PairExtractor} used to extract the name/value pair * @return a {@link Member} which may be configured further * @see #usingExtractedPairs(BiConsumer, Function, Function) * @see #usingPairs(BiConsumer) */ public Member usingExtractedPairs(BiConsumer> elements, PairExtractor extractor) { Assert.notNull(elements, "'elements' must not be null"); Assert.notNull(extractor, "'extractor' must not be null"); return usingExtractedPairs(elements, extractor::getName, extractor::getValue); } /** * Add JSON name/value pairs by extracting values from a series of elements. * Typically used with a {@link Iterable#forEach(Consumer)} call, for example: * *
		 * members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, Tag::getName, Tag::getValue);
		 * 
*

* When used with a named member, the pairs will be added as a new JSON value * object: * *

		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * 
* * When used with an unnamed member the pairs will be added to the existing JSON * object: * *
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * 
* @param the element type * @param the name type * @param the value type * @param elements callback used to provide the elements * @param nameExtractor {@link Function} used to extract the name * @param valueExtractor {@link Function} used to extract the value * @return a {@link Member} which may be configured further * @see #usingExtractedPairs(BiConsumer, PairExtractor) * @see #usingPairs(BiConsumer) */ public Member usingExtractedPairs(BiConsumer> elements, Function nameExtractor, Function valueExtractor) { Assert.notNull(elements, "'elements' must not be null"); Assert.notNull(nameExtractor, "'nameExtractor' must not be null"); Assert.notNull(valueExtractor, "'valueExtractor' must not be null"); return usingPairs((instance, pairsConsumer) -> elements.accept(instance, (element) -> { N name = nameExtractor.apply(element); V value = valueExtractor.apply(element); pairsConsumer.accept(name, value); })); } /** * Add JSON name/value pairs. Typically used with a * {@link Map#forEach(BiConsumer)} call, for example: * *
		 * members.add(Event::getLabels).usingPairs(Map::forEach);
		 * 
*

* When used with a named member, the pairs will be added as a new JSON value * object: * *

		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * 
* * When used with an unnamed member the pairs will be added to the existing JSON * object: * *
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * 
* @param the name type * @param the value type * @param pairs callback used to provide the pairs * @return a {@link Member} which may be configured further * @see #usingExtractedPairs(BiConsumer, PairExtractor) * @see #usingPairs(BiConsumer) */ @SuppressWarnings({ "unchecked", "rawtypes" }) public Member usingPairs(BiConsumer> pairs) { Assert.notNull(pairs, "'pairs' must not be null"); Assert.state(this.pairs == null, "Pairs cannot be declared multiple times"); Assert.state(this.members == null, "Pairs cannot be declared when using members"); this.pairs = (BiConsumer) pairs; return this; } /** * Add JSON based on further {@link Members} configuration. For example: * *
		 * members.add(User::getName).usingMembers((personMembers) -> {
		 *     personMembers.add("first", Name::first);
		 *     personMembers.add("last", Name::last);
		 * });
		 * 
* *

* When used with a named member, the result will be added as a new JSON value * object: * *

		 * {
		 *   "name": {
		 *     "first": "Jane",
		 *     "last": "Doe"
		 *   }
		 * }
		 * 
* * When used with an unnamed member the result will be added to the existing JSON * object: * *
		 * {
		 *   "first": "John",
		 *   "last": "Doe"
		 * }
		 * 
* @param members callback to configure the members * @return a {@link Member} which may be configured further * @see #usingExtractedPairs(BiConsumer, PairExtractor) * @see #usingPairs(BiConsumer) */ public Member usingMembers(Consumer> members) { Assert.notNull(members, "'members' must not be null"); Assert.state(this.members == null, "Members cannot be declared multiple times"); Assert.state(this.pairs == null, "Members cannot be declared when using pairs"); this.members = new Members<>(members, this.name == null); return this; } /** * Writes the given instance using details configure by this member. * @param instance the instance to write * @param valueWriter the JSON value writer to use */ void write(Object instance, JsonValueWriter valueWriter) { T extracted = this.extractor.extract(instance); if (Extractor.skip(extracted)) { return; } Object value = getValueToWrite(extracted, valueWriter); valueWriter.write(this.name, value); } private Object getValueToWrite(T extracted, JsonValueWriter valueWriter) { if (this.pairs != null) { return WritableJson.of((out) -> valueWriter.writePairs((pairs) -> this.pairs.accept(extracted, pairs))); } if (this.members != null) { return WritableJson.of((out) -> this.members.write(extracted, valueWriter)); } return extracted; } /** * Whether this contributes one or more name/value pairs to the JSON. * @return whether a name/value pair is contributed */ boolean contributesPair() { return this.name != null || this.pairs != null || (this.members != null && this.members.contributesPair()); } @Override public String toString() { return "Member at index " + this.index + ((this.name != null) ? "{%s}".formatted(this.name) : ""); } /** * Internal class used to manage member value extraction and filtering. * * @param the member type */ @FunctionalInterface interface Extractor { /** * Represents a skipped value. */ Object SKIP = new Object(); /** * Extract the value from the given instance. * @param instance the source instance * @return the extracted value or {@link #SKIP} */ T extract(Object instance); /** * Only extract when the given predicate matches. * @param predicate the predicate to test * @return a new {@link Extractor} */ default Extractor when(Predicate predicate) { return (instance) -> test(extract(instance), predicate); } @SuppressWarnings("unchecked") private T test(T extracted, Predicate predicate) { return (!skip(extracted) && predicate.test(extracted)) ? extracted : (T) SKIP; } /** * Adapt the extracted value. * @param the result type * @param adapter the adapter to use * @return a new {@link Extractor} */ default Extractor as(Function adapter) { return (instance) -> apply(extract(instance), adapter); } @SuppressWarnings("unchecked") private R apply(T extracted, Function function) { if (skip(extracted)) { return (R) SKIP; } return (extracted != null) ? function.apply(extracted) : null; } /** * Create a new {@link Extractor} based on the given {@link Function}. * @param the source type * @param the extracted type * @param extractor the extractor to use * @return a new {@link Extractor} instance */ @SuppressWarnings("unchecked") static Extractor of(Function extractor) { return (instance) -> !skip(instance) ? extractor.apply((S) instance) : (T) SKIP; } /** * Return if the extracted value should be skipped. * @param the value type * @param extracted the value to test * @return if the value is to be skipped */ static boolean skip(T extracted) { return extracted == SKIP; } } } /** * A path used to identify a specific JSON member. Paths can be represented as strings * in form {@code "my.json[1].item"} where elements are separated by {@code '.' } or * {@code []}. Reserved characters are escaped using {@code '\'}. * * @param parent the parent of this path * @param name the name of the member or {@code null} if the member is indexed. Path * names are provided as they were defined when the member was added and do not * include any {@link NameProcessor name processing}. * @param index the index of the member or {@link MemberPath#UNINDEXED} */ record MemberPath(MemberPath parent, String name, int index) { private static final String[] ESCAPED = { "\\", ".", "[", "]" }; public MemberPath { Assert.isTrue((name != null && index < 0) || (name == null && index >= 0), "'name' and 'index' cannot be mixed"); } /** * Indicates that the member has no index. */ public static final int UNINDEXED = -1; /** * The root of all member paths. */ static final MemberPath ROOT = new MemberPath(null, "", UNINDEXED); /** * Create a new child from this path with the specified index. * @param index the index of the child * @return a new {@link MemberPath} instance */ public MemberPath child(int index) { return new MemberPath(this, null, index); } /** * Create a new child from this path with the specified name. * @param name the name of the child * @return a new {@link MemberPath} instance */ public MemberPath child(String name) { return (!StringUtils.hasLength(name)) ? this : new MemberPath(this, name, UNINDEXED); } @Override public final String toString() { return toString(true); } /** * Return a string representation of the path without any escaping. * @return the unescaped string representation */ public final String toUnescapedString() { return toString(false); } private String toString(boolean escape) { StringBuilder string = new StringBuilder((this.parent != null) ? this.parent.toString(escape) : ""); if (this.index >= 0) { string.append("[").append(this.index).append("]"); } else { string.append((!string.isEmpty()) ? "." : "").append((!escape) ? this.name : escape(this.name)); } return string.toString(); } private String escape(String name) { for (String escape : ESCAPED) { name = name.replace(escape, "\\" + escape); } return name; } /** * Create a new {@link MemberPath} instance from the given string. * @param value the path value * @return a new {@link MemberPath} instance */ public static MemberPath of(String value) { MemberPath path = MemberPath.ROOT; StringBuilder buffer = new StringBuilder(); boolean escape = false; for (char ch : value.toCharArray()) { if (!escape && ch == '\\') { escape = true; } else if (!escape && (ch == '.' || ch == '[')) { path = path.child(buffer.toString()); buffer.setLength(0); } else if (!escape && ch == ']') { path = path.child(Integer.parseUnsignedInt(buffer.toString())); buffer.setLength(0); } else { buffer.append(ch); escape = false; } } path = path.child(buffer.toString()); return path; } } /** * Interface that can be used to extract name/value pairs from an element. * * @param the element type */ interface PairExtractor { /** * Extract the name. * @param the name type * @param element the source element * @return the extracted name */ N getName(E element); /** * Extract the name. * @param the value type * @param element the source element * @return the extracted value */ V getValue(E element); /** * Factory method to create a {@link PairExtractor} using distinct name and value * extraction functions. * @param the element type * @param nameExtractor the name extractor * @param valueExtractor the value extraction * @return a new {@link PairExtractor} instance */ static PairExtractor of(Function nameExtractor, Function valueExtractor) { Assert.notNull(nameExtractor, "'nameExtractor' must not be null"); Assert.notNull(valueExtractor, "'valueExtractor' must not be null"); return new PairExtractor<>() { @Override @SuppressWarnings("unchecked") public N getName(T instance) { return (N) nameExtractor.apply(instance); } @Override @SuppressWarnings("unchecked") public V getValue(T instance) { return (V) valueExtractor.apply(instance); } }; } } /** * Callback interface that can be {@link Members#applyingNameProcessor(NameProcessor) * applied} to {@link Members} to change names or filter members. */ @FunctionalInterface interface NameProcessor { /** * Return a new name for the JSON member or {@code null} if the member should be * filtered entirely. * @param path the path of the member * @param existingName the existing and possibly already processed name. * @return the new name */ String processName(MemberPath path, String existingName); /** * Factory method to create a new {@link NameProcessor} for the given operation. * @param operation the operation to apply * @return a new {@link NameProcessor} instance */ static NameProcessor of(UnaryOperator operation) { Assert.notNull(operation, "'operation' must not be null"); return (path, existingName) -> operation.apply(existingName); } } /** * Callback interface that can be * {@link Members#applyingValueProcessor(ValueProcessor) applied} to {@link Members} * to process values before they are written. Typically used to filter values, for * example to reduce superfluous information or sanitize sensitive data. * * @param the value type */ @FunctionalInterface interface ValueProcessor { /** * Process the value at the given path. * @param path the path of the member containing the value * @param value the value being written (may be {@code null}) * @return the processed value */ T processValue(MemberPath path, T value); /** * Return a new processor from this one that only applied to members with the * given path (ignoring escape characters). * @param path the patch to match * @return a new {@link ValueProcessor} that only applies when the path matches */ default ValueProcessor whenHasUnescapedPath(String path) { return whenHasPath((candidate) -> candidate.toString(false).equals(path)); } /** * Return a new processor from this one that only applied to members with the * given path. * @param path the patch to match * @return a new {@link ValueProcessor} that only applies when the path matches */ default ValueProcessor whenHasPath(String path) { return whenHasPath(MemberPath.of(path)::equals); } /** * Return a new processor from this one that only applied to members that match * the given path predicate. * @param predicate the predicate that must match * @return a new {@link ValueProcessor} that only applies when the predicate * matches */ default ValueProcessor whenHasPath(Predicate predicate) { return (path, value) -> (predicate.test(path)) ? processValue(path, value) : value; } /** * Return a new processor from this one that only applies to member with values of * the given type. * @param type the type that must match * @return a new {@link ValueProcessor} that only applies when value is the given * type. */ default ValueProcessor whenInstanceOf(Class type) { return when(type::isInstance); } /** * Return a new processor from this one that only applies to member with values * that match the given predicate. * @param predicate the predicate that must match * @return a new {@link ValueProcessor} that only applies when the predicate * matches */ default ValueProcessor when(Predicate predicate) { return (name, value) -> (predicate.test(value)) ? processValue(name, value) : value; } /** * Factory method to crate a new {@link ValueProcessor} that applies the given * action. * @param the value type * @param type the value type * @param action the action to apply * @return a new {@link ValueProcessor} instance */ static ValueProcessor of(Class type, UnaryOperator action) { return of(action).whenInstanceOf(type); } /** * Factory method to crate a new {@link ValueProcessor} that applies the given * action. * @param the value type * @param action the action to apply * @return a new {@link ValueProcessor} instance */ static ValueProcessor of(UnaryOperator action) { Assert.notNull(action, "'action' must not be null"); return (name, value) -> action.apply(value); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy