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 <[email protected]> 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
>