001/*
002 * TreeStrategy.java July 2006
003 *
004 * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
015 * implied. See the License for the specific language governing 
016 * permissions and limitations under the License.
017 */
018
019package org.simpleframework.xml.strategy;
020
021import static org.simpleframework.xml.strategy.Name.LABEL;
022import static org.simpleframework.xml.strategy.Name.LENGTH;
023
024import java.lang.reflect.Array;
025import java.util.Map;
026
027import org.simpleframework.xml.stream.Node;
028import org.simpleframework.xml.stream.NodeMap;
029
030/**
031 * The <code>TreeStrategy</code> object is used to provide a simple
032 * strategy for handling object graphs in a tree structure. This does
033 * not resolve cycles in the object graph. This will make use of the
034 * specified class attribute to resolve the class to use for a given 
035 * element during the deserialization process. For the serialization 
036 * process the "class" attribute will be added to the element specified.
037 * If there is a need to use an attribute name other than "class" then 
038 * the name of the attribute to use can be specified.
039 * 
040 * @author Niall Gallagher
041 * 
042 * @see org.simpleframework.xml.strategy.CycleStrategy
043 */
044public class TreeStrategy implements Strategy {
045   
046   /**
047    * This is the loader that is used to load the specified class.
048    */
049   private final Loader loader;
050   
051   /**
052    * This is the attribute that is used to determine an array size.
053    */
054   private final String length;
055   
056   /**   
057    * This is the attribute that is used to determine the real type.
058    */   
059   private final String label;
060   
061   /**
062    * Constructor for the <code>TreeStrategy</code> object. This 
063    * is used to create a strategy that can resolve and load class
064    * objects for deserialization using a "class" attribute. Also
065    * for serialization this will add the appropriate "class" value.
066    */
067   public TreeStrategy() {
068      this(LABEL, LENGTH);           
069   }        
070   
071   /**
072    * Constructor for the <code>TreeStrategy</code> object. This 
073    * is used to create a strategy that can resolve and load class
074    * objects for deserialization using the specified attribute. 
075    * The attribute value can be any legal XML attribute name.
076    * 
077    * @param label this is the name of the attribute to use
078    * @param length this is used to determine the array length
079    */
080   public TreeStrategy(String label, String length) {
081      this.loader = new Loader();
082      this.length = length;
083      this.label = label;         
084   } 
085   
086   /**
087    * This is used to resolve and load a class for the given element.
088    * Resolution of the class to used is done by inspecting the
089    * XML element provided. If there is a "class" attribute on the
090    * element then its value is used to resolve the class to use.
091    * If no such attribute exists on the element this returns null.
092    * 
093    * @param type this is the type of the XML element expected
094    * @param node this is the element used to resolve an override
095    * @param map this is used to maintain contextual information
096    * 
097    * @return returns the class that should be used for the object
098    * 
099    * @throws Exception thrown if the class cannot be resolved
100    */
101   public Value read(Type type, NodeMap node, Map map) throws Exception {
102      Class actual = readValue(type, node);
103      Class expect = type.getType();
104      
105      if(expect.isArray()) {
106         return readArray(actual, node);   
107      }
108      if(expect != actual) {
109         return new ObjectValue(actual);
110      }
111      return null;
112   }
113   
114   /**
115    * This is used to resolve and load a class for the given element.
116    * Resolution of the class to used is done by inspecting the
117    * XML element provided. If there is a "class" attribute on the
118    * element then its value is used to resolve the class to use.
119    * This also expects a "length" attribute for the array length.
120    * 
121    * @param type this is the type of the XML element expected
122    * @param node this is the element used to resolve an override
123    * 
124    * @return returns the class that should be used for the object
125    * 
126    * @throws Exception thrown if the class cannot be resolved
127    */   
128   private Value readArray(Class type, NodeMap node) throws Exception {      
129      Node entry = node.remove(length);
130      int size = 0;
131      
132      if(entry != null) {
133         String value = entry.getValue();
134         size = Integer.parseInt(value);
135      }      
136      return new ArrayValue(type, size);
137   }
138   
139   /**
140    * This is used to resolve and load a class for the given element.
141    * Resolution of the class to used is done by inspecting the
142    * XML element provided. If there is a "class" attribute on the
143    * element then its value is used to resolve the class to use.
144    * If no such attribute exists the specified field is returned,
145    * or if the field type is an array then the component type.
146    * 
147    * @param type this is the type of the XML element expected
148    * @param node this is the element used to resolve an override
149    * 
150    * @return returns the class that should be used for the object
151    * 
152    * @throws Exception thrown if the class cannot be resolved
153    */   
154   private Class readValue(Type type, NodeMap node) throws Exception {      
155      Node entry = node.remove(label);      
156      Class expect = type.getType();
157      
158      if(entry != null) {
159         String name = entry.getValue();
160         Class actual = loader.load(name);
161         // Arrays are annotated with the type of the element.
162         if (expect.isArray()) {
163            if (actual == expect.getComponentType())
164               actual = expect;
165            else
166               actual = Array.newInstance(actual, 0).getClass();
167         }
168         expect = actual;
169      }    
170      return expect;
171   }       
172   
173   /**
174    * This is used to attach a attribute to the provided element
175    * that is used to identify the class. The attribute name is
176    * "class" and has the value of the fully qualified class 
177    * name for the object provided. This will only be invoked
178    * if the object class is different from the field class.
179    *
180    * @param type this is the declared class for the field used
181    * @param value this is the instance variable being serialized
182    * @param node this is the element used to represent the value
183    * @param map this is used to maintain contextual information
184    * 
185    * @return this returns true if serialization is complete
186    */   
187   public boolean write(Type type, Object value, NodeMap node, Map map){
188      Class actual = value.getClass();
189      Class expect = type.getType();
190      Class real = actual;
191      
192      if(actual.isArray()) {
193         real = writeArray(expect, value, node);
194      }
195      if(actual != expect) {
196         node.put(label, real.getName());
197      }       
198      return false;
199   }
200   
201   /**
202    * This is used to add a length attribute to the element due to
203    * the fact that the serialized value is an array. The length
204    * of the array is acquired and inserted in to the attributes.
205    * 
206    * @param field this is the field type for the array to set
207    * @param value this is the actual value for the array to set
208    * @param node this is the map of attributes for the element
209    * 
210    * @return returns the array component type that is set
211    */
212   private Class writeArray(Class field, Object value, NodeMap node){
213      int size = Array.getLength(value);
214      
215      if(length != null) {       
216         node.put(length, String.valueOf(size));
217      }
218      return field.getComponentType();
219   }
220}