001 /*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020 package org.crsh.text;
021
022 import org.crsh.util.Utils;
023
024 import java.io.IOException;
025 import java.io.Serializable;
026 import java.lang.reflect.UndeclaredThrowableException;
027 import java.util.Arrays;
028
029 /**
030 * A control for the text stylistric attributes:
031 * <u>
032 * <li>background color</li>
033 * <li>foreground color</li>
034 * <li>underline</li>
035 * <li>bold</li>
036 * <li>blink</li>
037 * </u>
038 *
039 * A style is either a composite style or the {@link #reset} style. Styles can be composed together to form a new
040 * style <code>style.merge(other)</code>.
041 */
042 public abstract class Style implements Chunk {
043
044 public static final Style reset = new Style() {
045
046 @Override
047 public Style merge(Style s) throws NullPointerException {
048 if (s == null) {
049 throw new NullPointerException();
050 }
051 return s;
052 }
053
054 @Override
055 public String toString() {
056 return "Style.Reset[]";
057 }
058
059 @Override
060 public void writeAnsiTo(Appendable appendable) throws IOException {
061 appendable.append("\033[0m");
062 }
063 };
064
065 public static final class Composite extends Style {
066
067 /** . */
068 protected final Boolean bold;
069
070 /** . */
071 protected final Boolean underline;
072
073 /** . */
074 protected final Boolean blink;
075
076 /** . */
077 protected final Color foreground;
078
079 /** . */
080 protected final Color background;
081
082 private Composite(Boolean bold, Boolean underline, Boolean blink, Color foreground, Color background) {
083 this.bold = bold;
084 this.underline = underline;
085 this.blink = blink;
086 this.foreground = foreground;
087 this.background = background;
088 }
089
090 public Composite fg(Color color) {
091 return foreground(color);
092 }
093
094 public Composite foreground(Color color) {
095 return style(bold, underline, blink, color, background);
096 }
097
098 public Composite bg(Color value) {
099 return background(value);
100 }
101
102 public Composite background(Color value) {
103 return style(bold, underline, blink, foreground, value);
104 }
105
106 public Composite bold() {
107 return bold(true);
108 }
109
110 public Composite underline() {
111 return underline(true);
112 }
113
114 public Composite blink() {
115 return blink(true);
116 }
117
118 public Composite bold(Boolean value) {
119 return style(value, underline, blink, foreground, background);
120 }
121
122 public Composite underline(Boolean value) {
123 return style(bold, value, blink, foreground, background);
124 }
125
126 public Composite blink(Boolean value) {
127 return style(bold, underline, value, foreground, background);
128 }
129
130 public Composite decoration(Decoration decoration) {
131 if (decoration != null) {
132 switch (decoration) {
133 case bold:
134 return bold(true);
135 case bold_off:
136 return bold(false);
137 case underline:
138 return underline(true);
139 case underline_off:
140 return underline(false);
141 case blink:
142 return blink(true);
143 case blink_off:
144 return blink(false);
145 }
146 }
147 return this;
148 }
149
150 public Boolean getBold() {
151 return bold;
152 }
153
154 public Boolean getUnderline() {
155 return underline;
156 }
157
158 public Boolean getBlink() {
159 return blink;
160 }
161
162 public Color getForeground() {
163 return foreground;
164 }
165
166 public Color getBackground() {
167 return background;
168 }
169
170 public Style merge(Style s) throws NullPointerException {
171 if (s == null) {
172 throw new NullPointerException();
173 }
174 if (s == reset) {
175 return reset;
176 } else {
177 Style.Composite that = (Composite)s;
178 Boolean bold = Utils.notNull(that.getBold(), getBold());
179 Boolean underline = Utils.notNull(that.getUnderline(), getUnderline());
180 Boolean blink = Utils.notNull(that.getBlink(), getBlink());
181 Color foreground = Utils.notNull(that.getForeground(), getForeground());
182 Color background = Utils.notNull(that.getBackground(), getBackground());
183 return style(bold, underline, blink, foreground, background);
184 }
185 }
186
187 @Override
188 public String toString() {
189 return "Style.Composite[bold=" + bold + ",underline=" + underline + ",blink=" + blink +
190 ",background=" + background + ",foreground=" + foreground + "]";
191 }
192
193 private static boolean decoration(
194 Appendable appendable,
195 String on,
196 String off,
197 Boolean value,
198 boolean append) throws IOException {
199 if (value != null) {
200 if (append) {
201 appendable.append(';');
202 } else {
203 appendable.append("\033[");
204 }
205 if (value) {
206 appendable.append(on);
207 } else {
208 appendable.append(off);
209 }
210 return true;
211 }
212 return false;
213 }
214
215 private static boolean color(
216 Appendable appendable,
217 Color color,
218 char base,
219 boolean append) throws IOException {
220 if (color != null) {
221 if (append) {
222 appendable.append(';');
223 } else {
224 appendable.append("\033[");
225 }
226 appendable.append(base);
227 appendable.append(color.c);
228 return true;
229 }
230 return false;
231 }
232
233 @Override
234 public void writeAnsiTo(Appendable appendable) throws IOException {
235 boolean appended = decoration(appendable, Decoration.bold.code, Decoration.bold_off.code, bold, false);
236 appended |= decoration(appendable, Decoration.underline.code, Decoration.underline_off.code, underline, appended);
237 appended |= decoration(appendable, Decoration.blink.code, Decoration.blink_off.code, blink, appended);
238 appended |= color(appendable, foreground, '3', appended);
239 appended |= color(appendable, background, '4', appended);
240 if (appended) {
241 appendable.append("m");
242 }
243 }
244 }
245
246 /** . */
247 private static final Boolean[] BOOLEANS = {true,false,null};
248
249 /** . */
250 private static final Color[] COLORS = Arrays.copyOf(Color.values(), Color.values().length + 1);
251
252 /** [bold][underline][blink][foreground][background]. */
253 private static final Composite[][][][][] ALL;
254
255 static {
256 ALL = new Composite[BOOLEANS.length][][][][];
257 for (int bold = 0;bold < BOOLEANS.length;bold++) {
258 ALL[bold] = new Composite[BOOLEANS.length][][][];
259 for (int underline = 0;underline < BOOLEANS.length;underline++) {
260 ALL[bold][underline] = new Composite[BOOLEANS.length][][];
261 for (int blink = 0;blink < BOOLEANS.length;blink++) {
262 ALL[bold][underline][blink] = new Composite[COLORS.length][];
263 for (int foreground = 0;foreground < COLORS.length;foreground++) {
264 ALL[bold][underline][blink][foreground] = new Composite[COLORS.length];
265 for (int background = 0;background < COLORS.length;background++) {
266 ALL[bold][underline][blink][foreground][background] = new Composite(
267 BOOLEANS[bold],
268 BOOLEANS[underline],
269 BOOLEANS[blink],
270 COLORS[foreground],
271 COLORS[background]);
272 }
273 }
274 }
275 }
276 }
277 }
278
279 public static Composite style(Color foreground) {
280 return style(null, foreground, null);
281 }
282
283 public static Composite style(Color foreground, Color background) {
284 return style(null, foreground, background);
285 }
286
287 public static Composite style(Decoration decoration, Color foreground, Color background) {
288 Boolean bold = null;
289 Boolean underline = null;
290 Boolean blink = null;
291 if (decoration != null) {
292 switch (decoration) {
293 case bold:
294 bold = true;
295 break;
296 case bold_off:
297 bold = false;
298 break;
299 case underline:
300 underline = true;
301 break;
302 case underline_off:
303 underline = false;
304 break;
305 case blink:
306 blink = true;
307 break;
308 case blink_off:
309 blink = false;
310 break;
311 }
312 }
313 return style(bold, underline, blink, foreground, background);
314 }
315
316 public static Composite style(Boolean bold, Boolean underline, Boolean blink, Color foreground, Color background) {
317 int bo = bold != null ? bold ? 0 : 1: 2;
318 int un = underline != null ? underline ? 0 : 1: 2;
319 int bl = blink != null ? blink ? 0 : 1: 2;
320 int fg = foreground != null ? foreground.ordinal() : COLORS.length - 1;
321 int bg = background != null ? background.ordinal() : COLORS.length - 1;
322 return ALL[bo][un][bl][fg][bg];
323 }
324
325 /**
326 * Create a new blank style.
327 *
328 * @return the style
329 */
330 public static Composite style() {
331 return style(null, null, null);
332 }
333
334 public static Composite style(Decoration decoration) {
335 return style(decoration, null, null);
336 }
337
338 public static Composite style(Decoration decoration, Color foreground) {
339 return style(decoration, foreground, null);
340 }
341
342 public abstract Style merge(Style s) throws NullPointerException;
343
344 public CharSequence toAnsiSequence() {
345 StringBuilder sb = new StringBuilder();
346 try {
347 writeAnsiTo(sb);
348 }
349 catch (IOException e) {
350 // Should not happen
351 throw new UndeclaredThrowableException(e);
352 }
353 return sb.toString();
354 }
355
356 public abstract void writeAnsiTo(Appendable appendable) throws IOException;
357
358 @Override
359 public abstract String toString();
360 }