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 2010/04/19 15:20:32 UTC

svn commit: r935554 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/internal/services/ main/java/org/apache/tapestry5/services/ test/java/org/apache/tapestry5/internal/services/

Author: hlship
Date: Mon Apr 19 13:20:32 2010
New Revision: 935554

URL: http://svn.apache.org/viewvc?rev=935554&view=rev
Log:
Change ComponentClassResolver service to expose the library mappings it creates.

Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentClassResolverImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java?rev=935554&r1=935553&r2=935554&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java Mon Apr 19 13:20:32 2010
@@ -44,7 +44,7 @@ public class ComponentClassResolverImpl 
     private final ClassNameLocator classNameLocator;
 
     private final String appRootPackage;
-    
+
     private final String startPageName;
 
     // Map from folder name to a list of root package names.
@@ -105,7 +105,7 @@ public class ComponentClassResolverImpl 
     @Inject
     @Symbol(InternalConstants.TAPESTRY_APP_PACKAGE_PARAM)
     String appRootPackage,
-    
+
     @Inject
     @Symbol(SymbolConstants.START_PAGE_NAME)
     String startPageName,
@@ -578,4 +578,88 @@ public class ComponentClassResolverImpl 
             }
         });
     }
+
+    public Map<String, String> getFolderToPackageMapping()
+    {
+        Map<String, String> result = CollectionFactory.newCaseInsensitiveMap();
+
+        for (String folder : mappings.keySet())
+        {
+            List<String> packageNames = mappings.get(folder);
+
+            String packageName = findCommonPackageNameForFolder(folder, packageNames);
+
+            result.put(folder, packageName);
+        }
+
+        return result;
+    }
+
+    static String findCommonPackageNameForFolder(String folder, List<String> packageNames)
+    {
+        String packageName = findCommonPackageName(packageNames);
+
+        if (packageName == null)
+            throw new RuntimeException(
+                    String
+                            .format(
+                                    "Package names for library folder '%s' (%s) can not be reduced to a common base package (of at least two terms).",
+                                    folder, InternalUtils.joinSorted(packageNames)));
+        return packageName;
+    }
+
+    static String findCommonPackageName(List<String> packageNames)
+    {
+        // BTW, this is what reduce is for in Clojure ...
+
+        String commonPackageName = packageNames.get(0);
+
+        for (int i = 1; i < packageNames.size(); i++)
+        {
+            commonPackageName = findCommonPackageName(commonPackageName, packageNames.get(i));
+
+            if (commonPackageName == null)
+                break;
+        }
+
+        return commonPackageName;
+    }
+
+    static String findCommonPackageName(String commonPackageName, String packageName)
+    {
+        String[] commonExploded = explode(commonPackageName);
+        String[] exploded = explode(packageName);
+
+        int count = Math.min(commonExploded.length, exploded.length);
+
+        int commonLength = 0;
+        int commonTerms = 0;
+
+        for (int i = 0; i < count; i++)
+        {
+            if (exploded[i].equals(commonExploded[i]))
+            {
+                // Keep track of the number of shared characters (including the dot seperators)
+
+                commonLength += exploded[i].length() + (i == 0 ? 0 : 1);
+                commonTerms++;
+            }
+            else
+            {
+                break;
+            }
+        }
+
+        if (commonTerms < 2)
+            return null;
+
+        return commonPackageName.substring(0, commonLength);
+    }
+
+    private static final Pattern DOT = Pattern.compile("\\.");
+
+    private static String[] explode(String packageName)
+    {
+        return DOT.split(packageName);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java?rev=935554&r1=935553&r2=935554&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentClassResolver.java Mon Apr 19 13:20:32 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2010 The Apache Software Foundation
 //
 // Licensed 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
+// 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,
@@ -14,9 +14,12 @@
 
 package org.apache.tapestry5.services;
 
-import org.apache.tapestry5.ioc.services.ClassNameLocator;
-
 import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.ioc.annotations.UsesConfiguration;
+import org.apache.tapestry5.ioc.services.ClassNameLocator;
 
 /**
  * Resolves page names and component types to fully qualified class names. Pages and components may be provided by the
@@ -31,68 +34,91 @@ import java.util.List;
  * maps from logical page name, component type, or mixin type to fully qualified class name.
  * <p/>
  * Certain ambiguities occur if mapped packages overlap, either in terms of the the prefixes or the package names. Keep
- * things clearly seperate to avoid lookup problems.
+ * things clearly separate to avoid lookup problems.
  */
+@UsesConfiguration(LibraryMapping.class)
 public interface ComponentClassResolver
 {
     /**
      * Converts a logical page name (such as might be encoded into a URL) into a fully qualified class name. The case of
      * the page name is irrelevant.
-     *
-     * @param pageName page name
+     * 
+     * @param pageName
+     *            page name
      * @return fully qualified class name for the page
-     * @throws IllegalArgumentException if the name does not match a known page class
+     * @throws IllegalArgumentException
+     *             if the name does not match a known page class
      */
     String resolvePageNameToClassName(String pageName);
 
     /**
      * For a particular path, determines if the path is a logical page name. The check is case insensitive.
-     *
-     * @param pageName potential logical page name
+     * 
+     * @param pageName
+     *            potential logical page name
      * @return true if the page name is valid
      */
     boolean isPageName(String pageName);
 
     /**
-     * Returns a list of all  page names, in sorted order.
+     * Returns a list of all page names, in sorted order.
      */
     List<String> getPageNames();
 
     /**
      * Converts a fully qualified page class name into a page name (often, for inclusion as part of the URI). This value
      * may later be passed to {@link #resolvePageNameToClassName(String)}.
-     *
-     * @param pageClassName fully qualified name of a page class
+     * 
+     * @param pageClassName
+     *            fully qualified name of a page class
      * @return equivalent logical page name
-     * @throws IllegalArgumentException if the name can not be resolved
+     * @throws IllegalArgumentException
+     *             if the name can not be resolved
      */
     String resolvePageClassNameToPageName(String pageClassName);
 
     /**
      * Returns the canonical form of a page name. The canonical form uses character case matching the underlying class
      * name.
-     *
-     * @throws IllegalArgumentException if the page name does not match a logical page name
+     * 
+     * @throws IllegalArgumentException
+     *             if the page name does not match a logical page name
      */
     String canonicalizePageName(String pageName);
 
     /**
      * Converts a component type (a logical component name such as might be used inside a template or annotation) into a
      * fully qualified class name. Case is ignored in resolving the name.
-     *
-     * @param componentType a logical component type
+     * 
+     * @param componentType
+     *            a logical component type
      * @return fully qualified class name
-     * @throws IllegalArgumentException if the component type can not be resolved
+     * @throws IllegalArgumentException
+     *             if the component type can not be resolved
      */
     String resolveComponentTypeToClassName(String componentType);
 
     /**
      * Converts a logical mixin type (as with component types) into a fully qualified class name. Case is ignored when
      * resolving the name.
-     *
-     * @param mixinType a logical mixin type
+     * 
+     * @param mixinType
+     *            a logical mixin type
      * @return fully qualified class name
-     * @throws IllegalArgumentException if the mixin type can not be resolved
+     * @throws IllegalArgumentException
+     *             if the mixin type can not be resolved
      */
     String resolveMixinTypeToClassName(String mixinType);
+
+    /**
+     * A mapping from virtual folder name to a package name (used for converting classpath {@link Asset}s
+     * to client URLs). This is derived from the contributed {@link LibraryMapping}s.
+     * <p>
+     * It is allowed to contribute multiple root packages as a single folder name. In this case, the best common package
+     * name is used. For example, if both <code>com.example.main</code> and <code>com.example.extras</code> is mapped to
+     * folder "example", then the package mapping for "example" will be <code>com.example</code>.
+     * 
+     * @since 5.2.0
+     */
+    Map<String, String> getFolderToPackageMapping();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java?rev=935554&r1=935553&r2=935554&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/LibraryMapping.java Mon Apr 19 13:20:32 2010
@@ -4,7 +4,7 @@
 // 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
+// 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,
@@ -17,13 +17,21 @@ package org.apache.tapestry5.services;
 /**
  * Used to configure the {@link ComponentClassResolver}, to allow it to map prefixes to library root packages (the
  * application namespace is a special case of this). In each case, a prefix on the path is mapped to a package. Prefixes
- * should start and end with characters, such as "core". It is allowed for a prefix to contain a slash, though it is not
+ * should start and end with characters, such as "corelib". It is allowed for a prefix to contain a slash, though it is not
  * recommended.
  * <p/>
- * The root package name should have a number of sub-packages: <dl> <dt>pages</dt> <dd>contains named pages</dd>
- * <dt>components</dt> <dd>contains components</dd> <dt>mixins</dt> <dd>contains component mixins</dd> <dt>base</dt>
- * <dd>contains base classes</dd> </dl>
- *
+ * The root package name should have a number of sub-packages:
+ * <dl>
+ * <dt>pages</dt>
+ * <dd>contains named pages</dd>
+ * <dt>components</dt>
+ * <dd>contains components</dd>
+ * <dt>mixins</dt>
+ * <dd>contains component mixins</dd>
+ * <dt>base</dt>
+ * <dd>contains base classes</dd>
+ * </dl>
+ * 
  * @see org.apache.tapestry5.services.TapestryModule#contributeComponentClassResolver(org.apache.tapestry5.ioc.Configuration)
  */
 public final class LibraryMapping

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentClassResolverImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentClassResolverImplTest.java?rev=935554&r1=935553&r2=935554&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentClassResolverImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentClassResolverImplTest.java Mon Apr 19 13:20:32 2010
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.services.ClassNameLocator;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.services.ComponentClassResolver;
@@ -41,7 +42,6 @@ public class ComponentClassResolverImplT
 
     private static final String LIB_ROOT_PACKAGE = "org.example.lib";
 
-    
     private ComponentClassResolverImpl create(Logger logger, ComponentInstantiatorSource source,
             ClassNameLocator locator, LibraryMapping... mappings)
     {
@@ -156,7 +156,7 @@ public class ComponentClassResolverImplT
 
         verify();
     }
-    
+
     @Test
     public void canonicalize_start_page()
     {
@@ -171,10 +171,11 @@ public class ComponentClassResolverImplT
         train_locateComponentClassNames(locator, APP_ROOT_PACKAGE + ".pages", className);
 
         replay();
-        
+
         List<LibraryMapping> mappings = Arrays.asList();
 
-        ComponentClassResolver resolver = new ComponentClassResolverImpl(logger, source, locator, APP_ROOT_PACKAGE, "HomePage", mappings);
+        ComponentClassResolver resolver = new ComponentClassResolverImpl(logger, source, locator, APP_ROOT_PACKAGE,
+                "HomePage", mappings);
 
         assertEquals(resolver.canonicalizePageName("HomePage"), "HomePage");
         assertEquals(resolver.canonicalizePageName(""), "HomePage");
@@ -182,8 +183,6 @@ public class ComponentClassResolverImplT
 
         verify();
     }
-    
-    
 
     @Test
     public void page_name_in_subfolder()
@@ -926,4 +925,43 @@ public class ComponentClassResolverImplT
     {
         train_for_packages(source, APP_ROOT_PACKAGE);
     }
+
+    @Test
+    public void common_package_name()
+    {
+        List<String> packageNames = CollectionFactory.newList("org.example.app.main", "org.example.app.sub");
+
+        assertEquals(ComponentClassResolverImpl.findCommonPackageName(packageNames), "org.example.app");
+    }
+
+    @Test
+    public void common_package_name_for_single_package()
+    {
+        List<String> packageNames = CollectionFactory.newList("org.example.app.main");
+
+        assertEquals(ComponentClassResolverImpl.findCommonPackageName(packageNames), "org.example.app.main");
+    }
+
+    @Test
+    public void expect_failure_when_no_common_package()
+    {
+        List<String> packageNames = CollectionFactory.newList("org.example.app.main", "org.demo.app.sub");
+
+        // "org" isn't good enough, we expect at least two terms.
+
+        try
+        {
+
+            ComponentClassResolverImpl.findCommonPackageNameForFolder("fred", packageNames);
+
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Package names for library folder 'fred' (org.demo.app.sub, org.example.app.main) can not be reduced to a common base package (of at least two terms).");
+        }
+
+    }
 }