Home / Class/ Member Class — spring-boot Architecture

Member Class — spring-boot Architecture

Architecture documentation for the Member class in JsonWriter.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java lines 369–768

	final class Member<T> {

		private final int index;

		private final @Nullable String name;

		private ValueExtractor<? extends @Nullable T> valueExtractor;

		private @Nullable BiConsumer<T, BiConsumer<?, ?>> pairs;

		private @Nullable Members<T> members;

		Member(int index, @Nullable String name, ValueExtractor<? extends @Nullable T> valueExtractor) {
			this.index = index;
			this.name = name;
			this.valueExtractor = valueExtractor;
		}

		/**
		 * Only include this member when its value is not {@code null}.
		 * @return a {@link Member} which may be configured further
		 */
		public Member<T> 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<T> whenNotNull(Function<@Nullable T, ?> 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<T> 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<T> whenNotEmpty() {
			Predicate<@Nullable T> isEmpty = ObjectUtils::isEmpty;
			return whenNot(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<T> whenNot(Predicate<@Nullable T> 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<T> when(Predicate<? super @Nullable T> predicate) {
			Assert.notNull(predicate, "'predicate' must not be null");
			this.valueExtractor = this.valueExtractor.when(predicate);
			return this;
		}

		/**
		 * Adapt the value by applying the given {@link Function}.
		 * @param <R> the result type
		 * @param extractor a {@link Extractor} to adapt the value
		 * @return a {@link Member} which may be configured further
		 */
		@SuppressWarnings("unchecked")
		public <R> Member<R> as(Extractor<T, R> extractor) {
			Assert.notNull(extractor, "'adapter' must not be null");
			Member<R> result = (Member<R>) this;
			result.valueExtractor = this.valueExtractor.as(extractor::extract);
			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:
		 *
		 * <pre class="code">
		 * members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, pairExtractor);
		 * </pre>
		 * <p>
		 * When used with a named member, the pairs will be added as a new JSON value
		 * object:
		 *
		 * <pre>
		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * </pre>
		 *
		 * When used with an unnamed member the pairs will be added to the existing JSON
		 * object:
		 *
		 * <pre>
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * </pre>
		 * @param <E> 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 <E> Member<T> usingExtractedPairs(BiConsumer<T, Consumer<E>> elements, PairExtractor<E> 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:
		 *
		 * <pre class="code">
		 * members.add(Event::getTags).usingExtractedPairs(Iterable::forEach, Tag::getName, Tag::getValue);
		 * </pre>
		 * <p>
		 * When used with a named member, the pairs will be added as a new JSON value
		 * object:
		 *
		 * <pre>
		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * </pre>
		 *
		 * When used with an unnamed member the pairs will be added to the existing JSON
		 * object:
		 *
		 * <pre>
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * </pre>
		 * @param <E> the element type
		 * @param <N> the name type
		 * @param <V> 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 <E, N, V> Member<T> usingExtractedPairs(BiConsumer<T, Consumer<E>> elements,
				Function<E, N> nameExtractor, Function<E, V> 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:
		 *
		 * <pre class="code">
		 * members.add(Event::getLabels).usingPairs(Map::forEach);
		 * </pre>
		 * <p>
		 * When used with a named member, the pairs will be added as a new JSON value
		 * object:
		 *
		 * <pre>
		 * {
		 *   "name": {
		 *     "p1": 1,
		 *     "p2": 2
		 *   }
		 * }
		 * </pre>
		 *
		 * When used with an unnamed member the pairs will be added to the existing JSON
		 * object:
		 *
		 * <pre>
		 * {
		 *   "p1": 1,
		 *   "p2": 2
		 * }
		 * </pre>
		 * @param <N> the name type
		 * @param <V> 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 <N, V> Member<T> usingPairs(BiConsumer<T, BiConsumer<N, V>> 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:
		 *
		 * <pre class="code">
		 * members.add(User::getName).usingMembers((personMembers) -> {
		 *     personMembers.add("first", Name::first);
		 *     personMembers.add("last", Name::last);
		 * });
		 * </pre>
		 *
		 * <p>
		 * When used with a named member, the result will be added as a new JSON value
		 * object:
		 *
		 * <pre>
		 * {
		 *   "name": {
		 *     "first": "Jane",
		 *     "last": "Doe"
		 *   }
		 * }
		 * </pre>
		 *
		 * When used with an unnamed member the result will be added to the existing JSON
		 * object:
		 *
		 * <pre>
		 * {
		 *   "first": "John",
		 *   "last": "Doe"
		 * }
		 * </pre>
		 * @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<T> usingMembers(Consumer<Members<T>> 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(@Nullable Object instance, JsonValueWriter valueWriter) {
			T extracted = this.valueExtractor.extract(instance);
			if (ValueExtractor.skip(extracted)) {
				return;
			}
			Object value = getValueToWrite(extracted, valueWriter);
			valueWriter.write(this.name, value);
		}

		private @Nullable Object getValueToWrite(@Nullable T extracted, JsonValueWriter valueWriter) {
			WritableJson writableJson = getWritableJsonToWrite(extracted, valueWriter);
			return (writableJson != null) ? WritableJson.of(writableJson) : extracted;
		}

		private @Nullable WritableJson getWritableJsonToWrite(@Nullable T extracted, JsonValueWriter valueWriter) {
			BiConsumer<T, BiConsumer<?, ?>> pairs = this.pairs;
			if (pairs != null) {
				return (out) -> valueWriter.writePairs((outPairs) -> pairs.accept(extracted, outPairs));
			}
			Members<T> members = this.members;
			if (members != null) {
				return (out) -> members.write(extracted, valueWriter);
			}
			return null;
		}

		/**
		 * 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 <T> the member type
		 */
		@FunctionalInterface
		interface ValueExtractor<T extends @Nullable Object> {

			/**
			 * 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}
			 */
			@Nullable T extract(@Nullable Object instance);

			/**
			 * Only extract when the given predicate matches.
			 * @param predicate the predicate to test
			 * @return a new {@link ValueExtractor}
			 */
			default ValueExtractor<T> when(Predicate<? super @Nullable T> predicate) {
				return (instance) -> test(extract(instance), predicate);
			}

			@SuppressWarnings("unchecked")
			private @Nullable T test(@Nullable T extracted, Predicate<? super @Nullable T> predicate) {
				return (!skip(extracted) && predicate.test(extracted)) ? extracted : (T) SKIP;
			}

			/**
			 * Adapt the extracted value.
			 * @param <R> the result type
			 * @param extractor the extractor to use
			 * @return a new {@link ValueExtractor}
			 */
			default <R> ValueExtractor<R> as(Extractor<T, R> extractor) {
				return (instance) -> apply(extract(instance), extractor);
			}

			@SuppressWarnings("unchecked")
			private <R> @Nullable R apply(@Nullable T value, Extractor<T, R> extractor) {
				if (skip(value)) {
					return (R) SKIP;
				}
				return (value != null) ? extractor.extract(value) : null;
			}

			/**
			 * Create a new {@link ValueExtractor} based on the given {@link Function}.
			 * @param <S> the source type
			 * @param <T> the extracted type
			 * @param extractor the extractor to use
			 * @return a new {@link ValueExtractor} instance
			 */
			@SuppressWarnings("unchecked")
			static <S, T> ValueExtractor<T> of(Extractor<S, T> extractor) {
				return (instance) -> {
					if (instance == null) {
						return null;
					}
					return (skip(instance)) ? (T) SKIP : extractor.extract((S) instance);
				};
			}

			/**
			 * Return if the extracted value should be skipped.
			 * @param <T> the value type
			 * @param extracted the value to test
			 * @return if the value is to be skipped
			 */
			static <T> boolean skip(@Nullable T extracted) {
				return extracted == SKIP;
			}

		}

	}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free