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

Reply via email to