All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simpleframework.xml.core.ClassScanner Maven / Gradle / Ivy

Go to download

Simple is a high performance XML serialization and configuration framework for Java

There is a newer version: 2.7.1
Show newest version
/*
 * ClassScanner.java July 2008
 *
 * Copyright (C) 2008, Niall Gallagher 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General 
 * Public License along with this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA  02111-1307  USA
 */

package org.simpleframework.xml.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;

import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.NamespaceList;
import org.simpleframework.xml.Order;
import org.simpleframework.xml.Root;

/**
 * The ClassScanner performs the reflective inspection
 * of a class and extracts all the class level annotations. This will
 * also extract the methods that are annotated. This ensures that the
 * callback methods can be invoked during the deserialization process.
 * Also, this will read the namespace annotations that are used.
 * 
 * @author Niall Gallagher
 * 
 * @see org.simpleframework.xml.core.Scanner
 */ 
class ClassScanner  {
   
   /**
    * This is the namespace decorator associated with this scanner.
    */
   private NamespaceDecorator decorator;
   
   /**
    * This is the scanner that is used to acquire the constructors.
    */
   private ConstructorScanner scanner;
   
   /**
    * This is the namespace associated with the scanned class.
    */
   private Namespace namespace;
   
   /**
    * This function acts as a pointer to the types commit process.
    */
   private Function commit;
   
   /**
    * This function acts as a pointer to the types validate process.
    */
   private Function validate;

   /**
    * This function acts as a pointer to the types persist process.
    */
   private Function persist;

   /**
    * This function acts as a pointer to the types complete process.
    */
   private Function complete;   
   
   /**
    * This function is used as a pointer to the replacement method.
    */
   private Function replace;
   
   /**
    * This function is used as a pointer to the resolution method.
    */
   private Function resolve;
   
   /**
    * This is the optional order annotation for the scanned class.
    */
   private Order order;

   /**
    * This is the optional root annotation for the scanned class.
    */
   private Root root;
   
   /**
    * Constructor for the ClassScanner object. This is 
    * used to scan the provided class for annotations that are used 
    * to build a schema for an XML file to follow. 
    * 
    * @param type this is the type that is scanned for a schema
    */
   public ClassScanner(Class type) throws Exception { 
      this.scanner = new ConstructorScanner(type);
      this.decorator = new NamespaceDecorator();
      this.scan(type);
   }      

   /**
    * This is used to create the object instance. It does this by
    * either delegating to the default no argument constructor or by
    * using one of the annotated constructors for the object. This
    * allows deserialized values to be injected in to the created
    * object if that is required by the class schema.
    * 
    * @return this returns the creator for the class object
    */
   public Creator getCreator() {
      return scanner.getCreator();
   }
   
   /**
    * This is used to acquire the Decorator for this.
    * A decorator is an object that adds various details to the
    * node without changing the overall structure of the node. For
    * example comments and namespaces can be added to the node with
    * a decorator as they do not affect the deserialization.
    * 
    * @return this returns the decorator associated with this
    */
   public Decorator getDecorator() {
      return decorator;
   }
   
   /**
    * This returns the order annotation used to determine the order
    * of serialization of attributes and elements. The order is a
    * class level annotation that can be used only once per class
    * XML schema. If none exists then this will return null.
    *  of the class processed by this scanner.
    * 
    * @return this returns the name of the object being scanned
    */
   public Order getOrder() {
      return order;
   }
   
   /**
    * This returns the root of the class processed by this scanner.
    * The root determines the type of deserialization that is to
    * be performed and also contains the name of the root element. 
    * 
    * @return this returns the name of the object being scanned
    */
   public Root getRoot() {
      return root;
   }

   /**
    * This method is used to retrieve the schema class commit method
    * during the deserialization process. The commit method must be
    * marked with the Commit annotation so that when the
    * object is deserialized the persister has a chance to invoke the
    * method so that the object can build further data structures.
    * 
    * @return this returns the commit method for the schema class
    */
   public Function getCommit() {
      return commit;           
   }

   /**
    * This method is used to retrieve the schema class validation
    * method during the deserialization process. The validation method
    * must be marked with the Validate annotation so that
    * when the object is deserialized the persister has a chance to 
    * invoke that method so that object can validate its field values.
    * 
    * @return this returns the validate method for the schema class
    */   
   public Function getValidate() {
      return validate;       
   }
   
   /**
    * This method is used to retrieve the schema class persistence
    * method. This is invoked during the serialization process to
    * get the object a chance to perform an necessary preparation
    * before the serialization of the object proceeds. The persist
    * method must be marked with the Persist annotation.
    * 
    * @return this returns the persist method for the schema class
    */
   public Function getPersist() {
      return persist;           
   }

   /**
    * This method is used to retrieve the schema class completion
    * method. This is invoked after the serialization process has
    * completed and gives the object a chance to restore its state
    * if the persist method required some alteration or locking.
    * This is marked with the Complete annotation.
    * 
    * @return returns the complete method for the schema class
    */   
   public Function getComplete() {
      return complete;           
   }
   
   /**
    * This method is used to retrieve the schema class replacement
    * method. The replacement method is used to substitute an object
    * that has been deserialized with another object. This allows
    * a seamless delegation mechanism to be implemented. This is
    * marked with the Replace annotation. 
    * 
    * @return returns the replace method for the schema class
    */
   public Function getReplace() {
      return replace;
   }
   
   /**
    * This method is used to retrieve the schema class replacement
    * method. The replacement method is used to substitute an object
    * that has been deserialized with another object. This allows
    * a seamless delegation mechanism to be implemented. This is
    * marked with the Replace annotation. 
    * 
    * @return returns the replace method for the schema class
    */
   public Function getResolve() {
      return resolve;
   }
   
   /**
    * This method is used to determine whether strict mappings are
    * required. Strict mapping means that all labels in the class
    * schema must match the XML elements and attributes in the
    * source XML document. When strict mapping is disabled, then
    * XML elements and attributes that do not exist in the schema
    * class will be ignored without breaking the parser.
    *
    * @return true if strict parsing is enabled, false otherwise
    */ 
   public boolean isStrict() {
      if(root != null) {
         return root.strict();              
      }              
      return true;
   }
  
   /**
    * Scan the fields and methods such that the given class is scanned 
    * first then all super classes up to the root Object. 
    * All fields and methods from the most specialized classes override 
    * fields and methods from higher up the inheritance hierarchy. This
    * means that annotated details can be overridden.
    * 
    * @param type the class to extract method and class annotations
    */   
   private void scan(Class type) throws Exception {
      Class real = type;
      
      while(type != null) {
         if(namespace == null) {
            namespace(type);
         }
         if(root == null) {              
            root(type);
         }  
         if(order == null) {
            order(type);
         }
         scope(type);
         scan(real, type);
         type = type.getSuperclass();
      }      
      process(real); 
   }

   /**
    * This is used to scan the specified class for methods so that
    * the persister callback annotations can be collected. These
    * annotations help object implementations to validate the data
    * that is injected into the instance during deserialization.
    * 
    * @param real this is the actual type of the scanned class 
    * @param type this is a type from within the class hierarchy
    * 
    * @throws Exception thrown if the class schema is invalid
    */
   private void scan(Class real, Class type) throws Exception {
      Method[] method = type.getDeclaredMethods();

      for(int i = 0; i < method.length; i++) {         
         scan(method[i]);              
      }     
   }

   /**
    * This is used to acquire the optional Root from the
    * specified class. The root annotation provides information as
    * to how the object is to be parsed as well as other information
    * such as the name of the object if it is to be serialized.
    *
    * @param type this is the type of the class to be inspected
    */    
   private void root(Class type) {
      if(type.isAnnotationPresent(Root.class)) {
         root = type.getAnnotation(Root.class);
      }
   }
   
   /**
    * This is used to acquire the optional order annotation to provide
    * order to the elements and attributes for the generated XML. This
    * acts as an override to the order provided by the declaration of
    * the types within the object.  
    * 
    * @param type this is the type to be scanned for the order
    */
   private void order(Class type) {
      if(type.isAnnotationPresent(Order.class)) {
         order = type.getAnnotation(Order.class);
      }
   }
   
   /**
    * This is use to scan for Namespace annotations on
    * the class. Once a namespace has been located then it is used
    * to populate the internal namespace decorator. This can then be
    * used to decorate any output node that requires it.
    * 
    * @param type this is the XML schema class to scan for namespaces
    */
   private void namespace(Class type) {
      if(type.isAnnotationPresent(Namespace.class)) {
         namespace = type.getAnnotation(Namespace.class);
         
         if(namespace != null) {
            decorator.add(namespace);
         }
      }
   }
   
   /**
    * This is use to scan for NamespaceList annotations 
    * on the class. Once a namespace list has been located then it is 
    * used to populate the internal namespace decorator. This can then 
    * be used to decorate any output node that requires it.
    * 
    * @param type this is the XML class to scan for namespace lists
    */
   private void scope(Class type) {
      if(type.isAnnotationPresent(NamespaceList.class)) {
         NamespaceList scope = type.getAnnotation(NamespaceList.class);
         Namespace[] list = scope.value();
         
         for(Namespace name : list) {
            decorator.add(name);
         }
      }
   }
   
   /**
    * This is used to scan the specified object to extract the fields
    * and methods that are to be used in the serialization process.
    * This will acquire all fields and getter setter pairs that have
    * been annotated with the XML annotations.
    *
    * @param type this is the object type that is to be scanned
    */  
   private void process(Class type) throws Exception {
      if(namespace != null) {
         decorator.set(namespace);
      }
   }
   
   /**
    * Scans the provided method for a persister callback method. If 
    * the method contains an method annotated as a callback that 
    * method is stored so that it can be invoked by the persister
    * during the serialization and deserialization process.
    * 
    * @param method this is the method to scan for callback methods
    */
   private void scan(Method method) {
      if(commit == null) {           
         commit(method);
      }
      if(validate == null) {      
         validate(method);
      }
      if(persist == null) {      
         persist(method);
      }
      if(complete == null) {      
         complete(method);
      }    
      if(replace == null) {
         replace(method);              
      }   
      if(resolve == null) {
         resolve(method);              
      }  
   }
   
   /**
    * This method is used to check the provided method to determine
    * if it contains the Replace annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */ 
   private void replace(Method method) {
      Annotation mark = method.getAnnotation(Replace.class);

      if(mark != null) {
         replace = getFunction(method);                    
      }      
   }
   
   /**
    * This method is used to check the provided method to determine
    * if it contains the Resolve annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */ 
   private void resolve(Method method) {
      Annotation mark = method.getAnnotation(Resolve.class);

      if(mark != null) {
         resolve = getFunction(method);                    
      }      
   }
   
   /**
    * This method is used to check the provided method to determine
    * if it contains the Commit annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */ 
   private void commit(Method method) {
      Annotation mark = method.getAnnotation(Commit.class);

      if(mark != null) {
         commit = getFunction(method);                    
      }    
   }
   
   /**
    * This method is used to check the provided method to determine
    * if it contains the Validate annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */ 
   private void validate(Method method) {
      Annotation mark = method.getAnnotation(Validate.class);

      if(mark != null) {
         validate = getFunction(method);                    
      }         
   }
   
   /**
    * This method is used to check the provided method to determine
    * if it contains the Persist annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */    
   private void persist(Method method) {
      Annotation mark = method.getAnnotation(Persist.class);

      if(mark != null) {
         persist = getFunction(method);                    
      }      
   }

   /**
    * This method is used to check the provided method to determine
    * if it contains the Complete annotation. If the
    * method contains the required annotation it is stored so that
    * it can be invoked during the deserialization process.
    *
    * @param method this is the method checked for the annotation
    */ 
   private void complete(Method method) {
      Annotation mark = method.getAnnotation(Complete.class);

      if(mark != null) {
         complete = getFunction(method);                    
      }      
   } 
   
   /**
    * This is used to acquire a Function object for the
    * method provided. The function returned will allow the callback
    * method to be invoked when given the context and target object.
    * 
    * @param method this is the method that is to be invoked
    * 
    * @return this returns the function that is to be invoked
    */
   private Function getFunction(Method method) {
      boolean contextual = isContextual(method);
      
      if(!method.isAccessible()) {
         method.setAccessible(true);
      }
      return new Function(method, contextual);
   }
   
   /**
    * This is used to determine whether the annotated method takes a
    * contextual object. If the method takes a Map then
    * this returns true, otherwise it returns false.
    *
    * @param method this is the method to check the parameters of
    *
    * @return this returns true if the method takes a map object
    */ 
   private boolean isContextual(Method method)  {
      Class[] list = method.getParameterTypes();

      if(list.length == 1) {
         return Map.class.equals(list[0]);                 
      }      
      return false;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy