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

com.google.common.truth.extensions.proto.RecursableDiffEntity Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2017 Google, Inc.
 *
 * 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
 *
 * http://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 com.google.common.truth.extensions.proto;

/**
 * A generic entity in the {@link DiffResult} tree with queryable properties.
 *
 * 

This class is not directly extensible. Only the inner classes, {@link * RecursableDiffEntity.WithoutResultCode} and {@link RecursableDiffEntity.WithResultCode}, can be * extended. * *

A {@code RecursableDiffEntity}'s base properties (i.e., {@link #isMatched()}, {@link * #isIgnored()}) are determined differently depending on the subtype. The {@code WithoutResultCode} * subtype derives its base properties entirely from its children, while the {@code WithResultCode} * subtype derives its base properties from an enum explicitly set on the entity. The {@link * #isAnyChildMatched()} and {@link #isAnyChildIgnored()} properties are determined recursively on * both subtypes. * *

A {@code RecursableDiffEntity} may have no children. The nature and count of an entity's * children depends on the implementation - see {@link DiffResult} for concrete instances. */ abstract class RecursableDiffEntity { // Lazily-initialized return values for the recursive properties of the entity. // null = not initialized yet // // This essentially implements what @Memoized does, but @AutoValue doesn't support @Memoized on // parent classes. I think it's better to roll-our-own in the parent class to take advantage of // inheritance, than to duplicate the @Memoized methods for every subclass. private Boolean isAnyChildIgnored = null; private Boolean isAnyChildMatched = null; // Only extended by inner classes. private RecursableDiffEntity() {} /** * The children of this entity. May be empty. * *

Subclasses should {@link @Memoized} this method especially if it's expensive. */ abstract Iterable childEntities(); /** Returns whether or not the two entities matched according to the diff rules. */ abstract boolean isMatched(); /** Returns true if all sub-fields of both entities were ignored for comparison. */ abstract boolean isIgnored(); /** * Returns true if some child entity matched. * *

Caches the result for future calls. */ final boolean isAnyChildMatched() { if (isAnyChildMatched == null) { isAnyChildMatched = false; for (RecursableDiffEntity entity : childEntities()) { if ((entity.isMatched() && !entity.isContentEmpty()) || entity.isAnyChildMatched()) { isAnyChildMatched = true; break; } } } return isAnyChildMatched; } /** * Returns true if some child entity was ignored. * *

Caches the result for future calls. */ final boolean isAnyChildIgnored() { if (isAnyChildIgnored == null) { isAnyChildIgnored = false; for (RecursableDiffEntity entity : childEntities()) { if ((entity.isIgnored() && !entity.isContentEmpty()) || entity.isAnyChildIgnored()) { isAnyChildIgnored = true; break; } } } return isAnyChildIgnored; } /** * Prints the contents of this diff entity to {@code sb}. * * @param includeMatches Whether to include reports for fields which matched. * @param fieldPrefix The human-readable field path leading to this entity. Empty if this is the * root entity. * @param sb Builder to print the text to. */ abstract void printContents(boolean includeMatches, String fieldPrefix, StringBuilder sb); /** Returns true if this entity has no contents to print, with or without includeMatches. */ abstract boolean isContentEmpty(); final void printChildContents(boolean includeMatches, String fieldPrefix, StringBuilder sb) { for (RecursableDiffEntity entity : childEntities()) { entity.printContents(includeMatches, fieldPrefix, sb); } } /** * A generic entity in the {@link DiffResult} tree without a result code. * *

This entity derives its {@code isMatched()} and {@code isIgnored()} state purely from its * children. If it has no children, it is considered both matched and ignored. */ abstract static class WithoutResultCode extends RecursableDiffEntity { private Boolean isMatched = null; private Boolean isIgnored = null; @Override final boolean isMatched() { if (isMatched == null) { isMatched = true; for (RecursableDiffEntity entity : childEntities()) { if (!entity.isMatched()) { isMatched = false; break; } } } return isMatched; } @Override final boolean isIgnored() { if (isIgnored == null) { isIgnored = true; for (RecursableDiffEntity entity : childEntities()) { if (!entity.isIgnored()) { isIgnored = false; break; } } } return isIgnored; } } /** * A generic entity in the {@link DiffResult} tree with a result code. * *

The result code overrides {@code isMatched()} and {@code isIgnored()} evaluation, using the * provided enum instead of any child states. */ abstract static class WithResultCode extends RecursableDiffEntity { enum Result { /** No differences. The expected case. */ MATCHED, /** expected() didn't have this field, actual() did. */ ADDED, /** actual() didn't have this field, expected() did. */ REMOVED, /** Both messages had the field but the values don't match. */ MODIFIED, /** * The message was moved from one index to another, but strict ordering was expected. * *

This is only possible on {@link DiffResult.RepeatedField.PairResult}. */ MOVED_OUT_OF_ORDER, /** * The messages were ignored for the sake of comparison. * *

IGNORED fields should also be considered MATCHED, for the sake of pass/fail decisions. * The IGNORED information is useful for limiting diff output: i.e., if all fields in a deep * submessage-to-submessage comparison are ignored, we can print the top-level type as ignored * and omit diff lines for the rest of the fields within. */ IGNORED; static Builder builder() { return new Builder(); } /** * A helper class for computing a {@link Result}. It defaults to {@code MATCHED}, but can be * changed exactly once if called with a true {@code condition}. * *

All subsequent 'mark' calls after a successful mark are ignored. */ static final class Builder { private Result state = Result.MATCHED; private Builder() {} public void markAddedIf(boolean condition) { setIf(condition, Result.ADDED); } public void markRemovedIf(boolean condition) { setIf(condition, Result.REMOVED); } public void markModifiedIf(boolean condition) { setIf(condition, Result.MODIFIED); } public Result build() { return state; } private void setIf(boolean condition, Result newState) { if (condition && state == Result.MATCHED) { state = newState; } } } } abstract Result result(); @Override final boolean isMatched() { return result() == Result.MATCHED || result() == Result.IGNORED; } @Override final boolean isIgnored() { return result() == Result.IGNORED; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy