You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by kn...@apache.org on 2009/03/28 18:53:08 UTC

svn commit: r759525 [3/7] - in /wicket/sandbox/knopp/experimental/wicket-ng/src: main/java/org/ main/java/org/apache/ main/java/org/apache/wicket/ main/java/org/apache/wicket/behavior/ main/java/org/apache/wicket/markup/ main/java/org/apache/wicket/mar...

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/Url.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/Url.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/Url.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/Url.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,467 @@
+/*
+ * 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.request;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.protocol.http.WicketURLDecoder;
+import org.apache.wicket.protocol.http.WicketURLEncoder;
+import org.apache.wicket.util.lang.Objects;
+import org.apache.wicket.util.string.StringValue;
+import org.apache.wicket.util.string.Strings;
+
+ /**
+ * Represents the URL part after Wicket Filter.
+ * <p>
+ * URL consists of segments and query parameters.
+ * <p>
+ * Example URLs:
+ * 
+ * <pre>
+ * foo/bar/baz?a=1&amp;b=5    - segments: [&quot;foo&quot;,&quot;bar,&quot;baz], query parameters: [&quot;a&quot;=&quot;1&quot;, &quot;b&quot;=&quot;5&quot;]
+ * foo/bar//baz?=4&amp;6      - segments: [&quot;foo&quot;, &quot;bar&quot;, &quot;&quot;, &quot;baz&quot;], query parameters: [&quot;&quot;=&quot;4&quot;, &quot;6&quot;=&quot;&quot;]
+ * /foo/bar/              - segments: [&quot;&quot;, &quot;foo&quot;, &quot;bar&quot;, &quot;&quot;]
+ * foo/bar//              - segments: [&quot;foo&quot;, &quot;bar&quot;, &quot;&quot;, &quot;&quot;]
+ * ?a=b                   - segments: [ ], query parameters: ["a"="b"]
+ * /                      - segments: ["", ""]   (note that Url represents part after Wicket Filter 
+ *                                                - so if Wicket filter is mapped to /* this would be
+ *                                                an additional slash, i.e. //
+ * </pre>
+ * 
+ * The Url class takes care of encoding and decoding of the segments and parameters.
+ * 
+ * @author Matej Knopp
+ */
+public final class Url implements Serializable
+{
+	private static final long serialVersionUID = 1L;
+
+	private final List<String> segments = new ArrayList<String>();
+
+	private List<QueryParameter> parameters = new ArrayList<QueryParameter>();
+
+	/**
+	 * Construct.
+	 */
+	public Url()
+	{
+	}
+
+	/**
+	 * Construct.
+	 * 
+	 * @param url
+	 *            url being copied
+	 */
+	public Url(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may not be null.");
+		}
+		segments.addAll(url.getSegments());
+		parameters.addAll(url.getQueryParameters());
+	}
+
+	/**
+	 * Construct.
+	 * 
+	 * @param segments
+	 * @param parameters
+	 */
+	public Url(List<String> segments, List<QueryParameter> parameters)
+	{
+		if (segments == null)
+		{
+			throw new IllegalArgumentException("Argument 'segments' may not be null.");
+		}
+		if (parameters == null)
+		{
+			throw new IllegalArgumentException("Argument 'parameters' may not be null.");
+		}
+		this.segments.addAll(segments);
+		this.parameters.addAll(parameters);
+	}
+	
+	/**
+	 * Returns segments of the URL. Segments form the part before query string.
+	 * 
+	 * @return mutable list of segments
+	 */
+	public List<String> getSegments()
+	{
+		return segments;
+	}
+
+	/**
+	 * Returns query parameters of the URL.
+	 * 
+	 * @return mutable list of query parameters
+	 */
+	public List<QueryParameter> getQueryParameters()
+	{
+		return parameters;
+	}
+
+	/**
+	 * Returns whether the URL is absolute.
+	 * 
+	 * @return <code>true</code> if URL is absolute, <code>false</code> otherwise.
+	 */
+	public boolean isAbsolute()
+	{
+		return !getSegments().isEmpty() && Strings.isEmpty(getSegments().get(0));
+	}
+	
+	/**
+	 * Convenience method that removes all query parameters with given name.
+	 * 
+	 * @param name
+	 *            query parameter name
+	 */
+	public void removeQueryParameters(String name)
+	{
+		for (Iterator<QueryParameter> i = getQueryParameters().iterator(); i.hasNext();)
+		{
+			QueryParameter param = i.next();
+			if (Objects.equal(name, param.getName()))
+			{
+				i.remove();
+			}
+		}
+	}
+
+	/**
+	 * Convenience method that removes all query parameters with given name and adds new query
+	 * parameter with specified name and value
+	 * 
+	 * @param name
+	 * @param value
+	 */
+	public void setQueryParameter(String name, Object value)
+	{
+		removeQueryParameters(name);
+		if (value != null)
+		{
+			QueryParameter parameter = new QueryParameter(name, value.toString());
+			getQueryParameters().add(parameter);
+		}
+	}
+
+	/**
+	 * Returns first query parameter with specified name or null if such query parameter doesn't
+	 * exist.
+	 * 
+	 * @param name
+	 * @return query parameter or <code>null</code>
+	 */
+	public QueryParameter getQueryParameter(String name)
+	{
+		for (QueryParameter parameter : parameters)
+		{
+			if (Objects.equal(name, parameter.getName()))
+			{
+				return parameter;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the value of first query parameter with specified name. Note that this method never
+	 * returns <code>null</code>. Not even if the parameter does not exist.
+	 * 
+	 * @see StringValue#isNull()
+	 * 
+	 * @param name
+	 * @return {@link StringValue} instance wrapping the parameter value
+	 */
+	public StringValue getQueryParameterValue(String name)
+	{
+		QueryParameter parameter = getQueryParameter(name);
+		if (parameter == null)
+		{
+			return StringValue.valueOf((String)null);
+		}
+		else
+		{
+			return StringValue.valueOf(parameter.getValue());
+		}
+	}
+
+	/**
+	 * Represents a single query parameter
+	 * 
+	 * @author Matej Knopp
+	 */
+	public final static class QueryParameter
+	{
+		private final String name;
+		private final String value;
+
+		/**
+		 * Creates new {@link QueryParameter} instance. The <code>name</code> and
+		 * <code>value</code> parameters must not be <code>null</code>, though they can be
+		 * empty strings.
+		 * 
+		 * @param name
+		 *            parameter name
+		 * @param value
+		 *            parameter value
+		 */
+		public QueryParameter(String name, String value)
+		{
+			if (name == null)
+			{
+				throw new IllegalArgumentException("Argument 'name' can not be null.");
+			}
+			if (value == null)
+			{
+				throw new IllegalArgumentException("Argument 'value' can not be null.");
+			}
+			this.name = name;
+			this.value = value;
+		}
+
+		/**
+		 * Returns query parameter name.
+		 * 
+		 * @return query parameter name
+		 */
+		public String getName()
+		{
+			return name;
+		}
+
+		/**
+		 * Returns query parameter value.
+		 * 
+		 * @return query parameter value
+		 */
+		public String getValue()
+		{
+			return value;
+		}
+
+		@Override
+		public boolean equals(Object obj)
+		{
+			if (this == obj)
+			{
+				return true;
+			}
+			if (obj instanceof QueryParameter == false)
+			{
+				return false;
+			}
+			QueryParameter rhs = (QueryParameter)obj;
+			return Objects.equal(this.getName(), rhs.getName()) &&
+				Objects.equal(this.getValue(), rhs.getValue());
+		}
+
+		@Override
+		public int hashCode()
+		{
+			return Objects.hashCode(getName(), getValue());
+		}
+
+		@Override
+		public String toString()
+		{
+			StringBuilder result = new StringBuilder();
+			result.append(encodeParameter(getName()));
+			if (!Strings.isEmpty(getValue()))
+			{
+				result.append('=');
+				result.append(encodeParameter(getValue()));
+			}
+			return result.toString();
+		}
+	}
+
+	@Override
+	public boolean equals(Object obj)
+	{
+		if (this == obj)
+		{
+			return true;
+		}
+		if (obj instanceof Url == false)
+		{
+			return false;
+		}
+		Url rhs = (Url)obj;
+
+		return getSegments().equals(rhs.getSegments()) &&
+			getQueryParameters().equals(rhs.getQueryParameters());
+	}
+
+	@Override
+	public int hashCode()
+	{
+		return Objects.hashCode(getSegments(), getQueryParameters());
+	}
+
+	private static String encodeSegment(String string)
+	{
+		return WicketURLEncoder.PATH_INSTANCE.encode(string);
+	}
+	
+	private static String decodeSegment(String string)
+	{
+		return WicketURLDecoder.PATH_INSTANCE.decode(string);
+	}		
+
+	private static String encodeParameter(String string)
+	{
+		return WicketURLEncoder.QUERY_INSTANCE.encode(string);
+	}
+	
+	private static String decodeParameter(String string)
+	{
+		return WicketURLDecoder.QUERY_INSTANCE.decode(string);
+	}
+
+	@Override
+	public String toString()
+	{
+		StringBuilder result = new StringBuilder();
+		boolean first = true;
+		for (String s : getSegments())
+		{
+			if (!first)
+			{
+				result.append('/');
+			}
+			first = false;
+			result.append(encodeSegment(s));
+		}
+
+		first = true;
+
+		for (QueryParameter p : getQueryParameters())
+		{
+			if (first)
+			{
+				result.append("?");
+				first = false;
+			}
+			else
+			{
+				result.append("&");
+			}
+			result.append(p.toString());
+		}
+
+		return result.toString();
+	}
+
+	private static QueryParameter parseQueryParameter(String qp)
+	{
+		if (qp.indexOf('=') == -1)
+		{
+			return new QueryParameter(decodeParameter(qp), "");
+		}
+		String parts[] = qp.split("=");
+		if (parts.length == 0)
+		{
+			return new QueryParameter("", "");
+		}
+		else if (parts.length == 1)
+		{
+			return new QueryParameter("", decodeParameter(parts[0]));
+		}
+		else
+		{
+			return new QueryParameter(decodeParameter(parts[0]), decodeParameter(parts[1]));
+		}
+	}
+
+	/**
+	 * Parses the given URL string.
+	 * 
+	 * @param url
+	 * @return Url object
+	 */
+	public static Url parse(String url)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may not be null.");
+		}
+
+		Url result = new Url();
+
+		String segments;
+		String query;
+
+		int qIndex = url.indexOf('?');
+
+		if (qIndex == -1)
+		{
+			segments = url;
+			query = "";
+		}
+		else
+		{
+			segments = url.substring(0, qIndex);
+			query = url.substring(qIndex + 1);
+		}
+
+		if (segments.length() > 0)
+		{
+		
+			boolean removeLast = false;
+			if (segments.endsWith("/"))
+			{
+				// we need to append something and remove it after splitting because otherwise the
+				// trailing slashes will be lost
+				segments += "/x";
+				removeLast = true;
+			}
+	
+			String segmentArray[] = segments.split("/");
+	
+			if (removeLast)
+			{
+				segmentArray[segmentArray.length - 1] = null;
+			}
+	
+			for (String s : segmentArray)
+			{
+				if (s != null)
+				{
+					result.segments.add(decodeSegment(s));
+				}
+			}
+		}
+		
+		if (query.length() > 0)
+		{
+			String queryArray[] = query.split("&");
+			for (String s : queryArray)
+			{
+				result.parameters.add(parseQueryParameter(s));
+			}
+		}
+
+		return result;
+	};
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/Url.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRenderer.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRenderer.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRenderer.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRenderer.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,131 @@
+/*
+ * 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.request;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Takes care of rendering relative (or in future possibly absolute - depending on configuration)
+ * URLs.
+ * <p>
+ * All Urls are rendered relative to the base Url. Base Url is normally Url of the page being
+ * rendered. However, during Ajax request and redirect to buffer rendering the BaseUrl need to be
+ * adjusted.
+ * 
+ * @author Matej Knopp
+ */
+public class UrlRenderer
+{
+	/**
+	 * Construct.
+	 * 
+	 * @param base
+	 *            base Url. All generated Urls will be relative to this Url.
+	 */
+	public UrlRenderer(Url base)
+	{
+		if (base == null)
+		{
+			throw new IllegalArgumentException("Argument 'base' may not be null.");
+		}
+		this.baseUrl = base;
+	}
+
+	/**
+	 * Sets the base Url. All generated URLs will be relative to this Url.
+	 * 
+	 * @param base
+	 * @return original base Url
+	 */
+	public Url setBaseUrl(Url base)
+	{
+		if (base == null)
+		{
+			throw new IllegalArgumentException("Argument 'base' may not be null.");
+		}
+		Url original = this.baseUrl;
+		this.baseUrl = base;
+		return original;
+	}
+
+	/**
+	 * Returns the base Url.
+	 * 
+	 * @return base Url
+	 */
+	public Url getBaseUrl()
+	{
+		return baseUrl;
+	}
+
+	private Url baseUrl;
+
+	/**
+	 * Renders the Url relative to currently set Base Url.
+	 * 
+	 * @param url
+	 * @return Url rendered as string
+	 */
+	public String renderUrl(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may not be null.");
+		}
+
+		if (url.isAbsolute())
+		{
+			return url.toString();
+		}
+		else
+		{
+			List<String> baseUrlSegments = getBaseUrl().getSegments();
+			List<String> urlSegments = new ArrayList<String>(url.getSegments());
+
+			List<String> newSegments = new ArrayList<String>();
+			
+			int common = 0;
+			
+			// Remove the last segment. We need at least one segment in the result URL.
+			// This prevents the last segment to be marked as common. Note that the last
+			// segment can also be "" (which means that the URL ends with slash). 
+			List<String> baseUrlSegmentsCopy = new ArrayList<String>(baseUrlSegments);
+			if (!baseUrlSegments.isEmpty())
+			{
+				baseUrlSegmentsCopy.remove(baseUrlSegmentsCopy.size() - 1);	
+			}			
+			
+			for (String s : baseUrlSegmentsCopy)
+			{
+				if (!urlSegments.isEmpty() && s.equals(urlSegments.get(0)))
+				{
+					++common;
+					urlSegments.remove(0);
+				}
+			}
+			
+			for (int i = common + 1; i < baseUrlSegments.size(); ++i)
+			{
+				newSegments.add("..");
+			}
+			newSegments.addAll(urlSegments);
+
+			return new Url(newSegments, url.getQueryParameters()).toString();
+		}
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRenderer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRequestParameters.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRequestParameters.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRequestParameters.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRequestParameters.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,82 @@
+/*
+ * 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.request;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.request.Url.QueryParameter;
+import org.apache.wicket.util.lang.Objects;
+import org.apache.wicket.util.string.StringValue;
+
+/**
+ * Utility class that expresses query parameters from {@link Url} as {@link RequestParameters}.
+ * 
+ * @author Matej Knopp
+ */
+public class UrlRequestParameters implements RequestParameters
+{
+	private final Url url;
+
+	/**
+	 * Construct.
+	 * @param url
+	 */
+	public UrlRequestParameters(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may not be null.");
+		}
+		this.url = url;
+	}
+	
+	public Set<String> getParameterNames()
+	{
+		Set<String> result = new HashSet<String>();
+		for (QueryParameter parameter : url.getQueryParameters())
+		{
+			result.add(parameter.getName());
+		}
+		return Collections.unmodifiableSet(result);
+	}
+
+	public StringValue getParameterValue(String name)
+	{
+		return url.getQueryParameterValue(name);
+	}
+
+	public List<StringValue> getParameterValues(String name)
+	{
+		List<StringValue> values = null;
+		for (QueryParameter parameter : url.getQueryParameters())
+		{
+			if (Objects.equal(name, parameter.getName()))
+			{
+				if (values == null)
+				{
+					values = new ArrayList<StringValue>();
+				}
+				values.add(StringValue.valueOf(parameter.getValue()));
+			}
+		}
+		return values != null ? Collections.unmodifiableList(values) : null;
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/UrlRequestParameters.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractBookmarkableEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractBookmarkableEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractBookmarkableEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractBookmarkableEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,284 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IComponent;
+import org.apache.wicket.IPage;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.request.RequestHandler;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.encoder.info.ComponentInfo;
+import org.apache.wicket.request.encoder.info.PageComponentInfo;
+import org.apache.wicket.request.encoder.info.PageInfo;
+import org.apache.wicket.request.handler.impl.BookmarkableListenerInterfaceRequestHandler;
+import org.apache.wicket.request.handler.impl.BookmarkablePageRequestHandler;
+import org.apache.wicket.request.handler.impl.ListenerInterfaceRequestHandler;
+import org.apache.wicket.request.handler.impl.RenderPageRequestHandler;
+import org.apache.wicket.request.request.Request;
+
+/**
+ * Abstract encoder for Bookmarkable, Hybrid and BookmarkableListenerInterface URLs.
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractBookmarkableEncoder extends AbstractEncoder
+{
+	/**
+	 * Construct.
+	 */
+	public AbstractBookmarkableEncoder()
+	{
+	}
+
+	private RequestHandler processBookmarkable(Class<? extends IPage> pageClass,
+		PageParameters pageParameters)
+	{
+		IPage page = newPageInstance(pageClass, pageParameters);
+		return new RenderPageRequestHandler(page);
+	}
+
+	private RequestHandler processHybrid(PageInfo pageInfo, Class<? extends IPage> pageClass,
+		PageParameters pageParameters, Integer renderCount)
+	{
+		IPage page = getPageInstance(pageInfo, pageClass, pageParameters, renderCount);
+		return new RenderPageRequestHandler(page);
+	}
+
+	private RequestHandler processListener(PageComponentInfo pageComponentInfo,
+		Class<? extends IPage> pageClass, PageParameters pageParameters)
+	{
+		PageInfo pageInfo = pageComponentInfo.getPageInfo();
+		ComponentInfo componentInfo = pageComponentInfo.getComponentInfo();
+		Integer renderCount = componentInfo != null ? componentInfo.getRenderCount() : null;
+		
+		IPage page = getPageInstance(pageInfo, pageClass, pageParameters, true, renderCount);
+		IComponent component = getComponent(page, componentInfo.getComponentPath());
+		RequestListenerInterface listenerInterface = requestListenerInterfaceFromString(componentInfo.getListenerInterface());
+
+		return new ListenerInterfaceRequestHandler(page, component, listenerInterface,
+			componentInfo.getBehaviorIndex());
+	}
+
+	/**
+	 * Represents information stored in URL.
+	 * 
+	 * @author Matej
+	 */
+	protected static final class UrlInfo
+	{
+		private final PageComponentInfo pageComponentInfo;
+		private final PageParameters pageParameters;
+		private final Class<? extends IPage> pageClass;
+
+		/**
+		 * Construct.
+		 * 
+		 * @param pageComponentInfo
+		 *            optional parameter providing the page instance and component information
+		 * @param pageClass
+		 *            mandatory parameter
+		 * @param pageParameters
+		 *            optional parameter providing pageParameters
+		 */
+		public UrlInfo(PageComponentInfo pageComponentInfo, Class<? extends IPage> pageClass,
+			PageParameters pageParameters)
+		{
+			if (pageClass == null)
+			{
+				throw new IllegalArgumentException("Argument 'pageClass' may not be null.");
+			}
+			this.pageComponentInfo = pageComponentInfo;
+			this.pageParameters = pageParameters != null ? pageParameters : null;
+			this.pageClass = pageClass;
+		}
+
+		/**
+		 * @return PageComponentInfo instance or <code>null</code>
+		 */
+		public PageComponentInfo getPageComponentInfo()
+		{
+			return pageComponentInfo;
+		}
+
+		/**
+		 * @return page class
+		 */
+		public Class<? extends IPage> getPageClass()
+		{
+			return pageClass;
+		}
+
+		/**
+		 * @return PageParameters instance (never <code>null</code>)
+		 */
+		public PageParameters getPageParameters()
+		{
+			return pageParameters;
+		}
+	}
+
+	/**
+	 * Parse the given request to an {@link UrlInfo} instance.
+	 * 
+	 * @param request
+	 * @return UrlInfo instance or <code>null</code> if this encoder can not handle the request
+	 */
+	protected abstract UrlInfo parseRequest(Request request);
+
+	/**
+	 * Builds URL for the given {@link UrlInfo} instance. The URL this method produces must be
+	 * parseable by the {@link #parseRequest(Request)} method.
+	 * 
+	 * @param info
+	 * @return Url result URL
+	 */
+	protected abstract Url buildUrl(UrlInfo info);
+
+	public RequestHandler decode(Request request)
+	{
+		UrlInfo urlInfo = parseRequest(request);
+
+		// check if the URL is long enough and starts with the proper segments
+		if (urlInfo != null)
+		{
+			PageComponentInfo info = urlInfo.getPageComponentInfo();
+			Class<? extends IPage> pageClass = urlInfo.getPageClass();
+			PageParameters pageParameters = urlInfo.getPageParameters();
+
+			if (info == null || info.getPageInfo().getPageId() == null)
+			{
+				// if there are is no page instance information (only page map name - optionally)
+				// then this is a simple bookmarkable URL
+				return processBookmarkable(pageClass, pageParameters);
+			}
+			else if (info.getPageInfo().getPageId() != null && info.getComponentInfo() == null)
+			{
+				// if there is page instance information in the URL but no component and listener
+				// interface then this is a hybrid URL - we need to try to reuse existing page
+				// instance
+				return processHybrid(info.getPageInfo(), pageClass, pageParameters, null);
+			}
+			else if (info.getComponentInfo() != null)
+			{
+				// with both page instance and component+listener this is a listener interface URL
+				return processListener(info, pageClass, pageParameters);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Indicates whether hybrid {@link RenderPageRequestHandler} URL for page will be generated only
+	 * if page has been created with bookmarkable URL.
+	 * <p>
+	 * Generic bookmarkable encoders this method should return <code>true</code>. For explicit
+	 * (mounted) encoders this method should return <code>false</code>
+	 * 
+	 * @return <code>true</code> if hybrid URL requires page created bookmarkable,
+	 *         <code>false</code> otherwise.
+	 */
+	protected abstract boolean pageMustHaveBeenCreatedBookmarkable();
+
+	public Url encode(RequestHandler requestHandler)
+	{
+		if (requestHandler instanceof BookmarkablePageRequestHandler)
+		{
+			// simple bookmarkable URL with no page instance information
+			BookmarkablePageRequestHandler handler = (BookmarkablePageRequestHandler)requestHandler;
+
+			PageInfo info = new PageInfo();
+			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null),
+				handler.getPageClass(), handler.getPageParameters());
+
+			return buildUrl(urlInfo);
+		}
+		else if (requestHandler instanceof RenderPageRequestHandler)
+		{
+			// possibly hybrid URL - bookmarkable URL with page instance information
+			// but only allowed if the page was created by bookamarkable URL
+
+			RenderPageRequestHandler handler = (RenderPageRequestHandler)requestHandler;
+			IPage page = handler.getPage();
+
+			if (!pageMustHaveBeenCreatedBookmarkable() || page.wasCreatedBookmarkable())
+			{
+				PageInfo info = null;
+				if (!page.isPageStateless())
+				{
+					info = new PageInfo(page);
+				}
+				PageComponentInfo pageComponentInfo = info != null ? new PageComponentInfo(info,
+					null) : null;
+
+				UrlInfo urlInfo = new UrlInfo(pageComponentInfo, page.getClass(),
+					handler.getPageParameters());
+				return buildUrl(urlInfo);
+			}
+			else
+			{
+				return null;
+			}
+
+		}
+		else if (requestHandler instanceof BookmarkableListenerInterfaceRequestHandler)
+		{
+			// listener interface URL with page class information
+			BookmarkableListenerInterfaceRequestHandler handler = (BookmarkableListenerInterfaceRequestHandler)requestHandler;
+			IPage page = handler.getPage();
+			PageInfo pageInfo = new PageInfo(page);
+			ComponentInfo componentInfo = new ComponentInfo(
+				page.getRenderCount(), requestListenerInterfaceToString(handler.getListenerInterface()),
+				handler.getComponent().getPath(), handler.getBehaviorIndex());
+
+			UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(pageInfo, componentInfo),
+				page.getClass(), handler.getPageParameters());
+			return buildUrl(urlInfo);
+		}
+
+		return null;
+	}
+
+	public abstract int getMachingSegmentsCount(Request request);
+
+	/**
+	 * Convenience method for representing mountPath as array of segments
+	 * 
+	 * @param mountPath
+	 * @return array of path segments
+	 */
+	protected String[] getMountSegments(String mountPath)
+	{
+		if (mountPath.startsWith("/"))
+		{
+			mountPath = mountPath.substring(1);
+		}
+		Url url = Url.parse(mountPath);
+
+		if (url.getSegments().isEmpty())
+		{
+			throw new IllegalArgumentException("Mount path must have at least one segment.");
+		}
+
+		String[] res = new String[url.getSegments().size()];
+		for (int i = 0; i < res.length; ++i)
+		{
+			res[i] = url.getSegments().get(i);
+		}
+		return res;
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractBookmarkableEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,445 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IComponent;
+import org.apache.wicket.IPage;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.protocol.http.PageExpiredException;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.request.RequestHandlerEncoder;
+import org.apache.wicket.request.RequestParameters;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.Url.QueryParameter;
+import org.apache.wicket.request.encoder.info.PageComponentInfo;
+import org.apache.wicket.request.encoder.info.PageInfo;
+import org.apache.wicket.request.encoder.parameters.PageParametersEncoder;
+import org.apache.wicket.util.lang.Classes;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Convenience class for implementing page/components related encoders.
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractEncoder implements RequestHandlerEncoder
+{
+	protected EncoderContext getContext()
+	{
+		return WebApplication.get().getEncoderContext();
+	}
+
+	/**
+	 * Converts the specified listener interface to String.
+	 * 
+	 * @param listenerInterface
+	 * @return listenerInterface name as string
+	 */
+	protected String requestListenerInterfaceToString(RequestListenerInterface listenerInterface)
+	{
+		if (listenerInterface == null)
+		{
+			throw new IllegalArgumentException("Argument 'listenerInterface' may not be null.");
+		}
+		return getContext().requestListenerInterfaceToString(listenerInterface);
+	}
+
+	/**
+	 * Creates listener interface from the specified string
+	 * 
+	 * @param interfaceName
+	 * @return listener interface
+	 */
+	protected RequestListenerInterface requestListenerInterfaceFromString(String interfaceName)
+	{
+		if (interfaceName == null)
+		{
+			throw new IllegalArgumentException("Argument 'interfaceName' may not be null.");
+		}
+		return getContext().requestListenerInterfaceFromString(interfaceName);
+	}
+
+	protected static String getPlaceholder(String s)
+	{
+		if (s == null || s.length() < 4 || !s.startsWith("${") || !s.endsWith("}"))
+		{
+			return null;
+		}
+		else
+		{
+			return s.substring(2, s.length() - 1);
+		}
+	}
+
+	/**
+	 * Returns true if the given url starts with specified segments. Segments that contain
+	 * placelhoders are not compared.
+	 * 
+	 * @param url
+	 * @param segments
+	 * @return <code>true</code> if the URL starts with the specified segments, <code>false</code>
+	 *         otherwise
+	 */
+	protected boolean urlStartsWith(Url url, String... segments)
+	{
+		if (url == null)
+		{
+			return false;
+		}
+		else
+		{
+			if (url.getSegments().size() < segments.length)
+			{
+				return false;
+			}
+			else
+			{
+				for (int i = 0; i < segments.length; ++i)
+				{
+					if (segments[i].equals(url.getSegments().get(i)) == false && getPlaceholder(segments[i]) == null)
+					{
+						return false;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Extracts the {@link PageComponentInfo} from the URL. The {@link PageComponentInfo} is encoded
+	 * as the very first query parameter and the parameter consists of name only (no value).
+	 * 
+	 * @param url
+	 * 
+	 * @return PageComponentInfo instance if one was encoded in URL, <code>null</code> otherwise.
+	 */
+	protected PageComponentInfo getPageComponentInfo(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalStateException("Argument 'url' may not be null.");
+		}
+		if (url.getQueryParameters().size() > 0)
+		{
+			QueryParameter param = url.getQueryParameters().get(0);
+			if (Strings.isEmpty(param.getValue()))
+			{
+				return PageComponentInfo.parse(param.getName());
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Encodes the {@link PageComponentInfo} instance as the first query string parameter to the
+	 * URL.
+	 * 
+	 * @param url
+	 * @param info
+	 */
+	protected void encodePageComponentInfo(Url url, PageComponentInfo info)
+	{
+		if (url == null)
+		{
+			throw new IllegalStateException("Argument 'url' may not be null.");
+		}
+		if (info != null)
+		{
+			String s = info.toString();
+			if (!Strings.isEmpty(s))
+			{
+				QueryParameter parameter = new QueryParameter(s, "");
+				url.getQueryParameters().add(parameter);
+			}
+		}
+	}
+
+	/**
+	 * Returns the page instance specified by the {@link PageInfo} object. When there is no such
+	 * page instance this method either throws {@link PageExpiredException} (if the
+	 * <code>throwExpiredExceptionIfNotFound</code> parameter is true) or return <code>null>/code>
+	 * 
+	 * @param info
+	 * @param throwExpiredExceptionIfNotFound
+	 * @param renderCount
+	 * @return page instance or <code>null</code>
+	 */
+	protected IPage getPageInstance(PageInfo info, boolean throwExpiredExceptionIfNotFound, Integer renderCount)
+	{
+		if (info == null)
+		{
+			throw new IllegalArgumentException("Argument 'info' may not be null.");
+		}
+		IPage page = getContext().getPageInstance(info.getPageId());
+
+		if (page == null && throwExpiredExceptionIfNotFound)
+		{
+			throw new PageExpiredException("Page expired.");
+		}
+
+		if (page != null && renderCount != null && page.getRenderCount() != renderCount)
+		{
+			throw new StalePageException(page);
+		}
+
+		return page;
+	}
+
+	/**
+	 * Returns the page instance specified by the {@link PageInfo} object. If there is no such page
+	 * instance {@link PageExpiredException} is thrown.
+	 * 
+	 * @param info
+	 * @param renderCount
+	 * @return page instance
+	 */
+	protected IPage getPageInstance(PageInfo info, Integer renderCount)
+	{
+		return getPageInstance(info, true, renderCount);
+	}
+
+	/**
+	 * Returns component on page with given path. If the component is not found an exception is
+	 * thrown.
+	 * 
+	 * @param page
+	 * @param componentPath
+	 * @return component instance
+	 */
+	protected IComponent getComponent(IPage page, String componentPath)
+	{
+		if (page == null)
+		{
+			throw new IllegalArgumentException("Argument 'page' may not be nul.");
+		}
+		if (componentPath == null)
+		{
+			throw new IllegalArgumentException("Argument 'componentPath' may not be null.");
+		}
+		IComponent component = page.get(componentPath);
+		if (component == null)
+		{
+			throw new WicketRuntimeException("Component with path " + componentPath + " not found in page.");
+		}
+		else
+		{
+			return component;
+		}
+	}
+
+	/**
+	 * Loads page class with given name.
+	 * 
+	 * @param name
+	 * @return class
+	 */
+	protected Class<? extends IPage> getPageClass(String name)
+	{
+		if (name == null)
+		{
+			throw new IllegalArgumentException("Argument 'name' may not be null");
+		}
+		return Classes.resolveClass(name);
+	}
+
+	/**
+	 * Extracts {@link PageParameters} from the URL using the given {@link PageParametersEncoder}.
+	 * 
+	 * @param url
+	 * @param requestParameters
+	 * @param segmentsToSkip
+	 *            how many URL segments should be skipped because they "belong" to the
+	 *            {@link RequestHandlerEncoder}
+	 * @param encoder
+	 * @return PageParameters instance
+	 */
+	protected PageParameters extractPageParameters(Url url, RequestParameters requestParameters, int segmentsToSkip,
+			PageParametersEncoder encoder)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may not be null.");
+		}
+		if (encoder == null)
+		{
+			throw new IllegalArgumentException("Argument 'encoder' may not be null.");
+		}
+
+		// strip the segments and first query parameter from URL
+		Url urlCopy = new Url(url);
+		while (segmentsToSkip > 0 && urlCopy.getSegments().isEmpty() == false)
+		{
+			urlCopy.getSegments().remove(0);
+			--segmentsToSkip;
+		}
+
+		if (!urlCopy.getQueryParameters().isEmpty() && Strings.isEmpty(urlCopy.getQueryParameters().get(0).getValue()))
+		{
+			urlCopy.getQueryParameters().remove(0);
+		}
+
+		PageParameters decoded = encoder.decodePageParameters(urlCopy, requestParameters);
+		return decoded != null ? decoded : new PageParameters();
+	}
+
+	/**
+	 * Encodes the given {@link PageParameters} to the URL using the given
+	 * {@link PageParametersEncoder}. The original URL object is unchanged.
+	 * 
+	 * @param url
+	 * @param pageParameters
+	 * @param encoder
+	 * @return URL with encoded parameters
+	 */
+	protected Url encodePageParameters(Url url, PageParameters pageParameters, PageParametersEncoder encoder)
+	{
+		if (url == null)
+		{
+			throw new IllegalArgumentException("Argument 'url' may no be null.");
+		}
+
+		if (pageParameters == null)
+		{
+			pageParameters = new PageParameters();
+		}
+
+		if (encoder == null)
+		{
+			throw new IllegalArgumentException("Argument 'encoder' may not be null.");
+		}
+
+		Url parametersUrl = encoder.encodePageParameters(pageParameters);
+		if (parametersUrl != null)
+		{
+			// copy the url
+			url = new Url(url);
+
+			for (String s : parametersUrl.getSegments())
+			{
+				url.getSegments().add(s);
+			}
+			for (QueryParameter p : parametersUrl.getQueryParameters())
+			{
+				url.getQueryParameters().add(p);
+			}
+		}
+
+		return url;
+	}
+
+	/**
+	 * Creates new page instance with the specified attributes.
+	 * 
+	 * @param pageMapName
+	 * @param pageClass
+	 * @param pageParameters
+	 * @return new page instance
+	 */
+	protected IPage newPageInstance(Class<? extends IPage> pageClass, PageParameters pageParameters)
+	{
+		IPage page = getContext().newPageInstance(pageClass, pageParameters);
+
+		// the page might have not propagate page parameters from constructor. if that's the case
+		// we force the parameters 
+		if (pageParameters != null && page.getPageParameters() != pageParameters)
+		{
+			page.getPageParameters().assign(pageParameters);
+		}
+		return page;
+	}
+
+	/**
+	 * Tries to get existing page instance specified by the {@link PageInfo} object. If there is no
+	 * such instance or there instance class doesn't match the <code>pageClass</code> argument, new
+	 * page instance is created.
+	 * 
+	 * @param pageInfo
+	 * @param pageClass
+	 * @param pageParameters
+	 * @param renderCount
+	 * 
+	 * @return page instance
+	 */
+	protected IPage getPageInstance(PageInfo pageInfo, Class<? extends IPage> pageClass, PageParameters pageParameters,
+			Integer renderCount)
+	{
+		return getPageInstance(pageInfo, pageClass, pageParameters, false, renderCount);
+	}
+
+	/**
+	 * Tries to get existing page instance specified by the {@link PageInfo} object. If there is no
+	 * such instance or there instance class doesn't match the <code>pageClass</code> argument, new
+	 * page instance is created.
+	 * <p>
+	 * If new page was created (no existing page was found) and the the
+	 * <code>prepareForRenderNewPage</code> flag is set, <code>page.repareForRender(false)</code> is
+	 * called on the page. This is necessary to do when looking on a component on stateless page as
+	 * the component hierarchy has to be built.
+	 * 
+	 * @param pageInfo
+	 * @param pageClass
+	 * @param pageParameters
+	 * @param prepareForRenderNewPage
+	 * @param renderCount
+	 * 
+	 * @return page instance
+	 */
+	protected IPage getPageInstance(PageInfo pageInfo, Class<? extends IPage> pageClass, PageParameters pageParameters,
+			boolean prepareForRenderNewPage, Integer renderCount)
+	{
+		if (pageInfo == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageInfo' may not be null.");
+		}
+		if (pageClass == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageClass' may not be null.");
+		}
+		if (pageParameters == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageParameters' may not be null.");
+		}
+		IPage page = getContext().getPageInstance(pageInfo.getPageId());
+		if (page != null && page.getClass().equals(pageClass) == false)
+		{
+			page = null;
+		}
+		if (page == null)
+		{
+			page = newPageInstance(pageClass, pageParameters);
+
+			// this is required for stateless listeners
+			if (prepareForRenderNewPage && page instanceof Page)
+			{
+				((Page) page).prepareForRender(false);
+			}
+		}
+		else
+		{
+			if (renderCount != null && page.getRenderCount() != renderCount)
+			{
+				throw new StalePageException(page);
+			}
+			page.getPageParameters().assign(pageParameters);
+		}
+		return page;
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractResourceReferenceEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractResourceReferenceEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractResourceReferenceEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractResourceReferenceEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,133 @@
+/*
+ * 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.request.encoder;
+
+import java.util.Locale;
+
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.Url.QueryParameter;
+import org.apache.wicket.resource.ResourceReference;
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Base class for encoding and decoding {@link ResourceReference}s
+ * 
+ * @author Matej Knopp
+ */
+public abstract class AbstractResourceReferenceEncoder extends AbstractEncoder
+{
+
+	protected static class ResourceReferenceAttributes
+	{
+		protected Locale locale;
+		protected String style;
+	};
+	
+	protected static String encodeResourceReferenceAttributes(ResourceReferenceAttributes attributes)
+	{
+		if (attributes == null || (attributes.locale == null && attributes.style == null))
+		{
+			return null;
+		}
+		else
+		{
+			StringBuilder res = new StringBuilder();
+			if (attributes.locale != null)
+			{
+				res.append(attributes.locale.toString());
+			}
+			if (!Strings.isEmpty(attributes.style))
+			{
+				res.append("-");
+				res.append(attributes.style);
+			}
+			return res.toString();
+		}
+	};
+	
+	protected static ResourceReferenceAttributes decodeResourceReferenceAttributes(String attributes)
+	{
+		ResourceReferenceAttributes res = new ResourceReferenceAttributes();		
+		if (!Strings.isEmpty(attributes))
+		{
+			String split[] = attributes.split("-", 2);
+			res.locale = parseLocale(split[0]);
+			if (split.length == 2)
+			{
+				res.style = split[1];
+			}			
+		}
+		return res;
+	}
+	
+	private static Locale parseLocale(String locale)
+	{
+		if (Strings.isEmpty(locale))
+		{
+			return null;
+		}
+		else
+		{
+			String parts[] = locale.toLowerCase().split("_", 3);
+			if (parts.length == 1)
+			{
+				return new Locale(parts[0]);
+			}
+			else if (parts.length == 2)
+			{
+				return new Locale(parts[0], parts[1]);
+			}
+			else if (parts.length == 3)
+			{
+				return new Locale(parts[0], parts[1], parts[2]);	
+			}
+			else
+			{
+				return null;
+			}
+		}
+	}	
+	
+	protected void encodeResourceReferenceAttributes(Url url, ResourceReference reference)
+	{
+		ResourceReferenceAttributes attributes = new ResourceReferenceAttributes();
+		attributes.locale = reference.getLocale();
+		attributes.style = reference.getStyle();
+		String encoded = encodeResourceReferenceAttributes(attributes);
+		if (!Strings.isEmpty(encoded))
+		{
+			url.getQueryParameters().add(new Url.QueryParameter(encoded, ""));
+		}
+	}
+	
+	protected ResourceReferenceAttributes getResourceReferenceAttributes(Url url)
+	{
+		if (url == null)
+		{
+			throw new IllegalStateException("Argument 'url' may not be null.");
+		}
+		if (url.getQueryParameters().size() > 0)
+		{
+			QueryParameter param = url.getQueryParameters().get(0);
+			if (Strings.isEmpty(param.getValue()))
+			{
+				return decodeResourceReferenceAttributes(param.getName());
+			}
+		}
+		return new ResourceReferenceAttributes();
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/AbstractResourceReferenceEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/BookmarkableEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/BookmarkableEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/BookmarkableEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/BookmarkableEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,126 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IPage;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.encoder.info.PageComponentInfo;
+import org.apache.wicket.request.encoder.parameters.PageParametersEncoder;
+import org.apache.wicket.request.encoder.parameters.SimplePageParametersEncoder;
+import org.apache.wicket.request.request.Request;
+
+/**
+ * Decodes and encodes the following URLs:
+ * 
+ * <pre>
+ *  Page Class - Render (BookmarkablePageRequestHandler)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap
+ *  (these will redirect to hybrid alternative if page is not stateless)
+ * 
+ *  Page Instance - Render Hybrid (RenderPageRequestHandler for pages that were created using bookmarkable URLs)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap.2.4
+ * 
+ *  Page Instance - Bookmarkable Listener (BookmarkableListenerInterfaceRequestHandler)
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?pageMap.2.4-click-foo-bar-baz
+ *  /wicket/bookmarkable/org.apache.wicket.MyPage?2.4-click.1-foo-bar-baz (1 is behavior index)
+ *  (these will redirect to hybrid if page is not stateless)
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class BookmarkableEncoder extends AbstractBookmarkableEncoder
+{
+	private final PageParametersEncoder pageParametersEncoder;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param pageParametersEncoder
+	 */
+	public BookmarkableEncoder(PageParametersEncoder pageParametersEncoder)
+	{
+		if (pageParametersEncoder == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageParametersEncoder' may not be null.");
+		}
+		this.pageParametersEncoder = pageParametersEncoder;
+	}
+
+	/**
+	 * Construct.
+	 */
+	public BookmarkableEncoder()
+	{
+		this(new SimplePageParametersEncoder());
+	}
+
+	@Override
+	protected Url buildUrl(UrlInfo info)
+	{
+		Url url = new Url();
+		url.getSegments().add(getContext().getNamespace());
+		url.getSegments().add(getContext().getBookmarkableIdentifier());
+		url.getSegments().add(info.getPageClass().getName());
+
+		encodePageComponentInfo(url, info.getPageComponentInfo());
+
+		return encodePageParameters(url, info.getPageParameters(), pageParametersEncoder);
+	}
+
+	@Override
+	protected UrlInfo parseRequest(Request request)
+	{
+		Url url = request.getUrl();
+		if (url.getSegments().size() >= 3 &&
+			urlStartsWith(url, getContext().getNamespace(),
+				getContext().getBookmarkableIdentifier()))
+		{
+			// try to extract page and component information from URL
+			PageComponentInfo info = getPageComponentInfo(url);
+
+			// load the page class
+			String className = url.getSegments().get(2);
+			Class<? extends IPage> pageClass = getPageClass(className);
+
+			// extract the PageParameters from URL if there are any
+			PageParameters pageParameters = extractPageParameters(url,
+				request.getRequestParameters(), 3, pageParametersEncoder);
+
+			return new UrlInfo(info, pageClass, pageParameters);
+		}
+		return null;
+	}
+	
+	@Override
+	protected boolean pageMustHaveBeenCreatedBookmarkable()
+	{
+		return true;
+	}
+
+	public int getMachingSegmentsCount(Request request)
+	{
+		// always return 0 here so that the mounts have higher priority
+		return 0;
+	}
+
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/BookmarkableEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/EncoderContext.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/EncoderContext.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/EncoderContext.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/EncoderContext.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,98 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IPage;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.resource.ResourceReferenceRegistry;
+
+/**
+ * Utility interface for providing and creating new page instances.
+ * 
+ * @author Matej Knopp
+ */
+public interface EncoderContext
+{
+	/**
+	 * Returns existing page instance if the page exists.
+	 * 
+	 * @param pageId
+	 * @return page instance or <code>null</code> if the page does not exist.
+	 */
+	public IPage getPageInstance(int pageId);
+
+	/**
+	 * Creates new page instance of page with given class. The page should be marked as
+	 * create bookmarkable, so subsequent calls to {@link IPage#wasCreatedBookmarkable()}
+	 * must return <code>true</code>
+	 * 
+	 * @param pageMapName
+	 * @param pageClass
+	 * @param pageParameters
+	 * @return new page instance
+	 */
+	public IPage newPageInstance(Class<? extends IPage> pageClass,	PageParameters pageParameters);
+	
+	/**
+	 * @return the namespace for Wicket URLs.
+	 */
+	public String getNamespace();
+	
+	/**
+	 * @return identifier for non bookmarkable URLs
+	 */
+	public String getPageIdentifier();
+	
+	/**
+	 * @return identifier for bookmarkable URLs
+	 */
+	public String getBookmarkableIdentifier();
+	
+	/**
+	 * @return identifier for resources
+	 */
+	public String getResourceIdentifier();
+	
+	/**
+	 * @return {@link ResourceReferenceRegistry}
+	 */
+	public ResourceReferenceRegistry getResourceReferenceRegistry();
+	
+	/**
+	 * Returns the listener interface name as string.
+	 * 
+	 * @param listenerInterface
+	 * @return listener interface name as string
+	 */
+	public String requestListenerInterfaceToString(RequestListenerInterface listenerInterface);
+	
+	/**
+	 * Returns listener interface for the name
+	 * 
+	 * @param interfaceName
+	 * @return listener interface
+	 */
+	public RequestListenerInterface requestListenerInterfaceFromString(String interfaceName);
+	
+	/**
+	 * Returns the home page class.
+	 * 
+	 * @return home page class
+	 */
+	public Class<? extends IPage> getHomePageClass();
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/EncoderContext.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/MountedEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/MountedEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/MountedEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/MountedEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,230 @@
+/*
+ * 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.request.encoder;
+
+import java.lang.ref.WeakReference;
+
+import org.apache.wicket.IPage;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.encoder.info.PageComponentInfo;
+import org.apache.wicket.request.encoder.parameters.PageParametersEncoder;
+import org.apache.wicket.request.encoder.parameters.SimplePageParametersEncoder;
+import org.apache.wicket.request.request.Request;
+
+/**
+ * Encoder for mounted URL. The mount path can contain parameter placeholders, i.e.
+ * <code>/mount/${foo}/path</code>. In that case the appropriate segment from the URL will be
+ * accessible as named parameter "foo" in the {@link PageParameters}. Similarly when the URL is
+ * constructed, the second segment will contain the value of the "foo" named page parameter.
+ * <p>
+ * Decodes and encodes the following URLs:
+ * 
+ * <pre>
+ *  Page Class - Render (BookmarkablePageRequestHandler for mounted pages)
+ *  /mount/point
+ *  /mount/point?pageMap
+ *  (these will redirect to hybrid alternative if page is not stateless)
+ * 
+ *  IPage Instance - Render Hybrid (RenderPageRequestHandler for mounted pages) 
+ *  /mount/point?2
+ *  /mount/point?2.4
+ *  /mount/point?pageMap.2.4
+ * 
+ *  IPage Instance - Bookmarkable Listener (BookmarkableListenerInterfaceRequestHandler for mounted pages) 
+ *  /mount/point?2-click-foo-bar-baz
+ *  /mount/point?2.4-click-foo-bar-baz
+ *  /mount/point?pageMap.2.4-click-foo-bar-baz
+ *  /mount/point?2.4-click.1-foo-bar-baz (1 is behavior index)
+ *  (these will redirect to hybrid if page is not stateless)
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class MountedEncoder extends AbstractBookmarkableEncoder
+{
+	private final PageParametersEncoder pageParametersEncoder;
+	private final String[] mountSegments;
+
+	/** bookmarkable page class. */
+	protected final WeakReference<Class<? extends IPage>> pageClass;
+
+
+	/**
+	 * Construct.
+	 * 
+	 * @param mountPath
+	 * @param pageClass
+	 * @param pageParametersEncoder
+	 */
+	public MountedEncoder(String mountPath, Class<? extends IPage> pageClass,
+		PageParametersEncoder pageParametersEncoder)
+	{
+		if (pageParametersEncoder == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageParametersEncoder' may not be null.");
+		}
+		if (pageClass == null)
+		{
+			throw new IllegalArgumentException("Argument 'pageClass' may not be null.");
+		}
+		if (mountPath == null)
+		{
+			throw new IllegalArgumentException("Argument 'mountPath' may not be null.");
+		}
+		this.pageParametersEncoder = pageParametersEncoder;
+		this.pageClass = new WeakReference<Class<? extends IPage>>(pageClass);
+		this.mountSegments = getMountSegments(mountPath);
+	}
+
+	/**
+	 * Construct.
+	 * 
+	 * @param mountPath
+	 * @param pageClass
+	 */
+	public MountedEncoder(String mountPath, Class<? extends IPage> pageClass)
+	{
+		this(mountPath, pageClass, new SimplePageParametersEncoder());
+	}
+
+	@Override
+	protected UrlInfo parseRequest(Request request)
+	{
+		Url url = request.getUrl();
+
+		// when redirect to buffer/render is active and redirectFromHomePage returns true
+		// check mounted class against the home page class. if it matches let wicket redirect
+		// to the mounted URL
+		if (redirectFromHomePage() && checkHomePage(url))
+		{
+			UrlInfo info = new UrlInfo(null, getContext().getHomePageClass(), newPageParameters());
+			return info;
+		}
+		// check if the URL is long enough and starts with the proper segments
+		else if (url.getSegments().size() >= mountSegments.length &&
+			urlStartsWith(url, mountSegments))
+		{
+			// try to extract page and component information from URL
+			PageComponentInfo info = getPageComponentInfo(url);
+
+			Class<? extends IPage> pageClass = this.pageClass.get();
+
+			// extract the PageParameters from URL if there are any
+			PageParameters pageParameters = extractPageParameters(url,
+				request.getRequestParameters(), mountSegments.length, pageParametersEncoder);
+
+			// check if there are placeholders in mount segments
+			for (int i = 0; i < mountSegments.length; ++i)
+			{
+				String placeholder = getPlaceholder(mountSegments[i]);
+				if (placeholder != null)
+				{
+					// extract the parameter from URL
+					pageParameters.addNamedParameter(placeholder, url.getSegments().get(i));
+				}
+			}
+
+			return new UrlInfo(info, pageClass, pageParameters);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	protected PageParameters newPageParameters()
+	{
+		return new PageParameters();
+	}
+
+	@Override
+	protected Url buildUrl(UrlInfo info)
+	{
+		Url url = new Url();
+		for (String s : mountSegments)
+		{
+			url.getSegments().add(s);
+		}
+		encodePageComponentInfo(url, info.getPageComponentInfo());
+
+		PageParameters copy = new PageParameters(info.getPageParameters());
+
+		for (int i = 0; i < mountSegments.length; ++i)
+		{
+			String placeholder = getPlaceholder(mountSegments[i]);
+			if (placeholder != null)
+			{
+				url.getSegments().set(i, copy.getNamedParameter(placeholder).toString(""));
+				copy.removeNamedParameter(placeholder);
+			}
+		}
+
+		return encodePageParameters(url, copy, pageParametersEncoder);
+	}
+
+	/**
+	 * Check if the URL is for home page and the home page class match mounted class. If so,
+	 * redirect to mounted URL.
+	 * 
+	 * @param url
+	 * @return request handler or <code>null</code>
+	 */
+	private boolean checkHomePage(Url url)
+	{
+		if (url.getSegments().isEmpty() && url.getQueryParameters().isEmpty())
+		{
+			// this is home page
+			if (pageClass.get().equals(getContext().getHomePageClass()) && redirectFromHomePage())
+			{
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * If this method returns <code>true</code> and application home page class is same as the
+	 * class mounted with this encoder, request to home page will result in a redirect to the
+	 * mounted path.
+	 * 
+	 * @return whether this encode should respond to home page request when home page class is same
+	 *         as mounted class.
+	 */
+	protected boolean redirectFromHomePage()
+	{
+		return true;
+	}
+
+	@Override
+	protected boolean pageMustHaveBeenCreatedBookmarkable()
+	{
+		return false;
+	}
+
+	public int getMachingSegmentsCount(Request request)
+	{
+		if (urlStartsWith(request.getUrl(), mountSegments))
+		{
+			return mountSegments.length;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/MountedEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/PageInstanceEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/PageInstanceEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/PageInstanceEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/PageInstanceEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,136 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IComponent;
+import org.apache.wicket.IPage;
+import org.apache.wicket.RequestListenerInterface;
+import org.apache.wicket.request.RequestHandler;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.encoder.info.ComponentInfo;
+import org.apache.wicket.request.encoder.info.PageComponentInfo;
+import org.apache.wicket.request.encoder.info.PageInfo;
+import org.apache.wicket.request.handler.impl.ListenerInterfaceRequestHandler;
+import org.apache.wicket.request.handler.impl.RenderPageRequestHandler;
+import org.apache.wicket.request.request.Request;
+
+/**
+ * Decodes and encodes the following URLs:
+ * 
+ * <pre>
+ *  Page Instance - Render (RenderPageRequestHandler)
+ *  /wicket/page?2
+ *  /wicket/page?2.4
+ *  /wicket/page?abc.2.4
+ * 
+ *  Page Instance - Listener (ListenerInterfaceRequestHandler)
+ *  /wicket/page?2-click-foo-bar-baz
+ *  /wicket/page?2.4-click-foo-bar-baz
+ *  /wicket/page?pageMap.2.4-click-foo-bar-bazr-baz
+ *  /wicket/page?2.4-click.1-foo-bar-baz (1 is behavior index)  
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class PageInstanceEncoder extends AbstractEncoder
+{
+
+	/**
+	 * Construct.
+	 */
+	public PageInstanceEncoder()
+	{
+	}
+
+
+	public RequestHandler decode(Request request)
+	{
+		Url url = request.getUrl();
+		if (urlStartsWith(url, getContext().getNamespace(), getContext().getPageIdentifier()))
+		{
+			PageComponentInfo info = getPageComponentInfo(url);
+			if (info != null && info.getPageInfo().getPageId() != null)
+			{
+				Integer renderCount = info.getComponentInfo() != null ? info.getComponentInfo().getRenderCount() : null;
+				
+				IPage page = getPageInstance(info.getPageInfo(), renderCount);
+				
+				if (info.getComponentInfo() == null)
+				{
+					// render page
+					return new RenderPageRequestHandler(page);
+				}
+				else
+				{
+					ComponentInfo componentInfo = info.getComponentInfo();
+					// listener interface
+					IComponent component = getComponent(page, componentInfo.getComponentPath());
+					RequestListenerInterface listenerInterface = requestListenerInterfaceFromString(componentInfo.getListenerInterface());
+
+					return new ListenerInterfaceRequestHandler(page, component, listenerInterface,
+						componentInfo.getBehaviorIndex());
+				}
+			}
+		}
+		return null;
+	}
+
+	public Url encode(RequestHandler requestHandler)
+	{
+		PageComponentInfo info = null;
+
+		if (requestHandler instanceof RenderPageRequestHandler)
+		{
+			IPage page = ((RenderPageRequestHandler)requestHandler).getPage();
+
+			PageInfo i = new PageInfo(page);
+			info = new PageComponentInfo(i, null);
+		}
+		else if (requestHandler instanceof ListenerInterfaceRequestHandler)
+		{
+			ListenerInterfaceRequestHandler handler = (ListenerInterfaceRequestHandler)requestHandler;
+			IPage page = handler.getPage();
+			String componentPath = handler.getComponent().getPath();
+			RequestListenerInterface listenerInterface = handler.getListenerInterface();
+
+			PageInfo pageInfo = new PageInfo(page);
+			ComponentInfo componentInfo = new ComponentInfo(
+				page.getRenderCount(), requestListenerInterfaceToString(listenerInterface), componentPath,
+				handler.getBehaviorIndex());
+			info = new PageComponentInfo(pageInfo, componentInfo);
+		}
+
+		if (info != null)
+		{
+			Url url = new Url();
+			url.getSegments().add(getContext().getNamespace());
+			url.getSegments().add(getContext().getPageIdentifier());
+			encodePageComponentInfo(url, info);
+			return url;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	public int getMachingSegmentsCount(Request request)
+	{
+		// always return 0 here so that the mounts have higher priority
+		return 0;
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/PageInstanceEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/ResourceReferenceEncoder.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/ResourceReferenceEncoder.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/ResourceReferenceEncoder.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/ResourceReferenceEncoder.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,161 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.request.RequestHandler;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.encoder.parameters.PageParametersEncoder;
+import org.apache.wicket.request.encoder.parameters.SimplePageParametersEncoder;
+import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
+import org.apache.wicket.request.handler.resource.ResourceRequestHandler;
+import org.apache.wicket.request.request.Request;
+import org.apache.wicket.resource.Resource;
+import org.apache.wicket.resource.ResourceReference;
+import org.apache.wicket.util.lang.Classes;
+
+/**
+ * Generic {@link ResourceReference} encoder that encodes and decodes non-mounted
+ * {@link ResourceReference}s.
+ * <p>
+ * Decodes and encodes the following URLs:
+ * <pre>
+ *    /wicket/resource/org.apache.wicket.ResourceScope/name
+ *    /wicket/resource/org.apache.wicket.ResourceScope/name?en
+ *    /wicket/resource/org.apache.wicket.ResourceScope/name?-style
+ *    /wicket/resource/org.apache.wicket.ResourceScope/resource/name.xyz?en_EN-style
+ * </pre>
+ * 
+ * @author Matej Knopp
+ */
+public class ResourceReferenceEncoder extends AbstractResourceReferenceEncoder
+{
+	private final PageParametersEncoder pageParametersEncoder;
+
+	/**
+	 * Construct.
+	 * 
+	 * @param pageParametersEncoder
+	 */
+	public ResourceReferenceEncoder(PageParametersEncoder pageParametersEncoder)
+	{
+		this.pageParametersEncoder = pageParametersEncoder;
+	}
+
+	/**
+	 * Construct.
+	 */
+	public ResourceReferenceEncoder()
+	{
+		this(new SimplePageParametersEncoder());
+	}
+
+	protected EncoderContext getContext()
+	{
+		return null;
+	};
+
+	public RequestHandler decode(Request request)
+	{
+		Url url = request.getUrl();
+		if (url.getSegments().size() >= 4 &&
+			urlStartsWith(url, getContext().getNamespace(), getContext().getResourceIdentifier()))
+		{
+			String className = url.getSegments().get(2);
+			StringBuilder name = new StringBuilder();
+			for (int i = 3; i < url.getSegments().size(); ++i)
+			{
+				if (name.length() > 0)
+				{
+					name.append("/");
+				}
+				name.append(url.getSegments().get(i));
+			}
+
+			ResourceReferenceAttributes attributes = getResourceReferenceAttributes(url);
+
+			// extract the PageParameters from URL if there are any
+			PageParameters pageParameters = extractPageParameters(url,
+				request.getRequestParameters(), url.getSegments().size(), pageParametersEncoder);
+
+			Class<?> scope = resolveClass(className);
+			if (scope != null)
+			{
+				ResourceReference res = getContext().getResourceReferenceRegistry()
+					.getResourceReference(scope, name.toString(), attributes.locale,
+						attributes.style, false);
+				if (res != null)
+				{
+					Resource resource = res.getResource();
+					if (resource != null)
+					{
+						ResourceRequestHandler handler = new ResourceRequestHandler(resource,
+							attributes.locale, attributes.style, pageParameters);
+						return handler;
+					}
+				}
+			}
+		}
+		return null;
+	}
+
+	protected Class<?> resolveClass(String name)
+	{
+		return Classes.resolveClass(name);
+	}
+
+	protected String getClassName(Class<?> scope)
+	{
+		return scope.getName();
+	}
+
+	public Url encode(RequestHandler requestHandler)
+	{
+		if (requestHandler instanceof ResourceReferenceRequestHandler)
+		{
+			ResourceReferenceRequestHandler referenceRequestHandler = (ResourceReferenceRequestHandler)requestHandler;
+			ResourceReference reference = referenceRequestHandler.getResourceReference();
+			Url url = new Url();
+			url.getSegments().add(getContext().getNamespace());
+			url.getSegments().add(getContext().getResourceIdentifier());
+			url.getSegments().add(getClassName(reference.getScope()));
+			String nameParts[] = reference.getName().split("/");
+			for (String name : nameParts)
+			{
+				url.getSegments().add(name);
+			}
+			encodeResourceReferenceAttributes(url, reference);
+			PageParameters parameters = referenceRequestHandler.getPageParameters();
+			if (parameters != null)
+			{
+				parameters = new PageParameters(parameters);
+				// need to remove indexed parameters otherwise the URL won't be able to decode
+				parameters.clearIndexedParameters();
+				url = encodePageParameters(url, parameters, pageParametersEncoder);
+			}
+			return url;
+		}
+		return null;
+	}
+
+	public int getMachingSegmentsCount(Request request)
+	{
+		// always return 0 here so that the mounts have higher priority
+		return 0;
+	}
+
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/ResourceReferenceEncoder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/StalePageException.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/StalePageException.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/StalePageException.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/StalePageException.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,52 @@
+/*
+ * 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.request.encoder;
+
+import org.apache.wicket.IPage;
+import org.apache.wicket.WicketRuntimeException;
+
+/**
+ * Exception invoked when when stale link has been clicked. The page should then be rerendered
+ * with an explanatory error message.
+ * 
+ * @author Matej Knopp
+ */
+public class StalePageException extends WicketRuntimeException
+{
+	private static final long serialVersionUID = 1L;
+
+	private final IPage page;
+
+	/**
+	 * 
+	 * Construct.
+	 * @param page
+	 */
+	public StalePageException(IPage page)
+	{
+		this.page = page;
+	}
+	
+	/**
+	 * 
+	 * @return page instance
+	 */
+	public IPage getPage()
+	{
+		return page;
+	}
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/StalePageException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/info/ComponentInfo.java
URL: http://svn.apache.org/viewvc/wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/info/ComponentInfo.java?rev=759525&view=auto
==============================================================================
--- wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/info/ComponentInfo.java (added)
+++ wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/info/ComponentInfo.java Sat Mar 28 17:53:05 2009
@@ -0,0 +1,248 @@
+/*
+ * 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.request.encoder.info;
+
+import org.apache.wicket.util.string.Strings;
+
+/**
+ * Encodes listener inteface and component path in form of
+ * &lt;listenerInterface&gt-&lt;componentPath&gt; or
+ * &lt;listenerInterface&gt.&lt;behaviorIndex&gt;-&lt;componentPath&gt;
+ * <p>
+ * Component path is escaped (':' characters are replaced by '-')
+ * 
+ * @author Matej Knopp
+ */
+public class ComponentInfo
+{
+	private final String listenerInterface;
+	private final String componentPath;
+	private final Integer behaviorIndex;
+	private final Integer renderCount;
+
+	private static final char BEHAVIOR_INDEX_SEPARATOR = '.';
+	private static final char SEPARATOR = '-';
+
+	/**
+	 * Construct.
+	 * 
+	 * @param renderCount
+	 * @param listenerInterface
+	 * @param componentPath
+	 * @param behaviorIndex
+	 */
+	public ComponentInfo(Integer renderCount, String listenerInterface, String componentPath,
+		Integer behaviorIndex)
+	{
+		if (listenerInterface == null)
+		{
+			throw new IllegalArgumentException("Argument 'listenerInterface' may not be null.");
+		}
+		if (componentPath == null)
+		{
+			throw new IllegalArgumentException("Argument 'componentPath' may not be null.");
+		}
+		this.listenerInterface = listenerInterface;
+		this.componentPath = componentPath;
+		this.behaviorIndex = behaviorIndex;
+		this.renderCount = renderCount;
+	}
+
+	/**
+	 * @return component path
+	 */
+	public String getComponentPath()
+	{
+		return componentPath;
+	}
+
+	/**
+	 * @return listener interface name
+	 */
+	public String getListenerInterface()
+	{
+		return listenerInterface;
+	}
+
+	/**
+	 * @return behavior index
+	 */
+	public Integer getBehaviorIndex()
+	{
+		return behaviorIndex;
+	}
+	
+	/**
+	 * 
+	 * @return render count
+	 */
+	public Integer getRenderCount()
+	{
+		return renderCount;
+	}
+
+	private static final String TMP_PLACEHOLDER = "[[[[[[[WICKET[[TMP]]DASH]]" + Math.random() +
+		"]]]]";
+
+	private static String encodeComponentPath(String path)
+	{
+		if (path != null)
+		{
+			path = path.replace("" + SEPARATOR, TMP_PLACEHOLDER);
+			path = path.replace(':', SEPARATOR);
+			path = path.replace(TMP_PLACEHOLDER, "" + SEPARATOR + SEPARATOR);
+			return path;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	private static String decodeComponentPath(String path)
+	{
+		if (path != null)
+		{
+			path = path.replace("" + SEPARATOR + SEPARATOR, TMP_PLACEHOLDER);
+			path = path.replace(SEPARATOR, ':');
+			path = path.replace(TMP_PLACEHOLDER, "" + SEPARATOR);
+			return path;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	@Override
+	public String toString()
+	{
+		StringBuilder result = new StringBuilder();
+		
+		if (renderCount != null)
+		{
+			result.append(renderCount);
+			result.append(BEHAVIOR_INDEX_SEPARATOR);
+		}
+		
+		result.append(listenerInterface);
+
+		if (behaviorIndex != null)
+		{
+			result.append(BEHAVIOR_INDEX_SEPARATOR);
+			result.append(behaviorIndex);
+		}
+		result.append(SEPARATOR);
+		result.append(encodeComponentPath(componentPath));
+
+		return result.toString();
+	}
+
+	/**
+	 * Method that rigidly checks if the string consists of digits only.
+	 * 
+	 * @param string
+	 * @return whether the string consists of digits only
+	 */
+	private static boolean isNumber(String string)
+	{
+		if (string == null || string.length() == 0)
+		{
+			return false;
+		}
+		for (int i = 0; i < string.length(); ++i)
+		{
+			if (Character.isDigit(string.charAt(i)) == false)
+			{
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	/**
+	 * Parses the given string.
+	 * 
+	 * @param string
+	 * @return component info or <code>null</code> if the string is not in correct format.
+	 */
+	public static ComponentInfo parse(String string)
+	{
+		if (Strings.isEmpty(string))
+		{
+			return null;
+		}
+		int i = string.indexOf(SEPARATOR);
+		if (i == -1)
+		{
+			return null;
+		}
+		else
+		{
+			String listenerInterface = string.substring(0, i);
+			String componentPath = decodeComponentPath(string.substring(i + 1));
+
+			if (Strings.isEmpty(listenerInterface))
+			{
+				return null;
+			}
+			if (Strings.isEmpty(componentPath))
+			{
+				return null;
+			}
+
+			Integer behaviorIndex = null;
+			Integer renderCount = null;
+			
+			String listenerParts[] = Strings.split(listenerInterface, BEHAVIOR_INDEX_SEPARATOR);
+			if (listenerParts.length == 2)
+			{
+				if (isNumber(listenerParts[0]))
+				{
+					renderCount = Integer.valueOf(listenerParts[0]);
+					listenerInterface = listenerParts[1];
+				}
+				else if (isNumber(listenerParts[1]))
+				{
+					listenerInterface = listenerParts[0];
+					behaviorIndex = Integer.valueOf(listenerParts[1]);									
+				}
+				else
+				{
+					return null;
+				}
+			}
+			else if (listenerParts.length == 3)
+			{
+				if (!isNumber(listenerParts[0]) && !isNumber(listenerParts[1]))
+				{
+					return null;
+				}
+				renderCount = Integer.valueOf(listenerParts[0]);
+				listenerInterface = listenerParts[1];
+				behaviorIndex = Integer.valueOf(listenerParts[2]);
+			}
+			else if (listenerParts.length != 1)
+			{
+				return null;
+			}
+
+			return new ComponentInfo(renderCount, listenerInterface, componentPath, behaviorIndex);
+		}
+	}
+
+}

Propchange: wicket/sandbox/knopp/experimental/wicket-ng/src/main/java/org/apache/wicket/request/encoder/info/ComponentInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain