/**
*
*/
package org.sinnlabs.dbvim.zk.model;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.sinnlabs.dbvim.rules.engine.RulesEngine;
import org.sinnlabs.dbvim.rules.engine.exceptions.RulesException;
import org.sinnlabs.dbvim.ui.DesignerCanvas;
import org.zkoss.idom.Attribute;
import org.zkoss.idom.Document;
import org.zkoss.idom.Element;
import org.zkoss.idom.Item;
import org.zkoss.idom.util.IDOMs;
import org.zkoss.lang.Classes;
import org.zkoss.zk.ui.AbstractComponent;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.metainfo.ZScript;
import org.zkoss.zk.ui.sys.ComponentCtrl;
/**
* Class that converts
* a specified component model into a
* ZUML definition file
* @author peter.liverovsky
*
*/
public class ZUMLModel {
private Document doc;
private Element locatedElement;
private IDeveloperStudio developer;
/**
* single argument constructor
* @param cmpRoot root component whose encompassed
*/
public ZUMLModel(Component cmpRoot, IDeveloperStudio developer)
{
this.developer = developer;
// convert model directly
convertModelToZUML(cmpRoot);
}
/**
* Returns the ZUML iDOM Document object
* that represents the given component model.
* @return The iDOM Document object
*/
public Document getZUMLDocument() { return doc; }
/**
* Returns a textual (ZUML) representation of
* the converted model from the corresponding
* iDOM Document object.
* @return String that holds the ZUML
* representation
*/
public String getZUML()
{
String sZUML = "";
try
{
// convert the iDOM document into a string
sZUML = IDOMs.toString(doc);
}
catch (Exception e)
{
e.printStackTrace();
}
// return the ZUML string
return sZUML;
}
/**
* Parses the model and converts it into the
* ZUML corresponding representation by
* converting each model element.
* @param cmpRoot
*/
protected void convertModelToZUML(Component component)
{
if (component == null)
return;
// create a new Document
if (doc == null)
doc = new Document();
try
{
// apply the Model-to-ZUML rules of the component
// before creating its ZUML definition
RulesEngine.applyRules(component, RulesEngine.MODEL_TO_ZUML_RULES, developer);
}
catch (RulesException re)
{
// do not convert the component to ZUML
// if exception is thrown
return;
}
/* do not add designer canvas to the model
* if root component is the designer canvas */
if (! (component instanceof DesignerCanvas)) {
// create iDOM Element
Element domElement = createElement((AbstractComponent) component);
// add Element to the Document
addElementToDocument(domElement, component);
// check if component's children should be exported
// to the ZUML representation
if (! RulesEngine.getComponentFlag(component, RulesEngine.FLAG_EXPORT_CHILDREN_TO_ZUML))
return;
}
// create root <zk> element if root component is the designer canvas
else {
// create an iDOM element
Element domElement = new Element("zk");
// add root Element to the document
doc.setRootElement(domElement);
}
// get component's children
List<Component> listChildren = component.getChildren();
if (listChildren.size() == 0)
return;
// loop through all the component's children
Iterator<Component> iter = listChildren.iterator();
while (iter.hasNext())
{
try
{
// get the next component in the list
Component child = iter.next();
if (child == null)
continue;
// check if component child should be exported
if ( !RulesEngine.exportChildToZuml(component, child) )
continue;
/*** RECURSION ***/
convertModelToZUML(child);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
/**
* Creates a DOM XUL representation of the
* given XulElement.
* @param cmp XUL element to convert
* @return DOM XUL representation Element
*/
private Element createElement(AbstractComponent cmp)
{
if (cmp == null)
return null;
Element domElement = null;
try
{
// create a new iDOM Element using the component's name [in lowercase]
domElement = new Element(StringUtils.lowerCase(ComponentFactory.getSimpleClassName(cmp)));
// get the component's Id
String sId = cmp.getId();
// if Id is auto-generated by the framework
// it cannot be used for components that will be re-loaded
// in a page. For this reason it might need to be fixed
sId = ComponentFactory.fixAutoId(sId);
domElement.setIdAttribute(sId, true);
// create element's attributes
createAttributes(cmp, domElement);
// create element's event handlers
createEventHandlers(cmp, domElement);
}
catch (Exception e)
{
}
// return the iDOM Element
return domElement;
}
/**
* Retrieves all Component's attributes
* (get/set methods) and creates the corresponding
* attributes of the XUL element.
* @param cmp Component
* @param domElement XUL Element
*/
private void createAttributes(AbstractComponent cmp,
Element domElement)
{
if ((cmp == null) || (domElement == null))
return;
// get the Component's class
Class<? extends AbstractComponent> clazz = cmp.getClass();
// get property descriptors of the component's
// implementation class
PropertyDescriptor arrDescr[] = PropertyUtils.getPropertyDescriptors(clazz);
// get the list of the component's attributes
// that should not be visible in the ZUML definition
String[] arrExcludedAttr = RulesEngine.getComponentAttributes(cmp,
RulesEngine.ATTRIBUTES_EXCLUDE_FROM_ZUML);
// loop through the properties array
for (int i = 0; i < arrDescr.length; i++)
{
try
{
// get the next property descriptor
PropertyDescriptor descr = arrDescr[i];
// get the corresponding setter (Write) method
Method setter = descr.getWriteMethod();
Method getter = descr.getReadMethod();
// move on if there is no setter method available
if (setter == null)
continue;
// get the corresponding XUL attribute name
String sXULName = Classes.toAttributeName(setter.getName());
// move on to the next property
// if the attribute name exists in the
// list of excluded attributes
if (! ArrayUtils.isEmpty(arrExcludedAttr))
{
if (ArrayUtils.contains(arrExcludedAttr, sXULName))
continue;
}
// get parameter type
Class<?>[] arrParamTypes = setter.getParameterTypes();
// filter out setters with more than one parameters
if (arrParamTypes.length > 1)
continue;
// filter out attributes that take Object arguments
// (except String)
if (((arrParamTypes[0] != String.class) &&
(arrParamTypes[0] != boolean.class) &&
(arrParamTypes[0] != int.class) &&
(arrParamTypes[0] != long.class))
)
{
continue;
}
Object value = null;
String sDefaultValue = "";
String sCurrentValue = "";
// get default value
if (getter != null)
{
// create a new instance of the component (default)
AbstractComponent template = clazz.newInstance();
try
{
// get the start-up value of the specific property
value = PropertyUtils.getProperty(template, Classes.toAttributeName(getter.getName()));
}
catch (Exception e)
{
}
// clean up
template.detach();
template = null;
if (value != null)
sDefaultValue = String.valueOf(value);
try
{
// get the current property value
value = PropertyUtils.getProperty(cmp, Classes.toAttributeName(getter.getName()));
if (value != null)
sCurrentValue = String.valueOf(value);
}
catch (Exception e)
{
}
}
// move on if the current property value is equal to the
// default one or is empty
if ((sCurrentValue.equals(sDefaultValue)) ||
StringUtils.isEmpty(sCurrentValue))
continue;
// if this is the "id" attribute - fix it first
if (sXULName.equals("id"))
sCurrentValue = ComponentFactory.fixAutoId(sCurrentValue);
// create a new iDOM Attribute and attach it
// to the specified Element
Attribute domAttribute = new Attribute(sXULName, sCurrentValue);
domElement.setAttribute(domAttribute);
}
catch (Exception e)
{
e.printStackTrace();
continue;
}
}
}
/**
* Creates all the event handle attributes
* registered with the specified component.
* @param cmp Component
* @param domElement XUL Element
*/
protected void createEventHandlers(AbstractComponent cmp,
Element domElement)
{
if (cmp == null)
return;
// get all the events that apply to this component
String[] arrEvents = ComponentFactory.getComponentEvents(cmp.getClass());
if (ArrayUtils.isEmpty(arrEvents))
return;
// iterate through the events array
for (int i = 0; i < arrEvents.length; i++)
{
// get the next event's name
String sEventName = StringUtils.trim(arrEvents[i]);
// check if this event has a defined event handler (script)
// [we don't care about event listeners, we are after ZUML
// elements only - listeners are code elements]
ZScript zScript = ((ComponentCtrl)cmp).getEventHandler(sEventName);
if (zScript == null || StringUtils.isBlank(zScript.getRawContent()))
continue;
// add event handler attribute to the element
addEventHandler(domElement, sEventName, zScript.getRawContent());
}
// clean up
for (int i = 0; i < arrEvents.length; i++)
arrEvents[i] = null;
}
/**
* Adds the given Element to the current iDOM.
* @param domElement XUL representation of a
* model Component.
*/
private void addElementToDocument(Element domElement,
Component component)
{
if ((domElement == null) || (doc == null))
return;
if (doc.getRootElement() == null)
doc.setRootElement(domElement);
else
{
// get the parent iDOM Element using the parent
// component's Id as the search criterion
Element domParentElement = getElementById(ComponentFactory.fixAutoId(component.getParent().getId()));
// append the new Element to the parent
if (domParentElement != null)
domParentElement.appendChild(domElement);
else
doc.getRootElement().appendChild(domElement);
}
}
/**
* Walks through the Document model
* and performs an element-by-element
* search using the specified matching
* attribute
* @param sAttributeName Attribute to search for
* @param tree matching Attribute value
*/
protected Element locateElement(Element domElement,
String sAttributeName,
String sAttributeValue)
{
if (locatedElement != null)
return locatedElement;
if (domElement == null)
return null;
// get specified Attribute
Attribute domAttribute = domElement.getAttributeItem(sAttributeName);
if (domAttribute != null)
{
// check if we have an Attribute value match
if (domAttribute.getValue().equals(sAttributeValue))
{
locatedElement = domElement;
return domElement;
}
}
// get component's children
List<Item> listChildren = domElement.getChildren();
if ((listChildren == null) || (listChildren.size() == 0))
return null;
// loop through all component's children
Iterator<Item> iter = listChildren.iterator();
while (iter.hasNext())
{
// get next component in the list
Element child = (Element) iter.next();
if (child == null)
continue;
// parse the model of the child
locateElement(child, sAttributeName, sAttributeValue);
}
return null;
}
/**
* Searches the Document for the specified
* Element by using its Id as the criterion.
* @return The matching Element
*/
private Element getElementById(String sId)
{
locatedElement = null;
if (StringUtils.isEmpty(sId))
return null;
// try to locate the Element
locateElement(doc.getRootElement(), "id", sId);
return locatedElement;
}
/**
* Returns the iDOM element representation
* of the specified visual component.
* @param element component to look for
* in the iDOM tree
* @return corresponding iDOM element
*/
public Element getDOMElement(AbstractComponent element)
{
if (element == null)
return null;
// locate and return the Element representation
return getElementById(element.getId());
}
/**
* Adds an event handler attribute to the
* iDOM representation of the given visual
* component.
* @param element component instance
* @param sEventName name of the event to
* be handled
* @param sScript script to be triggered
*/
public void addEventHandler(AbstractComponent element,
String sEventName,
String sScript)
{
if (element == null)
return;
try
{
// retrieve the iDOM Element by using the component's Id
Element domElement = getDOMElement(element);
if (domElement == null)
return;
// add event handler attribute
addEventHandler(domElement, sEventName, sScript);
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* Adds an event handler attribute to the
* given iDOM Element
* @param element iDOM element that represents
* a visual component
* @param sEventName name of the event to
* be handled
* @param sScript script to be triggered
*/
protected void addEventHandler(Element domElement,
String sEventName,
String sScript)
{
if (domElement == null)
return;
// create a new Attribute that describes the event
Attribute attrEventHandler = new Attribute(sEventName, sScript);
// attach it to the element
domElement.setAttributeNode(attrEventHandler);
}
}