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

[9/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/resource/locator/LocaleResourceNameIterator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
new file mode 100644
index 0000000..a2d2763
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
@@ -0,0 +1,154 @@
+/*
+ * 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.Iterator;
+import java.util.Locale;
+
+import org.apache.wicket.util.string.Strings;
+
+
+/**
+ * Given a Locale it'll iterate over all possible combinations of the attrs making up the Locale.
+ * Starting the Locale provided to more 'weaker' combinations. The latest one will be no Locale in
+ * which case an empty string will be returned.
+ *
+ * @author Juergen Donnerstag
+ */
+public class LocaleResourceNameIterator implements Iterator<String>
+{
+	/** The locale (see Session) */
+	private final Locale locale;
+
+	/** Internal state */
+	private int state = 0;
+
+	private final boolean strict;
+
+	/**
+	 * Construct.
+	 *
+	 * @param locale
+	 * @param strict
+	 */
+	public LocaleResourceNameIterator(final Locale locale, boolean strict)
+	{
+		this.locale = locale;
+		this.strict = strict;
+	}
+
+	/**
+	 * @return Locale
+	 */
+	public Locale getLocale()
+	{
+		if (state == 1)
+		{
+			// Language, country, variation
+			return locale;
+		}
+		else if (state == 2)
+		{
+			return new Locale(locale.getLanguage(), locale.getCountry());
+		}
+		else if (state == 3)
+		{
+			return new Locale(locale.getLanguage());
+		}
+		return null;
+	}
+
+	/**
+	 *
+	 * @see java.util.Iterator#hasNext()
+	 */
+	@Override
+	public boolean hasNext()
+	{
+		int limit = 4;
+		if (strict && locale != null)
+		{
+			// omit the last step
+			limit = 3;
+		}
+		return (state < limit);
+	}
+
+	/**
+	 * @see java.util.Iterator#next()
+	 */
+	@Override
+	public String next()
+	{
+		if (locale == null)
+		{
+			state = 999;
+			return "";
+		}
+
+		// Get language and country, either of which may be the empty string
+		final String language = locale.getLanguage();
+		final String country = locale.getCountry();
+		final String variant = locale.getVariant();
+
+		// 1. If variant are available
+		if (state == 0)
+		{
+			state++;
+			if (!Strings.isEmpty(variant))
+			{
+				return '_' + language + '_' + country + '_' + variant;
+			}
+		}
+
+
+		// 2. If country available
+		if (state == 1)
+		{
+			state++;
+
+			if (!Strings.isEmpty(country))
+			{
+				return '_' + language + '_' + country;
+			}
+		}
+
+
+		// 4. If language is available
+		if (state == 2)
+		{
+			state++;
+			if (!Strings.isEmpty(language))
+			{
+				return '_' + language;
+			}
+		}
+
+		// 4. The path only; without locale
+		state++;
+		return "";
+	}
+
+	/**
+	 *
+	 * @see java.util.Iterator#remove()
+	 */
+	@Override
+	public void remove()
+	{
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/OsgiResourceStreamLocator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/OsgiResourceStreamLocator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/OsgiResourceStreamLocator.java
new file mode 100644
index 0000000..9037c8a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/OsgiResourceStreamLocator.java
@@ -0,0 +1,56 @@
+/*
+ * 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 org.apache.wicket.util.file.IResourceFinder;
+import org.apache.wicket.util.resource.IResourceStream;
+
+/**
+ * OSGI specific resource stream factory
+ *
+ * @author Juergen Donnerstag
+ */
+public class OsgiResourceStreamLocator extends ResourceStreamLocator
+{
+	/**
+	 * Construct.
+	 */
+	public OsgiResourceStreamLocator()
+	{
+	}
+
+	/**
+	 * Construct.
+	 *
+	 * @param finder
+	 */
+	public OsgiResourceStreamLocator(final IResourceFinder finder)
+	{
+		super(finder);
+	}
+
+	/**
+	 *
+	 * @see org.apache.wicket.util.resource.locator.ResourceStreamLocator#locate(java.lang.Class,
+	 *      java.lang.String)
+	 */
+	@Override
+	public IResourceStream locate(final Class<?> clazz, final String path)
+	{
+		return super.locate(clazz, "/" + path);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
new file mode 100644
index 0000000..899042e
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
@@ -0,0 +1,290 @@
+/*
+ * 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;
+import java.util.Locale;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Contains the logic to locate a resource based on a path, style (see
+ * {@link org.apache.wicket.Session}), variation, locale and extension strings. The full filename
+ * will be built like:
+ * &lt;path&gt;_&lt;variation&gt;_&lt;_&lt;style&gt;_&lt;locale&gt;.&lt;extension&gt;.
+ * <p>
+ * Resource matches will be attempted in the following order:
+ * <ol>
+ * <li>1. &lt;path&gt;_&lt;style&gt;_&lt;locale&gt;.&lt;extension&gt;</li>
+ * <li>2. &lt;path&gt;_&lt;locale&gt;.&lt;extension&gt;</li>
+ * <li>3. &lt;path&gt;_&lt;style&gt;.&lt;extension&gt;</li>
+ * <li>4. &lt;path&gt;.&lt;extension&gt;</li>
+ * </ol>
+ * <p>
+ * Locales may contain a language, a country and a region or variant. Combinations of these
+ * components will be attempted in the following order:
+ * <ol>
+ * <li>locale.toString() see javadoc for Locale for more details</li>
+ * <li>&lt;language&gt;_&lt;country&gt;</li>
+ * <li>&lt;language&gt;</li>
+ * </ol>
+ *
+ * @author Juergen Donnerstag
+ */
+public class ResourceNameIterator implements Iterator<String>
+{
+	// The base path without extension, style, locale etc.
+	private final String path;
+
+	// The extensions to search for the resource file
+	private final Iterable<String> extensions;
+
+	// The locale to search for the resource file
+	private final Locale locale;
+
+	// Do not test any combinations. Just return the full path based on the locale, style etc.
+	// provided. Only iterate over the extensions provided.
+	private final boolean strict;
+
+	// The various iterators used to locate the resource file
+	private final StyleAndVariationResourceNameIterator styleIterator;
+	private LocaleResourceNameIterator localeIterator;
+	private ExtensionResourceNameIterator extensionsIterator;
+
+	/**
+	 * Construct.
+	 *
+	 * @param path
+	 *            The path of the resource. In case the parameter 'extensions' is null, the path
+	 *            will be checked and if a filename extension is present, it'll be used instead.
+	 * @param style
+	 *            A theme or style (see {@link org.apache.wicket.Session})
+	 * @param variation
+	 *            The component's variation (of the style)
+	 * @param locale
+	 *            The Locale to apply
+	 * @param extensions
+	 *            the filename's extensions
+	 * @param strict
+	 *            If false, weaker combinations of style, locale, etc. are tested as well
+	 */
+	public ResourceNameIterator(final String path, final String style, final String variation,
+		final Locale locale, final Iterable<String> extensions, final boolean strict)
+	{
+		this.locale = locale;
+
+		boolean noext = extensions == null || !extensions.iterator().hasNext();
+
+		if (noext && (path != null) && (path.indexOf('.') != -1))
+		{
+			String[] extns = Strings.split(Strings.afterLast(path, '.'), ',');
+			this.extensions = Arrays.asList(extns);
+			this.path = Strings.beforeLast(path, '.');
+		}
+		else
+		{
+			this.extensions = extensions;
+			this.path = path;
+		}
+
+		styleIterator = newStyleAndVariationResourceNameIterator(style, variation);
+		this.strict = strict;
+	}
+
+	/**
+	 * Get the exact Locale which has been used for the latest resource path.
+	 *
+	 * @return current Locale
+	 */
+	public final Locale getLocale()
+	{
+		return localeIterator.getLocale();
+	}
+
+	/**
+	 * Get the exact Style which has been used for the latest resource path.
+	 *
+	 * @return current Style
+	 */
+	public final String getStyle()
+	{
+		return styleIterator.getStyle();
+	}
+
+	/**
+	 * Get the exact Variation which has been used for the latest resource path.
+	 *
+	 * @return current Variation
+	 */
+	public final String getVariation()
+	{
+		return styleIterator.getVariation();
+	}
+
+	/**
+	 * Get the exact filename extension used for the latest resource path.
+	 *
+	 * @return current filename extension
+	 */
+	public final String getExtension()
+	{
+		return extensionsIterator.getExtension();
+	}
+
+	/**
+	 * @see java.util.Iterator#hasNext()
+	 */
+	@Override
+	public boolean hasNext()
+	{
+		// Most inner loop. Loop through all extensions provided
+		if (extensionsIterator != null)
+		{
+			if (extensionsIterator.hasNext() == true)
+			{
+				return true;
+			}
+
+			// If there are no more extensions, than return to the next outer
+			// loop (locale). Get the next value from that loop and start
+			// over again with the first extension in the list.
+			extensionsIterator = null;
+		}
+
+		// 2nd inner loop: Loop through all Locale combinations
+		if (localeIterator != null)
+		{
+			while (localeIterator.hasNext())
+			{
+				localeIterator.next();
+
+				extensionsIterator = newExtensionResourceNameIterator(extensions);
+				if (extensionsIterator.hasNext() == true)
+				{
+					return true;
+				}
+			}
+			localeIterator = null;
+		}
+
+		// Most outer loop: Loop through all combinations of styles and variations
+		while (styleIterator.hasNext())
+		{
+			styleIterator.next();
+
+			localeIterator = newLocaleResourceNameIterator(locale, strict);
+			while (localeIterator.hasNext())
+			{
+				localeIterator.next();
+
+				extensionsIterator = newExtensionResourceNameIterator(extensions);
+				if (extensionsIterator.hasNext() == true)
+				{
+					return true;
+				}
+			}
+
+			if (strict)
+			{
+				break;
+			}
+		}
+
+		// No more combinations found. End of iteration.
+		return false;
+	}
+
+	/**
+	 * @see java.util.Iterator#next()
+	 */
+	@Override
+	public String next()
+	{
+		if (extensionsIterator != null)
+		{
+			extensionsIterator.next();
+
+			return toString();
+		}
+		throw new WicketRuntimeException(
+			"Illegal call of next(). Iterator not properly initialized");
+	}
+
+	/**
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString()
+	{
+		return path + prepend(getVariation(), '_') + prepend(getStyle(), '_') +
+			prepend(getLocale(), '_') + prepend(getExtension(), '.');
+	}
+
+	/**
+	 *
+	 * @param string
+	 * @param prepend
+	 * @return The string prepended with the char
+	 */
+	private String prepend(Object string, char prepend)
+	{
+		return (string != null) ? prepend + string.toString() : "";
+	}
+
+	/**
+	 * @param locale
+	 * @param strict
+	 * @return New iterator
+	 */
+	protected LocaleResourceNameIterator newLocaleResourceNameIterator(final Locale locale,
+		boolean strict)
+	{
+		return new LocaleResourceNameIterator(locale, strict);
+	}
+
+	/**
+	 *
+	 * @param style
+	 * @param variation
+	 * @return new iterator
+	 */
+	protected StyleAndVariationResourceNameIterator newStyleAndVariationResourceNameIterator(
+		final String style, final String variation)
+	{
+		return new StyleAndVariationResourceNameIterator(style, variation);
+	}
+
+	/**
+	 * @param extensions
+	 * @return New iterator
+	 */
+	protected ExtensionResourceNameIterator newExtensionResourceNameIterator(final Iterable<String> extensions)
+	{
+		return new ExtensionResourceNameIterator(extensions);
+	}
+
+	/**
+	 * @see java.util.Iterator#remove()
+	 */
+	@Override
+	public void remove()
+	{
+		// ignore
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceStreamLocator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceStreamLocator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceStreamLocator.java
new file mode 100644
index 0000000..7e614b7
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceStreamLocator.java
@@ -0,0 +1,282 @@
+/*
+ * 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.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Locale;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.util.file.IResourceFinder;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.ResourceUtils;
+import org.apache.wicket.util.resource.ResourceUtils.PathLocale;
+import org.apache.wicket.core.util.resource.UrlResourceStream;
+import org.apache.wicket.util.string.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Locate Wicket resource.
+ * <p>
+ * Contains the logic to locate a resource based on a path, a style (see
+ * {@link org.apache.wicket.Session}), a locale and an extension string. The full filename will be
+ * built like: &lt;path&gt;_&lt;style&gt;_&lt;locale&gt;.&lt;extension&gt;.
+ * <p>
+ * Resource matches will be attempted in the following order:
+ * <ol>
+ * <li>1. &lt;path&gt;_&lt;style&gt;_&lt;locale&gt;.&lt;extension&gt;</li>
+ * <li>2. &lt;path&gt;_&lt;locale&gt;.&lt;extension&gt;</li>
+ * <li>3. &lt;path&gt;_&lt;style&gt;.&lt;extension&gt;</li>
+ * <li>4. &lt;path&gt;.&lt;extension&gt;</li>
+ * </ol>
+ * <p>
+ * Locales may contain a language, a country and a region or variant. Combinations of these
+ * components will be attempted in the following order:
+ * <ol>
+ * <li>locale.toString() see javadoc for Locale for more details</li>
+ * <li>&lt;language&gt;_&lt;country&gt;</li>
+ * <li>&lt;language&gt;</li>
+ * </ol>
+ *
+ * @author Juergen Donnerstag
+ * @author Jonathan Locke
+ */
+public class ResourceStreamLocator implements IResourceStreamLocator
+{
+	/** Logging */
+	private static final Logger log = LoggerFactory.getLogger(ResourceStreamLocator.class);
+
+	private static final Iterable<String> NO_EXTENSIONS = new ArrayList<String>(0);
+
+	/** If null, the application registered finder will be used */
+	private IResourceFinder finder;
+
+	/**
+	 * Constructor
+	 */
+	public ResourceStreamLocator()
+	{
+	}
+
+	/**
+	 * Constructor
+	 *
+	 * @param finder
+	 *            resource finder
+	 */
+	public ResourceStreamLocator(final IResourceFinder finder)
+	{
+		this.finder = finder;
+	}
+
+	/**
+	 *
+	 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#locate(java.lang.Class,
+	 *      java.lang.String)
+	 */
+	@Override
+	public IResourceStream locate(final Class<?> clazz, final String path)
+	{
+		// First try with the resource finder registered with the application
+		// (allows for markup reloading)
+		IResourceStream stream = locateByResourceFinder(clazz, path);
+		if (stream != null)
+		{
+			return stream;
+		}
+
+		// Then search the resource on the classpath
+		stream = locateByClassLoader(clazz, path);
+		if (stream != null)
+		{
+			return stream;
+		}
+
+		return null;
+	}
+
+	/**
+	 *
+	 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#locate(java.lang.Class,
+	 *      java.lang.String, java.lang.String, java.lang.String, java.util.Locale,
+	 *      java.lang.String, boolean)
+	 */
+	@Override
+	public IResourceStream locate(final Class<?> clazz, String path, final String style,
+		final String variation, Locale locale, final String extension, final boolean strict)
+	{
+		// If path contains a locale, than it'll replace the locale provided to this method
+		PathLocale data = ResourceUtils.getLocaleFromFilename(path);
+		if ((data != null) && (data.locale != null))
+		{
+			path = data.path;
+			locale = data.locale;
+		}
+
+		// Try the various combinations of style, locale and extension to find the resource.
+		ResourceNameIterator iter = newResourceNameIterator(path, locale, style, variation,
+			extension, strict);
+		while (iter.hasNext())
+		{
+			String newPath = iter.next();
+
+			IResourceStream stream = locate(clazz, newPath);
+			if (stream != null)
+			{
+				stream.setLocale(iter.getLocale());
+				stream.setStyle(iter.getStyle());
+				stream.setVariation(iter.getVariation());
+				return stream;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Search the the resource my means of the various classloaders available
+	 *
+	 * @param clazz
+	 * @param path
+	 * @return resource stream
+	 */
+	protected IResourceStream locateByClassLoader(final Class<?> clazz, final String path)
+	{
+		IResourceStream resourceStream = null;
+
+		if (clazz != null)
+		{
+			resourceStream = getResourceStream(clazz.getClassLoader(), path);
+			if (resourceStream != null)
+			{
+				return resourceStream;
+			}
+		}
+
+		// use context classloader when no specific classloader is set
+		// (package resources for instance)
+		resourceStream = getResourceStream(Thread.currentThread().getContextClassLoader(), path);
+		if (resourceStream != null)
+		{
+			return resourceStream;
+		}
+
+		// use Wicket classloader when no specific classloader is set
+		resourceStream = getResourceStream(getClass().getClassLoader(), path);
+		if (resourceStream != null)
+		{
+			return resourceStream;
+		}
+		return null;
+	}
+
+	/**
+	 * Get the resource
+	 *
+	 * @param classLoader
+	 * @param path
+	 * @return resource stream
+	 */
+	private IResourceStream getResourceStream(final ClassLoader classLoader, final String path)
+	{
+		if (classLoader == null)
+		{
+			return null;
+		}
+
+		if (log.isDebugEnabled())
+		{
+			log.debug("Attempting to locate resource '" + path + "' using classloader " +
+				classLoader);
+		}
+
+		// Try loading path using classloader
+		URL url = classLoader.getResource(path);
+		if (url == null)
+		{
+			// maybe it is in the Servlet 3.0 like directory
+			url = classLoader.getResource("META-INF/resources/" + path);
+		}
+
+		if (url != null)
+		{
+			return new UrlResourceStream(url);
+		}
+		return null;
+	}
+
+	/**
+	 * Search the resource by means of the application registered resource finder
+	 *
+	 * @param clazz
+	 * @param path
+	 * @return resource stream
+	 */
+	protected IResourceStream locateByResourceFinder(final Class<?> clazz, final String path)
+	{
+		if (finder == null)
+		{
+			finder = Application.get().getResourceSettings().getResourceFinder();
+		}
+
+		// Log attempt
+		if (log.isDebugEnabled())
+		{
+			log.debug("Attempting to locate resource '" + path + "' on path " + finder);
+		}
+
+		// Try to find file resource on the path supplied
+		return finder.find(clazz, path);
+	}
+
+	/**
+	 *
+	 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator#newResourceNameIterator(java.lang.String,
+	 *      java.util.Locale, java.lang.String, java.lang.String, java.lang.String, boolean)
+	 */
+	@Override
+	public ResourceNameIterator newResourceNameIterator(final String path, final Locale locale,
+		final String style, final String variation, final String extension, final boolean strict)
+	{
+		final Iterable<String> extensions = extension == null ? NO_EXTENSIONS : Arrays.asList(extension);
+
+		final String realPath;
+		final String realExtension;
+
+		if ((extension == null) && (path != null) && (path.indexOf('.') != -1))
+		{
+			realPath = Strings.beforeLast(path, '.');
+			// for extensions with separator take the first extension
+			realExtension = Strings.afterLast(path, '.');
+			if (realExtension.indexOf(',') > -1)
+			{
+				// multiple extensions are not allowed in the path parameter
+				return new EmptyResourceNameIterator();
+			}
+		}
+		else
+		{
+			realPath = path;
+			realExtension = extension;
+		}
+
+		return new ResourceNameIterator(path, style, variation, locale, extensions, strict);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/StyleAndVariationResourceNameIterator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/StyleAndVariationResourceNameIterator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/StyleAndVariationResourceNameIterator.java
new file mode 100644
index 0000000..29596a6
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/StyleAndVariationResourceNameIterator.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.locator;
+
+import java.util.Iterator;
+
+/**
+ * Iterate over all possible combinations of style and variation
+ *
+ * @author Juergen Donnerstag
+ */
+public class StyleAndVariationResourceNameIterator implements Iterator<String>
+{
+	/** The style (see Session) */
+	private final String style;
+
+	/** The variation (see Component) */
+	private final String variation;
+
+	/** Internal state */
+	private int state = 0;
+
+	/**
+	 * Construct.
+	 *
+	 * @param style
+	 * @param variation
+	 */
+	public StyleAndVariationResourceNameIterator(final String style, final String variation)
+	{
+		this.style = style;
+		this.variation = variation;
+	}
+
+	/**
+	 *
+	 * @see java.util.Iterator#hasNext()
+	 */
+	@Override
+	public boolean hasNext()
+	{
+		return (state < 4);
+	}
+
+	/**
+	 * The return value will always be null. Use getStyle() and getVariation() instead.
+	 *
+	 * @see java.util.Iterator#next()
+	 */
+	@Override
+	public String next()
+	{
+		if (state == 0)
+		{
+			state++;
+			if ((style != null) && (variation != null))
+			{
+				return null;
+			}
+		}
+
+		if (state == 1)
+		{
+			state++;
+			if (style != null)
+			{
+				return null;
+			}
+		}
+
+		if (state == 2)
+		{
+			state++;
+			if (variation != null)
+			{
+				return null;
+			}
+		}
+
+		state = 4;
+		return null;
+	}
+
+	/**
+	 * @return Gets the style related to the iterator state
+	 */
+	public final String getStyle()
+	{
+		return ((state == 1) || (state == 2)) ? style : null;
+	}
+
+	/**
+	 * @return Gets the variation related to the iterator state
+	 */
+	public final String getVariation()
+	{
+		return ((state == 1) || (state == 3)) ? variation : null;
+	}
+
+	/**
+	 *
+	 * @see java.util.Iterator#remove()
+	 */
+	@Override
+	public void remove()
+	{
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/AbstractResourceStreamReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/AbstractResourceStreamReference.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/AbstractResourceStreamReference.java
new file mode 100644
index 0000000..5a3c86b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/AbstractResourceStreamReference.java
@@ -0,0 +1,46 @@
+/*
+ * 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.caching;
+
+import java.util.Locale;
+
+import org.apache.wicket.util.resource.IResourceStream;
+
+abstract class AbstractResourceStreamReference
+	implements
+		IResourceStreamReference
+{
+	private String style;
+
+	private Locale locale;
+
+	private String variation;
+
+	protected void saveResourceStream(final IResourceStream resourceStream)
+	{
+		style = resourceStream.getStyle();
+		locale = resourceStream.getLocale();
+		variation = resourceStream.getVariation();
+	}
+
+	protected void restoreResourceStream(final IResourceStream resourceStream)
+	{
+		resourceStream.setStyle(style);
+		resourceStream.setLocale(locale);
+		resourceStream.setVariation(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/locator/caching/CachingResourceStreamLocator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/CachingResourceStreamLocator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/CachingResourceStreamLocator.java
new file mode 100644
index 0000000..84c105a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/CachingResourceStreamLocator.java
@@ -0,0 +1,140 @@
+/*
+ * 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.caching;
+
+import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.wicket.core.util.resource.UrlResourceStream;
+import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
+import org.apache.wicket.core.util.resource.locator.ResourceNameIterator;
+import org.apache.wicket.request.resource.ResourceReference.Key;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.resource.FileResourceStream;
+import org.apache.wicket.util.resource.IResourceStream;
+
+
+/**
+ * Locating resources can take a significant amount of time, especially since there are often
+ * several CSS, JavaScript and image resources on any given page. To facilitate localization and
+ * styling, Wicket will usually make several attempts at locating each resource (i.e. first with an
+ * "en_US" suffix, then "en", and so on); multiply these attempts by the number of resources on the
+ * page and this starts to add up.
+ * <p>
+ * This locator mitigates this problem by caching (indefinitely) references to
+ * {@link UrlResourceStream} and {@link FileResourceStream} objects as they are found, and
+ * {@link NullResourceStreamReference} for all which are missing so they are not looked up again and
+ * again.
+ */
+public class CachingResourceStreamLocator implements IResourceStreamLocator
+{
+	private final ConcurrentMap<Key, IResourceStreamReference> cache;
+
+	private final IResourceStreamLocator delegate;
+
+	/**
+	 * Construct.
+	 *
+	 * @param resourceStreamLocator
+	 *            the delegate
+	 */
+	public CachingResourceStreamLocator(final IResourceStreamLocator resourceStreamLocator)
+	{
+		Args.notNull(resourceStreamLocator, "resourceStreamLocator");
+
+		delegate = resourceStreamLocator;
+
+		cache = new ConcurrentHashMap<Key, IResourceStreamReference>();
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * Checks for {@link IResourceStreamReference} in the cache and returns <code>null</code> if the
+	 * result is {@link NullResourceStreamReference#INSTANCE}, or {@link FileResourceStream} /
+	 * {@link UrlResourceStream} if there is an entry in the cache. Otherwise asks the delegate to
+	 * find one and puts it in the cache.
+	 */
+	@Override
+	public IResourceStream locate(Class<?> clazz, String path)
+	{
+		Key key = new Key(clazz.getName(), path, null, null, null);
+		IResourceStreamReference resourceStreamReference = cache.get(key);
+
+		final IResourceStream result;
+		if (resourceStreamReference == null)
+		{
+			result = delegate.locate(clazz, path);
+
+			updateCache(key, result);
+		}
+		else
+		{
+			result = resourceStreamReference.getReference();
+		}
+
+		return result;
+	}
+
+	private void updateCache(Key key, IResourceStream stream)
+	{
+		if (null == stream)
+		{
+			cache.put(key, NullResourceStreamReference.INSTANCE);
+		}
+		else if (stream instanceof FileResourceStream)
+		{
+			FileResourceStream fileResourceStream = (FileResourceStream)stream;
+			cache.put(key, new FileResourceStreamReference(fileResourceStream));
+		}
+		else if (stream instanceof UrlResourceStream)
+		{
+			UrlResourceStream urlResourceStream = (UrlResourceStream)stream;
+			cache.put(key, new UrlResourceStreamReference(urlResourceStream));
+		}
+	}
+
+	@Override
+	public IResourceStream locate(Class<?> scope, String path, String style, String variation,
+		Locale locale, String extension, boolean strict)
+	{
+		Key key = new Key(scope.getName(), path, locale, style, variation);
+		IResourceStreamReference resourceStreamReference = cache.get(key);
+
+		final IResourceStream result;
+		if (resourceStreamReference == null)
+		{
+			result = delegate.locate(scope, path, style, variation, locale, extension, strict);
+
+			updateCache(key, result);
+		}
+		else
+		{
+			result = resourceStreamReference.getReference();
+		}
+
+		return result;
+	}
+
+	@Override
+	public ResourceNameIterator newResourceNameIterator(String path, Locale locale, String style,
+		String variation, String extension, boolean strict)
+	{
+		return delegate.newResourceNameIterator(path, locale, style, variation, extension, strict);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/FileResourceStreamReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/FileResourceStreamReference.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/FileResourceStreamReference.java
new file mode 100644
index 0000000..2bbe92a
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/FileResourceStreamReference.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.locator.caching;
+
+import org.apache.wicket.util.file.File;
+import org.apache.wicket.util.resource.FileResourceStream;
+
+/**
+ * A reference which can be used to recreate {@link FileResourceStream}
+ */
+class FileResourceStreamReference extends AbstractResourceStreamReference
+{
+	private final String fileName;
+
+	FileResourceStreamReference(final FileResourceStream fileResourceStream)
+	{
+		fileName = fileResourceStream.getFile().getAbsolutePath();
+		saveResourceStream(fileResourceStream);
+	}
+
+	@Override
+	public FileResourceStream getReference()
+	{
+		FileResourceStream fileResourceStream = new FileResourceStream(new File(fileName));
+		restoreResourceStream(fileResourceStream);
+		return fileResourceStream;
+	}
+}
\ 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/locator/caching/IResourceStreamReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/IResourceStreamReference.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/IResourceStreamReference.java
new file mode 100644
index 0000000..42e2158
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/IResourceStreamReference.java
@@ -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.
+ */
+package org.apache.wicket.core.util.resource.locator.caching;
+
+import org.apache.wicket.util.resource.IResourceStream;
+
+/**
+ * Lightweight reference to the cached {@link IResourceStream}
+ */
+interface IResourceStreamReference
+{
+	IResourceStream getReference();
+}
\ 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/locator/caching/NullResourceStreamReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/NullResourceStreamReference.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/NullResourceStreamReference.java
new file mode 100644
index 0000000..91c25e0
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/NullResourceStreamReference.java
@@ -0,0 +1,35 @@
+/*
+ * 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.caching;
+
+import org.apache.wicket.util.resource.IResourceStream;
+
+/**
+ * A singleton reference that is used for resource streams which do not exists. I.e. if there is a
+ * key in the cache which value is NullResourceStreamReference.INSTANCE then there is no need to
+ * lookup again for this key anymore.
+ */
+class NullResourceStreamReference implements IResourceStreamReference
+{
+	final static NullResourceStreamReference INSTANCE = new NullResourceStreamReference();
+
+	@Override
+	public IResourceStream getReference()
+	{
+		return null;
+	}
+}
\ 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/locator/caching/UrlResourceStreamReference.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/UrlResourceStreamReference.java b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/UrlResourceStreamReference.java
new file mode 100644
index 0000000..f217827
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/caching/UrlResourceStreamReference.java
@@ -0,0 +1,54 @@
+/*
+ * 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.caching;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.core.util.resource.UrlResourceStream;
+
+/**
+ * A reference which may be used to recreate {@link UrlResourceStream}
+ */
+class UrlResourceStreamReference extends AbstractResourceStreamReference
+{
+	private final String url;
+
+	UrlResourceStreamReference(final UrlResourceStream urlResourceStream)
+	{
+		url = urlResourceStream.getURL().toExternalForm();
+		saveResourceStream(urlResourceStream);
+	}
+
+	@Override
+	public UrlResourceStream getReference()
+	{
+		try
+		{
+			UrlResourceStream resourceStream = new UrlResourceStream(new URL(url));
+			restoreResourceStream(resourceStream);
+			return resourceStream;
+		}
+		catch (MalformedURLException e)
+		{
+			// should not ever happen. The cached url is created by previously existing URL
+			// instance
+			throw new WicketRuntimeException(e);
+		}
+	}
+}
\ 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/locator/package.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/package.html b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/package.html
new file mode 100644
index 0000000..85790c5
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/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.resource package</title>
+</head>
+<body>
+<p>
+Locators for finding/ loading resources.
+</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/string/ComponentStrings.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentStrings.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentStrings.java
new file mode 100644
index 0000000..118368c
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentStrings.java
@@ -0,0 +1,117 @@
+/*
+ * 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.string;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.util.string.AppendingStringBuffer;
+
+/**
+ *
+ */
+public class ComponentStrings
+{
+	/**
+	 * Construct.
+	 */
+	private ComponentStrings()
+	{
+	}
+
+	/**
+	 * Creates a location stacktrace string representation for the component for reference when the
+	 * render check fails. This method filters out most of the unnecessary parts of the stack trace.
+	 * The message of the <code>location</code> is used as a verb in the rendered string. Use
+	 * "added", "constructed" or similar verbs as values.
+	 *
+	 * @param component
+	 *            the component that was constructed or added and failed to render
+	 * @param location
+	 *            the location where the component was created or added in the java code.
+	 * @return a string giving the line precise location where the component was added or created.
+	 */
+	public static String toString(final Component component, final Throwable location)
+	{
+		Class<?> componentClass = component.getClass();
+
+		// try to find the component type, if it is an inner element, then get
+		// the parent component.
+		String componentType = componentClass.getName();
+		if (componentType.indexOf('$') >= 0)
+		{
+			componentType = componentClass.getSuperclass().getName();
+		}
+
+		componentType = componentType.substring(componentType.lastIndexOf('.') + 1);
+
+		// create a user friendly message, using the location's message as a
+		// differentiator for the message (e.g. "component foo was ***added***"
+		// or "component foo was ***created***")
+		AppendingStringBuffer sb = new AppendingStringBuffer("The " + componentType.toLowerCase() +
+			" with id '" + component.getId() + "' that failed to render was " +
+			location.getMessage() + "\n");
+
+		// a list of stacktrace elements that need to be skipped in the location
+		// stack trace
+		String[] skippedElements = new String[] { "org.apache.wicket.MarkupContainer",
+				"org.apache.wicket.Component", "org.apache.wicket.markup" };
+
+		// a list of stack trace elements that stop the traversal of the stack
+		// trace
+		String[] breakingElements = new String[] { "org.apache.wicket.protocol.http.WicketServlet",
+				"org.apache.wicket.protocol.http.WicketFilter", "java.lang.reflect" };
+
+		StackTraceElement[] trace = location.getStackTrace();
+		for (int i = 0; i < trace.length; i++)
+		{
+			String traceString = trace[i].toString();
+			if (shouldSkip(traceString, skippedElements))
+			{
+				// don't print this line, is wicket internal
+				continue;
+			}
+
+			if (!(traceString.startsWith("sun.reflect.") && i > 1))
+			{
+				// filter out reflection API calls from the stack trace
+				if (!traceString.contains("java.lang.reflect"))
+				{
+					sb.append("     at ");
+					sb.append(traceString);
+					sb.append("\n");
+				}
+				if (shouldSkip(traceString, breakingElements))
+				{
+					break;
+				}
+			}
+		}
+		sb.append("\n");
+		return sb.toString();
+	}
+
+	private static boolean shouldSkip(String text, String[] filters)
+	{
+		for (String filter : filters)
+		{
+			if (text.contains(filter))
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/string/CssUtils.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/CssUtils.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/CssUtils.java
new file mode 100644
index 0000000..2fa1bb6
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/CssUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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.string;
+
+import org.apache.wicket.request.Response;
+
+/**
+ * Utility methods for CSS.
+ *
+ * @author eelcohillenius
+ */
+public final class CssUtils
+{
+	/** CSS inline open tag */
+	public final static String INLINE_OPEN_TAG = "<style type=\"text/css\"><!--\n";
+
+	/** CSS inline close tag */
+	public final static String INLINE_CLOSE_TAG = "--></style>\n";
+
+	/**
+	 * Hidden constructor.
+	 */
+	private CssUtils()
+	{
+	}
+
+	/**
+	 * Write the simple text to the response object surrounded by a style tag.
+	 *
+	 * @param response
+	 *            The HTTP: response
+	 * @param text
+	 *            The text to added in between the style tags
+	 * @param id
+	 *            Unique identifier of element
+	 */
+	public static void writeCss(final Response response, final CharSequence text, String id)
+	{
+		writeOpenTag(response, id);
+		response.write(text);
+		writeCloseTag(response);
+	}
+
+	/**
+	 *
+	 * @param response
+	 * @param id
+	 */
+	public static void writeOpenTag(final Response response, String id)
+	{
+		response.write("<style type=\"text/css\" ");
+		if (id != null)
+		{
+			response.write("id=\"" + id + "\"");
+		}
+		response.write("><!--\n");
+	}
+
+	/**
+	 *
+	 * @param response
+	 */
+	public static void writeCloseTag(final Response response)
+	{
+		response.write(INLINE_CLOSE_TAG);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptStripper.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptStripper.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptStripper.java
new file mode 100644
index 0000000..79c6542
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptStripper.java
@@ -0,0 +1,228 @@
+/*
+ * 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.string;
+
+
+import org.apache.wicket.util.string.AppendingStringBuffer;
+
+/**
+ * Strips comments and whitespace from javascript
+ *
+ * @author Matej Knopp
+ */
+public class JavaScriptStripper
+{
+	/*
+	 * Determines the state of script processing.
+	 */
+	/** Inside regular text */
+	private final static int REGULAR_TEXT = 1;
+
+	/** String started with single quote (') */
+	private final static int STRING_SINGLE_QUOTE = 2;
+
+	/** String started with double quotes (") */
+	private final static int STRING_DOUBLE_QUOTES = 3;
+
+	/** Inside two or more whitespace characters */
+	private final static int WHITE_SPACE = 4;
+
+	/** Inside a line comment (// ) */
+	private final static int LINE_COMMENT = 5;
+
+	/** Inside a multi line comment */
+	private final static int MULTILINE_COMMENT = 6;
+
+	/** Inside a regular expression */
+	private final static int REG_EXP = 7;
+
+	private int getPrevCount(String s, int fromIndex, char c)
+	{
+		int count = 0;
+		--fromIndex;
+		while (fromIndex >= 0)
+		{
+			if (s.charAt(fromIndex--) == c)
+			{
+				++count;
+			}
+			else
+			{
+				break;
+			}
+		}
+		return count;
+	}
+
+	/**
+	 * Removes javascript comments and whitespace from specified string.
+	 *
+	 * @param original
+	 *            Source string
+	 * @return String with removed comments and whitespace
+	 */
+	public String stripCommentsAndWhitespace(String original)
+	{
+		// let's be optimistic
+		AppendingStringBuffer result = new AppendingStringBuffer(original.length() / 2);
+		int state = REGULAR_TEXT;
+		boolean wasNewLineInWhitespace = false;
+
+		for (int i = 0; i < original.length(); ++i)
+		{
+			char c = original.charAt(i);
+			char next = (i < original.length() - 1) ? original.charAt(i + 1) : 0;
+			char prev = (i > 0) ? original.charAt(i - 1) : 0;
+
+			if (state == WHITE_SPACE)
+			{
+				// WICKET 2060
+				if (c == '\n' && !wasNewLineInWhitespace)
+				{
+					result.append("\n");
+					wasNewLineInWhitespace = true;
+				}
+				if (Character.isWhitespace(next) == false)
+				{
+					state = REGULAR_TEXT;
+				}
+				continue;
+			}
+
+			if (state == REGULAR_TEXT)
+			{
+				if (c == '/' && next == '/' && prev != '\\')
+				{
+					state = LINE_COMMENT;
+					continue;
+				}
+				else if (c == '/' && next == '*')
+				{
+					state = MULTILINE_COMMENT;
+					++i;
+					continue;
+				}
+				else if (c == '/')
+				{
+					// This might be a divide operator, or it might be a regular expression.
+					// Work out if it's a regular expression by finding the previous non-whitespace
+					// char, which
+					// will be either '=' or '('. If it's not, it's just a divide operator.
+					int idx = result.length() - 1;
+					while (idx > 0)
+					{
+						char tmp = result.charAt(idx);
+						if (Character.isWhitespace(tmp))
+						{
+							idx--;
+							continue;
+						}
+						if (tmp == '=' || tmp == '(' || tmp == '{' || tmp == ':' || tmp == ',' ||
+							tmp == '[' || tmp == ';' || tmp == '!')
+						{
+							state = REG_EXP;
+							break;
+						}
+						break;
+					}
+				}
+				else if (Character.isWhitespace(c) && Character.isWhitespace(next))
+				{
+					// WICKET-2060
+					if (c == '\n' || next == '\n')
+					{
+						c = '\n';
+						wasNewLineInWhitespace = true;
+					}
+					else
+					{
+						c = ' ';
+						wasNewLineInWhitespace = false;
+					}
+					// ignore all whitespace characters after this one
+					state = WHITE_SPACE;
+				}
+				else if (c == '\'')
+				{
+					state = STRING_SINGLE_QUOTE;
+				}
+				else if (c == '"')
+				{
+					state = STRING_DOUBLE_QUOTES;
+				}
+				result.append(c);
+				continue;
+			}
+
+			if (state == LINE_COMMENT)
+			{
+				if (c == '\n' || c == '\r')
+				{
+					state = REGULAR_TEXT;
+					continue;
+				}
+			}
+
+			if (state == MULTILINE_COMMENT)
+			{
+				if (c == '*' && next == '/')
+				{
+					state = REGULAR_TEXT;
+					++i;
+					continue;
+				}
+			}
+
+			if (state == STRING_SINGLE_QUOTE)
+			{
+				// to leave a string expression we need even (or zero) number of backslashes
+				int count = getPrevCount(original, i, '\\');
+				if (c == '\'' && count % 2 == 0)
+				{
+					state = REGULAR_TEXT;
+				}
+				result.append(c);
+				continue;
+			}
+
+			if (state == STRING_DOUBLE_QUOTES)
+			{
+				// to leave a string expression we need even (or zero) number of backslashes
+				int count = getPrevCount(original, i, '\\');
+				if (c == '"' && count % 2 == 0)
+				{
+					state = REGULAR_TEXT;
+				}
+				result.append(c);
+				continue;
+			}
+
+			if (state == REG_EXP)
+			{
+				// to leave regular expression we need even (or zero) number of backslashes
+				int count = getPrevCount(original, i, '\\');
+				if (c == '/' && count % 2 == 0)
+				{
+					state = REGULAR_TEXT;
+				}
+				result.append(c);
+			}
+		}
+
+		return result.toString();
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java
new file mode 100644
index 0000000..a6b3a56
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/JavaScriptUtils.java
@@ -0,0 +1,253 @@
+/*
+ * 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.string;
+
+import org.apache.wicket.request.Response;
+import org.apache.wicket.util.string.Strings;
+
+
+/**
+ * Provide some helpers to write javascript related tags to the response object.
+ *
+ * @author Juergen Donnerstag
+ */
+public class JavaScriptUtils
+{
+	/** Script open tag */
+	public final static String SCRIPT_OPEN_TAG = "<script type=\"text/javascript\">\n/*<![CDATA[*/\n";
+
+	/** Script close tag */
+	public final static String SCRIPT_CLOSE_TAG = "\n/*]]>*/\n</script>\n";
+
+	/**
+	 * Script open tag. If this tag is changed, also update Wicket.Head.Contributor.processScript()
+	 * function from wicket-ajax.js
+	 */
+	public final static String SCRIPT_CONTENT_PREFIX = "\n/*<![CDATA[*/\n";
+
+	/**
+	 * Script close tag. If this tag is changed, also update Wicket.Head.Contributor.processScript()
+	 * function from wicket-ajax.js
+	 */
+	public final static String SCRIPT_CONTENT_SUFFIX = "\n/*]]>*/\n";
+
+
+	/** The response object */
+	private final Response response;
+
+	/**
+	 * Construct.
+	 *
+	 * @param response
+	 *            The response object
+	 * @param id
+	 */
+	public JavaScriptUtils(final Response response, String id)
+	{
+		this.response = response;
+		writeOpenTag(response, id);
+	}
+
+	/**
+	 * Constructor without id for backward compatibility
+	 *
+	 * @param response
+	 *            The response object
+	 */
+	public JavaScriptUtils(final Response response)
+	{
+		this.response = response;
+		writeOpenTag(response);
+	}
+
+
+	/**
+	 * Escape single and double quotes so that they can be part of e.g. an alert call.
+	 *
+	 * @param input
+	 *            input
+	 * @return Escaped version of the input
+	 */
+	public static CharSequence escapeQuotes(final CharSequence input)
+	{
+		CharSequence s = input;
+		if (s != null)
+		{
+			s = Strings.replaceAll(s, "'", "\\'");
+			s = Strings.replaceAll(s, "\"", "\\\"");
+		}
+		return s;
+	}
+
+	/**
+	 * Write a reference to a javascript file to the response object
+	 *
+	 * @param response
+	 *            The HTTP response
+	 * @param url
+	 *            The javascript file URL
+	 * @param id
+	 *            Unique identifier of element
+	 */
+	public static void writeJavaScriptUrl(final Response response, final CharSequence url,
+		final String id)
+	{
+		writeJavaScriptUrl(response, url, id, false, null);
+	}
+
+	/**
+	 * Write a reference to a javascript file to the response object
+	 *
+	 * @param response
+	 *            The HTTP response
+	 * @param url
+	 *            The javascript file URL
+	 * @param id
+	 *            Unique identifier of element
+	 * @param defer
+	 *            specifies that the execution of a script should be deferred (delayed) until after
+	 *            the page has been loaded.
+	 * @param charset
+	 *            a non null value specifies the charset attribute of the script tag
+	 */
+	public static void writeJavaScriptUrl(final Response response, final CharSequence url,
+		final String id, boolean defer, String charset)
+	{
+		response.write("<script type=\"text/javascript\" ");
+		if (id != null)
+		{
+			response.write("id=\"" + id + "\" ");
+		}
+		if (defer)
+		{
+			response.write("defer=\"defer\" ");
+		}
+		if (charset != null)
+		{
+			response.write("charset=\"" + charset + "\" ");
+		}
+		response.write("src=\"");
+		response.write(url);
+		response.write("\"></script>");
+		response.write("\n");
+	}
+
+	/**
+	 * Write a reference to a javascript file to the response object
+	 *
+	 * @param response
+	 *            The HTTP response
+	 * @param url
+	 *            The javascript file URL
+	 */
+	public static void writeJavaScriptUrl(final Response response, final CharSequence url)
+	{
+		writeJavaScriptUrl(response, url, null);
+	}
+
+	/**
+	 * Write the simple text to the response object surrounded by a script tag.
+	 *
+	 * @param response
+	 *            The HTTP: response
+	 * @param text
+	 *            The text to added in between the script tags
+	 * @param id
+	 *            Unique identifier of element
+	 */
+	public static void writeJavaScript(final Response response, final CharSequence text, String id)
+	{
+		writeOpenTag(response, id);
+		response.write(text);
+		writeCloseTag(response);
+	}
+
+	/**
+	 * Write the simple text to the response object surrounded by a script tag.
+	 *
+	 * @param response
+	 *            The HTTP: response
+	 * @param text
+	 *            The text to added in between the script tags
+	 */
+	public static void writeJavaScript(final Response response, final CharSequence text)
+	{
+		writeJavaScript(response, text, null);
+	}
+
+	/**
+	 *
+	 * @param response
+	 * @param id
+	 */
+	public static void writeOpenTag(final Response response, String id)
+	{
+		response.write("<script type=\"text/javascript\" ");
+		if (id != null)
+		{
+			response.write("id=\"" + id + "\"");
+		}
+		response.write(">");
+		response.write(SCRIPT_CONTENT_PREFIX);
+	}
+
+	/**
+	 *
+	 * @param response
+	 */
+	public static void writeOpenTag(final Response response)
+	{
+		writeOpenTag(response, null);
+	}
+
+	/**
+	 *
+	 * @param response
+	 */
+	public static void writeCloseTag(final Response response)
+	{
+		response.write(SCRIPT_CONTENT_SUFFIX);
+		response.write("</script>\n");
+
+	}
+
+	/**
+	 * @see Response#write(java.lang.CharSequence)
+	 * @param script
+	 */
+	public void write(final CharSequence script)
+	{
+		response.write(script);
+	}
+
+	/**
+	 * @see Response#write(CharSequence)
+	 * @param script
+	 */
+	public void println(final CharSequence script)
+	{
+		response.write(script);
+	}
+
+	/**
+	 * Write the script close tag to the response. The response output stream remains open.
+	 */
+	public void close()
+	{
+		writeCloseTag(response);
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/core/util/string/UrlUtils.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/UrlUtils.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/UrlUtils.java
new file mode 100644
index 0000000..3e7b1ae
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/UrlUtils.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.string;
+
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Various url utilities
+ *
+ * @author igor.vaynberg
+ */
+public class UrlUtils
+{
+	/**
+	 * Constructor
+	 */
+	private UrlUtils()
+	{
+
+	}
+
+	/**
+	 * Checks if the url is relative or absolute
+	 *
+	 * @param url
+	 * @return <code>true</code> if url is relative, <code>false</code> otherwise
+	 */
+	public static boolean isRelative(final String url)
+	{
+		// the regex means "doesn't start with 'scheme://'"
+		if ((url != null) && (url.startsWith("/") == false) && (!url.matches("^\\w+\\:\\/\\/.*")) &&
+			!(url.startsWith("#")))
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Rewrites a relative url to be context relative, leaves absolute urls same.
+	 *
+	 * @param url
+	 * @param requestCycle
+	 * @return rewritten url
+	 */
+	public static String rewriteToContextRelative(String url, RequestCycle requestCycle)
+	{
+		if (isRelative(url))
+		{
+			return requestCycle.getUrlRenderer().renderContextRelativeUrl(url);
+		}
+		else
+		{
+			return url;
+		}
+	}
+
+	/**
+	 * Makes sure the path starts with a slash and does not end with a slash. Empty or null paths
+	 * are normalized into an empty string.
+	 *
+	 * @param path
+	 *            path to normalize
+	 * @return normalized path
+	 */
+	public static String normalizePath(String path)
+	{
+		if (Strings.isEmpty(path))
+		{
+			return "";
+		}
+		path = path.trim();
+		if (!path.startsWith("/"))
+		{
+			path = "/" + path;
+		}
+		if (path.endsWith("/"))
+		{
+			path = path.substring(0, path.length() - 1);
+		}
+		return path;
+	}
+}
\ 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/string/interpolator/PropertyVariableInterpolator.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/interpolator/PropertyVariableInterpolator.java b/wicket-core/src/main/java/org/apache/wicket/core/util/string/interpolator/PropertyVariableInterpolator.java
new file mode 100644
index 0000000..0b74d77
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/interpolator/PropertyVariableInterpolator.java
@@ -0,0 +1,129 @@
+/*
+ * 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.string.interpolator;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.IConverterLocator;
+import org.apache.wicket.Session;
+import org.apache.wicket.core.util.lang.PropertyResolver;
+import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.string.interpolator.VariableInterpolator;
+
+/**
+ * Interpolates values into <code>String</code>s that are produced by interpreting property
+ * expressions against a beans model.
+ * <p>
+ * The <code>interpolate(String string, Object model)</code> method takes a string such as "
+ * <code>My name is ${name}</code>" and a beans model such as a <code>Person</code>, and reflects on
+ * the object using any property expressions found inside <code>${}</code> markers in the
+ * <code>String</code>. In this case, if the <code>Person</code> model has a <code>getName()</code>
+ * method. The results of calling that method would be substituted for <code>${name}</code>. If
+ * <code>getName()</code> returned <code>"Jonathan"</code>, then <code>interpolate()</code> would
+ * return <code>"My name is Jonathan"</code>.
+ * <p>
+ * "$" is the escape char. Thus "$${text}" can be used to escape it (ignore interpretation). If
+ * '$3.24' is needed then '$$${amount}' should be used. The first $ sign escapes the second, and the
+ * third is used to interpolate the variable.
+ *
+ * @author Jonathan Locke
+ * @since 1.2.6
+ */
+public final class PropertyVariableInterpolator extends VariableInterpolator
+	implements
+		IConverterLocator
+{
+	private static final long serialVersionUID = 1L;
+
+	/** The model to introspect on */
+	private final Object model;
+
+	/**
+	 * Private constructor to force use of static interpolate method.
+	 *
+	 * @param string
+	 *            a <code>String</code> to interpolate into
+	 * @param model
+	 *            the model to apply property expressions to
+	 */
+	private PropertyVariableInterpolator(final String string, final Object model)
+	{
+		super(string);
+		this.model = model;
+	}
+
+	/**
+	 * Interpolates the given <code>String</code>, substituting values for property expressions.
+	 *
+	 * @param string
+	 *            a <code>String</code> containing property expressions like <code>${xyz}</code>
+	 * @param object
+	 *            the <code>Object</code> to reflect on
+	 * @return the interpolated <code>String</code>
+	 */
+	public static String interpolate(final String string, final Object object)
+	{
+		// If there's any reason to go to the expense of property expressions
+		if (string.contains("${"))
+		{
+			// Do property expression interpolation
+			return new PropertyVariableInterpolator(string, object).toString();
+		}
+
+		// Return simple string
+		return string;
+	}
+
+	/**
+	 * Retrieves a value for a variable name during interpolation.
+	 *
+	 * @param variableName
+	 *            the variable name
+	 * @return the value
+	 */
+	@Override
+	protected String getValue(final String variableName)
+	{
+		Object value = PropertyResolver.getValue(variableName, model);
+
+		if (value != null)
+		{
+			final IConverter converter = getConverter(value.getClass());
+			if (converter != null)
+			{
+				return converter.convertToString(value, Session.get().getLocale());
+			}
+			else
+			{
+				return value.toString();
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public <C> IConverter<C> getConverter(Class<C> type)
+	{
+		if (Application.exists())
+		{
+			return Application.get().getConverterLocator().getConverter(type);
+		}
+		return null;
+	}
+}
\ 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/string/package.html
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/string/package.html b/wicket-core/src/main/java/org/apache/wicket/core/util/string/package.html
new file mode 100644
index 0000000..60a3f81
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/string/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.string package</title>
+</head>
+<body>
+<p>
+String 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/feedback/FeedbackMessages.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackMessages.java b/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackMessages.java
index bb593f7..fcbcf9c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackMessages.java
+++ b/wicket-core/src/main/java/org/apache/wicket/feedback/FeedbackMessages.java
@@ -24,7 +24,7 @@ import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.wicket.Component;
-import org.apache.wicket.IClusterable;
+import org.apache.wicket.util.io.IClusterable;
 import org.apache.wicket.util.string.StringList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -239,8 +239,6 @@ public final class FeedbackMessages implements IClusterable, Iterable<FeedbackMe
 	/**
 	 * Retrieves the first message
 	 * 
-	 * @param level
-	 *            The level of the message
 	 * @return message or {@code null} if none
 	 */
 	public final FeedbackMessage first()

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedbackMessageFilter.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedbackMessageFilter.java b/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedbackMessageFilter.java
index 9372511..83706e5 100644
--- a/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedbackMessageFilter.java
+++ b/wicket-core/src/main/java/org/apache/wicket/feedback/IFeedbackMessageFilter.java
@@ -16,7 +16,7 @@
  */
 package org.apache.wicket.feedback;
 
-import org.apache.wicket.IClusterable;
+import org.apache.wicket.util.io.IClusterable;
 
 /**
  * Interface for filtering feedback messages.

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/javascript/DefaultJavaScriptCompressor.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/javascript/DefaultJavaScriptCompressor.java b/wicket-core/src/main/java/org/apache/wicket/javascript/DefaultJavaScriptCompressor.java
index 5a5d5f4..dd62f65 100644
--- a/wicket-core/src/main/java/org/apache/wicket/javascript/DefaultJavaScriptCompressor.java
+++ b/wicket-core/src/main/java/org/apache/wicket/javascript/DefaultJavaScriptCompressor.java
@@ -16,7 +16,7 @@
  */
 package org.apache.wicket.javascript;
 
-import org.apache.wicket.util.string.JavaScriptStripper;
+import org.apache.wicket.core.util.string.JavaScriptStripper;
 
 /**
  * Wicket default implementation of a javascript compressor

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/DefaultMarkupResourceStreamProvider.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/DefaultMarkupResourceStreamProvider.java b/wicket-core/src/main/java/org/apache/wicket/markup/DefaultMarkupResourceStreamProvider.java
index fcb4e24..2f8d2a7 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/DefaultMarkupResourceStreamProvider.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/DefaultMarkupResourceStreamProvider.java
@@ -21,7 +21,7 @@ import java.util.Locale;
 import org.apache.wicket.Application;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.util.resource.IResourceStream;
-import org.apache.wicket.util.resource.locator.IResourceStreamLocator;
+import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,7 +54,7 @@ public class DefaultMarkupResourceStreamProvider implements IMarkupResourceStrea
 	 * Note: IResourceStreamLocators should be used in case the strategy to find a markup resource
 	 * should be extended for ALL components of your application.
 	 * 
-	 * @see org.apache.wicket.util.resource.locator.IResourceStreamLocator
+	 * @see org.apache.wicket.core.util.resource.locator.IResourceStreamLocator
 	 * @see org.apache.wicket.markup.DefaultMarkupResourceStreamProvider
 	 * 
 	 * @param container

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/MarkupResourceStream.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/MarkupResourceStream.java b/wicket-core/src/main/java/org/apache/wicket/markup/MarkupResourceStream.java
index c14fb28..6e11b8b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/MarkupResourceStream.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/MarkupResourceStream.java
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
 import org.apache.wicket.Component;
 import org.apache.wicket.util.lang.Args;
 import org.apache.wicket.util.lang.Bytes;
-import org.apache.wicket.util.lang.WicketObjects;
+import org.apache.wicket.core.util.lang.WicketObjects;
 import org.apache.wicket.util.resource.IFixedLocationResourceStream;
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.ResourceStreamNotFoundException;

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/CssContentHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssContentHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssContentHeaderItem.java
index e1886e2..ade12b2 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssContentHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssContentHeaderItem.java
@@ -16,13 +16,13 @@
  */
 package org.apache.wicket.markup.head;
 
-import org.apache.wicket.request.Response;
-import org.apache.wicket.util.string.CssUtils;
-import org.apache.wicket.util.string.Strings;
-
 import java.util.Arrays;
 import java.util.Collections;
 
+import org.apache.wicket.request.Response;
+import org.apache.wicket.core.util.string.CssUtils;
+import org.apache.wicket.util.string.Strings;
+
 /**
  * {@link HeaderItem} for internal (embedded in the header) css content.
  * 

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/CssUrlReferenceHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssUrlReferenceHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssUrlReferenceHeaderItem.java
index a107c4a..08c809c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/CssUrlReferenceHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/CssUrlReferenceHeaderItem.java
@@ -20,7 +20,7 @@ import java.util.Arrays;
 
 import org.apache.wicket.request.Response;
 import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.util.string.UrlUtils;
+import org.apache.wicket.core.util.string.UrlUtils;
 
 /**
  * {@link HeaderItem} for style tags that are rendered using a fixed URL, for example resources from

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
index 091b425..a2797ac 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptContentHeaderItem.java
@@ -20,7 +20,7 @@ import java.util.Arrays;
 import java.util.Collections;
 
 import org.apache.wicket.request.Response;
-import org.apache.wicket.util.string.JavaScriptUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 import org.apache.wicket.util.string.Strings;
 
 /**

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptHeaderItem.java
index faf4d9a..8c7c48b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptHeaderItem.java
@@ -20,7 +20,7 @@ import org.apache.wicket.request.Response;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.string.JavaScriptUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 import org.apache.wicket.util.string.Strings;
 
 /**

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptUrlReferenceHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptUrlReferenceHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptUrlReferenceHeaderItem.java
index ec00d33..6d6f328 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptUrlReferenceHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/JavaScriptUrlReferenceHeaderItem.java
@@ -22,7 +22,7 @@ import java.util.Collections;
 import org.apache.wicket.request.Response;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.util.string.Strings;
-import org.apache.wicket.util.string.UrlUtils;
+import org.apache.wicket.core.util.string.UrlUtils;
 
 /**
  * {@link HeaderItem} for script tags that are rendered using a fixed URL, for example resources

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/OnDomReadyHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnDomReadyHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnDomReadyHeaderItem.java
index ae4b2bc..fd69af3 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnDomReadyHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnDomReadyHeaderItem.java
@@ -23,7 +23,7 @@ import org.apache.wicket.request.Response;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.settings.IJavaScriptLibrarySettings;
 import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.string.JavaScriptUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 
 /**
  * {@link HeaderItem} for scripts that need to be executed directly after the DOM has been built,

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/OnEventHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnEventHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnEventHeaderItem.java
index 3d226ee..2852249 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnEventHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnEventHeaderItem.java
@@ -23,7 +23,7 @@ import org.apache.wicket.request.Response;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.settings.IJavaScriptLibrarySettings;
 import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.string.JavaScriptUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 
 /**
  * {@link HeaderItem} for event triggered scripts.

http://git-wip-us.apache.org/repos/asf/wicket/blob/ddc37320/wicket-core/src/main/java/org/apache/wicket/markup/head/OnLoadHeaderItem.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnLoadHeaderItem.java b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnLoadHeaderItem.java
index 4e17745..9f58485 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/head/OnLoadHeaderItem.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/head/OnLoadHeaderItem.java
@@ -23,7 +23,7 @@ import org.apache.wicket.request.Response;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.apache.wicket.settings.IJavaScriptLibrarySettings;
 import org.apache.wicket.util.lang.Args;
-import org.apache.wicket.util.string.JavaScriptUtils;
+import org.apache.wicket.core.util.string.JavaScriptUtils;
 
 /**
  * {@link HeaderItem} for scripts that need to be executed after the entire page is loaded.