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
029public abstract class Style extends Chunk implements Serializable {
030
031  public static final Style reset = new Style() {
032
033    @Override
034    public Style merge(Style s) throws NullPointerException {
035      if (s == null) {
036        throw new NullPointerException();
037      }
038      return s;
039    }
040
041    @Override
042    public String toString() {
043      return "Style.Reset[]";
044    }
045
046    @Override
047    public void writeAnsiTo(Appendable appendable) throws IOException {
048      appendable.append("\033[0m");
049    }
050  };
051
052  public static final class Composite extends Style {
053
054    /** . */
055    protected final Boolean bold;
056
057    /** . */
058    protected final Boolean underline;
059
060    /** . */
061    protected final Boolean blink;
062
063    /** . */
064    protected final Color foreground;
065
066    /** . */
067    protected final Color background;
068
069    private Composite(Boolean bold, Boolean underline, Boolean blink, Color foreground, Color background) {
070      this.bold = bold;
071      this.underline = underline;
072      this.blink = blink;
073      this.foreground = foreground;
074      this.background = background;
075    }
076
077    public Composite fg(Color color) {
078      return foreground(color);
079    }
080
081    public Composite foreground(Color color) {
082      return style(bold, underline, blink, color, background);
083    }
084
085    public Composite bg(Color value) {
086      return background(value);
087    }
088
089    public Composite background(Color value) {
090      return style(bold, underline, blink, foreground, value);
091    }
092
093    public Composite bold() {
094      return bold(true);
095    }
096
097    public Composite underline() {
098      return underline(true);
099    }
100
101    public Composite blink() {
102      return blink(true);
103    }
104
105    public Composite bold(Boolean value) {
106      return style(value, underline, blink, foreground, background);
107    }
108
109    public Composite underline(Boolean value) {
110      return style(bold, value, blink, foreground, background);
111    }
112
113    public Composite blink(Boolean value) {
114      return style(bold, underline, value, foreground, background);
115    }
116
117    public Composite decoration(Decoration decoration) {
118      if (decoration != null) {
119        switch (decoration) {
120          case bold:
121            return bold(true);
122          case bold_off:
123            return bold(false);
124          case underline:
125            return underline(true);
126          case underline_off:
127            return underline(false);
128          case blink:
129            return blink(true);
130          case blink_off:
131            return blink(false);
132        }
133      }
134      return this;
135    }
136
137    public Boolean getBold() {
138      return bold;
139    }
140
141    public Boolean getUnderline() {
142      return underline;
143    }
144
145    public Boolean getBlink() {
146      return blink;
147    }
148
149    public Color getForeground() {
150      return foreground;
151    }
152
153    public Color getBackground() {
154      return background;
155    }
156
157    public Style merge(Style s) throws NullPointerException {
158      if (s == null) {
159        throw new NullPointerException();
160      }
161      if (s == reset) {
162        return reset;
163      } else {
164        Style.Composite that = (Composite)s;
165        Boolean bold = Utils.notNull(that.getBold(), getBold());
166        Boolean underline = Utils.notNull(that.getUnderline(), getUnderline());
167        Boolean blink = Utils.notNull(that.getBlink(), getBlink());
168        Color foreground = Utils.notNull(that.getForeground(), getForeground());
169        Color background = Utils.notNull(that.getBackground(), getBackground());
170        return style(bold, underline, blink, foreground, background);
171      }
172    }
173
174    @Override
175    public String toString() {
176      return "Style.Composite[bold=" + bold + ",underline=" + underline + ",blink=" + blink +
177          ",background=" + background + ",foreground=" + foreground + "]";
178    }
179
180    private static boolean decoration(
181        Appendable appendable,
182        String on,
183        String off,
184        Boolean value,
185        boolean append) throws IOException {
186      if (value != null) {
187        if (append) {
188          appendable.append(';');
189        } else {
190          appendable.append("\033[");
191        }
192        if (value) {
193          appendable.append(on);
194        } else {
195          appendable.append(off);
196        }
197        return true;
198      }
199      return false;
200    }
201
202    private static boolean color(
203        Appendable appendable,
204        Color color,
205        char base,
206        boolean append) throws IOException {
207      if (color != null) {
208        if (append) {
209          appendable.append(';');
210        } else {
211          appendable.append("\033[");
212        }
213        appendable.append(base);
214        appendable.append(color.c);
215        return true;
216      }
217      return false;
218    }
219
220    @Override
221    public void writeAnsiTo(Appendable appendable) throws IOException {
222      boolean appended = decoration(appendable, Decoration.bold.code, Decoration.bold_off.code, bold, false);
223      appended |= decoration(appendable, Decoration.underline.code, Decoration.underline_off.code, underline, appended);
224      appended |= decoration(appendable, Decoration.blink.code, Decoration.blink_off.code, blink, appended);
225      appended |= color(appendable, foreground, '3', appended);
226      appended |= color(appendable, background, '4', appended);
227      if (appended) {
228        appendable.append("m");
229      }
230    }
231  }
232
233  /** . */
234  private static final Boolean[] BOOLEANS = {true,false,null};
235
236  /** . */
237  private static final Color[] COLORS = Arrays.copyOf(Color.values(), Color.values().length + 1);
238
239  /** [bold][underline][blink][foreground][background]. */
240  private static final Composite[][][][][] ALL;
241
242  static {
243    ALL = new Composite[BOOLEANS.length][][][][];
244    for (int bold = 0;bold < BOOLEANS.length;bold++) {
245      ALL[bold] = new Composite[BOOLEANS.length][][][];
246      for (int underline = 0;underline < BOOLEANS.length;underline++) {
247        ALL[bold][underline] = new Composite[BOOLEANS.length][][];
248        for (int blink = 0;blink < BOOLEANS.length;blink++) {
249          ALL[bold][underline][blink] = new Composite[COLORS.length][];
250          for (int foreground = 0;foreground < COLORS.length;foreground++) {
251            ALL[bold][underline][blink][foreground] = new Composite[COLORS.length];
252            for (int background = 0;background < COLORS.length;background++) {
253              ALL[bold][underline][blink][foreground][background] = new Composite(
254                  BOOLEANS[bold],
255                  BOOLEANS[underline],
256                  BOOLEANS[blink],
257                  COLORS[foreground],
258                  COLORS[background]);
259            }
260          }
261        }
262      }
263    }
264  }
265
266  public static Composite style(Color foreground) {
267    return style(null, foreground, null);
268  }
269
270  public static Composite style(Color foreground, Color background) {
271    return style(null, foreground, background);
272  }
273
274  public static Composite style(Decoration decoration, Color foreground, Color background) {
275    Boolean bold = null;
276    Boolean underline = null;
277    Boolean blink = null;
278    if (decoration != null) {
279      switch (decoration) {
280        case bold:
281          bold = true;
282          break;
283        case bold_off:
284          bold = false;
285          break;
286        case underline:
287          underline = true;
288          break;
289        case underline_off:
290          underline = false;
291          break;
292        case blink:
293          blink = true;
294          break;
295        case blink_off:
296          blink = false;
297          break;
298      }
299    }
300    return style(bold, underline, blink, foreground, background);
301  }
302
303  public static Composite style(Boolean bold, Boolean underline, Boolean blink, Color foreground, Color background) {
304    int bo = bold != null ? bold ? 0 : 1: 2;
305    int un = underline != null ? underline ? 0 : 1: 2;
306    int bl = blink != null ? blink ? 0 : 1: 2;
307    int fg = foreground != null ? foreground.ordinal() : COLORS.length - 1;
308    int bg = background != null ? background.ordinal() : COLORS.length - 1;
309    return ALL[bo][un][bl][fg][bg];
310  }
311
312  public static Composite style() {
313    return style(null, null, null);
314  }
315
316  public static Composite style(Decoration decoration) {
317    return style(decoration, null, null);
318  }
319
320  public static Composite style(Decoration decoration, Color foreground) {
321    return style(decoration, foreground, null);
322  }
323
324  public abstract Style merge(Style s) throws NullPointerException;
325
326  public CharSequence toAnsiSequence() {
327    StringBuilder sb = new StringBuilder();
328    try {
329      writeAnsiTo(sb);
330    }
331    catch (IOException e) {
332      // Should not happen
333      throw new UndeclaredThrowableException(e);
334    }
335    return sb.toString();
336  }
337
338  public abstract void writeAnsiTo(Appendable appendable) throws IOException;
339
340  @Override
341  public abstract String toString();
342}