1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package hat.codebuilders;
 26 
 27 /**
 28  * The base abstract class for a slew of fluent style builders.
 29  * <p>
 30  * At this level the builder just deals with basic appending, indenting, newline handling
 31  *
 32  * <pre>
 33  *     TextBuilder textBuilder = ....;
 34  *     textBuilder
 35  *        .nl()
 36  *        .in()
 37  *        .append("hello)
 38  *        .space()
 39  *        .append("world")
 40  *        .out();
 41  *
 42  * </pre>
 43  *
 44  * @author Gary Frost
 45  */
 46 
 47 
 48 public abstract class TextBuilder<T extends TextBuilder<T>> {
 49 
 50 
 51     public static class State {
 52         private final StringBuilder stringBuilder = new StringBuilder();
 53         final public boolean indenting = true;
 54         private int indent = 0;
 55         private final String indentation = "    ";
 56         private boolean newLined = true;
 57         public void indentation() {
 58             for (int i = 0; i < indent; i++) {
 59                 stringBuilder.append(indentation);
 60             }
 61         }
 62 
 63         public void indentIfNeededAndAppend(String text) {
 64             if (indenting && newLined) {
 65                 indentation();
 66             }
 67             newLined = false;
 68             stringBuilder.append(text);
 69         }
 70 
 71         public void preformatted(String text){
 72             stringBuilder.append(text);
 73         }
 74 
 75         public void incIndent() {
 76             indent++;
 77         }
 78         public void decIndent() {
 79             indent--;
 80         }
 81 
 82         public void nl() {
 83             newLined = true;
 84         }
 85 
 86         @Override
 87         public String toString(){
 88             return stringBuilder.toString();
 89         }
 90     }
 91 
 92     private State state = new State();
 93 
 94     public T clear() {
 95         state = new State();
 96         return self();
 97     }
 98 
 99     public String getText() {
100         return toString();
101     }
102 
103 
104 
105     @SuppressWarnings("unchecked")
106     public T self() {
107         return (T) this;
108     }
109 
110     private static String escape(char ch) {
111         return switch (ch) {
112             case '\b' -> "\\b";
113             case '\f' -> "\\f";
114             case '\n' -> "\\n";
115             case '\r' -> "\\r";
116             case '\t' -> "\\t";
117             case '\'' -> "\\'";
118             case '\"' -> "\\\"";
119             case '\\' -> "\\\\";
120             default -> (ch >= ' ' && ch <= '~') ? String.valueOf(ch) : String.format("\\u%04x", (int) ch);
121         };
122     }
123 
124     public T escaped(String text) {
125         StringBuilder buf = new StringBuilder();
126         for (int i = 0; i < text.length(); i++) {
127             buf.append(escape(text.charAt(i)));
128         }
129         return emitText(text);
130     }
131 
132      T emitText(String text) {
133         state.indentIfNeededAndAppend(text);
134         return self();
135     }
136 
137     public T preformatted(String text){
138         state.preformatted(text);
139         return self();
140     }
141 
142 
143     public T identifier(String text, int  padWidth) {
144         return emitText(text).emitText(" ".repeat(padWidth-text.length()));
145     }
146 
147 
148     public T intValue(int i) {
149         return emitText(Integer.toString(i));
150     }
151     public T intHexValue(int i) {
152         return emitText("0x").emitText(Integer.toHexString(i));
153     }
154 
155     public final T literal(int i) {
156         return emitText(Integer.toString(i));
157     }
158 
159     public final T literal(long i) {
160         return emitText(Long.toString(i));
161     }
162 
163     public T in() {
164         state.incIndent();
165         return self();
166     }
167 
168     public T out() {
169         state.decIndent();
170         return self();
171     }
172     public T nl() {
173         emitText("\n");
174         state.nl();
175         return self();
176     }
177     @Override
178     public final String toString() {
179         return state.toString();
180     }
181 
182 
183 
184 
185 }