/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.tobago.util;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.tobago.component.ComponentUtil;
import org.apache.myfaces.tobago.component.LayoutTokens;
import org.apache.myfaces.tobago.component.UICell;
import org.apache.myfaces.tobago.component.UIForm;
import org.apache.myfaces.tobago.component.UIHiddenInput;
import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.component.UIParameter;
import javax.faces.context.FacesContext;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HEIGHT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_HEIGHT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MINIMUM_SIZE;
import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH;
import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
public final class LayoutUtil {
private static final Log LOG = LogFactory.getLog(LayoutUtil.class);
private LayoutUtil() {
// to prevent instantiation
}
public static int getInnerSpace(FacesContext facesContext,
UIComponent component, boolean width) {
String attribute;
if (width) {
attribute = ATTR_INNER_WIDTH;
} else {
attribute = ATTR_INNER_HEIGHT;
}
Integer innerSpace = (Integer) component.getAttributes().get(attribute);
if (innerSpace == null) {
int space = -1;
Integer spaceInteger;
if (width) {
spaceInteger = getLayoutWidth(component);
} else {
spaceInteger = getLayoutHeight(component);
}
if (spaceInteger != null) {
space = spaceInteger;
}
// if (space < 0 && component.getParent() instanceof UIComponentBase) {
if (space < 0 && component.getParent() != null) {
space = getInnerSpace(facesContext, component.getParent(), width);
}
if (space != -1) {
innerSpace = getInnerSpace(facesContext, component, space, width);
} else {
innerSpace = -1;
}
component.getAttributes().put(attribute, innerSpace);
}
return innerSpace;
}
public static int getInnerSpace(FacesContext facesContext, UIComponent component,
int outerSpace, boolean width) {
int margin = 0;
if (component.getRendererType() != null) {
try {
LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
if (width) {
margin += renderer.getPaddingWidth(facesContext, component);
margin += renderer.getComponentExtraWidth(facesContext, component);
} else {
margin += renderer.getHeaderHeight(facesContext, component);
margin += renderer.getPaddingHeight(facesContext, component);
margin += renderer.getComponentExtraHeight(facesContext, component);
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("cannot find margin", e);
}
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("renderertype = null, component: " + component);
}
}
return outerSpace - margin;
}
public static int getLabelWidth(UIComponent component) {
if (component != null) {
UIComponent label = component.getFacet(FACET_LABEL);
if (label != null) {
String labelWidth = (String) label.getAttributes().get(ATTR_WIDTH);
if (labelWidth != null) {
try {
return Integer.parseInt(stripNonNumericChars(labelWidth));
} catch (NumberFormatException e) {
LOG.warn("Can't parse label width, using default value", e);
}
}
}
}
return 0;
}
// TODO Change this to DimensionUtils.getWidth?
public static Integer getLayoutWidth(UIComponent component) {
return getLayoutSpace(component, ATTR_WIDTH, ATTR_LAYOUT_WIDTH);
}
// TODO Change this to DimensionUtils.getHeight?
public static Integer getLayoutHeight(UIComponent component) {
return getLayoutSpace(component, ATTR_HEIGHT, ATTR_LAYOUT_HEIGHT);
}
public static Integer getLayoutSpace(UIComponent component,
String sizeAttribute, String layoutAttribute) {
Object value = ComponentUtil.getAttribute(component, sizeAttribute);
if (value != null) {
if (value instanceof String) {
return new Integer(stripNonNumericChars((String) value));
} else {
return (Integer) value;
}
} else if (!ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
value = ComponentUtil.getAttribute(component, layoutAttribute);
return (Integer) value;
}
return null;
}
public static List<UIComponent> addChildren(List<UIComponent> children, UIComponent panel) {
for (Object o : panel.getChildren()) {
UIComponent child = (UIComponent) o;
if (isTransparentForLayout(child)) {
addChildren(children, child);
} else {
children.add(child);
}
}
return children;
}
public static boolean isTransparentForLayout(UIComponent component) {
// SubViewTag's component is UINamingContainer with 'null' rendererType
// is transparent for laying out.
if (component instanceof UINamingContainer && component.getRendererType() == null) {
return true;
}
// debugging info
if ("facelets".equals(component.getFamily())) {
return !"com.sun.facelets.tag.UIDebug".equals(component.getClass().getName());
}
// also Forms are transparent for laying out
if (component instanceof UIForm) {
return true;
}
// hidden fields, parameter and facelets instructions are transparent.
// this feature must be activated in the Tobago config for backward compatibility.
if (fixLayoutTransparency) {
if (component instanceof UIHiddenInput
|| component instanceof UIParameter
|| component.getClass().getPackage().getName().equals("com.sun.facelets.compiler")) {
return true;
}
}
return false;
}
public static UIComponent getLayoutParent(UIComponent component) {
UIComponent parent = component.getParent();
while (parent != null && isTransparentForLayout(parent)) {
parent = parent.getParent();
}
return parent;
}
public static void maybeSetLayoutAttribute(UIComponent cell, String attribute,
Integer value) {
if (RENDERER_TYPE_OUT.equals(cell.getRendererType())) {
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("set " + value + " to " + cell.getRendererType());
}
cell.getAttributes().put(attribute, value);
if (ATTR_LAYOUT_WIDTH.equals(attribute)) {
cell.getAttributes().remove(ATTR_INNER_WIDTH);
} else if (ATTR_LAYOUT_HEIGHT.equals(attribute)) {
cell.getAttributes().remove(ATTR_INNER_HEIGHT);
}
if (cell instanceof UICell) {
List<UIComponent> children = addChildren(new ArrayList<UIComponent>(), cell);
for (UIComponent component : children) {
maybeSetLayoutAttribute(component, attribute, value);
}
}
}
public static int calculateFixedHeightForChildren(FacesContext facesContext, UIComponent component) {
int height = 0;
for (Object o : component.getChildren()) {
UIComponent child = (UIComponent) o;
LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, child);
if (renderer == null
&& child instanceof UINamingContainer
&& child.getChildren().size() > 0) {
// this is a subview component ??
renderer = ComponentUtil.getRenderer(facesContext, (UIComponent) child.getChildren().get(0));
}
if (renderer != null) {
int h = renderer.getFixedHeight(facesContext, child);
if (h > 0) {
height += h;
}
}
}
return height;
}
public static Dimension getMinimumSize(
FacesContext facesContext, UIComponent component) {
Dimension dimension = (Dimension) component.getAttributes().get(ATTR_MINIMUM_SIZE);
if (dimension == null) {
LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
if (renderer != null) {
dimension = renderer.getMinimumSize(facesContext, component);
}
}
if (dimension == null) {
dimension = new Dimension(-1, -1);
}
return dimension;
}
public static boolean checkTokens(String columns) {
StringTokenizer st = new StringTokenizer(columns, ";");
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (!checkToken(token)) {
return false;
}
}
return true;
}
public static boolean checkToken(String columnToken) {
return LayoutTokens.parseToken(columnToken) != null;
}
// XXX perhaps move to StringUtils
public static String stripNonNumericChars(String token) {
if (token == null || token.length() == 0) {
return token;
}
StringBuilder builder = new StringBuilder(token.length());
for (int i = 0; i < token.length(); ++i) {
char c = token.charAt(i);
if (Character.isDigit(c)) {
builder.append(c);
}
}
return builder.toString();
}
public static boolean isNumberAndSuffix(String token, String suffix) {
return token.endsWith(suffix)
&& NumberUtils.isDigits(removeSuffix(token, suffix));
}
public static String removeSuffix(String token, String suffix) {
return token.substring(0, token.length() - suffix.length());
}
private static Boolean fixLayoutTransparency;
public static void setFixLayoutTransparency(boolean fixLayoutTransparency) {
if (LayoutUtil.fixLayoutTransparency == null) {
LayoutUtil.fixLayoutTransparency = fixLayoutTransparency;
} else {
LOG.error("LayoutUtil.setFixLayoutTransparency() can only called one time.");
}
}
}