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