001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.betwixt.expression;
018:
019: import java.util.HashMap;
020: import java.util.Map;
021:
022: import org.apache.commons.betwixt.BindingConfiguration;
023: import org.apache.commons.betwixt.Options;
024: import org.apache.commons.betwixt.strategy.IdStoringStrategy;
025: import org.apache.commons.betwixt.strategy.ObjectStringConverter;
026: import org.apache.commons.betwixt.strategy.ValueSuppressionStrategy;
027: import org.apache.commons.collections.ArrayStack;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: /** <p><code>Context</code> describes the context used to evaluate
032: * bean expressions.
033: * This is mostly a bean together with a number of context variables.
034: * Context variables are named objects.
035: * In other words,
036: * a context variable associates an object with a string.</p>
037: *
038: * <p> Logging during expression evaluation is done through the logging
039: * instance held by this class.
040: * The object initiating the evaluation should control this logging
041: * and so passing a <code>Log</code> instance is enforced by the constructors.</p>
042: *
043: * <p><code>Context</code> is a natural place to include shared evaluation code.
044: * One of the problems that you get with object graphs is that they can be cyclic.
045: * Xml cannot (directly) include cycles.
046: * Therefore <code>betwixt</code> needs to find and deal properly with cycles.
047: * The algorithm used is to check the parentage of a new child.
048: * If the child is a parent then that operation fails. </p>
049: *
050: * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
051: */
052: public class Context {
053:
054: /** Evaluate this bean */
055: private Object bean;
056: /** Variables map */
057: private Map variables;
058: /** Store options */
059: private ArrayStack optionStack = new ArrayStack();
060: /**
061: * Logging uses commons-logging <code>Log</code>
062: * named <code>org.apache.commons.betwixt</code>
063: */
064: private Log log;
065: /** Configuration for dynamic binding properties */
066: private BindingConfiguration bindingConfiguration;
067:
068: /**
069: * Construct context with default log
070: */
071: public Context() {
072: this (null, LogFactory.getLog(Context.class));
073: }
074:
075: /** Convenience constructor sets evaluted bean and log.
076: *
077: * @param bean evaluate expressions against this bean
078: * @param log log to this logger
079: * @deprecated 0.5 use constructor which takes a BindingConfiguration
080: */
081: public Context(Object bean, Log log) {
082: this (bean, log, new BindingConfiguration());
083: }
084:
085: /** Convenience constructor sets evaluted bean and log.
086: *
087: * @param bean evaluate expressions against this bean
088: * @param log log to this logger
089: * @param bindingConfiguration not null
090: */
091: public Context(Object bean, Log log,
092: BindingConfiguration bindingConfiguration) {
093: this (bean, new HashMap(), log, bindingConfiguration);
094: }
095:
096: /**
097: * Construct a cloned context.
098: * The constructed context should share bean, variables, log and binding configuration.
099: * @param context duplicate the attributes of this bean
100: */
101: public Context(Context context) {
102: this (context.bean, context.variables, context.log,
103: context.bindingConfiguration);
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,
125: BindingConfiguration bindingConfiguration) {
126: this .bean = bean;
127: this .variables = variables;
128: this .log = log;
129: this .bindingConfiguration = bindingConfiguration;
130: }
131:
132: /** Returns a new child context with the given bean but the same log and variables.
133: *
134: * @param newBean create a child context for this bean
135: * @return new Context with new bean but shared variables
136: */
137: // TODO: need to think about whether this is a good idea and how subclasses
138: // should handle this
139: public Context newContext(Object newBean) {
140: Context context = new Context(this );
141: context.setBean(newBean);
142: return context;
143: }
144:
145: /**
146: * Gets the current bean.
147: * @return the bean against which expressions are evaluated
148: */
149: public Object getBean() {
150: return bean;
151: }
152:
153: /**
154: * Set the current bean.
155: * @param bean the Object against which expressions will be evaluated
156: */
157: public void setBean(Object bean) {
158: this .bean = bean;
159: }
160:
161: /**
162: * Gets context variables.
163: * @return map containing variable values keyed by variable name
164: */
165: public Map getVariables() {
166: return variables;
167: }
168:
169: /**
170: * Sets context variables.
171: * @param variables map containing variable values indexed by varibable name Strings
172: */
173: public void setVariables(Map variables) {
174: this .variables = variables;
175: }
176:
177: /**
178: * Gets the value of a particular context variable.
179: * @param name the name of the variable whose value is to be returned
180: * @return the variable value or null if the variable isn't set
181: */
182: public Object getVariable(String name) {
183: return variables.get(name);
184: }
185:
186: /**
187: * Sets the value of a particular context variable.
188: * @param name the name of the variable
189: * @param value the value of the variable
190: */
191: public void setVariable(String name, Object value) {
192: variables.put(name, value);
193: }
194:
195: /**
196: * Gets the current log.
197: *
198: * @return the implementation to which this class logs
199: */
200: public Log getLog() {
201: return log;
202: }
203:
204: /**
205: * Set the log implementation to which this class logs
206: *
207: * @param log the implemetation that this class should log to
208: */
209: public void setLog(Log log) {
210: this .log = log;
211: }
212:
213: /**
214: * Gets object <-> string converter.
215: * @return the Converter to be used for conversions, not null
216: * @since 0.5
217: */
218: public ObjectStringConverter getObjectStringConverter() {
219: return bindingConfiguration.getObjectStringConverter();
220: }
221:
222: /**
223: * Should <code>ID</code>'s and <code>IDREF</code> attributes
224: * be used to cross-reference matching objects?
225: *
226: * @return true if <code>ID</code> and <code>IDREF</code>
227: * attributes should be used to cross-reference instances
228: * @since 0.5
229: */
230: public boolean getMapIDs() {
231: return bindingConfiguration.getMapIDs();
232: }
233:
234: /**
235: * The name of the attribute which can be specified in the XML to override the
236: * type of a bean used at a certain point in the schema.
237: *
238: * <p>The default value is 'className'.</p>
239: *
240: * @return The name of the attribute used to overload the class name of a bean
241: * @since 0.5
242: */
243: public String getClassNameAttribute() {
244: return bindingConfiguration.getClassNameAttribute();
245: }
246:
247: /**
248: * Sets the name of the attribute which can be specified in
249: * the XML to override the type of a bean used at a certain
250: * point in the schema.
251: *
252: * <p>The default value is 'className'.</p>
253: *
254: * @param classNameAttribute The name of the attribute used to overload the class name of a bean
255: * @since 0.5
256: */
257: public void setClassNameAttribute(String classNameAttribute) {
258: bindingConfiguration.setClassNameAttribute(classNameAttribute);
259: }
260:
261: /**
262: * Gets the <code>ValueSuppressionStrategy</code>.
263: * This is used to control the expression of attributes with certain values.
264: * @since 0.7
265: * @return <code>ValueSuppressionStrategy</code>, not null
266: */
267: public ValueSuppressionStrategy getValueSuppressionStrategy() {
268: return bindingConfiguration.getValueSuppressionStrategy();
269: }
270:
271: /**
272: * Sets the <code>ValueSuppressionStrategy</code>.
273: * This is used to control the expression of attributes with certain values.
274: * @since 0.7
275: * @param valueSuppressionStrategy <code>ValueSuppressionStrategy</code>, not null
276: */
277: public void setValueSuppressionStrategy(
278: ValueSuppressionStrategy valueSuppressionStrategy) {
279: bindingConfiguration
280: .setValueSuppressionStrategy(valueSuppressionStrategy);
281: }
282:
283: /**
284: * Gets the strategy used to manage storage and retrieval of id's.
285: * @since 0.7
286: * @return Returns the idStoringStrategy, not null
287: */
288: public IdStoringStrategy getIdMappingStrategy() {
289: return bindingConfiguration.getIdMappingStrategy();
290: }
291:
292: /**
293: * Gets the current <code>Options</code>.
294: * @return <code>Options</code> that currently apply
295: * or null if there are no current options.
296: * @since 0.7
297: */
298: public Options getOptions() {
299: Options results = null;
300: if (!optionStack.isEmpty()) {
301: results = (Options) optionStack.peek();
302: }
303: return results;
304: }
305:
306: /**
307: * <p>Pushes the given <code>Options</code> onto the stack.
308: * </p><p>
309: * <strong>Note</strong> that code calling push should ensure that {@link #popOptions}
310: * is called once the options are no longer current.
311: * This ensures that the previous options are reinstated.
312: * </p>
313: * @since 0.7
314: * @param options newly current <code>Options</code>, not null
315: */
316: public void pushOptions(Options options) {
317: optionStack.push(options);
318: }
319:
320: /**
321: * <p>Pops the current options from the stack.
322: * The previously current options (if any exist)
323: * will be reinstated by this method.
324: * </p><p>
325: * <stong>Note</strong> code calling this method should
326: * have previsouly called {@link #popOptions}.
327: * @since 0.7
328: */
329: public void popOptions() {
330: if (optionStack.isEmpty()) {
331: log.debug("Cannot pop options off empty stack");
332: } else {
333: optionStack.pop();
334: }
335: }
336:
337: /**
338: * Gets the value of the first option with this name.
339: * The stack of inherited options is search (starting
340: * from the current option) until an option with a non-null
341: * value for the named option is found.
342: *
343: * @param name the name of the option to be found
344: * @return option value or null if this value is never set
345: * @since 0.8
346: */
347: public String getInheritedOption(String name) {
348: String result = null;
349: for (int i = 0; i < optionStack.size(); i++) {
350: Options options = (Options) optionStack.peek(i);
351: if (options != null) {
352: result = options.getValue(name);
353: if (result != null) {
354: break;
355: }
356: }
357: }
358: return result;
359: }
360:
361: }
|