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