View Javadoc

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 &lt;-&gt; 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 }