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 }