Author: dashorst
Date: Mon May  2 13:15:49 2011
New Revision: 1098550

URL: http://svn.apache.org/viewvc?rev=1098550&view=rev
Log:
MountedMapper now has optional parameter support. Thanks to Emond Papegaaij
Issue: WICKET-3660

Modified:
    
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
    
wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
    
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java

Modified: 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
 (original)
+++ 
wicket/trunk/wicket-core/src/main/java/org/apache/wicket/request/mapper/MountedMapper.java
 Mon May  2 13:15:49 2011
@@ -16,6 +16,9 @@
  */
 package org.apache.wicket.request.mapper;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.wicket.request.Request;
 import org.apache.wicket.request.Url;
 import org.apache.wicket.request.component.IRequestablePage;
@@ -31,6 +34,10 @@ import org.apache.wicket.util.lang.Args;
  * <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.
+ * Optional parameters are denoted by using a # instead of $: 
<code>/mount/#{foo}/path/${bar}</code>
+ * has an optional {@code foo} parameter, a fixed {@code /path/} part and a 
required {@code bar}
+ * parameter. When in doubt, parameters are matched from left to right, where 
required parameters
+ * are matched before optional parameters, and optional parameters eager (from 
left to right).
  * <p>
  * Decodes and encodes the following URLs:
  * 
@@ -54,6 +61,72 @@ public class MountedMapper extends Abstr
 {
        private final IPageParametersEncoder pageParametersEncoder;
 
+       private static class MountPathSegment
+       {
+               private int segmentIndex;
+               private String fixedPart;
+               private int minParameters;
+               private int optionalParameters;
+
+               public MountPathSegment(int segmentIndex)
+               {
+                       this.segmentIndex = segmentIndex;
+               }
+
+               public void setFixedPart(String fixedPart)
+               {
+                       this.fixedPart = fixedPart;
+               }
+
+               public void addRequiredParameter()
+               {
+                       minParameters++;
+               }
+
+               public void addOptionalParameter()
+               {
+                       optionalParameters++;
+               }
+
+               public int getSegmentIndex()
+               {
+                       return segmentIndex;
+               }
+
+               public String getFixedPart()
+               {
+                       return fixedPart;
+               }
+
+               public int getMinParameters()
+               {
+                       return minParameters;
+               }
+
+               public int getOptionalParameters()
+               {
+                       return optionalParameters;
+               }
+
+               public int getMaxParameters()
+               {
+                       return getOptionalParameters() + getMinParameters();
+               }
+
+               public int getFixedPartSize()
+               {
+                       return getFixedPart() == null ? 0 : 1;
+               }
+
+               @Override
+               public String toString()
+               {
+                       return "(" + getSegmentIndex() + ") " + 
getMinParameters() + "-" + getMaxParameters() +
+                               " " + (getFixedPart() == null ? "(end)" : 
getFixedPart());
+               }
+       }
+
+       private final List<MountPathSegment> pathSegments;
        private final String[] mountSegments;
 
        /** bookmarkable page class. */
@@ -82,7 +155,6 @@ public class MountedMapper extends Abstr
                this(mountPath, pageClassProvider, new PageParametersEncoder());
        }
 
-
        /**
         * Construct.
         * 
@@ -114,6 +186,39 @@ public class MountedMapper extends Abstr
                this.pageParametersEncoder = pageParametersEncoder;
                this.pageClassProvider = pageClassProvider;
                mountSegments = getMountSegments(mountPath);
+               pathSegments = getPathSegments(mountSegments);
+       }
+
+       private List<MountPathSegment> getPathSegments(String[] segments)
+       {
+               List<MountPathSegment> ret = new ArrayList<MountPathSegment>();
+               int segmentIndex = 0;
+               MountPathSegment curPathSegment = new 
MountPathSegment(segmentIndex);
+               ret.add(curPathSegment);
+               for (String curSegment : segments)
+               {
+                       if (isFixedSegment(curSegment))
+                       {
+                               curPathSegment.setFixedPart(curSegment);
+                               curPathSegment = new 
MountPathSegment(segmentIndex + 1);
+                               ret.add(curPathSegment);
+                       }
+                       else if (getPlaceholder(curSegment) != null)
+                       {
+                               curPathSegment.addRequiredParameter();
+                       }
+                       else
+                       {
+                               curPathSegment.addOptionalParameter();
+                       }
+                       segmentIndex++;
+               }
+               return ret;
+       }
+
+       private boolean isFixedSegment(String segment)
+       {
+               return getOptionalPlaceholder(segment) == null && 
getPlaceholder(segment) == null;
        }
 
        /**
@@ -136,36 +241,113 @@ public class MountedMapper extends Abstr
                {
                        // try to extract page and component information from 
URL
                        PageComponentInfo info = getPageComponentInfo(url);
-
                        Class<? extends IRequestablePage> pageClass = 
getPageClass();
+                       PageParameters pageParameters = 
extractPageParameters(request, url);
 
-                       // extract the PageParameters from URL if there are any
-                       PageParameters pageParameters = 
extractPageParameters(request, mountSegments.length,
-                               pageParametersEncoder);
+                       return new UrlInfo(info, pageClass, pageParameters);
+               }
+               else
+               {
+                       return null;
+               }
+       }
+
+       /*
+        * extract the PageParameters from URL if there are any
+        */
+       private PageParameters extractPageParameters(Request request, Url url)
+       {
+               int[] matchedParameters = getMatchedSegmentSizes(url);
+               int total = 0;
+               for (int curMatchSize : matchedParameters)
+                       total += curMatchSize;
+               PageParameters pageParameters = extractPageParameters(request, 
total, pageParametersEncoder);
 
-                       // check if there are placeholders in mount segments
-                       for (int i = 0; i < mountSegments.length; ++i)
+               int skippedParameters = 0;
+               for (int pathSegmentIndex = 0; pathSegmentIndex < 
pathSegments.size(); pathSegmentIndex++)
+               {
+                       MountPathSegment curPathSegment = 
pathSegments.get(pathSegmentIndex);
+                       int matchSize = matchedParameters[pathSegmentIndex] - 
curPathSegment.getFixedPartSize();
+                       int optionalParameterMatch = matchSize - 
curPathSegment.getMinParameters();
+                       for (int matchSegment = 0; matchSegment < matchSize; 
matchSegment++)
                        {
-                               String placeholder = 
getPlaceholder(mountSegments[i]);
+                               if (pageParameters == null)
+                               {
+                                       pageParameters = new PageParameters();
+                               }
+
+                               int curSegmentIndex = matchSegment + 
curPathSegment.getSegmentIndex();
+                               String curSegment = 
mountSegments[curSegmentIndex];
+                               String placeholder = getPlaceholder(curSegment);
+                               String optionalPlaceholder = 
getOptionalPlaceholder(curSegment);
+                               // extract the parameter from URL
                                if (placeholder != null)
                                {
-                                       // extract the parameter from URL
-                                       if (pageParameters == null)
-                                       {
-                                               pageParameters = new 
PageParameters();
-                                       }
-                                       pageParameters.add(placeholder, 
url.getSegments().get(i));
+                                       pageParameters.add(placeholder,
+                                               
url.getSegments().get(curSegmentIndex - skippedParameters));
+                               }
+                               else if (optionalPlaceholder != null && 
optionalParameterMatch > 0)
+                               {
+                                       pageParameters.add(optionalPlaceholder,
+                                               
url.getSegments().get(curSegmentIndex - skippedParameters));
+                                       optionalParameterMatch--;
                                }
                        }
+                       skippedParameters += curPathSegment.getMaxParameters() 
- matchSize;
+               }
+               return pageParameters;
+       }
 
-                       return new UrlInfo(info, pageClass, pageParameters);
+       @Override
+       protected boolean urlStartsWith(Url url, String... segments)
+       {
+               if (url == null)
+               {
+                       return false;
                }
                else
                {
-                       return null;
+                       return getMatchedSegmentSizes(url) != null;
                }
        }
 
+       private int[] getMatchedSegmentSizes(Url url)
+       {
+               int[] ret = new int[pathSegments.size()];
+               int segmentIndex = 0;
+               int pathSegmentIndex = 0;
+               for (MountPathSegment curPathSegment : pathSegments.subList(0, 
pathSegments.size() - 1))
+               {
+                       boolean foundFixedPart = false;
+                       segmentIndex += curPathSegment.getMinParameters();
+                       int max = 
Math.min(curPathSegment.getOptionalParameters() + 1,
+                               url.getSegments().size() - segmentIndex);
+
+                       for (int count = max - 1; count >= 0; count--)
+                       {
+                               if (url.getSegments()
+                                       .get(segmentIndex + count)
+                                       .equals(curPathSegment.getFixedPart()))
+                               {
+                                       foundFixedPart = true;
+                                       segmentIndex += count + 1;
+                                       ret[pathSegmentIndex] = count + 
curPathSegment.getMinParameters() + 1;
+                                       break;
+                               }
+                       }
+                       if (!foundFixedPart)
+                               return null;
+                       pathSegmentIndex++;
+               }
+               MountPathSegment lastSegment = 
pathSegments.get(pathSegments.size() - 1);
+               segmentIndex += lastSegment.getMinParameters();
+               if (segmentIndex > url.getSegments().size())
+                       return null;
+               ret[pathSegmentIndex] = 
Math.min(lastSegment.getMaxParameters(), url.getSegments().size() -
+                       segmentIndex + lastSegment.getMinParameters());
+               return ret;
+       }
+
        protected PageParameters newPageParameters()
        {
                return new PageParameters();
@@ -186,14 +368,29 @@ public class MountedMapper extends Abstr
 
                PageParameters copy = new 
PageParameters(info.getPageParameters());
 
+               int dropped = 0;
                for (int i = 0; i < mountSegments.length; ++i)
                {
                        String placeholder = getPlaceholder(mountSegments[i]);
+                       String optionalPlaceholder = 
getOptionalPlaceholder(mountSegments[i]);
                        if (placeholder != null)
                        {
-                               url.getSegments().set(i, 
copy.get(placeholder).toString(""));
+                               url.getSegments().set(i - dropped, 
copy.get(placeholder).toString(""));
                                copy.remove(placeholder);
                        }
+                       else if (optionalPlaceholder != null)
+                       {
+                               if 
(copy.getNamedKeys().contains(optionalPlaceholder))
+                               {
+                                       url.getSegments().set(i - dropped, 
copy.get(optionalPlaceholder).toString(""));
+                                       copy.remove(optionalPlaceholder);
+                               }
+                               else
+                               {
+                                       url.getSegments().remove(i - dropped);
+                                       dropped++;
+                               }
+                       }
                }
 
                return encodePageParameters(url, copy, pageParametersEncoder);

Modified: 
wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- 
wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
 (original)
+++ 
wicket/trunk/wicket-core/src/test/java/org/apache/wicket/request/mapper/MountedMapperTest.java
 Mon May  2 13:15:49 2011
@@ -65,6 +65,16 @@ public class MountedMapperTest extends A
                }
        };
 
+       private final MountedMapper optionPlaceholderEncoder = new 
MountedMapper(
+               "/some/#{param1}/path/${param2}/#{param3}", MockPage.class)
+       {
+               @Override
+               protected IMapperContext getContext()
+               {
+                       return context;
+               }
+       };
+
        /**
         * 
         */
@@ -493,6 +503,23 @@ public class MountedMapperTest extends A
                assertEquals("p2", 
page.getPageParameters().get("param2").toString());
        }
 
+       /**      */
+       public void testPlaceholderDecodeWithIndexedParameters()
+       {
+               Url url = Url.parse("some/p1/path/p2/p3/p4");
+               IRequestHandler handler = 
placeholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(2, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+               assertEquals("p1", 
page.getPageParameters().get("param1").toString());
+               assertEquals("p2", 
page.getPageParameters().get("param2").toString());
+               assertEquals("p3", page.getPageParameters().get(0).toString());
+               assertEquals("p4", page.getPageParameters().get(1).toString());
+       }
+
        /**
         * 
         */
@@ -541,4 +568,128 @@ public class MountedMapperTest extends A
                Url url = placeholderEncoder.mapHandler(handler);
                assertEquals("some/p1/path/p2/i1/i2?1&a=b&b=c", url.toString());
        }
+
+       /** */
+       public void testOptionPlaceholderDecode1()
+       {
+               Url url = Url.parse("some/p1/path/p2/p3");
+               IRequestHandler handler = 
optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(0, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 3);
+               assertEquals("p1", 
page.getPageParameters().get("param1").toString());
+               assertEquals("p2", 
page.getPageParameters().get("param2").toString());
+               assertEquals("p3", 
page.getPageParameters().get("param3").toString());
+       }
+
+       /** */
+       public void testOptionPlaceholderDecodeEagerMatchParameters()
+       {
+               Url url = Url.parse("some/path/path/path");
+               IRequestHandler handler = 
optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(0, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+               assertEquals("path", 
page.getPageParameters().get("param1").toString());
+               assertEquals("path", 
page.getPageParameters().get("param2").toString());
+               assertFalse("param3 should not be set",
+                       
page.getPageParameters().getNamedKeys().contains("param3"));
+       }
+
+       /** */
+       public void testOptionPlaceholderDecode2()
+       {
+               Url url = Url.parse("some/p1/path/p2");
+               IRequestHandler handler = 
optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(0, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+               assertEquals("p1", 
page.getPageParameters().get("param1").toString());
+               assertEquals("p2", 
page.getPageParameters().get("param2").toString());
+               assertFalse("param3 should not be set",
+                       
page.getPageParameters().getNamedKeys().contains("param3"));
+       }
+
+       /** */
+       public void testOptionPlaceholderDecode3()
+       {
+               Url url = Url.parse("some/path/p2");
+               IRequestHandler handler = 
optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(0, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 1);
+               assertFalse("param1 should not be set",
+                       
page.getPageParameters().getNamedKeys().contains("param1"));
+               assertEquals("p2", 
page.getPageParameters().get("param2").toString());
+               assertFalse("param3 should not be set",
+                       
page.getPageParameters().getNamedKeys().contains("param3"));
+       }
+
+       /** */
+       public void testOptionPlaceholderDecodeWithIndexParams()
+       {
+               Url url = Url.parse("some/path/p2/p3/p4");
+               IRequestHandler handler = 
optionPlaceholderEncoder.mapRequest(getRequest(url));
+
+               assertTrue(handler instanceof RenderPageRequestHandler);
+               IRequestablePage page = 
((RenderPageRequestHandler)handler).getPage();
+
+               assertEquals(1, page.getPageParameters().getIndexedCount());
+               assertTrue(page.getPageParameters().getNamedKeys().size() == 2);
+               assertFalse("param1 should not be set",
+                       
page.getPageParameters().getNamedKeys().contains("param1"));
+               assertEquals("p2", 
page.getPageParameters().get("param2").toString());
+               assertEquals("p3", 
page.getPageParameters().get("param3").toString());
+               assertEquals("p4", page.getPageParameters().get(0).toString());
+       }
+
+       /** */
+       public void testOptionPlaceholderEncode1()
+       {
+               PageParameters parameters = new PageParameters();
+               parameters.set(0, "i1");
+               parameters.set(1, "i2");
+               parameters.set("a", "b");
+               parameters.set("b", "c");
+               parameters.set("param1", "p1");
+               parameters.set("param2", "p2");
+
+
+               PageProvider provider = new PageProvider(MockPage.class, 
parameters);
+               provider.setPageSource(context);
+               IRequestHandler handler = new 
BookmarkablePageRequestHandler(provider);
+               Url url = optionPlaceholderEncoder.mapHandler(handler);
+               assertEquals("some/p1/path/p2/i1/i2?a=b&b=c", url.toString());
+       }
+
+       /** */
+       public void testOptionPlaceholderEncode2()
+       {
+               PageParameters parameters = new PageParameters();
+               parameters.set(0, "i1");
+               parameters.set(1, "i2");
+               parameters.set("a", "b");
+               parameters.set("b", "c");
+               parameters.set("param2", "p2");
+               parameters.set("param3", "p3");
+
+
+               PageProvider provider = new PageProvider(MockPage.class, 
parameters);
+               provider.setPageSource(context);
+               IRequestHandler handler = new 
BookmarkablePageRequestHandler(provider);
+               Url url = optionPlaceholderEncoder.mapHandler(handler);
+               assertEquals("some/path/p2/p3/i1/i2?a=b&b=c", url.toString());
+       }
 }

Modified: 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java
URL: 
http://svn.apache.org/viewvc/wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java?rev=1098550&r1=1098549&r2=1098550&view=diff
==============================================================================
--- 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java
 (original)
+++ 
wicket/trunk/wicket-request/src/main/java/org/apache/wicket/request/mapper/AbstractMapper.java
 Mon May  2 13:15:49 2011
@@ -39,7 +39,33 @@ public abstract class AbstractMapper imp
         */
        protected static String getPlaceholder(final String s)
        {
-               if ((s == null) || (s.length() < 4) || !s.startsWith("${") || 
!s.endsWith("}"))
+               return getPlaceholder(s, '$');
+       }
+
+       /**
+        * If the string is in an optional parameter placeholder format #{key} 
this method returns the
+        * key.
+        * 
+        * @param s
+        * @return placeholder key or <code>null</code> if string is not in 
right format
+        */
+       protected static String getOptionalPlaceholder(final String s)
+       {
+               return getPlaceholder(s, '#');
+       }
+
+       /**
+        * If the string is in a placeholder format x{key}, where 'x' can be 
specified, this method
+        * returns the key.
+        * 
+        * @param s
+        * @param startChar
+        *            the character used to indicate the start of the 
placeholder
+        * @return placeholder key or <code>null</code> if string is not in 
right format
+        */
+       protected static String getPlaceholder(final String s, char startChar)
+       {
+               if ((s == null) || (s.length() < 4) || !s.startsWith(startChar 
+ "{") || !s.endsWith("}"))
                {
                        return null;
                }


Reply via email to