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