001    package org.crsh.text;
002    
003    import org.crsh.util.Safe;
004    
005    import java.io.IOException;
006    import java.io.PrintWriter;
007    import java.io.Serializable;
008    import java.util.Iterator;
009    import java.util.LinkedList;
010    import java.util.NoSuchElementException;
011    
012    /**
013     * @author <a href="mailto:alain.defrance@exoplatform.com">Alain Defrance</a>
014     */
015    public class CharReader implements Iterable<Object>, Serializable {
016    
017      private static class Chunk implements Serializable {
018    
019        /** . */
020        final Style style;
021    
022        /** . */
023        final StringBuilder buffer;
024    
025        private Chunk(Style style) {
026          this.style = style;
027          this.buffer = new StringBuilder();
028        }
029      }
030    
031      /** . */
032      private final LinkedList<Chunk> chunks;
033    
034      /** . */
035      private Style style;
036    
037      public CharReader() {
038        this.chunks = new LinkedList<Chunk>();
039        this.style = null;
040      }
041    
042      public CharReader(CharSequence s) {
043        this();
044    
045        //
046        append(s);
047      }
048    
049      public Iterator<Object> iterator() {
050        return new Iterator<Object>() {
051          Iterator<Chunk> i = chunks.iterator();
052          Style nextStyle;
053          StringBuilder nextBuffer;
054          public boolean hasNext() {
055            if (nextStyle != null || nextBuffer != null) {
056              return true;
057            } else if (i != null) {
058              if (i.hasNext()) {
059                Chunk next = i.next();
060                nextStyle = next.style;
061                nextBuffer = next.buffer;
062                return true;
063              } else {
064                i = null;
065                if (style != null) {
066                  Style last = chunks.size() > 0 ? chunks.peekLast().style : null;
067                  if (style.equals(last)) {
068                    return false;
069                  } else {
070                    nextStyle = style;
071                    return true;
072                  }
073                } else {
074                  return false;
075                }
076              }
077            } else {
078              return false;
079            }
080          }
081          public Object next() {
082            if (hasNext()) {
083              Object next;
084              if (nextStyle != null) {
085                next = nextStyle;
086                nextStyle = null;
087              } else {
088                next = nextBuffer;
089                nextBuffer = null;
090              }
091              return next;
092            } else {
093              throw new NoSuchElementException();
094            }
095          }
096          public void remove() {
097            throw new UnsupportedOperationException();
098          }
099        };
100      }
101    
102      public void writeAnsiTo(PrintWriter writer) {
103        try {
104          writeAnsiTo((Appendable)writer);
105        }
106        catch (IOException ignore) {
107        }
108      }
109    
110      public void writeAnsiTo(Appendable appendable) throws IOException {
111        Iterator<Object> iterator = iterator();
112        while (iterator.hasNext()) {
113          Object o = iterator.next();
114          if (o instanceof Style) {
115            try {
116              ((Style)o).writeAnsiTo(appendable);
117            }
118            catch (IOException ignore) {
119            }
120          } else if (o instanceof CharSequence) {
121            appendable.append((CharSequence)o);
122          } else {
123            appendable.append(o.toString());
124          }
125        }
126      }
127    
128      public CharReader append(Object... data) throws NullPointerException {
129        for (Object o : data) {
130          append(o);
131        }
132        return this;
133      }
134    
135      public CharReader append(Object data) throws NullPointerException {
136        if (data == null) {
137          throw new NullPointerException("No null accepted");
138        }
139        if (data instanceof CharReader) {
140          CharReader reader = (CharReader)data;
141          for (Chunk chunk : reader.chunks) {
142            if (chunk.style != null) {
143              append(chunk.style);
144            }
145            append(chunk.buffer);
146          }
147          style = reader.style;
148        } else {
149          if (data instanceof Style) {
150            if (data == Style.reset) {
151              style = Style.reset;
152            } else {
153              if (style != null) {
154                style = style.merge((Style)data);
155              } else {
156                style = (Style)data;
157              }
158            }
159          } else {
160            CharSequence s;
161            if (data instanceof CharSequence) {
162              s = (CharSequence)data;
163            } else {
164              s = data.toString();
165            }
166            if (s.length() > 0) {
167              Chunk chunk;
168              if (chunks.size() > 0) {
169                Chunk last = chunks.peekLast();
170                if (Safe.equals(last.style, style)) {
171                  chunk = last;
172                } else {
173                  chunks.addLast(chunk = new Chunk(style));
174                }
175              } else {
176                chunks.addLast(chunk = new Chunk(style));
177              }
178              chunk.buffer.append(s);
179            }
180          }
181        }
182        return this;
183      }
184    
185      public boolean contains(Object o) {
186        return toString().contains(o.toString());
187      }
188    
189      public boolean isEmpty() {
190        return chunks.isEmpty();
191      }
192    
193      public void clear() {
194        chunks.clear();
195      }
196    
197      @Override
198      public int hashCode() {
199        return toString().hashCode();
200      }
201    
202      @Override
203      public boolean equals(Object obj) {
204        if (obj == this) {
205          return true;
206        }
207        if (obj instanceof CharReader) {
208          CharReader that = (CharReader)obj;
209          return toString().equals(that.toString());
210        }
211        return false;
212      }
213    
214      @Override
215      public String toString() {
216        StringBuilder sb = new StringBuilder();
217        for (Chunk chunk : chunks) {
218          sb.append(chunk.buffer);
219        }
220        return sb.toString();
221      }
222    }