You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2012/03/07 11:42:23 UTC
[8/24] WICKET-4439 Move classes around so that there are no two
packages with the same name in different modules
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
new file mode 100644
index 0000000..01efa84
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolver.java
@@ -0,0 +1,1542 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.lang;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.util.convert.ConversionException;
+import org.apache.wicket.util.lang.Generics;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * NOTE: THIS CLASS IS NOT PART OF THE WICKET PUBLIC API, DO NOT USE IT UNLESS YOU KNOW WHAT YOU ARE
+ * DOING.
+ * <p>
+ * This class parses expressions to lookup or set a value on the object that is given. <br/>
+ * The supported expressions are:
+ * <p>
+ * "property": This can can then be a bean property with get and set method. Or if a map is given as
+ * an object it will be lookup with the property as a key when there is not get method for that
+ * property.
+ * <p/>
+ * <p>
+ * "property1.property2": Both properties are lookup as written above. If property1 evaluates to
+ * null then if there is a setMethod (or if it is a map) and the Class of the property has a default
+ * constructor then the object will be constructed and set on the object.
+ * <p/>
+ * <p>
+ * "property.index": If the property is a List or Array then the second property can be a index on
+ * that list like: 'mylist.0' this expression will also map on a getProperty(index) or
+ * setProperty(index,value) methods. If the object is a List then the list will grow automatically
+ * if the index is greater then the size
+ * <p/>
+ * <p>
+ * Index or map properties can also be written as: "property[index]" or "property[key]"
+ * <p/>
+ *
+ * @author jcompagner
+ */
+@SuppressWarnings("unused")
+public final class PropertyResolver
+{
+ /** Log. */
+ private static final Logger log = LoggerFactory.getLogger(PropertyResolver.class);
+
+ private final static int RETURN_NULL = 0;
+ private final static int CREATE_NEW_VALUE = 1;
+ private final static int RESOLVE_CLASS = 2;
+
+ private final static Map<Object, IClassCache> applicationToClassesToGetAndSetters = Generics.newConcurrentHashMap(2);
+
+ private static final String GET = "get";
+ private static final String IS = "is";
+ private static final String SET = "set";
+
+ /**
+ * Looks up the value from the object with the given expression. If the expression, the object
+ * itself or one property evaluates to null then a null will be returned.
+ *
+ * @param expression
+ * The expression string with the property to be lookup.
+ * @param object
+ * The object which is evaluated.
+ * @return The value that is evaluated. Null something in the expression evaluated to null.
+ */
+ public final static Object getValue(final String expression, final Object object)
+ {
+ if (expression == null || expression.equals("") || object == null)
+ {
+ return object;
+ }
+
+ ObjectAndGetSetter getter = getObjectAndGetSetter(expression, object, RETURN_NULL);
+ if (getter == null)
+ {
+ return null;
+ }
+
+ return getter.getValue();
+ }
+
+ /**
+ * Set the value on the object with the given expression. If the expression can't be evaluated
+ * then a WicketRuntimeException will be thrown. If a null object is encountered then it will
+ * try to generate it by calling the default constructor and set it on the object.
+ *
+ * The value will be tried to convert to the right type with the given converter.
+ *
+ * @param expression
+ * The expression string with the property to be set.
+ * @param object
+ * The object which is evaluated to set the value on.
+ * @param value
+ * The value to set.
+ * @param converter
+ * The converter to convert the value if needed to the right type.
+ */
+ public final static void setValue(final String expression, final Object object,
+ final Object value, final PropertyResolverConverter converter)
+ {
+ if (expression == null || expression.equals(""))
+ {
+ throw new WicketRuntimeException("Empty expression setting value: " + value +
+ " on object: " + object);
+ }
+ if (object == null)
+ {
+ throw new WicketRuntimeException(
+ "Attempted to set property value on a null object. Property expression: " +
+ expression + " Value: " + value);
+ }
+
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, CREATE_NEW_VALUE);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("Null object returned for expression: " + expression +
+ " for setting value: " + value + " on: " + object);
+ }
+ setter.setValue(value, converter == null ? new PropertyResolverConverter(Application.get()
+ .getConverterLocator(), Session.get().getLocale()) : converter);
+ }
+
+ /**
+ * @param expression
+ * @param object
+ * @return class of the target property object
+ */
+ public final static Class<?> getPropertyClass(final String expression, final Object object)
+ {
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, RESOLVE_CLASS);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("Null object returned for expression: " + expression +
+ " for getting the target classs of: " + object);
+ }
+ return setter.getTargetClass();
+ }
+
+ /**
+ * @param <T>
+ * @param expression
+ * @param clz
+ * @return class of the target Class property expression
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> getPropertyClass(final String expression, final Class<?> clz)
+ {
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, null, RESOLVE_CLASS, clz);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("No Class returned for expression: " + expression +
+ " for getting the target classs of: " + clz);
+ }
+ return (Class<T>)setter.getTargetClass();
+ }
+
+ /**
+ * @param expression
+ * @param object
+ * @return Field for the property expression or null if such field doesn't exist (only getters
+ * and setters)
+ */
+ public final static Field getPropertyField(final String expression, final Object object)
+ {
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, RESOLVE_CLASS);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("Null object returned for expression: " + expression +
+ " for getting the target classs of: " + object);
+ }
+ return setter.getField();
+ }
+
+ /**
+ * @param expression
+ * @param object
+ * @return Getter method for the property expression or null if such getter doesn't exist (only
+ * field)
+ */
+ public final static Method getPropertyGetter(final String expression, final Object object)
+ {
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, RESOLVE_CLASS);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("Null object returned for expression: " + expression +
+ " for getting the target classs of: " + object);
+ }
+ return setter.getGetter();
+ }
+
+ /**
+ * @param expression
+ * @param object
+ * @return Setter method for the property expression or null if such setter doesn't exist (only
+ * field)
+ */
+ public final static Method getPropertySetter(final String expression, final Object object)
+ {
+ ObjectAndGetSetter setter = getObjectAndGetSetter(expression, object, RESOLVE_CLASS);
+ if (setter == null)
+ {
+ throw new WicketRuntimeException("Null object returned for expression: " + expression +
+ " for getting the target classs of: " + object);
+ }
+ return setter.getSetter();
+ }
+
+ /**
+ * Just delegating the call to the original getObjectAndGetSetter passing the object type as
+ * parameter.
+ *
+ * @param expression
+ * @param object
+ * @param tryToCreateNull
+ * @return {@link ObjectAndGetSetter}
+ */
+ private static ObjectAndGetSetter getObjectAndGetSetter(final String expression,
+ final Object object, int tryToCreateNull)
+ {
+ return getObjectAndGetSetter(expression, object, tryToCreateNull, object.getClass());
+ }
+
+ /**
+ * Receives the class parameter also, since this method can resolve the type for some
+ * expression, only knowing the target class
+ *
+ * @param expression
+ * @param object
+ * @param tryToCreateNull
+ * @param clz
+ * @return {@link ObjectAndGetSetter}
+ */
+ private static ObjectAndGetSetter getObjectAndGetSetter(final String expression,
+ final Object object, final int tryToCreateNull, Class<?> clz)
+ {
+ String expressionBracketsSeperated = Strings.replaceAll(expression, "[", ".[").toString();
+ int index = getNextDotIndex(expressionBracketsSeperated, 0);
+ while (index == 0 && expressionBracketsSeperated.startsWith("."))
+ {
+ // eat dots at the beginning of the expression since they will confuse
+ // later steps
+ expressionBracketsSeperated = expressionBracketsSeperated.substring(1);
+ index = getNextDotIndex(expressionBracketsSeperated, 0);
+ }
+ int lastIndex = 0;
+ Object value = object;
+ String exp = expressionBracketsSeperated;
+ while (index != -1)
+ {
+ exp = expressionBracketsSeperated.substring(lastIndex, index);
+ if (exp.length() == 0)
+ {
+ exp = expressionBracketsSeperated.substring(index + 1);
+ break;
+ }
+
+ IGetAndSet getAndSetter = null;
+ try
+ {
+ getAndSetter = getGetAndSetter(exp, clz);
+ }
+ catch (WicketRuntimeException ex)
+ {
+ // expression by it self can't be found. try to find a
+ // setPropertyByIndex(int,value) method
+ index = getNextDotIndex(expressionBracketsSeperated, index + 1);
+ if (index != -1)
+ {
+ String indexExpression = expressionBracketsSeperated.substring(lastIndex, index);
+ getAndSetter = getGetAndSetter(indexExpression, clz);
+ }
+ else
+ {
+ exp = expressionBracketsSeperated.substring(lastIndex);
+ break;
+ }
+ }
+ Object newValue = null;
+ if (value != null)
+ {
+ newValue = getAndSetter.getValue(value);
+ }
+ if (newValue == null)
+ {
+ if (tryToCreateNull == CREATE_NEW_VALUE)
+ {
+ newValue = getAndSetter.newValue(value);
+ if (newValue == null)
+ {
+ return null;
+ }
+ }
+ else if (tryToCreateNull == RESOLVE_CLASS)
+ {
+ clz = getAndSetter.getTargetClass();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ value = newValue;
+ if (value != null)
+ {
+ // value can be null if we are in the RESOLVE_CLASS
+ clz = value.getClass();
+ }
+
+ lastIndex = index + 1;
+ index = getNextDotIndex(expressionBracketsSeperated, lastIndex);
+ if (index == -1)
+ {
+ exp = expressionBracketsSeperated.substring(lastIndex);
+ break;
+ }
+ }
+ IGetAndSet getAndSetter = getGetAndSetter(exp, clz);
+ return new ObjectAndGetSetter(getAndSetter, value);
+ }
+
+ /**
+ *
+ * @param expression
+ * @param start
+ * @return next dot index
+ */
+ private static int getNextDotIndex(final String expression, final int start)
+ {
+ boolean insideBracket = false;
+ for (int i = start; i < expression.length(); i++)
+ {
+ char ch = expression.charAt(i);
+ if (ch == '.' && !insideBracket)
+ {
+ return i;
+ }
+ else if (ch == '[')
+ {
+ insideBracket = true;
+ }
+ else if (ch == ']')
+ {
+ insideBracket = false;
+ }
+ }
+ return -1;
+ }
+
+ private final static IGetAndSet getGetAndSetter(String exp, final Class<?> clz)
+ {
+ IClassCache classesToGetAndSetters = getClassesToGetAndSetters();
+ Map<String, IGetAndSet> getAndSetters = classesToGetAndSetters.get(clz);
+ if (getAndSetters == null)
+ {
+ getAndSetters = new ConcurrentHashMap<String, IGetAndSet>(8);
+ classesToGetAndSetters.put(clz, getAndSetters);
+ }
+
+ IGetAndSet getAndSetter = getAndSetters.get(exp);
+ if (getAndSetter == null)
+ {
+ Method method = null;
+ Field field = null;
+ if (exp.startsWith("["))
+ {
+ // if expression begins with [ skip method finding and use it as
+ // a key/index lookup on a map.
+ exp = exp.substring(1, exp.length() - 1);
+ }
+ else if (exp.endsWith("()"))
+ {
+ // if expression ends with (), don't test for setters just skip
+ // directly to method finding.
+ method = findMethod(clz, exp);
+ }
+ else
+ {
+ method = findGetter(clz, exp);
+ }
+ if (method == null)
+ {
+ if (List.class.isAssignableFrom(clz))
+ {
+ try
+ {
+ int index = Integer.parseInt(exp);
+ getAndSetter = new ListGetSet(index);
+ }
+ catch (NumberFormatException ex)
+ {
+ // can't parse the exp as an index, maybe the exp was a
+ // method.
+ method = findMethod(clz, exp);
+ if (method != null)
+ {
+ getAndSetter = new MethodGetAndSet(method, MethodGetAndSet.findSetter(
+ method, clz), null);
+ }
+ else
+ {
+ field = findField(clz, exp);
+ if (field != null)
+ {
+ getAndSetter = new FieldGetAndSetter(field);
+ }
+ else
+ {
+ throw new WicketRuntimeException(
+ "The expression '" +
+ exp +
+ "' is neither an index nor is it a method or field for the list " +
+ clz);
+ }
+ }
+ }
+ }
+ else if (Map.class.isAssignableFrom(clz))
+ {
+ getAndSetter = new MapGetSet(exp);
+ }
+ else if (clz.isArray())
+ {
+ try
+ {
+ int index = Integer.parseInt(exp);
+ getAndSetter = new ArrayGetSet(clz.getComponentType(), index);
+ }
+ catch (NumberFormatException ex)
+ {
+ if (exp.equals("length") || exp.equals("size"))
+ {
+ getAndSetter = new ArrayLengthGetSet();
+ }
+ else
+ {
+ throw new WicketRuntimeException("Can't parse the expression '" + exp +
+ "' as an index for an array lookup");
+ }
+ }
+ }
+ else
+ {
+ field = findField(clz, exp);
+ if (field == null)
+ {
+ method = findMethod(clz, exp);
+ if (method == null)
+ {
+ int index = exp.indexOf('.');
+ if (index != -1)
+ {
+ String propertyName = exp.substring(0, index);
+ String propertyIndex = exp.substring(index + 1);
+ try
+ {
+ int parsedIndex = Integer.parseInt(propertyIndex);
+ // if so then it could be a getPropertyIndex(int)
+ // and setPropertyIndex(int, object)
+ String name = Character.toUpperCase(propertyName.charAt(0)) +
+ propertyName.substring(1);
+ method = clz.getMethod(GET + name, new Class[] { int.class });
+ getAndSetter = new ArrayPropertyGetSet(method, parsedIndex);
+ }
+ catch (Exception e)
+ {
+ throw new WicketRuntimeException(
+ "No get method defined for class: " + clz +
+ " expression: " + propertyName);
+ }
+ }
+ else
+ {
+ // We do not look for a public FIELD because
+ // that is not good programming with beans patterns
+ throw new WicketRuntimeException(
+ "No get method defined for class: " + clz + " expression: " +
+ exp);
+ }
+ }
+ else
+ {
+ getAndSetter = new MethodGetAndSet(method, MethodGetAndSet.findSetter(
+ method, clz), null);
+ }
+ }
+ else
+ {
+ getAndSetter = new FieldGetAndSetter(field);
+ }
+ }
+ }
+ else
+ {
+ field = findField(clz, exp);
+ getAndSetter = new MethodGetAndSet(method, MethodGetAndSet.findSetter(method, clz),
+ field);
+ }
+ getAndSetters.put(exp, getAndSetter);
+ }
+ return getAndSetter;
+ }
+
+
+ /**
+ * @param clz
+ * @param expression
+ * @return introspected field
+ */
+ private static Field findField(final Class<?> clz, final String expression)
+ {
+ Field field = null;
+ try
+ {
+ field = clz.getField(expression);
+ }
+ catch (Exception e)
+ {
+ Class<?> tmp = clz;
+ while (tmp != null && tmp != Object.class)
+ {
+ Field[] fields = tmp.getDeclaredFields();
+ for (Field aField : fields)
+ {
+ if (aField.getName().equals(expression))
+ {
+ aField.setAccessible(true);
+ return aField;
+ }
+ }
+ tmp = tmp.getSuperclass();
+ }
+ log.debug("Cannot find field " + clz + "." + expression);
+ }
+ return field;
+ }
+
+ /**
+ * @param clz
+ * @param expression
+ * @return The method for the expression null if not found
+ */
+ private final static Method findGetter(final Class<?> clz, final String expression)
+ {
+ String name = Character.toUpperCase(expression.charAt(0)) + expression.substring(1);
+ Method method = null;
+ try
+ {
+ method = clz.getMethod(GET + name, (Class[])null);
+ }
+ catch (Exception ignored)
+ {
+ }
+ if (method == null)
+ {
+ try
+ {
+ method = clz.getMethod(IS + name, (Class[])null);
+ }
+ catch (Exception e)
+ {
+ log.debug("Cannot find getter " + clz + "." + expression);
+ }
+ }
+ return method;
+ }
+
+ private final static Method findMethod(final Class<?> clz, String expression)
+ {
+ if (expression.endsWith("()"))
+ {
+ expression = expression.substring(0, expression.length() - 2);
+ }
+ Method method = null;
+ try
+ {
+ method = clz.getMethod(expression, (Class[])null);
+ }
+ catch (Exception e)
+ {
+ log.debug("Cannot find method " + clz + "." + expression);
+ }
+ return method;
+ }
+
+ /**
+ * Utility class: instantiation not allowed.
+ */
+ private PropertyResolver()
+ {
+ }
+
+ /**
+ * @author jcompagner
+ *
+ */
+ private final static class ObjectAndGetSetter
+ {
+ private final IGetAndSet getAndSetter;
+ private final Object value;
+
+ /**
+ * @param getAndSetter
+ * @param value
+ */
+ public ObjectAndGetSetter(IGetAndSet getAndSetter, Object value)
+ {
+ this.getAndSetter = getAndSetter;
+ this.value = value;
+ }
+
+ /**
+ * @param value
+ * @param converter
+ */
+ public void setValue(Object value, PropertyResolverConverter converter)
+ {
+ getAndSetter.setValue(this.value, value, converter);
+ }
+
+ /**
+ * @return The value
+ */
+ public Object getValue()
+ {
+ return getAndSetter.getValue(value);
+ }
+
+ /**
+ * @return class of property value
+ */
+ public Class<?> getTargetClass()
+ {
+ return getAndSetter.getTargetClass();
+ }
+
+ /**
+ * @return Field or null if no field exists for expression
+ */
+ public Field getField()
+ {
+ return getAndSetter.getField();
+ }
+
+ /**
+ * @return Getter method or null if no getter exists for expression
+ */
+ public Method getGetter()
+ {
+ return getAndSetter.getGetter();
+ }
+
+ /**
+ * @return Setter method or null if no setter exists for expression
+ */
+ public Method getSetter()
+ {
+ return getAndSetter.getSetter();
+ }
+ }
+
+ /**
+ * @author jcompagner
+ */
+ public static interface IGetAndSet
+ {
+ /**
+ * @param object
+ * The object where the value must be taken from.
+ *
+ * @return The value of this property
+ */
+ public Object getValue(final Object object);
+
+ /**
+ * @return The target class of the object that as to be set.
+ */
+ public Class<?> getTargetClass();
+
+ /**
+ * @param object
+ * The object where the new value must be set on.
+ *
+ * @return The new value for the property that is set back on that object.
+ */
+ public Object newValue(Object object);
+
+ /**
+ * @param object
+ * @param value
+ * @param converter
+ */
+ public void setValue(final Object object, final Object value,
+ PropertyResolverConverter converter);
+
+ /**
+ * @return Field or null if there is no field
+ */
+ public Field getField();
+
+ /**
+ * @return Getter method or null if there is no getter
+ */
+ public Method getGetter();
+
+ /**
+ * @return Setter of null if there is no setter
+ */
+ public Method getSetter();
+ }
+
+ private static abstract class AbstractGetAndSet implements IGetAndSet
+ {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Field getField()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Method getGetter()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Method getSetter()
+ {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return null;
+ }
+ }
+
+ private static final class MapGetSet extends AbstractGetAndSet
+ {
+ private final String key;
+
+ MapGetSet(String key)
+ {
+ this.key = key;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(final Object object)
+ {
+ return ((Map<?, ?>)object).get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void setValue(final Object object, final Object value,
+ final PropertyResolverConverter converter)
+ {
+ ((Map<String, Object>)object).put(key, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(final Object object)
+ {
+ // Map can't make a newValue or should it look what is more in the
+ // map and try to make one of the class if finds?
+ return null;
+ }
+ }
+
+ private static final class ListGetSet extends AbstractGetAndSet
+ {
+ final private int index;
+
+ ListGetSet(int index)
+ {
+ this.index = index;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(final Object object)
+ {
+ if (((List<?>)object).size() <= index)
+ {
+ return null;
+ }
+ return ((List<?>)object).get(index);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public void setValue(final Object object, final Object value,
+ final PropertyResolverConverter converter)
+ {
+ List<Object> lst = (List<Object>)object;
+
+ if (lst.size() > index)
+ {
+ lst.set(index, value);
+ }
+ else if (lst.size() == index)
+ {
+ lst.add(value);
+ }
+ else
+ {
+ while (lst.size() < index)
+ {
+ lst.add(null);
+ }
+ lst.add(value);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(Object object)
+ {
+ // List can't make a newValue or should it look what is more in the
+ // list and try to make one of the class if finds?
+ return null;
+ }
+ }
+
+ private static final class ArrayGetSet extends AbstractGetAndSet
+ {
+ private final int index;
+ private final Class<?> clzComponentType;
+
+ ArrayGetSet(Class<?> clzComponentType, int index)
+ {
+ this.clzComponentType = clzComponentType;
+ this.index = index;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(Object object)
+ {
+ if (Array.getLength(object) > index)
+ {
+ return Array.get(object, index);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(Object object, Object value, PropertyResolverConverter converter)
+ {
+ value = converter.convert(value, clzComponentType);
+ Array.set(object, index, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(Object object)
+ {
+ Object value = null;
+ try
+ {
+ value = clzComponentType.newInstance();
+ Array.set(object, index, value);
+ }
+ catch (Exception e)
+ {
+ log.warn("Cannot set new value " + value + " at index " + index +
+ " for array holding elements of class " + clzComponentType, e);
+ }
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return clzComponentType;
+ }
+ }
+
+ private static final class ArrayLengthGetSet extends AbstractGetAndSet
+ {
+ ArrayLengthGetSet()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(final Object object)
+ {
+ return Array.getLength(object);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(final Object object, final Object value,
+ final PropertyResolverConverter converter)
+ {
+ throw new WicketRuntimeException("You can't set the length on an array:" + object);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(final Object object)
+ {
+ throw new WicketRuntimeException("Can't get a new value from a length of an array: " +
+ object);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return int.class;
+ }
+ }
+
+ private static final class ArrayPropertyGetSet extends AbstractGetAndSet
+ {
+ final private Integer index;
+ final private Method getMethod;
+ private Method setMethod;
+
+ ArrayPropertyGetSet(final Method method, final int index)
+ {
+ this.index = index;
+ getMethod = method;
+ getMethod.setAccessible(true);
+ }
+
+ private final static Method findSetter(final Method getMethod, final Class<?> clz)
+ {
+ String name = getMethod.getName();
+ name = SET + name.substring(3);
+ try
+ {
+ return clz.getMethod(name, new Class[] { int.class, getMethod.getReturnType() });
+ }
+ catch (Exception e)
+ {
+ log.debug("Can't find setter method corresponding to " + getMethod);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(Object object)
+ {
+ Object ret = null;
+ try
+ {
+ ret = getMethod.invoke(object, index);
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new WicketRuntimeException("Error calling index property method: " +
+ getMethod + " on object: " + object, ex.getCause());
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error calling index property method: " +
+ getMethod + " on object: " + object, ex);
+ }
+ return ret;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(final Object object, final Object value,
+ final PropertyResolverConverter converter)
+ {
+ if (setMethod == null)
+ {
+ setMethod = findSetter(getMethod, object.getClass());
+ }
+ if (setMethod != null)
+ {
+ setMethod.setAccessible(true);
+ Object converted = converter.convert(value, getMethod.getReturnType());
+ if (converted == null && value != null)
+ {
+ throw new ConversionException("Can't convert value: " + value + " to class: " +
+ getMethod.getReturnType() + " for setting it on " + object);
+ }
+ try
+ {
+ setMethod.invoke(object, index, converted);
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new WicketRuntimeException("Error index property calling method: " +
+ setMethod + " on object: " + object, ex.getCause());
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error index property calling method: " +
+ setMethod + " on object: " + object, ex);
+ }
+ }
+ else
+ {
+ throw new WicketRuntimeException("No set method defined for value: " + value +
+ " on object: " + object);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return getMethod.getReturnType();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(Object object)
+ {
+ if (setMethod == null)
+ {
+ setMethod = findSetter(getMethod, object.getClass());
+ }
+
+ if (setMethod == null)
+ {
+ log.warn("Null setMethod");
+ return null;
+ }
+
+ Class<?> clz = getMethod.getReturnType();
+ Object value = null;
+ try
+ {
+ value = clz.newInstance();
+ setMethod.invoke(object, index, value);
+ }
+ catch (Exception e)
+ {
+ log.warn("Cannot set new value " + value + " at index " + index, e);
+ }
+ return value;
+ }
+ }
+
+ private static final class MethodGetAndSet extends AbstractGetAndSet
+ {
+ private final Method getMethod;
+ private final Method setMethod;
+ private final Field field;
+
+ MethodGetAndSet(Method getMethod, Method setMethod, Field field)
+ {
+ this.getMethod = getMethod;
+ this.getMethod.setAccessible(true);
+ this.field = field;
+ this.setMethod = setMethod;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final Object getValue(final Object object)
+ {
+ Object ret = null;
+ try
+ {
+ ret = getMethod.invoke(object, (Object[])null);
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new WicketRuntimeException("Error calling method: " + getMethod +
+ " on object: " + object, ex.getCause());
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error calling method: " + getMethod +
+ " on object: " + object, ex);
+ }
+ return ret;
+ }
+
+ /**
+ * @param object
+ * @param value
+ * @param converter
+ */
+ @Override
+ public final void setValue(final Object object, final Object value,
+ PropertyResolverConverter converter)
+ {
+ Class<?> type = null;
+ if (setMethod != null)
+ {
+ // getMethod is always there and if the value will be set through a setMethod then
+ // the getMethod return type will be its type. Else we have to look at the
+ // parameters if the setter but getting the return type is quicker
+ type = getMethod.getReturnType();
+ }
+ else if (field != null)
+ {
+ type = field.getType();
+ }
+
+ Object converted = null;
+ if (type != null)
+ {
+ converted = converter.convert(value, type);
+ if (converted == null)
+ {
+ if (value != null)
+ {
+ throw new ConversionException("Method [" + getMethod +
+ "]. Can't convert value: " + value + " to class: " +
+ getMethod.getReturnType() + " for setting it on " + object);
+ }
+ else if (getMethod.getReturnType().isPrimitive())
+ {
+ throw new ConversionException("Method [" + getMethod +
+ "]. Can't convert null value to a primitive class: " +
+ getMethod.getReturnType() + " for setting it on " + object);
+ }
+ }
+ }
+
+ if (setMethod != null)
+ {
+ try
+ {
+ setMethod.invoke(object, converted);
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new WicketRuntimeException("Error calling method: " + setMethod +
+ " on object: " + object, ex.getCause());
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error calling method: " + setMethod +
+ " on object: " + object, ex);
+ }
+ }
+ else if (field != null)
+ {
+ try
+ {
+ field.set(object, converted);
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error setting field: " + field +
+ " on object: " + object, ex);
+ }
+ }
+ else
+ {
+ throw new WicketRuntimeException("no set method defined for value: " + value +
+ " on object: " + object + " while respective getMethod being " +
+ getMethod.getName());
+ }
+ }
+
+ private final static Method findSetter(Method getMethod, Class<?> clz)
+ {
+ String name = getMethod.getName();
+ if (name.startsWith(GET))
+ {
+ name = SET + name.substring(3);
+ }
+ else
+ {
+ name = SET + name.substring(2);
+ }
+ try
+ {
+ Method method = clz.getMethod(name, new Class[] { getMethod.getReturnType() });
+ if (method != null)
+ {
+ method.setAccessible(true);
+ }
+ return method;
+ }
+ catch (NoSuchMethodException e)
+ {
+ Method[] methods = clz.getMethods();
+ for (Method method : methods)
+ {
+ if (method.getName().equals(name))
+ {
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length == 1)
+ {
+ if (parameterTypes[0].isAssignableFrom(getMethod.getReturnType()))
+ {
+ return method;
+ }
+ }
+ }
+ }
+ log.debug("Cannot find setter corresponding to " + getMethod);
+ }
+ catch (Exception e)
+ {
+ log.debug("Cannot find setter corresponding to " + getMethod);
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(Object object)
+ {
+ if (setMethod == null)
+ {
+ log.warn("Null setMethod");
+ return null;
+ }
+
+ Class<?> clz = getMethod.getReturnType();
+ Object value = null;
+ try
+ {
+ value = clz.newInstance();
+ setMethod.invoke(object, value);
+ }
+ catch (Exception e)
+ {
+ log.warn("Cannot set new value " + value, e);
+ }
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return getMethod.getReturnType();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Method getGetter()
+ {
+ return getMethod;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Method getSetter()
+ {
+ return setMethod;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Field getField()
+ {
+ return field;
+ }
+ }
+
+ /**
+ * @author jcompagner
+ */
+ private static class FieldGetAndSetter extends AbstractGetAndSet
+ {
+ private final Field field;
+
+ /**
+ * Construct.
+ *
+ * @param field
+ */
+ public FieldGetAndSetter(final Field field)
+ {
+ super();
+ this.field = field;
+ this.field.setAccessible(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getValue(final Object object)
+ {
+ try
+ {
+ return field.get(object);
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error getting field value of field " + field +
+ " from object " + object, ex);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object newValue(final Object object)
+ {
+ Class<?> clz = field.getType();
+ Object value = null;
+ try
+ {
+ value = clz.newInstance();
+ field.set(object, value);
+ }
+ catch (Exception e)
+ {
+ log.warn("Cannot set field " + field + " to " + value, e);
+ }
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(final Object object, Object value,
+ final PropertyResolverConverter converter)
+ {
+ value = converter.convert(value, field.getType());
+ try
+ {
+ field.set(object, value);
+ }
+ catch (Exception ex)
+ {
+ throw new WicketRuntimeException("Error setting field value of field " + field +
+ " on object " + object + ", value " + value, ex);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Class<?> getTargetClass()
+ {
+ return field.getType();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Field getField()
+ {
+ return field;
+ }
+ }
+
+ private static IClassCache getClassesToGetAndSetters()
+ {
+ Object key = null;
+ if (Application.exists())
+ {
+ key = Application.get();
+ }
+ else
+ {
+ key = PropertyResolver.class;
+ }
+ IClassCache result = applicationToClassesToGetAndSetters.get(key);
+ if (result == null)
+ {
+ applicationToClassesToGetAndSetters.put(key, result = new DefaultClassCache());
+ }
+ return result;
+ }
+
+ /**
+ * Clean up cache for this app.
+ *
+ * @param application
+ */
+ public static void destroy(Application application)
+ {
+ applicationToClassesToGetAndSetters.remove(application);
+ }
+
+ /**
+ * Sets the {@link IClassCache} for the given application.
+ *
+ * If the Application is null then it will be the default if no application is found. So if you
+ * want to be sure that your {@link IClassCache} is handled in all situations then call this
+ * method twice with your implementations. One time for the application and the second time with
+ * null.
+ *
+ * @param application
+ * to use or null if the default must be set.
+ * @param classCache
+ */
+ public static void setClassCache(final Application application, final IClassCache classCache)
+ {
+ if (application != null)
+ {
+ applicationToClassesToGetAndSetters.put(application, classCache);
+ }
+ else
+ {
+ applicationToClassesToGetAndSetters.put(PropertyResolver.class, classCache);
+ }
+ }
+
+ /**
+ * An implementation of the class can be set on the
+ * {@link PropertyResolver#setClassCache(org.apache.wicket.Application, org.apache.wicket.core.util.lang.PropertyResolver.IClassCache)}
+ * method for a specific application. This class cache can then be a special map with
+ * an eviction policy or do nothing if nothing should be cached for the given class.
+ *
+ * For example if you have proxy classes that are constantly created you could opt for not
+ * caching those at all or have a special Map implementation that will evict that class at a
+ * certain point.
+ *
+ * @author jcompagner
+ */
+ public static interface IClassCache
+ {
+ /**
+ * Put the class into the cache, or if that class shouldn't be cached do nothing.
+ *
+ * @param clz
+ * @param values
+ */
+ void put(Class<?> clz, Map<String, IGetAndSet> values);
+
+ /**
+ * Returns the class map from the cache.
+ *
+ * @param clz
+ * @return the map of the given class
+ */
+ Map<String, IGetAndSet> get(Class<?> clz);
+ }
+
+ private static class DefaultClassCache implements IClassCache
+ {
+ private final ConcurrentHashMap<Class<?>, Map<String, IGetAndSet>> map = Generics.newConcurrentHashMap(16);
+
+ @Override
+ public Map<String, IGetAndSet> get(Class<?> clz)
+ {
+ return map.get(clz);
+ }
+
+ @Override
+ public void put(Class<?> clz, Map<String, IGetAndSet> values)
+ {
+ map.put(clz, values);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolverConverter.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolverConverter.java b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolverConverter.java
new file mode 100644
index 0000000..d069041
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/PropertyResolverConverter.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.lang;
+
+import java.util.Locale;
+
+import org.apache.wicket.util.io.IClusterable;
+import org.apache.wicket.IConverterLocator;
+import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.lang.Objects;
+
+
+/**
+ * @author jcompagner
+ */
+public class PropertyResolverConverter implements IClusterable
+{
+ private static final long serialVersionUID = 1L;
+
+
+ private final IConverterLocator converterSupplier;
+ private final Locale locale;
+
+ /**
+ * Construct.
+ *
+ * @param converterSupplier
+ * @param locale
+ */
+ public PropertyResolverConverter(IConverterLocator converterSupplier, Locale locale)
+ {
+ this.converterSupplier = converterSupplier;
+ this.locale = locale;
+ }
+
+ /**
+ * @param <T>
+ * target type
+ * @param object
+ * @param clz
+ * @return converted value of the type given, or null if the value cannot be converted to the
+ * given type.
+ */
+ public <T> T convert(Object object, Class<T> clz)
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ if (clz.isAssignableFrom(object.getClass()))
+ {
+ @SuppressWarnings("unchecked")
+ T result = (T)object;
+ return result;
+ }
+ IConverter<T> converter = converterSupplier.getConverter(clz);
+ if (object instanceof String)
+ {
+ return converter.convertToObject((String)object, locale);
+ }
+ else if (clz == String.class)
+ {
+ @SuppressWarnings("unchecked")
+ T result = (T)convertToString(object, locale);
+ return result;
+ }
+ else
+ {
+ T result;
+ try
+ {
+ result = Objects.convertValue(object, clz);
+ }
+ catch (RuntimeException ex)
+ {
+ result = null;
+ }
+ if (result == null)
+ {
+ String tmp = convertToString(object, locale);
+ result = converter.convertToObject(tmp, locale);
+ }
+ return result;
+ }
+ }
+
+ protected <C> String convertToString(C object, Locale locale)
+ {
+ @SuppressWarnings("unchecked")
+ Class<C> type = (Class<C>)object.getClass();
+
+ IConverter<C> converterForObj = converterSupplier.getConverter(type);
+ return converterForObj.convertToString(object, locale);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
new file mode 100644
index 0000000..b2c41e2
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.lang;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Component;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.application.IClassResolver;
+import org.apache.wicket.settings.IApplicationSettings;
+import org.apache.wicket.util.io.ByteCountingOutputStream;
+import org.apache.wicket.util.lang.Generics;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Object (de)serialization utilities.
+ */
+public class WicketObjects
+{
+ /** log. */
+ private static final Logger log = LoggerFactory.getLogger(WicketObjects.class);
+
+ private WicketObjects()
+ {
+ }
+
+ /**
+ * @param <T>
+ * class type
+ * @param className
+ * Class to resolve
+ * @return Resolved class
+ * @throws ClassNotFoundException
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Class<T> resolveClass(final String className)
+ {
+ Class<T> resolved = null;
+ try
+ {
+ if (Application.exists())
+ {
+ resolved = (Class<T>)Application.get()
+ .getApplicationSettings()
+ .getClassResolver()
+ .resolveClass(className);
+ }
+
+ if (resolved == null)
+ {
+ resolved = (Class<T>)Class.forName(className, false, Thread.currentThread()
+ .getContextClassLoader());
+ }
+ }
+ catch (ClassNotFoundException cnfx)
+ {
+ log.warn("Could not resolve class [" + className + "]", cnfx);
+ }
+ return resolved;
+ }
+
+ /**
+ * Interface that enables users to plugin the way object sizes are calculated with Wicket.
+ */
+ public static interface IObjectSizeOfStrategy
+ {
+ /**
+ * Computes the size of an object. This typically is an estimation, not an absolute accurate
+ * size.
+ *
+ * @param object
+ * The serializable object to compute size of
+ * @return The size of the object in bytes.
+ */
+ long sizeOf(Serializable object);
+ }
+
+ /**
+ * {@link IObjectSizeOfStrategy} that works by serializing the object to an instance of
+ * {@link ByteCountingOutputStream}, which records the number of bytes written to it. Hence,
+ * this gives the size of the object as it would be serialized,including all the overhead of
+ * writing class headers etc. Not very accurate (the real memory consumption should be lower)
+ * but the best we can do in a cheap way pre JDK 5.
+ */
+ public static final class SerializingObjectSizeOfStrategy implements IObjectSizeOfStrategy
+ {
+ /**
+ * @see org.apache.wicket.core.util.lang.WicketObjects.IObjectSizeOfStrategy#sizeOf(java.io.Serializable)
+ */
+ @Override
+ public long sizeOf(Serializable object)
+ {
+ if (object == null)
+ {
+ return 0;
+ }
+ try
+ {
+ final ByteCountingOutputStream out = new ByteCountingOutputStream();
+ new ObjectOutputStream(out).writeObject(object);
+ out.close();
+ return out.size();
+ }
+ catch (IOException e)
+ {
+ if (log.isWarnEnabled())
+ {
+ log.warn("Unable to determine object size: " + object.toString(), e);
+ }
+ return -1;
+ }
+ }
+
+ }
+
+ private static final class ReplaceObjectInputStream extends ObjectInputStream
+ {
+ private final ClassLoader classloader;
+ private final HashMap<String, Component> replacedComponents;
+
+ private ReplaceObjectInputStream(InputStream in,
+ HashMap<String, Component> replacedComponents, ClassLoader classloader)
+ throws IOException
+ {
+ super(in);
+ this.replacedComponents = replacedComponents;
+ this.classloader = classloader;
+ enableResolveObject(true);
+ }
+
+ // This override is required to resolve classes inside in different
+ // bundle, i.e.
+ // The classes can be resolved by OSGI classresolver implementation
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
+ ClassNotFoundException
+ {
+ String className = desc.getName();
+
+ try
+ {
+ return Class.forName(className, true, classloader);
+ }
+ catch (ClassNotFoundException ex1)
+ {
+ // ignore this exception.
+ log.debug("Class not found by using objects own classloader, trying the IClassResolver");
+ }
+
+ Application application = Application.get();
+ IApplicationSettings applicationSettings = application.getApplicationSettings();
+ IClassResolver classResolver = applicationSettings.getClassResolver();
+
+ Class<?> candidate = null;
+ try
+ {
+ candidate = classResolver.resolveClass(className);
+ if (candidate == null)
+ {
+ candidate = super.resolveClass(desc);
+ }
+ }
+ catch (WicketRuntimeException ex)
+ {
+ if (ex.getCause() instanceof ClassNotFoundException)
+ {
+ throw (ClassNotFoundException)ex.getCause();
+ }
+ }
+ return candidate;
+ }
+
+ @Override
+ protected Object resolveObject(Object obj) throws IOException
+ {
+ Object replaced = replacedComponents.get(obj);
+ if (replaced != null)
+ {
+ return replaced;
+ }
+ return super.resolveObject(obj);
+ }
+ }
+
+ private static final class ReplaceObjectOutputStream extends ObjectOutputStream
+ {
+ private final HashMap<String, Component> replacedComponents;
+
+ private ReplaceObjectOutputStream(OutputStream out,
+ HashMap<String, Component> replacedComponents) throws IOException
+ {
+ super(out);
+ this.replacedComponents = replacedComponents;
+ enableReplaceObject(true);
+ }
+
+ @Override
+ protected Object replaceObject(Object obj) throws IOException
+ {
+ if (obj instanceof Component)
+ {
+ final Component component = (Component)obj;
+ String name = component.getPath();
+ replacedComponents.put(name, component);
+ return name;
+ }
+ return super.replaceObject(obj);
+ }
+ }
+
+ /**
+ * Makes a deep clone of an object by serializing and deserializing it. The object must be fully
+ * serializable to be cloned. This method will not clone wicket Components, it will just reuse
+ * those instances so that the complete component tree is not copied over only the model data.
+ *
+ * @param object
+ * The object to clone
+ * @return A deep copy of the object
+ */
+ public static Object cloneModel(final Object object)
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ else
+ {
+ try
+ {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
+ final HashMap<String, Component> replacedObjects = Generics.newHashMap();
+ ObjectOutputStream oos = new ReplaceObjectOutputStream(out, replacedObjects);
+ oos.writeObject(object);
+ ObjectInputStream ois = new ReplaceObjectInputStream(new ByteArrayInputStream(
+ out.toByteArray()), replacedObjects, object.getClass().getClassLoader());
+ return ois.readObject();
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new WicketRuntimeException("Internal error cloning object", e);
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException("Internal error cloning object", e);
+ }
+ }
+ }
+
+ /**
+ * Strategy for calculating sizes of objects. Note: I didn't make this an application setting as
+ * we have enough of those already, and the typical way this probably would be used is that
+ * install a different one according to the JDK version used, so varying them between
+ * applications doesn't make a lot of sense.
+ */
+ private static IObjectSizeOfStrategy objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();
+
+ /**
+ * Makes a deep clone of an object by serializing and deserializing it. The object must be fully
+ * serializable to be cloned. No extra debug info is gathered.
+ *
+ * @param object
+ * The object to clone
+ * @return A deep copy of the object
+ * @see #cloneModel(Object)
+ */
+ public static Object cloneObject(final Object object)
+ {
+ if (object == null)
+ {
+ return null;
+ }
+ else
+ {
+ try
+ {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(256);
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ oos.writeObject(object);
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
+ out.toByteArray()))
+ {
+ // This override is required to resolve classes inside in different bundle, i.e.
+ // The classes can be resolved by OSGI classresolver implementation
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
+ ClassNotFoundException
+ {
+ String className = desc.getName();
+
+ try
+ {
+ return Class.forName(className, true, object.getClass()
+ .getClassLoader());
+ }
+ catch (ClassNotFoundException ex1)
+ {
+ // ignore this exception.
+ log.debug("Class not found by using objects own classloader, trying the IClassResolver");
+ }
+
+
+ Application application = Application.get();
+ IApplicationSettings applicationSettings = application.getApplicationSettings();
+ IClassResolver classResolver = applicationSettings.getClassResolver();
+
+ Class<?> candidate = null;
+ try
+ {
+ candidate = classResolver.resolveClass(className);
+ if (candidate == null)
+ {
+ candidate = super.resolveClass(desc);
+ }
+ }
+ catch (WicketRuntimeException ex)
+ {
+ if (ex.getCause() instanceof ClassNotFoundException)
+ {
+ throw (ClassNotFoundException)ex.getCause();
+ }
+ }
+ return candidate;
+ }
+ };
+ return ois.readObject();
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new WicketRuntimeException("Internal error cloning object", e);
+ }
+ catch (IOException e)
+ {
+ throw new WicketRuntimeException("Internal error cloning object", e);
+ }
+ }
+ }
+
+ /**
+ * Creates a new instance using the current application's class resolver. Returns null if
+ * className is null.
+ *
+ * @param className
+ * The full class name
+ * @return The new object instance
+ */
+ public static Object newInstance(final String className)
+ {
+ if (!Strings.isEmpty(className))
+ {
+ try
+ {
+ Class<?> c = WicketObjects.resolveClass(className);
+ return c.newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new WicketRuntimeException("Unable to create " + className, e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the strategy for determining the sizes of objects.
+ *
+ * @param objectSizeOfStrategy
+ * the strategy. Pass null to reset to the default.
+ */
+ public static void setObjectSizeOfStrategy(IObjectSizeOfStrategy objectSizeOfStrategy)
+ {
+ if (objectSizeOfStrategy == null)
+ {
+ WicketObjects.objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();
+ }
+ else
+ {
+ WicketObjects.objectSizeOfStrategy = objectSizeOfStrategy;
+ }
+ log.info("using " + objectSizeOfStrategy + " for calculating object sizes");
+ }
+
+ /**
+ * Computes the size of an object. Note that this is an estimation, never an absolute accurate
+ * size.
+ *
+ * @param object
+ * Object to compute size of
+ * @return The size of the object in bytes
+ */
+ public static long sizeof(final Serializable object)
+ {
+ return objectSizeOfStrategy.sizeOf(object);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/lang/package.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/package.html b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/package.html
new file mode 100644
index 0000000..4e26359
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/package.html
@@ -0,0 +1,27 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<!DOCTYPE HTML PUBLIC "-//W3C/DTD HTML 3.2 Final//NL">
+<html>
+<head>
+<title>wicket.util.lang package</title>
+</head>
+<body>
+<p>
+Java utilities.
+</p>
+</body>
+</html>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/AbstractResourceStreamWriter.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/AbstractResourceStreamWriter.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/AbstractResourceStreamWriter.java
new file mode 100644
index 0000000..7971b74
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/AbstractResourceStreamWriter.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.apache.wicket.util.time.Time;
+
+/**
+ * Base implementation of an IResourceStreamWriter so that you only have to override the
+ * {@link IResourceStreamWriter#write(java.io.OutputStream)} Don't forget to overwrite the
+ * {@link IResourceStream#length()} method if you do know the total length that will be generated.
+ *
+ * @see IResourceStreamWriter
+ */
+public abstract class AbstractResourceStreamWriter implements IResourceStreamWriter
+{
+ private static final long serialVersionUID = 1L;
+
+ private Locale locale;
+
+ private String variation;
+
+ private String style;
+
+ /**
+ * Default implementation to return -1. Do override this if you know the length up front.
+ */
+ @Override
+ public Bytes length()
+ {
+ return null;
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return locale;
+ }
+
+ @Override
+ public void setLocale(final Locale locale)
+ {
+ this.locale = locale;
+ }
+
+ /**
+ * Just returns now.
+ */
+ @Override
+ public Time lastModifiedTime()
+ {
+ return Time.now();
+ }
+
+ /**
+ * this method should not be used as it is not required for resource writers
+ */
+ @Override
+ public final InputStream getInputStream() throws ResourceStreamNotFoundException
+ {
+ throw new IllegalStateException("getInputStream is not used with IResourceStreamWriter");
+ }
+
+ /**
+ * this method should not be used as it is not required for resource writers
+ * <p>
+ * resource write generate content directly, not using an input stream, so there's nothing to
+ * close later.
+ */
+ @Override
+ public final void close() throws IOException
+ {
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return null;
+ }
+
+ @Override
+ public String getStyle()
+ {
+ return style;
+ }
+
+ @Override
+ public void setStyle(String style)
+ {
+ this.style = style;
+ }
+
+ @Override
+ public String getVariation()
+ {
+ return variation;
+ }
+
+ @Override
+ public void setVariation(String variation)
+ {
+ this.variation = variation;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/IResourceStreamWriter.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/IResourceStreamWriter.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/IResourceStreamWriter.java
new file mode 100644
index 0000000..394f2a4
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/IResourceStreamWriter.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource;
+
+import org.apache.wicket.request.Response;
+import org.apache.wicket.util.resource.IResourceStream;
+
+/**
+ * Special IResourceStream implementation that a Resource can return when it directly wants to write
+ * to an output stream instead of return the {@link IResourceStream#getInputStream()}. That one can
+ * return null for a IResourceStreamWriter.
+ *
+ * This behavior comes in use when the resource is generated on the fly and should be streamed
+ * directly to the client so that it won't be buffered completely if the generated resource is
+ * really big.
+ *
+ * @author jcompagner
+ */
+public interface IResourceStreamWriter extends IResourceStream
+{
+ /**
+ * Implement this method to write the resource data directly the the given OutputStream.
+ *
+ * @param output
+ * The response where the resource can write its content into.
+ */
+ void write(Response output);
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
new file mode 100644
index 0000000..770d29b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/PackageResourceStream.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.lang.Packages;
+import org.apache.wicket.util.resource.AbstractResourceStream;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.apache.wicket.util.time.Time;
+
+
+/**
+ * An {@link IResourceStream} that reads data from a resource in the classpath. It simply delegates
+ * all operations to the {@link IResourceStream} returned by the application's
+ * {@link IResourceStreamLocator}.
+ *
+ * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
+ */
+public class PackageResourceStream extends AbstractResourceStream
+{
+ /** */
+ private static final long serialVersionUID = 1L;
+
+ private IResourceStream resourceStream;
+
+ /**
+ * Obtains an {@link IResourceStream} from the application's
+ * {@link IResourceStreamLocator#locate(Class, String)}
+ *
+ * @param scope
+ * This argument will be used to get the class loader for loading the package
+ * resource, and to determine what package it is in.
+ * @param path
+ * The path to the resource
+ */
+ public PackageResourceStream(Class<?> scope, String path)
+ {
+ String absolutePath = Packages.absolutePath(scope, path);
+ resourceStream = Application.get()
+ .getResourceSettings()
+ .getResourceStreamLocator()
+ .locate(scope, absolutePath, null, null, null, null, false);
+
+ if (resourceStream == null)
+ {
+ throw new WicketRuntimeException("Cannot find resource with " + scope.getName() +
+ " and path " + path);
+ }
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ resourceStream.close();
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return resourceStream.getContentType();
+ }
+
+ @Override
+ public InputStream getInputStream() throws ResourceStreamNotFoundException
+ {
+ return resourceStream.getInputStream();
+ }
+
+ @Override
+ public Bytes length()
+ {
+ return resourceStream.length();
+ }
+
+ @Override
+ public Time lastModifiedTime()
+ {
+ return resourceStream.lastModifiedTime();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/UrlResourceStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/UrlResourceStream.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/UrlResourceStream.java
new file mode 100644
index 0000000..98aed1a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/UrlResourceStream.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.util.io.Connections;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.lang.Objects;
+import org.apache.wicket.util.resource.AbstractResourceStream;
+import org.apache.wicket.util.resource.IFixedLocationResourceStream;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.apache.wicket.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * UrlResourceStream implements IResource for URLs.
+ *
+ * @see org.apache.wicket.util.resource.IResourceStream
+ * @see org.apache.wicket.util.watch.IModifiable
+ * @author Jonathan Locke
+ * @author Igor Vaynberg
+ */
+public class UrlResourceStream extends AbstractResourceStream
+ implements
+ IFixedLocationResourceStream
+{
+ private static final long serialVersionUID = 1L;
+
+ /** Logging. */
+ private static final Logger log = LoggerFactory.getLogger(UrlResourceStream.class);
+
+ /**
+ * The meta data for this stream. Lazy loaded on demand.
+ */
+ private transient StreamData streamData;
+
+ /** The URL to this resource. */
+ private final URL url;
+
+ /** Last known time the stream was last modified. */
+ private Time lastModified;
+
+ /**
+ * Meta data class for the stream attributes
+ */
+ private static class StreamData
+ {
+ private URLConnection connection;
+
+ /**
+ * The streams read from this connection.
+ * Some URLConnection implementations return the same instance of InputStream
+ * every time URLConnection#getInputStream() is called. Other return a new instance
+ * of InputStream.
+ * Here we keep a list of all returned ones and close them in UrlResourceStream#close().
+ * Even it is the same instance several times we will try to close it quietly several times.
+ */
+ private List<InputStream> inputStreams;
+
+ /** Length of stream. */
+ private long contentLength;
+
+ /** Content type for stream. */
+ private String contentType;
+
+ }
+
+ /**
+ * Construct.
+ *
+ * @param url
+ * URL of resource
+ */
+ public UrlResourceStream(final URL url)
+ {
+ // save the url
+ this.url = Args.notNull(url, "url");
+ }
+
+ /**
+ * Lazy loads the stream settings on demand
+ *
+ * @param initialize
+ * a flag indicating whether to load the settings
+ * @return the meta data with the stream settings
+ */
+ private StreamData getData(boolean initialize)
+ {
+ if (streamData == null && initialize)
+ {
+ streamData = new StreamData();
+
+ try
+ {
+ streamData.connection = url.openConnection();
+ streamData.contentLength = streamData.connection.getContentLength();
+ streamData.contentType = streamData.connection.getContentType();
+
+ if (streamData.contentType == null || streamData.contentType.contains("unknown"))
+ {
+ if (Application.exists())
+ {
+ streamData.contentType = Application.get().getMimeType(url.getFile());
+ }
+ else
+ {
+ streamData.contentType = URLConnection.getFileNameMap().getContentTypeFor(
+ url.getFile());
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ throw new IllegalArgumentException("Invalid URL parameter " + url, ex);
+ }
+ }
+
+ return streamData;
+ }
+
+ /**
+ * Closes this resource.
+ *
+ * @throws IOException
+ */
+ @Override
+ public void close() throws IOException
+ {
+ StreamData data = getData(false);
+
+ if (data != null)
+ {
+ Connections.closeQuietly(data.connection);
+ if (data.inputStreams != null)
+ {
+ for (InputStream is : data.inputStreams) {
+ IOUtils.closeQuietly(is);
+ }
+ }
+ streamData = null;
+ }
+ }
+
+ /**
+ * @return A readable input stream for this resource.
+ * @throws ResourceStreamNotFoundException
+ */
+ @Override
+ public InputStream getInputStream() throws ResourceStreamNotFoundException
+ {
+ try
+ {
+ StreamData data = getData(true);
+ InputStream is = data.connection.getInputStream();
+ if (data.inputStreams == null) {
+ data.inputStreams = new ArrayList<InputStream>();
+ }
+ data.inputStreams.add(is);
+ return is;
+ }
+ catch (IOException e)
+ {
+ throw new ResourceStreamNotFoundException("Resource " + url + " could not be opened", e);
+ }
+ }
+
+ /**
+ * @return The URL to this resource (if any)
+ */
+ public URL getURL()
+ {
+ return url;
+ }
+
+ /**
+ * @see org.apache.wicket.util.watch.IModifiable#lastModifiedTime()
+ * @return The last time this resource was modified
+ */
+ @Override
+ public Time lastModifiedTime()
+ {
+ try
+ {
+ // get url modification timestamp
+ final Time time = Connections.getLastModified(url);
+
+ // if timestamp changed: update content length and last modified date
+ if (Objects.equal(time, lastModified) == false)
+ {
+ lastModified = time;
+ updateContentLength();
+ }
+ return lastModified;
+ }
+ catch (IOException e)
+ {
+ log.warn("getLastModified for " + url + " failed: " + e.getMessage());
+
+ // allow modification watcher to detect the problem
+ return null;
+ }
+ }
+
+ private void updateContentLength() throws IOException
+ {
+ StreamData data = getData(false);
+
+ if (data != null)
+ {
+ URLConnection connection = url.openConnection();
+ try {
+ data.contentLength = connection.getContentLength();
+ } finally {
+ Connections.close(connection);
+ }
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return url.toString();
+ }
+
+ /**
+ * @return The content type of this resource, such as "image/jpeg" or "text/html"
+ */
+ @Override
+ public String getContentType()
+ {
+ return getData(true).contentType;
+ }
+
+ @Override
+ public Bytes length()
+ {
+ long length = getData(true).contentLength;
+
+ if (length == -1)
+ {
+ return null;
+ }
+
+ return Bytes.bytes(length);
+ }
+
+ @Override
+ public String locationAsString()
+ {
+ return url.toExternalForm();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/WebExternalResourceStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/WebExternalResourceStream.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/WebExternalResourceStream.java
new file mode 100644
index 0000000..3991b28
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/WebExternalResourceStream.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.servlet.ServletContext;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.io.Connections;
+import org.apache.wicket.util.io.IOUtils;
+import org.apache.wicket.util.lang.Bytes;
+import org.apache.wicket.util.resource.AbstractResourceStream;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.apache.wicket.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An {@link org.apache.wicket.util.resource.IResourceStream} that reads data from a file in the web application
+ *
+ * @author <a href="mailto:jbq@apache.org">Jean-Baptiste Quenot</a>
+ */
+public class WebExternalResourceStream extends AbstractResourceStream
+{
+ private static final Logger log = LoggerFactory.getLogger(WebExternalResourceStream.class);
+ private static final long serialVersionUID = 1L;
+
+ transient InputStream in;
+
+ /** the relative url of the external resource. */
+ private final String url;
+
+ /**
+ * Construct.
+ *
+ * @param url
+ * the relative url of the external resource
+ */
+ public WebExternalResourceStream(String url)
+ {
+ if (url == null)
+ {
+ throw new IllegalArgumentException("Argument url must be not null");
+ }
+
+ this.url = url;
+ }
+
+ @Override
+ public Bytes length()
+ {
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ // getInputStream() is not always called (WICKET-790)
+ IOUtils.close(in);
+ }
+
+ @Override
+ public Time lastModifiedTime()
+ {
+ try
+ {
+ final ServletContext context = ((WebApplication)Application.get()).getServletContext();
+ final URL resourceURL = context.getResource(url);
+ if (resourceURL == null)
+ {
+ throw new FileNotFoundException("Unable to find resource '" + url +
+ "' in the serlvet context");
+ }
+
+ return Connections.getLastModified(resourceURL);
+ }
+ catch (IOException e)
+ {
+ log.warn("failed to retrieve last modified timestamp", e);
+ return null;
+ }
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return WebApplication.get().getServletContext().getMimeType(url);
+ }
+
+ @Override
+ public InputStream getInputStream() throws ResourceStreamNotFoundException
+ {
+ final ServletContext context = ((WebApplication)Application.get()).getServletContext();
+
+ in = context.getResourceAsStream(url);
+ if (in == null)
+ {
+ throw new ResourceStreamNotFoundException("The requested resource was not found: " +
+ url);
+ }
+ return in;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/EmptyResourceNameIterator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/EmptyResourceNameIterator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/EmptyResourceNameIterator.java
new file mode 100644
index 0000000..9dcd5aa
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/EmptyResourceNameIterator.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource.locator;
+
+/**
+ * A ResourceNameIterator that doesn't find any resources.
+ *
+ * @since 1.5.5
+ */
+public final class EmptyResourceNameIterator extends ResourceNameIterator
+{
+ /**
+ * Constructor.
+ */
+ public EmptyResourceNameIterator()
+ {
+ super(null, null, null, null, null, true);
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return false;
+ }
+
+ @Override
+ public void remove()
+ {
+ // noop
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
new file mode 100644
index 0000000..b1ffe27
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource.locator;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * Iterate over a set of extensions. If null is provided, hasNext() will
+ * successfully return once with next() returning {@code null}.
+ *
+ * @author Juergen Donnerstag
+ */
+public class ExtensionResourceNameIterator implements Iterator<String>
+{
+ private static final Iterable<String> NULL_ITERABLE = Arrays.asList((String)null);
+
+ private final Iterator<String> iterator;
+
+ private String current;
+
+ /**
+ * Construct.
+ *
+ * @param extensions
+ * {@code null} or iterable with extensions
+ */
+ public ExtensionResourceNameIterator(final Iterable<String> extensions)
+ {
+ // Fail safe: hasNext() needs to return at least once with true
+ if (extensions == null || !extensions.iterator().hasNext())
+ {
+ this.iterator = NULL_ITERABLE.iterator();
+ }
+ else
+ {
+ this.iterator = extensions.iterator();
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return iterator.hasNext();
+ }
+
+ /**
+ * @return The next filename extension.
+ */
+ @Override
+ public String next()
+ {
+ current = iterator.next();
+ return getExtension();
+ }
+
+ /**
+ * @return Assuming you've called next() already, it'll return the very same value.
+ */
+ public final String getExtension()
+ {
+ String ext = current;
+
+ if (ext != null)
+ {
+ if (ext.startsWith("."))
+ {
+ ext = ext.substring(1);
+ }
+ }
+ return ext;
+ }
+
+ @Override
+ public void remove()
+ {
+ iterator.remove();
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/IResourceStreamLocator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/IResourceStreamLocator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/IResourceStreamLocator.java
new file mode 100644
index 0000000..cf52ce4
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/IResourceStreamLocator.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.wicket.core.util.resource.locator;
+
+import java.util.Locale;
+
+import org.apache.wicket.util.resource.IResourceStream;
+
+
+/**
+ * Interface for code that locates resources.
+ *
+ * @author Jonathan Locke
+ */
+public interface IResourceStreamLocator
+{
+ /**
+ * Locate a resource, given a path and class. Typically this method is either called by external
+ * clients if they are not interested in a lookup that takes the style and locale into account,
+ * or it is called by the implementation of
+ * {@link #locate(Class, String, String, String, java.util.Locale, String, boolean)} where the latter just takes care of
+ * trying out the different combinations for the provided style and locale and uses this method
+ * to actually load the resource stream.
+ *
+ * @param clazz
+ * The class loader for delegating the loading of the resource
+ * @param path
+ * The path of the resource
+ *
+ * @return The resource or null
+ */
+ IResourceStream locate(Class<?> clazz, String path);
+
+ /**
+ * Locate a resource by combining the given path, style, variation, locale and extension
+ * parameters. The exact search order depends on the implementation.
+ *
+ * @param clazz
+ * The class loader for delegating the loading of the resource
+ * @param path
+ * The path of the resource
+ * @param style
+ * Any resource style, such as a skin style (see {@link org.apache.wicket.Session})
+ * @param variation
+ * The component's variation (of the style)
+ * @param locale
+ * The locale of the resource to load
+ * @param extension
+ * A comma separate list of extensions
+ * @param strict
+ * whether the specified attributes must match exactly
+ * @return The resource or null
+ */
+ IResourceStream locate(Class<?> clazz, String path, String style, String variation,
+ Locale locale, String extension, boolean strict);
+
+ /**
+ * Markup resources and Properties files both need to iterate over different combinations of
+ * locale, style, etc.. And though no single locate(..) method exists which is used by both,
+ * they both use ResourceNameIterators.
+ *
+ * @param path
+ * The path of the resource
+ * @param style
+ * Any resource style, such as a skin style (see {@link org.apache.wicket.Session})
+ * @param variation
+ * The component's variation (of the style)
+ * @param locale
+ * The locale of the resource to load
+ * @param extension
+ * A comma separate list of extensions
+ * @param strict
+ * whether the specified attributes must match exactly
+ * @return resource name iterator
+ */
+ ResourceNameIterator newResourceNameIterator(String path, Locale locale, String style,
+ String variation, String extension, boolean strict);
+}