All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jsftemplating.component.ComponentUtil Maven / Gradle / Ivy

/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://jsftemplating.dev.java.net/cddl1.html or
 * jsftemplating/cddl1.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at jsftemplating/cddl1.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.jsftemplating.component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;

import com.sun.jsftemplating.el.VariableResolver;
import com.sun.jsftemplating.layout.descriptors.ComponentType;
import com.sun.jsftemplating.layout.descriptors.LayoutComponent;
import com.sun.jsftemplating.layout.descriptors.LayoutElement;
import com.sun.jsftemplating.util.LogUtil;
import com.sun.jsftemplating.util.TypeConverter;


/**
 *  

Utility class that contains helper methods for components.

* * @author Ken Paulsen ([email protected]) */ public class ComponentUtil { /** *

Default Constructor.

*/ private ComponentUtil() { } /** *

Method to get access to the ComponentUtil instance for the current * application.

*/ public static ComponentUtil getInstance(FacesContext ctx) { ComponentUtil cu = null; if (ctx != null) { Map appMap = ctx.getExternalContext().getApplicationMap(); cu = (ComponentUtil) appMap.get(COMPONENT_UTIL_KEY); if (cu == null) { cu = new ComponentUtil(); // Perhaps a SoftReference would be a good idea here? appMap.put(COMPONENT_UTIL_KEY, cu); } } else { // Not JSF env, create every time... cu = new ComponentUtil(); } return cu; } /** *

Return a child with the specified component id from the specified * component. If not found, return null.

* *

This method will NOT create a new UIComponent.

* * @param parent UIComponent to be searched * @param id Component id (or facet name) to search for * * @return The child UIComponent if it exists, null otherwise. */ public UIComponent getChild(UIComponent parent, String id) { return findChild(parent, id, id); } /** *

Return a child with the specified component id (or facetName) from * the specified component. If not found, return null. * facetName or id may be null to avoid * searching the facet Map or the parent's children.

* *

This method will NOT create a new UIComponent.

* * @param parent UIComponent to be searched * @param id id to search for * @param facetName Facet name to search for * * @return The child UIComponent if it exists, null otherwise. */ public UIComponent findChild(UIComponent parent, String id, String facetName) { // Sanity Check if (parent == null) { return null; } // First search for facet UIComponent child = null; if (facetName != null) { child = parent.getFacets().get(facetName); if (child != null) { return child; } } // Search for component by id if (id != null) { Iterator it = parent.getChildren().iterator(); while (it.hasNext()) { child = it.next(); if (id.equals(child.getId())) { return (child); } } } // Not found, return null return null; } /** *

This method finds or creates a child UIComponent * identified by the given id. If the child is not found, it will * attempt to create it using the provided * {@link com.sun.jsftemplating.component.factory.ComponentFactory} * (factoryClass).

* *

If there are Properties to be set on the UIComponent, * this method should generally be avoided. It is preferable to use * the {@link #getChild(UIComponent, String, String, Properties)} * form of getChild.

* *

* // Example (no properties):
* UIComponent child = Util.getChild(component, "jklLabel", "com.sun.jsftemplating.component.factory.basic.LabelFactory");
* ((Label)child).setText("JKL Label:");
* ((Label)child).setFor("jkl");
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child); *

* * @param parent Parent UIComponent * @param id Identifier for child UIComponent * @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name * * @return The child UIComponent that was found or created. * * @see #getChild(UIComponent, String, String, Properties) */ public UIComponent getChild(UIComponent parent, String id, String factoryClass) { return getChild(parent, id, factoryClass, id); } /** *

Same as {@link #getChild(UIComponent, String, String)} except that * it allows you to specify a facetName different than the id. If * null is supplied, it won't save the component as a facet.

* * @param parent Parent UIComponent * @param id Identifier for the child UIComponent * @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name * @param facetName The facet name (null means don't store it) * * @return The child UIComponent that was found or created. * * @see #getChild(UIComponent, String, String) */ public UIComponent getChild(UIComponent parent, String id, String factoryClass, String facetName) { return getChild(parent, id, getComponentType(factoryClass), null, facetName); } /** *

This method finds or creates a child UIComponent * identified by the given id. If the child is not found, it will * attempt to create it using the provided * {@link com.sun.jsftemplating.component.factory.ComponentFactory} * (factoryClass). It will also initialize the * UIComponent using the provided set of * Properties.

* *

* // Example (with properties):
* Properties props = new Properties();
* props.setProperty("text", "ABC Label:");
* props.setProperty("for", "abc");
* UIComponent child = Util.getChild(component, "abcLabel", "com.sun.jsftemplating.component.factory.basic.LabelFactory", props);
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child); *

* * @param parent Parent UIComponent * @param id Identifier for the child UIComponent * @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name * @param properties java.util.Properties needed to * create and/or initialize the * UIComponent * * @return The child UIComponent that was found or created. */ public UIComponent getChild(UIComponent parent, String id, String factoryClass, Properties properties) { return getChild(parent, id, factoryClass, properties, id); } /** *

Same as {@link #getChild(UIComponent, String, String, Properties)} * except that it allows you to specify a facetName different than the * id. If null is supplied, it won't save the component as a * facet.

* * @param parent Parent UIComponent * @param id Identifier for the child UIComponent * @param factoryClass Full {@link com.sun.jsftemplating.component.factory.ComponentFactory} class name * @param properties java.util.Properties needed to * create and/or initialize the * UIComponent * @param facetName The facet name (null means don't store it) * * @return The child UIComponent that was found or created. */ public UIComponent getChild(UIComponent parent, String id, String factoryClass, Properties properties, String facetName) { return getChild(parent, id, getComponentType(factoryClass), properties, facetName); } /** *

This method finds or creates a child UIComponent * identified by the given id. If the child is not found, it will * attempt to create it using the provided {@link ComponentType} * (type). It will also initialize the * UIComponent using the provided set of * properties.

* * @param parent Parent UIComponent * @param id Identifier for the child * UIComponent * @param type The ComponentType class name * @param properties Properties needed to create and/or initialize * the UIComponent * @param facetName The facet name (null means don't store it) * * @return The child UIComponent that was found or created. */ private UIComponent getChild(UIComponent parent, String id, ComponentType type, Properties properties, String facetName) { LayoutComponent desc = new LayoutComponent(null, id, type); if (properties != null) { //Remove Generics for now. Check with Ken to change thisinto HashMap. desc.setOptions((Map) properties); } if (facetName != null) { // Add the facetName to use // FIXME: Decide if this should have its own method desc.addOption(LayoutComponent.FACET_NAME, facetName); } return getChild(parent, desc); } /** *

This method creates a {@link ComponentType} instance from the given * factoryClass. It will first check its cache to see if * one has already been created. If not, it will create one and add * to the cache for future use.

* *

This method sets factoryClass for the * {@link ComponentType} id.

* * @param facatoryClass The full classname of the * {@link com.sun.jsftemplating.component.factory.ComponentFactory}. * * @return A ComponentType instance for factoryClass. */ private ComponentType getComponentType(String factoryClass) { // Check the cache ComponentType type = (ComponentType) _types.get(factoryClass); if (type == null) { // Not in the cache... add it... type = new ComponentType(factoryClass, factoryClass); Map newMap = new HashMap(_types); newMap.put(factoryClass, type); _types = newMap; } // Return the ComponentType return type; } /** *

This method finds or creates a child UIComponent * identified by the given id. If the child is not found, it will * attempt to create it using the provided {@link LayoutComponent} * (descriptor). It will also initialize the * UIComponent using the options set on the * {@link LayoutComponent}.

* *

If parent implements {@link ChildManager}, then the * responsibility of finding and creating the child will be delegated * to the {@link ChildManager} UIComponent.

* *

If you are constructing and populating a LayoutComponent before * calling this method, there are a few features that should be noted. * Besides id and type which can be set in * the LayoutComponent constructor, you can also set * options, and * {@link com.sun.jsftemplating.layout.descriptors.handler.Handler}'s.

* *

Options may be set via * {@link LayoutComponent#setOptions(Map)}. These options will be * applied to the UIComponent and may also be used by the * {@link com.sun.jsftemplating.component.factory.ComponentFactory} * while instantiating the UIComponent.

* *

{@link com.sun.jsftemplating.layout.descriptors.handler.Handler}'s * can be supplied by calling * {@link LayoutComponent#setHandlers(String, List)}. The * type must match the event name which invokes the * List of handlers you provide. The * Renderer for this UIComponent is * responsible for declaring and dispatching events. * {@link com.sun.jsftemplating.renderer.TemplateRenderer} * will invoke beforeCreate and afterCreate * events for each child it creates (such as the one being requested * here).

* *

* // Example (with LayoutComponent):
* {@link ComponentType} type = new {@link ComponentType#ComponentType(String, String) ComponentType}("LabelFactory", "com.sun.jsftemplating.component.factory.basic.LabelFactory");
* {@link LayoutComponent} descriptor = new {@link LayoutComponent#LayoutComponent(LayoutElement, String, ComponentType) LayoutComponent}(null, "abcLabel", type);
* {@link LayoutComponent#addOption(String, Object) descriptor.addOption}("text", "ABC Label:");
* {@link LayoutComponent#addOption(String, Object) descriptor.addOption}("for", "abc");
* UIComponent child = Util.getChild(component, descriptor);
*
* {@link LayoutComponent#encodeChild(FacesContext, UIComponent) LayoutComponent.encodeChild}(context, child); *

* * @param parent Parent UIComponent * @param descriptor The {@link LayoutComponent} describing the * UIComponent * * @return The child UIComponent that was found or created. */ public UIComponent getChild(UIComponent parent, LayoutComponent descriptor) { FacesContext context = FacesContext.getCurrentInstance(); // First check to see if the UIComponent can create its own children if (parent instanceof ChildManager) { return ((ChildManager) parent).getChild( context, descriptor); } // Make sure it doesn't already exist String childId = descriptor.getId(context, parent); UIComponent childComponent = findChild(parent, childId, (String) descriptor.getEvaluatedOption( context, LayoutComponent.FACET_NAME, null)); if (childComponent != null) { return childComponent; } // Not found, create a new UIComponent return createChildComponent(context, descriptor, parent); } /** *

This method creates a child UIComponent by using the * provided {@link LayoutComponent} (descriptor). It * will associate the parent and the newly created * UIComponent.

* *

It is recommended that this method NOT be called from a Renderer. * It should not be called if you have not yet checked to see if a * child UIComponent with the requested ID already exists.

* * @param context The FacesContext object. * @param descriptor The {@link LayoutComponent} describing the * UIComponent to be created. * @param parent Parent UIComponent. * * @return A new UIComponent based on the provided * {@link LayoutComponent}. * * @throws IllegalArgumentException Thrown if descriptor equals null. * * @see #getChild(UIComponent, LayoutComponent) * @see #getChild(UIComponent, String, String, Properties) * @see LayoutComponent#getType() * @see ComponentType#getFactory() * @see com.sun.jsftemplating.component.factory.ComponentFactory#create(FacesContext, LayoutComponent, UIComponent) */ public UIComponent createChildComponent(FacesContext context, LayoutComponent descriptor, UIComponent parent) { // Make sure a LayoutComponent was provided. if (descriptor == null) { throw new IllegalArgumentException("'descriptor' cannot be null!"); } // Create & return the child UIComponent return descriptor.getType().getFactory().create( context, descriptor, parent); } /** *

This util method will set the given key/value on the * UIComponent. It will resolve all $...{...} * expressions, and convert the String into a * ValueExpression if a valid expression is detected. * The return value will be a ValueExpression or the * value.

* * @param context FacesContext * @param key The Property name to set * @param value The Property value to set * @param desc The {@link LayoutElement} associated with the * UIComponent * @param comp The UIComponent * * @return A ValueExpression, or the "$...{...}" evaulated * value (if no ValueExpression is present). */ public Object setOption(FacesContext context, String key, Object value, LayoutElement desc, UIComponent comp) { // Invoke our own EL. This is needed b/c JSF's EL is designed for // context-insensitive EL. Resolve our variables now because we // cannot depend on the individual components to do this later. We // need to find a way to make this work as a regular ValueExpression... // for now, we'll continue to use it... value = VariableResolver.resolveVariables(context, desc, comp, value); // Next check to see if the value is/contains a JSF ValueExpression if (value instanceof ValueExpression) { if (comp != null) { comp.setValueExpression(key, (ValueExpression) value); } } else if ((value instanceof String) && isValueReference((String) value)) { ValueExpression ve = context.getApplication().getExpressionFactory(). createValueExpression( context.getELContext(), (String) value, Object.class); if (comp != null) { comp.setValueExpression(key, ve); } value = ve; } else if (comp != null) { // In JSF, you must directly modify the attribute Map Map attributes = comp.getAttributes(); if (value == null) { // Setting null, assume they want to remove the value try { attributes.remove(key); } catch (Exception ex) { // Switched from IAE to E b/c of MyFaces incompatibility // JSF is mesed up... it throws an exception if it has a // property descriptor and you call remove(...). It also // throws an exception if you attempt to call put w/ null // and there is no property descriptor. Either way you // MUST catch something and then handle the other case. try { attributes.put(key, (Object) null); } catch (Exception iae) { // Switched from IAE to E b/c of MyFaces incompatibility // We'll make this non-fatal, but log a message if (LogUtil.infoEnabled()) { LogUtil.info("JSFT0006", new Object[] { key, comp.getId(), comp.getClass().getName()}); if (LogUtil.fineEnabled()) { LogUtil.fine("Unable to set (" + key + ").", iae); } } } } } else { try { // Attempt to set the value as given... attributes.put(key, value); } catch (Exception ex) { // Switched from IAE to E b/c of MyFaces incompatibility // Ok, try a little harder... Class type = findPropertyType(comp, key); if (type != null) { try { attributes.put(key, TypeConverter.asType(type, value)); } catch (Exception ex2) { // Switched from IAE to E b/c of MyFaces incompatibility throw new IllegalArgumentException( "Failed to set property (" + key + ") with " + "value (" + value + "), which is of type (" + value.getClass().getName() + "). Expected " + "type (" + type.getName() + "). This " + "occured on the component named (" + comp.getId() + ") of type (" + comp.getClass().getName() + ").", ex2); } } else { throw new IllegalArgumentException( "Failed to set property (" + key + ") with value (" + value + "), which is of type (" + value.getClass().getName() + "). This occured " + "on the component named (" + comp.getId() + ") of type (" + comp.getClass().getName() + ").", ex); } } } } // Return the value (which may be a ValueExpression) return value; } /** *

This method attempts to resolve the expected type for the given * property key and UIComponent.

*/ private Class findPropertyType(UIComponent comp, String key) { // First check to see if we've done this before... Class compClass = comp.getClass(); String cacheKey = compClass.getName() + ';' + key; if (_typeCache.containsKey(cacheKey)) { // May return null if method previously executed unsuccessfully return _typeCache.get(cacheKey); } // Search a little... Class val = null; Method meth = null; String methodName = getGetterName(key); try { meth = compClass.getMethod(methodName); } catch (NoSuchMethodException ex) { // May fail if we have a boolean property that has an "is" getter. try { // Try again, replace "get" with "is" meth = compClass.getMethod( "is" + methodName.substring(3)); } catch (NoSuchMethodException ex2) { // Still not found, must not have getter / setter ex2.printStackTrace(); } } if (meth != null) { val = meth.getReturnType(); } else { Object obj = comp.getAttributes().get("key"); if (val != null) { val = obj.getClass(); } } // Save the value for future calls for the same information // NOTE: We do it this way to avoid modifying a shared Map Map newTypeCache = new HashMap(_typeCache); newTypeCache.put(cacheKey, val); _typeCache = newTypeCache; // Return the result return val; } /** *

This method converts the given name to a bean getter * method name. In other words, it capitalizes the first letter and * prepends "get".

*/ public String getGetterName(String name) { return "get" + ((char) (name.charAt(0) & 0xFFDF)) + name.substring(1); } /** *

This method will attempt to resolve the given EL string.

* * @param context The FacesContext. * @param elt The LayoutElement associated w/ the expression. * @param parent The parent UIComponent. This is used * because the current UIComponent is typically * unknown (or not even created yet). * @param value The String (or List / Array) to resolve. * * @return The evaluated value (may be null). */ public Object resolveValue(FacesContext context, LayoutElement elt, UIComponent parent, Object value) { // Invoke our own EL. This is needed b/c JSF's EL is designed for // Bean getters only. It does not get CONSTANTS or pull data from // other sources without adding a custom VariableResolver and/or // PropertyResolver. Eventually we may want to find a good way to // make this work as a regular ValueExpression expression... but for // now, we'll just resolve it this way. Object result = VariableResolver.resolveVariables( context, elt, parent, value); // Next check to see if the result contains a JSF ValueExpression if ((result != null) && (result instanceof String) && isValueReference((String) result)) { ELContext elctx = context.getELContext(); ValueExpression ve = context.getApplication().getExpressionFactory(). createValueExpression(elctx, (String) result, Object.class); result = ve.getValue(elctx); /* 1.1+ // JSF 1.1 VB: try { ValueBinding vb = context.getApplication().createValueBinding((String) result); result = vb.getValue(context); } catch (EvaluationException ex) { if (LogUtil.infoEnabled()) { LogUtil.info("JSFT0007", new Object[] {value}); } throw ex; } */ } // Return the result return result; } /** *

Returns true if this expression looks like an EL expression.

*/ public boolean isValueReference(String value) { if (value == null) { return false; } // FIXME: Consider adding logic to look for "matching" {}'s int start = value.indexOf("#{"); if ((start != -1) && (start < value.indexOf('}', start))) { return true; } return false; } // While this is static, the information seems reasonable to share across // applications as it is very unlikely to be different... leaving for now private static Map _typeCache = new HashMap(); /** *

This Map caches ComponentTypes by their factoryClass name.

*/ private Map _types = new HashMap(); /** *

Application scope key for an instance of * ComponentUtil.

*/ public static final String COMPONENT_UTIL_KEY = "_jsft_COMP_UTIL"; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy