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
>

Reply via email to