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.logging.Log; 021 import org.apache.commons.logging.LogFactory; 022 import org.apache.commons.lang.math.NumberUtils; 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.UICell; 035 import org.apache.myfaces.tobago.component.UIForm; 036 import org.apache.myfaces.tobago.component.LayoutTokens; 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 StringUtil 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 }