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 android.app.appsearch.util; 18 19 import android.app.appsearch.annotation.CanIgnoreReturnValue; 20 21 import org.jspecify.annotations.NonNull; 22 23 /** 24 * Utility for building indented strings. 25 * 26 * <p>This is a wrapper for {@link StringBuilder} for appending strings with indentation. The 27 * indentation level can be increased by calling {@link #increaseIndentLevel()} and decreased by 28 * calling {@link #decreaseIndentLevel()}. 29 * 30 * <p>Indentation is applied after each newline character for the given indent level. 31 * 32 * @hide 33 */ 34 public class IndentingStringBuilder { 35 private final StringBuilder mStringBuilder = new StringBuilder(); 36 37 // Indicates whether next non-newline character should have an indent applied before it. 38 private boolean mIndentNext = false; 39 private int mIndentLevel = 0; 40 41 /** Increases the indent level by one for appended strings. */ 42 @CanIgnoreReturnValue increaseIndentLevel()43 public @NonNull IndentingStringBuilder increaseIndentLevel() { 44 mIndentLevel++; 45 return this; 46 } 47 48 /** Decreases the indent level by one for appended strings. */ 49 @CanIgnoreReturnValue decreaseIndentLevel()50 public @NonNull IndentingStringBuilder decreaseIndentLevel() throws IllegalStateException { 51 if (mIndentLevel == 0) { 52 throw new IllegalStateException("Cannot set indent level below 0."); 53 } 54 mIndentLevel--; 55 return this; 56 } 57 58 /** 59 * Appends provided {@code String} at the current indentation level. 60 * 61 * <p>Indentation is applied after each newline character. 62 */ 63 @CanIgnoreReturnValue append(@onNull String str)64 public @NonNull IndentingStringBuilder append(@NonNull String str) { 65 applyIndentToString(str); 66 return this; 67 } 68 69 /** 70 * Appends provided {@code Object}, represented as a {@code String}, at the current indentation 71 * level. 72 * 73 * <p>Indentation is applied after each newline character. 74 */ 75 @CanIgnoreReturnValue append(@onNull Object obj)76 public @NonNull IndentingStringBuilder append(@NonNull Object obj) { 77 applyIndentToString(obj.toString()); 78 return this; 79 } 80 81 @Override toString()82 public @NonNull String toString() { 83 return mStringBuilder.toString(); 84 } 85 86 /** Adds indent string to the {@link StringBuilder} instance for current indent level. */ applyIndent()87 private void applyIndent() { 88 for (int i = 0; i < mIndentLevel; i++) { 89 mStringBuilder.append(" "); 90 } 91 } 92 93 /** 94 * Applies indent, for current indent level, after each newline character. 95 * 96 * <p>Consecutive newline characters are not indented. 97 */ applyIndentToString(@onNull String str)98 private void applyIndentToString(@NonNull String str) { 99 int index = str.indexOf("\n"); 100 if (index == 0) { 101 // String begins with new line character: append newline and slide past newline. 102 mStringBuilder.append("\n"); 103 mIndentNext = true; 104 if (str.length() > 1) { 105 applyIndentToString(str.substring(index + 1)); 106 } 107 } else if (index >= 1) { 108 // String contains new line character: divide string between newline, append new line, 109 // and recurse on each string. 110 String beforeIndentString = str.substring(0, index); 111 applyIndentToString(beforeIndentString); 112 mStringBuilder.append("\n"); 113 mIndentNext = true; 114 if (str.length() > index + 1) { 115 String afterIndentString = str.substring(index + 1); 116 applyIndentToString(afterIndentString); 117 } 118 } else { 119 // String does not contain newline character: append string. 120 if (mIndentNext) { 121 applyIndent(); 122 mIndentNext = false; 123 } 124 mStringBuilder.append(str); 125 } 126 } 127 } 128