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 }