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 java.util.ArrayList;
023    import java.util.Iterator;
024    
025    /**
026     * Something that can be rendered within a context.
027     */
028    public abstract class Renderer {
029    
030      public static final Renderer NULL = new Renderer() {
031        @Override
032        public int getActualWidth() {
033          return 0;
034        }
035        @Override
036        public int getMinWidth() {
037          return 0;
038        }
039        @Override
040        public int getMinHeight(int width) {
041          return 0;
042        }
043        @Override
044        public int getActualHeight(int width) {
045          return 0;
046        }
047        @Override
048        public LineReader reader(int width) {
049          return new LineReader() {
050            public boolean hasLine() {
051              return false;
052            }
053            public void renderLine(RenderAppendable to) throws IllegalStateException {
054              throw new IllegalStateException();
055            }
056          };
057        }
058      };
059    
060      public static Renderer vertical(Iterable<? extends Renderer> renderers) {
061        Iterator<? extends Renderer> i = renderers.iterator();
062        if (i.hasNext()) {
063          Renderer renderer = i.next();
064          if (i.hasNext()) {
065            return new Composite(renderers);
066          } else {
067            return renderer;
068          }
069        } else {
070          return NULL;
071        }
072      }
073    
074      /**
075       * Returns the element actual width.
076       *
077       * @return the actual width
078       */
079      public abstract int getActualWidth();
080    
081      /**
082       * Returns the element minimum width.
083       *
084       * @return the minimum width
085       */
086      public abstract int getMinWidth();
087    
088      /**
089       * Return the minimum height for the specified with.
090       *
091       * @param width the width
092       * @return the actual height
093       */
094      public abstract int getMinHeight(int width);
095    
096      /**
097       * Return the actual height for the specified with.
098       *
099       * @param width the width
100       * @return the minimum height
101       */
102      public abstract int getActualHeight(int width);
103    
104      /**
105       * Create a renderer for the specified width and height or return null if the element does not provide any output
106       * for the specified dimensions. The default implementation delegates to the {@link #reader(int)} method when the
107       * <code>height</code> argument is not positive otherwise it returns null. Subclasses should override this method
108       * when they want to provide content that can adapts to the specified height.
109       *
110       * @param width the width
111       * @param height the height
112       * @return the renderer
113       */
114      public LineReader reader(int width, int height) {
115        if (height > 0) {
116          return null;
117        } else {
118          return reader(width);
119        }
120      }
121    
122      /**
123       * Create a renderer for the specified width or return null if the element does not provide any output.
124       *
125       * @param width the width
126       * @return the renderer
127       */
128      public abstract LineReader reader(int width);
129    
130      /**
131       * Renders this object to the provided output.
132       *
133       * @param out the output
134       */
135      public final void render(RenderAppendable out) {
136        LineReader renderer = reader(out.getWidth());
137        if (renderer != null) {
138          while (renderer.hasLine()) {
139            renderer.renderLine(out);
140            out.append('\n');
141          }
142        }
143      }
144    
145      private static class Composite extends Renderer {
146    
147        /** . */
148        private final Iterable<? extends Renderer> renderers;
149    
150        /** . */
151        private final int actualWidth;
152    
153        /** . */
154        private final int minWidth;
155    
156        private Composite(Iterable<? extends Renderer> renderers) {
157    
158          int actualWidth = 0;
159          int minWidth = 0;
160          for (Renderer renderer : renderers) {
161            actualWidth = Math.max(actualWidth, renderer.getActualWidth());
162            minWidth = Math.max(minWidth, renderer.getMinWidth());
163          }
164    
165          this.actualWidth = actualWidth;
166          this.minWidth = minWidth;
167          this.renderers = renderers;
168        }
169    
170        @Override
171        public int getActualWidth() {
172          return actualWidth;
173        }
174    
175        @Override
176        public int getMinWidth() {
177          return minWidth;
178        }
179    
180        @Override
181        public int getActualHeight(int width) {
182          int actualHeight = 0;
183          for (Renderer renderer : renderers) {
184            actualHeight += renderer.getActualHeight(width);
185          }
186          return actualHeight;
187        }
188    
189        @Override
190        public int getMinHeight(int width) {
191          return 1;
192        }
193    
194        @Override
195        public LineReader reader(final int width, final int height) {
196    
197          final Iterator<? extends Renderer> i = renderers.iterator();
198    
199          //
200          return new LineReader() {
201    
202            /** . */
203            private LineReader current;
204    
205            /** . */
206            private int index = 0;
207    
208            public boolean hasLine() {
209              if (height > 0 && index >= height) {
210                return false;
211              } else {
212                if (current == null || !current.hasLine()) {
213                  while (i.hasNext()) {
214                    Renderer next = i.next();
215                    LineReader reader = next.reader(width);
216                    if (reader != null && reader.hasLine()) {
217                      current = reader;
218                      return true;
219                    }
220                  }
221                  return false;
222                } else {
223                  return true;
224                }
225              }
226            }
227    
228            public void renderLine(RenderAppendable to) throws IllegalStateException {
229              if (hasLine()) {
230                current.renderLine(to);
231                index++;
232              } else {
233                throw new IllegalStateException();
234              }
235            }
236          };
237        }
238    
239        @Override
240        public LineReader reader(final int width) {
241          return reader(width, -1);
242        }
243      }
244    }