content: /*
   * GroupExtractor.java March 2011
   *
   * Copyright (C) 2011, 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.core;
  
  import com.aliyun.odps.simpleframework.xml.Text;
  import com.aliyun.odps.simpleframework.xml.stream.Format;
  import java.lang.annotation.Annotation;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  
  /**
   * The <code>GroupExtractor</code> represents an extractor for labels
   * associated with a particular union annotation. This extractor 
   * registers <code>Label</code> by name and by type. Acquiring
   * the label by type allows the serialization process to dynamically
   * select a label, and thus converter, based on the instance type.
   * On deserialization a label is dynamically selected based on name.
   * 
   * @author Niall Gallagher
   * 
   * @see Group
   */
  class GroupExtractor implements Group {
  
     /**
      * This represents a factory for creating union extractors.
      */
     private final ExtractorFactory factory;
     
     /**
      * This represents the union label to be used for this group.
      */
     private final Annotation label;
     
     /**
      * This contains each label registered by name and by type.
      */
     private final Registry registry;
     
     /**
      * This contains each label registered by label name.
      */
     private final LabelMap elements;
     
     /**
      * Constructor for the <code>GroupExtractor</code> object. This
      * will create an extractor for the provided union annotation.
      * Each individual declaration within the union is extracted
      * and made available within the group.
      * 
      * @param contact this is the annotated field or method
      * @param label this is the label associated with the contact
      * @param format this is the format used by this extractor
      */
     public GroupExtractor(Contact contact, Annotation label, Format format) throws Exception{
        this.factory = new ExtractorFactory(contact, label, format);
        this.elements = new LabelMap();
        this.registry = new Registry(elements);
        this.label = label;
        this.extract();
     } 
     
     /**
      * This is used to acquire the names for each label associated
      * with this <code>Group</code> instance. The names provided
      * here are not styled according to a serialization context.
      * 
      * @return this returns the names of each union extracted
      */
     public String[] getNames() throws Exception {
        return elements.getKeys();
     }
     
     /**
      * This is used to acquire the paths for each label associated
      * with this <code>Group</code> instance. The paths provided
      * here are not styled according to a serialization context.
      * 
      * @return this returns the paths of each union extracted
      */
     public String[] getPaths() throws Exception {
        return elements.getPaths();
     }
     
     /**
      * This is used to acquire a <code>LabelMap</code> containing the
      * labels available to the group. Providing a context object 
      * ensures that each of the labels is mapped to a name that is
      * styled according to its internal style.
      * 
      * @return this returns a label map containing the labels 
      */
     public LabelMap getElements() throws Exception {
        return elements.getLabels();
     }
  
     /**
      * This is used to acquire a <code>Label</code> based on the type
      * of an object. Selecting a label based on the type ensures that
      * the serialization process can dynamically convert an object
      * to XML. If the type is not supported, this returns null.
      * 
      * @param type this is the type to select the label from
      * 
      * @return this returns the label based on the type
      */
     public Label getLabel(Class type) {
        return registry.resolve(type);
     }
     
     /**
      * This is used to get a <code>Label</code> that represents the
      * text between elements on an element union. Providing a label
      * here ensures that the free text found between elements can
      * be converted in to strings and added to the list.
      * 
      * @return a label if a text annotation has been declared
      */
     public Label getText() {
        return registry.resolveText();
     }
     
     /**
      * This is used to determine if the associated type represents a
      * label defined within the union group. If the label exists
      * this returns true, if not then this returns false.
      * 
      * @param type this is the type to check for
      * 
      * @return this returns true if a label for the type exists
      */
     public boolean isValid(Class type) {
        return registry.resolve(type) != null;
     }
     
     /**
      * This is used to determine if a type has been declared by the
      * annotation associated with the group. Unlike determining if
      * the type is valid this will not consider super types.
      * 
      * @param type this is the type to determine if it is declared
      * 
      * @return this returns true if the type has been declared
      */
     public boolean isDeclared(Class type) {
        return registry.containsKey(type);
     }
     
     /**
      * This is used to determine if the group is inline. A group is
      * inline if all of the elements in the group is inline. If any of
      * the <code>Label<code> objects in the group is not inline then
      * the entire group is not inline, although this is unlikely.
      * 
      * @return this returns true if each label in the group is inline
      */
     public boolean isInline() {
        for(Label label : registry) {
           if(!label.isInline()) {
              return false;
           }
        }
        return !registry.isEmpty();
     }
     
     /**
      * This is used to determine if an annotated list is a text 
      * list. A text list is a list of elements that also accepts
      * free text. Typically this will be an element list union that
      * will allow unstructured XML such as XHTML to be parsed.
      * 
      * @return returns true if the label represents a text list
      */
     public boolean isTextList() {
        return registry.isText();
     }
     
     /**
      * This is used to extract the labels associated with the group.
      * Extraction will instantiate a <code>Label</code> object for
      * an individual annotation declared within the union. Each of
      * the label instances is then registered by both name and type.
      */
     private void extract() throws Exception {
        Extractor extractor = factory.getInstance();
  
        if(extractor != null) {
           extract(extractor);
        }
     }
     
     /**
      * This is used to extract the labels associated with the group.
      * Extraction will instantiate a <code>Label</code> object for
      * an individual annotation declared within the union. Each of
      * the label instances is then registered by both name and type.
      * 
      * @param extractor this is the extractor to get labels for
      */
     private void extract(Extractor extractor) throws Exception {
        Annotation[] list = extractor.getAnnotations();
        
        for(Annotation label : list) {
           extract(extractor, label);
        }
     }
     
     /**
      * This is used to extract the labels associated with the group.
      * Extraction will instantiate a <code>Label</code> object for
      * an individual annotation declared within the union. Each of
      * the label instances is then registered by both name and type.
      * 
      * @param extractor this is the extractor to get labels for
      * @param value this is an individual annotation declared
      */
     private void extract(Extractor extractor, Annotation value) throws Exception {
        Label label = extractor.getLabel(value);
        Class type = extractor.getType(value);
        
        if(registry != null) {
           registry.register(type, label);
        }
     }
     
     /**
      * This returns a string representation of the union group.
      * Providing a string representation in this way ensures that the
      * group can be used in exception messages and for any debugging.
      * 
      * @return this returns a string representation of the group
      */
     public String toString() {
        return label.toString();
     }
     
     /**
      * The <code>Registry</code> object is used to maintain mappings
      * from types to labels. Each of the mappings can be used to 
      * dynamically select a label based on the instance type that is
      * to be serialized. This also registers based on the label name.
      */
     private static class Registry extends LinkedHashMap<Class, Label> implements Iterable<Label> {
     
        /**
         * This maintains a mapping between label names and labels.
         */
        private LabelMap elements;
        
        /**
         * This label represents the free text between elements.
         */
        private Label text;
        
        /**
         * Constructor for the <code>Registry</code> object. This is
         * used to register label instances using both the name and
         * type of the label. Registration in this way ensures that
         * each label can be dynamically selected.
         * 
         * @param elements this contains name to label mappings
         */
        public Registry(LabelMap elements){
           this.elements = elements;
        }
        
        /**
         * This is used to determine if an annotated list is a text 
         * list. A text list is a list of elements that also accepts
         * free text. Typically this will be an element list union that
         * will allow unstructured XML such as XHTML to be parsed.
         * 
         * @return returns true if the label represents a text list
         */
        public boolean isText() {
           return text != null;
        }
        
        /**
         * This is used so that all the <code>Label</code> objects
         * that have been added to the registry can be iterated over.
         * Iteration over the labels allows for easy validation.
         * 
         * @return this returns an iterator for all the labels
         */
        public Iterator<Label> iterator() {
           return values().iterator();
        }
        
        /**
         * This is used to resolve the text for this registry. If there
         * is a text annotation declared with the union then this will
         * return a <code>Label</code> that can be used to convert the
         * free text between elements in to strings.
         * 
         * @return this returns the label representing free text
         */
        public Label resolveText() {
           return resolveText(String.class);
        }
        
        /**
         * Here we resolve the <code>Label</code> the type is matched
         * with by checking if the type is directly mapped or if any of
         * the super classes of the type are mapped. If there are no
         * classes in the hierarchy of the type that are mapped then
         * this will return null otherwise the label will be returned.
         * 
         * @param type this is the type to resolve the label for
         * 
         * @return this will return the label that is best matched
         */
        public Label resolve(Class type) {
           Label label = resolveText(type);
           
           if(label == null) {
              return resolveElement(type);
           }
           return label;
        }
        
        /**
         * This is used to resolve the text for this registry. If there
         * is a text annotation declared with the union then this will
         * return a <code>Label</code> that can be used to convert the
         * free text between elements in to strings.
         * 
         * @param type this is the type to resolve the text as
         * 
         * @return this returns the label representing free text
         */
        private Label resolveText(Class type) {
           if(text != null) {
              if(type == String.class) {
                 return text;
              }
           }
           return null;
        }
        
        /**
         * Here we resolve the <code>Label</code> the type is matched
         * with by checking if the type is directly mapped or if any of
         * the super classes of the type are mapped. If there are no
         * classes in the hierarchy of the type that are mapped then
         * this will return null otherwise the label will be returned.
         * 
         * @param type this is the type to resolve the label for
         * 
         * @return this will return the label that is best matched
         */
        private Label resolveElement(Class type) {
           while(type != null) {
              Label label = get(type);
              
              if(label != null) {
                 return label;
              }
              type = type.getSuperclass();
           }
           return null; 
        }
        
        /**
         * This is used to register a label based on the name. This is
         * done to ensure the label instance can be dynamically found
         * during the deserialization process by providing the name.
         * 
         * @param name this is the name of the label to be registered
         * @param label this is the label that is to be registered
         */
        public void register(Class type, Label label) throws Exception {
           Label cache = new CacheLabel(label);
           
           registerElement(type, cache);
           registerText(cache);
        }
        
        
        /**
         * This is used to register a label based on the name. This is
         * done to ensure the label instance can be dynamically found
         * during the deserialization process by providing the name.
         * 
         * @param name this is the name of the label to be registered
         * @param label this is the label that is to be registered
         */
        private void registerElement(Class type, Label label) throws Exception {
           String name = label.getName();
           
           if(!elements.containsKey(name)) {
              elements.put(name, label);
           }
           if(!containsKey(type)) {
              put(type, label);
           }
        }
        
        /**
         * This is used to register the provided label is a text label.
         * Registration as a text label can only happen if the field or
         * method has a <code>Text</code> annotation declared on it.
         * 
         * @param label this is the label to register as text
         */
        private void registerText(Label label) throws Exception {
           Contact contact = label.getContact();
           Text value = contact.getAnnotation(Text.class);
           
           if(value != null) {
              text = new TextListLabel(label, value);
           }
        }
     }
  } 