http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java new file mode 100644 index 0000000..49b0b15 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java @@ -0,0 +1,59 @@ +// Copyright 2008 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.ioc.internal.services; + +import org.apache.tapestry5.ioc.AnnotationProvider; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * Chain of command for {@link org.apache.tapestry5.ioc.AnnotationProvider}. + */ +public class AnnotationProviderChain implements AnnotationProvider +{ + private final AnnotationProvider[] providers; + + public AnnotationProviderChain(AnnotationProvider[] providers) + { + this.providers = providers; + } + + /** + * Creates an AnnotationProvider from the list of providers. Returns either an {@link AnnotationProviderChain} or + * the sole element in the list. + */ + public static AnnotationProvider create(List<AnnotationProvider> providers) + { + int size = providers.size(); + + if (size == 1) return providers.get(0); + + return new AnnotationProviderChain(providers.toArray(new AnnotationProvider[providers.size()])); + } + + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) + { + for (AnnotationProvider p : providers) + { + T result = p.getAnnotation(annotationClass); + + if (result != null) return result; + } + + return null; + } +}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java new file mode 100644 index 0000000..4a5dece --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java @@ -0,0 +1,54 @@ +// Copyright 2006, 2007 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.ioc.internal.services; + +import org.apache.tapestry5.ioc.services.Coercion; + +/** + * Combines two coercions to create a coercion through an intermediate type. + * + * @param <S> The source (input) type + * @param <I> The intermediate type + * @param <T> The target (output) type + */ +public class CompoundCoercion<S, I, T> implements Coercion<S, T> +{ + private final Coercion<S, I> op1; + + private final Coercion<I, T> op2; + + public CompoundCoercion(Coercion<S, I> op1, Coercion<I, T> op2) + { + this.op1 = op1; + this.op2 = op2; + } + + @Override + public T coerce(S input) + { + // Run the input through the first operation (S --> I), then run the result of that through + // the second operation (I --> T). + + I intermediate = op1.coerce(input); + + return op2.coerce(intermediate); + } + + @Override + public String toString() + { + return String.format("%s, %s", op1, op2); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java new file mode 100644 index 0000000..e92ef2d --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java @@ -0,0 +1,68 @@ +// Copyright 2006, 2007, 2011, 2012 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.ioc.internal.services; + +import org.apache.tapestry5.ioc.Messages; +import org.apache.tapestry5.ioc.internal.util.MessagesImpl; +import org.apache.tapestry5.ioc.services.Coercion; +import org.apache.tapestry5.plastic.PlasticUtils; + +public class ServiceMessages +{ + private final static Messages MESSAGES = MessagesImpl.forClass(ServiceMessages.class); + + private ServiceMessages() + { + } + + public static String noSuchProperty(Class clazz, String propertyName) + { + return MESSAGES.format("no-such-property", clazz.getName(), propertyName); + } + + + public static String readFailure(String propertyName, Object instance, Throwable cause) + { + return MESSAGES.format("read-failure", propertyName, instance, cause); + } + + public static String propertyTypeMismatch(String propertyName, Class sourceClass, Class propertyType, + Class expectedType) + { + return MESSAGES.format("property-type-mismatch", propertyName, sourceClass.getName(), propertyType.getName(), + expectedType.getName()); + } + + public static String shutdownListenerError(Object listener, Throwable cause) + { + return MESSAGES.format("shutdown-listener-error", listener, cause); + } + + public static String failedCoercion(Object input, Class targetType, Coercion coercion, Throwable cause) + { + return MESSAGES.format("failed-coercion", String.valueOf(input), PlasticUtils.toTypeName(targetType), + coercion, cause); + } + + public static String registryShutdown(String serviceId) + { + return MESSAGES.format("registry-shutdown", serviceId); + } + + public static String serviceBuildFailure(String serviceId, Throwable cause) + { + return MESSAGES.format("service-build-failure", serviceId, cause); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java new file mode 100644 index 0000000..0769b7e --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java @@ -0,0 +1,65 @@ +// Copyright 2007 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.ioc.internal.services; + +import org.apache.tapestry5.ioc.Location; +import org.apache.tapestry5.ioc.Resource; + +/** + * Implementation of {@link Location} used when the underlying resource isn't really known. + */ +public final class StringLocation implements Location +{ + private final String description; + + private final int line; + + public StringLocation(String description, int line) + { + this.description = description; + this.line = line; + } + + @Override + public String toString() + { + return description; + } + + /** + * Returns 0. + */ + @Override + public int getColumn() + { + return 0; + } + + @Override + public int getLine() + { + return line; + } + + /** + * Returns null; we don't know where the file really is (it's probably a class on the class path). + */ + @Override + public Resource getResource() + { + return null; + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java new file mode 100644 index 0000000..6481384 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java @@ -0,0 +1,508 @@ +// Copyright 2006, 2007, 2008, 2010, 2012 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.ioc.internal.services; + +import org.apache.tapestry5.func.F; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InheritanceSearch; +import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; +import org.apache.tapestry5.ioc.internal.util.LockSupport; +import org.apache.tapestry5.ioc.services.Coercion; +import org.apache.tapestry5.ioc.services.CoercionTuple; +import org.apache.tapestry5.ioc.services.TypeCoercer; +import org.apache.tapestry5.ioc.util.AvailableValues; +import org.apache.tapestry5.ioc.util.UnknownValueException; +import org.apache.tapestry5.plastic.PlasticUtils; +import org.apache.tapestry5.util.StringToEnumCoercion; + +import java.util.*; + +@SuppressWarnings("all") +public class TypeCoercerImpl extends LockSupport implements TypeCoercer +{ + // Constructed from the service's configuration. + + private final Map<Class, List<CoercionTuple>> sourceTypeToTuple = CollectionFactory.newMap(); + + /** + * A coercion to a specific target type. Manages a cache of coercions to specific types. + */ + private class TargetCoercion + { + private final Class type; + + private final Map<Class, Coercion> cache = CollectionFactory.newConcurrentMap(); + + TargetCoercion(Class type) + { + this.type = type; + } + + void clearCache() + { + cache.clear(); + } + + Object coerce(Object input) + { + Class sourceType = input != null ? input.getClass() : Void.class; + + if (type.isAssignableFrom(sourceType)) + { + return input; + } + + Coercion c = getCoercion(sourceType); + + try + { + return type.cast(c.coerce(input)); + } catch (Exception ex) + { + throw new RuntimeException(ServiceMessages.failedCoercion(input, type, c, ex), ex); + } + } + + String explain(Class sourceType) + { + return getCoercion(sourceType).toString(); + } + + private Coercion getCoercion(Class sourceType) + { + Coercion c = cache.get(sourceType); + + if (c == null) + { + c = findOrCreateCoercion(sourceType, type); + cache.put(sourceType, c); + } + + return c; + } + } + + /** + * Map from a target type to a TargetCoercion for that type. + */ + private final Map<Class, TargetCoercion> typeToTargetCoercion = new WeakHashMap<Class, TargetCoercion>(); + + private static final Coercion NO_COERCION = new Coercion<Object, Object>() + { + @Override + public Object coerce(Object input) + { + return input; + } + }; + + private static final Coercion COERCION_NULL_TO_OBJECT = new Coercion<Void, Object>() + { + @Override + public Object coerce(Void input) + { + return null; + } + + @Override + public String toString() + { + return "null --> null"; + } + }; + + public TypeCoercerImpl(Collection<CoercionTuple> tuples) + { + for (CoercionTuple tuple : tuples) + { + Class key = tuple.getSourceType(); + + InternalCommonsUtils.addToMapList(sourceTypeToTuple, key, tuple); + } + } + + @Override + @SuppressWarnings("unchecked") + public Object coerce(Object input, Class targetType) + { + assert targetType != null; + + Class effectiveTargetType = PlasticUtils.toWrapperType(targetType); + + if (effectiveTargetType.isInstance(input)) + { + return input; + } + + + return getTargetCoercion(effectiveTargetType).coerce(input); + } + + @Override + @SuppressWarnings("unchecked") + public <S, T> Coercion<S, T> getCoercion(Class<S> sourceType, Class<T> targetType) + { + assert sourceType != null; + assert targetType != null; + + Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType); + Class effectiveTargetType = PlasticUtils.toWrapperType(targetType); + + if (effectiveTargetType.isAssignableFrom(effectiveSourceType)) + { + return NO_COERCION; + } + + return getTargetCoercion(effectiveTargetType).getCoercion(effectiveSourceType); + } + + @Override + @SuppressWarnings("unchecked") + public <S, T> String explain(Class<S> sourceType, Class<T> targetType) + { + assert sourceType != null; + assert targetType != null; + + Class effectiveTargetType = PlasticUtils.toWrapperType(targetType); + Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType); + + // Is a coercion even necessary? Not if the target type is assignable from the + // input value. + + if (effectiveTargetType.isAssignableFrom(effectiveSourceType)) + { + return ""; + } + + return getTargetCoercion(effectiveTargetType).explain(effectiveSourceType); + } + + private TargetCoercion getTargetCoercion(Class targetType) + { + try + { + acquireReadLock(); + + TargetCoercion tc = typeToTargetCoercion.get(targetType); + + return tc != null ? tc : createAndStoreNewTargetCoercion(targetType); + } finally + { + releaseReadLock(); + } + } + + private TargetCoercion createAndStoreNewTargetCoercion(Class targetType) + { + try + { + upgradeReadLockToWriteLock(); + + // Inner check since some other thread may have beat us to it. + + TargetCoercion tc = typeToTargetCoercion.get(targetType); + + if (tc == null) + { + tc = new TargetCoercion(targetType); + typeToTargetCoercion.put(targetType, tc); + } + + return tc; + } finally + { + downgradeWriteLockToReadLock(); + } + } + + @Override + public void clearCache() + { + try + { + acquireReadLock(); + + // There's no need to clear the typeToTargetCoercion map, as it is a WeakHashMap and + // will release the keys for classes that are no longer in existence. On the other hand, + // there's likely all sorts of references to unloaded classes inside each TargetCoercion's + // individual cache, so clear all those. + + for (TargetCoercion tc : typeToTargetCoercion.values()) + { + // Can tc ever be null? + + tc.clearCache(); + } + } finally + { + releaseReadLock(); + } + } + + /** + * Here's the real meat; we do a search of the space to find coercions, or a system of + * coercions, that accomplish + * the desired coercion. + * <p/> + * There's <strong>TREMENDOUS</strong> room to improve this algorithm. For example, inheritance lists could be + * cached. Further, there's probably more ways to early prune the search. However, even with dozens or perhaps + * hundreds of tuples, I suspect the search will still grind to a conclusion quickly. + * <p/> + * The order of operations should help ensure that the most efficient tuple chain is located. If you think about how + * tuples are added to the queue, there are two factors: size (the number of steps in the coercion) and + * "class distance" (that is, number of steps up the inheritance hiearchy). All the appropriate 1 step coercions + * will be considered first, in class distance order. Along the way, we'll queue up all the 2 step coercions, again + * in class distance order. By the time we reach some of those, we'll have begun queueing up the 3 step coercions, and + * so forth, until we run out of input tuples we can use to fabricate multi-step compound coercions, or reach a + * final response. + * <p/> + * This does create a good number of short lived temporary objects (the compound tuples), but that's what the GC is + * really good at. + * + * @param sourceType + * @param targetType + * @return coercer from sourceType to targetType + */ + @SuppressWarnings("unchecked") + private Coercion findOrCreateCoercion(Class sourceType, Class targetType) + { + if (sourceType == Void.class) + { + return searchForNullCoercion(targetType); + } + + // These are instance variables because this method may be called concurrently. + // On a true race, we may go to the work of seeking out and/or fabricating + // a tuple twice, but it's more likely that different threads are looking + // for different source/target coercions. + + Set<CoercionTuple> consideredTuples = CollectionFactory.newSet(); + LinkedList<CoercionTuple> queue = CollectionFactory.newLinkedList(); + + seedQueue(sourceType, targetType, consideredTuples, queue); + + while (!queue.isEmpty()) + { + CoercionTuple tuple = queue.removeFirst(); + + // If the tuple results in a value type that is assignable to the desired target type, + // we're done! Later, we may add a concept of "cost" (i.e. number of steps) or + // "quality" (how close is the tuple target type to the desired target type). Cost + // is currently implicit, as compound tuples are stored deeper in the queue, + // so simpler coercions will be located earlier. + + Class tupleTargetType = tuple.getTargetType(); + + if (targetType.isAssignableFrom(tupleTargetType)) + { + return tuple.getCoercion(); + } + + // So .. this tuple doesn't get us directly to the target type. + // However, it *may* get us part of the way. Each of these + // represents a coercion from the source type to an intermediate type. + // Now we're going to look for conversions from the intermediate type + // to some other type. + + queueIntermediates(sourceType, targetType, tuple, consideredTuples, queue); + } + + // Not found anywhere. Identify the source and target type and a (sorted) list of + // all the known coercions. + + throw new UnknownValueException(String.format("Could not find a coercion from type %s to type %s.", + sourceType.getName(), targetType.getName()), buildCoercionCatalog()); + } + + /** + * Coercion from null is special; we match based on the target type and its not a spanning + * search. In many cases, we + * return a pass-thru that leaves the value as null. + * + * @param targetType + * desired type + * @return the coercion + */ + private Coercion searchForNullCoercion(Class targetType) + { + List<CoercionTuple> tuples = getTuples(Void.class, targetType); + + for (CoercionTuple tuple : tuples) + { + Class tupleTargetType = tuple.getTargetType(); + + if (targetType.equals(tupleTargetType)) + return tuple.getCoercion(); + } + + // Typical case: no match, this coercion passes the null through + // as null. + + return COERCION_NULL_TO_OBJECT; + } + + /** + * Builds a string listing all the coercions configured for the type coercer, sorted + * alphabetically. + */ + @SuppressWarnings("unchecked") + private AvailableValues buildCoercionCatalog() + { + List<CoercionTuple> masterList = CollectionFactory.newList(); + + for (List<CoercionTuple> list : sourceTypeToTuple.values()) + { + masterList.addAll(list); + } + + return new AvailableValues("Configured coercions", masterList); + } + + /** + * Seeds the pool with the initial set of coercions for the given type. + */ + private void seedQueue(Class sourceType, Class targetType, Set<CoercionTuple> consideredTuples, + LinkedList<CoercionTuple> queue) + { + // Work from the source type up looking for tuples + + for (Class c : new InheritanceSearch(sourceType)) + { + List<CoercionTuple> tuples = getTuples(c, targetType); + + if (tuples == null) + { + continue; + } + + for (CoercionTuple tuple : tuples) + { + queue.addLast(tuple); + consideredTuples.add(tuple); + } + + // Don't pull in Object -> type coercions when doing + // a search from null. + + if (sourceType == Void.class) + { + return; + } + } + } + + /** + * Creates and adds to the pool a new set of coercions based on an intermediate tuple. Adds + * compound coercion tuples + * to the end of the queue. + * + * @param sourceType + * the source type of the coercion + * @param targetType + * TODO + * @param intermediateTuple + * a tuple that converts from the source type to some intermediate type (that is not + * assignable to the target type) + * @param consideredTuples + * set of tuples that have already been added to the pool (directly, or as a compound + * coercion) + * @param queue + * the work queue of tuples + */ + @SuppressWarnings("unchecked") + private void queueIntermediates(Class sourceType, Class targetType, CoercionTuple intermediateTuple, + Set<CoercionTuple> consideredTuples, LinkedList<CoercionTuple> queue) + { + Class intermediateType = intermediateTuple.getTargetType(); + + for (Class c : new InheritanceSearch(intermediateType)) + { + for (CoercionTuple tuple : getTuples(c, targetType)) + { + if (consideredTuples.contains(tuple)) + { + continue; + } + + Class newIntermediateType = tuple.getTargetType(); + + // If this tuple is for coercing from an intermediate type back towards our + // initial source type, then ignore it. This should only be an optimization, + // as branches that loop back towards the source type will + // eventually be considered and discarded. + + if (sourceType.isAssignableFrom(newIntermediateType)) + { + continue; + } + + // The intermediateTuple coercer gets from S --> I1 (an intermediate type). + // The current tuple's coercer gets us from I2 --> X. where I2 is assignable + // from I1 (i.e., I2 is a superclass/superinterface of I1) and X is a new + // intermediate type, hopefully closer to our eventual target type. + + Coercion compoundCoercer = new CompoundCoercion(intermediateTuple.getCoercion(), tuple.getCoercion()); + + CoercionTuple compoundTuple = new CoercionTuple(sourceType, newIntermediateType, compoundCoercer, false); + + // So, every tuple that is added to the queue can take as input the sourceType. + // The target type may be another intermediate type, or may be something + // assignable to the target type, which will bring the search to a successful + // conclusion. + + queue.addLast(compoundTuple); + consideredTuples.add(tuple); + } + } + } + + /** + * Returns a non-null list of the tuples from the source type. + * + * @param sourceType + * used to locate tuples + * @param targetType + * used to add synthetic tuples + * @return non-null list of tuples + */ + private List<CoercionTuple> getTuples(Class sourceType, Class targetType) + { + List<CoercionTuple> tuples = sourceTypeToTuple.get(sourceType); + + if (tuples == null) + { + tuples = Collections.emptyList(); + } + + // So, when we see String and an Enum type, we add an additional synthetic tuple to the end + // of the real list. This is the easiest way to accomplish this is a thread-safe and class-reloading + // safe way (i.e., what if the Enum is defined by a class loader that gets discarded? Don't want to cause + // memory leaks by retaining an instance). In any case, there are edge cases where we may create + // the tuple unnecessarily (such as when an explicit string-to-enum coercion is part of the TypeCoercer + // configuration), but on the whole, this is cheap and works. + + if (sourceType == String.class && Enum.class.isAssignableFrom(targetType)) + { + tuples = extend(tuples, new CoercionTuple(sourceType, targetType, new StringToEnumCoercion(targetType))); + } + + return tuples; + } + + private static <T> List<T> extend(List<T> list, T extraValue) + { + return F.flow(list).append(extraValue).toList(); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java new file mode 100644 index 0000000..f1830a7 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java @@ -0,0 +1,159 @@ +// Copyright 2006 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.ioc.internal.util; + +import org.apache.tapestry5.plastic.PlasticUtils; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +/** + * Used to search from a particular class up the inheritance hierarchy of extended classes and implemented interfaces. + * <p/> + * The search starts with the initial class (provided in the constructor). It progresses up the inheritance chain, but + * skips java.lang.Object. + * <p/> + * Once classes are exhausted, the inheritance hierarchy is searched. This is a breadth-first search, rooted in the + * interfaces implemented by the initial class at its super classes. + * <p/> + * Once all interfaces are exhausted, java.lang.Object is returned (it is always returned last). + * <p/> + * Two minor tweak to normal inheritance rules: <ul> <li> Normally, the parent class of an <em>object</em> array is + * java.lang.Object, which is odd because FooService[] is assignable to Object[]. Thus, we tweak the search so that the + * effective super class of FooService[] is Object[]. <li> The "super class" of a primtive type is its <em>wrapper type</em>, + * with the exception of void, whose "super class" is left at its normal value (Object.class) </ul> + * <p/> + * This class implements the {@link Iterable} interface, so it can be used directly in a for loop: <code> for (Class + * search : new InheritanceSearch(startClass)) { ... } </code> + * <p/> + * This class is not thread-safe. + */ +public class InheritanceSearch implements Iterator<Class>, Iterable<Class> +{ + private Class searchClass; + + private final Set<Class> addedInterfaces = CollectionFactory.newSet(); + + private final LinkedList<Class> interfaceQueue = CollectionFactory.newLinkedList(); + + private enum State + { + CLASS, INTERFACE, DONE + } + + private State state; + + public InheritanceSearch(Class searchClass) + { + this.searchClass = searchClass; + + queueInterfaces(searchClass); + + state = searchClass == Object.class ? State.INTERFACE : State.CLASS; + } + + private void queueInterfaces(Class searchClass) + { + for (Class intf : searchClass.getInterfaces()) + { + if (addedInterfaces.contains(intf)) continue; + + interfaceQueue.addLast(intf); + addedInterfaces.add(intf); + } + } + + @Override + public Iterator<Class> iterator() + { + return this; + } + + @Override + public boolean hasNext() + { + return state != State.DONE; + } + + @Override + public Class next() + { + switch (state) + { + case CLASS: + + Class result = searchClass; + + searchClass = parentOf(searchClass); + + if (searchClass == null) state = State.INTERFACE; + else queueInterfaces(searchClass); + + return result; + + case INTERFACE: + + if (interfaceQueue.isEmpty()) + { + state = State.DONE; + return Object.class; + } + + Class intf = interfaceQueue.removeFirst(); + + queueInterfaces(intf); + + return intf; + + default: + throw new IllegalStateException(); + } + + } + + /** + * Returns the parent of the given class. Tweaks inheritance for object arrays. Returns null instead of + * Object.class. + */ + private Class parentOf(Class clazz) + { + if (clazz != void.class && clazz.isPrimitive()) return PlasticUtils.toWrapperType(clazz); + + if (clazz.isArray() && clazz != Object[].class) + { + Class componentType = clazz.getComponentType(); + + while (componentType.isArray()) componentType = componentType.getComponentType(); + + if (!componentType.isPrimitive()) return Object[].class; + } + + Class parent = clazz.getSuperclass(); + + return parent != Object.class ? parent : null; + } + + /** + * @throws UnsupportedOperationException + * always + */ + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java new file mode 100644 index 0000000..3c391e0 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java @@ -0,0 +1,388 @@ +// Copyright 2006-2014 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.ioc.internal.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.tapestry5.ioc.AnnotationProvider; +import org.apache.tapestry5.ioc.Locatable; +import org.apache.tapestry5.ioc.Location; +import org.apache.tapestry5.ioc.Messages; +import org.apache.tapestry5.ioc.internal.NullAnnotationProvider; + +/** + * Utility methods class for the Commons package. + */ +public class InternalCommonsUtils { + + /** + * @since 5.3 + */ + public final static AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider(); + private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]"); + + /** + * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map + * that allows multiple values for the same key. + * + * @param map + * to store value into + * @param key + * for which a value is added + * @param value + * to add + * @param <K> + * the type of key + * @param <V> + * the type of the list + */ + public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value) + { + List<V> list = map.get(key); + + if (list == null) + { + list = CollectionFactory.newList(); + map.put(key, list); + } + + list.add(value); + } + + /** + * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not + * convertable to a location. + */ + + public static Location locationOf(Object location) + { + if (location == null) + return null; + + if (location instanceof Location) + return (Location) location; + + if (location instanceof Locatable) + return ((Locatable) location).getLocation(); + + return null; + } + + public static AnnotationProvider toAnnotationProvider(final Method element) + { + if (element == null) + return NULL_ANNOTATION_PROVIDER; + + return new AnnotationProvider() + { + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) + { + return element.getAnnotation(annotationClass); + } + }; + } + + /** + * 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, ""); + } + + public static String replace(String input, Pattern pattern, String replacement) + { + return pattern.matcher(input).replaceAll(replacement); + } + + /** + * 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(InternalCommonsUtils.lastTerm(propertyExpression))); + } + + /** + * 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(); + } + + /** + * @since 5.3 + */ + public static AnnotationProvider toAnnotationProvider(final Class element) + { + return new AnnotationProvider() + { + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) + { + return annotationClass.cast(element.getAnnotation(annotationClass)); + } + }; + } + + /** + * Pattern used to eliminate leading and trailing underscores and dollar signs. + */ + static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$", + Pattern.CASE_INSENSITIVE); + + /** + * Converts a method to a user presentable string consisting of the containing class name, the method name, and the + * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). + * + * @param method + * @return short string representation + */ + public static String asString(Method method) + { + StringBuilder buffer = new StringBuilder(); + + buffer.append(method.getDeclaringClass().getName()); + buffer.append("."); + buffer.append(method.getName()); + buffer.append("("); + + for (int i = 0; i < method.getParameterTypes().length; i++) + { + if (i > 0) + buffer.append(", "); + + String name = method.getParameterTypes()[i].getSimpleName(); + + buffer.append(name); + } + + return buffer.append(")").toString(); + } + + /** + * Strips leading "_" and "$" and trailing "_" from the name. + */ + public static String stripMemberName(String memberName) + { + assert InternalCommonsUtils.isNonBlank(memberName); + Matcher matcher = NAME_PATTERN.matcher(memberName); + + if (!matcher.matches()) + throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName)); + + return matcher.group(1); + } + + /** + * Joins together some number of elements to form a comma separated list. + */ + public static String join(List elements) + { + return InternalCommonsUtils.join(elements, ", "); + } + + /** + * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the + * string "(blank)". + * + * @param elements + * objects to be joined together + * @param separator + * used between elements when joining + */ + public static String join(List elements, String separator) + { + switch (elements.size()) + { + case 0: + return ""; + + case 1: + return elements.get(0).toString(); + + default: + + StringBuilder buffer = new StringBuilder(); + boolean first = true; + + for (Object o : elements) + { + if (!first) + buffer.append(separator); + + String string = String.valueOf(o); + + if (string.equals("")) + string = "(blank)"; + + buffer.append(string); + + first = false; + } + + return buffer.toString(); + } + } + + /** + * Creates a sorted copy of the provided elements, then turns that into a comma separated list. + * + * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or + * empty + */ + public static String joinSorted(Collection elements) + { + if (elements == null || elements.isEmpty()) + return "(none)"; + + List<String> list = CollectionFactory.newList(); + + for (Object o : elements) + list.add(String.valueOf(o)); + + Collections.sort(list); + + return join(list); + } + + /** + * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). + */ + + public static boolean isBlank(String input) + { + return input == null || input.length() == 0 || input.trim().length() == 0; + } + + /** + * Capitalizes a string, converting the first character to uppercase. + */ + public static String capitalize(String input) + { + if (input.length() == 0) + return input; + + return input.substring(0, 1).toUpperCase() + input.substring(1); + } + + public static boolean isNonBlank(String input) + { + return !isBlank(input); + } + + /** + * Return true if the input string contains the marker for symbols that must be expanded. + */ + public static boolean containsSymbols(String input) + { + return input.contains("${"); + } + + /** + * Searches the string for the final period ('.') character and returns everything after that. The input string is + * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property + * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period + * character. + */ + public static String lastTerm(String input) + { + assert isNonBlank(input); + int dotx = input.lastIndexOf('.'); + + if (dotx < 0) + return input; + + return input.substring(dotx + 1); + } + + /** + * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings. + * + * @param map + * the map to extract keys from (may be null) + * @return the sorted keys, or the empty set if map is null + */ + + public static List<String> sortedKeys(Map map) + { + if (map == null) + return Collections.emptyList(); + + List<String> keys = CollectionFactory.newList(); + + for (Object o : map.keySet()) + keys.add(String.valueOf(o)); + + Collections.sort(keys); + + return keys; + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java deleted file mode 100644 index e345593..0000000 --- a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2006-2014 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.ioc.internal.util; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * String-related utilities used within various internal implementations of the Apache Tapestry subprojects. - * Broken off Tapestry-IoC's InternalUtils. - */ -@SuppressWarnings("all") -public class InternalStringUtils -{ - - /** - * Pattern used to eliminate leading and trailing underscores and dollar signs. - */ - private static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$", - Pattern.CASE_INSENSITIVE); - - /** - * Converts a method to a user presentable string consisting of the containing class name, the method name, and the - * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). - * - * @param method - * @return short string representation - */ - public static String asString(Method method) - { - StringBuilder buffer = new StringBuilder(); - - buffer.append(method.getDeclaringClass().getName()); - buffer.append("."); - buffer.append(method.getName()); - buffer.append("("); - - for (int i = 0; i < method.getParameterTypes().length; i++) - { - if (i > 0) - buffer.append(", "); - - String name = method.getParameterTypes()[i].getSimpleName(); - - buffer.append(name); - } - - return buffer.append(")").toString(); - } - - /** - * Strips leading "_" and "$" and trailing "_" from the name. - */ - public static String stripMemberName(String memberName) - { - assert isNonBlank(memberName); - Matcher matcher = NAME_PATTERN.matcher(memberName); - - if (!matcher.matches()) - throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName)); - - return matcher.group(1); - } - - /** - * Joins together some number of elements to form a comma separated list. - */ - public static String join(List elements) - { - return join(elements, ", "); - } - - /** - * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the - * string "(blank)". - * - * @param elements - * objects to be joined together - * @param separator - * used between elements when joining - */ - public static String join(List elements, String separator) - { - switch (elements.size()) - { - case 0: - return ""; - - case 1: - return elements.get(0).toString(); - - default: - - StringBuilder buffer = new StringBuilder(); - boolean first = true; - - for (Object o : elements) - { - if (!first) - buffer.append(separator); - - String string = String.valueOf(o); - - if (string.equals("")) - string = "(blank)"; - - buffer.append(string); - - first = false; - } - - return buffer.toString(); - } - } - - /** - * Creates a sorted copy of the provided elements, then turns that into a comma separated list. - * - * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or - * empty - */ - public static String joinSorted(Collection elements) - { - if (elements == null || elements.isEmpty()) - return "(none)"; - - List<String> list = CollectionFactory.newList(); - - for (Object o : elements) - list.add(String.valueOf(o)); - - Collections.sort(list); - - return join(list); - } - - /** - * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). - */ - - public static boolean isBlank(String input) - { - return input == null || input.length() == 0 || input.trim().length() == 0; - } - - public static boolean isNonBlank(String input) - { - return !isBlank(input); - } - - /** - * Capitalizes a string, converting the first character to uppercase. - */ - public static String capitalize(String input) - { - if (input.length() == 0) - return input; - - return input.substring(0, 1).toUpperCase() + input.substring(1); - } - - /** - * Return true if the input string contains the marker for symbols that must be expanded. - */ - public static boolean containsSymbols(String input) - { - return input.contains("${"); - } - - /** - * Searches the string for the final period ('.') character and returns everything after that. The input string is - * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property - * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period - * character. - */ - public static String lastTerm(String input) - { - assert isNonBlank(input); - int dotx = input.lastIndexOf('.'); - - if (dotx < 0) - return input; - - return input.substring(dotx + 1); - } - -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java new file mode 100644 index 0000000..41de363 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java @@ -0,0 +1,89 @@ +// Copyright 2012 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.ioc.internal.util; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Base class for classes that need to manage a ReadWriteLock. + */ +public abstract class LockSupport +{ + private final Lock readLock, writeLock; + + protected LockSupport() + { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + readLock = lock.readLock(); + writeLock = lock.writeLock(); + } + + /** + * Locks the shared read lock. Any number of threads may lock the read lock at the same time. + */ + protected final void acquireReadLock() + { + readLock.lock(); + } + + /** + * Takes the exclusive write lock. Once started, no other thread lock the read or write lock. When this method returns, + * this thread will have locked the write lock and no other thread will have either the read or write lock. + * Note that this thread must first drop the read lock (if it has it) before attempting to take the write lock, or this method will block forever. + */ + protected final void takeWriteLock() + { + writeLock.lock(); + } + + /** + * Releases the shared read lock. + */ + protected final void releaseReadLock() + { + readLock.unlock(); + } + + /** + * Releases the exclusive read lock. + */ + protected final void releaseWriteLock() + { + writeLock.unlock(); + } + + /** + * Releases the read lock, then takes the write lock. There's a short window where the thread will have neither lock: + * during that window, some other thread may have a chance to take the write lock. In code, you'll often see a second check + * inside the code that has the write lock to see if the update to perform is still necessary. + */ + protected final void upgradeReadLockToWriteLock() + { + releaseReadLock(); + // This is that instant where another thread may grab the write lock. Very rare, but possible. + takeWriteLock(); + } + + /** + * Takes the read lock then releases the write lock. + */ + protected final void downgradeWriteLockToReadLock() + { + acquireReadLock(); + releaseWriteLock(); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java new file mode 100644 index 0000000..f4d8949 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java @@ -0,0 +1,65 @@ +// Copyright 2006-2013 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.ioc.internal.util; + +import org.apache.tapestry5.ioc.MessageFormatter; +import org.apache.tapestry5.ioc.util.ExceptionUtils; + +import java.util.Locale; + + +public class MessageFormatterImpl implements MessageFormatter +{ + private final String format; + + private final Locale locale; + + public MessageFormatterImpl(String format, Locale locale) + { + this.format = format; + this.locale = locale; + } + + @Override + public String format(Object... args) + { + for (int i = 0; i < args.length; i++) + { + Object arg = args[i]; + + if (Throwable.class.isInstance(arg)) + { + args[i] = ExceptionUtils.toMessage((Throwable) arg); + } + } + + // Might be tempting to create a Formatter object and just keep reusing it ... but + // Formatters are not threadsafe. + + return String.format(locale, format, args); + } + + /** + * Returns the underlying format string for this formatter. + * + * @since 5.4 + */ + @Override + public String toString() + { + return format; + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java new file mode 100644 index 0000000..c06ef90 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java @@ -0,0 +1,74 @@ +// Copyright 2006, 2012 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.ioc.internal.util; + +import org.apache.tapestry5.ioc.Messages; +import org.apache.tapestry5.ioc.util.AbstractMessages; + +import java.util.*; + +/** + * Implementation of {@link org.apache.tapestry5.ioc.Messages} based around a {@link java.util.ResourceBundle}. + */ +public class MessagesImpl extends AbstractMessages +{ + private final Map<String, String> properties = CollectionFactory.newCaseInsensitiveMap(); + + /** + * Finds the messages for a given Messages utility class. Strings the trailing "Messages" and replaces it with + * "Strings" to form the base path. Loads the bundle using the default locale, and the class' class loader. + * + * @param forClass + * @return Messages for the class + */ + public static Messages forClass(Class forClass) + { + String className = forClass.getName(); + String stringsClassName = className.replaceAll("Messages$", "Strings"); + + Locale locale = Locale.getDefault(); + + ResourceBundle bundle = ResourceBundle.getBundle(stringsClassName, locale, forClass.getClassLoader()); + + return new MessagesImpl(locale, bundle); + } + + public MessagesImpl(Locale locale, ResourceBundle bundle) + { + super(locale); + + // Our best (threadsafe) chance to determine all the available keys. + Enumeration<String> e = bundle.getKeys(); + while (e.hasMoreElements()) + { + String key = e.nextElement(); + String value = bundle.getString(key); + + properties.put(key, value); + } + } + + @Override + protected String valueForKey(String key) + { + return properties.get(key); + } + + @Override + public Set<String> getKeys() + { + return properties.keySet(); + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java index d8d8018..6e23c5b 100644 --- a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java @@ -34,7 +34,7 @@ public class TapestryException extends RuntimeException implements Locatable */ public TapestryException(String message, Object location, Throwable cause) { - this(message, locationOf(location), cause); + this(message, InternalCommonsUtils.locationOf(location), cause); } /** @@ -71,26 +71,5 @@ public class TapestryException extends RuntimeException implements Locatable return String.format("%s [at %s]", super.toString(), location); } - - /** - * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not - * convertable to a location. - * Copied from InternalUtils to avoid having it moved to BeanModel or Commons subprojects. - */ - - private static Location locationOf(Object location) - { - if (location == null) - return null; - - if (location instanceof Location) - return (Location) location; - - if (location instanceof Locatable) - return ((Locatable) location).getLocation(); - - return null; - } - } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java new file mode 100644 index 0000000..590c337 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java @@ -0,0 +1,94 @@ +// Copyright 2006, 2009, 2011 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.ioc.util; + +import org.apache.tapestry5.ioc.MessageFormatter; +import org.apache.tapestry5.ioc.Messages; +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.MessageFormatterImpl; + +import java.util.Locale; +import java.util.Map; + +/** + * Abstract implementation of {@link Messages} that doesn't know where values come from (that information is supplied in + * a subclass, via the {@link #valueForKey(String)} method). + */ +public abstract class AbstractMessages implements Messages +{ + /** + * String key to MF instance. + */ + private final Map<String, MessageFormatter> cache = CollectionFactory.newConcurrentMap(); + + private final Locale locale; + + protected AbstractMessages(Locale locale) + { + this.locale = locale; + } + + /** + * Invoked to provide the value for a particular key. This may be invoked multiple times even for the same key. The + * implementation should <em>ignore the case of the key</em>. + * + * @param key the key to obtain a value for (case insensitive) + * @return the value for the key, or null if this instance can not provide the value + */ + protected abstract String valueForKey(String key); + + + @Override + public boolean contains(String key) + { + return valueForKey(key) != null; + } + + @Override + public String get(String key) + { + if (contains(key)) return valueForKey(key); + + return String.format("[[missing key: %s]]", key); + } + + @Override + public MessageFormatter getFormatter(String key) + { + MessageFormatter result = cache.get(key); + + if (result == null) + { + result = buildMessageFormatter(key); + cache.put(key, result); + } + + return result; + } + + private MessageFormatter buildMessageFormatter(String key) + { + String format = get(key); + + return new MessageFormatterImpl(format, locale); + } + + @Override + public String format(String key, Object... args) + { + return getFormatter(key).format(args); + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java index 8c9cb3f..b2f1386 100644 --- a/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java +++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; import org.apache.tapestry5.ioc.internal.util.CollectionFactory; -import org.apache.tapestry5.ioc.internal.util.InternalStringUtils; +import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; /** * Used (as part of a {@link UnknownValueException} to identify what available values @@ -81,7 +81,7 @@ public class AvailableValues @Override public String toString() { - return String.format("AvailableValues[%s: %s]", valueType, InternalStringUtils.join(values)); + return String.format("AvailableValues[%s: %s]", valueType, InternalCommonsUtils.join(values)); } } http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java new file mode 100644 index 0000000..01ef99e --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java @@ -0,0 +1,195 @@ +// Copyright 2007, 2008, 2010, 2011 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.ioc.util; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; + +/** + * Used to represent a period of time, specifically as a configuration value. This is often used to specify timeouts. + * <p/> + * TimePeriods are parsed from strings. + * <p/> + * The string specifys a number of terms. The values of all the terms are summed together to form the total time period. + * Each term consists of a number followed by a unit. Units (from largest to smallest) are: <dl> <dt>y <dd>year <dt>d + * <dd>day <dt>h <dd>hour <dt>m <dd>minute <dt>s <dd>second <dt>ms <dd>millisecond </dl> <p> Example: "2 h 30 m". By + * convention, terms are specified largest to smallest. A term without a unit is assumed to be milliseconds. Units are + * case insensitive ("h" or "H" are treated the same). + */ +public class TimeInterval +{ + private static final Map<String, Long> UNITS = CollectionFactory.newCaseInsensitiveMap(); + + private static final long MILLISECOND = 1000l; + + static + { + UNITS.put("ms", 1l); + UNITS.put("s", MILLISECOND); + UNITS.put("m", 60 * MILLISECOND); + UNITS.put("h", 60 * UNITS.get("m")); + UNITS.put("d", 24 * UNITS.get("h")); + UNITS.put("y", 365 * UNITS.get("d")); + } + + /** + * The unit keys, sorted in descending order. + */ + private static final String[] UNIT_KEYS = + { "y", "d", "h", "m", "s", "ms" }; + + private static final Pattern PATTERN = Pattern.compile("\\s*(\\d+)\\s*([a-z]*)", Pattern.CASE_INSENSITIVE); + + private final long milliseconds; + + /** + * Creates a TimeInterval for a string. + * + * @param input + * the string specifying the amount of time in the period + */ + public TimeInterval(String input) + { + this(parseMilliseconds(input)); + } + + public TimeInterval(long milliseconds) + { + this.milliseconds = milliseconds; + } + + public long milliseconds() + { + return milliseconds; + } + + public long seconds() + { + return milliseconds / MILLISECOND; + } + + /** + * Converts the milliseconds back into a string (compatible with {@link #TimeInterval(String)}). + * + * @since 5.2.0 + */ + public String toDescription() + { + StringBuilder builder = new StringBuilder(); + + String sep = ""; + + long remainder = milliseconds; + + for (String key : UNIT_KEYS) + { + if (remainder == 0) + break; + + long value = UNITS.get(key); + + long units = remainder / value; + + if (units > 0) + { + builder.append(sep); + builder.append(units); + builder.append(key); + + sep = " "; + + remainder = remainder % value; + } + } + + return builder.toString(); + } + + static long parseMilliseconds(String input) + { + long milliseconds = 0l; + + Matcher matcher = PATTERN.matcher(input); + + matcher.useAnchoringBounds(true); + + // TODO: Notice non matching characters and reject input, including at end + + int lastMatchEnd = -1; + + while (matcher.find()) + { + int start = matcher.start(); + + if (lastMatchEnd + 1 < start) + { + String invalid = input.substring(lastMatchEnd + 1, start); + throw new RuntimeException(String.format("Unexpected string '%s' (in time interval '%s').", invalid, input)); + } + + lastMatchEnd = matcher.end(); + + long count = Long.parseLong(matcher.group(1)); + String units = matcher.group(2); + + if (units.length() == 0) + { + milliseconds += count; + continue; + } + + Long unitValue = UNITS.get(units); + + if (unitValue == null) + throw new RuntimeException(String.format("Unknown time interval unit '%s' (in '%s'). Defined units: %s.", units, input, InternalCommonsUtils.joinSorted(UNITS.keySet()))); + + milliseconds += count * unitValue; + } + + if (lastMatchEnd + 1 < input.length()) + { + String invalid = input.substring(lastMatchEnd + 1); + throw new RuntimeException(String.format("Unexpected string '%s' (in time interval '%s').", invalid, input)); + } + + return milliseconds; + } + + @Override + public String toString() + { + return String.format("TimeInterval[%d ms]", milliseconds); + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + return false; + + if (obj instanceof TimeInterval) + { + TimeInterval tp = (TimeInterval) obj; + + return milliseconds == tp.milliseconds; + } + + return false; + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java ---------------------------------------------------------------------- diff --git a/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java b/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java new file mode 100644 index 0000000..bdde866 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java @@ -0,0 +1,91 @@ +// Copyright 2007, 2008, 2010, 2011 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.util; + +import java.util.Map; + +import org.apache.tapestry5.ioc.internal.util.CollectionFactory; +import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils; +import org.apache.tapestry5.ioc.services.Coercion; +import org.apache.tapestry5.ioc.util.AvailableValues; +import org.apache.tapestry5.ioc.util.UnknownValueException; + +/** + * A {@link org.apache.tapestry5.ioc.services.Coercion} for converting strings into an instance of a particular + * enumerated type. The {@link Enum#name() name} is used as the key to identify the enum instance, in a case-insensitive + * fashion. + * <p> + * Moved from tapestry-core to tapestry-ioc is release 5.3, but kept in same package for compatibility. + * + * @param <T> + * the type of enumeration + */ +public final class StringToEnumCoercion<T extends Enum> implements Coercion<String, T> +{ + private final Class<T> enumClass; + + private final Map<String, T> stringToEnum = CollectionFactory.newCaseInsensitiveMap(); + + public StringToEnumCoercion(Class<T> enumClass) + { + this(enumClass, enumClass.getEnumConstants()); + } + + public StringToEnumCoercion(Class<T> enumClass, T... values) + { + this.enumClass = enumClass; + + for (T value : values) + stringToEnum.put(value.name(), value); + } + + @Override + public T coerce(String input) + { + if (InternalCommonsUtils.isBlank(input)) + return null; + + T result = stringToEnum.get(input); + + if (result == null) + { + String message = String.format("Input '%s' does not identify a value from enumerated type %s.", input, + enumClass.getName()); + + throw new UnknownValueException(message, new AvailableValues(enumClass.getName() + " enum constants", + stringToEnum)); + } + + return result; + } + + /** + * Allows an alias value (alternate) string to reference a value. + * + * @since 5.2.2 + */ + public StringToEnumCoercion<T> addAlias(String alias, T value) + { + stringToEnum.put(alias, value); + + return this; + } + + public static <T extends Enum> StringToEnumCoercion<T> create(Class<T> enumClass) + { + return new StringToEnumCoercion<T>(enumClass); + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java index e69377f..e032975 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java @@ -59,7 +59,7 @@ public class TapestryInternalUtils */ public static String toUserPresentable(String id) { - return InternalBeanModelUtils.toUserPresentable(id); + return InternalUtils.toUserPresentable(id); } public static Map<String, String> mapFromKeysAndValues(String... keysAndValues) @@ -228,7 +228,7 @@ public class TapestryInternalUtils */ public static String extractIdFromPropertyExpression(String expression) { - return InternalBeanModelUtils.extractIdFromPropertyExpression(expression); + return InternalUtils.extractIdFromPropertyExpression(expression); } /** @@ -237,7 +237,7 @@ public class TapestryInternalUtils */ public static String defaultLabel(String id, Messages messages, String propertyExpression) { - return InternalBeanModelUtils.defaultLabel(id, messages, propertyExpression); + return InternalUtils.defaultLabel(id, messages, propertyExpression); } /** @@ -304,7 +304,7 @@ public class TapestryInternalUtils private static String replace(String input, Pattern pattern, String replacement) { - return InternalBeanModelUtils.replace(input, pattern, replacement); + return InternalUtils.replace(input, pattern, replacement); } /** http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java ---------------------------------------------------------------------- diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java deleted file mode 100644 index 43519dc..0000000 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2009, 2012 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.services; - -import org.apache.tapestry5.ioc.internal.util.CollectionFactory; -import org.apache.tapestry5.services.ComponentClasses; -import org.apache.tapestry5.services.InvalidationEventHub; - -import javax.annotation.PostConstruct; -import java.util.Map; - -public class StringInternerImpl implements StringInterner -{ - private final Map<String, String> cache = CollectionFactory.newConcurrentMap(); - - @PostConstruct - public void setupInvalidation(@ComponentClasses InvalidationEventHub hub) - { - hub.clearOnInvalidation(cache); - } - - public String intern(String string) - { - String result = cache.get(string); - - // Not yet in the cache? Add it. - - if (result == null) - { - cache.put(string, string); - result = string; - } - - return result; - } - - public String format(String format, Object... arguments) - { - return intern(String.format(format, arguments)); - } -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java deleted file mode 100644 index 03814f5..0000000 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2006, 2008, 2009 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.ioc; - -/** - * Object passed into a service contributor method that allows the method provide contributed values to the service's - * configuration. - * <p/> - * A service can <em>collect</em> contributions in three different ways: - * <ul> - * <li>As an un-ordered collection of values</li> - * <li>As an ordered list of values (where each value has a unique id, pre-requisites and post-requisites)</li> - * <li>As a map of keys and values - * </ul> - * <p/> - * This implementation is used for un-ordered configuration data. - * <p/> - * The service defines the <em>type</em> of contribution, in terms of a base class or service interface. Contributions - * must be compatible with the type. - */ -public interface Configuration<T> -{ - /** - * Adds an object to the service's contribution. - * - * @param object - * to add to the service's configuration - */ - void add(T object); - - /** - * Automatically instantiates an instance of the class, with dependencies injected, and adds it to the - * configuration. When the configuration type is an interface and the class to be contributed is a local file, - * then a reloadable proxy for the class will be created and contributed. - * - * @param clazz - * what class to instantiate - * @since 5.1.0.0 - */ - void addInstance(Class<? extends T> clazz); -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/eb7ec86e/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java deleted file mode 100644 index 47c6026..0000000 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2006, 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.ioc; - -/** - * Object passed into a service contributor method that allows the method provide contributed values to the service's - * configuration. - * <p/> - * A service can <em>collect</em> contributions in three different ways: - * <ul> - * <li>As an un-ordered collection of values</li> - * <li>As an ordered list of values (where each value has a unique id, pre-requisites and post-requisites)</li> - * <li>As a map of keys and values - * </ul> - * <p/> - * The service defines the <em>type</em> of contribution, in terms of a base class or service interface. Contributions - * must be compatible with the type. - */ -public interface MappedConfiguration<K, V> -{ - - /** - * Adds a keyed object to the service's contribution. - * - * @param key - * unique id for the value - * @param value - * to contribute - * @throws IllegalArgumentException - * if key is not unique - */ - void add(K key, V value); - - /** - * Overrides an existing contribution by its key. - * - * @param key - * unique id of value to override - * @param value - * new value, or null to remove the key entirely - * @since 5.1.0.0 - */ - void override(K key, V value); - - /** - * Adds a keyed object as an instantiated instance (with dependencies injected) of a class. When the value - * type is an interface and the class to be contributed is a local file, - * then a reloadable proxy for the value class will be created and contributed. - * - * @param key - * unique id for the value - * @param clazz - * class to instantiate and contribute - * @since 5.1.0.0 - */ - void addInstance(K key, Class<? extends V> clazz); - - /** - * Overrides an existing contribution with a new instance. When the value - * type is an interface and the class to be contributed is a local file, - * then a reloadable proxy for the value class will be created and contributed. - * - * @param key - * unique id of value to override - * @param clazz - * class to instantiate as override - */ - void overrideInstance(K key, Class<? extends V> clazz); -}