[Webwork-devel] Re: ValueStack abstraction
Brought to you by:
baldree,
rickardoberg
From: Sven K. <sv...@im...> - 2002-05-21 19:35:10
|
Since a diff does not make much sense for this, I'll repost the files completely in plain ascii. Sorry for the inconvenience. Sven.... -- snip "AbstractValueStack.java" /* * WebWork, Web Application Framework * * Distributable under Apache license. * See terms of license at opensource.org */ package webwork.util; import org.apache.log4j.Category; import webwork.expr.ParseException; import webwork.expr.Parser; import java.beans.*; import java.io.StringReader; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * Value stack. A VS is used by the WebWork system as a way to make * values available by using the push and pop methods. They can then * be accessed by using the find* methods. * * @author Rickard Öberg (ri...@mi...) * @author Maurice C. Parker (ma...@vi...) * @version $Revision$ */ public abstract class AbstractValueStack{ // Static ------------------------------------------------------- public static final String STACK_NAME = "webwork.result"; static Map classes = new HashMap(); // Method cache private static Category log = Category.getInstance(ValueStack.class); /** * Clear the method cache. This must be called if the * application is restarted. * */ public static void clearMethods() { classes = new HashMap(); } // Attributes ---------------------------------------------------- private List valueList = Collections.EMPTY_LIST; private Parser parser; // Constructor --------------------------------------------------- public AbstractValueStack() { } // Public -------------------------------------------------------- /** * Push a value onto the value stack. * * @param value the value */ public void pushValue(Object value) { if (valueList == Collections.EMPTY_LIST) { valueList = new ArrayList(); } valueList.add(value); // log.debug("Push to value stack:"); // log.debug(toString()); // new Throwable().printStackTrace(); } /** * Pop a value from the value stack. * * @return the popped value */ public Object popValue() { try { return valueList.remove(valueList.size() - 1); } catch (IndexOutOfBoundsException e) { return null; } finally { // log.debug("Pop from value stack:"); // log.debug(toString()); // new Throwable().printStackTrace(); } } /** * Returns the size of the value stack. * * @return size of value stack */ public int size() { return valueList != null?valueList.size():0; } /** * Returns TRUE is the value stack is empty. * * @return true is value stack is empty */ public boolean isEmpty() { return size() == 0; } /** * Resolve a WebWork expression statement. * * @param expression * @return the boolean result of the expression */ public boolean test(String expression) { Parser p = getParser(expression); boolean answer = false; try { answer = p.test(); } catch (ParseException pe) { throw new IllegalArgumentException("\n\nAn error occurred while parsing the expression: \n \"" + expression + "\"\n" + pe.getMessage()); } return answer; } // SK /** Find a value by id. AbstractValueStack expects subclasses to * have some context concept to evaluate @identified expressions. */ abstract protected Object findInContext(String id); /** Find a request parameter. AbstractValueStack expects subclasses to * have some request concept to evaluate $parameter expressions */ abstract protected String getRequestParameter(String id); // /SK /** * Find a value for a given name. * * @param expression * @return the object corresponding to the query */ public Object findValue(String query) throws IllegalArgumentException { if (query == null || query.equals("")) query = "."; // The query segments and the current segment Query q = Query.getQuery(query); QuerySegment[] segments = q.getSegments(); if (log.isDebugEnabled()) { log.debug("findValue() for: " + q); } QuerySegment segment = segments[0]; int segmentIdx = 1; // The working stack, its pointer, and the current object, List workList = null; int stackIdx = 0; Object value = null; ///////////////////////////////////////////////////////////////////////// // evaluate the first element of the expression to see where to // get the requested value. These should be quick and easy objects // to find or create. ///////////////////////////////////////////////////////////////////////// switch (segment.getType()) { // get the top value off of the stack case QuerySegment.CURRENT: if (valueList.size() < 1) return null; else value = valueList.get(valueList.size() - 1); if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); if (value == null && log.isDebugEnabled()) log.debug("value for [" + query + "] is null."); return value; // return the id since it is the actual string case QuerySegment.STRING: return segment.getId(); // the integer is the first value and only value in the segment case QuerySegment.NUMBER: return segment.getValues().get(0); // get an attribute case QuerySegment.ATTRIBUTE: // get the attribute // SK value=findInContext(segment.getId()); // /SK if (value == null && log.isDebugEnabled()) { log.debug("value for [" + query + "] is null."); return null; //throw new IllegalArgumentException("No such attribute: " + token.image); } // get the real value if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); // always have the next segment ready to go segment = segments[segmentIdx++]; // if we don't need to search through this attribute simply return it if (segment == null) return value; // Set the stack index to zero so that we don't try to search the stack stackIdx = 0; break; // return the http request parameter case QuerySegment.PARAMETER: // SK return getRequestParameter(segment.getId()); // /SK // the reserved keywork "true" case QuerySegment.TRUE: return Boolean.TRUE; // the reserved keywork "false" case QuerySegment.FALSE: return Boolean.FALSE; // get the root object off of the bottom of the stack case QuerySegment.ROOT: if (valueList.size() < 1) return null; // set up the stack, pointer, and current value workList = valueList; stackIdx = 0; value = workList.get(stackIdx); // always have the next segment ready to go segment = segments[segmentIdx++]; // if we don't need to search through the stack then return the value // this is very unlikely to happen, but we have account for it anyway if (segment == null) return value; break; default: if (valueList.size() < 1) return null; // set up the stack, pointer, and current value workList = valueList; stackIdx = workList.size() - 1; try { value = workList.get(stackIdx); } catch (IndexOutOfBoundsException e) { return null; //throw new IllegalArgumentException("Illegal valuestack query:"+query); } break; } //log.debug( "first segment id: '" + segment.getId() + "' segment type: '" + segment.getType() + "'" ); ///////////////////////////////////////////////////////////////////////// // Now that stack has been set up and the context set (valueIdx) we will // begin parsing the rest of the expression and drilling down through // the object properties, collection elements, and method calls. ///////////////////////////////////////////////////////////////////////// int saveSegmentIdx = segmentIdx; while (true) { //log.debug("beginning valuestack search at level: '" + stackIdx); int workStackIdx = stackIdx; if (value != null) { objectWalk: do { switch (segment.getType()) { // access a classes property case QuerySegment.PROPERTY: // get the real value if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); //log.debug( "PROPERTY: attempting to get: " + segment.getId() ); try { Method[] methods = getMethod(value.getClass(), segment.getId()); if (methods == null) { //log.debug( "PROPERTY: method not found: '" + segment.getId() + "' current value: '" + value + "' (" + value.getClass() + ")"); value = null; break objectWalk; } else { value = methods[0].invoke(value, new Object[0]); //log.debug( "PROPERTY: found property value: " + value + " (" + value.getClass() + ")"); } } catch (Exception e) { //log.debug( "PROPERTY: method called failed: " + e.getMessage() ); value = null; break objectWalk; } break; // access a class method case QuerySegment.METHOD: // get the real value if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); //log.debug( "going after method: " + segment.getId() ); try { Method[] methods = getMethod(value.getClass(), segment.getId()); if (methods == null) { //log.debug( "METHOD: " + segment.getId() + " was not found." ); value = null; break objectWalk; } Iterator it = segment.getValues().iterator(); List params = new ArrayList(); while (it.hasNext()) { Object param = findValue((String) it.next()); params.add(param); } Method target = findMethod(methods, params); // Convert if necessary int paramCount = target.getParameterTypes().length; for (int i = 0; i < paramCount; i++) { Class parameterClass = target.getParameterTypes()[i]; //log.debug(paramCount + ": " + parameterClass.getName()); if (!parameterClass.equals(String.class) && !parameterClass.equals(params.get(i).getClass())) { // Get property editor PropertyEditor pe; pe = PropertyEditorManager.findEditor(parameterClass); // Convert value if (pe != null) { pe.setAsText(params.get(i).toString()); Object param = pe.getValue(); //replace with the converted value params.set(i, param); } } } //log.debug("METHOD: trying call: " + segment.getId() + " param: " + params); value = target.invoke(value, params.toArray()); } catch (Exception e) { //log.debug("METHOD: \"" + segment.getId() + "\" exception: \"" + e.getMessage() + "\""); value = null; break objectWalk; //throw new IllegalArgumentException("Invalid single method access. " + // "Error accessing method \"" + token.image + // "\" using parameter: \"" + param + "\""); } break; // access the current value as a collection case QuerySegment.COLLECTION: // get the real value if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); Object key = findValue(segment.getId()); if (key == null) { value = null; break objectWalk; } // Map if (Map.class.isAssignableFrom(value.getClass())) { value = ((Map) value).get(key.toString()); break; } // Resource Bundle else if (ResourceBundle.class.isAssignableFrom(value.getClass())) { value = ((ResourceBundle) value).getObject(key.toString()); break; } // Array else if (value.getClass().isArray()) { value = ((Object[]) value)[((Integer) key).intValue()]; break; } // List else if (List.class.isAssignableFrom(value.getClass())) { value = ((List) value).get(((Integer) key).intValue()); break; } // Collection else if (Collection.class.isAssignableFrom(value.getClass())) { // Not very efficient, but at least it works value = ((Collection) value).toArray()[((Integer) key).intValue()]; break; } // fail if the user tries to access something other than a Collection value = null; break objectWalk; // access the parent by going up one level on the stack case QuerySegment.PARENT: workStackIdx--; if (workStackIdx < 0) { value = null; break objectWalk; //throw new IllegalArgumentException("Parent object not available."); } else { value = workList.get(workStackIdx); } break; // currently we only allow to expand properties (no parameter methods) case QuerySegment.EXPAND: //log.debug( "EXPAND: going after: " + segment.getId() ); try { Object methodName = findValue(segment.getId()); if (methodName == null) { value = null; break objectWalk; } value = findValue(methodName.toString()); } catch (Exception e) { value = null; break objectWalk; } break; } //log.debug( "finished case statement" ); // always have the next segment ready to go segment = segments[segmentIdx++]; } while (segment != null); } // if we didn't find the value, then move one down the stack and // try again. if (value == null && stackIdx > 0) { stackIdx--; value = workList.get(stackIdx); // reset the segment index to reset the search segmentIdx = saveSegmentIdx; segment = segments[segmentIdx - 1]; } else { break; } } // get the real value if (value instanceof ValueHolder) value = ((ValueHolder) value).getValue(); if (value == null && log.isDebugEnabled()) log.debug("value for [" + query + "] is null."); return value; } /** * Return a string representation of the Stack * * @return the stack as a String */ public String toString() { String str = "Value stack\n"; str += "===========\n"; for (int i = 0; i < valueList.size(); i++) { Object val = valueList.get(i); str += val == null ? "null\n" : val.toString() + "\n"; } str += "===========\n"; return str; } /** * Get the parser associated with this ValueStack * * @return the Parser for this ValueStack */ private Parser getParser(String expression) { if (parser == null) { parser = new Parser(new StringReader(expression)); parser.setValueStack(this); } else { parser.ReInit(new StringReader(expression)); } return parser; } /** * Get a method with a given name. * * @param cl the class of the method * @param name the name of the method * @return the wanted method * @exception IntrospectionException */ private Method[] getMethod(Class cl, String name) throws IntrospectionException { Map methods = (Map) classes.get(cl); if (methods == null) { // Get methods that can be invoked for this class methods = new HashMap(); // Find get methods for properties BeanInfo bi = Introspector.getBeanInfo(cl); PropertyDescriptor[] pd = bi.getPropertyDescriptors(); for (int i = 0; i < pd.length; ++i) { Method method = pd[i].getReadMethod(); // Check if readable property if (method != null) { if (!Modifier.isPublic(cl.getModifiers())) { // Find a method in an interface that *is* public Class[] interfaces = bi.getBeanDescriptor().getBeanClass().getInterfaces(); for (int j = 0; j < interfaces.length; j++) { try { //log.debug("Try "+interfaces[j]); method = interfaces[j].getMethod(method.getName(), new Class[0]); break; } catch (Exception e) { // Ignore } } // We're in trouble! Try to sneak through security if (method.equals(pd[i].getReadMethod())) { AccessibleObject.setAccessible(new AccessibleObject[]{method}, true); } } // Save method in map //log.debug( "GET_METHOD: class: " + cl + " method: " + method.getName()); methods.put(pd[i].getName(), new Method[]{method}); } } // Find param methods Method[] getters = cl.getMethods(); for (int i = 0; i < getters.length; i++) { Method method = getters[i]; if (!method.getName().startsWith("set") && !method.getReturnType().equals(Void.TYPE)) { // Valid method // Check if public if (!Modifier.isPublic(cl.getModifiers())) { // Find a method in an interface that *is* public Class[] interfaces = cl.getInterfaces(); for (int j = 0; j < interfaces.length; j++) { try { method = interfaces[j].getMethod(method.getName(), method.getParameterTypes()); break; } catch (Exception e) { // Ignore } } // We're in trouble! Try to sneak through security if (method.equals(getters[i])) { AccessibleObject.setAccessible(new AccessibleObject[]{method}, true); } } // Get name String methodName = method.getName(); if (methodName.startsWith("get")) methodName = Introspector.decapitalize(methodName.substring(3)); else if (methodName.startsWith("is")) methodName = Introspector.decapitalize(methodName.substring(2)); // Save method in map Method[] current = (Method[]) methods.get(methodName); Method[] newlist = null; if (current == null) { newlist = new Method[]{method}; } else { newlist = new Method[current.length + 1]; System.arraycopy(current, 0, newlist, 0, current.length); newlist[current.length] = method; } methods.put(methodName, newlist); //log.debug( "GET_METHOD: class: " + cl + " method: " + method.getName()); } } // Add map to class map // RO: Use clone to avoid synchronization Map newClassMap = (Map) ((HashMap) classes).clone(); newClassMap.put(cl, methods); classes = newClassMap; //log.debug("Added "+cl+" to class map:"+methods); } // Get named method/property getter Method[] nameMethods = (Method[]) methods.get(name); //log.debug("Got "+cl+" "+name+":"+m); return nameMethods; } public Method findMethod(Method[] m, List params) { if (m.length == 1) return m[0]; List match = new ArrayList(); int noOfArgument = params.size(); for (int i = 0; i < m.length; i++) { if (m[i].getParameterTypes().length == noOfArgument) match.add(m[i]); } //log.debug("No of match for: " + m[0].getName() + " , " + noOfArgument + ", " + match.size()); if (match.size() == 1) return (Method) match.get(0); Method exact = null; List close = new ArrayList(); List convert = new ArrayList(); for (Iterator i = match.iterator(); i.hasNext();) { Method current = (Method) i.next(); Class[] paramClass = current.getParameterTypes(); boolean exactMatch = true; boolean closeMatch = true; boolean convertable = true; for (int j = 0; j < paramClass.length; j++) { Class p = params.get(j).getClass(); //log.debug("Argument: " + j + " , parameterClass: " + paramClass[j].getName() //+ " , argumentClass: " + p.getName()); if (paramClass[j].getName().equals(p.getName())) { continue; } else if (paramClass[j].isAssignableFrom(p)) { exactMatch = false; continue; } else { exactMatch = false; closeMatch = false; try { // Get property editor PropertyEditor pe; pe = PropertyEditorManager.findEditor(paramClass[j]); // Convert value pe.setAsText(params.get(j).toString()); pe.getValue(); } catch (Exception e) { convertable = false; } if (!convertable) break; } } if (exactMatch) { exact = current; break; } else if (closeMatch) { close.add(current); } else if (convertable) { convert.add(current); } } if (exact != null) return exact; else if (close.size() > 0) return (Method) close.get(0); else if (convert.size() > 0) return (Method) convert.get(0); else return null; } // Inner classes ------------------------------------------------- // Value providers that want to use lazy evaluation should use this // interface public interface ValueHolder { // Public ----------------------------------------------------- public Object getValue(); } } -- Snip ValueStack.java /* * WebWork, Web Application Framework * * Distributable under Apache license. * See terms of license at opensource.org */ package webwork.util; import javax.servlet.ServletRequest; import javax.servlet.jsp.PageContext; /** * Value stack implementation for a servlet based scenario. * * @author Rickard Öberg (ri...@mi...) * @author Maurice C. Parker (ma...@vi...) * @version $Revision: 1.31 $ */ public class ValueStack extends AbstractValueStack{ // Constructor --------------------------------------------------- public ValueStack() { super(); } /** * Get the value stack for a given request. If there is no * stack available, create one. * * @param request the request for which a stack shall be returned * @return the value stack */ public static ValueStack getStack(ServletRequest request) { ValueStack stack = (ValueStack) request.getAttribute(STACK_NAME); if (stack == null) { stack = new ValueStack(); request.setAttribute(STACK_NAME, stack); } stack.setRequest(request); return stack; } /** * Get the value stack for a given page context. If there is no * stack available, create one. * * @param context the page context to associate with the stack * @return the value stack */ public static ValueStack getStack(PageContext context) { ServletRequest request = context.getRequest(); ValueStack stack = (ValueStack) request.getAttribute(STACK_NAME); if (stack == null) { stack = new ValueStack(); request.setAttribute(STACK_NAME, stack); } stack.setRequest(request); stack.setContext(context); return stack; } private ServletRequest request; private PageContext context; // Private ------------------------------------------------------- /** * Set the request that should be associated with this stack. * This is called for each use of the stack so that attributes can be accessed * properly. * * @param request */ private void setRequest(ServletRequest request) { this.request = request; } /** * Set the page context that should be associated with this stack. * This is called for each use of the stack in a JSP context. * * @param request */ private void setContext(PageContext context) { this.context = context; } protected Object findInContext(String id){ if (context != null) { return context.findAttribute(id); // Used in a JSP environment } else { return request.getAttribute(id); // Rarely, if ever, used. Ignore } } protected String getRequestParameter(String id){ return request.getParameter(id); } } |