Coverage Report - org.webmacro.engine.Variable
 
Classes in this File Line Coverage Branch Coverage Complexity
Variable
62%
40/64
59%
19/32
2.769
 
 1  
 /*
 2  
  * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
 3  
  *
 4  
  * Redistribution and use in source and binary forms, with or without
 5  
  * modification, are permitted under the terms of either of the following
 6  
  * Open Source licenses:
 7  
  *
 8  
  * The GNU General Public License, version 2, or any later version, as
 9  
  * published by the Free Software Foundation
 10  
  * (http://www.fsf.org/copyleft/gpl.html);
 11  
  *
 12  
  *  or
 13  
  *
 14  
  * The Semiotek Public License (http://webmacro.org/LICENSE.)
 15  
  *
 16  
  * This software is provided "as is", with NO WARRANTY, not even the
 17  
  * implied warranties of fitness to purpose, or merchantability. You
 18  
  * assume all risks and liabilities associated with its use.
 19  
  *
 20  
  * See www.webmacro.org for more information on the WebMacro project.
 21  
  */
 22  
 
 23  
 
 24  
 package org.webmacro.engine;
 25  
 
 26  
 import java.io.IOException;
 27  
 
 28  
 import org.webmacro.Context;
 29  
 import org.webmacro.FastWriter;
 30  
 import org.webmacro.Macro;
 31  
 import org.webmacro.PropertyException;
 32  
 import org.webmacro.TemplateVisitor;
 33  
 import org.webmacro.Visitable;
 34  
 import org.webmacro.util.Named;
 35  
 
 36  
 // PRIMARY CLASS: Variable
 37  
 
 38  
 /**
 39  
  * A Variable is a reference into a Propertymap.
 40  
  * <p>
 41  
  * A variable name contains a list of names separated by dots, for
 42  
  * example "$User.Identity.email.address" is the list: User, Identity,
 43  
  * email, and address.
 44  
  * <p>
 45  
  * PLEASE NOTE: Case-sensitivity is enforced. "User" is the the same
 46  
  * name as "user".
 47  
  * <p>
 48  
  * What that means: When a template is interpreted, it is interpreted
 49  
  * in terms of data in a hashtable/map called the "context". This is
 50  
  * actually a Map of type Map. The context contains all the
 51  
  * local variables that have been set, as well as other information
 52  
  * that Macros may use to evaluate the request.
 53  
  * <p>
 54  
  * Variable depends heavily on Property introspection: It is defined
 55  
  * as a list of one or more names (separated by dots when written).
 56  
  * <p>
 57  
  * Those names are references to sub-objects within the context. The
 58  
  * Variable instance, when interpreted, will decend through the context
 59  
  * following fields, method references, or hash table look-ups based
 60  
  * on its names.
 61  
  * <p>
 62  
  * For example, the variable "$User.Identity.email.address" implies
 63  
  * that there is a "User" object under the Map--either it is
 64  
  * a field within the map, or the map has a getUser() method, or
 65  
  * the User can be obtained by calling Map.get("User").
 66  
  * <p>
 67  
  * The full expansion of $User.Identity.email.address might be:<pre>
 68  
  *
 69  
  *    Map.get("User").getIdentity().get("email").address
 70  
  *
 71  
  * </pre>. Variable (actually the Property class it uses) will figure
 72  
  * out how to decend through the object like this until it finds the
 73  
  * final reference--which is the "value" of the variable.
 74  
  * <p>
 75  
  * When searchin for subfields Variable prefers fields over getFoo()
 76  
  * methods, and getFoo() over get("Foo").
 77  
  *
 78  
  */
 79  
 public abstract class Variable implements Macro, Visitable
 80  
 {
 81  
 
 82  
 
 83  
     // null: because in BuildContext.getVariableType() we can just
 84  
     // return null from a HashMap for a never before heard of variable
 85  
     // to mean that it is a PROPERTY_TYPE. Only this code right here
 86  
     // and BuildContext.getVariableType() needs to know that.
 87  2
     final static public Object PROPERTY_TYPE = new Object();
 88  2
     final static public Object LOCAL_TYPE = new Object();
 89  
 
 90  
     /**
 91  
      * The name of this variable.
 92  
      */
 93  
     private String _vname;
 94  
 
 95  
     /**
 96  
      * The name as an array.
 97  
      */
 98  
     protected Object[] _names;
 99  
 
 100  
     /**
 101  
      * Create a variable with the supplied name. The elements of the name
 102  
      * are either strings, or a method reference.
 103  
      */
 104  
     Variable (Object names[])
 105  5834
     {
 106  5834
         _names = names;
 107  
 
 108  5834
     }
 109  
 
 110  
     /**
 111  
      * Return the property names for this variable. These are stringified
 112  
      * names corresponding to the names of the variable; if one of the
 113  
      * elements of the variable name is a method call then the name of
 114  
      * the method is inserted at that point as if it were a property name.
 115  
      */
 116  
     static final String[] makePropertyNames (Object names[])
 117  
     {
 118  0
         String[] sn = new String[names.length];
 119  0
         for (int i = 0; i < sn.length; i++)
 120  
         {
 121  0
             sn[i] = (names[i] instanceof Named) ?
 122  
                     ((Named) names[i]).getName() : (String) names[i];
 123  
         }
 124  0
         return sn;
 125  
     }
 126  
 
 127  
     public final String[] getPropertyNames ()
 128  
     {
 129  0
         return makePropertyNames(_names);
 130  
     }
 131  
 
 132  
     /**
 133  
      * Like getPropertyNames, but only works if isSimpleName is true.
 134  
      */
 135  
     public final String getName ()
 136  
     {
 137  0
         return (_names[0] instanceof Named) ?
 138  
                 ((Named) _names[0]).getName()
 139  
                 : (String) _names[0];
 140  
     }
 141  
 
 142  
     /**
 143  
      * Returns true if the Variable describes a simple name (one with only
 144  
      * one element).
 145  
      */
 146  
     public boolean isSimpleName ()
 147  
     {
 148  26
         return (_names.length == 1);
 149  
     }
 150  
 
 151  
     /**
 152  
      * Looks in the hashTable (context) for a value keyed to this variables
 153  
      * name and returns the value string. If the resulting value is a Macro,
 154  
      * recursively call its evaluate method.
 155  
      * @return String
 156  
      */
 157  
     final public Object evaluate (Context context) throws PropertyException
 158  
     {
 159  
         try
 160  
         {
 161  97452
             Object val = getValue(context);
 162  97430
             if (val instanceof Macro)
 163  
             {
 164  11038
                 val = ((Macro) val).evaluate(context); // recurse
 165  
             }
 166  97430
             return val;
 167  
         }
 168  0
         catch (NullPointerException e)
 169  
         {
 170  
             // May throw
 171  0
             context.getEvaluationExceptionHandler()
 172  
                     .evaluate(this, context,
 173  
                               new PropertyException.NullValueException(getVariableName()));
 174  0
             return null;
 175  
         }
 176  22
         catch (PropertyException e)
 177  
         {
 178  
             // May throw
 179  22
             if (e instanceof PropertyException.UndefinedVariableException)
 180  
             {
 181  0
                 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
 182  0
                 if (_names.length > 1)
 183  0
                     uve.setMessage(
 184  
                             "Attempted to reference a property or method of an undefined variable: $" + _names[0]);
 185  
                 else
 186  0
                     uve.setMessage(
 187  
                             "Attempted to evaluate an undefined variable: $" + _names[0]);
 188  
             }
 189  22
             context.getEvaluationExceptionHandler()
 190  
                     .evaluate(this, context, e);
 191  0
             return null;
 192  
         }
 193  0
         catch (Exception e)
 194  
         {
 195  
             // May throw
 196  0
             context.getEvaluationExceptionHandler()
 197  
                     .evaluate(this, context,
 198  
                             new PropertyException("Variable: exception evaluating "
 199  
                                                   + getVariableName(), e));
 200  0
             return null;
 201  
         }
 202  
     }
 203  
 
 204  
     /**
 205  
      * Look in the hashtable (context) for a value keyed to this variables
 206  
      * name and write its value to the stream.
 207  
      * @exception PropertyException is required data is missing
 208  
      * @exception IOException if could not write to output stream
 209  
      */
 210  
     final public void write (FastWriter out, Context context)
 211  
             throws PropertyException, IOException
 212  
     {
 213  
         try
 214  
         {
 215  63978
             Object val = getValue(context);
 216  63956
             if (val instanceof Macro)
 217  4
                 ((Macro) val).write(out, context);
 218  
             else
 219  
             {
 220  63952
                 if (val != null)
 221  
                 {
 222  63926
                     String v = val.toString();
 223  63926
                     if (v != null)
 224  63926
                         out.write(v);
 225  
                     else
 226  
                     {
 227  0
                         out.write(context.getEvaluationExceptionHandler()
 228  
                                 .expand(this, context,
 229  
                                         new PropertyException.NullToStringException(getVariableName())));
 230  
                     }
 231  63926
                 }
 232  
                 else
 233  
                 {
 234  26
                     if (isSimpleName())
 235  
                     {
 236  
                         // user accessed a variable that isn't in the context
 237  
                         //     $ObjectNotInContext
 238  0
                         out.write(context.getEvaluationExceptionHandler()
 239  
                                 .expand(this, context,
 240  
                                         new PropertyException.NoSuchVariableException(getVariableName())));
 241  
                     }
 242  
                     else
 243  
                     {
 244  
                         // user accessed a valid property who's value is null
 245  26
                         out.write(context.getEvaluationExceptionHandler()
 246  
                                 .expand(this, context,
 247  
                                         new PropertyException.NullValueException(getVariableName())));
 248  
                     }
 249  
                 }
 250  
             }
 251  
         }
 252  26
         catch (PropertyException e)
 253  
         {
 254  26
             if (e instanceof PropertyException.UndefinedVariableException)
 255  
             {
 256  4
                 PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
 257  4
                 if (_names.length > 1)
 258  0
                     uve.setMessage(
 259  
                             "Attempted to write a property or method value of an undefined variable: $" + _names[0]);
 260  
                 else
 261  4
                     uve.setMessage(
 262  
                             "Attempted to write an undefined variable: $" + _names[0]);
 263  
             }
 264  26
             out.write(context.getEvaluationExceptionHandler()
 265  
                     .expand(this, context, e));
 266  
         }
 267  0
         catch (Exception e)
 268  
         {
 269  
             // something we weren't expecting happened!
 270  
             // I wonder if we would ever get here?  --eric
 271  0
             out.write(context.getEvaluationExceptionHandler()
 272  
                     .expand(this, context, e));
 273  63962
         }
 274  63962
     }
 275  
 
 276  
     /**
 277  
      * Helper method to construct a String name from a Object[] name.
 278  
      */
 279  
     final static String makeName (Object[] names)
 280  
     {
 281  12
         StringBuffer buf = new StringBuffer();
 282  32
         for (int i = 0; i < names.length; i++)
 283  
         {
 284  20
             if (i != 0)
 285  
             {
 286  8
                 buf.append(".");
 287  
             }
 288  20
             buf.append(names[i]);
 289  
         }
 290  12
         return buf.toString();
 291  
     }
 292  
 
 293  
     /**
 294  
      * The code to get the value represented by the variable from the
 295  
      * supplied context.
 296  
      */
 297  
     public abstract Object getValue (Context context) throws PropertyException;
 298  
 
 299  
     /**
 300  
      * The code to set the value represented by the variable in the
 301  
      * supplied context.
 302  
      */
 303  
     public abstract void setValue (Context c, Object v) throws PropertyException;
 304  
 
 305  
     /**
 306  
      * Return the String name of the variable prefixed with a string
 307  
      * representing its type. For example local:a.b.c
 308  
      */
 309  
     public abstract String toString ();
 310  
 
 311  
     /**
 312  
      * Return the canonical name for this variable.
 313  
      */
 314  
     public synchronized String getVariableName ()
 315  
     {
 316  38
       if (_vname == null) {
 317  12
         _vname = makeName(_names).intern();
 318  
       }
 319  38
       return _vname;
 320  
     }
 321  
 
 322  
     public void accept (TemplateVisitor v)
 323  
     {
 324  0
         v.visitVariable(this, _names);
 325  0
     }
 326  
 
 327  
 }
 328  
 
 329  
 
 330