View Javadoc

1   /*
2       Jameleon - An automation testing tool..
3       Copyright (C) 2003-2006 Christian W. Hargraves (engrean@hotmail.com)
4       
5       This library is free software; you can redistribute it and/or
6       modify it under the terms of the GNU Lesser General Public
7       License as published by the Free Software Foundation; either
8       version 2.1 of the License, or (at your option) any later version.
9   
10      This library is distributed in the hope that it will be useful,
11      but WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13      Lesser General Public License for more details.
14  
15      You should have received a copy of the GNU Lesser General Public
16      License along with this library; if not, write to the Free Software
17      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  */
19  package net.sf.jameleon.function;
20  
21  import net.sf.jameleon.JameleonTagSupport;
22  import net.sf.jameleon.bean.Attribute;
23  import net.sf.jameleon.exception.JameleonException;
24  
25  import org.apache.commons.jelly.JellyContext;
26  import org.apache.commons.jelly.MissingAttributeException;
27  
28  import java.io.File;
29  import java.util.Iterator;
30  import java.util.HashMap;
31  import java.util.Map;
32  import java.util.List;
33  import java.lang.reflect.Field;
34  
35  /***
36   * The <code>AttributeBroker</code> class is used to copy values from
37   * a <code>JellyContext</code> or the script directory to instance variables 
38   * in an <code>Attributable</code>.
39   */
40  public class AttributeBroker {
41      /***
42       * The instance of the Attributable to which the values will be transfered to
43       */
44      protected Attributable consumer;
45      /***
46       * A List of attributes or instance variables of the consumer
47       */
48      protected Map attributes = new HashMap();
49  
50      public static final boolean REQUIRED = true;
51      public static final boolean NOT_REQUIRED = false;
52      public static final boolean OPTIONAL = false;
53  
54      /***
55       * An Attributable is required to instaniate this class.
56       */
57      public AttributeBroker(Attributable consumer) {
58          this.consumer = consumer;
59      }
60  
61      /***
62       * Gets the attributes registered for this Attributable
63       */
64      public Map getAttributes(){
65          return attributes;
66      }
67  
68      /***
69       * Add an attribute to the list of instance variables supported by the <code>consumer</code>
70       */
71      public void registerAttribute(Attribute attr) {
72          if (attributes.get(attr.getName()) == null) {
73              attributes.put(attr.getName(), attr);
74          }
75      }
76  
77      /***
78       * Calls the <code>Attributable.describeAttributes()</code> method.
79       * This needs to be called before anything else is called
80       */
81      public void setUp() {
82          consumer.describeAttributes(this);
83      }
84  
85      /***
86       * Copy all variables from the context (JellyContext) to instances variables registered in the
87       * <code>Attributable</code> instance.
88       * @param context - The context that stores the <code>Attributable</code>-independent key/value
89       *                  pairs which will be used to set the instance variables of the <code>Attributable</code>
90       *                  to.
91       */
92      public void transferAttributes(JellyContext context) {
93          if (attributes == null || attributes.size() == 0) return;
94          Iterator it = attributes.keySet().iterator();
95          Attribute attr = null;
96          while (it.hasNext()) {
97              attr = (Attribute)attributes.get(it.next());
98              String name = attr.getName();
99              if ( name != null && !attr.isValueSet() ) {
100                 if (attr.isInstanceVariable() && (attr.isContextVariable() || attr.getDefaultValue() != null)) {
101                     Object value = getValueFromContext(context, attr.getContextName(), attr.getDefaultValue());
102                     setConsumerAttribute(attr, value);
103                 }else if (!attr.isInstanceVariable() && attr.getValue() == null) {
104                     //Only set the value if it hasn't already been set. This is basically for attributes that are 
105                     //read in from external data sources (CSV, properties ... )
106                     //This means the attribute is a set method. Basically, we are setting the value to the value 
107                     //in the context.
108                     attr.setValue(ContextHelper.getVariable(context, name));
109                 }
110             }
111         }
112     }
113 
114     /***
115      * Validates that all context variables marked as <code>required</code> are set.
116      * @param context The JellyContext of the tag.
117      * @throws MissingAttributeException if any required attributes were not set.
118      */
119     public void validate(JellyContext context) throws MissingAttributeException {
120         Iterator it = attributes.keySet().iterator();
121         StringBuffer errors = new StringBuffer();
122         Attribute attr = null;
123         while (it.hasNext()) {
124             attr = (Attribute)attributes.get(it.next());
125             if (attr.isRequired() && !attr.isValueSet()) {
126                 if (getAttributeValue(attr, context) == null) {
127                     if (errors.length() > 0) {
128                         errors.append(",");
129                     }
130                     errors.append(attr);
131                 }
132             }
133         }
134         if (errors.length() > 0) {
135             throw new MissingAttributeException(errors.toString());
136         }
137     }
138 
139     // Implementation
140     
141     /***
142      * Attempts to get the value for the given key (<code>contextName</code>) from the context.
143      * If a value is not found from the context, then a defaultValue is used.
144      * @param context - A JellyContext of key/value pairs.
145      * @param contextName - The key from the context
146      * @param defaultValue - A value to return only if the variable is not set in the context.
147      * @return The value for the given context variable.
148      */
149     protected Object getValueFromContext(JellyContext context, String contextName, String defaultValue) {
150         Object value = ContextHelper.getVariable(context, contextName);
151 
152         if (value == null) {
153             value = defaultValue;
154         }
155         return value;
156     }
157     
158     /***
159      * Gets the real-time value of <code>attr</code> for the consumer
160      * @param attr - The attr that represents the property's value to be returned.
161      * @return The real-time value of the attribute.
162      */
163     public Object getAttributeValue(Attribute attr, JellyContext context){
164         Object value = null;
165         if (attr.isInstanceVariable()) {
166             value = getAttributeValueFromInstance(attr);
167         }else if (attr.getContextName() != null && attr.getContextName().length() > 0) {
168             value = ContextHelper.getVariable(context, attr.getContextName());
169         }else{
170             //Used to support set methods. This only works when the method  is set via the 
171         	//script directly.
172             //To always keep the value up to date call setAttribute();
173             value = attr.getValue();
174         }
175         if (value == null) {
176             value = attr.getDefaultValue();
177         }
178         return value;
179     }
180 
181     public Field getConsumerField(Attribute attr){
182         Field field = null;
183         String name = attr.getName();
184         Class c = consumer.getClass();
185         //Get a field matching the given name by searching through all super classes that extend
186         //JameleonTagSupport
187         while ( c != null &&
188                 ! c.equals(JameleonTagSupport.class) ) {
189             try{    
190                 field = c.getDeclaredField(name);
191                 field.setAccessible(true);
192                 break;
193             }catch(NoSuchFieldException nsfe){
194                 c = c.getSuperclass();
195             }
196         }
197         return field;
198     }
199 
200     protected Object getAttributeValueFromInstance(Attribute attr){
201         Object value = null;
202         if (attr.isInstanceVariable()) {
203             Field field = getConsumerField(attr);
204             if (field != null) {
205                 try{
206                     Class type = field.getType();
207                     value = field.get(consumer);
208                     if (type.isPrimitive()) {
209                         boolean nullValue = false;
210                         if (value instanceof Number && ((Number)value).byteValue() == 0) {
211                             nullValue = true;
212                         }else if (value instanceof Boolean && !((Boolean)value).booleanValue() ) {
213                             nullValue = true;
214                         }else if (value instanceof Character && ((Character)value).charValue() == 0) {
215                             nullValue = true;
216                         }
217                         if (nullValue) {
218                             value = null;
219                         }
220                     }
221                 }catch (IllegalAccessException iae){
222                     throw new JameleonException("Please report this problem along with as much info as you can give to the Jameleon bugtracker.", iae);
223                 }
224             }
225         }
226         return value;
227     }
228 
229     /***
230      * Sets the instance variable of the <code>Attributable</code> to the value with the correct type.
231      * @param attr - The Attribute representing the variable to set.
232      * @param objValue - The value to the instance variable to.
233      */
234     public void setConsumerAttribute(Attribute attr, Object objValue) {
235         String name = attr.getName();
236         Class cOrig = consumer.getClass();
237         Field f = getConsumerField(attr);
238         boolean valueSet = true;
239         if (f != null){
240             //Found a field, now let's set it to objValue after finding it's appropriate type
241             Class type = f.getType();
242             if (type.isPrimitive()) {
243                 //Looks like the instance is a primitive
244                 if (objValue != null) {
245                     setConsumerAttributeAsPrimitive(f, objValue);
246                 }else{
247                     valueSet = false;
248                 }
249             }else if ( objValue != null) {
250                 //Looks like the instance is an object.
251                 setConsumerAttributeAsObject(f, objValue);
252             }else{
253                 valueSet = false;
254             }
255         }else{
256             throw new JameleonException("Instance variable " + name + " does not exist in " + cOrig);
257         }
258         if (valueSet) {
259             attr.setValue(objValue);
260         }
261     }
262 
263     public void setConsumerAttributeAsPrimitive(Field f, Object objValue){
264         if (f != null) {
265             try{
266                 Class type = f.getType();
267                 Object o = f.get(consumer);
268                 String value = null;
269                 if (type.isPrimitive()) {
270                     if (objValue != null) {
271                         value = (String)objValue;
272                     }else{
273                         value = "0";
274                     }
275                     if (o instanceof Byte) {
276                         f.setByte(consumer,Byte.parseByte(value));
277                     }else if (o instanceof Integer) {
278                         f.setInt(consumer, Integer.parseInt(value));
279                     }else if (o instanceof Long) {
280                         f.setLong(consumer,Long.parseLong(value));
281                     }else if (o instanceof Short) {
282                         f.setShort(consumer, Short.parseShort(value));
283                     }else if (o instanceof Double) {
284                         f.setDouble(consumer, Double.parseDouble(value));
285                     }else if (o instanceof Float) {
286                         f.setFloat(consumer,Float.parseFloat(value));
287                     }else if (o instanceof Boolean) {
288                         if ("true".equals(value) || "yes".equals(value)) {
289                             f.setBoolean(consumer, true);
290                         }else{
291                             f.setBoolean(consumer, false);
292                         }
293                     }else if (o instanceof Character) {
294                         char cValue = '\u0000';
295                         if (objValue != null) {
296                             cValue = value.charAt(0);
297                         }
298                         f.setChar(consumer, cValue);
299                     }else{
300                         //How did I get here?
301                         throw new JameleonException(f.getName() + " of type " + f.getType() +
302                                                            ", not a supported type");
303                     }
304                 }
305             } catch (IllegalAccessException e) {
306                 throw new JameleonException("Instance variable " + f.getName() + " is not settable (may be final) in " + consumer.getClass(), e);
307             }
308         }else{
309             throw new JameleonException("Cannot set a null field!");
310         }
311 
312     }
313 
314     public void setConsumerAttributeAsObject(Field f, Object objValue){
315         if (f != null) {
316             try{
317                 Class type = f.getType();
318                 if ( objValue != null) {
319                     //Looks like the instance is an object.
320                     if (objValue instanceof String) {
321                         if (type.getName().equals(List.class.getName())) {
322                             //If objvalue isn't a List, let's make it one.
323                             f.set(consumer,ContextHelper.makeList(objValue));
324                         }else if (type.getName().equals(Boolean.class.getName())) {
325                             f.set(consumer, Boolean.valueOf((String)objValue));
326                         }else if (type.getName().equals(Byte.class.getName())){
327                             f.set(consumer, Byte.valueOf((String)objValue));
328                         }else if (type.getName().equals(Short.class.getName())){
329                             f.set(consumer, Short.valueOf((String)objValue));
330                         }else if (type.getName().equals(Character.class.getName())){
331                             f.set(consumer, new Character(((String)objValue).charAt(0)));
332                         }else if (type.getName().equals(Integer.class.getName())){
333                             f.set(consumer, Integer.valueOf((String)objValue));
334                         }else if (type.getName().equals(Long.class.getName())){
335                             f.set(consumer, Long.valueOf((String)objValue));
336                         }else if (type.getName().equals(Float.class.getName())){
337                             f.set(consumer, Float.valueOf((String)objValue));
338                         }else if (type.getName().equals(Double.class.getName())){
339                             f.set(consumer, Double.valueOf((String)objValue));
340                         }else if (type.getName().equals(File.class.getName())){
341                             f.set(consumer, new File((String)objValue));
342                         }else{
343                             f.set(consumer, objValue);
344                         }
345                     }else{
346                         f.set(consumer, objValue);
347                     }
348                 }else{
349                     f.set(consumer, objValue);
350                 }
351             } catch (IllegalAccessException e) {
352                 throw new JameleonException("Instance variable " + f.getName() + " is not settable (may be final) in " + consumer.getClass(), e);
353             }
354         }else{
355             throw new JameleonException("Cannot set a null field!");
356         }
357     }
358 
359 }