blob: bcf3824733f8c6b0110db4b8b041f0751b712fe8 [file] [log] [blame]
// Copyright 2004 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import javax.servlet.ServletContext;
import org.apache.tapestry.event.ChangeObserver;
import org.apache.tapestry.event.ObservedChangeEvent;
import org.apache.tapestry.request.RequestContext;
import org.apache.tapestry.resource.ContextResourceLocation;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.util.AdaptorRegistry;
import org.apache.tapestry.util.StringSplitter;
/**
* A placeholder for a number of (static) methods that don't belong elsewhere, as well
* as a global location for static constants.
*
* @since 1.0.1
* @version $Id$
* @author Howard Lewis Ship
*
**/
public final class Tapestry
{
/**
* Name of a request attribute used with the
* {@link #TAGSUPPORT_SERVICE} service. The attribute
* defines the underlying service to for which a URL will be generated.
*
* @since 3.0
*
**/
public final static String TAG_SUPPORT_SERVICE_ATTRIBUTE =
"org.apache.tapestry.tagsupport.service";
/**
* Name of a request attribute used with the
* {@link #TAGSUPPORT_SERVICE} service. The attribute
* defines the correct servlet path for the
* Tapestry application (which, for the odd-man-out TAGSUPPORT_SERVICE
* may not match HttpServletRequest.getServletPath() because of
* the use of an include.
*
* @since 3.0
*/
public final static String TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE =
"org.apache.tapestry.tagsupport.servlet-path";
/**
* Name of a request attribute used with the
* {@link #TAGSUPPORT_SERVICE} service. The attribute
* defines an array of objects to be converted into
* service parameters (i.e., for use with the
* {@link #EXTERNAL_SERVICE}).
*
* @since 3.0
*
**/
public final static String TAG_SUPPORT_PARAMETERS_ATTRIBUTE =
"org.apache.tapestry.tagsupport.parameters";
/**
* Service used to support rendering of JSP tags. tagsupport is provided
* with a service and service parameters via request attributes
* and creates a URI from the result, which is output to the response.
*
* @since 3.0
*
**/
public static final String TAGSUPPORT_SERVICE = "tagsupport";
/**
* The name ("action") of a service that allows behavior to be associated with
* an {@link IAction} component, such as {@link org.apache.tapestry.link.ActionLink} or
* {@link org.apache.tapestry.form.Form}.
*
* <p>This service is used with actions that are tied to the
* dynamic state of the page, and which require a rewind of the page.
*
**/
public final static String ACTION_SERVICE = "action";
/**
* The name ("direct") of a service that allows stateless behavior for an {@link
* org.apache.tapestry.link.DirectLink} component.
*
* <p>This service rolls back the state of the page but doesn't
* rewind the the dynamic state of the page the was the action
* service does, which is more efficient but less powerful.
*
* <p>An array of String parameters may be included with the
* service URL; these will be made available to the {@link org.apache.tapestry.link.DirectLink}
* component's listener.
*
**/
public final static String DIRECT_SERVICE = "direct";
/**
* The name ("external") of a service that a allows {@link IExternalPage} to be selected.
* Associated with a {@link org.apache.tapestry.link.ExternalLink} component.
*
* <p>This service enables {@link IExternalPage}s to be accessed via a URL.
* External pages may be booked marked using their URL for future reference.
*
* <p>An array of Object parameters may be included with the
* service URL; these will be passed to the
* {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method.
*
**/
public final static String EXTERNAL_SERVICE = "external";
/**
* The name ("page") of a service that allows a new page to be selected.
* Associated with a {@link org.apache.tapestry.link.PageLink} component.
*
* <p>The service requires a single parameter: the name of the target page.
**/
public final static String PAGE_SERVICE = "page";
/**
* The name ("home") of a service that jumps to the home page. A stand-in for
* when no service is provided, which is typically the entrypoint
* to the application.
*
**/
public final static String HOME_SERVICE = "home";
/**
* The name ("restart") of a service that invalidates the session and restarts
* the application. Typically used just
* to recover from an exception.
*
**/
public static final String RESTART_SERVICE = "restart";
/**
* The name ("asset") of a service used to access internal assets.
*
**/
public static final String ASSET_SERVICE = "asset";
/**
* The name ("reset") of a service used to clear cached template
* and specification data and remove all pooled pages.
* This is only used when debugging as
* a quick way to clear the out cached data, to allow updated
* versions of specifications and templates to be loaded (without
* stopping and restarting the servlet container).
*
* <p>This service is only available if the Java system property
* <code>org.apache.tapestry.enable-reset-service</code>
* is set to <code>true</code>.
*
**/
public static final String RESET_SERVICE = "reset";
/**
* Query parameter that identfies the service for the
* request.
*
* @since 1.0.3
*
**/
public static final String SERVICE_QUERY_PARAMETER_NAME = "service";
/**
* The query parameter for application specific parameters to the
* service (this is used with the direct service). Each of these
* values is encoded with {@link java.net.URLEncoder#encode(String)} before
* being added to the URL. Multiple values are handle by repeatedly
* establishing key/value pairs (this is a change from behavior in
* 2.1 and earlier).
*
* @since 1.0.3
*
**/
public static final String PARAMETERS_QUERY_PARAMETER_NAME = "sp";
/**
* Property name used to get the extension used for templates. This
* may be set in the page or component specification, or in the page (or
* component's) immediate container (library or application specification).
* Unlike most properties, value isn't inherited all the way up the chain.
* The default template extension is "html".
*
* @since 3.0
*
**/
public static final String TEMPLATE_EXTENSION_PROPERTY =
"org.apache.tapestry.template-extension";
/**
* The default extension for templates, "html".
*
* @since 3.0
*
**/
public static final String DEFAULT_TEMPLATE_EXTENSION = "html";
/**
* The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the
* currently rendering {@link org.apache.tapestry.components.ILinkComponent}
* is stored. Link components do not nest.
*
**/
public static final String LINK_COMPONENT_ATTRIBUTE_NAME =
"org.apache.tapestry.active-link-component";
/**
* Suffix appended to a parameter name to form the name of a property that stores the
* binding for the parameter.
*
* @since 3.0
*
**/
public static final String PARAMETER_PROPERTY_NAME_SUFFIX = "Binding";
/**
* Name of application extension used to resolve page and component
* specifications that can't be located by the normal means. The
* extension must implement
* {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate}.
*
* @since 3.0
*
**/
public static final String SPECIFICATION_RESOLVER_DELEGATE_EXTENSION_NAME =
"org.apache.tapestry.specification-resolver-delegate";
/**
* Name of application extension used to resolve page and component
* templates that can't be located by the normal means.
* The extension must implement
* {@link org.apache.tapestry.engine.ITemplateSourceDelegate}.
*
* @since 3.0
*
**/
public static final String TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME =
"org.apache.tapestry.template-source-delegate";
/**
* Key used to obtain an extension from the application specification. The extension,
* if it exists, implements {@link org.apache.tapestry.request.IRequestDecoder}.
*
* @since 2.2
*
**/
public static final String REQUEST_DECODER_EXTENSION_NAME =
"org.apache.tapestry.request-decoder";
/**
* Name of optional application extension for the multipart decoder
* used by the application. The extension must implement
* {@link org.apache.tapestry.multipart.IMultipartDecoder}
* (and is generally a configured instance of
* {@link org.apache.tapestry.multipart.DefaultMultipartDecoder}).
*
* @since 3.0
*
**/
public static final String MULTIPART_DECODER_EXTENSION_NAME =
"org.apache.tapestry.multipart-decoder";
/**
* Method id used to check that {@link IPage#validate(IRequestCycle)}
* is invoked.
* @see #checkMethodInvocation(Object, String, Object)
* @since 3.0
*/
public static final String ABSTRACTPAGE_VALIDATE_METHOD_ID = "AbstractPage.validate()";
/**
* Method id used to check that {@link IPage#detach()} is invoked.
* @see #checkMethodInvocation(Object, String, Object)
* @since 3.0
*/
public static final String ABSTRACTPAGE_DETACH_METHOD_ID = "AbstractPage.detach()";
/**
* Regular expression defining a simple property name. Used by several different
* parsers. Simple property names match Java variable names; a leading letter
* (or underscore), followed by letters, numbers and underscores.
*
* @since 3.0
*
**/
public static final String SIMPLE_PROPERTY_NAME_PATTERN = "^_?[a-zA-Z]\\w*$";
/**
* Name of an application extension used as a factory for
* {@link org.apache.tapestry.engine.IMonitor} instances. The extension
* must implement {@link org.apache.tapestry.engine.IMonitorFactory}.
*
* @since 3.0
*/
public static final String MONITOR_FACTORY_EXTENSION_NAME =
"org.apache.tapestry.monitor-factory";
/**
* Class name of an {@link ognl.TypeConverter} implementing class
* to use as a type converter for {@link org.apache.tapestry.binding.ExpressionBinding}
*/
public static final String OGNL_TYPE_CONVERTER = "org.apache.tapestry.ognl-type-converter";
/**
* Prevent instantiation.
*
**/
private Tapestry()
{
}
/**
* The version of the framework; this is updated for major releases.
*
**/
public static final String VERSION = readVersion();
/**
* Contains strings loaded from TapestryStrings.properties.
*
* @since 1.0.8
*
**/
private static ResourceBundle _strings;
/**
* A {@link Map} that links Locale names (as in {@link Locale#toString()} to
* {@link Locale} instances. This prevents needless duplication
* of Locales.
*
**/
private static final Map _localeMap = new HashMap();
static {
Locale[] locales = Locale.getAvailableLocales();
for (int i = 0; i < locales.length; i++)
{
_localeMap.put(locales[i].toString(), locales[i]);
}
}
/**
* Used for tracking if a particular super-class method has been invoked.
*/
private static final ThreadLocal _invokedMethodIds = new ThreadLocal();
/**
* A {@link org.apache.tapestry.util.AdaptorRegistry} used to coerce arbitrary objects
* to boolean values.
*
* @see #evaluateBoolean(Object)
**/
private static final AdaptorRegistry _booleanAdaptors = new AdaptorRegistry();
private static abstract class BoolAdaptor
{
/**
* Implemented by subclasses to coerce an object to a boolean.
*
**/
public abstract boolean coerce(Object value);
}
private static class BooleanAdaptor extends BoolAdaptor
{
public boolean coerce(Object value)
{
Boolean b = (Boolean) value;
return b.booleanValue();
}
}
private static class NumberAdaptor extends BoolAdaptor
{
public boolean coerce(Object value)
{
Number n = (Number) value;
return n.intValue() > 0;
}
}
private static class CollectionAdaptor extends BoolAdaptor
{
public boolean coerce(Object value)
{
Collection c = (Collection) value;
return c.size() > 0;
}
}
private static class StringAdaptor extends BoolAdaptor
{
public boolean coerce(Object value)
{
String s = (String) value;
if (s.length() == 0)
return false;
String ts = s.trim();
if (ts.length() == 0)
return false;
// Here probably Boolean.getBoolean(s) should be used,
// but we need the opposite check
if (ts.equalsIgnoreCase("false"))
return false;
return true;
}
}
static {
_booleanAdaptors.register(Boolean.class, new BooleanAdaptor());
_booleanAdaptors.register(Number.class, new NumberAdaptor());
_booleanAdaptors.register(Collection.class, new CollectionAdaptor());
_booleanAdaptors.register(String.class, new StringAdaptor());
// Register a default, catch-all adaptor.
_booleanAdaptors.register(Object.class, new BoolAdaptor()
{
public boolean coerce(Object value)
{
return true;
}
});
}
/**
* {@link AdaptorRegistry} used to extract an {@link Iterator} from
* an arbitrary object.
*
**/
private static AdaptorRegistry _iteratorAdaptors = new AdaptorRegistry();
private abstract static class IteratorAdaptor
{
/**
* Coeerces the object into an {@link Iterator}.
*
**/
abstract public Iterator coerce(Object value);
}
private static class DefaultIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
return (Iterator) value;
}
}
private static class CollectionIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
Collection c = (Collection) value;
if (c.size() == 0)
return null;
return c.iterator();
}
}
private static class ObjectIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
return Collections.singleton(value).iterator();
}
}
private static class ObjectArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
Object[] array = (Object[]) value;
if (array.length == 0)
return null;
return Arrays.asList(array).iterator();
}
}
private static class BooleanArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
boolean[] array = (boolean[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(array[i] ? Boolean.TRUE : Boolean.FALSE);
return l.iterator();
}
}
private static class ByteArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
byte[] array = (byte[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Byte(array[i]));
return l.iterator();
}
}
private static class CharArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
char[] array = (char[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Character(array[i]));
return l.iterator();
}
}
private static class ShortArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
short[] array = (short[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Short(array[i]));
return l.iterator();
}
}
private static class IntArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
int[] array = (int[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Integer(array[i]));
return l.iterator();
}
}
private static class LongArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
long[] array = (long[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Long(array[i]));
return l.iterator();
}
}
private static class FloatArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
float[] array = (float[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Float(array[i]));
return l.iterator();
}
}
private static class DoubleArrayIteratorAdaptor extends IteratorAdaptor
{
public Iterator coerce(Object value)
{
double[] array = (double[]) value;
if (array.length == 0)
return null;
List l = new ArrayList(array.length);
for (int i = 0; i < array.length; i++)
l.add(new Double(array[i]));
return l.iterator();
}
}
static {
_iteratorAdaptors.register(Iterator.class, new DefaultIteratorAdaptor());
_iteratorAdaptors.register(Collection.class, new CollectionIteratorAdaptor());
_iteratorAdaptors.register(Object.class, new ObjectIteratorAdaptor());
_iteratorAdaptors.register(Object[].class, new ObjectArrayIteratorAdaptor());
_iteratorAdaptors.register(boolean[].class, new BooleanArrayIteratorAdaptor());
_iteratorAdaptors.register(byte[].class, new ByteArrayIteratorAdaptor());
_iteratorAdaptors.register(char[].class, new CharArrayIteratorAdaptor());
_iteratorAdaptors.register(short[].class, new ShortArrayIteratorAdaptor());
_iteratorAdaptors.register(int[].class, new IntArrayIteratorAdaptor());
_iteratorAdaptors.register(long[].class, new LongArrayIteratorAdaptor());
_iteratorAdaptors.register(float[].class, new FloatArrayIteratorAdaptor());
_iteratorAdaptors.register(double[].class, new DoubleArrayIteratorAdaptor());
}
/**
* Copys all informal {@link IBinding bindings} from a source component
* to the destination component. Informal bindings are bindings for
* informal parameters. This will overwrite parameters (formal or
* informal) in the
* destination component if there is a naming conflict.
*
*
**/
public static void copyInformalBindings(IComponent source, IComponent destination)
{
Collection names = source.getBindingNames();
if (names == null)
return;
IComponentSpecification specification = source.getSpecification();
Iterator i = names.iterator();
while (i.hasNext())
{
String name = (String) i.next();
// If not a formal parameter, then copy it over.
if (specification.getParameter(name) == null)
{
IBinding binding = source.getBinding(name);
destination.setBinding(name, binding);
}
}
}
/**
* Evaluates an object to determine its boolean value.
*
* <table border=1>
* <tr> <th>Class</th> <th>Test</th> </tr>
* <tr>
* <td>{@link Boolean}</td>
* <td>Self explanatory.</td>
* </tr>
* <tr> <td>{@link Number}</td>
* <td>True if non-zero, false otherwise.</td>
* </tr>
* <tr>
* <td>{@link Collection}</td>
* <td>True if contains any elements (non-zero size), false otherwise.</td>
* </tr>
* <tr>
* <td>{@link String}</td>
* <td>True if contains any non-whitespace characters, false otherwise.</td>
* </tr>
* <tr>
* <td>Any Object array type</td>
* <td>True if contains any elements (non-zero length), false otherwise.</td>
* <tr>
*</table>
*
* <p>Any other non-null object evaluates to true.
*
**/
public static boolean evaluateBoolean(Object value)
{
if (value == null)
return false;
Class valueClass = value.getClass();
if (valueClass.isArray())
{
Object[] array = (Object[]) value;
return array.length > 0;
}
BoolAdaptor adaptor = (BoolAdaptor) _booleanAdaptors.getAdaptor(valueClass);
return adaptor.coerce(value);
}
/**
* Converts an Object into an {@link Iterator}, following some basic rules.
*
* <table border=1>
* <tr><th>Input Class</th> <th>Result</th> </tr>
* <tr><td>array</td> <td>Converted to a {@link List} and iterator returned.
* null returned if the array is empty. This works with both object arrays and
* arrays of scalars. </td>
* </tr>
* <tr><td>{@link Iterator}</td> <td>Returned as-is.</td>
* <tr><td>{@link Collection}</td> <td>Iterator returned, or null
* if the Collection is empty</td> </tr>
* <tr><td>Any other</td> <td>{@link Iterator} for singleton collection returned</td> </tr>
* <tr><td>null</td> <td>null returned</td> </tr>
* </table>
*
**/
public static Iterator coerceToIterator(Object value)
{
if (value == null)
return null;
IteratorAdaptor adaptor = (IteratorAdaptor) _iteratorAdaptors.getAdaptor(value.getClass());
return adaptor.coerce(value);
}
/**
* Gets the {@link Locale} for the given string, which is the result
* of {@link Locale#toString()}. If no such locale is already registered,
* a new instance is created, registered and returned.
*
*
**/
public static Locale getLocale(String s)
{
Locale result = null;
synchronized (_localeMap)
{
result = (Locale) _localeMap.get(s);
}
if (result == null)
{
StringSplitter splitter = new StringSplitter('_');
String[] terms = splitter.splitToArray(s);
switch (terms.length)
{
case 1 :
result = new Locale(terms[0], "");
break;
case 2 :
result = new Locale(terms[0], terms[1]);
break;
case 3 :
result = new Locale(terms[0], terms[1], terms[2]);
break;
default :
throw new IllegalArgumentException(
"Unable to convert '" + s + "' to a Locale.");
}
synchronized (_localeMap)
{
_localeMap.put(s, result);
}
}
return result;
}
/**
* Closes the stream (if not null), ignoring any {@link IOException} thrown.
*
* @since 1.0.2
*
**/
public static void close(InputStream stream)
{
if (stream != null)
{
try
{
stream.close();
}
catch (IOException ex)
{
// Ignore.
}
}
}
/**
* Gets a string from the TapestryStrings resource bundle.
* The string in the bundle
* is treated as a pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
*
* @since 1.0.8
*
**/
public static String format(String key, Object[] args)
{
if (_strings == null)
_strings = ResourceBundle.getBundle("org.apache.tapestry.TapestryStrings");
String pattern = _strings.getString(key);
if (args == null)
return pattern;
return MessageFormat.format(pattern, args);
}
/**
* Convienience method for invoking {@link #format(String, Object[])}.
*
* @since 3.0
**/
public static String getMessage(String key)
{
return format(key, null);
}
/**
* Convienience method for invoking {@link #format(String, Object[])}.
*
* @since 3.0
**/
public static String format(String key, Object arg)
{
return format(key, new Object[] { arg });
}
/**
* Convienience method for invoking {@link #format(String, Object[])}.
*
* @since 3.0
*
**/
public static String format(String key, Object arg1, Object arg2)
{
return format(key, new Object[] { arg1, arg2 });
}
/**
* Convienience method for invoking {@link #format(String, Object[])}.
*
* @since 3.0
*
**/
public static String format(String key, Object arg1, Object arg2, Object arg3)
{
return format(key, new Object[] { arg1, arg2, arg3 });
}
private static final String UNKNOWN_VERSION = "Unknown";
/**
* Invoked when the class is initialized to read the current version file.
*
**/
private static final String readVersion()
{
Properties props = new Properties();
try
{
InputStream in = Tapestry.class.getResourceAsStream("Version.properties");
if (in == null)
return UNKNOWN_VERSION;
props.load(in);
in.close();
return props.getProperty("framework.version", UNKNOWN_VERSION);
}
catch (IOException ex)
{
return UNKNOWN_VERSION;
}
}
/**
* Returns the size of a collection, or zero if the collection is null.
*
* @since 2.2
*
**/
public static int size(Collection c)
{
if (c == null)
return 0;
return c.size();
}
/**
* Returns the length of the array, or 0 if the array is null.
*
* @since 2.2
*
**/
public static int size(Object[] array)
{
if (array == null)
return 0;
return array.length;
}
/**
* Returns true if the Map is null or empty.
*
* @since 3.0
*
**/
public static boolean isEmpty(Map map)
{
return map == null || map.isEmpty();
}
/**
* Returns true if the Collection is null or empty.
*
* @since 3.0
*
**/
public static boolean isEmpty(Collection c)
{
return c == null || c.isEmpty();
}
/**
* Converts a {@link Map} to an even-sized array of key/value
* pairs. This may be useful when using a Map as service parameters
* (with {@link org.apache.tapestry.link.DirectLink}. Assuming the keys
* and values are simple objects (String, Boolean, Integer, etc.), then
* the representation as an array will encode more efficiently
* (via {@link org.apache.tapestry.util.io.DataSqueezer} than
* serializing the Map and its contents.
*
* @return the array of keys and values, or null if the input
* Map is null or empty
*
* @since 2.2
**/
public static Object[] convertMapToArray(Map map)
{
if (isEmpty(map))
return null;
Set entries = map.entrySet();
Object[] result = new Object[2 * entries.size()];
int x = 0;
Iterator i = entries.iterator();
while (i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
result[x++] = entry.getKey();
result[x++] = entry.getValue();
}
return result;
}
/**
* Converts an even-sized array of objects back
* into a {@link Map}.
*
* @see #convertMapToArray(Map)
* @return a Map, or null if the array is null or empty
* @since 2.2
*
**/
public static Map convertArrayToMap(Object[] array)
{
if (array == null || array.length == 0)
return null;
if (array.length % 2 != 0)
throw new IllegalArgumentException(getMessage("Tapestry.even-sized-array"));
Map result = new HashMap();
int x = 0;
while (x < array.length)
{
Object key = array[x++];
Object value = array[x++];
result.put(key, value);
}
return result;
}
/**
* Returns the application root location, which is in the
* {@link javax.servlet.ServletContext}, based on
* the {@link javax.servlet.http.HttpServletRequest#getServletPath() servlet path}.
*
* @since 3.0
*
**/
public static IResourceLocation getApplicationRootLocation(IRequestCycle cycle)
{
RequestContext context = cycle.getRequestContext();
ServletContext servletContext = context.getServlet().getServletContext();
String servletPath = context.getRequest().getServletPath();
// Could strip off the servlet name (i.e., "app" in "/app") but
// there's no need.
return new ContextResourceLocation(servletContext, servletPath);
}
/**
* Given a Class, creates a presentable name for the class, even if the
* class is a scalar type or Array type.
*
* @since 3.0
*/
public static String getClassName(Class subject)
{
if (subject.isArray())
return getClassName(subject.getComponentType()) + "[]";
return subject.getName();
}
/**
* Selects the first {@link org.apache.tapestry.ILocation} in an array of objects.
* Skips over nulls. The objects may be instances of
* {Location or {@link org.apache.tapestry.ILocatable}. May return null
* if no Location found found.
*
**/
public static ILocation findLocation(Object[] locations)
{
for (int i = 0; i < locations.length; i++)
{
Object location = locations[i];
if (location == null)
continue;
if (location instanceof ILocation)
return (ILocation) location;
if (location instanceof ILocatable)
{
ILocatable locatable = (ILocatable) location;
ILocation result = locatable.getLocation();
if (result != null)
return result;
}
}
return null;
}
/**
* Creates an exception indicating the binding value is null.
*
* @since 3.0
*
**/
public static BindingException createNullBindingException(IBinding binding)
{
return new BindingException(getMessage("null-value-for-binding"), binding);
}
/** @since 3.0 **/
public static ApplicationRuntimeException createNoSuchComponentException(
IComponent component,
String id,
ILocation location)
{
return new ApplicationRuntimeException(
format("no-such-component", component.getExtendedId(), id),
component,
location,
null);
}
/** @since 3.0 **/
public static BindingException createRequiredParameterException(
IComponent component,
String parameterName)
{
return new BindingException(
format("required-parameter", parameterName, component.getExtendedId()),
component,
null,
component.getBinding(parameterName),
null);
}
/** @since 3.0 **/
public static ApplicationRuntimeException createRenderOnlyPropertyException(
IComponent component,
String propertyName)
{
return new ApplicationRuntimeException(
format("render-only-property", propertyName, component.getExtendedId()),
component,
null,
null);
}
/**
* Clears the list of method invocations.
* @see #checkMethodInvocation(Object, String, Object)
*
* @since 3.0
*/
public static void clearMethodInvocations()
{
_invokedMethodIds.set(null);
}
/**
* Adds a method invocation to the list of invocations. This is done
* in a super-class implementations.
*
* @see #checkMethodInvocation(Object, String, Object)
* @since 3.0
*
*/
public static void addMethodInvocation(Object methodId)
{
List methodIds = (List) _invokedMethodIds.get();
if (methodIds == null)
{
methodIds = new ArrayList();
_invokedMethodIds.set(methodIds);
}
methodIds.add(methodId);
}
/**
* Checks to see if a particular method has been invoked. The method is identified by a
* methodId (usually a String). The methodName and object are used to create an
* error message.
*
* <p>
* The caller should invoke {@link #clearMethodInvocations()}, then invoke a method on
* the object. The super-class implementation should invoke {@link #addMethodInvocation(Object)}
* to indicate that it was, in fact, invoked. The caller then invokes
* this method to vlaidate that the super-class implementation was invoked.
*
* <p>
* The list of method invocations is stored in a {@link ThreadLocal} variable.
*
* @since 3.0
*/
public static void checkMethodInvocation(Object methodId, String methodName, Object object)
{
List methodIds = (List) _invokedMethodIds.get();
if (methodIds != null && methodIds.contains(methodId))
return;
throw new ApplicationRuntimeException(
Tapestry.format(
"Tapestry.missing-method-invocation",
object.getClass().getName(),
methodName));
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(
IComponent component,
String propertyName,
Object newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event = new ObservedChangeEvent(component, propertyName, newValue);
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(
IComponent component,
String propertyName,
boolean newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(
component,
propertyName,
newValue ? Boolean.TRUE : Boolean.FALSE);
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(
IComponent component,
String propertyName,
double newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Double(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(
IComponent component,
String propertyName,
float newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Float(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(IComponent component, String propertyName, int newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Integer(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(IComponent component, String propertyName, long newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Long(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(IComponent component, String propertyName, char newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Character(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(IComponent component, String propertyName, byte newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Byte(newValue));
observer.observeChange(event);
}
/**
* Method used by pages and components to send notifications about
* property changes.
*
* @param component the component containing the property
* @param propertyName the name of the property which changed
* @param newValue the new value for the property
*
* @since 3.0
*/
public static void fireObservedChange(
IComponent component,
String propertyName,
short newValue)
{
ChangeObserver observer = component.getPage().getChangeObserver();
if (observer == null)
return;
ObservedChangeEvent event =
new ObservedChangeEvent(component, propertyName, new Short(newValue));
observer.observeChange(event);
}
/**
* Returns true if the input is null or contains only whitespace.
*
* <p>
* Note: Yes, you'd think we'd use <code>StringUtils</code>, but with
* the change in names and behavior between releases, it is smarter
* to just implement our own little method!
*
* @since 3.0
*/
public static boolean isBlank(String input)
{
if (input == null || input.length() == 0)
return true;
return input.trim().length() == 0;
}
/**
* Returns true if the input is not null and not empty (or only whitespace).
*
* @since 3.0
*
*/
public static boolean isNonBlank(String input)
{
return !isBlank(input);
}
}