You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2006/10/11 05:40:10 UTC

svn commit: r462683 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/structure/ main/java/org/apache/tapestry/services/ main/java/or...

Author: hlship
Date: Tue Oct 10 20:40:09 2006
New Revision: 462683

URL: http://svn.apache.org/viewvc?view=rev&rev=462683
Log:
Start working on Link and LinkFactory.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Link.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/LinkFactory.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResources.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebRequestImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebResponseImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebRequest.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebResponse.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/CollectionFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/Defense.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/UtilMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/util/UtilStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/HelloWorld.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/util/DefenseTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResources.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResources.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResources.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResources.java Tue Oct 10 20:40:09 2006
@@ -35,12 +35,20 @@
     String getId();
 
     /**
-     * Returns a string consisting of the name of the containing page, plus the concatinated ids of
-     * all containing components, separated by periods. I.e., "com.foo.pages.MyPage:foo.bar.baz".
+     * Returns a string consisting of the class name of the containing page, and the
+     * {@link #getNestedId() nested id} of this component, separated by a colon. I.e.,
+     * "com.foo.pages.MyPage:foo.bar.baz". For a page, returns just the page class name.
      */
 
     String getCompleteId();
 
+    /**
+     * Return a string consisting the concatinated ids of all containing components, separated by
+     * periods. I.e., "foo.bar.baz". Returns null for a page.
+     */
+
+    String getNestedId();
+
     /** Returns the component model object that defines the behavior of the component. */
     ComponentModel getComponentModel();
 
@@ -48,6 +56,13 @@
      * Returns the component this object provides resources for.
      */
     ComponentLifecycle getComponent();
+
+    /**
+     * Returns the page that contains this component. Technically, the page itself is an internal
+     * object in Tapestry and this returns the root component of the actual page, but from an
+     * application developer point of view, this is the page.
+     */
+    ComponentLifecycle getPage();
 
     /**
      * Returns an embedded component, given the component's id.

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Link.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Link.java?view=auto&rev=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Link.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Link.java Tue Oct 10 20:40:09 2006
@@ -0,0 +1,40 @@
+package org.apache.tapestry;
+
+import java.util.List;
+
+import org.apache.tapestry.services.Dispatcher;
+
+/**
+ * A link is the Tapestry representation of a URL or URI that triggers dynamic behavior. This link
+ * is in two parts: a path portion and a set of query parameters. A request for a link will
+ * ultimately be recognized by a {@link Dispatcher}.
+ * <p>
+ * Query parameter values are kept separate from the path portion to support encoding those values
+ * into hidden form fields (where appropriate).
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface Link
+{
+    /**
+     * The path portion of the URI. This will generally identify the the Tapestry page, event type,
+     * and component (within the page).
+     */
+    String getPath();
+
+    /**
+     * The names of any additional query parameters for the URI. Query parameters store less regular
+     * or less often used values that can not be expressed in the path. They also are used to store,
+     * or link to, persistent state.
+     * 
+     * @return list of query parameter names, is alphabetical order
+     */
+    List<String> getParameterNames();
+
+    /**
+     * Returns the value of a specifically named query parameter, or null if no such query parameter
+     * is stored in the link.
+     */
+
+    String getParameterValue(String name);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java Tue Oct 10 20:40:09 2006
@@ -37,7 +37,7 @@
 
     private final Map<String, List<String>> _mappings = newMap();
 
-    // Cache of page names to page class names
+    // Cache of logical page names to page class names
 
     private final Map<String, String> _pageClassCache = newThreadSafeMap();
 
@@ -45,6 +45,10 @@
 
     private final Map<String, String> _componentClassCache = newThreadSafeMap();
 
+    // Cache of page class names back to logical page names
+
+    private final Map<String, String> _logicalPageNameCache = newThreadSafeMap();
+
     public ComponentClassResolverImpl(ComponentInstantiatorSource componentInstantiatorSource,
             Collection<LibraryMapping> mappings)
     {
@@ -83,6 +87,7 @@
     {
         _pageClassCache.clear();
         _componentClassCache.clear();
+        _logicalPageNameCache.clear();
     }
 
     public String resolvePageNameToClassName(String pageName)
@@ -193,6 +198,89 @@
     private String buildClassName(String rootPackage, String subpackage, String extension)
     {
         return rootPackage + "." + subpackage + "." + extension;
+    }
+
+    public String resolvePageClassNameToPageName(String pageClassName)
+    {
+        String result = _logicalPageNameCache.get(pageClassName);
+
+        if (result == null)
+        {
+            result = findLogicalPageNameForClassName(pageClassName);
+            _logicalPageNameCache.put(pageClassName, result);
+        }
+
+        return result;
+    }
+
+    private String findLogicalPageNameForClassName(String pageClassName)
+    {
+        String bestName = null;
+
+        for (Map.Entry<String, List<String>> entry : _mappings.entrySet())
+        {
+            String prefix = entry.getKey();
+
+            for (String packageName : entry.getValue())
+            {
+                String potential = convertToLogicalPageName(pageClassName, packageName, prefix);
+
+                if (potential == null)
+                    continue;
+
+                if (bestName == null)
+                {
+                    bestName = potential;
+                    continue;
+                }
+
+                // Generally, for there to be more than one match, something borderline illegal
+                // must be going on.
+
+                if (potential.length() < bestName.length())
+                    bestName = potential;
+            }
+        }
+
+        if (bestName == null)
+            bestName = convertToLogicalPageName(pageClassName, _appRootPackage, null);
+
+        if (bestName == null)
+            throw new IllegalArgumentException(ServicesMessages.pageNameUnresolved(pageClassName));
+
+        return bestName;
+    }
+
+    private String convertToLogicalPageName(String pageClassName, String packageName, String prefix)
+    {
+        if (!pageClassName.startsWith(packageName))
+            return null;
+
+        String afterPackage = pageClassName.substring(packageName.length() + 1);
+        String[] names = afterPackage.split("\\.");
+
+        // The package name is the *root* package; we ignore it, but build up the rest
+        // of the logical page name from any intermediate folders/packages, leading
+        // up to the actual class name. If packages are nested in an ambiguous way,
+        // we may find something that looks like a mapping, but isn't.
+
+        if (!names[0].equals("pages"))
+            return null;
+
+        StringBuilder builder = new StringBuilder();
+
+        if (prefix != null)
+            builder.append(prefix);
+
+        for (int i = 1; i < names.length; i++)
+        {
+            if (builder.length() > 0)
+                builder.append("/");
+
+            builder.append(names[i]);
+        }
+
+        return builder.toString();
     }
 
     public void setApplicationPackage(String packageName)

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java?view=auto&rev=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryImpl.java Tue Oct 10 20:40:09 2006
@@ -0,0 +1,73 @@
+package org.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.util.Defense.cast;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Link;
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
+import org.apache.tapestry.runtime.ComponentLifecycle;
+import org.apache.tapestry.services.ComponentClassResolver;
+import org.apache.tapestry.services.LinkFactory;
+import org.apache.tapestry.services.WebRequest;
+import org.apache.tapestry.services.WebResponse;
+import org.apache.tapestry.util.Defense;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+@SuppressNullCheck
+public class LinkFactoryImpl implements LinkFactory
+{
+    private final WebRequest _request;
+
+    private final WebResponse _response;
+
+    private final ComponentClassResolver _componentClassResolver;
+
+    public LinkFactoryImpl(WebRequest request, WebResponse response,
+            ComponentClassResolver componentClassResolver)
+    {
+        _request = request;
+        _response = response;
+        _componentClassResolver = componentClassResolver;
+    }
+
+    public Link createActionLink(Object component, String action, boolean forForm,
+            Object... context)
+    {
+        ComponentLifecycle lifecycle = cast(component, ComponentLifecycle.class, "component");
+        Defense.notBlank(action, "action");
+
+        ComponentResources resources = lifecycle.getComponentResources();
+
+        ComponentLifecycle page = resources.getPage();
+
+        String logicalPageName = _componentClassResolver.resolvePageClassNameToPageName(page
+                .getClass().getName());
+
+        StringBuilder builder = new StringBuilder();
+
+        builder.append(_request.getContextPath());
+        builder.append("/");
+        builder.append(logicalPageName);
+        builder.append(".");
+        builder.append(action);
+        builder.append("/");
+        builder.append(resources.getNestedId());
+
+        for (Object id : context)
+        {
+            builder.append("/");
+
+            // TODO: Need to encode this for URLs? What if the string contains slashes, etc.?
+
+            builder.append(id.toString());
+        }
+
+        // TODO: Much more: query parameter for case where active page != component page.
+        // Letting listeners add extra parameters.
+
+        return new LinkImpl(_response, builder.toString());
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkImpl.java?view=auto&rev=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkImpl.java Tue Oct 10 20:40:09 2006
@@ -0,0 +1,41 @@
+package org.apache.tapestry.internal.services;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry.Link;
+import org.apache.tapestry.services.WebResponse;
+
+/**
+ * Starting implementation of {@link Link}. Currently does not support query parameters.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class LinkImpl implements Link
+{
+    private final WebResponse _response;
+
+    private final String _path;
+
+    public LinkImpl(WebResponse response, String path)
+    {
+        _response = response;
+        _path = path;
+    }
+
+    public List<String> getParameterNames()
+    {
+        return Collections.emptyList();
+    }
+
+    public String getParameterValue(String name)
+    {
+        return null;
+    }
+
+    public String getPath()
+    {
+        return _response.encodeURL(_path);
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Tue Oct 10 20:40:09 2006
@@ -197,4 +197,9 @@
     {
         return MESSAGES.format("page-does-not-exist", pageName);
     }
+
+    static String pageNameUnresolved(String pageClassName)
+    {
+        return MESSAGES.format("page-name-unresolved", pageClassName);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebRequestImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebRequestImpl.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebRequestImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebRequestImpl.java Tue Oct 10 20:40:09 2006
@@ -56,4 +56,9 @@
         return _request.getServletPath();
     }
 
+    public String getContextPath()
+    {
+        return _request.getContextPath();
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebResponseImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebResponseImpl.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebResponseImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/WebResponseImpl.java Tue Oct 10 20:40:09 2006
@@ -38,4 +38,8 @@
         return _response.getWriter();
     }
 
+    public String encodeURL(String URL)
+    {
+        return _response.encodeURL(URL);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java Tue Oct 10 20:40:09 2006
@@ -29,7 +29,7 @@
         RenderCommand
 {
     /** Returns the page which contains this component. */
-    Page getPage();
+    Page getContainingPage();
 
     /**
      * Containing component (or null for the root component of a page).

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java Tue Oct 10 20:40:09 2006
@@ -17,7 +17,6 @@
 import static org.apache.tapestry.util.CollectionFactory.newList;
 import static org.apache.tapestry.util.CollectionFactory.newMap;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -62,7 +61,9 @@
 
     private final String _id;
 
-    private String _completeId;
+    private final String _nestedId;
+
+    private final String _completeId;
 
     // For the momement, every component will have a template, even if it consists of
     // just a page element to queue up a BeforeRenderBody phase.
@@ -99,6 +100,7 @@
             TypeCoercer typeCoercer)
     {
         this(page, null, null, instantiator, model, typeCoercer, null);
+
     }
 
     /**
@@ -134,51 +136,36 @@
         _typeCoercer = typeCoercer;
 
         _component = instantiator.newInstance(this);
-    }
 
-    public String getCompleteId()
-    {
-        if (_completeId == null)
-            buildCompleteId();
-
-        return _completeId;
-    }
+        String componentClassName = _component.getClass().getName();
 
-    private void buildCompleteId()
-    {
-        List<String> ids = newList();
+        // A page (really, the root component of a page) does not have a container.
 
-        ComponentPageElement cpe = this;
-        while (true)
+        if (container == null)
         {
-            // Stop at the root component, which has no id.
-            String id = cpe.getId();
-
-            if (id == null)
-                break;
-
-            ids.add(id);
-
-            cpe = cpe.getContainer();
+            _completeId = componentClassName;
+            _nestedId = null;
         }
-
-        String pageName = getPage().getName();
-
-        Collections.reverse(ids);
-
-        StringBuilder builder = new StringBuilder(pageName);
-
-        int count = ids.size();
-        for (int i = 0; i < count; i++)
+        else
         {
-            builder.append(i == 0 ? ':' : '.');
-            builder.append(ids.get(i));
+            String containerId = _container.getNestedId();
+
+            _nestedId = containerId == null ? id : containerId + "." + id;
+            _completeId = componentClassName + ":" + _nestedId;
         }
+    }
 
-        _completeId = builder.toString();
+    public String getCompleteId()
+    {
+        return _completeId;
     }
 
-    public Page getPage()
+    public String getNestedId()
+    {
+        return _nestedId;
+    }
+
+    public Page getContainingPage()
     {
         return _page;
     }
@@ -186,6 +173,11 @@
     public ComponentPageElement getContainer()
     {
         return _container;
+    }
+
+    public ComponentLifecycle getPage()
+    {
+        return _page.getRootElement().getComponent();
     }
 
     public void addToBody(PageElement element)

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java Tue Oct 10 20:40:09 2006
@@ -25,11 +25,11 @@
  * package is expected to have two sub-packages, "pages" and "components". Page names are searched
  * for in the pages package, and components types are searched for the components package.
  * <p>
- * When searching, the portion of the page name (or component type) before the slash is compared
- * with known prefixes in order to find the correct root package name. Thus when encountering the
- * page name (or component type) "foo/Bar", a search for a mapping for prefix "foo" will occur. If
- * "foo" is mapped, then the final class name will be built from foo's root package, then "pages"
- * (or "components"), then "Bar".
+ * When searching, the portion of the logical page name (or component type) before the slash is
+ * compared with known prefixes in order to find the correct root package name. Thus when
+ * encountering the page name (or component type) "foo/Bar", a search for a mapping for prefix "foo"
+ * will occur. If "foo" is mapped, then the final class name will be built from foo's root package,
+ * then "pages" (or "components"), then "Bar".
  * <p>
  * If "foo" is not mapped, then the name is resolved as part of the application. The final class
  * name will be "<em>application-root</em>.pages.foo.Bar", meaning that the prefix is
@@ -61,7 +61,7 @@
 public interface ComponentClassResolver
 {
     /**
-     * Converts a partial page name (such as might be encoded into a URL) into a fully qualified
+     * Converts a logical page name (such as might be encoded into a URL) into a fully qualified
      * class name.
      * 
      * @param pageName
@@ -69,6 +69,18 @@
      * @return fully qualified class name of null if the page name can not be resolved
      */
     String resolvePageNameToClassName(String pageName);
+
+    /**
+     * Converts a fully qualified page class name into a logical class name (often, for inclusion in
+     * as part of the URI.
+     * 
+     * @param pageClassName
+     *            fully qualified name of a page class
+     * @return equivalent logical page name
+     * @throws IllegalArgumentException
+     *             if the name can not be resolved
+     */
+    String resolvePageClassNameToPageName(String pageClassName);
 
     /**
      * Converts a component type (a partial component name such as might be used inside a template

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/LinkFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/LinkFactory.java?view=auto&rev=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/LinkFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/LinkFactory.java Tue Oct 10 20:40:09 2006
@@ -0,0 +1,28 @@
+package org.apache.tapestry.services;
+
+import org.apache.tapestry.Link;
+
+/**
+ * A source for {@link Link} objects.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface LinkFactory
+{
+    /**
+     * Creates a stateful action link. Action links are built for components. Action links are
+     * encoded by the current request (that is, bound to the current request's session, if any).
+     * 
+     * @param component
+     *            a component instance
+     * @param action
+     *            a name associated with the action
+     * @param forForm
+     *            true if the link is for a form, false otherwise
+     * @param context
+     *            Additional path data, each value will be converted to a string and appended to the
+     *            URI
+     * @return a link
+     */
+    Link createActionLink(Object component, String action, boolean forForm, Object... context);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java Tue Oct 10 20:40:09 2006
@@ -746,4 +746,13 @@
             }
         };
     }
+
+    /** Service used to create links for components and pages. */
+
+    public LinkFactory buildLinkFactory(@InjectService("WebRequest")
+    WebRequest request, @InjectService("WebResponse")
+    WebResponse response)
+    {
+        return null;
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebRequest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebRequest.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebRequest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebRequest.java Tue Oct 10 20:40:09 2006
@@ -45,4 +45,12 @@
      * @return
      */
     String getPath();
+
+    /**
+     * Returns the context path portion of the URI. This always starts with a "/" character and does
+     * not end with one, with the exception of servlets in the root context, which return the empty
+     * string.
+     * 
+     */
+    String getContextPath();
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebResponse.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebResponse.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebResponse.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/WebResponse.java Tue Oct 10 20:40:09 2006
@@ -29,4 +29,13 @@
      * commit the output.
      */
     PrintWriter getPrintWriter() throws IOException;
+
+    /**
+     * Encodes the URL, ensuring that a session id is included (if a session exists, and as
+     * necessary depending on the client browser's use of cookies).
+     * 
+     * @param URL
+     * @return the same URL or a different one with additional information to track the user session
+     */
+    String encodeURL(String URL);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/CollectionFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/CollectionFactory.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/CollectionFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/CollectionFactory.java Tue Oct 10 20:40:09 2006
@@ -61,7 +61,7 @@
     }
 
     /** Contructs a new {@link HashSet} and initializes it using the provided collection. */
-    public static <T> Set<T> newSet(Collection<T> values)
+    public static <T> Set<T> newSet(Collection<? extends T> values)
     {
         return new HashSet<T>(values);
     }
@@ -69,7 +69,7 @@
     /**
      * Constructs a new {@link java.util.HashMap} instance by copying an existing Map instance.
      */
-    public static <K, V> Map<K, V> newMap(Map<K, V> map)
+    public static <K, V> Map<K, V> newMap(Map<? extends K, ? extends V> map)
     {
         return new HashMap<K, V>(map);
     }
@@ -94,8 +94,8 @@
         return new LinkedList<T>();
     }
 
-    /** Constructs and returns a new {@link ArrayList} as a copy of the provided list. */
-    public static <T> List<T> newList(Collection<T> list)
+    /** Constructs and returns a new {@link ArrayList} as a copy of the provided collection. */
+    public static <T> List<T> newList(Collection<? extends T> list)
     {
         return new ArrayList<T>(list);
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/Defense.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/Defense.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/Defense.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/Defense.java Tue Oct 10 20:40:09 2006
@@ -74,4 +74,28 @@
 
         throw new IllegalArgumentException(UtilMessages.parameterWasBlank(parameterName));
     }
+
+    /**
+     * Checks that the provided value is not null, and may be cast to the desired type.
+     * 
+     * @param <T>
+     * @param parameterValue
+     * @param type
+     * @param parameterName
+     * @throws IllegalArgumentException
+     *             if the value is null, or is not assignable to the indicated type
+     * @return the casted value
+     */
+    public static <T> T cast(Object parameterValue, Class<T> type, String parameterName)
+    {
+        notNull(parameterValue, parameterName);
+
+        if (!type.isInstance(parameterValue))
+            throw new IllegalArgumentException(UtilMessages.badCast(
+                    parameterName,
+                    parameterValue,
+                    type));
+
+        return type.cast(parameterValue);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/UtilMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/UtilMessages.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/UtilMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/util/UtilMessages.java Tue Oct 10 20:40:09 2006
@@ -37,4 +37,9 @@
     {
         return MESSAGES.format("parameter-was-blank", parameterName);
     }
+
+    static String badCast(String parameterName, Object parameterValue, Class type)
+    {
+        return MESSAGES.format("bad-cast", parameterName, parameterValue, type.getName());
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Tue Oct 10 20:40:09 2006
@@ -49,4 +49,5 @@
 binding-source-failure=Could not convert '%s' into a component parameter binding: %s
 no-coercion-found=Could not find a coercion from type %s to type %s.  Available coercions: %s.
 unable-to-resolve-component-type=Unable to resolve component '%s' to a component class name.
-page-does-not-exist=Page '%s' is not defined by this application.
\ No newline at end of file
+page-does-not-exist=Page '%s' is not defined by this application.
+page-name-unresolved=Unable to resolve class name %s to a logical page name.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/util/UtilStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/util/UtilStrings.properties?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/util/UtilStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/util/UtilStrings.properties Tue Oct 10 20:40:09 2006
@@ -13,4 +13,5 @@
 # limitations under the License.
 
 parameter-was-null=Parameter %s was null.
-parameter-was-blank=Parameter %s was null or contained only whitespace.
\ No newline at end of file
+parameter-was-blank=Parameter %s was null or contained only whitespace.
+bad-cast=Parameter %s (%s) is not assignable to type %s.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/HelloWorld.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/HelloWorld.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/HelloWorld.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/HelloWorld.java Tue Oct 10 20:40:09 2006
@@ -24,7 +24,7 @@
     @BeginRender
     void renderMessage(MarkupWriter writer)
     {
-        writer.write("Bonjour from HelloWorld component.");
+        writer.write("Why wait?  Make changes here and see them immediately!");
     }
 
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java Tue Oct 10 20:40:09 2006
@@ -69,6 +69,24 @@
     }
 
     @Test
+    public void class_name_to_simple_page_name()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source);
+
+        assertEquals(
+                resolver.resolvePageClassNameToPageName(APP_ROOT_PACKAGE + ".pages.SimplePage"),
+                "SimplePage");
+
+        verify();
+    }
+
+    @Test
     public void resolved_page_names_are_cached()
     {
         ComponentInstantiatorSource source = newComponentInstantiatorSource();
@@ -131,6 +149,52 @@
     }
 
     @Test
+    public void page_class_name_resolved_to_core_page()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        assertEquals(
+                resolver.resolvePageClassNameToPageName(CORE_ROOT_PACKAGE + ".pages.CorePage"),
+                "core/CorePage");
+
+        verify();
+    }
+
+    @Test
+    public void resolved_logical_page_names_are_cached()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        String className = CORE_ROOT_PACKAGE + ".pages.CorePage";
+
+        String logicalName1 = resolver.resolvePageClassNameToPageName(className);
+        String logicalName2 = resolver.resolvePageClassNameToPageName(className);
+
+        // Given that the value is computed on the fly, if its the same (not just equal, but the
+        // same), that proves that the value was cached.
+
+        assertSame(logicalName2, logicalName1);
+
+        verify();
+    }
+
+    @Test
     public void page_found_in_library()
     {
         ComponentInstantiatorSource source = newComponentInstantiatorSource();
@@ -148,6 +212,84 @@
 
         assertEquals(resolver.resolvePageNameToClassName("lib/LibPage"), LIB_ROOT_PACKAGE
                 + ".pages.LibPage");
+
+        verify();
+    }
+
+    @Test
+    public void class_name_resolves_to_folder_under_library()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, LIB_ROOT_PACKAGE);
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(LIB_PREFIX,
+                LIB_ROOT_PACKAGE), new LibraryMapping(CORE_PREFIX, CORE_ROOT_PACKAGE));
+
+        assertEquals(resolver.resolvePageClassNameToPageName(LIB_ROOT_PACKAGE
+                + ".pages.foo.bar.LibPage"), LIB_PREFIX + "/foo/bar/LibPage");
+
+        verify();
+    }
+
+    @Test
+    public void class_name_does_not_resolve_to_page_name()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        String className = LIB_ROOT_PACKAGE + ".pages.LibPage";
+
+        try
+        {
+            resolver.resolvePageClassNameToPageName(className);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(ex.getMessage(), "Unable to resolve class name " + className
+                    + " to a logical page name.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void class_name_not_in_a_pages_package()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        String className = CORE_ROOT_PACKAGE + ".foo.CorePage";
+
+        try
+        {
+            resolver.resolvePageClassNameToPageName(className);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(ex.getMessage(), "Unable to resolve class name " + className
+                    + " to a logical page name.");
+        }
 
         verify();
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java Tue Oct 10 20:40:09 2006
@@ -115,6 +115,8 @@
         ParameterModel pmodel = newParameterModel();
         Location l = newLocation();
 
+        train_getNestedId(container, null);
+
         train_getParameterNames(model, "wilma", "barney", "fred");
         train_getParameterModel(model, "wilma", pmodel);
         train_isRequired(pmodel, true);
@@ -147,6 +149,12 @@
         verify();
     }
 
+    protected void train_getNestedId(ComponentPageElement container, String nestedId)
+    {
+        container.getNestedId();
+        setReturnValue(nestedId);
+    }
+
     @Test
     public void is_invariant()
     {
@@ -239,8 +247,6 @@
         Instantiator ins = newInstantiator(component);
         ComponentModel model = newComponentModel();
 
-        train_getName(page, "foo.Bar");
-
         replay();
 
         ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, model, null);
@@ -252,10 +258,11 @@
         }
         catch (IllegalArgumentException ex)
         {
-            assertEquals(
-                    ex.getMessage(),
-                    "Component foo.Bar does not contain an embedded component with id 'unknown'.");
+            assertEquals(ex.getMessage(), "Component " + component.getClass().getName()
+                    + " does not contain an embedded component with id 'unknown'.");
         }
+
+        verify();
     }
 
     @Test

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/util/DefenseTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/util/DefenseTest.java?view=diff&rev=462683&r1=462682&r2=462683
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/util/DefenseTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/util/DefenseTest.java Tue Oct 10 20:40:09 2006
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry.util;
 
+import static org.apache.tapestry.util.Defense.cast;
 import static org.apache.tapestry.util.Defense.notBlank;
 import static org.apache.tapestry.util.Defense.notNull;
 
@@ -85,5 +86,47 @@
     public void non_blank_parameter_is_valid()
     {
         assertEquals("fred", notBlank(" fred\n", "biff"));
+    }
+
+    @Test
+    public void cast_is_also_not_null()
+    {
+        try
+        {
+            cast(null, String.class, "fred");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(ex.getMessage(), "Parameter fred was null.");
+        }
+    }
+
+    @Test
+    public void succesful_cast()
+    {
+        StringBuffer b = new StringBuffer();
+
+        Appendable a = cast(b, Appendable.class, "fred");
+
+        assertSame(a, b);
+    }
+
+    @Test
+    public void bad_cast()
+    {
+        StringBuffer b = new StringBuffer("fred-value");
+
+        try
+        {
+            cast(b, String.class, "fred");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Parameter fred (fred-value) is not assignable to type java.lang.String.");
+        }
     }
 }