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
020package org.crsh.text;
021
022import org.crsh.util.Utils;
023
024import java.io.IOException;
025import java.io.Serializable;
026import java.lang.reflect.UndeclaredThrowableException;
027import 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 */
042public abstract class Style implements Serializable {
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}