package com.sap.demo.input_validator; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import com.sap.dictionary.runtime.ISimpleType; import com.sap.tc.webdynpro.clientserver.uielib.standard.api.IWDInputField; import com.sap.tc.webdynpro.clientserver.uielib.standard.api.WDState; import com.sap.tc.webdynpro.progmodel.api.IWDAttributePointer; import com.sap.tc.webdynpro.progmodel.api.IWDContext; import com.sap.tc.webdynpro.progmodel.api.IWDMessage; import com.sap.tc.webdynpro.progmodel.api.IWDMessageManager; import com.sap.tc.webdynpro.progmodel.api.IWDNode; import com.sap.tc.webdynpro.progmodel.api.IWDNodeElement; import com.sap.tc.webdynpro.progmodel.api.IWDViewElement; // This class provides a depth-first, generic traversal of a UI element hierarchy. public class UiTreeVisitor { private Map aggregationsByClass = new HashMap(); private Set visited = new HashSet(); private IWDMessageManager msgMgr; private IWDContext wdContext; private Vector messages; // The constructor needs a reference to the WD component's message manager and // the context of the view controller using this utility class. public UiTreeVisitor(IWDMessageManager msg, IWDContext ctx, Vector msgs) { this.msgMgr = msg; this.wdContext = ctx; this.messages = msgs; } // Do something useful before visiting the view element's children public void beforeVisitChildren(IWDViewElement e) { } // Since this utility class is called every round trip, its list of visited UI elements // must be reset. This also accounts for the fact that UI elements can be added and // removed dynamically public void reset() { visited.clear(); aggregationsByClass.clear(); } // Do something useful with the current UI element. public void visitElement(IWDViewElement e) { String uiElementType = e.getClass().getSimpleName(); StringBuffer msg = new StringBuffer(); // Have we found an input field? if (e instanceof IWDInputField) { IWDInputField in = (IWDInputField)e; // Should there be a value in this field? if (in.getState() == WDState.REQUIRED) { // Yup, so check the context attribute to which this UI element's value property is bound // This code is quite simplistic in that is does not attempt to parse the context path to // see if there are multiple nodes between the root node and the erroneous node attribute. // It simply assumes there's just a single node String contextPath = in.bindingOfValue(); String attrName = contextPath.substring(contextPath.lastIndexOf('.')+1); String nodeName = contextPath.substring(0,contextPath.lastIndexOf('.')); // Using the node and attribute names, use the generic context API to locate the correct // field. Then retrieve the attribute pointer to this field and its metadata. IWDNode thisNode = wdContext.getRootNode().getChildNode(nodeName, 0); IWDNodeElement thisEl = thisNode.getCurrentElement(); IWDAttributePointer thisAttrPtr = thisEl.getAttributePointer(attrName); ISimpleType st = thisAttrPtr.getAttributeInfo().getSimpleType(); String dataType = st.getBuiltInType(); // If the data type is numeric (but not decimal) then set the emptyValue to 0. // The "decimal" data type is excluded because an empty decimal field is blank rather than 0. if (in.getValue().equals((st.isNumeric() && !dataType.equals("decimal")) ? "0" : "")) msgMgr.reportContextAttributeMessage(thisAttrPtr, (st.isNumeric()) ? messages.get(1) : messages.get(0), new Object[] {in.getId()}); } } } public void afterVisitChildren(IWDViewElement e) { // Do something useful after the view element's children have been visited } // Traverse all UI aggregations under the given view element public final void visit(IWDViewElement e) { // Record visit to this element visited.add(e); visitElement(e); beforeVisitChildren(e); // Find all the methods of the current element that return UI // element aggregations. These aggregations could have single // or multiple cardinality. List aggregations = getOutgoingAggregations(e.getClass()); // Process each method for (Iterator it = aggregations.iterator(); it.hasNext();) { // Get a reference to the next method Method m = (Method) it.next(); // Does this method return an aggregation of multiple cardinality? if (m.getName().startsWith("iterate")) { // Yup, so invoke this method process and all the child UI elements recursively try { // Loop around the child UI elements in the iteration for (Iterator targets = (Iterator) m.invoke(e, null); targets.hasNext();) { IWDViewElement child = (IWDViewElement) targets.next(); // Visit the child (as long as we haven't been here before) if (!visited.contains(child)) visit(child); } } catch (Exception x) { // Oh dear, we shouldn't have arrived here... } } else { // Nope, so this method must return a single UI element try { // Call the method to see what we get IWDViewElement child = (IWDViewElement) m.invoke(e, null); // Visit the child (as long as we haven't been here before) if (!visited.contains(child)) visit(child); } catch (Exception x) { // We shouldn't have arrived here either... } } } afterVisitChildren(e); } // Returns the list of outgoing aggregations for the given view // element class. This information is cached for efficiency. // An empty list will be returned when a leaf node is // encountered. This behaviour defines the termination // condition for recursion. private List getOutgoingAggregations(Class clazz) { // Have we already encountered this class before? if (aggregationsByClass.containsKey(clazz)) // Yup, so return the results from the cache return (List) aggregationsByClass.get(clazz); else { // Nope, so find the methods of this class that all return one or more UI elements List aggregations = collectOutgoingAggregations(clazz); // Store the method list to avoid having to repeat the above processing aggregationsByClass.put(clazz, aggregations); return aggregations; } } // Build a list of outgoing aggregations for the given view // element class using reflection. private List collectOutgoingAggregations(Class clazz) { List result = new ArrayList(); // Get a list of all the methods in this element class Method[] methods = clazz.getMethods(); // Now check to see what each method does... for (int i = 0; i < methods.length; ++i) { // Does the method name start with "iterate"? if (methods[i].getName().startsWith("iterate")) // Yup, we can safely assume that this method returns an // aggregation of UI elements of multiple cardinality result.add(methods[i]); // Does the method name start with "get"? if (methods[i].getName().startsWith("get")) { // Yup, check the method’s return type Class returnType = methods[i].getReturnType(); // Is the return type ultimately derived from an // IWDViewElement? if (IWDViewElement.class.isAssignableFrom(returnType)) // Yup, we can safely assume that this method returns // an aggregation of single cardinality (i.e., one UI // element). result.add(methods[i]); } } // Return a list of methods that, when called, will all // return an aggregation of one or more UI elements return result; } }