content: /*
   * RegistryStrategy.java January 2010
   *
   * Copyright (C) 2010, Niall Gallagher <niallg@users.sf.net>
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
   * implied. See the License for the specific language governing 
   * permissions and limitations under the License.
   */
  
  package com.aliyun.odps.simpleframework.xml.convert;
  
  import com.aliyun.odps.simpleframework.xml.stream.InputNode;
  import com.aliyun.odps.simpleframework.xml.stream.NodeMap;
  import com.aliyun.odps.simpleframework.xml.stream.OutputNode;
  import java.util.Map;
  
  import com.aliyun.odps.simpleframework.xml.strategy.Strategy;
  import com.aliyun.odps.simpleframework.xml.strategy.TreeStrategy;
  import com.aliyun.odps.simpleframework.xml.strategy.Type;
  import com.aliyun.odps.simpleframework.xml.strategy.Value;
  
  /**
   * The <code>RegistryStrategy</code> object is used to intercept
   * the serialization process and delegate to custom converters. The
   * custom converters are resolved from a <code>Registry</code>
   * object, which is provided to the constructor. If there is no
   * binding for a particular object then serialization is delegated
   * to an internal strategy. All converters resolved by this are
   * instantiated once and cached internally for performance.
   * <p>
   * By default the <code>TreeStrategy</code> is used to perform the
   * normal serialization process should there be no class binding
   * specifying a converter to use. However, any implementation can
   * be used, including the <code>CycleStrategy</code>, which handles
   * cycles in the object graph. To specify the internal strategy to
   * use it can be provided in the constructor.
   * 
   * @author Niall Gallagher
   *
   * @see Registry
   */
  public class RegistryStrategy implements Strategy {
     
     /**
      * This is the registry that is used to resolve bindings.
      */
     private final Registry registry;
     
     /**
      * This is the strategy used if there is no bindings.
      */
     private final Strategy strategy;
     
     /**
      * Constructor for the <code>RegistryStrategy</code> object. This
      * is used to create a strategy that will intercept the normal
      * serialization process by searching for bindings within the
      * provided <code>Registry</code> instance.
      * 
      * @param registry this is the registry instance with bindings
      */
     public RegistryStrategy(Registry registry) {
        this(registry, new TreeStrategy());
     }
     
     /**
      * Constructor for the <code>RegistryStrategy</code> object. This
      * is used to create a strategy that will intercept the normal
      * serialization process by searching for bindings within the
      * provided <code>Registry</code> instance.
      * 
      * @param registry this is the registry instance with bindings
      * @param strategy this is the strategy to delegate to
      */
     public RegistryStrategy(Registry registry, Strategy strategy){
        this.registry = registry;
        this.strategy = strategy;
     }
     
     /**
      * This is used to read the <code>Value</code> which will be used 
      * to represent the deserialized object. If there is an binding
      * present then the value will contain an object instance. If it
      * does not then it is up to the internal strategy to determine 
      * what the returned value contains.
      * 
      * @param type this is the type that represents a method or field
      * @param node this is the node representing the XML element
      * @param map this is the session map that contain variables
      * 
      * @return the value representing the deserialized value
      */
     public Value read(Type type, NodeMap<InputNode> node, Map map) throws Exception {
        Value value = strategy.read(type, node, map);
        
        if(isReference(value)) {
           return value;
        }
        return read(type, node, value);
     }
    
     /**
      * This is used to read the <code>Value</code> which will be used 
      * to represent the deserialized object. If there is an binding
      * present then the value will contain an object instance. If it
      * does not then it is up to the internal strategy to determine 
      * what the returned value contains.
      * 
      * @param type this is the type that represents a method or field
      * @param node this is the node representing the XML element
      * @param value this is the value from the internal strategy
      * 
      * @return the value representing the deserialized value
      */   
     private Value read(Type type, NodeMap<InputNode> node, Value value) throws Exception {
        Converter converter = lookup(type, value);
        InputNode source = node.getNode();
        
        if(converter != null) {
           Object data = converter.read(source);
           Class actual = type.getType();
        
           if(value != null) {
              value.setValue(data);
           }
           return new Reference(value, data, actual);
        }
        return value;
     }
     
     /**
      * This is used to serialize a representation of the object value
      * provided. If there is a <code>Registry</code> binding present
      * for the provided type then this will use the converter specified
      * to serialize a representation of the object. If however there
      * is no binding present then this will delegate to the internal 
      * strategy. This returns true if the serialization has completed.
      * 
      * @param type this is the type that represents the field or method
      * @param value this is the object instance to be serialized
      * @param node this is the XML element to be serialized to
      * @param map this is the session map used by the serializer
      * 
      * @return this returns true if it was serialized, false otherwise
      */
     public boolean write(Type type, Object value, NodeMap<OutputNode> node, Map map) throws Exception {
        boolean reference = strategy.write(type, value, node, map);
        
        if(!reference) {
           return write(type, value, node);
        }
        return reference;
     }
     
     /**
      * This is used to serialize a representation of the object value
      * provided. If there is a <code>Registry</code> binding present
      * for the provided type then this will use the converter specified
      * to serialize a representation of the object. If however there
      * is no binding present then this will delegate to the internal 
      * strategy. This returns true if the serialization has completed.
      * 
      * @param type this is the type that represents the field or method
      * @param value this is the object instance to be serialized
      * @param node this is the XML element to be serialized to
      * 
      * @return this returns true if it was serialized, false otherwise
      */
     private boolean write(Type type, Object value, NodeMap<OutputNode> node) throws Exception {
        Converter converter = lookup(type, value);
        OutputNode source = node.getNode();
        
        if(converter != null) {
           converter.write(source, value);
           return true;
        }
        return false;  
     }
     
     /**
      * This is used to acquire a <code>Converter</code> instance for 
      * the provided value object. The value object is used to resolve
      * the converter to use for the serialization process.
      * 
      * @param type this is the type representing the field or method
      * @param value this is the value that is to be serialized
      * 
      * @return this returns the converter instance that is matched
      */
     private Converter lookup(Type type, Value value) throws Exception {
        Class real = type.getType();
        
        if(value != null) {
           real = value.getType();
        }
        return registry.lookup(real);
     }
     
     /**
      * This is used to acquire a <code>Converter</code> instance for 
      * the provided object instance. The instance class is used to
      * resolve the converter to use for the serialization process.
      * 
      * @param type this is the type representing the field or method
      * @param value this is the value that is to be serialized
      * 
      * @return this returns the converter instance that is matched
      */
     private Converter lookup(Type type, Object value) throws Exception {
        Class real = type.getType();
        
        if(value != null) {
           real = value.getClass();
        }
        return registry.lookup(real);
     }
     
     /**
      * This is used to determine if the <code>Value</code> provided
      * represents a reference. If it does represent a reference then
      * this will return true, if it does not then this returns false.
      * 
      * @param value this is the value instance to be evaluated
      * 
      * @return this returns true if the value represents a reference
      */
     private boolean isReference(Value value) {
        return value != null && value.isReference();
     }
  }
  