Hi,

IMHO this proposal is very easy to implement and use by specialized projects (e.g. wicket-pure-css).

I don't see how Wicket itself would benefit from the code:
It's just IComponentInstantiationListeners - anything more probably won't work for all use-cases anyway.

Regards
Sven


On 07/17/2014 03:25 PM, Martin Grigorov wrote:
Hi,

For some reason this feature doesn't seem that usable to me... Maybe other
devs and users will like it...

See some comments from me inline:

Martin Grigorov
Wicket Training and Consulting
https://twitter.com/mtgrigorov


On Sun, Jul 13, 2014 at 3:21 AM, Garret Wilson <gar...@globalmentor.com>
wrote:

On 7/12/2014 3:14 PM, Garret Wilson wrote:

... The approach I am proposing allows one to encapsulate a whole series
of behaviors and component associations within one "bundle".

Another benefit of "behavior bundles" is that in Wicket 7.x, legacy
behavior (such as wrapping disabled links in <em><span>) could easily be
included in a Wicket6BehaviorBundle. This would obviate the need for
one-off backwards-compatibility classes <https://issues.apache.org/
jira/browse/WICKET-4904?focusedCommentId=14059918&page=com.atlassian.jira.
plugin.system.issuetabpanels:comment-tabpanel#comment-14059918> such as
DisabledLinkBehavior.LinkInstantiationListener to be included in the main
framework.

But without further ado, here is the class itself. Please tell me what you
think about it. I'd like to contribute it. (I couldn't help but use a few
Google Guava classes---they are so useful, and I've grown accustomed to
having them handy, but if it's not possible to use them I can rip them out
before submitting a patch.) Note the documentation around multiple
registration of behaviors---I didn't know what Wicket allows in the way of
a behavior added multiple times, so I took a simple implementation and
documented it.

As a reminder, the bundle is used like this (but preferably someone would
create bundle subclasses to distribute separately):

     final BehaviorBundle pureCSSBehaviorBundle = new BehaviorBundle();
     pureCSSBehaviorBundle.registerBehavior(AbstractLink.class, new
DisabledClassAttributeAppender("pure-button-disabled"));
     getComponentInstantiationListeners().add(pureCSSBehaviorBundle);

After some discussion on the list, would it be appropriate for me to go
ahead and create a JIRA and then submit a patch? (I guess I'll be forced to
touch Git, now. ;) )

Code below. Cheers!

Garret

/**
  * A collection of behaviors associated with component classes. When added
to the application's collection of component
  * instantiation listeners, the designated behaviors will be registered
with the associated components and their subclasses. This
  * facilities grouping behaviors based upon functionality or library.
  * <p>
  * For example, the <a href="http://purecss.io/";>Yahoo! Pure CSS</a>
library requires that <a
  * href="http://purecss.io/buttons/";>disabled buttons</a> have the
<code>pure-button-disabled</code> class. A behavior bundle may
  * be created supporting this feature maybe be added as follows:
  * </p>
  *
  * <pre>
  * <code>BehaviorBundle pureCSSBehaviorBundle = new BehaviorBundle();
  * pureCSSBehaviorBundle.registerBehavior(AbstractLink.class, new
DisabledClassAttributeAppender("pure-button-disabled"));
  * getComponentInstantiationListeners().add(pureCSSBehaviorBundle);</code>
  * </pre>
  * <p>
  * Note that a production behavior bundle would likely create a {@link
BehaviorBundle} subclass containing the appropriate
  * behaviors to be distributed as a unit.
  * </p>
  * @author Garret Wilson
  * @see Application#getComponentInstantiationListeners()
  */
public class BehaviorBundle implements IComponentInstantiationListener {

   /** The lock governing multithreaded access to the registered component
behaviors. */
   final ReadWriteLock lock = new ReentrantReadWriteLock();

Usually Wicket apps add the IXyzListeners in Application#init(). So there
is no really need to use locking.
It is possible to add/remove listeners (and in your case register
behaviors) in the "business logic" (e.g. after clicking some link or
similar) but usually people don't do it. Or at least I haven't seen such
code.


   /** The map of behaviors, accessed under {@link #lock}. */
   private final ListMultimap<Class<? extends Component>, Behavior>
behaviorMap = ArrayListMultimap.create();

   /**
    * Registers a behavior with a component class for adding to each
component when it is instantiated.
    * <p>
    * Behaviors registered with a given class will be added to instances of
any child classes as well. For example, registering a
    * behavior for {@link AbstractLink} will result in the behavior being
added to instances of e.g. {@link BookmarkablePageLink}
    * as well.
    * </p>
    * <p>
    * Behaviors registered twice for one class, or at multiple places in
the hierarchy of a class, will consequently be added
    * multiple times to the same component.
    * </p>
    * @param componentClass The class and subclasses of which the behavior
should be registered.
    * @param behaviors The behavior to register for addition upon component
instantiation..
    * @return The bundle itself, to allow method call chaining.
    */
   public BehaviorBundle registerBehavior(final Class<? extends Component>
componentClass, final Behavior behavior) {

since you use Guava anyway I think it would be better to use Precondition
that accepts the Class instead of the Class itself. It will be much more
flexible in the future.


     lock.writeLock().lock();
     try {
       behaviorMap.put(checkNotNull(componentClass),
checkNotNull(behavior));
     } finally {
       lock.writeLock().unlock();
     }
     return this;
   }

   /**
    * {@inheritDoc} This implementation adds all behaviors registered with
the component's class and all parent classes up to and
    * including {@link Component}. Behaviors will be added in order from
the most general (close to {@link Component} to the most
    * specific (the component's actual class).
    */
   @Override
   public void onInstantiation(final Component component) {
     lock.readLock().lock();
     try {
       addBehaviors(component, component.getClass());
     } finally {
       lock.readLock().unlock();
     }
   }

   /**
    * Adds all registered behaviors to the given component based upon the
given component class and the parent classes, stopping at
    * {@link Component}.
    * <p>
    * Parent class behaviors are added first in order to allow general
behaviors to take effect earlier.
    * </p>
    * <p>
    * This method should only be called under a read lock.
    * </p>
    * @param component The component to which the behaviors should be added.
    * @param componentClass
    */
   //we ensure manually that the parent classes are subclasses of Component
   @SuppressWarnings("unchecked")
   protected void addBehaviors(final Component component, final Class<?
extends Component> componentClass) {

     //do a depth-first traversal to add general (super-class) behaviors
first
     //no need to look higher up the hierarchy than Component
     if (!componentClass.equals(Component.class)) {
       final Class<?> parentComponentClass = componentClass.getSuperclass()
;
       //the class is a subclass of Component; because we stop when we find
Component,
       //we expect never to run out of parents
       assert parentComponentClass != null;

In Wicket we don't use JDK's assert anywhere so far.
In my experience these should be used only in the application code. A
library/framework should not change its behavior depending on JVM argument.


       addBehaviors(component, (Class<? extends Component>)
parentComponentClass);
     }

     //add behaviors (if any) registered for this class
     for (final Behavior behavior : behaviorMap.get(componentClass)) {
       component.add(behavior);
     }

   }

}



Reply via email to