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