Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Variable |
|
| 2.769230769230769;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 |