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 }