content: /*
   * ConverterScanner.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 java.lang.annotation.Annotation;
  
  import com.aliyun.odps.simpleframework.xml.Element;
  import com.aliyun.odps.simpleframework.xml.Root;
  import com.aliyun.odps.simpleframework.xml.strategy.Type;
  import com.aliyun.odps.simpleframework.xml.strategy.Value;
  
  /**
   * The <code>ConverterScanner</code> is used to create a converter 
   * given a method or field representation. Creation of the converter
   * is done using the <code>Convert</code> annotation, which may
   * be used to annotate a field, method or class. This describes the
   * implementation to use for object serialization. To account for
   * polymorphism the type scanned for annotations can be overridden
   * from type provided in the <code>Type</code> object. This ensures
   * that if a collection of objects are serialized the correct
   * implementation will be used for each type or subtype.
   * 
   * @author Niall Gallagher
   */
  class ConverterScanner {
     
     /**
      * This is used to instantiate converters given the type.
      */
     private final ConverterFactory factory;
     
     /**
      * This is used to build a scanner to scan for annotations.
      */
     private final ScannerBuilder builder;
     
     /**
      * Constructor for the <code>ConverterScanner</code> object. This
      * uses an internal factory to instantiate and cache all of the
      * converters created. This will ensure that there is reduced
      * overhead for a serialization process using converters.
      */
     public ConverterScanner() {
        this.factory = new ConverterFactory();
        this.builder = new ScannerBuilder();
     }
     
     /**
      * This method will lookup and instantiate a converter found from
      * scanning the field or method type provided. If the type has
      * been overridden then the <code>Value</code> object will provide
      * the type to scan. If no annotation is found on the class, field
      * or method then this will return null.
      * 
      * @param type this is the type to search for the annotation
      * @param value this contains the type if it was overridden
      * 
      * @return a converter scanned from the provided field or method
      */
     public Converter getConverter(Type type, Value value) throws Exception {
        Class real = getType(type, value);
        Convert convert = getConvert(type, real);
        
        if(convert != null) {
           return factory.getInstance(convert);
        }
        return null;
     }
     
     /**
      * This method will lookup and instantiate a converter found from
      * scanning the field or method type provided. If the type has
      * been overridden then the object instance will provide the type 
      * to scan. If no annotation is found on the class, field or 
      * method then this will return null.
      * 
      * @param type this is the type to search for the annotation
      * @param value this contains the type if it was overridden
      * 
      * @return a converter scanned from the provided field or method
      */
     public Converter getConverter(Type type, Object value) throws Exception {
        Class real = getType(type, value);
        Convert convert = getConvert(type, real);
        
        if(convert != null) {
           return factory.getInstance(convert);
        }
        return null;
     }
     
     /**
      * This method is used to scan the provided <code>Type</code> for
      * an annotation. If the <code>Type</code> represents a field or
      * method then the annotation can be taken directly from that
      * field or method. If however the type represents a class then
      * the class itself must contain the annotation. 
      * 
      * @param type the field or method containing the annotation
      * @param real the type that represents the field or method
      * 
      * @return this returns the annotation on the field or method
      */
     private Convert getConvert(Type type, Class real) throws Exception {
        Convert convert = getConvert(type);
        
        if(convert == null) {
           return getConvert(real);
        }
        return convert;
     }
     
     /**
      * This method is used to scan the provided <code>Type</code> for
      * an annotation. If the <code>Type</code> represents a field or
      * method then the annotation can be taken directly from that
      * field or method. If however the type represents a class then
      * the class itself must contain the annotation. 
      * 
      * @param type the field or method containing the annotation
      * 
      * @return this returns the annotation on the field or method
      */
     private Convert getConvert(Type type) throws Exception {
        Convert convert = type.getAnnotation(Convert.class);
        
        if(convert != null) {
           Element element = type.getAnnotation(Element.class);
        
           if(element == null) {
              throw new ConvertException("Element annotation required for %s", type);
           }
        }
        return convert;
     }
     
     /**
      * This method is used to scan the provided <code>Type</code> for
      * an annotation. If the <code>Type</code> represents a field or
      * method then the annotation can be taken directly from that
      * field or method. If however the type represents a class then
      * the class itself must contain the annotation. 
      * 
      * @param real the type that represents the field or method
      * 
      * @return this returns the annotation on the field or method
      */
     private Convert getConvert(Class real) throws Exception {
        Convert convert = getAnnotation(real, Convert.class);
        
        if(convert != null) {
           Root root = getAnnotation(real, Root.class);
           
           if(root == null) {
              throw new ConvertException("Root annotation required for %s", real);
           }
        }
        return convert;
     }
     
     /**
      * This is used to acquire the <code>Convert</code> annotation from
      * the class provided. If the type does not contain the annotation
      * then this scans all supertypes until either an annotation is
      * found or there are no further supertypes. 
      * 
      * @param type this is the type to scan for annotations
      * @param label this is the annotation type that is to be found
      * 
      * @return this returns the annotation if found otherwise null
      */
     private <T extends Annotation> T getAnnotation(Class<?> type, Class<T> label) {
        return builder.build(type).scan(label);
     }
     
     /**
      * This is used to acquire the class that should be scanned. The
      * type is found either on the method or field, or should there
      * be a subtype then the class is taken from the provided value.
      * 
      * @param type this is the type representing the field or method
      * @param value this contains the type if it was overridden
      * 
      * @return this returns the class that has been scanned
      */
     private Class getType(Type type, Value value) {
        Class real = type.getType();
        
        if(value != null) {
           return value.getType();
        }
        return real;
     }
     
     /**
      * This is used to acquire the class that should be scanned. The
      * type is found either on the method or field, or should there
      * be a subtype then the class is taken from the provided value.
      * 
      * @param type this is the type representing the field or method
      * @param value this contains the type if it was overridden
      * 
      * @return this returns the class that has been scanned
      */
     private Class getType(Type type, Object value) {
        Class real = type.getType();
        
        if(value != null) {
           return value.getClass();
        }
        return real;
     }
  }
  