Coverage Report - org.simpleframework.xml.core.AnnotationHandler
 
Classes in this File Line Coverage Branch Coverage Complexity
AnnotationHandler
95%
47/49
80%
16/20
3
 
 1  
 /*
 2  
  * AnnotationHandler.java December 2009
 3  
  *
 4  
  * Copyright (C) 2009, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 15  
  * implied. See the License for the specific language governing 
 16  
  * permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.simpleframework.xml.core;
 20  
 
 21  
 import java.lang.annotation.Annotation;
 22  
 import java.lang.reflect.InvocationHandler;
 23  
 import java.lang.reflect.Method;
 24  
 
 25  
 /**
 26  
  * The <code>AnnotationHandler</code> object is used to handle all
 27  
  * invocation made on a synthetic annotation. This is required so
 28  
  * that annotations can be created without an implementation. The
 29  
  * <code>java.lang.reflect.Proxy</code> object is used to wrap this
 30  
  * invocation handler with the annotation interface. 
 31  
  * 
 32  
  * @author Niall Gallagher
 33  
  */
 34  
 class AnnotationHandler implements InvocationHandler {
 35  
 
 36  
    /**
 37  
     * This is the method used to acquire the associated type.
 38  
     */
 39  
    private static final String CLASS = "annotationType";
 40  
    
 41  
    /**
 42  
     * This is used to acquire a string value for the annotation.
 43  
     */
 44  
    private static final String STRING = "toString";
 45  
    
 46  
    /**
 47  
     * This is used to determine if annotations are optional.
 48  
     */
 49  
    private static final String REQUIRED = "required";
 50  
    
 51  
    /**
 52  
     * This is used to perform a comparison of the annotations.
 53  
     */
 54  
    private static final String EQUAL = "equals";
 55  
 
 56  
    /**
 57  
     * This is used to perform a comparison of the annotations.
 58  
     */
 59  
    private final Comparer comparer;
 60  
    
 61  
    /**
 62  
     * This is annotation type associated with this handler. 
 63  
     */
 64  
    private final Class type;
 65  
    
 66  
    /**
 67  
     * This is used to determine if the annotation is required.
 68  
     */
 69  
    private final boolean required;
 70  
    
 71  
    /**
 72  
     * Constructor for the <code>AnnotationHandler</code> object. This 
 73  
     * is used to create a handler for invocations on a synthetic 
 74  
     * annotation. The annotation type wrapped must be provided. By
 75  
     * default the requirement of the annotations is true.
 76  
     * 
 77  
     * @param type this is the annotation type that this is wrapping
 78  
     */
 79  
    public AnnotationHandler(Class type) {
 80  4
       this(type, true);
 81  4
    }
 82  
 
 83  
    /**
 84  
     * Constructor for the <code>AnnotationHandler</code> object. This 
 85  
     * is used to create a handler for invocations on a synthetic 
 86  
     * annotation. The annotation type wrapped must be provided.
 87  
     * 
 88  
     * @param type this is the annotation type that this is wrapping
 89  
     * @param required this is used to determine if its required
 90  
     */
 91  474
    public AnnotationHandler(Class type, boolean required) {
 92  474
       this.comparer = new Comparer();
 93  474
       this.required = required;
 94  474
       this.type = type;
 95  474
    }
 96  
 
 97  
    /**
 98  
     * This is used to handle all invocations on the wrapped annotation.
 99  
     * Typically the response to an invocation will result in the
 100  
     * default value of the annotation attribute being returned. If the
 101  
     * method is an <code>equals</code> or <code>toString</code> then
 102  
     * this will be handled by an internal implementation.
 103  
     * 
 104  
     * @param proxy this is the proxy object the invocation was made on
 105  
     * @param method this is the method that was invoked on the proxy
 106  
     * @param list this is the list of parameters to be used
 107  
     * 
 108  
     * @return this is used to return the result of the invocation
 109  
     */
 110  
    public Object invoke(Object proxy, Method method, Object[] list) throws Throwable {
 111  3734
       String name = method.getName();
 112  
 
 113  3734
       if(name.equals(STRING)) {
 114  0
          return toString();
 115  
       }
 116  3734
       if(name.equals(EQUAL)) {
 117  35
          return equals(proxy, list);
 118  
       }
 119  3699
       if(name.equals(CLASS)) {
 120  1539
          return type;
 121  
       }
 122  2160
       if(name.equals(REQUIRED)) {
 123  499
          return required;
 124  
       }
 125  1661
       return method.getDefaultValue();
 126  
    }
 127  
 
 128  
    /**
 129  
     * This is used to determine if two annotations are equals based
 130  
     * on the attributes of the annotation. The comparison done can
 131  
     * ignore specific attributes, for instance the name attribute.
 132  
     * 
 133  
     * @param proxy this is the annotation the invocation was made on
 134  
     * @param list this is the parameters provided to the invocation
 135  
     * 
 136  
     * @return this returns true if the annotations are equals
 137  
     */
 138  
    private boolean equals(Object proxy, Object[] list) throws Throwable {
 139  35
       Annotation left = (Annotation) proxy;
 140  35
       Annotation right = (Annotation) list[0];
 141  
 
 142  35
       if(left.annotationType() != right.annotationType()) {
 143  0
          throw new PersistenceException("Annotation %s is not the same as %s", left, right);
 144  
       }
 145  35
       return comparer.equals(left, right);
 146  
    }
 147  
 
 148  
    /**
 149  
     * This is used to build a string from the annotation. The string
 150  
     * produces adheres to the typical string representation of a
 151  
     * normal annotation. This ensures that an exceptions that are
 152  
     * thrown with a string representation of the annotation are
 153  
     * identical to those thrown with a normal annotation.
 154  
     *
 155  
     * @return returns a string representation of the annotation 
 156  
     */
 157  
    public String toString() {
 158  4
       StringBuilder builder = new StringBuilder();
 159  
  
 160  4
       if(type != null) {
 161  4
          name(builder);
 162  4
          attributes(builder);
 163  
       }
 164  4
       return builder.toString();
 165  
    }
 166  
    
 167  
    /**
 168  
     * This is used to build a string from the annotation. The string
 169  
     * produces adheres to the typical string representation of a
 170  
     * normal annotation. This ensures that an exceptions that are
 171  
     * thrown with a string representation of the annotation are
 172  
     * identical to those thrown with a normal annotation.
 173  
     * 
 174  
     * @param builder this is the builder used to compose the text
 175  
     */
 176  
    private void name(StringBuilder builder) {
 177  4
       String name = type.getName();
 178  
       
 179  4
       if(name != null) {
 180  4
          builder.append('@');
 181  4
          builder.append(name);
 182  4
          builder.append('(');
 183  
       }  
 184  4
    }
 185  
    
 186  
    /**
 187  
     * This is used to build a string from the annotation. The string
 188  
     * produces adheres to the typical string representation of a
 189  
     * normal annotation. This ensures that an exceptions that are
 190  
     * thrown with a string representation of the annotation are
 191  
     * identical to those thrown with a normal annotation.
 192  
     * 
 193  
     * @param builder this is the builder used to compose the text
 194  
     */
 195  
    private void attributes(StringBuilder builder) {
 196  4
       Method[] list = type.getDeclaredMethods();
 197  
 
 198  31
       for(int i = 0; i < list.length; i++) {
 199  27
          String attribute = list[i].getName();
 200  27
          Object value = value(list[i]);
 201  
          
 202  27
          if(i > 0) {
 203  23
             builder.append(',');
 204  23
             builder.append(' ');
 205  
          }
 206  27
          builder.append(attribute);
 207  27
          builder.append('=');
 208  27
          builder.append(value);
 209  
       }
 210  4
       builder.append(')');
 211  4
    }
 212  
    
 213  
    /**
 214  
     * This is used to extract the default value used for the provided
 215  
     * annotation attribute. This will return the default value for 
 216  
     * all attributes except that it makes the requirement optional.
 217  
     * Making the requirement optional provides better functionality.
 218  
     * 
 219  
     * @param method this is the annotation representing the attribute
 220  
     * 
 221  
     * @return this returns the default value for the attribute
 222  
     */
 223  
    private Object value(Method method) {
 224  27
       String name = method.getName();
 225  
               
 226  27
       if(name.equals(REQUIRED)) {
 227  4
          return required;
 228  
       }
 229  23
       return method.getDefaultValue();
 230  
    }
 231  
 }