nextapp.echo.webcontainer.AbstractComponentSynchronizePeer Maven / Gradle / Ivy
The newest version!
/*
* This file is part of the Echo Web Application Framework (hereinafter "Echo").
* Copyright (C) 2002-2009 NextApp, Inc.
*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*/
package nextapp.echo.webcontainer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import nextapp.echo.app.Component;
import nextapp.echo.app.reflect.ComponentIntrospector;
import nextapp.echo.app.reflect.IntrospectorFactory;
import nextapp.echo.app.update.ClientUpdateManager;
import nextapp.echo.app.update.ServerComponentUpdate;
import nextapp.echo.app.util.Context;
/**
* Default abstract implementation of ComponentSynchronizePeer
.
* Provides implementations of all methods less getComponentClass()
.
* Determines properties to render to client by querying a Component
's
* local style and using a ComponentIntrospector
to determine whether
* those properties
*/
public abstract class AbstractComponentSynchronizePeer
implements ComponentSynchronizePeer {
/**
* Peer for synchronizing events between client and server.
* This is a convenience object that is used with the
* addEvent()
method of the AbstractComponentSynchronizePeer
* object.
*
* This object will often be derived with overriding implementations of the
* hasListeners()
method to return true in cases where the supported
* server-side Component
has registered listeners of the appropriate type,
* such that only events that actually will result in code being executed will cause
* immediate server interactions.
*/
public static class EventPeer {
/**
* The Class
type of the event data that will be received from the client (used to determine serialization
* peer to use for processing).
*/
private Class eventDataClass;
/** The client side event type name. */
private String eventType;
/** The listener property name, defined in the server-side Component
. */
private String listenerPropertyName;
/** Default constructor. */
public EventPeer() {
this(null, null, null);
}
/**
* @param eventType the name of the event, as serialized to the client
* @param listenerPropertyName the name of the event property in the Component
, i.e., the property name of the
* PropertyChangeEvent
fired when listeners are added/removed
*/
public EventPeer(String eventType, String listenerPropertyName) {
this(eventType, listenerPropertyName, null);
}
/**
* @param eventType the name of the event, as serialized to the client
* @param listenerPropertyName the name of the event property in the Component
, i.e., the property name of the
* PropertyChangeEvent
fired when listeners are added/removed
* @param eventDataClass the Class
type of the event data that will be received from the client (used to
* determine serialization peer to use for processing)
*/
public EventPeer(String eventType, String listenerPropertyName, Class eventDataClass) {
super();
this.eventType = eventType;
this.listenerPropertyName = listenerPropertyName;
this.eventDataClass = eventDataClass;
}
/**
* Returns the client-side event type name.
*
* @return the client-side event type name
*/
public String getEventType() {
return eventType;
}
/**
* Returns the name of the event property in the Component
, i.e., the property name of the
* PropertyChangeEvent
fired when listeners are added/removed.
*
* @return the name of the event property
*/
public String getListenerPropertyName() {
return listenerPropertyName;
}
/**
* Returns the Class
type of the event data that will be received from the client (used to
* determine serialization peer to use for processing)
*
* @return the event data Class
*/
public Class getEventDataClass() {
return eventDataClass;
}
/**
* Determines if the Component
has any listeners of this type.
* Default implementation simply returns true, should be overridden by derived implementations
* when possible to return false when no listeners of the this type exist.
*
* @param context the relevant Context
* @param c the Component
* @return true if the Component
has registered listeners of this type
*/
public boolean hasListeners(Context context, Component c) {
return true;
}
/**
* Processes an event received from the client-side component.
*
* @param context the relevant contextual information
* @param component the server-side Component
* @param eventData the serialized event data from the client (will be of type specified by
* getEventDataClass()
)
*/
public void processEvent(Context context, Component component, Object eventData) {
ClientUpdateManager clientUpdateManager = (ClientUpdateManager) context.get(ClientUpdateManager.class);
clientUpdateManager.setComponentAction(component, eventType, eventData);
}
}
/** A Set
containing the names of all additional properties to be rendered to the client. */
private Set additionalProperties = null;
/** A Set
containing the names of all style properties. */
private Set stylePropertyNames = null;
/** A Set
containing the names of all properties which are indexed. */
private Set indexedPropertyNames = null;
/** A Set
containing the names of properties which should be rendered-by-reference. */
private Set referencedProperties = null;
/** Mapping between event types (String
s) and EventPeer
s. */
private Map eventTypeToEventPeer;
/** Set of Component
Class
es whose peers must be initialized in order for the component to render. */
private Set requiredComponentClasses;
/**
* Default constructor.
*/
public AbstractComponentSynchronizePeer() {
super();
try {
stylePropertyNames = new HashSet();
indexedPropertyNames = new HashSet();
Class componentClass = getComponentClass();
ComponentIntrospector ci = (ComponentIntrospector) IntrospectorFactory.get(componentClass.getName(),
componentClass.getClassLoader());
Iterator propertyNameIt = ci.getPropertyNames();
while (propertyNameIt.hasNext()) {
String propertyName = (String) propertyNameIt.next();
if (ci.getStyleConstantName(propertyName) != null) {
stylePropertyNames.add(propertyName);
if (ci.isIndexedProperty(propertyName)) {
indexedPropertyNames.add(propertyName);
}
}
}
} catch (ClassNotFoundException ex) {
// Should never occur.
throw new RuntimeException("Internal error.", ex);
}
}
/**
* Adds an EventPeer
to process client-side events.
*
* @param eventPeer the EventPeer
to add
*/
public void addEvent(EventPeer eventPeer) {
if (eventTypeToEventPeer == null) {
eventTypeToEventPeer = new HashMap();
}
eventTypeToEventPeer.put(eventPeer.getEventType(), eventPeer);
}
/**
* Adds a non-indexed output property.
*
* @see #addOutputProperty(java.lang.String, boolean)
*/
public void addOutputProperty(String propertyName) {
addOutputProperty(propertyName, false);
}
/**
* Adds an output property.
* Property names added via this method will be returned by the
* getOutputPropertyName()
method of this class.
* If the indexed flag is set, the isOutputPropertyIndexed
* method will also return true for this property name
*
* @param propertyName the property name to add
* @param indexed a flag indicating whether the property is indexed
*/
public void addOutputProperty(String propertyName, boolean indexed) {
if (additionalProperties == null) {
additionalProperties = new HashSet();
}
additionalProperties.add(propertyName);
if (indexed) {
indexedPropertyNames.add(propertyName);
}
}
/**
* Adds a required component class that must also be initialized before this
* component can be rendered.
*
* @param componentClass
*/
public void addRequiredComponentClass(Class componentClass) {
if (requiredComponentClasses == null) {
requiredComponentClasses = new HashSet();
}
requiredComponentClasses.add(componentClass);
}
/**
* Returns the (most basic) supported component class.
*
* @return the (most basic) supported component class
*/
public abstract Class getComponentClass();
/**
* Returns null. Implementations should override if they wish
* to provide event data.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getEventDataClass(java.lang.String)
*/
public Class getEventDataClass(String eventType) {
if (eventTypeToEventPeer == null) {
return null;
}
EventPeer eventPeer = (EventPeer) eventTypeToEventPeer.get(eventType);
if (eventPeer == null) {
return null;
}
return eventPeer.getEventDataClass();
}
/**
* Returns an iterator of String
s containing all event types registered using addEvent()
.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getEventTypes(Context, Component)
*/
public Iterator getEventTypes(Context context, Component component) {
if (eventTypeToEventPeer == null) {
return Collections.EMPTY_SET.iterator();
} else {
return Collections.unmodifiableSet(eventTypeToEventPeer.keySet()).iterator();
}
}
/**
* Returns any property from the local style of the Component
.
* Implementations should override if they wish to support additional properties.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getOutputProperty(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String, int)
*/
public Object getOutputProperty(Context context, Component component, String propertyName, int propertyIndex) {
if (propertyIndex == -1) {
return component.getLocalStyle().get(propertyName);
} else {
return component.getLocalStyle().getIndex(propertyName, propertyIndex);
}
}
/**
* Returns the indices of any indexed property from the local style of the Component
.
* Implementations should override if they wish to support additional properties.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getOutputPropertyIndices(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String)
*/
public Iterator getOutputPropertyIndices(Context context, Component component, String propertyName) {
return component.getLocalStyle().getPropertyIndices(propertyName);
}
/**
* Returns null.
* Implementations should override if they wish to set properties on the client by invoking
* specific methods other than setProperty()/setIndexedProperty().
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getOutputPropertyMethodName(
* nextapp.echo.app.util.Context, nextapp.echo.app.Component, java.lang.String)
*/
public String getOutputPropertyMethodName(Context context, Component component, String propertyName) {
return null;
}
/**
* Returns the names of all properties currently set in the component's local Style
,
* in addition to any properties added by invoking addOutputProperty()
.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getOutputPropertyNames(Context, nextapp.echo.app.Component)
*/
public Iterator getOutputPropertyNames(Context context, Component component) {
final Iterator styleIterator = component.getLocalStyle().getPropertyNames();
final Iterator additionalPropertyIterator = additionalProperties == null ? null : additionalProperties.iterator();
return new Iterator() {
/**
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return styleIterator.hasNext() || (additionalPropertyIterator != null && additionalPropertyIterator.hasNext());
}
/**
* @see java.util.Iterator#next()
*/
public Object next() {
if (styleIterator.hasNext()) {
return styleIterator.next();
} else {
return additionalPropertyIterator.next();
}
}
/**
* @see java.util.Iterator#remove()
*/
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Returns null. Implementations receiving input properties should override.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getInputPropertyClass(java.lang.String)
*/
public Class getInputPropertyClass(String propertyName) {
return null;
}
/**
* Returns property names that have been updated in the specified
* ServerComponentUpdate
that are either part of the local style
* or have been added via the addOutputProperty()
method.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#getUpdatedOutputPropertyNames(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component,
* nextapp.echo.app.update.ServerComponentUpdate)
*/
public Iterator getUpdatedOutputPropertyNames(Context context, Component component,
ServerComponentUpdate update) {
if (!update.hasUpdatedProperties()) {
return Collections.EMPTY_SET.iterator();
}
final String[] updatedPropertyNames = update.getUpdatedPropertyNames();
return new Iterator() {
private int i = 0;
private Object nextValue = null;
{
loadNext();
}
public void remove() {
throw new UnsupportedOperationException();
}
public Object next() {
if (nextValue == null) {
throw new IndexOutOfBoundsException();
} else {
Object value = nextValue;
loadNext();
return value;
}
}
/**
* Load next value.
*/
private void loadNext() {
nextValue = null;
while (nextValue == null && i < updatedPropertyNames.length) {
if (stylePropertyNames.contains(updatedPropertyNames[i])
|| (additionalProperties != null && additionalProperties.contains(updatedPropertyNames[i]))) {
nextValue = updatedPropertyNames[i];
}
++i;
}
}
public boolean hasNext() {
return nextValue != null;
}
};
}
/**
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#hasListeners(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String)
*/
public boolean hasListeners(Context context, Component component, String eventType) {
if (eventTypeToEventPeer == null) {
return false;
}
EventPeer eventPeer = (EventPeer) eventTypeToEventPeer.get(eventType);
if (eventPeer == null) {
return false;
}
return eventPeer.hasListeners(context, component);
}
/**
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#hasUpdatedListeners(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, nextapp.echo.app.update.ServerComponentUpdate, java.lang.String)
*/
public boolean hasUpdatedListeners(Context context, Component component, ServerComponentUpdate update,
String eventType) {
if (eventTypeToEventPeer == null) {
return false;
}
EventPeer eventPeer = (EventPeer) eventTypeToEventPeer.get(eventType);
if (eventPeer == null) {
return false;
}
return update.hasUpdatedProperty(eventPeer.getListenerPropertyName());
}
/**
* Invokes the init() methods of peers of required component classes (added via
* addRequiredComponentClass()).
* Implementations requiring initialization should override this method and invoke the
* super-implementation out of convention (even if they do not presently have any
* dependencies on other components).
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#init(Context, Component)
*/
public void init(Context context, Component component) {
if (requiredComponentClasses == null) {
return;
}
Iterator componentClassIt = requiredComponentClasses.iterator();
while (componentClassIt.hasNext()) {
Class componentClass = (Class) componentClassIt.next();
ComponentSynchronizePeer syncPeer = SynchronizePeerFactory.getPeerForComponent(componentClass);
syncPeer.init(context, component);
}
}
/**
* Determines if a local style property or additional property (added via addOutputProperty()
)
* is indexed.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#isOutputPropertyIndexed(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String)
*/
public boolean isOutputPropertyIndexed(Context context, Component component, String propertyName) {
return indexedPropertyNames.contains(propertyName);
}
/**
* Returns true for any property set as rendered-by-reference via the
* setOutputPropertyReferenced()
method.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#isOutputPropertyReferenced(
* nextapp.echo.app.util.Context, nextapp.echo.app.Component, java.lang.String)
*/
public boolean isOutputPropertyReferenced(Context context, Component component, String propertyName) {
return referencedProperties != null && referencedProperties.contains(propertyName);
}
/**
* Does nothing. Implementations handling events should overwrite this method.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#processEvent(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String, java.lang.Object)
*/
public void processEvent(Context context, Component component, String eventType, Object eventData) {
if (eventTypeToEventPeer == null) {
return;
}
EventPeer eventPeer = (EventPeer) eventTypeToEventPeer.get(eventType);
if (eventPeer == null) {
return;
}
eventPeer.processEvent(context, component, eventData);
}
/**
* Sets the rendered-by-reference state of a property.
* isOutputPropertyReferenced
will return true for any property set as
* referenced using this method.
*
* @param propertyName the propertyName
* @param newValue true if the property should be rendered by reference
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#isOutputPropertyReferenced(
* nextapp.echo.app.util.Context, nextapp.echo.app.Component, java.lang.String)
*/
public void setOutputPropertyReferenced(String propertyName, boolean newValue) {
if (newValue) {
if (referencedProperties == null) {
referencedProperties = new HashSet();
}
referencedProperties.add(propertyName);
} else {
if (referencedProperties != null) {
referencedProperties.remove(propertyName);
}
}
}
/**
* Does nothing. Implementations that receive input from the client should override this method.
*
* @see nextapp.echo.webcontainer.ComponentSynchronizePeer#storeInputProperty(nextapp.echo.app.util.Context,
* nextapp.echo.app.Component, java.lang.String, int, java.lang.Object)
*/
public void storeInputProperty(Context context, Component component, String propertyName, int index, Object newValue) {
// Do nothing.
}
}