blob: d9acbf7af94895c9bb4b1d62ddc85fe7ad74a82c [file] [log] [blame]
// Copyright 2006, 2007, 2008, 2009, 2010 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.tapestry5.internal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.tapestry5.Asset;
import org.apache.tapestry5.Asset2;
import org.apache.tapestry5.EventContext;
import org.apache.tapestry5.Link;
import org.apache.tapestry5.OptionModel;
import org.apache.tapestry5.PropertyConduit;
import org.apache.tapestry5.SelectModel;
import org.apache.tapestry5.func.F;
import org.apache.tapestry5.func.Mapper;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.Orderable;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.ComponentEventRequestParameters;
import org.apache.tapestry5.services.LinkCreationListener;
import org.apache.tapestry5.services.LinkCreationListener2;
import org.apache.tapestry5.services.PageRenderRequestParameters;
import org.apache.tapestry5.services.javascript.StylesheetLink;
/**
* Shared utility methods used by various implementation classes.
*/
@SuppressWarnings("all")
public class TapestryInternalUtils
{
private static final String SLASH = "/";
private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);
private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
private static final Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*");
private static final int BUFFER_SIZE = 5000;
/**
* Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
* characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
* following word), thus "user_id" also becomes "User Id".
*/
public static String toUserPresentable(String id)
{
StringBuilder builder = new StringBuilder(id.length() * 2);
char[] chars = id.toCharArray();
boolean postSpace = true;
boolean upcaseNext = true;
for (char ch : chars)
{
if (upcaseNext)
{
builder.append(Character.toUpperCase(ch));
upcaseNext = false;
continue;
}
if (ch == '_')
{
builder.append(' ');
upcaseNext = true;
continue;
}
boolean upperCase = Character.isUpperCase(ch);
if (upperCase && !postSpace)
builder.append(' ');
builder.append(ch);
postSpace = upperCase;
}
return builder.toString();
}
public static Map<String, String> mapFromKeysAndValues(String... keysAndValues)
{
Map<String, String> result = CollectionFactory.newMap();
int i = 0;
while (i < keysAndValues.length)
{
String key = keysAndValues[i++];
String value = keysAndValues[i++];
result.put(key, value);
}
return result;
}
/**
* Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is
* omitted, then the same value is used for both value and label.
*
* @param input
* @return
*/
public static OptionModel toOptionModel(String input)
{
assert input != null;
int equalsx = input.indexOf('=');
if (equalsx < 0)
return new OptionModelImpl(input);
String value = input.substring(0, equalsx);
String label = input.substring(equalsx + 1);
return new OptionModelImpl(label, value);
}
/**
* Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits
* on commas. Ignores whitespace around commas.
*
* @param input
* comma seperated list of terms
* @return list of option models
*/
public static List<OptionModel> toOptionModels(String input)
{
assert input != null;
List<OptionModel> result = CollectionFactory.newList();
for (String term : input.split(","))
result.add(toOptionModel(term.trim()));
return result;
}
/**
* Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups).
*
* @param input
* @return
*/
public static SelectModel toSelectModel(String input)
{
List<OptionModel> options = toOptionModels(input);
return new SelectModelImpl(null, options);
}
/**
* Converts a map entry to an {@link OptionModel}.
*
* @param input
* @return
*/
public static OptionModel toOptionModel(Map.Entry input)
{
assert input != null;
String label = input.getValue() != null ? String.valueOf(input.getValue()) : "";
return new OptionModelImpl(label, input.getKey());
}
/**
* Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}.
*
* @param input
* map of elements
* @return list of option models
*/
public static <K, V> List<OptionModel> toOptionModels(Map<K, V> input)
{
assert input != null;
List<OptionModel> result = CollectionFactory.newList();
for (Map.Entry entry : input.entrySet())
result.add(toOptionModel(entry));
return result;
}
/**
* Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups).
*
* @param input
* @return
*/
public static <K, V> SelectModel toSelectModel(Map<K, V> input)
{
List<OptionModel> options = toOptionModels(input);
return new SelectModelImpl(null, options);
}
/**
* Converts an object to an {@link OptionModel}.
*
* @param input
* @return
*/
public static OptionModel toOptionModel(Object input)
{
String label = (input != null ? String.valueOf(input) : "");
return new OptionModelImpl(label, input);
}
/**
* Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}.
*
* @param input
* list of elements
* @return list of option models
*/
public static <E> List<OptionModel> toOptionModels(List<E> input)
{
assert input != null;
List<OptionModel> result = CollectionFactory.newList();
for (E element : input)
result.add(toOptionModel(element));
return result;
}
/**
* Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups).
*
* @param input
* @return
*/
public static <E> SelectModel toSelectModel(List<E> input)
{
List<OptionModel> options = toOptionModels(input);
return new SelectModelImpl(null, options);
}
/**
* Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are
* trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
*
* @param input
* @return
*/
public static KeyValue parseKeyValue(String input)
{
int pos = input.indexOf('=');
if (pos < 1)
throw new IllegalArgumentException(InternalMessages.badKeyValue(input));
String key = input.substring(0, pos);
String value = input.substring(pos + 1);
return new KeyValue(key.trim(), value.trim());
}
/**
* Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
* etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
* underscore).
*
* @param expression
* a property expression
* @return the expression with punctuation removed
*/
public static String extractIdFromPropertyExpression(String expression)
{
return replace(expression, NON_WORD_PATTERN, "");
}
/**
* Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
* user presentable form.
*/
public static String defaultLabel(String id, Messages messages, String propertyExpression)
{
String key = id + "-label";
if (messages.contains(key))
return messages.get(key);
return toUserPresentable(extractIdFromPropertyExpression(lastTerm(propertyExpression)));
}
/**
* Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that
* expression, by locating the last period ('.') in the string.
*/
public static String lastTerm(String input)
{
int dotx = input.lastIndexOf('.');
return input.substring(dotx + 1);
}
/**
* Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class
* attribute value.
*
* @param classes
* classes to combine
* @return the joined classes, or null if classes is empty
*/
public static String toClassAttributeValue(List<String> classes)
{
if (classes.isEmpty())
return null;
return InternalUtils.join(classes, " ");
}
/**
* Converts an enum to a label string, allowing for overrides from a message catalog.
* <p/>
* <ul>
* <li>As key <em>prefix</em>.<em>name</em> if present. Ex: "ElementType.LOCAL_VARIABLE"
* <li>As key <em>name</em> if present, i.e., "LOCAL_VARIABLE".
* <li>As a user-presentable version of the name, i.e., "Local Variable".
* </ul>
*
* @param messages
* the messages to search for the label
* @param prefix
* @param value
* to get a label for
* @return the label
*/
public static String getLabelForEnum(Messages messages, String prefix, Enum value)
{
String name = value.name();
String key = prefix + "." + name;
if (messages.contains(key))
return messages.get(key);
if (messages.contains(name))
return messages.get(name);
return toUserPresentable(name.toLowerCase());
}
public static String getLabelForEnum(Messages messages, Enum value)
{
String prefix = lastTerm(value.getClass().getName());
return getLabelForEnum(messages, prefix, value);
}
private static String replace(String input, Pattern pattern, String replacement)
{
return pattern.matcher(input).replaceAll(replacement);
}
/**
* Determines if the two values are equal. They are equal if they are the exact same value (including if they are
* both null). Otherwise standard equals() comparison is used.
*
* @param <T>
* @param left
* value to compare, possibly null
* @param right
* value to compare, possibly null
* @return true if same value, both null, or equal
*/
public static <T> boolean isEqual(T left, T right)
{
if (left == right)
return true;
if (left == null)
return right == null;
return left.equals(right);
}
/**
* Splits a path at each slash.
*/
public static String[] splitPath(String path)
{
return SLASH_PATTERN.split(path);
}
/**
* Splits a value around commas. Whitespace around the commas is removed, as is leading and trailing whitespace.
*
* @since 5.1.0.0
*/
public static String[] splitAtCommas(String value)
{
if (InternalUtils.isBlank(value))
return InternalConstants.EMPTY_STRING_ARRAY;
return COMMA_PATTERN.split(value.trim());
}
/**
* Copies some content from an input stream to an output stream. It is the caller's responsibility to close the
* streams.
*
* @param in
* source of data
* @param out
* sink of data
* @throws IOException
* @since 5.1.0.0
*/
public static void copy(InputStream in, OutputStream out) throws IOException
{
byte[] buffer = new byte[BUFFER_SIZE];
while (true)
{
int length = in.read(buffer);
if (length < 0)
break;
out.write(buffer, 0, length);
}
// TAPESTRY-2415: WebLogic needs this flush() call.
out.flush();
}
public static boolean isEqual(EventContext left, EventContext right)
{
if (left == right)
return true;
int count = left.getCount();
if (count != right.getCount())
return false;
for (int i = 0; i < count; i++)
{
if (!left.get(Object.class, i).equals(right.get(Object.class, i)))
return false;
}
return true;
}
/**
* Converts an Asset to an Asset2 if necessary. When actually wrapping an Asset as an Asset2, the asset is assumed
* to be variant (i.e., not cacheable).
*
* @since 5.1.0.0
*/
public static Asset2 toAsset2(final Asset asset)
{
if (asset instanceof Asset2)
return (Asset2) asset;
return new Asset2()
{
/** Returns false. */
public boolean isInvariant()
{
return false;
}
public Resource getResource()
{
return asset.getResource();
}
public String toClientURL()
{
return asset.toClientURL();
}
@Override
public String toString()
{
return asset.toString();
}
};
}
public static InternalPropertyConduit toInternalPropertyConduit(final PropertyConduit conduit)
{
if (conduit instanceof InternalPropertyConduit)
return (InternalPropertyConduit) conduit;
return new InternalPropertyConduit()
{
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
{
return conduit.getAnnotation(annotationClass);
}
public void set(Object instance, Object value)
{
conduit.set(instance, value);
}
public Class getPropertyType()
{
return conduit.getPropertyType();
}
public Object get(Object instance)
{
return conduit.get(instance);
}
public String getPropertyName()
{
return null;
}
};
}
/**
* @param mixinDef
* the original mixin definition.
* @return an Orderable whose id is the mixin name.
*/
public static Orderable<String> mixinTypeAndOrder(String mixinDef)
{
int idx = mixinDef.indexOf("::");
if (idx == -1) { return new Orderable(mixinDef, mixinDef); }
String type = mixinDef.substring(0, idx);
String[] constraints = splitMixinConstraints(mixinDef.substring(idx + 2));
return new Orderable(type, type, constraints);
}
public static String[] splitMixinConstraints(String s)
{
return InternalUtils.isBlank(s) ? null : s.split(";");
}
/**
* Common mapper, used primarily with {@link F#map(Mapper, java.util.Collection)}
*
* @since 5.2.0
*/
public static Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>()
{
public StylesheetLink map(Asset input)
{
return new StylesheetLink(input);
};
};
public static LinkCreationListener2 toLinkCreationListener2(final LinkCreationListener delegate)
{
return new LinkCreationListener2()
{
public void createdPageRenderLink(Link link, PageRenderRequestParameters parameters)
{
delegate.createdPageRenderLink(link);
}
public void createdComponentEventLink(Link link, ComponentEventRequestParameters parameters)
{
delegate.createdComponentEventLink(link);
}
};
}
}