/*******************************************************************************
* 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.util;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.rwt.internal.browser.Browser;
import com.w4t.*;
/**
* <p>Provides <code>Renderer</code>s for <code>WebComponents</code> using a
* cache.</p>
*/
public class RendererCache {
/** the internal datastructure of the RendererCache */
private final Map cache;
private final Map keyCache;
/** the singleton instance of RendererCache. */
private static RendererCache _instance;
/** <p>Creates a new instance of RendererCache; private to ensure the
* singleton instance.</p> */
private RendererCache() {
cache = new HashMap();
keyCache = new HashMap();
}
/** <p>returns a reference to the singleton instance of
* <code>RendererCache</code>.</p> */
public static synchronized RendererCache getInstance() {
if( _instance == null ) {
_instance = new RendererCache();
}
return _instance;
}
public String getRendererClass( final Class componentClass ) {
return getRendererClass( componentClass, W4TContext.getBrowser() );
}
/** <p>Returns the class name (a superclass of <code>Renderer</code>) which is
* responsible for rendering the given <code>componentClass</code>.</p>
* <p>The browser capabilities are determined from the current browser
* (<code>W4TContext#getBrowser()</code>).</p>
* @return the class name of the components' renderer or <code>null</code>
* if the renderer classname could not be determined.
*/
public synchronized String getRendererClass( final Class componentClass,
final Browser browser )
{
String rendererKey = createRendererKey( componentClass, browser );
RendererClassCache rcc = RendererClassCache.getInstance();
String rendererClassName = rcc.getRendererName( rendererKey );
if ( rendererClassName == null ) {
try {
Class rendererClass = loadRendererClass( componentClass, browser );
rendererClassName = rendererClass.getName();
rcc.addRendererName( rendererKey, rendererClassName );
} catch( final Exception e ) {
}
}
return rendererClassName;
}
/**
* <p>Returns a <code>Renderer</code> object for the given
* <code>componentClass</code> and the <em>current</em> (obtained by
* {@link org.eclipse.rwt.W4TContext#getBrowser()
* <code>W4TContext.getBrowser()</code>}) browser.</p>
* <p>Invoking this method is equivalent to
* {@link #retrieveRenderer(Class, Browser)
* <code>retrieveRenderer(componentClass, W4TContext.getBrowser());</code>}
*/
public Renderer retrieveRenderer( final Class componentClass ) {
return retrieveRenderer( componentClass, W4TContext.getBrowser() );
}
/** <p>Returns a renderer object the given browser for a WebComponent of the
* specified class.</p> */
public synchronized Renderer retrieveRenderer( final Class componentClass,
final Browser browser )
{
Renderer result = null;
String rendererKey = createRendererKey( componentClass, browser );
if( cache.containsKey( rendererKey ) ) {
result = ( Renderer )cache.get( rendererKey );
} else {
RendererClassCache rcc = RendererClassCache.getInstance();
String rendererClassName = rcc.getRendererName( rendererKey );
Class rendererClass = null;
if( rendererClassName == null ) {
rendererClass = loadRendererClass( componentClass, browser );
rcc.addRendererName( rendererKey, rendererClass.getName() );
} else {
try {
ClassLoader loader = componentClass.getClassLoader();
rendererClass = loader.loadClass( rendererClassName );
} catch( final ClassNotFoundException cnfe ) {
// TODO: [fappel] improve Exception handling
String msg = "Could not find renderer class '"
+ rendererClassName
+ "' for component '"
+ componentClass.getName()
+ "'. Detected Browser: "
+ browser.getClass().getName();
throw new IllegalStateException( msg );
}
}
try {
result = ( Renderer )rendererClass.newInstance();
} catch( final Exception e ) {
// TODO: [fappel] improve Exception handling
String msg = "Could not load renderer class '"
+ rendererClassName
+ "' for component '"
+ componentClass.getName()
+ "'. Detected Browser: "
+ browser.getClass().getName();
throw new IllegalStateException( msg );
}
cache.put( rendererKey, result );
}
return result;
}
// helping methods
//////////////////
private Class loadRendererClass( final Class clazz, final Browser browser ) {
Class result = null;
RendererDescriptor descriptor = new RendererDescriptor( clazz, browser );
if( clazz.equals( WebComponent.class ) ) {
result = Renderer.class;
} else {
boolean found = false;
boolean hasPredecessor;
do {
hasPredecessor = false;
try {
String toString = descriptor.toString();
ClassLoader loader = clazz.getClassLoader();
result = loader.loadClass( toString );
found = true;
} catch( Exception e ) {
hasPredecessor = descriptor.useBrowserPredecessor();
}
} while( !found && hasPredecessor );
if( result == null ) {
result = loadRendererClass( clazz.getSuperclass(), browser );
}
}
return result;
}
private String createRendererKey( final Class componentClass,
final Browser browser )
{
// for perfomance reasons we buffer the created keys since the
// StringBuffer-concatenation showed up in yourkit-profiler.
String renderMode = determineRendererMode( browser );
Map components = ( Map )keyCache.get( componentClass.getName() );
if( components == null ) {
components = new HashMap();
keyCache.put( componentClass.getName(), components );
}
String browserName = browser.getClass().getName();
Map browsers = ( Map )components.get( browserName );
if( browsers == null ) {
// Since we know that there are only 3 modes (currently) we can set
// the exact size of the Map - note that we must use '4' since adding
// the third element causes the capacity to be increased
browsers = new HashMap( 4, 1f );
components.put( browserName, browsers );
}
String result = ( String )browsers.get( renderMode );
if( result == null ) {
StringBuffer buffer = new StringBuffer();
buffer.append( componentClass.getName() );
buffer.append( "_" );
buffer.append( browser.toString() );
buffer.append( "_" );
buffer.append( renderMode );
result = buffer.toString();
browsers.put( renderMode, result );
}
return result;
}
private String determineRendererMode( final Browser browser ) {
String result;
if( browser.isAjaxEnabled() ) {
result = "ajax";
} else if( browser.isScriptEnabled() ) {
result = "script";
} else {
result = "noscript";
}
return result;
}
}