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 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    }