/******************************************************************************* * Copyright (c) 2002, 2008 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 org.eclipse.rwt.lifecycle; import java.text.MessageFormat; import org.eclipse.rwt.internal.lifecycle.UITestUtil; import org.eclipse.swt.internal.widgets.WidgetAdapter; import org.eclipse.swt.internal.widgets.WidgetTreeVisitor; import org.eclipse.swt.internal.widgets.WidgetTreeVisitor.AllWidgetTreeVisitor; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Widget; /** * This is a helper class to obtain different aspects for a widget * related to the handling of widgets in RAP. * * @since 1.0 */ public final class WidgetUtil { /** * Used to mark a widget as belonging to a custom variant using * <code>Widget.setData</code>. For more information on custom widget * variants, see the RAP help on theming. * * @see Widget#setData(String,Object) * @since 1.1 */ public static final String CUSTOM_VARIANT = "org.eclipse.rwt.themeVariant"; /** * <p><strong>Note:</strong> This constant is provisional and subject to * change without further notice.</p> * * By default, the widget-id returned by {@link IWidgetAdapter#getId()} is * an automatically generated value that is session-wide unique. * A custom id can be assigned by using the <code>Widget#setData(String, * Object)</code> method and using this constant for the <code>key</code> * argument and a string that denotes the new id as the <code>data</code> * argument. In addition the system property denoted by * <code>ENABLE_UI_TESTS</code> must be set to <code>true</code>. * * <p>The <code>id</code> must only contain characters that are valid according * to the <a href="http://www.w3.org/TR/html401/types.html#type-cdata">W3C * recommendation for id and name attributes</a>.</p> * * <p>It is the clients' responsibility to choose a unique id. Assigning an * id that is used by another widget will lead to indeterministic behavior. * </p> * * <p>The following code would assign the id 'myId' to the widget: * <pre> * Widget widget = new ... * widget.setData( WidgetUtil.CUSTOM_WIDGET_ID, "myId" ); * </pre></p> * * @see Widget#setData(String,Object) * @see #getId(Widget) * @see #ENABLE_UI_TESTS * * @since 1.1 */ public static final String CUSTOM_WIDGET_ID = "org.eclipse.rwt.UITests#customId"; /** * <p><strong>Note:</strong> This constant is provisional and subject to * change without further notice.</p> * * If a system property with this name is set to <code>true</code>, the * UI testing support is activated. For all widgets that are rendered to * the client, the HTML id attribute is set. * * <p>In conjunction with <code>CUSTOM_WIDGET_ID</code>, each widget can * be assigned a custom, more human-readable, identifier that is independant * if the order in which widgets are created.</p> * * @see #CUSTOM_WIDGET_ID * * @since 1.1 */ public static final String ENABLE_UI_TESTS = "org.eclipse.rwt.enableUITests"; private WidgetUtil() { // prevent instantiation } /** * Returns the according {@link IWidgetAdapter} for a specified * widget. * * @param widget the widget * @return the {@link IWidgetAdapter} instance */ public static IWidgetAdapter getAdapter( final Widget widget ) { IWidgetAdapter result; result = ( IWidgetAdapter )widget.getAdapter( IWidgetAdapter.class ); if( result == null ) { throwAdapterException( IWidgetAdapter.class ); } return result; } /** * Returns the id of the given <code>widget</code> that is used to identify * the widget on the client. * * @param widget the widget to obtain the id for, must not be * <code>null</code> * @return the id for the given <code>widget</code> */ public static String getId( final Widget widget ) { // TODO [rh] consider overriding the id when Widget#setData is called // - safer 1: in case someone tries to obtain id directly from addapter // - safer 2: changing the id after widget was initialized could be // detected and prevented // - less memory: new 'data' array created per widget to hold the id // - illegal id's could be rejected immediately (close to error source) // - faster (?): only "return getAdapter( widget ).getId();" in here String result = null; if( UITestUtil.isEnabled() ) { result = ( String )widget.getData( CUSTOM_WIDGET_ID ); } if( result == null ) { result = getAdapter( widget ).getId(); } return result; } /** * Returns the widget variant defined for the given widget using * <code>Widget.setData()</code>. * * @param widget the widget whose variant is requested * @return the variant or <code>null</code> if no variant has been specified * for the given widget */ public static String getVariant( final Widget widget ) { String result = null; WidgetAdapter widgetAdapter = ( WidgetAdapter )getAdapter( widget ); Object data = widget.getData( WidgetUtil.CUSTOM_VARIANT ); if( data instanceof String ) { result = ( String )data; if( !result.equals( widgetAdapter.getCachedVariant() ) ) { if( validateVariantString( result ) ) { widgetAdapter.setCachedVariant( result ); } else { String pattern = "Illegal character in widget variant ''{0}''"; Object[] arguments = new Object[] { result }; String message = MessageFormat.format( pattern, arguments ); throw new IllegalArgumentException( message ); } } } return result; } /** * Returns the {@link AbstractWidgetLCA} instance for this widget. * * @param widget the widget to obtain the life cycle adapter from * @return the life cycle adapter for the given <code>widget</code> */ // TODO [bm] why do we return AbstractWidgetLCA instead of pulling the interesting // methods up to IWidgetLifeCycleAdapter and using this to talk to the outside // world public static AbstractWidgetLCA getLCA( final Widget widget ) { Class clazz = ILifeCycleAdapter.class; AbstractWidgetLCA result = ( AbstractWidgetLCA )widget.getAdapter( clazz ); if( result == null ) { throwAdapterException( clazz ); } return result; } /** * This method searches for a widget with the given <code>id</code> within * the widget hierachy starting at <code>root</code>. * * @param root the root widget where to start the search * @param id the id of the widget to search for * @return the widget or <code>null</code> if there was no widget found with * the given <code>id</code> within the widget hierarchy */ public static Widget find( final Composite root, final String id ) { final Widget[] result = { null }; if( id != null ) { WidgetTreeVisitor.accept( root, new AllWidgetTreeVisitor() { public boolean doVisit( final Widget widget ) { if( getId( widget ).equals( id ) ) { result[ 0 ] = widget; } return result[ 0 ] == null; } } ); } return result[ 0 ]; } private static void throwAdapterException( final Class clazz ) { String text = "Could not retrieve an instance of ''{0}''. Probably the " + "AdapterFactory was not properly registered."; Object[] param = new Object[]{ clazz.getName() }; String msg = MessageFormat.format( text, param ); throw new IllegalStateException( msg ); } private static boolean validateVariantString( final String variant ) { boolean result = false; String name = variant; if( name.startsWith( "-" ) ) { name = name.substring( 1 ); } int length = name.length(); if( length > 0 ) { result = isValidStart( name.charAt( 0 ) ); for( int i = 1; i < length && result; i++ ) { result &= isValidPart( name.charAt( i ) ); } } return result; } private static boolean isValidStart( final char ch ) { return ch == '_' || ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ( ch >= 128 && ch <= 255 ); } private static boolean isValidPart( final char ch ) { return isValidStart( ch ) || ( ch >= '0' && ch <= '9' ) || ch == '-'; } }