/* * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.arsdigita.bebop; import com.arsdigita.bebop.event.ActionEvent; import com.arsdigita.bebop.event.ActionListener; import com.arsdigita.bebop.event.RequestEvent; import com.arsdigita.bebop.event.RequestListener; import com.arsdigita.bebop.parameters.BitSetParameter; import com.arsdigita.bebop.parameters.ParameterModel; import com.arsdigita.bebop.parameters.StringParameter; import com.arsdigita.bebop.util.Traversal; import com.arsdigita.developersupport.DeveloperSupport; import com.arsdigita.kernel.Kernel; import com.arsdigita.profiler.Profiler; import com.arsdigita.util.Assert; import com.arsdigita.xml.Document; import com.arsdigita.xml.Element; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.ParserConfigurationException; import org.apache.log4j.Logger; /** *
The top-level container for all Bebop components and containers.
* *
* A typical Page may be created as follows:
*
* * * @author David Lutterkort * @author Stanislav Freidin * @author Uday Mathur * * @version $Id: Page.java 1270 2006-07-18 13:34:55Z cgyg9330 $ */ public class Page extends BlockStylable implements Container { /** Class specific logger instance. */ private static final Logger s_log = Logger.getLogger(Page.class); /** The delimiter character for components naming */ private static final String DELIMITER = "."; /** * The prefix that gets prepended to all state variables. Components must * not use variables starting with this prefix. This guarantees that the * page state and variables individual components wish to pass do not * interfere with each other. */ private static final String COMPONENT_PREFIX = "bbp" + DELIMITER; private static final String INTERNAL = COMPONENT_PREFIX; /** * The name of the special parameter that indicates which component has * been selected. */ static final String SELECTED = INTERNAL + "s"; static final String CONTROL_EVENT = INTERNAL + "e"; static final String CONTROL_VALUE = INTERNAL + "v"; static final Collection CONTROL_EVENT_KEYS; static { s_log.debug("Static initalizer is starting..."); CONTROL_EVENT_KEYS = new ArrayList(3); CONTROL_EVENT_KEYS.add(SELECTED); CONTROL_EVENT_KEYS.add(CONTROL_EVENT); CONTROL_EVENT_KEYS.add(CONTROL_VALUE); s_log.debug("Static initalizer finished."); } /** * The name of the request parameter used for the visibility state of * components stored in m_invisible. */ static final String INVISIBLE = INTERNAL + "i"; /** * Map of stateful components (id --> Component) * SortedMap used because component based hash for page is based on concatenation of * component ids, and so need to guarantee that they are returned in the same order * for the same page - cg. */ private SortedMap m_componentMap; private List m_components; /** * Map of component -> owned parameter collection */ private Map m_componentParameterMap = new HashMap(); private FormModel m_stateModel; /** ** Page p = new Page("Hello World"); * p.add(new Label("Hello World"); * p.lock(); *
Container that renders this Page.
*/
protected Container m_panel;
private List m_actionListeners;
private List m_requestListeners;
/**
* The title of the page to be added in the head of HTML output. The
* title is wrapped in a Label to allow developers to add
* PrintListeners to dynamically change the value of the title.
*/
private Label m_title;
/**
* Stores the actual title for the current request. The title may be
* generated with a PrintListener of the m_title Label.
*/
private RequestLocal m_currentTitle;
/**
* A list of all the client-side stylesheets. The elements of the list
* are of type Page.Stylesheet, defined at the end of this file.
*/
private List m_clientStylesheets;
private StringParameter m_selected;
private StringParameter m_controlEvent;
private StringParameter m_controlValue;
/**
* The default (initial) visibility of components. The encoding is
* identical to that for PageState.m_invisible.
*
* This variable is package-friendly since it needs to be accessed by
* PageState.
*/
protected BitSet m_invisible;
/**
* The PageErrorDisplay component that will display page state validation
* errors on this page
*/
private Component m_errorDisplay;
/**
* Indicates whether finish() has been called on this Page.
*/
private boolean m_finished = false;
/**
* indicates whether pageState.stateAsURL() should export the
* entire state for this page, or whether it should only export
* the control event as a URL and use the HttpSession for the
* rest of the page state.
*/
private boolean m_useHttpSession = false;
/**
* Returns true if this page should export state through
* the HttpSession instead of the URL query string.
* If this returns true, then PageState.stateAsURL()
* will only export the control event as
* a URL query string. If this returns false,
* then stateAsURL() will export the entire page state.
*
* @see PageState#stateAsURL
*
* @return true if this page should export state through
* the HttpSession; false if it should export using
* the URL query string.
*/
public boolean isUsingHttpSession() {
return m_useHttpSession;
}
/**
* Indicates to this page whether it should export its entire state
* to subsequent requests through the URL query string, or if it should
* use the HttpSession instead and only use the URL query string for
* the control event.
*
* @see PageState#stateAsURL
*
* @param b true if PageState.stateAsURL()
* will export only the control event as a URL query string.
* false if stateAsURL() will export the entire page state.
*/
public void setUsingHttpSession(boolean b) {
m_useHttpSession = b;
}
/**
* Creates an empty page with the specified title and
* panel.
*
* @param title title for this page
*
* @param panel container for this page
*/
public Page(String title, Container panel) {
this(new Label(title), panel);
}
/**
* Creates an empty page with the specified title and
* panel.
*
* @param title title for this page
*
* @param panel container for this page
*/
public Page(Label title, Container panel) {
super();
m_actionListeners = new LinkedList();
m_requestListeners = new LinkedList();
m_panel = panel;
m_clientStylesheets = new ArrayList();
m_components = new ArrayList();
m_componentMap = new TreeMap();
setErrorDisplay(new PageErrorDisplay());
setTitle(title);
// Initialize the RequestLocal where the title for the current
// request will be kept
m_currentTitle = new RequestLocal() {
@Override
protected Object initialValue(PageState state) {
return m_title.firePrintEvent(state);
}
};
// Initialize the set of state parameters to hold
// the ones necessary for keeping track of the selected component and
// the name and value of a 'control event'
m_selected = new StringParameter(SELECTED);
m_controlEvent = new StringParameter(CONTROL_EVENT);
m_controlValue = new StringParameter(CONTROL_VALUE);
m_stateModel = new FormModel("stateModel", true);
m_stateModel.addFormParam(m_selected);
m_stateModel.addFormParam(m_controlEvent);
m_stateModel.addFormParam(m_controlValue);
// Set up the visibility tracking parameters
m_invisible = new BitSet(32);
BitSetParameter p = new BitSetParameter(INVISIBLE,
BitSetParameter.ENCODE_DGAP);
m_stateModel.addFormParam(p);
}
/**
* Creates an empty page with default title and implicit BoxPanel
* container.
*/
public Page() {
this("");
}
/**
* Creates an empty page with the specified title and implicit
* BoxPanel container.
*
* @param title title for this page
*/
public Page(Label title) {
this(title, new BoxPanel());
BoxPanel bp = (BoxPanel) m_panel;
bp.setWidth("100%");
}
/**
* Creates an empty page with the specified title and implicit
* BoxPanel container.
*
* @param title title for this page
*/
public Page(String title) {
this(new Label(title));
}
/**
* Adds a component to this container.
*
* @param c component to add to this container
*/
public void add(Component c) {
m_panel.add(c);
}
/**
* Adds a component with the specified layout constraints to this
* container. Layout constraints are defined in each layout container as
* static ints. To specify multiple constraints, use bitwise OR.
*
* @param c component to add to this container
*
* @param constraints layout constraints (a
* bitwise OR of static ints in the particular layout)
*/
public void add(Component c, int constraints) {
m_panel.add(c, constraints);
}
/**
* Returns true if this list contains the specified element.
* More formally, returns true if and only if this list
* contains at least
* one element e such that (o==null ? e==null : o.equals(e)).
*
* This method returns true only if the component has been
* directly added to this container. If this container contains another
* container that contains this component, this method returns
* false.
*
* @param o element whose presence in this container is to be tested
*
* @return true if this Container contains the specified
* component directly; false otherwise.
*/
public boolean contains(Object o) {
return m_panel.contains(o);
}
/**
* Returns the
* component at the specified position. Each call to the add method
* increments the index. Since the user has no control over the index
* of added components (other than counting each call to add), this
* method should be used in conjunction with indexOf.
*
* @param index the index of the item to be retrieved from this
* Container
*
* @return the component at the specified position in this container.
*/
public Component get(int index) {
return m_panel.get(index);
}
/**
* Gets the index of a
* component.
*
* @param c component to search for
*
* @return the index in this list of the first occurrence of the
* specified element, or -1 if this list does not contain this element.
*
* @pre c != null
* @post contains(c) implies (return >= 0) && (return < size())
* @post !contains(c) implies return == -1 */
public int indexOf(Component c) {
return m_panel.indexOf(c);
}
/**
* Returns true if the container contains no components.
*
* @return true if this container contains no
* components; false otherwise.
*/
public boolean isEmpty() {
return m_panel.isEmpty();
}
/**
* Returns the number of elements in this container. This does not
* recursively count the components that are indirectly contained
* in this container.
*
* @return the number of components directly in this container.
*/
public int size() {
return m_panel.size();
}
@Override
public Iterator children() {
return Collections.singletonList(m_panel).iterator();
}
/**
* Returns the panel that the Page uses for rendering its
* components.
* @return the panel.
*/
public final Container getPanel() {
return m_panel;
}
/**
* Set the Container used for rendering components on this page. Caution
* should be used with this function, as the existing container is simply
* overwritten.
*
* @author Matthew Booth (mbooth@redhat.com)
*/
public void setPanel(Container c) {
m_panel = c;
}
/**
*
* Retrieves the title of this page. *
* * @return the static title of this page. */ public final Label getTitle() { return m_title; } /** * Retrieves the title of this page as a Bebop label component. * * @param state the state of the current request * @return the title of the page for the current request. */ public final Label getTitle(PageState state) { return (Label) m_currentTitle.get(state); } /** * Sets the title for this page from the passed in string. * * @param title title for this page */ public void setTitle(String title) { Assert.isUnlocked(this); setTitle(new Label(title)); } /** * Set the title for this page from the passed in label. * * @param title title for this page */ public void setTitle(Label title) { Assert.isUnlocked(this); m_title = title; } /** * Sets the {@link Component} that will display the validation errors * in the current {@link PageState}. Any validation error in the *PageState will cause the Page to completely
* ignore all other components and render only the error display component.
*
* By default, a {@link PageErrorDisplay} component is used to display the
* validation errors.
*
* @param c the component that will display the validation errors
* in the current PageState
*/
public final void setErrorDisplay(Component c) {
Assert.isUnlocked(this);
m_errorDisplay = c;
}
/**
* Gets the {@link Component} that will display the validation errors
* in the current {@link PageState}. Any validation error in the
* PageState will cause the Page to completely
* ignore all other components and render only the error display component.
*
* By default, a {@link PageErrorDisplay} component is used to display the
* validation errors.
*
* @return the component that will display the validation errors
* in the current PageState.
*/
public final Component getErrorDisplay() {
return m_errorDisplay;
}
/**
* Adds a client-side stylesheet that should be used in HTML
* output. Arbitrarily many client-side stylesheets can be added with
* this method. To use a CSS stylesheet, call something like
* setStyleSheet("style.css", "text/css").
*
*
These values will ultimately wind up in a <link> * tag in the head of the HTML page. * *
Note that the stylesheet set with this call has nothing to do with * the XSLT stylesheet (transformer) that is applied to the XML generated * from this page! * * @param styleSheetURI the location of the stylesheet * @param mimeType the MIME type of the stylesheet, usually * text/css * @pre ! isLocked() */ public void addClientStylesheet(String styleSheetURI, String mimeType) { m_clientStylesheets.add(new Stylesheet(styleSheetURI, mimeType)); } /** * Adds a global state parameter to this page. Global parameters are * values that need to be preserved between requests, but that have no * special connection to any of the components on the page. For a page * that displays details about an item, a global parameter would be * used to identify the item. * *
If the parameter was previously added as a component state * parameter, its name is unmangled and stays unmangled. * * @param p the global parameter to add * * @see #addComponentStateParam * * @pre ! isLocked() * @pre parameter != null */ public void addGlobalStateParam(ParameterModel p) { Assert.isUnlocked(this); p.setName(unmangle(p.getName())); m_stateModel.addFormParam(p); } /** * Constructs the top nodes of the DOM or JDOM tree. Used by * generateXML(PageState, Document) below. *
Generates DOM fragment: *
* <bebop:page>
* <bebop:title> ... value set with setTitle ... </bebop:title>
* <bebop:stylesheet href='styleSheetURI' type='mimeType'>
* ... page content gnerated by children ...
* </bebop:page>
* The content of the <title> element can be set by
* calling {@link #setTitle setTitle}. The <stylesheet>
* element will only be present if a stylesheet has been set with {@link
* #setStyleSheet setStyleSheet}.
*
* @param ps the page state for the current page
* @param parent the DOM node for the whole Document
*
* @pre isLocked()
*/
protected Element generateXMLHelper(PageState ps, Document parent) {
Assert.isLocked(this);
Element page = parent.createRootElement("bebop:page", BEBOP_XML_NS);
exportAttributes(page);
Element title = page.newChildElement("bebop:title", BEBOP_XML_NS);
title.setText(getTitle(ps).getLabel(ps));
for (Iterator i = m_clientStylesheets.iterator(); i.hasNext();) {
((Stylesheet) i.next()).generateXML(page);
}
return page;
}
/**
* Constructs a DOM or JDOM tree with all components on the page. The tree
* represents the page that
* results from the {@link javax.servlet.http.HttpServletRequest} kept in
* the state.
* @param state the page state produced by {@link #process}
* @param parent the DOM node for the whole Document
* @see #process process
* @pre isLocked()
* @pre state != null
*/
public void generateXML(PageState state, Document parent) {
// always export page state as HTTP session
if (m_useHttpSession) {
state.stateAsHttpSession();
}
Element page = generateXMLHelper(state, parent);
// If the page state has errors, ignore all the components and
// render only the error display component
if (state.getErrors().hasNext()) {
m_errorDisplay.generateXML(state, page);
} else {
m_panel.generateXML(state, page);
}
if (Kernel.getConfig().isDebugEnabled() && debugStructure(state.
getRequest())) {
Element structure =
page.newChildElement("bebop:structure", BEBOP_XML_NS);
showStructure(state, structure);
}
}
private static boolean debugStructure(HttpServletRequest req) {
return "transform".equals(req.getParameter("debug"));
}
/**
* Do nothing. Top-level add nodes is meaningless.
*/
public void generateXML(PageState state, Element elt) {
return;
}
/**
* Creates a PageState object and processes it by calling the
* respond method on the
* selected component. Processes a request by notifying the component
* from which the process originated and {@link #fireActionEvent
* broadcasts} an {@link ActionEvent} to all the listeners that
* registered with {@link #addActionListener addActionListener}.
*
* @see #generateXML(PageState,Document) generateXML
* @pre isLocked()
* @pre request != null
* @pre response != null
*/
public PageState process(HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
PageState result = new PageState(this, request, response);
try {
DeveloperSupport.startStage("Bebop Page Process");
process(result);
} finally {
DeveloperSupport.endStage("Bebop Page Process");
}
return result;
}
/**
* Processes the supplied PageState object according to this
* PageModel. Calls the respond method on the selected Bebop
* component.
*/
public void process(PageState state) throws ServletException {
Assert.isLocked(this);
try {
DeveloperSupport.startStage("Bebop Request Event");
fireRequestEvent(state);
} finally {
DeveloperSupport.endStage("Bebop Request Event");
}
// Validate the state; any errors in the state will be displayed
// by generateXML
state.forceValidate();
if (state.isValid()) {
try {
DeveloperSupport.startStage("Bebop Respond");
state.respond();
} finally {
DeveloperSupport.endStage("Bebop Respond");
}
try {
DeveloperSupport.startStage("Bebop Action Event");
fireActionEvent(state);
} finally {
DeveloperSupport.endStage("Bebop Action Event");
}
}
}
// /**
// * Does all the servicing of a request except output generation.
// * This includes most of the common duties of buildDocument and print.
// *
// * @deprecated Use {@link
// * #process(HttpServletRequest,HttpServletResponse)} instead.
// */
// protected PageState prepare(HttpServletRequest req, HttpServletResponse res)
// throws ServletException {
// Assert.isLocked(this);
// PageState state = process(req, res);
// return state;
// }
/**
* Builds a DOM Document from the current request state by
* doing a depth-first tree walk on the current set of components
* in this Page, calling generateXML on each. Does NOT do the rendering.
* If the HTTP response has already been committed, does not
* build the XML document.
*
* @return a DOM ready for rendering, or null if the response has
* already been committed.
* @post res.isCommitted() == (return == null)
*/
public Document buildDocument(HttpServletRequest req,
HttpServletResponse res)
throws ServletException {
try {
Document doc = new Document();
PageState state = process(req, res);
// only generate XML document if the response is not already
// committed
if (!res.isCommitted()) {
try {
DeveloperSupport.startStage("Bebop XML");
Profiler.startOp("XML");
generateXML(state, doc);
} finally {
DeveloperSupport.endStage("Bebop XML");
Profiler.stopOp("XML");
}
return doc;
} else {
return null;
}
} catch (ParserConfigurationException e) {
throw new ServletException(e);
}
}
/**
* Finishes building the page. The tree of components is traversed and each
* component is told to add its state parameters to the page's state
* model.
*
* @pre ! isLocked()
*/
private void finish() {
if (!m_finished) {
Assert.isUnlocked(this);
Traversal componentRegistrar = new Traversal() {
protected void act(Component c) {
addComponent(c);
c.register(Page.this);
}
};
if (m_panel == null) {
s_log.warn("m_panel is null");
}
componentRegistrar.preorder(m_panel);
if (m_errorDisplay != null) {
addComponent(m_errorDisplay);
m_errorDisplay.register(Page.this);
}
m_finished = true;
}
}
/**
* Locks the page and all its components against further modifications.
*
* Locking a page helps in finding mistakes that result from modifying a * page's structure.
*/ public void lock() { if (!m_finished) { finish(); } m_stateModel.lock(); Traversal componentLocker = new Traversal() { protected void act(Component c) { c.lock(); } }; componentLocker.preorder(m_panel); super.lock(); } public void respond(PageState state) throws javax.servlet.ServletException { throw new UnsupportedOperationException(); } /** * Registers a listener that is notified whenever a request to this page * is made, after the selected component has had a chance to respond. * @pre l != null * @pre ! isLocked() */ public void addActionListener(ActionListener l) { Assert.isUnlocked(this); m_actionListeners.add(l); } /** * Remove a previously registered action listener. * @pre l != null * @pre ! isLocked() */ public void removeActionListener(ActionListener l) { Assert.isUnlocked(this); m_actionListeners.remove(l); } /** * Registers a listener that is notified whenever a request to this page * is made, before the selected component has had a chance to respond. * @pre l != null * @pre ! isLocked() */ public void addRequestListener(RequestListener l) { Assert.isUnlocked(this); m_requestListeners.add(l); } /** * Removes a previously registered request listener. * @param 1 the listener to remove * @pre l != null * @pre ! isLocked() */ public void removeRequestListener(RequestListener l) { Assert.isUnlocked(this); m_requestListeners.remove(l); } /** * Broadcasts an {@link ActionEvent} to all registered listeners. The * source of the event is this page, and the state recorded in the event * is the one resulting from processing the current request. * @param the state for this event * * @pre state != null */ protected void fireActionEvent(PageState state) { ActionEvent e = null; for (Iterator i = m_actionListeners.iterator(); i.hasNext();) { if (e == null) { e = new ActionEvent(this, state); } final ActionListener listener = (ActionListener) i.next(); if (s_log.isDebugEnabled()) { s_log.debug("Firing action listener " + listener); } listener.actionPerformed(e); } } /** * Broadcasts a {@link RequestEvent} to all registered listeners. The * source of the event is this page, and the state recorded in the event * is the one resulting from processing the current request. * @param the state for this event * * @pre state != null */ protected void fireRequestEvent(PageState state) { RequestEvent e = null; for (Iterator i = m_requestListeners.iterator(); i.hasNext();) { if (e == null) { e = new RequestEvent(this, state); } final RequestListener listener = (RequestListener) i.next(); if (s_log.isDebugEnabled()) { s_log.debug("Firing request listener " + listener); } listener.pageRequested(e); } } // Client-side stylesheet storage private class Stylesheet { String m_URI; String m_type; public Stylesheet(String stylesheetURI, String mimeType) { m_URI = stylesheetURI; m_type = mimeType; } public void generateXML(Element parent) { Element style = parent.newChildElement("bebop:stylesheet", BEBOP_XML_NS); style.addAttribute("href", m_URI); if (m_type != null) { style.addAttribute("type", m_type); } } } /** * Adds a component to the page model. * * @deprecated This method will become private in ACS 5.0. */ public void addComponent(Component c) { Assert.isUnlocked(this); if (!stateContains(c)) { if (c == null) { s_log.error("c is null"); } /*else { s_log.error("c: " + c.toString()); }*/ String key = c.getKey(); if (key == null) { key = Integer.toString(m_components.size()); } if (m_componentMap.get(key) != null) { throw new IllegalArgumentException( "Component key must not be duplicated. The key " + key + " is shared by more than one component."); } m_componentMap.put(key, c); m_components.add(c); } } /** * Registers a state parameter for a component. It is permissible to * register the same state parameter several times, from the same or * different components. The name of the parameter will be changed * to ensure that it won't clash with any other component's parameter. * If the parameter is added more than once, the name is only changed * the first time it is added. * * @param c the component to register the parameter for * @param p the state parameter to register * * @see #addGlobalStateParam * * @pre stateContains(c) * @pre ! isLocked() * @pre p != null */ public void addComponentStateParam(Component c, ParameterModel p) { Assert.isUnlocked(this); if (!stateContains(c)) { throw new IllegalArgumentException( "Component must be registered in Page"); } if (!m_stateModel.containsFormParam(p)) { String name = parameterName(c, p.getName()); s_log.debug(String.format("Setting name of parameter to add to '%s'", name)); p.setName(name); m_stateModel.addFormParam(p); Collection params = (Collection) m_componentParameterMap.get(c); if (params == null) { params = new ArrayList(); m_componentParameterMap.put(c, params); } params.add(p); } } /** *Get the parameters registered for a given component.
*/ public Collection getComponentParameters(Component c) { return (Collection) m_componentParameterMap.get(c); } /** * Gets the state index of a component. This is the number assigned * to the component in the register traveral * * @param c the component to search for * * @return the index in this list of the first occurrence of the * specified element, or -1 if this list does not contain this element. * * @pre c != null * @post contains(c) implies (return >= 0) && (return < size()) * @post !contains(c) implies return == -1 */ public int stateIndex(Component c) { return m_components.indexOf(c); } /** * The number of components in the page model. * * @post return >= 0 */ public int stateSize() { return m_components.size(); } /** * Checks whether this component is already in the page model. * * @pre c != null */ public boolean stateContains(Component c) { return m_components.contains(c); } /** * Gets a page component by index. * * @pre (i >= 0) && (i < size()) * @post return != null */ public Component getComponent(int i) { return (Component) m_components.get(i); } /** * Gets a page component by key. * * @pre s != null */ Component getComponent(String s) { return (Component) m_componentMap.get(s); } /** * Gets the form model that contains the parameters for the page's * state. */ public final FormModel getStateModel() { return m_stateModel; } /** * Gets the ParameterModels held in this Page. * * @return an iterator of ParameterModels. */ public Iterator getParameters() { return m_stateModel.getParameters(); } /** * Checks whether the specified component is visible by default on * the page. * * @param c a component contained in the page * @returntrue if the component is visible by default;
* false otherwise.
* @see #setVisibleDefault setVisibleDefault
* @see Component#setVisible Component.setVisible
*/
public boolean isVisibleDefault(Component c) {
Assert.isTrue(stateContains(c));
return !m_invisible.get(stateIndex(c));
}
/**
* Sets whether the specified component is visible by default. The
* default visibility is used when a page is displayed for the first
* time and on subsequent requests until the visibility of a component
* is changed explicitly with {@link Component#setVisible
* Component.setVisible}.
*
* When a component is first added to a page, it is visible.
*
* @param c a component whose visibility is to be set
* @param v true if the component is visible;
* false otherwise.
* @see Component#setVisible Component.setVisible
* @see Component#register Component.register
*/
public void setVisibleDefault(Component c, boolean v) {
Assert.isUnlocked(this);
addComponent(c);
int i = stateIndex(c);
if (v) {
m_invisible.clear(i);
} else {
m_invisible.set(i);
}
}
/**
* The global name of the parameter name in the component
* c.
*/
public String parameterName(Component c, String name) {
if (c == null || !stateContains(c)) {
return name;
}
return componentPrefix(c) + name;
}
/**
* The global name of the parameter name.
*/
public String parameterName(String name) {
return parameterName(null, name);
}
void reset(final PageState ps, Component cmpnt) {
Traversal resetter = new Traversal() {
protected void act(Component c) {
Collection cp = getComponentParameters(c);
if (cp != null) {
Iterator iter = cp.iterator();
while (iter.hasNext()) {
ParameterModel p = (ParameterModel) iter.next();
ps.setValue(p, null);
}
}
c.setVisible(ps, isVisibleDefault(c));
}
};
resetter.preorder(cmpnt);
}
/**
* Return the prefix that is prepended to each component's state
* parameters to keep them unique.
*/
private final String componentPrefix(Component c) {
if (c == null) {
return COMPONENT_PREFIX + "g" + DELIMITER;
} else {
// WRS: preferentially use key if it exists
String key = c.getKey();
if (key == null) {
if (stateContains(c)) {
key = String.valueOf(stateIndex(c));
} else {
throw new IllegalArgumentException(
"Cannot generate prefix for component: key is null "
+ "and component " + c.toString() + "/" + c.getKey()
+ " did not register with page.");
}
}
return COMPONENT_PREFIX + key + DELIMITER;
}
}
/**
* Undo the name change that {@link #parameterName} does.
*
* @param name a possibly mangled name
* @return the unmangled name.
*/
private static final String unmangle(String name) {
if (!name.startsWith(COMPONENT_PREFIX)) {
return name;
}
// Find the second occurence of delimiter
int prefix = name.indexOf(DELIMITER, name.indexOf(DELIMITER) + 1);
if (prefix >= 0 && prefix < name.length()) {
return name.substring(prefix + 1);
}
return name;
}
// Procs for debugging output
private static String NAME = "name";
/**
* Produces an XML fragment that captures the layout of this page.
*/
private void showStructure(PageState s, Element root) {
final HttpServletRequest req = s.getRequest();
Element state = root.newChildElement("bebop:state", BEBOP_XML_NS);
// Selected component
String sel = req.getParameter(m_selected.getName());
Element selected = state.newChildElement("bebop:selected", BEBOP_XML_NS);
selected.addAttribute(NAME, m_selected.getName());
selected.setText(sel);
// Control event
Element eventName =
state.newChildElement("bebop:eventName", BEBOP_XML_NS);
eventName.addAttribute(NAME, m_controlEvent.getName());
eventName.setText(req.getParameter(m_controlEvent.getName()));
Element eventValue =
state.newChildElement("bebop:eventValue", BEBOP_XML_NS);
eventValue.addAttribute(NAME, m_controlValue.getName());
eventValue.setText(req.getParameter(m_controlValue.getName()));
// Global parameters
Element globalState =
root.newChildElement("bebop:params", BEBOP_XML_NS);
for (Iterator ii = getStateModel().getParameters(); ii.hasNext();) {
ParameterModel p = (ParameterModel) ii.next();
if (!p.getName().startsWith(COMPONENT_PREFIX)) {
Element param =
globalState.newChildElement("bebop:param", BEBOP_XML_NS);
param.addAttribute(NAME, p.getName());
param.setText(String.valueOf(s.getValue(p)));
}
}
showVisibility(s, this, root);
}
/**
* @see showStructure(PageState, Element)
*/
private void showVisibility(PageState s, Component c, Element parent) {
HttpServletRequest req = s.getRequest();
Element cmp = parent.newChildElement("bebop:component", BEBOP_XML_NS);
cmp.addAttribute(NAME, getDebugLabel(c));
cmp.addAttribute("idx", String.valueOf(stateIndex(c)));
cmp.addAttribute("isVisible", (s.isVisible(c) ? "yes" : "no"));
cmp.addAttribute("class", c.getClass().getName());
if (c.getKey() != null) {
String prefix = componentPrefix(c);
for (Iterator i = getStateModel().getParameters(); i.hasNext();) {
ParameterModel p = (ParameterModel) i.next();
if (!p.getName().startsWith(prefix)) {
continue;
}
Element param =
parent.newChildElement("bebop:param", BEBOP_XML_NS);
param.addAttribute(NAME, unmangle(p.getName()));
param.addAttribute("defaultValue",
String.valueOf(req.getParameter(p.getName())));
param.addAttribute("currentValue", String.valueOf(s.getValue(p)));
}
}
for (Iterator i = c.children(); i.hasNext();) {
showVisibility(s, ((Component) i.next()), cmp);
}
}
private static String getDebugLabel(Component c) {
if (c.getKey() != null) {
return c.getKey();
}
String klass = c.getClass().getName();
return klass.substring(klass.lastIndexOf(".") + 1, klass.length());
}
/**
* return a string that represents an ordered list of component
* ids used on the page. For situations where only the
* components present is of importance, this may be used
* by implementations of hashCode & equals
* @return
*/
public String getComponentString() {
Iterator it = m_componentMap.keySet().iterator();
/*int hash = 0;
while (it.hasNext()) {
String componentId = (String)it.next();
s_log.debug("component id = " + componentId);
hash = hash | componentId.hashCode();
s_log.debug("hash so far = " + hash);
}*/
Date start = new Date();
StringBuffer hashString = new StringBuffer();
while (it.hasNext()) {
String componentId = (String) it.next();
hashString.append(componentId);
}
s_log.debug("Time to create hashCode for page: " + (new Date().getTime() - start.
getTime()));
return hashString.toString();
}
}