/* OperationAgentManager.java
Purpose:
Description:
History:
Mar 20, 2012 Created by pao
Copyright (C) 2011 Potix Corporation. All Rights Reserved.
*/
package org.zkoss.zats.mimic.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.zkoss.zats.mimic.Agent;
import org.zkoss.zats.mimic.impl.operation.AuAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.ButtonUploadAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.ColumnSortAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.DesktopBookmarkAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.DialogUploadAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericCheckAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericClickAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericCloseAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericDragAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericFocusAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericGroupAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericHoverAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericKeyStrokeAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericMoveAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GenericOpenAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.GridRenderAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.ListboxRenderAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.ListheaderSortAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.MenuitemUploadAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.PagingAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.PanelSizeAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.SliderInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.TextboxOpenAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.TreecolSortAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.WindowSizeAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.DateInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.DecimalInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.DecimalStringInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.IntegerInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.IntegerStringInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.TextInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.input.TimeInputAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.ComboitemSelectAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.LisitemSelectAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.ListitemMultipleSelectAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.TabSelectAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.TreeSelectAgentBuilder;
import org.zkoss.zats.mimic.impl.operation.select.TreeitemMultipleSelectAgentBuilder;
import org.zkoss.zats.mimic.operation.OperationAgent;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zul.A;
import org.zkoss.zul.Bandbox;
import org.zkoss.zul.Button;
import org.zkoss.zul.Center;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Column;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.Datebox;
import org.zkoss.zul.Decimalbox;
import org.zkoss.zul.Detail;
import org.zkoss.zul.Doublebox;
import org.zkoss.zul.Doublespinner;
import org.zkoss.zul.East;
import org.zkoss.zul.Fileupload;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Group;
import org.zkoss.zul.Groupbox;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listgroup;
import org.zkoss.zul.Listheader;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.Longbox;
import org.zkoss.zul.Menuitem;
import org.zkoss.zul.North;
import org.zkoss.zul.Paging;
import org.zkoss.zul.Panel;
import org.zkoss.zul.Popup;
import org.zkoss.zul.Slider;
import org.zkoss.zul.South;
import org.zkoss.zul.Spinner;
import org.zkoss.zul.Splitter;
import org.zkoss.zul.Tab;
import org.zkoss.zul.Timebox;
import org.zkoss.zul.Toolbarbutton;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Treecol;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.West;
import org.zkoss.zul.Window;
import org.zkoss.zul.impl.InputElement;
/**
* <p>
* This class maintains a mapping registry between ZK components (in specific version range) and {@link OperationAgentBuilder}.
* It registers all entries in the constructor and retrieve them when {@link ValueResolver} requests.
* </p>
* We design the registration mechanism in order to deal with the issue: one component might have different behaviors in different versions.
* This mechanism has several features:
* Later registered entry overwrite previous one with the same key.
* When get {@link OperationAgentBuilder}, it will keeping search ZK component's class and its parent class until it finds a match or fails to match.
*
*
*
* @author pao
* @author dennis
*/
public class OperationAgentManager {
private static OperationAgentManager instance;
public static synchronized OperationAgentManager getInstance(){
if(instance==null){
instance = new OperationAgentManager();
}
return instance;
}
//hold registered builders
private Map<Key, OperationAgentBuilder<? extends Agent, ? extends OperationAgent>> registeredBuilders;
//cache the resolved builders
private Map<Key, OperationAgentBuilder<? extends Agent, ? extends OperationAgent>> resolvedBuilders;
public OperationAgentManager() {
registeredBuilders = Collections.synchronizedMap(new HashMap<OperationAgentManager.Key, OperationAgentBuilder<? extends Agent, ? extends OperationAgent>>());
resolvedBuilders = Collections.synchronizedMap(new HashMap<OperationAgentManager.Key, OperationAgentBuilder<? extends Agent, ? extends OperationAgent>>());
//most common agents
registerBuilder("5.0.0", "*", Desktop.class, new DesktopBookmarkAgentBuilder());
registerBuilder("5.0.0", "*", AbstractComponent.class, new GenericClickAgentBuilder());
registerBuilder("5.0.0", "*", AbstractComponent.class, new GenericKeyStrokeAgentBuilder());
registerBuilder("5.0.0", "*", Component.class, new AuAgentBuilder());
// the focus
registerBuilder("5.0.0", "*", InputElement.class, new GenericFocusAgentBuilder());
registerBuilder("5.0.0", "*", A.class, new GenericFocusAgentBuilder());
registerBuilder("5.0.0", "*", Button.class, new GenericFocusAgentBuilder());
registerBuilder("5.0.0", "*", Checkbox.class, new GenericFocusAgentBuilder());
registerBuilder("5.0.0", "*", Listbox.class, new GenericFocusAgentBuilder());
registerBuilder("5.0.0", "*", Tree.class,new GenericFocusAgentBuilder());
// the inputs
registerBuilder("5.0.0", "*", InputElement.class, new TextInputAgentBuilder());
registerBuilder("5.0.0", "*", Intbox.class, new IntegerInputAgentBuilder());
registerBuilder("5.0.0", "*", Longbox.class, new IntegerStringInputAgentBuilder());
registerBuilder("5.0.0", "*", Spinner.class, new IntegerInputAgentBuilder());
registerBuilder("5.0.0", "*", Decimalbox.class, new DecimalStringInputAgentBuilder());
registerBuilder("5.0.0", "*", Doublebox.class, new DecimalInputAgentBuilder());
registerBuilder("5.0.0", "*", Doublespinner.class, new DecimalInputAgentBuilder());
registerBuilder("5.0.0", "*", Datebox.class, new DateInputAgentBuilder());
registerBuilder("5.0.0", "*", Timebox.class, new TimeInputAgentBuilder());
// the check
registerBuilder("5.0.0", "*", Menuitem.class,new GenericCheckAgentBuilder());
// the check of check box and radio button
registerBuilder("5.0.0", "*", Checkbox.class, new GenericCheckAgentBuilder());
// the single select
registerBuilder("5.0.0", "*", Comboitem.class, new ComboitemSelectAgentBuilder());
registerBuilder("5.0.0", "*", Tab.class, new TabSelectAgentBuilder());
registerBuilder("5.0.0", "*", Listitem.class, new LisitemSelectAgentBuilder());
registerBuilder("5.0.0", "*", Treeitem.class, new TreeSelectAgentBuilder());
// the multiple select
registerBuilder("5.0.0", "*", Listitem.class, new ListitemMultipleSelectAgentBuilder());
registerBuilder("5.0.0", "*", Treeitem.class, new TreeitemMultipleSelectAgentBuilder());
// the open
registerBuilder("5.0.0", "*", Groupbox.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Detail.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Group.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Listgroup.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Treeitem.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Window.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Panel.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Center.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", North.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", East.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", West.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", South.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Splitter.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Popup.class, new GenericOpenAgentBuilder());
registerBuilder("5.0.0", "*", Bandbox.class, new TextboxOpenAgentBuilder());
registerBuilder("5.0.0", "*", Combobox.class, new TextboxOpenAgentBuilder());
// the close
registerBuilder("5.0.0", "*", Window.class,new GenericCloseAgentBuilder());
registerBuilder("5.0.0", "*", Panel.class, new GenericCloseAgentBuilder());
registerBuilder("5.0.0", "*", Tab.class,new GenericCloseAgentBuilder());
// the render
registerBuilder("5.0.0", "*", Listbox.class, new ListboxRenderAgentBuilder());
registerBuilder("5.0.0", "*", Grid.class, new GridRenderAgentBuilder());
// the resize
registerBuilder("5.0.0", "*", Window.class, new WindowSizeAgentBuilder());
registerBuilder("5.0.0", "*", Panel.class, new PanelSizeAgentBuilder());
// registerBuilder("5.0.0", "*", Column.class, new HeaderSizeAgentBuilder());
// registerBuilder("5.0.0", "*", Listheader.class, new HeaderSizeAgentBuilder());
// registerBuilder("5.0.0", "*", Treecol.class, new HeaderSizeAgentBuilder());
//drag & drop
registerBuilder("5.0.0", "*", HtmlBasedComponent.class, new GenericDragAgentBuilder());
//hover
registerBuilder("5.0.0", "*", HtmlBasedComponent.class, new GenericHoverAgentBuilder());
//paging
registerBuilder("5.0.0", "*", Paging.class, new PagingAgentBuilder());
//group
registerBuilder("5.0.0", "*", Column.class, new GenericGroupAgentBuilder());
//sort
registerBuilder("5.0.0", "*", Column.class, new ColumnSortAgentBuilder());
registerBuilder("5.0.0", "*", Listheader.class, new ListheaderSortAgentBuilder());
registerBuilder("5.0.0", "*", Treecol.class, new TreecolSortAgentBuilder());
// the scroll
registerBuilder("5.0.0", "*", Slider.class, new SliderInputAgentBuilder());
// the move
registerBuilder("5.0.0", "*", Window.class, new GenericMoveAgentBuilder());
registerBuilder("5.0.0", "*", Panel.class, new GenericMoveAgentBuilder());
// upload
registerBuilder("5.0.0", "*", Button.class, new ButtonUploadAgentBuilder());
registerBuilder("5.0.0", "*", Fileupload.class, new ButtonUploadAgentBuilder());
registerBuilder("5.0.0", "*", Toolbarbutton.class, new ButtonUploadAgentBuilder());
registerBuilder("5.0.0", "*", Menuitem.class, new MenuitemUploadAgentBuilder());
registerBuilder("5.0.0", "*", Desktop.class, new DialogUploadAgentBuilder());
//----------special case ---
String extClz;
//colorbox in zkex.jar which is optional
extClz = "org.zkoss.zkex.zul.Colorbox";
if(Util.hasClass(extClz)){
registerBuilder("5.0.0", "*", extClz,
"org.zkoss.zats.mimic.impl.operation.input.ColorboxInputAgentBuilder");
}
// the ckeditor (optional)
extClz = "org.zkforge.ckez.CKeditor";
if(Util.hasClass(extClz)){
registerBuilder("5.0.0", "*", extClz,
"org.zkoss.zats.mimic.impl.operation.input.TextInputAgentBuilder");
}
}
/**
* Register a operation builder mapping to component and operation. We can
* specify zk version worked on. The version text could be normal version
* format (e.g. 6.0.0 or 5.0.7.1) or "*" sign means no specify. If specify
* version range doesn't include current zk version at runtime, this
* register will be ignored.
* <p/>
*
* Use this API if the component is only in a particular zk version only to
* avoid initial exception.
* <p/>
*
* @param startVersion
* start version (include)
* @param endVersion
* end version (include)
* @param delegateeClass
* the component class that builder maps to ( *notice: it should
* not specify interface)
* @param builderClazz
* operation builder
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public <O extends OperationAgent> void registerBuilder(String startVersion, String endVersion,
String delegateeClass, String builderClazz) {
if (startVersion == null || endVersion == null || delegateeClass == null || builderClazz == null)
throw new IllegalArgumentException();
if (!Util.checkVersion(startVersion, endVersion))
return;
Class clz = null;
try {
clz = Class.forName(delegateeClass);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("delegateeClass " + delegateeClass + " not found ", e);
}
OperationAgentBuilder<Agent,O> builder = null;
try {
Class buildClz = Class.forName(builderClazz);
builder = (OperationAgentBuilder<Agent,O>) buildClz.newInstance();
} catch (Exception x) {
throw new IllegalArgumentException(x.getMessage(), x);
}
registerBuilder(startVersion, endVersion, clz, builder);
}
/**
* register a operation builder mapping to component and operation. We can
* specify zk version worked on. The version text could be normal version
* format (e.g 6.0.0 or 5.0.7.1) or "*" sign means no specify. If specify
* version range doesn't include current zk version at runtime, this
* register will be ignored.
* @param startVersion
* start version (include)
* @param endVersion
* end version (include)
* @param delegateeClass
* the component class that builder maps to ( *notice: it should
* not specify interface)
* @param builder
* operation builder
*/
public <O extends OperationAgent, C extends Object> void registerBuilder(String startVersion,
String endVersion, Class<C> delegateeClass, OperationAgentBuilder<? extends Agent,O> builder) {
if (startVersion == null || endVersion == null || delegateeClass == null || builder == null)
throw new IllegalArgumentException();
if (!Util.checkVersion(startVersion, endVersion))
return;
// component and operation classes mapping to builder
// builder would be replace by later register
registeredBuilders.put(new Key(delegateeClass, builder.getOperationClass()), builder);
}
/**
* Return a corresponding OperationAgentBuilder for delegatee supported operation class.
* It will search in registry with delegatee's class first. If not found, it keeps searching with delegatee's parent class until found or failed.
* @param delegatee
* @param operation
* @return the operation agent builder
*/
@SuppressWarnings("unchecked")
public <O extends OperationAgent> OperationAgentBuilder<Agent, O> getBuilder(Object delegatee,
Class<O> operation) {
// search from self class to parent class
Class<?> c = delegatee.getClass();
Key key = new Key(c, operation);
OperationAgentBuilder<? extends Agent, ? extends OperationAgent> builder = resolvedBuilders.get(key);
if(builder==null){
//cache the lookup
builder = lookupBuilder(c, operation);
if(builder!=null){
resolvedBuilders.put(key, builder);
}
}
return (OperationAgentBuilder<Agent, O>)builder;
}
@SuppressWarnings("unchecked")
private <O extends OperationAgent> OperationAgentBuilder<Agent, O> lookupBuilder(Class<?> delegatee, Class<O> operation) {
// search from class to super class and interfaces
OperationAgentBuilder<? extends Agent, ? extends OperationAgent> builder;
//look super first
Class<?> c = delegatee;
while (c != null) {
builder = registeredBuilders.get(new Key(c, operation));
if (builder != null)
return (OperationAgentBuilder<Agent, O>) builder;
//check super
c = c.getSuperclass();
}
//then lookup interface
c = delegatee;
while (c != null) {
Class<?>[] ifs = c.getInterfaces();
for (Class<?> i : ifs) {
builder = lookupBuilder(i, operation);
if (builder != null) {
return (OperationAgentBuilder<Agent, O>)builder;
}
}
//check interface of super
c = c.getSuperclass();
}
return null; // not found
}
/**
* for operation builder mapping
* @author pao
*/
private static class Key {
public Class<?> c;
public Class<?> o;
public <O extends OperationAgent, C extends Object> Key(Class<C> c, Class<O> o) {
this.c = c;
this.o = o;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((c == null) ? 0 : c.hashCode());
result = prime * result + ((o == null) ? 0 : o.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
else if (obj instanceof Key) {
Key o = (Key) obj;
return o.o == this.o && o.c == this.c;
}
return false;
}
}
}