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