Do you have commit rights to wicketstuff? If you do, just commit it. If not, you could ask for it or create a patch on the JIRA: http://wicketstuff.org/jira/secure/BrowseProject.jspa?id=10020
If you need help, let us know. On Tue, Dec 16, 2008 at 3:37 PM, Matthew Hanlon <[email protected]> wrote: > 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 <[email protected]> > 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 < > > [email protected]> 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 <[email protected] > >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 < > >>> [email protected]> 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 <[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 > >>>>> > >>>> > >>>> > >>>> > >>>> > >>> > >>> > >>> -- > >>> 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 > -- Jeremy Thomerson http://www.wickettraining.com
