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