1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.appsearch.util; 18 19 import androidx.annotation.RestrictTo; 20 import androidx.appsearch.annotation.CanIgnoreReturnValue; 21 22 import org.jspecify.annotations.NonNull; 23 24 /** 25 * Utility for building indented strings. 26 * 27 * <p>This is a wrapper for {@link StringBuilder} for appending strings with indentation. 28 * The indentation level can be increased by calling {@link #increaseIndentLevel()} and decreased 29 * by calling {@link #decreaseIndentLevel()}. 30 * 31 * <p>Indentation is applied after each newline character for the given indent level. 32 * 33 * @exportToFramework:hide 34 */ 35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 36 public class IndentingStringBuilder { 37 private final StringBuilder mStringBuilder = new StringBuilder(); 38 39 // Indicates whether next non-newline character should have an indent applied before it. 40 private boolean mIndentNext = false; 41 private int mIndentLevel = 0; 42 43 /** 44 * Increases the indent level by one for appended strings. 45 */ 46 @CanIgnoreReturnValue increaseIndentLevel()47 public @NonNull IndentingStringBuilder increaseIndentLevel() { 48 mIndentLevel++; 49 return this; 50 } 51 52 /** 53 * Decreases the indent level by one for appended strings. 54 */ 55 @CanIgnoreReturnValue decreaseIndentLevel()56 public @NonNull IndentingStringBuilder decreaseIndentLevel() throws IllegalStateException { 57 if (mIndentLevel == 0) { 58 throw new IllegalStateException("Cannot set indent level below 0."); 59 } 60 mIndentLevel--; 61 return this; 62 } 63 64 /** 65 * Appends provided {@code String} at the current indentation level. 66 * 67 * <p>Indentation is applied after each newline character. 68 */ 69 @CanIgnoreReturnValue append(@onNull String str)70 public @NonNull IndentingStringBuilder append(@NonNull String str) { 71 applyIndentToString(str); 72 return this; 73 } 74 75 /** 76 * Appends provided {@code Object}, represented as a {@code String}, at the current indentation 77 * level. 78 * 79 * <p>Indentation is applied after each newline character. 80 */ 81 @CanIgnoreReturnValue append(@onNull Object obj)82 public @NonNull IndentingStringBuilder append(@NonNull Object obj) { 83 applyIndentToString(obj.toString()); 84 return this; 85 } 86 87 @Override toString()88 public @NonNull String toString() { 89 return mStringBuilder.toString(); 90 } 91 92 /** 93 * Adds indent string to the {@link StringBuilder} instance for current indent level. 94 */ applyIndent()95 private void applyIndent() { 96 for (int i = 0; i < mIndentLevel; i++) { 97 mStringBuilder.append(" "); 98 } 99 } 100 101 /** 102 * Applies indent, for current indent level, after each newline character. 103 * 104 * <p>Consecutive newline characters are not indented. 105 */ applyIndentToString(@onNull String str)106 private void applyIndentToString(@NonNull String str) { 107 int index = str.indexOf("\n"); 108 if (index == 0) { 109 // String begins with new line character: append newline and slide past newline. 110 mStringBuilder.append("\n"); 111 mIndentNext = true; 112 if (str.length() > 1) { 113 applyIndentToString(str.substring(index + 1)); 114 } 115 } else if (index >= 1) { 116 // String contains new line character: divide string between newline, append new line, 117 // and recurse on each string. 118 String beforeIndentString = str.substring(0, index); 119 applyIndentToString(beforeIndentString); 120 mStringBuilder.append("\n"); 121 mIndentNext = true; 122 if (str.length() > index + 1) { 123 String afterIndentString = str.substring(index + 1); 124 applyIndentToString(afterIndentString); 125 } 126 } else { 127 // String does not contain newline character: append string. 128 if (mIndentNext) { 129 applyIndent(); 130 mIndentNext = false; 131 } 132 mStringBuilder.append(str); 133 } 134 } 135 } 136