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 
 26 package jdk.code.tools.renderer;
 27 
 28 import java.io.IOException;
 29 import java.io.UncheckedIOException;
 30 import java.io.Writer;
 31 import java.util.Map;
 32 
 33 public class TextRenderer<T extends TextRenderer<T>> {
 34 
 35     public interface NestedRendererSAM<T> {
 36         T build(T nb);
 37     }
 38 
 39     public enum TokenType {
 40         WHITESPACE, OP, TYPE, CONTROL, LITERAL, COMMENT, KEYWORD, IDENTIFIER, LABELTARGET, NONE
 41     }
 42 
 43     public static class TokenColorMap {
 44 
 45 
 46         private final Map<TokenType, TerminalColors.Colorizer> map;
 47 
 48         public TokenColorMap(Map<TokenType, TerminalColors.Colorizer> map) {
 49             this.map = map;
 50         }
 51 
 52         public TokenColorMap() {
 53             this(Map.of(
 54                     TokenType.NONE, TerminalColors.Color.WHITE,
 55                     TokenType.IDENTIFIER, TerminalColors.Color.YELLOW,
 56                     TokenType.LABELTARGET, TerminalColors.Color.BLUE,
 57                     TokenType.TYPE, TerminalColors.Color.WHITE,
 58                     TokenType.COMMENT, TerminalColors.Color.GREEN,
 59                     TokenType.KEYWORD, TerminalColors.Color.ORANGE,
 60                     TokenType.CONTROL, TerminalColors.Color.GREY,
 61                     TokenType.LITERAL, TerminalColors.Color.GREEN,
 62                     TokenType.OP, TerminalColors.Color.WHITE,
 63                     TokenType.WHITESPACE, TerminalColors.Color.WHITE));
 64         }
 65 
 66         public String colorize(TokenType tokenType, String string) {
 67             if (map.containsKey(tokenType)) {
 68                 return map.get(tokenType).colorize(string);
 69             } else {
 70                 return string;
 71             }
 72         }
 73     }
 74 
 75     public static class State {
 76         public Writer writer;
 77         public int indent;
 78         public TokenColorMap tokenColorMap = null;
 79         public boolean isFirst = false;
 80 
 81         public boolean newLined = false;
 82 
 83         State() {
 84             this.writer = null;
 85             this.indent = 0;
 86             this.tokenColorMap = null;
 87             this.isFirst = false;
 88             this.newLined = true;
 89         }
 90     }
 91 
 92     public T writer(Writer writer) {
 93         this.state.writer = writer;
 94         return self();
 95     }
 96 
 97     public T colorize(TokenColorMap tokenColorMap) {
 98         this.state.tokenColorMap = tokenColorMap;
 99         return self();
100     }
101 
102     public T colorize() {
103         return colorize(new TokenColorMap());
104     }
105 
106     public State state;
107 
108     protected TextRenderer() {
109         this.state = new State();
110     }
111 
112     protected TextRenderer(TextRenderer<?> renderer) {
113         this.state = renderer.state;
114     }
115 
116     @SuppressWarnings("unchecked")
117     public T self() {
118         return (T) this;
119     }
120 
121 
122     public boolean first() {
123         var was = state.isFirst;
124         state.isFirst = false;
125         return was;
126     }
127 
128     public T startList() {
129         state.isFirst = true;
130         return self();
131     }
132 
133     public T append(String text) {
134         try {
135             // While we do expect appends text to be simple tokens. We can handle newlines.
136             var lines = text.split("\n");
137             for (int i = 0; i < lines.length - 1; i++) {
138                 state.writer.append(" ".repeat(state.indent) + lines[i] + "\n");
139                 state.newLined = true;
140             }
141             if (state.newLined) {
142                 state.writer.append(" ".repeat(state.indent));
143             }
144             state.writer.append(lines[lines.length - 1]);
145             state.newLined = false;
146             return self();
147         } catch (IOException ioe) {
148             throw new UncheckedIOException(ioe);
149         }
150     }
151 
152     public T identifier(String ident) {
153         if (state.tokenColorMap != null) {
154             return append(state.tokenColorMap.colorize(TokenType.IDENTIFIER, ident));
155         } else {
156             return append(ident);
157         }
158     }
159 
160     public T type(String typeName) {
161         if (state.tokenColorMap != null) {
162             return append(state.tokenColorMap.colorize(TokenType.TYPE, typeName));
163         } else {
164             return append(typeName);
165         }
166     }
167 
168     public T keyword(String keyword) {
169         if (state.tokenColorMap != null) {
170             return append(state.tokenColorMap.colorize(TokenType.KEYWORD, keyword));
171         } else {
172             return append(keyword);
173         }
174     }
175 
176     public T literal(String literal) {
177         if (state.tokenColorMap != null) {
178             return append(state.tokenColorMap.colorize(TokenType.LITERAL, literal));
179         } else {
180             return append(literal);
181         }
182     }
183 
184     public T ws(String whitespace) {
185         if (state.tokenColorMap != null) {
186             return append(state.tokenColorMap.colorize(TokenType.WHITESPACE, whitespace));
187         } else {
188             return append(whitespace);
189         }
190     }
191 
192     public T op(String op) {
193         if (state.tokenColorMap != null) {
194             return append(state.tokenColorMap.colorize(TokenType.OP, op));
195         } else {
196             return append(op);
197         }
198     }
199 
200     public T control(String control) {
201         if (state.tokenColorMap != null) {
202             return append(state.tokenColorMap.colorize(TokenType.CONTROL, control));
203         } else {
204             return append(control);
205         }
206     }
207 
208     public T labelTarget(String labelTarget) {
209         if (state.tokenColorMap != null) {
210             return append(state.tokenColorMap.colorize(TokenType.LABELTARGET, labelTarget));
211         } else {
212             return append(labelTarget);
213         }
214     }
215 
216     public T comment(String comment) {
217         if (state.tokenColorMap != null) {
218             return append(state.tokenColorMap.colorize(TokenType.COMMENT, comment));
219         } else {
220             return append(comment);
221         }
222     }
223 
224     public T strLiteral(String s) {
225         return oquot().literal(s).cquot();
226     }
227 
228     public T oquot() {
229         return literal("\"");
230     }
231 
232     public T cquot() {
233         return literal("\"");
234     }
235 
236     public T decLiteral(int i) {
237         return literal(String.format("%d", i));
238     }
239 
240     public T hexLiteral(int i) {
241         return literal(String.format("%x", i));
242     }
243 
244     public T in() {
245         state.indent += 2;
246         return self();
247     }
248 
249     public T out() {
250         state.indent -= 2;
251         return self();
252     }
253 
254     public T flush() {
255         try {
256             state.writer.flush();
257             return self();
258         } catch (IOException ioe) {
259             throw new RuntimeException(ioe);
260         }
261     }
262 
263     public T nl() {
264         try {
265             // note we go directly to the underlying writer!
266             state.writer.append("\n");
267             state.newLined = true;
268             return flush().self();
269         } catch (IOException ioe) {
270             throw new RuntimeException(ioe);
271         }
272     }
273 
274     public T space() {
275         return ws(" ");
276     }
277 
278     public T nest(NestedRendererSAM<T> nb) {
279         return nb.build(self());
280     }
281 
282     public T open(String op) {
283         control(op);
284         return self();
285     }
286 
287     public T close(String op) {
288         control(op);
289         return self();
290     }
291 }
292