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); > } > > } > > } > >