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.math.NumberUtils;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    import org.apache.myfaces.tobago.component.ComponentUtil;
024    import org.apache.myfaces.tobago.component.LayoutTokens;
025    import org.apache.myfaces.tobago.component.UICell;
026    import org.apache.myfaces.tobago.component.UIForm;
027    import org.apache.myfaces.tobago.component.UIHiddenInput;
028    import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
029    
030    import javax.faces.component.UIComponent;
031    import javax.faces.component.UINamingContainer;
032    import javax.faces.component.UIParameter;
033    import javax.faces.context.FacesContext;
034    import java.awt.Dimension;
035    import java.util.ArrayList;
036    import java.util.List;
037    import java.util.StringTokenizer;
038    
039    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HEIGHT;
040    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
041    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_HEIGHT;
042    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
043    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
044    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
045    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MINIMUM_SIZE;
046    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH;
047    import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
048    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
049    
050    public final class LayoutUtil {
051    
052      private static final Log LOG = LogFactory.getLog(LayoutUtil.class);
053    
054      private LayoutUtil() {
055        // to prevent instantiation
056      }
057    
058      public static int getInnerSpace(FacesContext facesContext,
059          UIComponent component, boolean width) {
060        String attribute;
061        if (width) {
062          attribute = ATTR_INNER_WIDTH;
063        } else {
064          attribute = ATTR_INNER_HEIGHT;
065        }
066        Integer innerSpace = (Integer) component.getAttributes().get(attribute);
067    
068        if (innerSpace == null) {
069          int space = -1;
070    
071          Integer spaceInteger;
072          if (width) {
073            spaceInteger = getLayoutWidth(component);
074          } else {
075            spaceInteger = getLayoutHeight(component);
076          }
077          if (spaceInteger != null) {
078            space = spaceInteger;
079          }
080    
081    //      if (space < 0 && component.getParent() instanceof UIComponentBase) {
082          if (space < 0 && component.getParent() != null) {
083            space = getInnerSpace(facesContext, component.getParent(), width);
084          }
085    
086          if (space != -1) {
087            innerSpace = getInnerSpace(facesContext, component, space, width);
088          } else {
089            innerSpace = -1;
090          }
091    
092          component.getAttributes().put(attribute, innerSpace);
093        }
094    
095        return innerSpace;
096      }
097    
098      public static int getInnerSpace(FacesContext facesContext, UIComponent component,
099          int outerSpace, boolean width) {
100        int margin = 0;
101        if (component.getRendererType() != null) {
102          try {
103    
104            LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
105    
106            if (width) {
107              margin += renderer.getPaddingWidth(facesContext, component);
108              margin += renderer.getComponentExtraWidth(facesContext, component);
109            } else {
110              margin += renderer.getHeaderHeight(facesContext, component);
111              margin += renderer.getPaddingHeight(facesContext, component);
112              margin += renderer.getComponentExtraHeight(facesContext, component);
113            }
114          } catch (Exception e) {
115            if (LOG.isDebugEnabled()) {
116              LOG.debug("cannot find margin", e);
117            }
118          }
119        } else {
120          if (LOG.isDebugEnabled()) {
121            LOG.debug("renderertype = null, component: " + component);
122          }
123        }
124        return outerSpace - margin;
125      }
126    
127    
128      public static int getLabelWidth(UIComponent component) {
129        if (component != null) {
130          UIComponent label = component.getFacet(FACET_LABEL);
131          if (label != null) {
132            String labelWidth = (String) label.getAttributes().get(ATTR_WIDTH);
133            if (labelWidth != null) {
134              try {
135                return Integer.parseInt(stripNonNumericChars(labelWidth));
136              } catch (NumberFormatException e) {
137                LOG.warn("Can't parse label width, using default value", e);
138              }
139            }
140          }
141        }
142        return 0;
143      }
144    
145      // TODO Change this to DimensionUtils.getWidth?
146      public static Integer getLayoutWidth(UIComponent component) {
147        return getLayoutSpace(component, ATTR_WIDTH, ATTR_LAYOUT_WIDTH);
148      }
149    
150      // TODO Change this to DimensionUtils.getHeight?
151      public static Integer getLayoutHeight(UIComponent component) {
152        return getLayoutSpace(component, ATTR_HEIGHT, ATTR_LAYOUT_HEIGHT);
153      }
154    
155      public static Integer getLayoutSpace(UIComponent component,
156          String sizeAttribute, String layoutAttribute) {
157        Object value = ComponentUtil.getAttribute(component, sizeAttribute);
158        if (value != null) {
159          if (value instanceof String) {
160            return new Integer(stripNonNumericChars((String) value));
161          } else {
162            return (Integer) value;
163          }
164        } else if (!ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
165          value = ComponentUtil.getAttribute(component, layoutAttribute);
166          return (Integer) value;
167        }
168        return null;
169      }
170    
171      public static List<UIComponent> addChildren(List<UIComponent> children, UIComponent panel) {
172        for (Object o : panel.getChildren()) {
173          UIComponent child = (UIComponent) o;
174          if (isTransparentForLayout(child)) {
175            addChildren(children, child);
176          } else {
177            children.add(child);
178          }
179        }
180        return children;
181      }
182    
183      public static boolean isTransparentForLayout(UIComponent component) {
184    
185        // SubViewTag's component is UINamingContainer with 'null' rendererType
186        // is transparent for laying out.
187        if (component instanceof UINamingContainer && component.getRendererType() == null) {
188          return true;
189        }
190    
191        // debugging info
192        if ("facelets".equals(component.getFamily())) {
193          return !"com.sun.facelets.tag.UIDebug".equals(component.getClass().getName());
194        }
195    
196        //  also Forms are transparent for laying out
197        if (component instanceof UIForm) {
198          return true;
199        }
200    
201        // hidden fields, parameter and facelets instructions are transparent.
202        // this feature must be activated in the Tobago config for backward compatibility.
203        if (fixLayoutTransparency) {
204          if (component instanceof UIHiddenInput
205              || component instanceof UIParameter
206              || component.getClass().getPackage().getName().equals("com.sun.facelets.compiler")) {
207            return true;
208          }
209        }
210    
211        return false;
212      }
213    
214      public static UIComponent getLayoutParent(UIComponent component) {
215        UIComponent parent = component.getParent();
216        while (parent != null && isTransparentForLayout(parent)) {
217          parent = parent.getParent();
218        }
219        return parent;
220      }
221    
222      public static void maybeSetLayoutAttribute(UIComponent cell, String attribute,
223          Integer value) {
224        if (RENDERER_TYPE_OUT.equals(cell.getRendererType())) {
225          return;
226        }
227        if (LOG.isDebugEnabled()) {
228          LOG.debug("set " + value + " to " + cell.getRendererType());
229        }
230        cell.getAttributes().put(attribute, value);
231        if (ATTR_LAYOUT_WIDTH.equals(attribute)) {
232          cell.getAttributes().remove(ATTR_INNER_WIDTH);
233        } else if (ATTR_LAYOUT_HEIGHT.equals(attribute)) {
234          cell.getAttributes().remove(ATTR_INNER_HEIGHT);
235        }
236        if (cell instanceof UICell) {
237          List<UIComponent> children = addChildren(new ArrayList<UIComponent>(), cell);
238          for (UIComponent component : children) {
239            maybeSetLayoutAttribute(component, attribute, value);
240          }
241        }
242      }
243    
244      public static int calculateFixedHeightForChildren(FacesContext facesContext, UIComponent component) {
245        int height = 0;
246        for (Object o : component.getChildren()) {
247          UIComponent child = (UIComponent) o;
248          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, child);
249          if (renderer == null
250              && child instanceof UINamingContainer
251              && child.getChildren().size() > 0) {
252            // this is a subview component ??
253            renderer = ComponentUtil.getRenderer(facesContext, (UIComponent) child.getChildren().get(0));
254          }
255          if (renderer != null) {
256            int h = renderer.getFixedHeight(facesContext, child);
257            if (h > 0) {
258              height += h;
259            }
260          }
261        }
262        return height;
263      }
264    
265      public static Dimension getMinimumSize(
266          FacesContext facesContext, UIComponent component) {
267        Dimension dimension = (Dimension) component.getAttributes().get(ATTR_MINIMUM_SIZE);
268        if (dimension == null) {
269          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
270          if (renderer != null) {
271            dimension = renderer.getMinimumSize(facesContext, component);
272          }
273        }
274        if (dimension == null) {
275          dimension = new Dimension(-1, -1);
276        }
277        return dimension;
278      }
279    
280      public static boolean checkTokens(String columns) {
281        StringTokenizer st = new StringTokenizer(columns, ";");
282        while (st.hasMoreTokens()) {
283          String token = st.nextToken();
284          if (!checkToken(token)) {
285            return false;
286          }
287        }
288        return true;
289      }
290    
291      public static boolean checkToken(String columnToken) {
292        return LayoutTokens.parseToken(columnToken) != null;
293      }
294    
295      // XXX perhaps move to StringUtils
296      public static String stripNonNumericChars(String token) {
297        if (token == null || token.length() == 0) {
298          return token;
299        }
300        StringBuilder builder = new StringBuilder(token.length());
301        for (int i = 0; i < token.length(); ++i) {
302          char c = token.charAt(i);
303          if (Character.isDigit(c)) {
304            builder.append(c);
305          }
306        }
307        return builder.toString();
308      }
309    
310      public static boolean isNumberAndSuffix(String token, String suffix) {
311        return token.endsWith(suffix)
312            && NumberUtils.isDigits(removeSuffix(token, suffix));
313      }
314    
315      public static String removeSuffix(String token, String suffix) {
316        return token.substring(0, token.length() - suffix.length());
317      }
318    
319      private static Boolean fixLayoutTransparency;
320    
321      public static void setFixLayoutTransparency(boolean fixLayoutTransparency) {
322        if (LayoutUtil.fixLayoutTransparency == null) {
323          LayoutUtil.fixLayoutTransparency = fixLayoutTransparency;
324        } else {
325          LOG.error("LayoutUtil.setFixLayoutTransparency() can only called one time.");
326        }
327      }
328    }