001    package org.apache.myfaces.tobago.util;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.apache.commons.lang.builder.ToStringBuilder;
023    import org.apache.myfaces.tobago.component.LayoutTokens;
024    import org.apache.myfaces.tobago.component.RelativeLayoutToken;
025    import org.apache.myfaces.tobago.component.PixelLayoutToken;
026    import org.apache.myfaces.tobago.component.LayoutToken;
027    import org.apache.myfaces.tobago.component.PercentLayoutToken;
028    import org.apache.myfaces.tobago.component.HideLayoutToken;
029    
030    import java.util.ArrayList;
031    import java.util.List;
032    import java.util.StringTokenizer;
033    
034    public class LayoutInfo {
035    
036      private static final int FREE = -1;
037      private static final Log LOG = LogFactory.getLog(LayoutInfo.class);
038      public static final int HIDE = -2;
039    
040      private int cellsLeft;
041      private int spaceLeft;
042      private int[] spaces;
043      private LayoutTokens layoutTokens;
044    
045      public LayoutInfo(int cellCount, int space, LayoutTokens layoutTokens) {
046        this(cellCount, space, layoutTokens, false);
047      }
048    
049      public LayoutInfo(int cellCount, int space, LayoutTokens layoutTokens, boolean ignoreMismatch) {
050    
051        this.cellsLeft = cellCount;
052        this.spaceLeft = space;
053        this.layoutTokens = layoutTokens;
054        /*if (layoutTokens.length == cellCount) {
055          this.layoutTokens = layoutTokens;
056        } else */
057        if (layoutTokens.getSize() > cellCount) {
058          if (!ignoreMismatch) {
059            LOG.warn("More tokens (" + layoutTokens.getSize()
060                + ") for layout than cells (" + cellCount + ") found! Ignoring"
061                + " redundant tokens. Token string was: "
062                + layoutTokens);
063          }
064    
065          layoutTokens.shrinkSizeTo(cellCount);
066        } else {
067          if (!ignoreMismatch && LOG.isWarnEnabled() && (cellCount - layoutTokens.getSize()) != 0) {
068            LOG.warn("More cells (" + cellCount + ") than tokens (" + layoutTokens.getSize()
069                + ") for layout found! Setting missing tokens to '1*'."
070                + " Token string was: " + layoutTokens);
071          }
072          layoutTokens.ensureSize(cellCount, new RelativeLayoutToken(1));
073          //this.layoutTokens = new String[cellCount];
074          //for (int i = 0; i < cellCount; i++) {
075          //  if (i < layoutTokens.length) {
076          //    this.layoutTokens[i] = layoutTokens[i];
077          //  } else {
078          //    this.layoutTokens[i] = "1*";
079          //  }
080          //}
081        }
082        createAndInitSpaces(cellCount, FREE);
083      }
084    
085      private void createAndInitSpaces(int columns, int initValue) {
086        spaces = new int[columns];
087        for (int j = 0; j < spaces.length; j++) {
088          spaces[j] = initValue;
089        }
090      }
091    
092      public void update(int space, int index) {
093        update(space, index, false);
094      }
095    
096      public void update(int space, int index, boolean force) {
097        if (space > spaceLeft) {
098          if (LOG.isDebugEnabled()) {
099            LOG.debug("More space (" + space + ") needed than available (" + spaceLeft + ")!");
100          }
101          if (!force) {
102            if (LOG.isDebugEnabled()) {
103              LOG.debug("Cutting to fit.");
104            }
105            if (spaceLeft < 0) {
106              space = 0;
107            } else {
108              space = spaceLeft;
109            }
110          }
111        }
112    
113        spaceLeft -= space;
114        cellsLeft--;
115        if (index < spaces.length) {
116          spaces[index] = space;
117          if (spaceLeft < 1 && columnsLeft()) {
118            if (LOG.isWarnEnabled()) {
119              LOG.warn("There are columns left but no more space! cellsLeft="
120                  + cellsLeft + ", tokens=" + layoutTokens);
121              LOG.warn("calculated spaces = " + tokensToString(spaces));
122            }
123          }
124        } else {
125          LOG.warn("More space to assign (" + space + "px) but no more columns!"
126              + " More layout tokens than column tags?");
127        }
128      }
129    
130      public boolean columnsLeft() {
131        return cellsLeft > 0;
132      }
133    
134    
135      public void handleIllegalTokens() {
136        for (int i = 0; i < spaces.length; i++) {
137          if (isFree(i)) {
138            if (LOG.isWarnEnabled()) {
139              LOG.warn("Illegal layout token pattern \"" + layoutTokens.get(i)
140                  + "\" ignored, set to 0px !");
141            }
142            spaces[i] = 0;
143          }
144        }
145      }
146    
147    
148      public static String[] createLayoutTokens(String columnLayout, int count) {
149        return createLayoutTokens(columnLayout, count, "1*");
150      }
151    
152      public static String[] createLayoutTokens(String columnLayout, int count,
153          String defaultToken) {
154        String[] tokens;
155        if (columnLayout != null) {
156          List<String>  list = new ArrayList<String>();
157          StringTokenizer tokenizer = new StringTokenizer(columnLayout, ";");
158          while (tokenizer.hasMoreTokens()) {
159            String token = tokenizer.nextToken().trim();
160            if ("*".equals(token)) {
161              token = "1*";
162            }
163            list.add(token);
164          }
165          tokens = list.toArray(new String[list.size()]);
166        } else {
167          defaultToken = "*".equals(defaultToken) ? "1*" : defaultToken;
168          tokens = new String[count];
169          for (int i = 0; i < tokens.length; i++) {
170            tokens[i] = defaultToken;
171          }
172        }
173        if (LOG.isDebugEnabled()) {
174          LOG.debug("created Tokens : " + tokensToString(tokens));
175        }
176        return tokens;
177      }
178    
179      public static String listToTokenString(List list) {
180        String[] tokens = new String[list.size()];
181        for (int i = 0; i < list.size(); i++) {
182          tokens[i] = list.get(i).toString();
183        }
184        return tokensToString(tokens);
185      }
186    
187      public static String tokensToString(int[] tokens) {
188        String[] strings = new String[tokens.length];
189        for (int i = 0; i < tokens.length; i++) {
190          strings[i] = Integer.toString(tokens[i]);
191        }
192        return tokensToString(strings);
193      }
194    
195      public static String tokensToString(String[] tokens) {
196        StringBuilder sb = new StringBuilder();
197        for (String token : tokens) {
198          if (sb.length() != 0) {
199            sb.append(";");
200          }
201          sb.append(token);
202        }
203        sb.insert(0, "\"");
204        sb.append("\"");
205        return sb.toString();
206      }
207    
208      public boolean isFree(int column) {
209        return spaces[column] == FREE;
210      }
211    
212      public int getSpaceForColumn(int column) {
213        return spaces[column];
214      }
215    
216      public int getSpaceLeft() {
217        return spaceLeft;
218      }
219    
220      public LayoutTokens getLayoutTokens() {
221        return layoutTokens;
222      }
223    
224      public boolean hasLayoutTokens() {
225        return !layoutTokens.isEmpty();
226      }
227    
228      public List<Integer> getSpaceList() {
229        List<Integer> list = new ArrayList<Integer>(spaces.length);
230        for (int space : spaces) {
231          list.add(space);
232        }
233        return list;
234      }
235    
236      public void handleSpaceLeft() {
237        if (spaceLeft > 0) {
238          if (LOG.isDebugEnabled()) {
239            LOG.debug("spread spaceLeft (" + spaceLeft + "px) to columns");
240            LOG.debug("spaces before spread :" + arrayAsString(spaces));
241          }
242    
243         for (int i = 0; i < layoutTokens.getSize(); i++) {
244            if (layoutTokens.get(i) instanceof RelativeLayoutToken) {
245              addSpace(spaceLeft, i);
246              break;
247            }
248          }
249          boolean found = false;
250          while (spaceLeft > 0) {
251    //        for (int i = 0; i < layoutTokens.length; i++) {
252            for (int i = layoutTokens.getSize() - 1; i > -1; i--) {
253              //String layoutToken = layoutTokens[i];
254              if (spaceLeft > 0 && layoutTokens.get(i) instanceof RelativeLayoutToken) {
255                found = true;
256                addSpace(1, i);
257              }
258            }
259            if (!found) {
260              break;
261            }
262          }
263        }
264        if (spaceLeft > 0 && LOG.isWarnEnabled()) {
265          LOG.warn("Space left after spreading : " + spaceLeft + "px!");
266        }
267        if (LOG.isDebugEnabled()) {
268          LOG.debug("spaces after spread  :" + arrayAsString(spaces));
269        }
270      }
271      //TODO replace with Arrays.asList ..
272      private String arrayAsString(int[] currentSpaces) {
273        StringBuilder sb = new StringBuilder("[");
274        for (int currentSpace : currentSpaces) {
275          sb.append(currentSpace);
276          sb.append(", ");
277        }
278        sb.replace(sb.lastIndexOf(", "), sb.length(), "]");
279        return sb.toString();
280      }
281    
282      private void addSpace(int space, int i) {
283        if (spaces[i] > HIDE) {
284          if (spaces[i] == FREE) {
285            spaces[i] = space;
286          } else {
287            spaces[i] += space;
288          }
289          spaceLeft -= space;
290        }
291      }
292    
293    
294      public void parseHides(int padding) {
295        for (int i = 0; i < layoutTokens.getSize(); i++) {
296          if (layoutTokens.get(i) instanceof HideLayoutToken) {
297            update(0, i);
298            spaces[i] = HIDE;
299            if (LOG.isDebugEnabled()) {
300              LOG.debug("set column " + i + " from " + layoutTokens.get(i)
301                  + " to hide ");
302            }
303          }
304        }
305      }
306    
307      public void parsePixels() {
308        for (int i = 0; i < layoutTokens.getSize(); i++) {
309          LayoutToken token = layoutTokens.get(i);
310          if (token instanceof PixelLayoutToken) {
311            int w = ((PixelLayoutToken) token).getPixel();
312            update(w, i, true);
313            if (LOG.isDebugEnabled()) {
314              LOG.debug("set column " + i + " from " + token
315                  + " to with " + w);
316            }
317          }
318        }
319      }
320    
321    
322      public void parsePercent(double innerWidth) {
323        if (columnsLeft()) {
324          for (int i = 0; i < layoutTokens.getSize(); i++) {
325            LayoutToken token = layoutTokens.get(i);
326            if (isFree(i) && token instanceof PercentLayoutToken) {
327              int percent = ((PercentLayoutToken) token).getPercent();
328              int w = (int) (innerWidth / 100 * percent);
329              update(w, i);
330              if (LOG.isDebugEnabled()) {
331                LOG.debug("set column " + i + " from " + token
332                    + " to with " + w);
333              }
334            }
335          }
336        }
337      }
338    
339      public void parsePortions() {
340        if (columnsLeft()) {
341          //   1. count portions
342          int portions = 0;
343          for (int i = 0; i < layoutTokens.getSize(); i++) {
344            LayoutToken token = layoutTokens.get(i);
345            if (isFree(i) && token instanceof RelativeLayoutToken) {
346              portions += ((RelativeLayoutToken) token).getFactor();
347            }
348          }
349          //  2. calc and set portion
350          if (portions > 0) {
351            int widthForPortions = getSpaceLeft();
352            for (int i = 0; i < layoutTokens.getSize(); i++) {
353              LayoutToken token = layoutTokens.get(i);
354              if (isFree(i) && token instanceof RelativeLayoutToken) {
355                int portion = ((RelativeLayoutToken) token).getFactor();
356                float w = (float) widthForPortions / portions * portion;
357                if (w < 0) {
358                  update(0, i);
359                  if (LOG.isDebugEnabled()) {
360                    LOG.debug("set column " + i + " from " + token
361                        + " to with " + w + " == 0px");
362                  }
363                } else {
364                  update(Math.round(w), i);
365                  if (LOG.isDebugEnabled()) {
366                    LOG.debug("set column " + i + " from " + token
367                        + " to with " + w + " == " + Math.round(w) + "px");
368                  }
369                }
370              }
371            }
372          }
373        }
374      }
375    
376    /*
377      public void parseAsterisks() {
378        String[] tokens = getLayoutTokens();
379        if (columnsLeft()) {
380          //  1. count unset columns
381          int portions = 0;
382          for (int i = 0; i < tokens.length; i++) {
383            if (isFree(i) && tokens[i].equals("*")) {
384              portions++;
385            }
386          }
387          //  2. calc and set portion
388          int widthPerPortion;
389          if (portions > 0) {
390            widthPerPortion = getSpaceLeft() / portions;
391            for (int i = 0; i < tokens.length; i++) {
392              if (isFree(i) && tokens[i].equals("*")) {
393                int w = widthPerPortion;
394                update(w, i);
395                if (LOG.isDebugEnabled()) {
396                  LOG.debug("set column " + i + " from " + tokens[i]
397                      + " to with " + w);
398                }
399              }
400            }
401          }
402        }
403      }
404    */
405    
406      public void parseColumnLayout(double space) {
407        parseColumnLayout(space, 0);
408      }
409    
410      public void parseColumnLayout(double space, int padding) {
411    
412        if (hasLayoutTokens()) {
413          parseHides(padding);
414          parsePixels();
415          parsePercent(space);
416          parsePortions();
417    //      parseAsterisks();
418          handleSpaceLeft();
419        }
420    
421        if (columnsLeft() && LOG.isWarnEnabled()) {
422          handleIllegalTokens();
423        }
424      }
425    
426      public String toString() {
427        return new ToStringBuilder(this).
428            append("cellLeft", cellsLeft).
429            append("spaceLeft", spaceLeft).
430            append("spaces", spaces).
431            append("layoutTokens", layoutTokens).
432            toString();
433      }
434    }
435