You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2011/09/08 23:11:02 UTC
svn commit: r1166906 -
/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java
Author: hlship
Date: Thu Sep 8 21:11:01 2011
New Revision: 1166906
URL: http://svn.apache.org/viewvc?rev=1166906&view=rev
Log:
TAP5-1638: Reduce thread contention inside ComponentClassResolverImpl
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.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=1166906&r1=1166905&r2=1166906&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 Thu Sep 8 21:11:01 2011
@@ -14,20 +14,10 @@
package org.apache.tapestry5.internal.services;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Formatter;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Pattern;
-
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.internal.InternalConstants;
-import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.ClassNameLocator;
import org.apache.tapestry5.ioc.util.AvailableValues;
@@ -38,10 +28,19 @@ import org.apache.tapestry5.services.Lib
import org.apache.tapestry5.services.transform.ControlledPackageType;
import org.slf4j.Logger;
+import java.util.*;
+import java.util.regex.Pattern;
+
public class ComponentClassResolverImpl implements ComponentClassResolver, InvalidationListener
{
private static final String CORE_LIBRARY_PREFIX = "core/";
+ private static final Pattern SPLIT_PACKAGE_PATTERN = Pattern.compile("\\.");
+
+ private static final Pattern SPLIT_FOLDER_PATTERN = Pattern.compile("/");
+
+ private static final int LOGICAL_NAME_BUFFER_SIZE = 40;
+
private final Logger logger;
private final ClassNameLocator classNameLocator;
@@ -62,51 +61,183 @@ public class ComponentClassResolverImpl
// structure of Tapestry, there should not be any reader threads while the write thread
// is operating.
- private boolean needsRebuild = true;
+ private volatile boolean needsRebuild = true;
- /**
- * Logical page name to class name.
- */
- private final Map<String, String> pageToClassName = CollectionFactory.newCaseInsensitiveMap();
+ private class Data
+ {
- /**
- * Component type to class name.
- */
- private final Map<String, String> componentToClassName = CollectionFactory.newCaseInsensitiveMap();
+ /**
+ * Logical page name to class name.
+ */
+ private final Map<String, String> pageToClassName = CollectionFactory.newCaseInsensitiveMap();
- /**
- * Mixing type to class name.
- */
- private final Map<String, String> mixinToClassName = CollectionFactory.newCaseInsensitiveMap();
+ /**
+ * Component type to class name.
+ */
+ private final Map<String, String> componentToClassName = CollectionFactory.newCaseInsensitiveMap();
- /**
- * Page class name to logical name (needed to build URLs). This one is case sensitive, since class names do always
- * have a particular case.
- */
- private final Map<String, String> pageClassNameToLogicalName = CollectionFactory.newMap();
+ /**
+ * Mixing type to class name.
+ */
+ private final Map<String, String> mixinToClassName = CollectionFactory.newCaseInsensitiveMap();
- /**
- * Used to convert a logical page name to the canonical form of the page name; this ensures that uniform case for
- * page names is used.
- */
- private final Map<String, String> pageNameToCanonicalPageName = CollectionFactory.newCaseInsensitiveMap();
+ /**
+ * Page class name to logical name (needed to build URLs). This one is case sensitive, since class names do always
+ * have a particular case.
+ */
+ private final Map<String, String> pageClassNameToLogicalName = CollectionFactory.newMap();
- private final ConcurrentBarrier barrier = new ConcurrentBarrier();
+ /**
+ * Used to convert a logical page name to the canonical form of the page name; this ensures that uniform case for
+ * page names is used.
+ */
+ private final Map<String, String> pageNameToCanonicalPageName = CollectionFactory.newCaseInsensitiveMap();
- private static final Pattern SPLIT_PACKAGE_PATTERN = Pattern.compile("\\.");
+ private void rebuild(String pathPrefix, String rootPackage)
+ {
+ fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.PAGES_SUBPACKAGE, pageToClassName);
+ fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.COMPONENTS_SUBPACKAGE, componentToClassName);
+ fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.MIXINS_SUBPACKAGE, mixinToClassName);
+ }
- private static final Pattern SPLIT_FOLDER_PATTERN = Pattern.compile("/");
+ private void fillNameToClassNameMap(String pathPrefix, String rootPackage, String subPackage,
+ Map<String, String> logicalNameToClassName)
+ {
+ String searchPackage = rootPackage + "." + subPackage;
+ boolean isPage = subPackage.equals(InternalConstants.PAGES_SUBPACKAGE);
- private static final int LOGICAL_NAME_BUFFER_SIZE = 40;
+ Collection<String> classNames = classNameLocator.locateClassNames(searchPackage);
+
+ int startPos = searchPackage.length() + 1;
+
+ for (String name : classNames)
+ {
+ String logicalName = toLogicalName(name, pathPrefix, startPos, true);
+ String unstrippedName = toLogicalName(name, pathPrefix, startPos, false);
+
+ if (isPage)
+ {
+ int lastSlashx = logicalName.lastIndexOf("/");
+
+ String lastTerm = lastSlashx < 0 ? logicalName : logicalName.substring(lastSlashx + 1);
+
+ if (lastTerm.equalsIgnoreCase("index") || lastTerm.equalsIgnoreCase(startPageName))
+ {
+ String reducedName = lastSlashx < 0 ? "" : logicalName.substring(0, lastSlashx);
+
+ // Make the super-stripped name another alias to the class.
+ // TAP5-1444: Everything else but a start page has precedence
+
+ if (!(lastTerm.equalsIgnoreCase(startPageName) && logicalNameToClassName.containsKey(reducedName)))
+ {
+ logicalNameToClassName.put(reducedName, name);
+ pageNameToCanonicalPageName.put(reducedName, logicalName);
+ }
+ }
+
+ pageClassNameToLogicalName.put(name, logicalName);
+ pageNameToCanonicalPageName.put(logicalName, logicalName);
+ pageNameToCanonicalPageName.put(unstrippedName, logicalName);
+ }
+
+ logicalNameToClassName.put(logicalName, name);
+ logicalNameToClassName.put(unstrippedName, name);
+ }
+ }
+
+ /**
+ * Converts a fully qualified class name to a logical name
+ *
+ * @param className fully qualified class name
+ * @param pathPrefix prefix to be placed on the logical name (to identify the library from in which the class
+ * lives)
+ * @param startPos start position within the class name to extract the logical name (i.e., after the final '.' in
+ * "rootpackage.pages.").
+ * @param stripTerms
+ * @return a short logical name in folder format ('.' replaced with '/')
+ */
+ private String toLogicalName(String className, String pathPrefix, int startPos, boolean stripTerms)
+ {
+ List<String> terms = CollectionFactory.newList();
+
+ addAll(terms, SPLIT_FOLDER_PATTERN, pathPrefix);
+
+ addAll(terms, SPLIT_PACKAGE_PATTERN, className.substring(startPos));
+
+ StringBuilder builder = new StringBuilder(LOGICAL_NAME_BUFFER_SIZE);
+ String sep = "";
+
+ String logicalName = terms.remove(terms.size() - 1);
+
+ String unstripped = logicalName;
+
+ for (String term : terms)
+ {
+ builder.append(sep);
+ builder.append(term);
+
+ sep = "/";
+
+ if (stripTerms)
+ logicalName = stripTerm(term, logicalName);
+ }
+
+ if (logicalName.equals(""))
+ logicalName = unstripped;
+
+ builder.append(sep);
+ builder.append(logicalName);
+
+ return builder.toString();
+ }
+
+ private void addAll(List<String> terms, Pattern splitter, String input)
+ {
+ for (String term : splitter.split(input))
+ {
+ if (term.equals(""))
+ continue;
+
+ terms.add(term);
+ }
+ }
+
+ private String stripTerm(String term, String logicalName)
+ {
+ if (isCaselessPrefix(term, logicalName))
+ {
+ logicalName = logicalName.substring(term.length());
+ }
+
+ if (isCaselessSuffix(term, logicalName))
+ {
+ logicalName = logicalName.substring(0, logicalName.length() - term.length());
+ }
+
+ return logicalName;
+ }
+
+ private boolean isCaselessPrefix(String prefix, String string)
+ {
+ return string.regionMatches(true, 0, prefix, 0, prefix.length());
+ }
+
+ private boolean isCaselessSuffix(String suffix, String string)
+ {
+ return string.regionMatches(true, string.length() - suffix.length(), suffix, 0, suffix.length());
+ }
+ }
+
+ private volatile Data data = new Data();
public ComponentClassResolverImpl(Logger logger,
- ClassNameLocator classNameLocator,
+ ClassNameLocator classNameLocator,
- @Symbol(SymbolConstants.START_PAGE_NAME)
- String startPageName,
+ @Symbol(SymbolConstants.START_PAGE_NAME)
+ String startPageName,
- Collection<LibraryMapping> mappings)
+ Collection<LibraryMapping> mappings)
{
this.logger = logger;
this.classNameLocator = classNameLocator;
@@ -165,45 +296,21 @@ public class ComponentClassResolverImpl
*/
public synchronized void objectWasInvalidated()
{
- barrier.withWrite(new Runnable()
- {
- public void run()
- {
- needsRebuild = true;
- }
- });
+ needsRebuild = true;
}
/**
* Invoked from within a withRead() block, checks to see if a rebuild is needed, and then performs the rebuild
* within a withWrite() block.
*/
- private void rebuild()
+ private Data getData()
{
if (!needsRebuild)
- return;
-
- barrier.withWrite(new Runnable()
{
- public void run()
- {
- performRebuild();
- }
- });
- }
-
- private void performRebuild()
- {
-
- Map<String, String> savedPages = CollectionFactory.newMap(pageToClassName);
- Map<String, String> savedComponents = CollectionFactory.newMap(componentToClassName);
- Map<String, String> savedMixins = CollectionFactory.newMap(mixinToClassName);
+ return data;
+ }
- pageToClassName.clear();
- componentToClassName.clear();
- mixinToClassName.clear();
- pageClassNameToLogicalName.clear();
- pageNameToCanonicalPageName.clear();
+ Data newData = new Data();
for (String prefix : mappings.keySet())
{
@@ -212,14 +319,23 @@ public class ComponentClassResolverImpl
String folder = prefix + "/";
for (String packageName : packages)
- rebuild(folder, packageName);
+ newData.rebuild(folder, packageName);
}
- showChanges("pages", savedPages, pageToClassName);
- showChanges("components", savedComponents, componentToClassName);
- showChanges("mixins", savedMixins, mixinToClassName);
+ showChanges("pages", data.pageToClassName, newData.pageToClassName);
+ showChanges("components", data.componentToClassName, newData.componentToClassName);
+ showChanges("mixins", data.mixinToClassName, newData.mixinToClassName);
needsRebuild = false;
+
+ data = newData;
+
+ return data;
+ }
+
+ private static int countUnique(Map<String, String> map)
+ {
+ return CollectionFactory.newSet(map.values()).size();
}
private void showChanges(String title, Map<String, String> savedMap, Map<String, String> newMap)
@@ -230,6 +346,7 @@ public class ComponentClassResolverImpl
Map<String, String> core = CollectionFactory.newMap();
Map<String, String> nonCore = CollectionFactory.newMap();
+
int maxLength = 0;
// Pass # 1: Get all the stuff in the core library
@@ -245,8 +362,7 @@ public class ComponentClassResolverImpl
maxLength = Math.max(maxLength, key.length());
core.put(key, newMap.get(name));
- }
- else
+ } else
{
maxLength = Math.max(maxLength, name.length());
@@ -263,7 +379,17 @@ public class ComponentClassResolverImpl
StringBuilder builder = new StringBuilder(2000);
Formatter f = new Formatter(builder);
- f.format("Available %s:\n", title);
+ int oldCount = countUnique(savedMap);
+ int newCount = countUnique(newMap);
+
+ f.format("Available %s (%d", title, newCount);
+
+ if (oldCount > 0 && oldCount != newCount)
+ {
+ f.format(", +%d", newCount - oldCount);
+ }
+
+ builder.append("):\n");
String formatString = "%" + maxLength + "s: %s\n";
@@ -282,215 +408,57 @@ public class ComponentClassResolverImpl
logger.info(builder.toString());
}
- private void rebuild(String pathPrefix, String rootPackage)
- {
- fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.PAGES_SUBPACKAGE, pageToClassName);
- fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.COMPONENTS_SUBPACKAGE, componentToClassName);
- fillNameToClassNameMap(pathPrefix, rootPackage, InternalConstants.MIXINS_SUBPACKAGE, mixinToClassName);
- }
-
- private void fillNameToClassNameMap(String pathPrefix, String rootPackage, String subPackage,
- Map<String, String> logicalNameToClassName)
- {
- String searchPackage = rootPackage + "." + subPackage;
- boolean isPage = subPackage.equals(InternalConstants.PAGES_SUBPACKAGE);
-
- Collection<String> classNames = classNameLocator.locateClassNames(searchPackage);
- int startPos = searchPackage.length() + 1;
-
- for (String name : classNames)
- {
- String logicalName = toLogicalName(name, pathPrefix, startPos, true);
- String unstrippedName = toLogicalName(name, pathPrefix, startPos, false);
-
- if (isPage)
- {
- int lastSlashx = logicalName.lastIndexOf("/");
-
- String lastTerm = lastSlashx < 0 ? logicalName : logicalName.substring(lastSlashx + 1);
-
- if (lastTerm.equalsIgnoreCase("index") || lastTerm.equalsIgnoreCase(startPageName))
- {
- String reducedName = lastSlashx < 0 ? "" : logicalName.substring(0, lastSlashx);
-
- // Make the super-stripped name another alias to the class.
- // TAP5-1444: Everything else but a start page has precedence
-
- if (!(lastTerm.equalsIgnoreCase(startPageName) && logicalNameToClassName.containsKey(reducedName)))
- {
- logicalNameToClassName.put(reducedName, name);
- pageNameToCanonicalPageName.put(reducedName, logicalName);
- }
- }
-
- pageClassNameToLogicalName.put(name, logicalName);
- pageNameToCanonicalPageName.put(logicalName, logicalName);
- pageNameToCanonicalPageName.put(unstrippedName, logicalName);
- }
-
- logicalNameToClassName.put(logicalName, name);
- logicalNameToClassName.put(unstrippedName, name);
- }
- }
-
- /**
- * Converts a fully qualified class name to a logical name
- *
- * @param className
- * fully qualified class name
- * @param pathPrefix
- * prefix to be placed on the logical name (to identify the library from in which the class
- * lives)
- * @param startPos
- * start position within the class name to extract the logical name (i.e., after the final '.' in
- * "rootpackage.pages.").
- * @param stripTerms
- * @return a short logical name in folder format ('.' replaced with '/')
- */
- private String toLogicalName(String className, String pathPrefix, int startPos, boolean stripTerms)
- {
- List<String> terms = CollectionFactory.newList();
-
- addAll(terms, SPLIT_FOLDER_PATTERN, pathPrefix);
-
- addAll(terms, SPLIT_PACKAGE_PATTERN, className.substring(startPos));
-
- StringBuilder builder = new StringBuilder(LOGICAL_NAME_BUFFER_SIZE);
- String sep = "";
-
- String logicalName = terms.remove(terms.size() - 1);
-
- String unstripped = logicalName;
-
- for (String term : terms)
- {
- builder.append(sep);
- builder.append(term);
-
- sep = "/";
-
- if (stripTerms)
- logicalName = stripTerm(term, logicalName);
- }
-
- if (logicalName.equals(""))
- logicalName = unstripped;
-
- builder.append(sep);
- builder.append(logicalName);
-
- return builder.toString();
- }
-
- private void addAll(List<String> terms, Pattern splitter, String input)
+ public String resolvePageNameToClassName(final String pageName)
{
- for (String term : splitter.split(input))
- {
- if (term.equals(""))
- continue;
-
- terms.add(term);
- }
- }
+ Data data = getData();
- private String stripTerm(String term, String logicalName)
- {
- if (isCaselessPrefix(term, logicalName))
- {
- logicalName = logicalName.substring(term.length());
- }
+ String result = locate(pageName, data.pageToClassName);
- if (isCaselessSuffix(term, logicalName))
+ if (result == null)
{
- logicalName = logicalName.substring(0, logicalName.length() - term.length());
+ throw new UnknownValueException(String.format("Unable to resolve '%s' to a page class name.",
+ pageName), new AvailableValues("Page names", presentableNames(data.pageToClassName)));
}
- return logicalName;
- }
-
- private boolean isCaselessPrefix(String prefix, String string)
- {
- return string.regionMatches(true, 0, prefix, 0, prefix.length());
- }
-
- private boolean isCaselessSuffix(String suffix, String string)
- {
- return string.regionMatches(true, string.length() - suffix.length(), suffix, 0, suffix.length());
- }
-
- public String resolvePageNameToClassName(final String pageName)
- {
- return barrier.withRead(new Invokable<String>()
- {
- public String invoke()
- {
- String result = locate(pageName, pageToClassName);
-
- if (result == null)
- throw new UnknownValueException(String.format("Unable to resolve '%s' to a page class name.",
- pageName), new AvailableValues("Page names", presentableNames(pageToClassName)));
-
- return result;
- }
- });
+ return result;
}
public boolean isPageName(final String pageName)
{
- return barrier.withRead(new Invokable<Boolean>()
- {
- public Boolean invoke()
- {
- return locate(pageName, pageToClassName) != null;
- }
- });
+ return locate(pageName, getData().pageToClassName) != null;
}
public boolean isPage(final String pageClassName)
{
- return barrier.withRead(new Invokable<Boolean>()
- {
- public Boolean invoke()
- {
- return locate(pageClassName, pageClassNameToLogicalName) != null;
- }
- });
+ return locate(pageClassName, getData().pageClassNameToLogicalName) != null;
}
public List<String> getPageNames()
{
- return barrier.withRead(new Invokable<List<String>>()
- {
- public List<String> invoke()
- {
- rebuild();
+ Data data = getData();
- List<String> result = CollectionFactory.newList(pageClassNameToLogicalName.values());
+ List<String> result = CollectionFactory.newList(data.pageClassNameToLogicalName.values());
- Collections.sort(result);
+ Collections.sort(result);
- return result;
- }
- });
+ return result;
}
public String resolveComponentTypeToClassName(final String componentType)
{
- return barrier.withRead(new Invokable<String>()
- {
- public String invoke()
- {
- String result = locate(componentType, componentToClassName);
+ Data data = getData();
- if (result == null)
- throw new UnknownValueException(String.format("Unable to resolve '%s' to a component class name.",
- componentType), new AvailableValues("Component types",
- presentableNames(componentToClassName)));
+ String result = locate(componentType, data.componentToClassName);
- return result;
- }
- });
+ if (result == null)
+ {
+ throw new UnknownValueException(String.format("Unable to resolve '%s' to a component class name.",
+ componentType), new AvailableValues("Component types",
+ presentableNames(data.componentToClassName)));
+ }
+
+ return result;
}
Collection<String> presentableNames(Map<String, ?> map)
@@ -514,79 +482,67 @@ public class ComponentClassResolverImpl
public String resolveMixinTypeToClassName(final String mixinType)
{
- return barrier.withRead(new Invokable<String>()
- {
- public String invoke()
- {
- String result = locate(mixinType, mixinToClassName);
+ Data data = getData();
- if (result == null)
- throw new UnknownValueException(String.format("Unable to resolve '%s' to a mixin class name.",
- mixinType), new AvailableValues("Mixin types", presentableNames(mixinToClassName)));
+ String result = locate(mixinType, data.mixinToClassName);
- return result;
- }
- });
+ if (result == null)
+ {
+ throw new UnknownValueException(String.format("Unable to resolve '%s' to a mixin class name.",
+ mixinType), new AvailableValues("Mixin types", presentableNames(data.mixinToClassName)));
+ }
+
+ return result;
}
/**
* Locates a class name within the provided map, given its logical name. If not found naturally, a search inside the
* "core" library is included.
- *
- * @param logicalName
- * name to search for
- * @param logicalNameToClassName
- * mapping from logical name to class name
+ *
+ * @param logicalName name to search for
+ * @param logicalNameToClassName mapping from logical name to class name
* @return the located class name or null
*/
private String locate(String logicalName, Map<String, String> logicalNameToClassName)
{
- rebuild();
-
String result = logicalNameToClassName.get(logicalName);
// If not found, see if it exists under the core package. In this way,
// anything in core is "inherited" (but overridable) by the application.
- if (result == null)
- result = logicalNameToClassName.get(CORE_LIBRARY_PREFIX + logicalName);
+ if (result != null)
+ {
+ return result;
+ }
- return result;
+ return logicalNameToClassName.get(CORE_LIBRARY_PREFIX + logicalName);
}
public String resolvePageClassNameToPageName(final String pageClassName)
{
- return barrier.withRead(new Invokable<String>()
- {
- public String invoke()
- {
- rebuild();
-
- String result = pageClassNameToLogicalName.get(pageClassName);
+ String result = getData().pageClassNameToLogicalName.get(pageClassName);
- if (result == null)
- throw new IllegalArgumentException(ServicesMessages.pageNameUnresolved(pageClassName));
+ if (result == null)
+ {
+ throw new IllegalArgumentException(ServicesMessages.pageNameUnresolved(pageClassName));
+ }
- return result;
- }
- });
+ return result;
}
public String canonicalizePageName(final String pageName)
{
- return barrier.withRead(new Invokable<String>()
- {
- public String invoke()
- {
- String result = locate(pageName, pageNameToCanonicalPageName);
+ Data data = getData();
- if (result == null)
- throw new UnknownValueException(String.format("Unable to resolve '%s' to a known page name.",
- pageName), new AvailableValues("Page names", presentableNames(pageNameToCanonicalPageName)));
+ String result = locate(pageName, data.pageNameToCanonicalPageName);
- return result;
- }
- });
+ if (result == null)
+ {
+ throw new UnknownValueException(String.format("Unable to resolve '%s' to a known page name.",
+ pageName), new AvailableValues("Page names", presentableNames(data.pageNameToCanonicalPageName)));
+ }
+
+ return result;
}
public Map<String, String> getFolderToPackageMapping()
@@ -652,8 +608,7 @@ public class ComponentClassResolverImpl
commonLength += exploded[i].length() + (i == 0 ? 0 : 1);
commonTerms++;
- }
- else
+ } else
{
break;
}