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