I just realized that gmail wasn't replying to the list.  Sorry.  For
posterity...

On Tue, Dec 16, 2008 at 3:34 PM, Matthew Hanlon <mrhan...@gmail.com> wrote:

> Below is the updated CaseInsensitiveClassResolver I implemented per your
> suggestions.  It uses MatchingResources from wicketstuff annotation.  It
> scans packages on-demand and caches the results for future lookup.  It seems
> fairly fast, and due to the cache it doesn't have to re-scan the classpath.
>  Also below is my CaseInsensitivePackageRequestTargetUrlCodingStrategy
> (Gasp!  What an awfully long name.).
> If you think it's worthwhile, I'd love to contribute it to
> wicketstuff-minis.  How would one got about doing that?
>
> public class CaseInsensitiveClassResolver implements IClassResolver {
>
> private static final Logger logger =
> LoggerFactory.getLogger(CaseInsensitiveClassResolver.class);
>  private DefaultClassResolver resolver = new DefaultClassResolver();
>  public Iterator<URL> getResources(String name) {
>  return resolver.getResources(name);
> }
>  private Map<String, Map<String, Class<?>>> cache = new HashMap<String,
> Map<String,Class<?>>>();
>  public Class<?> resolveClass(String classname) throws
> ClassNotFoundException {
> Class<?> clazz = null;
>  try {
> clazz = resolver.resolveClass(classname);
> } catch (ClassNotFoundException e1) {
>  clazz = resolveClassCaseInsensitive(classname);
> } catch (NoClassDefFoundError e2) {
>  clazz = resolveClassCaseInsensitive(classname);
> }
> if (clazz == null) {
>  throw new ClassNotFoundException("Unable to resolve class for name " +
> classname);
> }
>  return clazz;
> }
>  public Class<?> resolveClassCaseInsensitive(String classname) {
> if (logger.isDebugEnabled()) {
>  logger.debug("Class not found for " + classname + ".  Trying to look up
> case-insensitive.");
> }
>  String packageName = classname.substring(0, classname.lastIndexOf('.'));
>  if (! cache.containsKey(packageName)) {
> cache.put(packageName, scan(getPatternForPackage(packageName)));
>  }
>  return cache.get(packageName).get(classname.toLowerCase());
>  }
>  /**
>      * Get the Spring search pattern given a package name or part of a
> package name
>      * @param packageName a package name
>      * @return a Spring search pattern for the given package
>      */
>     public String getPatternForPackage(String packageName)
>     {
>         if (packageName == null) packageName = "";
>         packageName = packageName.replace('.', '/');
>         if (!packageName.endsWith("/"))
>         {
>             packageName += '/';
>         }
>
>         return "classpath*:" + packageName + "**/*.class";
>     }
>
>     /**
>      *
>      * @param pattern
>      */
>     private Map<String, Class<?>> scan(final String pattern) {
>      Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
>  MatchingResources resources = new MatchingResources(pattern);
>         MetadataReaderFactory f = new SimpleMetadataReaderFactory();
> for (Resource r : resources.getAllMatches()) {
>  MetadataReader meta = null;
>             try
>             {
>                 meta = f.getMetadataReader(r);
>             }
>             catch (IOException e)
>             {
>                 throw new RuntimeException("Unable to get MetadataReader
> for " + r, e);
>             }
>             try {
>             ClassMetadata cmd = meta.getClassMetadata();
>             String classname = cmd.getClassName();
>             try {
>              classMap.put(classname.toLowerCase(),
> getClass().getClassLoader().loadClass(classname));
>  } catch (ClassNotFoundException e) {
> logger.error("Error loading class for name " + classname);
>  }
>             } catch (Throwable e) {
> logger.error("Unknown Error.", e);
>             }
> }
> return classMap;
> }
>
> }
>
> public class CaseInsensitivePackageRequestTargetUrlCodingStrategy extends
> PackageRequestTargetUrlCodingStrategy {
>
> private static final Logger log =
> LoggerFactory.getLogger(CaseInsensitivePackageRequestTargetUrlCodingStrategy.class);
>  public CaseInsensitivePackageRequestTargetUrlCodingStrategy(final String
> path, PackageName packageName) {
> super(path, packageName);
>  this.packageName = packageName;
> }
>
> /** package for this mount. */
>  private final PackageName packageName;
>  private IClassResolver resolver = new CaseInsensitiveClassResolver();
>  /**
>  * @see
> org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy#decode(org.apache.wicket.request.RequestParameters)
>  */
> public IRequestTarget decode(RequestParameters requestParameters)
> {
>  String remainder =
> requestParameters.getPath().substring(getMountPath().length());
> final String parametersFragment;
>  int ix = remainder.indexOf('/', 1);
> if (ix == -1)
> {
>  ix = remainder.length();
> parametersFragment = "";
> }
>  else
> {
> parametersFragment = remainder.substring(ix);
>  }
>
> if (remainder.startsWith("/"))
> {
>  remainder = remainder.substring(1);
> ix--;
> }
>  else
> {
> // There is nothing after the mount path!
>  return null;
> }
>
> final String bookmarkablePageClassName = packageName + "." +
> remainder.substring(0, ix);
>  Class bookmarkablePageClass;
> try
> {
>  bookmarkablePageClass = resolver.resolveClass(bookmarkablePageClassName);
> }
>  catch (Exception e)
> {
> log.debug(e.getMessage());
>  return null;
> }
> PageParameters parameters = new
> PageParameters(decodeParameters(parametersFragment,
>  requestParameters.getParameters()));
>
> String pageMapName =
> (String)parameters.remove(WebRequestCodingStrategy.PAGEMAP);
>  pageMapName = WebRequestCodingStrategy.decodePageMapName(pageMapName);
> requestParameters.setPageMapName(pageMapName);
>
> // do some extra work for checking whether this is a normal request to a
> // bookmarkable page, or a request to a stateless page (in which case a
>  // wicket:interface parameter should be available
> final String interfaceParameter = (String)parameters
>  .remove(WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME);
>
> if (interfaceParameter != null)
>  {
> WebRequestCodingStrategy.addInterfaceParameters(interfaceParameter,
> requestParameters);
>  return new BookmarkableListenerInterfaceRequestTarget(pageMapName,
> bookmarkablePageClass, parameters, requestParameters.getComponentPath(),
>  requestParameters.getInterfaceName(),
> requestParameters.getVersionNumber());
> }
>  else
> {
> return new BookmarkablePageRequestTarget(pageMapName,
> bookmarkablePageClass, parameters);
>  }
> }
> }
>
>
>
> On Fri, Dec 12, 2008 at 10:57 AM, Jeremy Thomerson <
> jer...@wickettraining.com> wrote:
>
>> One other thing - I think that the contract of IClassResolver would mean
>> that rather than returning null, you throw a ClassNotFoundException.
>>
>>
>> On Fri, Dec 12, 2008 at 8:24 AM, Matthew Hanlon <mrhan...@gmail.com>wrote:
>>
>>> Great ideas, thanks for the input.  I agree on all points.  My initial
>>> implementation is certainly the naive approach, basically a proof of
>>> concept.  I'll look into what you mention in 4 and let you know what I find.
>>>
>>>
>>>
>>> On Fri, Dec 12, 2008 at 12:44 AM, Jeremy Thomerson <
>>> jer...@wickettraining.com> wrote:
>>>
>>>> Sounds like an interesting idea.  Here are a few thoughts I had after
>>>> seeing it.  Hopefully these are helpful.
>>>>
>>>> 1 - Say you had a page "CustomerAdminLoginPage" - this yields 4,194,304 
>>>> combinations!
>>>> Cache the result - either the class you found or the fact that you could 
>>>> not
>>>> find a class.  (you will have 2 to the nth power, where n equals the length
>>>> of the simple name)
>>>> 2 - DON'T use StringBuilder just to split it later - that's not what
>>>> it's for!  It's very slow and is constantly resizing it's internal array.
>>>> You could use something like the code I pasted below to use a single array,
>>>> initialized ahead of time to the proper size.
>>>>
>>>> 3 - I would suggest not even holding an array of possible combos -
>>>> longer class names take a ton of memory because of all the millions of
>>>> strings created.  If you must go through all combos to try to find a match,
>>>> just look for the match in your loop rather than looping to create an array
>>>> of combos and then re-looping to try to find a match.
>>>>
>>>> 4 - Now - I would suggest seeing if you can avoid looping through all
>>>> possible combos altogether.  Look at how Wicket Annotations (in Wicket
>>>> Stuff) does classpath scanning...  I would think that this would be much
>>>> more efficient - scan the package ahead of time and find all classes in the
>>>> package and cache their names.  Then just do a case-insensitive look into
>>>> your cache - this saves you all the memory and processing trouble of ever
>>>> computing all the combos and trying to load potentially millions of
>>>> non-existent classes.
>>>>
>>>> 5 - If you get this to work and work well, add it to wicketstuff-minis
>>>> or a similar project where others can easily use it - let me know if you
>>>> need help accomplishing that.
>>>>
>>>>
>>>> Here's an example of an improved method of finding the combos - probably
>>>> could still be improved considerably, but this is a significant improvement
>>>> over your first rough draft.  (Although see point 4 - I recommend not even
>>>> using this method at all)
>>>>
>>>>     private static int capsCombinations(String[] combos, String word,
>>>> int startIndex, int arrayIndex) {
>>>>         if (arrayIndex == 0) {
>>>>             word = word.toLowerCase();
>>>>             combos[arrayIndex++] = word;
>>>>         }
>>>>         if (arrayIndex == combos.length) {
>>>>             return arrayIndex;
>>>>         } else {
>>>>             while (startIndex < word.length()) {
>>>>                 char[] chars = word.toCharArray();
>>>>                 chars[startIndex] =
>>>> Character.toUpperCase(chars[startIndex]);
>>>>                 String string = String.valueOf(chars);
>>>>                 combos[arrayIndex++] = string;
>>>>                 arrayIndex = capsCombinations(combos, string,
>>>> ++startIndex, arrayIndex);
>>>>             }
>>>>             return arrayIndex;
>>>>         }
>>>>     }
>>>>
>>>>     public static void main(String[] args) throws Exception {
>>>>         long start = System.currentTimeMillis();
>>>>         String name = "CustomerAdminLoginPage";
>>>>         String[] combos = new String[(int) Math.pow(2, name.length())];
>>>>         capsCombinations(combos, name, 0, 0);
>>>>         System.out.print(combos.length + " combos - took ");
>>>>         System.out.print((System.currentTimeMillis() - start) + " millis
>>>> - used ");
>>>>         Runtime rt = Runtime.getRuntime();
>>>>         System.out.println((rt.totalMemory() / 1048576) + "MB of
>>>> memory");
>>>>     }
>>>>
>>>> --
>>>> Jeremy Thomerson
>>>> http://www.wickettraining.com
>>>>
>>>>
>>>> On Thu, Dec 11, 2008 at 2:55 PM, Matthew Hanlon <mrhan...@gmail.com>wrote:
>>>>
>>>>> I am looking for some feedback any may have on this:
>>>>> Let's say I've mounted a package "com.company.package" using
>>>>> PackageRequestTargetUrlCodingStrategy
>>>>> on "/foo." So I have several pages, /foo/Bar, /foo/Baz, etc. Now, I
>>>>> want my
>>>>> page mounts to be case-insensitive in the case that a user has caps
>>>>> lock on
>>>>> or types in all lower case or whatever. For
>>>>> PackageRequestTargetUrlCodingStrategy this works for the "/foo" part,
>>>>> but
>>>>> not the classname part, obviously.
>>>>>
>>>>> So I implemented a CaseInsensitiveClassResolver that delegates to a
>>>>> DefaultClassResolver. In the case that the DefaultClassResolver cannot
>>>>> find
>>>>> the class, the CaseInsensitiveClassResolver tries to load the class by
>>>>> trying different combinations of upper/lower case in the classname. So,
>>>>> for
>>>>> "bar" it would try to resolve "com.company.package.Bar," "
>>>>> com.company.package.bAr," "com.company.package.baR," etc, obviously
>>>>> finding
>>>>> "com.company.package.Bar" and returning that class.
>>>>>
>>>>> This works pretty well. Now, obviously it's not the most efficient
>>>>> thing,
>>>>> possibly having to catch several
>>>>> ClassNotFoundException/NoClassDefFoundError exceptions
>>>>> before finding the class (assuming the name exists and is spelled
>>>>> correctly,
>>>>> just with wrong case). But it might be better than returning a 404 on a
>>>>> page
>>>>> simply due to improper case. I wouldn't expect it to happen often, as
>>>>> more
>>>>> often than not the user will probably use a link the get to the pages,
>>>>> and
>>>>> so no typing at all. But in the rare case...
>>>>>
>>>>> So, any thoughts?
>>>>>
>>>>> Here's the code:
>>>>>
>>>>> public class CaseInsensitiveClassResolver implements IClassResolver {
>>>>>
>>>>> private static final Logger logger =
>>>>> LoggerFactory.getLogger(CaseInsensitiveClassResolver.class);
>>>>>  private DefaultClassResolver resolver = new DefaultClassResolver();
>>>>>  public Iterator<URL> getResources(String name) {
>>>>> return resolver.getResources(name);
>>>>> }
>>>>>
>>>>> public Class<?> resolveClass(String classname) {
>>>>> Class<?> clazz = null;
>>>>> try {
>>>>> clazz = resolver.resolveClass(classname);
>>>>> } catch (ClassNotFoundException e1) {
>>>>> clazz = resolveClassCaseInsensitive(classname);
>>>>> } catch (NoClassDefFoundError e2) {
>>>>> clazz = resolveClassCaseInsensitive(classname);
>>>>> }
>>>>> return clazz;
>>>>> }
>>>>>  public Class<?> resolveClassCaseInsensitive(String classname) throws
>>>>> ClassNotFoundException {
>>>>> if (logger.isDebugEnabled()) {
>>>>> logger.debug("Class not found for " + classname + ".  Trying to look up
>>>>> case-insensitive.");
>>>>> }
>>>>> String packageName = classname.substring(0,
>>>>> classname.lastIndexOf('.'));
>>>>> String simpleName = classname.substring(classname.lastIndexOf('.') +
>>>>> 1);
>>>>>  String combos = capsCombinations(simpleName.toLowerCase(), 0);
>>>>> Class<?> cls = null;
>>>>> for (String combo : combos.split(",")) {
>>>>> try {
>>>>> cls = resolver.resolveClass(packageName + "." + combo);
>>>>> } catch (ClassNotFoundException e1) {
>>>>> if (logger.isDebugEnabled()) {
>>>>> logger.debug("Class not found for " + packageName + "." + combo + ".");
>>>>> }
>>>>> } catch (NoClassDefFoundError e2) {
>>>>> if (logger.isDebugEnabled()) {
>>>>> logger.debug("Class not found for " + packageName + "." + combo + ".");
>>>>> }
>>>>> }
>>>>> if (cls != null) {
>>>>> if (logger.isDebugEnabled()) {
>>>>> logger.debug("Class found for " + packageName + "." + combo + ".");
>>>>> }
>>>>> return cls;
>>>>> }
>>>>> }
>>>>> return null;
>>>>> }
>>>>>
>>>>> private String capsCombinations(String word, int startIndex) {
>>>>> StringBuilder sb = new StringBuilder(word);
>>>>> if (word.equals(word.toUpperCase())) {
>>>>> return sb.toString();
>>>>> } else {
>>>>> for (; startIndex < word.length();) {
>>>>> char[] chars = word.toCharArray();
>>>>> chars[startIndex] = Character.toUpperCase(chars[startIndex]);
>>>>> sb.append(",");
>>>>> sb.append(capsCombinations(new String(chars), ++startIndex));
>>>>> }
>>>>> return sb.toString();
>>>>> }
>>>>> }
>>>>> }
>>>>> --
>>>>> Matthew Rollins Hanlon
>>>>> http://squareoftwo.org
>>>>> _____________________
>>>>> Hanlon's Razor:
>>>>> "Never attribute to malice that which can be adequately explained by
>>>>> stupidity."
>>>>> http://wikipedia.org/wiki/Hanlon's_razor
>>>>>
>>>>
>>>>
>>>>
>>>>
>>>
>>>
>>> --
>>>  Matthew Rollins Hanlon
>>> http://squareoftwo.org
>>> _____________________
>>> Hanlon's Razor:
>>> "Never attribute to malice that which can be adequately explained by
>>> stupidity."
>>> http://wikipedia.org/wiki/Hanlon's_razor
>>>
>>
>>
>>
>> --
>> Jeremy Thomerson
>> http://www.wickettraining.com
>>
>
>
>
> --
> Matthew Rollins Hanlon
> http://squareoftwo.org
> _____________________
> Hanlon's Razor:
> "Never attribute to malice that which can be adequately explained by
> stupidity."
> http://wikipedia.org/wiki/Hanlon's_razor
>



-- 
Matthew Rollins Hanlon
http://squareoftwo.org
_____________________
Hanlon's Razor:
"Never attribute to malice that which can be adequately explained by
stupidity."
http://wikipedia.org/wiki/Hanlon's_razor

Reply via email to