1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.betwixt.expression;
18
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.apache.commons.betwixt.BindingConfiguration;
23 import org.apache.commons.betwixt.Options;
24 import org.apache.commons.betwixt.strategy.IdStoringStrategy;
25 import org.apache.commons.betwixt.strategy.ObjectStringConverter;
26 import org.apache.commons.betwixt.strategy.ValueSuppressionStrategy;
27 import org.apache.commons.collections.ArrayStack;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 /** <p><code>Context</code> describes the context used to evaluate
32 * bean expressions.
33 * This is mostly a bean together with a number of context variables.
34 * Context variables are named objects.
35 * In other words,
36 * a context variable associates an object with a string.</p>
37 *
38 * <p> Logging during expression evaluation is done through the logging
39 * instance held by this class.
40 * The object initiating the evaluation should control this logging
41 * and so passing a <code>Log</code> instance is enforced by the constructors.</p>
42 *
43 * <p><code>Context</code> is a natural place to include shared evaluation code.
44 * One of the problems that you get with object graphs is that they can be cyclic.
45 * Xml cannot (directly) include cycles.
46 * Therefore <code>betwixt</code> needs to find and deal properly with cycles.
47 * The algorithm used is to check the parentage of a new child.
48 * If the child is a parent then that operation fails. </p>
49 *
50 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
51 */
52 public class Context {
53
54 /** Evaluate this bean */
55 private Object bean;
56 /** Variables map */
57 private Map variables;
58 /** Store options */
59 private ArrayStack optionStack = new ArrayStack();
60 /**
61 * Logging uses commons-logging <code>Log</code>
62 * named <code>org.apache.commons.betwixt</code>
63 */
64 private Log log;
65 /** Configuration for dynamic binding properties */
66 private BindingConfiguration bindingConfiguration;
67
68 /**
69 * Construct context with default log
70 */
71 public Context() {
72 this( null, LogFactory.getLog( Context.class ) );
73 }
74
75 /** Convenience constructor sets evaluted bean and log.
76 *
77 * @param bean evaluate expressions against this bean
78 * @param log log to this logger
79 * @deprecated 0.5 use constructor which takes a BindingConfiguration
80 */
81 public Context(Object bean, Log log) {
82 this( bean, log, new BindingConfiguration() );
83 }
84
85
86 /** Convenience constructor sets evaluted bean and log.
87 *
88 * @param bean evaluate expressions against this bean
89 * @param log log to this logger
90 * @param bindingConfiguration not null
91 */
92 public Context(Object bean, Log log, BindingConfiguration bindingConfiguration) {
93 this( bean, new HashMap(), log, bindingConfiguration );
94 }
95
96 /**
97 * Construct a cloned context.
98 * The constructed context should share bean, variables, log and binding configuration.
99 * @param context duplicate the attributes of this bean
100 */
101 public Context( Context context ) {
102 this(context.bean, context.variables, context.log, context.bindingConfiguration);
103 }
104
105
106 /** Convenience constructor sets evaluted bean, context variables and log.
107 *
108 * @param bean evaluate expressions against this bean
109 * @param variables context variables
110 * @param log log to this logger
111 * @deprecated 0.5 use constructor which takes a converter
112 */
113 public Context(Object bean, Map variables, Log log) {
114 this( bean, variables, log, new BindingConfiguration() );
115 }
116
117 /** Convenience constructor sets evaluted bean, context variables and log.
118 *
119 * @param bean evaluate expressions against this bean
120 * @param variables context variables
121 * @param log log to this logger
122 * @param bindingConfiguration not null
123 */
124 public Context(Object bean, Map variables, Log log, BindingConfiguration bindingConfiguration) {
125 this.bean = bean;
126 this.variables = variables;
127 this.log = log;
128 this.bindingConfiguration = bindingConfiguration;
129 }
130
131 /** Returns a new child context with the given bean but the same log and variables.
132 *
133 * @param newBean create a child context for this bean
134 * @return new Context with new bean but shared variables
135 */
136 // TODO: need to think about whether this is a good idea and how subclasses
137 // should handle this
138 public Context newContext(Object newBean) {
139 Context context = new Context(this);
140 context.setBean( newBean );
141 return context;
142 }
143
144 /**
145 * Gets the current bean.
146 * @return the bean against which expressions are evaluated
147 */
148 public Object getBean() {
149 return bean;
150 }
151
152 /**
153 * Set the current bean.
154 * @param bean the Object against which expressions will be evaluated
155 */
156 public void setBean(Object bean) {
157 this.bean = bean;
158 }
159
160 /**
161 * Gets context variables.
162 * @return map containing variable values keyed by variable name
163 */
164 public Map getVariables() {
165 return variables;
166 }
167
168 /**
169 * Sets context variables.
170 * @param variables map containing variable values indexed by varibable name Strings
171 */
172 public void setVariables(Map variables) {
173 this.variables = variables;
174 }
175
176 /**
177 * Gets the value of a particular context variable.
178 * @param name the name of the variable whose value is to be returned
179 * @return the variable value or null if the variable isn't set
180 */
181 public Object getVariable(String name) {
182 return variables.get( name );
183 }
184
185 /**
186 * Sets the value of a particular context variable.
187 * @param name the name of the variable
188 * @param value the value of the variable
189 */
190 public void setVariable(String name, Object value) {
191 variables.put( name, value );
192 }
193
194 /**
195 * Gets the current log.
196 *
197 * @return the implementation to which this class logs
198 */
199 public Log getLog() {
200 return log;
201 }
202
203 /**
204 * Set the log implementation to which this class logs
205 *
206 * @param log the implemetation that this class should log to
207 */
208 public void setLog(Log log) {
209 this.log = log;
210 }
211
212 /**
213 * Gets object <-> string converter.
214 * @return the Converter to be used for conversions, not null
215 * @since 0.5
216 */
217 public ObjectStringConverter getObjectStringConverter() {
218 return bindingConfiguration.getObjectStringConverter();
219 }
220
221 /**
222 * Should <code>ID</code>'s and <code>IDREF</code> attributes
223 * be used to cross-reference matching objects?
224 *
225 * @return true if <code>ID</code> and <code>IDREF</code>
226 * attributes should be used to cross-reference instances
227 * @since 0.5
228 */
229 public boolean getMapIDs() {
230 return bindingConfiguration.getMapIDs();
231 }
232
233 /**
234 * The name of the attribute which can be specified in the XML to override the
235 * type of a bean used at a certain point in the schema.
236 *
237 * <p>The default value is 'className'.</p>
238 *
239 * @return The name of the attribute used to overload the class name of a bean
240 * @since 0.5
241 */
242 public String getClassNameAttribute() {
243 return bindingConfiguration.getClassNameAttribute();
244 }
245
246 /**
247 * Sets the name of the attribute which can be specified in
248 * the XML to override the type of a bean used at a certain
249 * point in the schema.
250 *
251 * <p>The default value is 'className'.</p>
252 *
253 * @param classNameAttribute The name of the attribute used to overload the class name of a bean
254 * @since 0.5
255 */
256 public void setClassNameAttribute(String classNameAttribute) {
257 bindingConfiguration.setClassNameAttribute( classNameAttribute );
258 }
259
260 /**
261 * Gets the <code>ValueSuppressionStrategy</code>.
262 * This is used to control the expression of attributes with certain values.
263 * @since 0.7
264 * @return <code>ValueSuppressionStrategy</code>, not null
265 */
266 public ValueSuppressionStrategy getValueSuppressionStrategy() {
267 return bindingConfiguration.getValueSuppressionStrategy();
268 }
269
270 /**
271 * Sets the <code>ValueSuppressionStrategy</code>.
272 * This is used to control the expression of attributes with certain values.
273 * @since 0.7
274 * @param valueSuppressionStrategy <code>ValueSuppressionStrategy</code>, not null
275 */
276 public void setValueSuppressionStrategy(
277 ValueSuppressionStrategy valueSuppressionStrategy) {
278 bindingConfiguration.setValueSuppressionStrategy(valueSuppressionStrategy);
279 }
280
281 /**
282 * Gets the strategy used to manage storage and retrieval of id's.
283 * @since 0.7
284 * @return Returns the idStoringStrategy, not null
285 */
286 public IdStoringStrategy getIdMappingStrategy() {
287 return bindingConfiguration.getIdMappingStrategy();
288 }
289
290 /**
291 * Gets the current <code>Options</code>.
292 * @return <code>Options</code> that currently apply
293 * or null if there are no current options.
294 * @since 0.7
295 */
296 public Options getOptions() {
297 Options results = null;
298 if (!optionStack.isEmpty()) {
299 results = (Options) optionStack.peek();
300 }
301 return results;
302 }
303
304 /**
305 * <p>Pushes the given <code>Options</code> onto the stack.
306 * </p><p>
307 * <strong>Note</strong> that code calling push should ensure that {@link #popOptions}
308 * is called once the options are no longer current.
309 * This ensures that the previous options are reinstated.
310 * </p>
311 * @since 0.7
312 * @param options newly current <code>Options</code>, not null
313 */
314 public void pushOptions(Options options) {
315 optionStack.push(options);
316 }
317
318 /**
319 * <p>Pops the current options from the stack.
320 * The previously current options (if any exist)
321 * will be reinstated by this method.
322 * </p><p>
323 * <stong>Note</strong> code calling this method should
324 * have previsouly called {@link #popOptions}.
325 * @since 0.7
326 */
327 public void popOptions() {
328 if (optionStack.isEmpty()) {
329 log.debug("Cannot pop options off empty stack");
330 } else {
331 optionStack.pop();
332 }
333 }
334
335 /**
336 * Gets the value of the first option with this name.
337 * The stack of inherited options is search (starting
338 * from the current option) until an option with a non-null
339 * value for the named option is found.
340 *
341 * @param name the name of the option to be found
342 * @return option value or null if this value is never set
343 * @since 0.8
344 */
345 public String getInheritedOption(String name) {
346 String result = null;
347 for (int i=0; i<optionStack.size() ; i++)
348 {
349 Options options = (Options) optionStack.peek(i);
350 if (options != null)
351 {
352 result = options.getValue(name);
353 if (result != null)
354 {
355 break;
356 }
357 }
358 }
359 return result;
360 }
361
362 }