/*
* 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.layout.descriptors;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import com.sun.jsftemplating.component.ChildManager;
import com.sun.jsftemplating.component.ComponentUtil;
import com.sun.jsftemplating.component.TemplateComponent;
import com.sun.jsftemplating.el.VariableResolver;
import com.sun.jsftemplating.layout.LayoutViewHandler;
import com.sun.jsftemplating.layout.ViewRootUtil;
import com.sun.jsftemplating.layout.descriptors.handler.Handler;
import com.sun.jsftemplating.layout.event.AfterCreateEvent;
import com.sun.jsftemplating.layout.event.AfterEncodeEvent;
import com.sun.jsftemplating.layout.event.BeforeCreateEvent;
import com.sun.jsftemplating.layout.event.BeforeEncodeEvent;
import com.sun.jsftemplating.util.LayoutElementUtil;
/**
* <p> This class defines a <code>LayoutComponent</code>. A
* <code>LayoutComponent</code> describes a <code>UIComponent</code> to be
* instantiated. The method {@link #getType()} provides a
* {@link ComponentType} descriptor that is capable of providing a
* {@link com.sun.jsftemplating.component.factory.ComponentFactory}
* to perform the actual instantiation. This class also stores properties
* and facets (children) to be set on a newly instantiated instance.</p>
*
* @author Ken Paulsen (ken.paulsen@sun.com)
*/
public class LayoutComponent extends LayoutElementBase implements LayoutElement {
private static final long serialVersionUID = 1L;
/**
* <p> Constructor.</p>
*/
public LayoutComponent(LayoutElement parent, String id, ComponentType type) {
super(parent, id);
_type = type;
}
/**
* <p> Accessor for type.</p>
*/
public ComponentType getType() {
return _type;
}
/**
* <p> Determines if this component should be created even if there is
* already an existing <code>UIComponent</code>. It will "overwrite"
* the existing component if this property is true.</p>
*/
public void setOverwrite(boolean value) {
_overwrite = value;
}
/**
* <p> Determines if this component should be created even if there is
* already an existing <code>UIComponent</code>. It will "overwrite"
* the existing component if this property is true.</p>
*/
public boolean isOverwrite() {
return _overwrite;
}
/**
* <p> This method adds an option to the LayoutComponent. Options may be
* useful in constructing the LayoutComponent.</p>
*
* @param name The name of the option
* @param value The value of the option (may be List or String)
*/
public void addOption(String name, Object value) {
_options.put(name, value);
}
/**
* <p> This method adds all the options in the given Map to the
* {@link LayoutComponent}. Options may be useful in constructing the
* {@link LayoutComponent}.</p>
*
* @param map The map of options to add.
*/
public void addOptions(Map<String, Object> map) {
_options.putAll(map);
}
/**
* <p> Accessor method for an option. This method does not evaluate
* expressions.</p>
*
* @param name The option name to retrieve.
*
* @return The option value (List or String), or null if not found.
*
* @see #getEvaluatedOption(FacesContext, String, UIComponent)
*/
public Object getOption(String name) {
return _options.get(name);
}
/**
* <p> Accessor method for an option. This method evaluates our own
* expressions (not JSF expressions).</p>
*
* @param ctx The <code>FacesContext</code>.
* @param name The option name to retrieve.
* @param component The <code>UIComponent</code> (may be null).
*
* @return The option value (List or String), or null if not found.
*
* @see #getOption(String)
*/
public Object getEvaluatedOption(FacesContext ctx, String name, UIComponent component) {
// Get the option value
Object value = getOption(name);
// 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 (such as session, request attributes, etc., etc.)
// Resolve our variables now because we cannot depend on the
// individual components to do this. We may want to find a way to
// make this work as a regular ValueExpression... but for
// now, we'll just resolve it here.
return VariableResolver.resolveVariables(ctx, this, component, value);
}
/**
* <p> This method returns true/false based on whether the given option
* name has been set.</p>
*
* @param name The option name to look for.
*
* @return true/false depending on whether the options exists.
*/
public boolean containsOption(String name) {
return _options.containsKey(name);
}
/**
* <p> This method sets the Map of options.</p>
*
* @param options <code>Map</code> of options.
*/
public void setOptions(Map<String, Object> options) {
_options = options;
}
/**
* <p> This method returns the options as a Map. This method does not
* evaluate expressions.</p>
*
* @return Map of options.
*/
public Map<String, Object> getOptions() {
return _options;
}
/**
* <p> This method is overriden so that the correct UIComponent can be
* passed into the events. This is important so that correct
* component is searched for "instance" handlers.</p>
*
* @param context The <code>FacesContext</code>.
* @param parent The <code>UIComponent</code>.
*/
public void encode(FacesContext context, UIComponent parent) throws IOException {
if (!this.getClass().getName().equals(CLASS_NAME)) {
// The sub-classes of this component shouldn't use this method,
// this is a hack to allow them to use LayoutElementBase.encode
super.encode(context, parent);
return;
}
// If overwrite...
if (isOverwrite()) {
// FIXME: shouldn't this do a replace, not a remove? Otherwise the order may change
String id = getId(context, parent);
if (parent.getFacets().remove(id) == null) {
UIComponent child = ComponentUtil.getInstance(context).findChild(parent, id, null);
if (child != null) {
// Not a facet, try child...
parent.getChildren().remove(child);
}
}
}
// Display this UIComponent
// First find the UIComponent
UIComponent childComponent = null;
if (parent instanceof ChildManager) {
// If we have a ChildManager, take advantage of it...
childComponent = ((ChildManager) parent).getChild(context, this);
} else {
// Use local util method for finding / creating child component...
childComponent = getChild(context, parent);
}
dispatchHandlers(context, BEFORE_ENCODE,
new BeforeEncodeEvent(childComponent));
// Add child components... (needs to be done here, LE's can't do it)
// Use check for instance of TC. If present we must instantiate its
// children as they were skipped when the tree was initially created.
if (parent instanceof TemplateComponent) {
// Only do this for TemplateRenderer use-cases (LayoutViewHandler
// does this for pages)
LayoutViewHandler.buildUIComponentTree(
context, childComponent, this);
}
// Render the child UIComponent
encodeChild(context, childComponent);
// Invoke "after" handlers
dispatchHandlers(context, AFTER_ENCODE,
new AfterEncodeEvent(childComponent));
}
/**
* <p> Although this method is part of the interface, it is not used b/c
* I overrode the encode() method which calls this method. This
* method does nothing except satisfy the compiler.</p>
*/
public boolean encodeThis(FacesContext context, UIComponent parent) throws IOException {
return false;
}
/**
* <p> This method will find or create a <code>UIComponent</code> as
* described by this <code>LayoutComponent</code> descriptor. If the
* component already exists as a child or facet, it will be returned.
* If it creates a new <code>UIComponent</code>, it will typically be
* added to the given parent <code>UIComponent</code> as a facet (this
* actually depends on the factory that instantiates the
* <code>UIComponent</code>).</p>
*
* @param context The <code>FacesContext</code>
* @param parent The <code>UIComponent</code> to serve as the parent to
* search and to store the new <code>UIComponent</code>.
*
* @return The <code>UIComponent</code> requested (found or newly created)
*/
public UIComponent getChild(FacesContext context, UIComponent parent) {
UIComponent childComponent = null;
// First pull off the id from the descriptor
String id = this.getId(context, parent);
// We have an id, use it to search for an already-created child
ComponentUtil compUtil = ComponentUtil.getInstance(context);
childComponent = compUtil.findChild(parent, id, id);
if (childComponent != null) {
return childComponent;
}
// Invoke "beforeCreate" handlers
this.beforeCreate(context, parent);
// Create UIComponent
childComponent = compUtil.createChildComponent(context, this, parent);
// Invoke "afterCreate" handlers
this.afterCreate(context, childComponent);
// Return the newly created UIComponent
return childComponent;
}
/**
* <p> This method retrieves the Handlers for the requested type. But
* also includes any handlers that are associated with the instance
* (i.e. the UIComponent).</p>
*
* @param type The type of <code>Handler</code>s to retrieve.
* @param comp The associated <code>UIComponent</code> (or null).
*
* @return A List of Handlers.
*/
public List<Handler> getHandlers(String type, UIComponent comp) {
// 1st get list of handlers for definition of this LayoutElement
List<Handler> handlers = null;
// Now check to see if there are any on the UIComponent
if (comp != null) {
List<Handler> instHandlers =
(List<Handler>) comp.getAttributes().get(type);
if ((instHandlers != null) && (instHandlers.size() > 0)) {
// NOTE: Copy b/c this is <i>instance</i> + static
// Add the UIComponent instance handlers
handlers = new ArrayList<Handler>(instHandlers);
List<Handler> defHandlers = getHandlers(type);
if (defHandlers != null) {
// Add the LayoutElement "definition" handlers, if any
handlers.addAll(getHandlers(type));
}
}
}
if (handlers == null) {
handlers = getHandlers(type);
}
return handlers;
}
/**
* <p> This method is invoked before the Component described by this
* LayoutComponent is created. This allows handlers registered for
* "beforeCreate" functionality to be invoked.</p>
*
* @param context The FacesContext
*
* @return The result of invoking the handlers (null by default)
*/
public Object beforeCreate(FacesContext context, UIComponent parent) {
// Invoke "beforeCreate" handlers
return dispatchHandlers(
context, BEFORE_CREATE, new BeforeCreateEvent(parent));
}
/**
* <p> This method is invoked after the Component described by this
* LayoutComponent is created. This allows handlers registered for
* "afterCreate" functionality to be invoked.</p>
*
* @param context The FacesContext
*
* @return The result of invoking the handlers (null by default)
*/
public Object afterCreate(FacesContext context, UIComponent component) {
// Invoke "afterCreate" handlers
return dispatchHandlers(
context, AFTER_CREATE, new AfterCreateEvent(component));
}
/**
* <p> This method returns true if the child should be added to the parent
* component as a facet. Otherwise, it returns false indicating that
* it should exist as a real child.</p>
*
* <p> This value is calculated every time this call is made to allow for
* the context in which the LayoutComponent exists to determine its
* value. If a {@link LayoutFacet} exists as a parent
* {@link LayoutElement}, or a <code>UIViewRoot</code> or
* {@link TemplateComponent} exists as the immediate parent, it will
* return the facet name that should be used. Otherwise, it will
* return <code>null</code>.</p>
*
* @param parent This is the parent UIComponent.
*
* @return The facet name if the UIComponent should be added as a facet.
*/
public String getFacetName(UIComponent parent) {
String name = null;
// First check to see if this LC specifies a different facet name...
name = (String) getOption(FACET_NAME);
if ((name != null) && name.equals(getUnevaluatedId())) {
// No special facet name supplied, don't assume this is a facet yet
name = null;
}
// Next check to see if we are inside a LayoutFacet
if (name == null) {
LayoutElement parentElt = getParent();
while (parentElt != null) {
if (parentElt instanceof LayoutFacet) {
// Inside a LayoutFacet, use its name... only if this facet
// is a child of a LayoutComponent (otherwise, it is a
// layout facet used for layout, not for defining a facet
// of a UIComponent)
if (LayoutElementUtil.isLayoutComponentChild(parentElt)) {
name = parentElt.getUnevaluatedId();
} else {
name = getUnevaluatedId();
}
if (name == null) {
name = "_noname";
}
break;
}
if (parentElt instanceof LayoutComponent) {
// No need to process further, this is not a facet child
return null;
}
parentElt = parentElt.getParent();
}
}
// If not found yet, check to see if we're at the top...
if (name == null) {
if (parent instanceof TemplateComponent) {
// We don't know if we are adding a child of a
// TemplateComponent from a page, or if the TemplateComponent
// itself has a child... if the TemplateComponent is driving
// the rendering process, then we want this to be a facet. If
// the page is adding a child to a TemplateComponent, we do
// not want this to be a facet.
// Look to see if the parent LayoutDefinition == the current
// LayoutDefinition. If so, we're "inside" a
// TemplateComponent, not a page.
FacesContext ctx = FacesContext.getCurrentInstance();
if (((TemplateComponent) parent).getLayoutDefinition(ctx)
== getLayoutDefinition()) {
name = getUnevaluatedId();
}
} else if ((parent instanceof UIViewRoot) && (ViewRootUtil.
getLayoutDefinition((UIViewRoot) parent) != null)) {
// NOTE: Only set the name if its a JSFT ViewRoot
name = getUnevaluatedId();
}
}
// Return the result
return name;
}
/**
* <p> This method returns a flag that indicates if this
* <code>LayoutComponent</code> is nested (directly or indirectly)
* inside another <code>LayoutComponent</code>. This flag is used
* for such purposes as deciding if "instance" handlers are
* appropriate.</p>
*
* @return <code>true</code> if component is nested.
*/
public boolean isNested() {
return _nested;
}
/**
* <p> This method sets the nested flag for this
* <code>LayoutComponent</code>. This method is commonly only called
* from code that constructs the tree of {@link LayoutElement}
* components.</p>
*
* @param value The boolean value.
*/
public void setNested(boolean value) {
_nested = value;
}
/**
* <p> Component type</p>
*/
private ComponentType _type = null;
/**
* <p> Determines if this component should be created even if there is
* already an existing <code>UIComponent</code>. It will "overwrite"
* the existing component if this property is true. Usually only
* applies when this is used within the context of a
* <code>Renderer</code>.</p>
*/
private boolean _overwrite = false;
/**
* <p> Map of options.</p>
*/
private Map<String, Object> _options = new HashMap<String, Object>();
/**
* <p> This is the "type" for handlers to be invoked to handle
* "afterCreate" functionality for this element.</p>
*/
public static final String AFTER_CREATE = "afterCreate";
/**
* <p> This is the "type" for handlers to be invoked to handle
* "beforeCreate" functionality for this element.</p>
*/
public static final String BEFORE_CREATE = "beforeCreate";
/**
* <p> This is the "type" for handlers to be invoked to handle
* "command" functionality for this element.</p>
*/
public static final String COMMAND = "command";
/**
* <p> This defines the property key for specifying the facet name in
* which the component should be stored under in its parent
* UIComponent.</p>
*/
public static final String FACET_NAME = "_facetName";
/**
* <p> This defines the attribute name on the tag that flags that
* duplicate ID's should not be checked for this component. If
* specified on a component, the check will be skipped, regardless
* of the value of the attribute. The attribute name is
* ("skipIdCheck").</p>
*/
public static final String SKIP_ID_CHECK= "skipIdCheck";
public static final String CLASS_NAME = LayoutComponent.class.getName();
/**
* <p> The value of the nested property.</p>
*/
private boolean _nested = false;
}