I committed it. Thanks. On Tue, Dec 16, 2008 at 9:33 PM, Matthew Hanlon <[email protected]> wrote:
> No, I do not have commit rights. I have created an issue on JIRA and > attached the project with the classes added. > http://wicketstuff.org/jira/browse/WSMINIS-8 > > Regards, > Matthew. > > On Tue, Dec 16, 2008 at 4:08 PM, Jeremy Thomerson < > [email protected] > > wrote: > > > 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 > > > > > > -- > 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
