001/*
002 * AnnotationStrategy.java January 2010
003 *
004 * Copyright (C) 2010, 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.convert;
020
021import java.util.Map;
022
023import org.simpleframework.xml.strategy.Strategy;
024import org.simpleframework.xml.strategy.TreeStrategy;
025import org.simpleframework.xml.strategy.Type;
026import org.simpleframework.xml.strategy.Value;
027import org.simpleframework.xml.stream.InputNode;
028import org.simpleframework.xml.stream.NodeMap;
029import org.simpleframework.xml.stream.OutputNode;
030
031/**
032 * The <code>AnnotationStrategy</code> object is used to intercept
033 * the serialization process and delegate to custom converters. This
034 * strategy uses the <code>Convert</code> annotation to specify the
035 * converter to use for serialization and deserialization. If there
036 * is no annotation present on the field or method representing the
037 * object instance to be serialized then this acts as a transparent
038 * proxy to an internal strategy.
039 * <p>
040 * By default the <code>TreeStrategy</code> is used to perform the
041 * normal serialization process should there be no annotation
042 * specifying a converter to use. However, any implementation can
043 * be used, including the <code>CycleStrategy</code>, which handles
044 * cycles in the object graph. To specify the internal strategy to
045 * use it can be provided in the constructor.
046 * 
047 * @author Niall Gallagher
048 *
049 * @see org.simpleframework.xml.strategy.TreeStrategy
050 */
051public class AnnotationStrategy implements Strategy {
052   
053   /**
054    * This is used to scan for an annotation and create a converter.
055    */
056   private final ConverterScanner scanner;
057   
058   /**
059    * This is the strategy that is delegated to for serialization.
060    */
061   private final Strategy strategy;
062   
063   /**
064    * Constructor for the <code>AnnotationStrategy</code> object. 
065    * This creates a strategy that intercepts serialization on any
066    * annotated method or field. If no annotation exists then this
067    * delegates to an internal <code>TreeStrategy</code> object.
068    */
069   public AnnotationStrategy() {
070      this(new TreeStrategy());
071   }
072   
073   /**
074    * Constructor for the <code>AnnotationStrategy</code> object. 
075    * This creates a strategy that intercepts serialization on any
076    * annotated method or field. If no annotation exists then this
077    * will delegate to the <code>Strategy</code> provided.
078    * 
079    * @param strategy the internal strategy to delegate to
080    */
081   public AnnotationStrategy(Strategy strategy) {
082      this.scanner = new ConverterScanner();
083      this.strategy = strategy;
084   }
085   
086   /**
087    * This is used to read the <code>Value</code> which will be used 
088    * to represent the deserialized object. If there is an annotation
089    * present then the value will contain an object instance. If it
090    * does not then it is up to the internal strategy to determine 
091    * what the returned value contains.
092    * 
093    * @param type this is the type that represents a method or field
094    * @param node this is the node representing the XML element
095    * @param map this is the session map that contain variables
096    * 
097    * @return the value representing the deserialized value
098    */
099   public Value read(Type type, NodeMap<InputNode> node, Map map) throws Exception {
100      Value value = strategy.read(type, node, map);
101      
102      if(isReference(value)) {
103         return value;
104      }
105      return read(type, node, value);
106   }
107   
108   /**
109    * This is used to read the <code>Value</code> which will be used 
110    * to represent the deserialized object. If there is an annotation
111    * present then the value will contain an object instance. If it
112    * does not then it is up to the internal strategy to determine 
113    * what the returned value contains.
114    * 
115    * @param type this is the type that represents a method or field
116    * @param node this is the node representing the XML element
117    * @param value this is the value from the internal strategy
118    * 
119    * @return the value representing the deserialized value
120    */
121   private Value read(Type type, NodeMap<InputNode> node, Value value) throws Exception {
122      Converter converter = scanner.getConverter(type, value);
123      InputNode parent = node.getNode();
124      
125      if(converter != null) {
126         Object data = converter.read(parent);
127         Class actual = type.getType();
128         
129         if(value != null) {
130            value.setValue(data);
131         }
132         return new Reference(value, data, actual);
133      }
134      return value;
135   }
136   
137   /**
138    * This is used to serialize a representation of the object value
139    * provided. If there is a <code>Convert</code> annotation present
140    * on the provided type then this will use the converter specified
141    * to serialize a representation of the object. If however there
142    * is no annotation then this will delegate to the internal 
143    * strategy. This returns true if the serialization has completed.
144    * 
145    * @param type this is the type that represents the field or method
146    * @param value this is the object instance to be serialized
147    * @param node this is the XML element to be serialized to
148    * @param map this is the session map used by the serializer
149    * 
150    * @return this returns true if it was serialized, false otherwise
151    */
152   public boolean write(Type type, Object value, NodeMap<OutputNode> node, Map map) throws Exception {
153      boolean reference = strategy.write(type, value, node, map);
154      
155      if(!reference) {
156         return write(type, value, node);
157      }
158      return reference;
159   }
160   
161   /**
162    * This is used to serialize a representation of the object value
163    * provided. If there is a <code>Convert</code> annotation present
164    * on the provided type then this will use the converter specified
165    * to serialize a representation of the object. If however there
166    * is no annotation then this will delegate to the internal 
167    * strategy. This returns true if the serialization has completed.
168    * 
169    * @param type this is the type that represents the field or method
170    * @param value this is the object instance to be serialized
171    * @param node this is the XML element to be serialized to
172    * 
173    * @return this returns true if it was serialized, false otherwise
174    */
175   private boolean write(Type type, Object value, NodeMap<OutputNode> node) throws Exception {
176      Converter converter = scanner.getConverter(type, value);
177      OutputNode parent = node.getNode();
178      
179      if(converter != null) {
180         converter.write(parent, value);
181         return true;
182      }
183      return false;
184   }
185   
186   /**
187    * This is used to determine if the <code>Value</code> provided
188    * represents a reference. If it does represent a reference then
189    * this will return true, if it does not then this returns false.
190    * 
191    * @param value this is the value instance to be evaluated
192    * 
193    * @return this returns true if the value represents a reference
194    */
195   private boolean isReference(Value value) {
196      return value != null && value.isReference();
197   }
198}