/*******************************************************************************
* Copyright (c) 2002-2006 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
******************************************************************************/
package com.w4t.ajax;
import java.io.IOException;
import java.util.IdentityHashMap;
import java.util.Map;
import org.eclipse.rwt.Adaptable;
import org.eclipse.rwt.internal.lifecycle.HtmlResponseWriter;
import org.eclipse.rwt.internal.service.ContextProvider;
import org.eclipse.rwt.internal.service.IServiceStateInfo;
import org.eclipse.rwt.internal.util.HTML;
import com.w4t.*;
import com.w4t.dhtml.Item;
import com.w4t.engine.lifecycle.standard.IRenderingSchedule;
import com.w4t.util.ComponentTreeVisitor;
import com.w4t.util.RendererCache;
import com.w4t.util.ComponentTreeVisitor.AllComponentVisitor;
/**
* <p>Utility class with helper methods for AJaX rendering.</p>
*/
public final class AjaxStatusUtil {
private static final String ENVELOPE = "envelope";
private AjaxStatusUtil() {
// prevent instantiation
}
// TODO [rh] Verify that only the component is updated. In case it is a
// container, none of its children must be changed
// TODO [rh] JavaDoc
public static void updateHashCode( final WebComponent component ) {
HashCodeBuilder builder;
builder = HashCodeBuilderFactory.getBuilder( component.getClass() );
HashCodeBuilderSupportImpl support = new HashCodeBuilderSupportImpl();
int hashCode = builder.compute( support, component );
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
ajaxStatus.setComponentHashCode( hashCode );
}
/**
* <p>Collects information that is needed for the actual rendering run
* of the given component-tree to render.</p>
*
* @param root the <code>WebContainer</code> that represents the root of
* the component-tree.
*/
public static void preRender( final WebContainer root ) {
HashCodeBuilderSupport support = new HashCodeBuilderSupportImpl();
ComponentTreeVisitor visitor = new Visitor( root, support );
int strategy = ComponentTreeVisitor.STRATEGY_BREADTH_FIRST;
ComponentTreeVisitor.accept( root, strategy, visitor );
}
/**
* <p>Collects information that is needed for the rendering run of the next
* request of the given component-tree.</p>
* @param rootContainer the <code>WebContainer</code> that represents the
* root of the component-tree.
*/
public static void postRender( final WebContainer rootContainer ) {
ComponentTreeVisitor visitor = new AllComponentVisitor() {
public boolean doVisit( final WebComponent component ) {
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
ajaxStatus.setMustRender( false );
ajaxStatus.setWasVisible( component.isVisible() );
ajaxStatus.setWasEnabled( component.isEnabled() );
return true;
}
};
ComponentTreeVisitor.accept( rootContainer, visitor );
}
/**
* <p>Calls <code>mustRender</code> for the <code>AjaxStatus</code> which is
* associated with the given <code>component</code>.</p>
*/
public static boolean mustRender( final WebComponent component ) {
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
return ajaxStatus.mustRender();
}
static boolean isRootOfSubTreeToRender( final WebComponent comp ) {
return mustRender( comp )
&& ( comp instanceof WebForm
|| ( getPredecessor( comp ) != null
&& !mustRender( getPredecessor( comp ) ) ) );
}
private static WebComponent getPredecessor( final WebComponent comp ) {
WebComponent result = null;
if( comp.getParent() != null && Decorator.isDecorated( comp ) ) {
result = Decorator.getParentDecorator( comp );
} else if( comp instanceof Item ) {
result = ( ( Item )comp ).getParentNode();
if( result == null ) {
result = comp.getParent();
}
} else {
result = comp.getParent();
}
return result;
}
public static void startEnvelope( final Adaptable adaptable )
throws IOException
{
if( adaptable instanceof WebComponent ) {
startEnvelope( ( WebComponent )adaptable );
}
}
public static void endEnvelope( final Adaptable adaptable )
throws IOException
{
if( adaptable instanceof WebComponent ) {
endEnvelope( ( WebComponent )adaptable );
}
}
public static void startEnvelope( final WebComponent component )
throws IOException
{
if( W4TContext.getBrowser().isAjaxEnabled()
&& isRootOfSubTreeToRender( component ) )
{
HtmlResponseWriter writer = getResponseWriter();
writer.startElement( ENVELOPE, null );
writer.writeAttribute( HTML.ID, component.getUniqueID(), null );
writer.closeElementIfStarted();
writer.append( "<!--" );
}
}
public static void endEnvelope( final WebComponent component )
throws IOException
{
if( W4TContext.getBrowser().isAjaxEnabled()
&& isRootOfSubTreeToRender( component ) )
{
HtmlResponseWriter writer = getResponseWriter();
writer.append( "-->" );
writer.endElement( ENVELOPE );
}
}
private static HtmlResponseWriter getResponseWriter() {
IServiceStateInfo stateInfo = ContextProvider.getStateInfo();
return stateInfo.getResponseWriter();
}
public static HashCodeBuilderSupport newHashCodeBuilderSupport() {
return new HashCodeBuilderSupportImpl();
}
///////////////////
// Private classes
private static class Visitor extends AllComponentVisitor {
private final HashCodeBuilderSupport support;
public Visitor( final WebContainer rootContainer,
final HashCodeBuilderSupport support )
{
this.support = support;
}
public boolean doVisit( final WebComponent component ) {
boolean result = false;
if( isScheduled( component ) ) {
// Compute new hashCode
HashCodeBuilder builder;
builder = HashCodeBuilderFactory.getBuilder( component.getClass() );
int newHashCode = builder.compute( support, component );
// Decide whether component must be rendered
determineMustRender( component, newHashCode );
// Store new hashCode
storeHashCode( component, newHashCode );
result = true;
} else {
// ensure that next time the component is scheduled for rendering
// will be marked as changed.
storeHashCode( component, 0 );
}
return result;
}
private boolean isScheduled( final WebComponent component ) {
IRenderingSchedule renderingSchedule = LifeCycleHelper.getSchedule();
return renderingSchedule.isScheduled( component );
}
private void determineMustRender( final WebComponent component,
final int newHashCode )
{
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
boolean mustRender = ajaxStatus.mustRender();
/////////////////////////////////////
// TODO: [fappel] debug only
// if( !hasAjaxRenderer( component ) ) {
// System.out.println( "No ajax renderer found for ["
// + component.getClass().getName()
// + "]. Schedule parent for rendering." );
// }
////////////////////////////////////
// TODO [rh] Bug? if no ajax-renderer was found, the parent must be
// rendererd in any case - not only when the component itself
// is not to be rendered
// see AjaxStatus_Test#testComponentWithNonAjaxParent
if( !mustRender && !hasAjaxRenderer( component ) ) {
mustRender = true;
markParentAsMustRender( component );
}
if ( !mustRender ) {
if ( !ajaxStatus.hasComponentHashCode()
|| ajaxStatus.getComponentHashCode() != newHashCode )
{
mustRender = true;
ajaxStatus.updateStatus( mustRender );
}
}
}
private static void markParentAsMustRender( final WebComponent component ) {
WebContainer parent = component.getParent();
if( parent == null ) {
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
ajaxStatus.updateStatus( true );
} else {
AjaxStatus parentAjaxStatus;
parentAjaxStatus = ( AjaxStatus )parent.getAdapter( AjaxStatus.class );
parentAjaxStatus.updateStatus( true );
}
}
private static void storeHashCode( final WebComponent component,
final int hashCode )
{
AjaxStatus ajaxStatus;
ajaxStatus = ( AjaxStatus )component.getAdapter( AjaxStatus.class );
ajaxStatus.setComponentHashCode( hashCode );
}
}
private static class HashCodeBuilderSupportImpl
implements HashCodeBuilderSupport
{
private final Map recursionList = new IdentityHashMap();
public void addToRecursionList( final Object component ) {
recursionList.put( component, null );
}
public boolean isInRecursionList( final Object component ) {
return recursionList.containsKey( component );
}
}
private static boolean hasAjaxRenderer( final WebComponent component ) {
RendererCache rendererCache = RendererCache.getInstance();
String rendererClassName;
rendererClassName = rendererCache.getRendererClass( component.getClass() );
return rendererClassName != null
&& rendererClassName.endsWith( "_Ajax" );
}
}