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        
086        /** Convenience constructor sets evaluted bean and log.
087          *
088          * @param bean evaluate expressions against this bean
089          * @param log log to this logger
090          * @param bindingConfiguration not null
091          */
092        public Context(Object bean, Log log, 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, 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    }