| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| MethodScanner |
|
| 0.0;0 | ||||
| MethodScanner$1 |
|
| 0.0;0 | ||||
| MethodScanner$PartMap |
|
| 0.0;0 |
| 1 | /* | |
| 2 | * MethodScanner.java April 2007 | |
| 3 | * | |
| 4 | * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> | |
| 5 | * | |
| 6 | * This library is free software; you can redistribute it and/or | |
| 7 | * modify it under the terms of the GNU Lesser General Public | |
| 8 | * License as published by the Free Software Foundation. | |
| 9 | * | |
| 10 | * This library is distributed in the hope that it will be useful, | |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 13 | * GNU Lesser General Public License for more details. | |
| 14 | * | |
| 15 | * You should have received a copy of the GNU Lesser General | |
| 16 | * Public License along with this library; if not, write to the | |
| 17 | * Free Software Foundation, Inc., 59 Temple Place, Suite 330, | |
| 18 | * Boston, MA 02111-1307 USA | |
| 19 | */ | |
| 20 | ||
| 21 | package org.simpleframework.xml.load; | |
| 22 | ||
| 23 | import org.simpleframework.xml.Attribute; | |
| 24 | import org.simpleframework.xml.Element; | |
| 25 | import org.simpleframework.xml.ElementArray; | |
| 26 | import org.simpleframework.xml.ElementList; | |
| 27 | import org.simpleframework.xml.ElementMap; | |
| 28 | import org.simpleframework.xml.Text; | |
| 29 | import java.lang.annotation.Annotation; | |
| 30 | import java.lang.reflect.Method; | |
| 31 | import java.util.LinkedHashMap; | |
| 32 | import java.util.Iterator; | |
| 33 | ||
| 34 | /** | |
| 35 | * The <code>MethodScanner</code> object is used to scan an object | |
| 36 | * for matching get and set methods for an XML annotation. This will | |
| 37 | * scan for annotated methods starting with the most specialized | |
| 38 | * class up the class hierarchy. Thus, annotated methods can be | |
| 39 | * overridden in a type specialization. | |
| 40 | * <p> | |
| 41 | * The annotated methods must be either a getter or setter method | |
| 42 | * following the Java Beans naming conventions. This convention is | |
| 43 | * such that a method must begin with "get", "set", or "is". A pair | |
| 44 | * of set and get methods for an annotation must make use of the | |
| 45 | * same type. For instance if the return type for the get method | |
| 46 | * was <code>String</code> then the set method must have a single | |
| 47 | * argument parameter that takes a <code>String</code> type. | |
| 48 | * <p> | |
| 49 | * For a method to be considered there must be both the get and set | |
| 50 | * methods. If either method is missing then the scanner fails with | |
| 51 | * an exception. Also, if an annotation marks a method which does | |
| 52 | * not follow Java Bean naming conventions an exception is thrown. | |
| 53 | * | |
| 54 | * @author Niall Gallagher | |
| 55 | */ | |
| 56 | class MethodScanner extends ContactList { | |
| 57 | ||
| 58 | /** | |
| 59 | * This is used to acquire the hierarchy for the class scanned. | |
| 60 | */ | |
| 61 | private Hierarchy hierarchy; | |
| 62 | ||
| 63 | /** | |
| 64 | * This is used to collect all the set methods from the object. | |
| 65 | */ | |
| 66 | private PartMap write; | |
| 67 | ||
| 68 | /** | |
| 69 | * This is used to collect all the get methods from the object. | |
| 70 | */ | |
| 71 | private PartMap read; | |
| 72 | ||
| 73 | /** | |
| 74 | * This is the type of the object that is being scanned. | |
| 75 | */ | |
| 76 | private Class type; | |
| 77 | ||
| 78 | /** | |
| 79 | * Constructor for the <code>MethodScanner</code> object. This is | |
| 80 | * used to create an object that will scan the specified class | |
| 81 | * such that all bean property methods can be paired under the | |
| 82 | * XML annotation specified within the class. | |
| 83 | * | |
| 84 | * @param type this is the type that is to be scanned for methods | |
| 85 | * | |
| 86 | * @throws Exception thrown if there was a problem scanning | |
| 87 | */ | |
| 88 | 306 | public MethodScanner(Class type) throws Exception { |
| 89 | 306 | this.hierarchy = new Hierarchy(type); |
| 90 | 306 | this.write = new PartMap(); |
| 91 | 306 | this.read = new PartMap(); |
| 92 | 306 | this.type = type; |
| 93 | 306 | this.scan(type); |
| 94 | 302 | } |
| 95 | ||
| 96 | /** | |
| 97 | * This method is used to scan the class hierarchy for each class | |
| 98 | * in order to extract methods that contain XML annotations. If | |
| 99 | * a method is annotated it is converted to a contact so that | |
| 100 | * it can be used during serialization and deserialization. | |
| 101 | * | |
| 102 | * @param type this is the type to be scanned for methods | |
| 103 | * | |
| 104 | * @throws Exception thrown if the object schema is invalid | |
| 105 | */ | |
| 106 | private void scan(Class type) throws Exception { | |
| 107 | 306 | for(Class next : hierarchy) { |
| 108 | 732 | scan(type, next); |
| 109 | 731 | } |
| 110 | 305 | build(); |
| 111 | 303 | validate(); |
| 112 | 302 | } |
| 113 | ||
| 114 | /** | |
| 115 | * This is used to scan the declared methods within the specified | |
| 116 | * class. Each method will be checked to determine if it contains | |
| 117 | * an XML element and can be used as a <code>Contact</code> for | |
| 118 | * an entity within the object. | |
| 119 | * | |
| 120 | * @param real this is the actual type of the object scanned | |
| 121 | * @param type this is one of the super classes for the object | |
| 122 | * | |
| 123 | * @throws Exception thrown if the class schema is invalid | |
| 124 | */ | |
| 125 | private void scan(Class real, Class type) throws Exception { | |
| 126 | 732 | Method[] method = type.getDeclaredMethods(); |
| 127 | ||
| 128 | 9630 | for(int i = 0; i < method.length; i++) { |
| 129 | 8899 | scan(method[i]); |
| 130 | } | |
| 131 | 731 | } |
| 132 | ||
| 133 | /** | |
| 134 | * This is used to scan all annotations within the given method. | |
| 135 | * Each annotation is checked against the set of supported XML | |
| 136 | * annotations. If the annotation is one of the XML annotations | |
| 137 | * then the method is considered for acceptance as either a | |
| 138 | * get method or a set method for the annotated property. | |
| 139 | * | |
| 140 | * @param method the method to be scanned for XML annotations | |
| 141 | * | |
| 142 | * @throws Exception if the method is not a Java Bean method | |
| 143 | */ | |
| 144 | private void scan(Method method) throws Exception { | |
| 145 | 8899 | Annotation[] list = method.getDeclaredAnnotations(); |
| 146 | ||
| 147 | 9121 | for(int i = 0; i < list.length; i++) { |
| 148 | 223 | scan(method, list[i]); |
| 149 | } | |
| 150 | 8898 | } |
| 151 | ||
| 152 | /** | |
| 153 | * This reflectively checks the annotation to determine the type | |
| 154 | * of annotation it represents. If it represents an XML schema | |
| 155 | * annotation it is used to create a <code>Contact</code> which | |
| 156 | * can be used to represent the method within the source object. | |
| 157 | * | |
| 158 | * @param method the method that the annotation comes from | |
| 159 | * @param label the annotation used to model the XML schema | |
| 160 | * | |
| 161 | * @throws Exception if there is more than one text annotation | |
| 162 | */ | |
| 163 | private void scan(Method method, Annotation label) throws Exception { | |
| 164 | 223 | if(label instanceof Attribute) { |
| 165 | 26 | process(method, label); |
| 166 | } | |
| 167 | 223 | if(label instanceof ElementList) { |
| 168 | 8 | process(method, label); |
| 169 | } | |
| 170 | 223 | if(label instanceof ElementArray) { |
| 171 | 0 | process(method, label); |
| 172 | } | |
| 173 | 223 | if(label instanceof ElementMap) { |
| 174 | 0 | process(method, label); |
| 175 | } | |
| 176 | 223 | if(label instanceof Element) { |
| 177 | 82 | process(method, label); |
| 178 | } | |
| 179 | 222 | if(label instanceof Text) { |
| 180 | 4 | process(method, label); |
| 181 | } | |
| 182 | 222 | } |
| 183 | ||
| 184 | /** | |
| 185 | * This is used to classify the specified method into either a get | |
| 186 | * or set method. If the method is neither then an exception is | |
| 187 | * thrown to indicate that the XML annotations can only be used | |
| 188 | * with methods following the Java Bean naming conventions. Once | |
| 189 | * the method is classified is is added to either the read or | |
| 190 | * write map so that it can be paired after scanning is complete. | |
| 191 | * | |
| 192 | * @param method this is the method that is to be classified | |
| 193 | * @param label this is the annotation applied to the method | |
| 194 | */ | |
| 195 | private void process(Method method, Annotation label) throws Exception { | |
| 196 | 120 | MethodPart part = MethodPartFactory.getInstance(method, label); |
| 197 | 119 | MethodType type = part.getMethodType(); |
| 198 | ||
| 199 | 119 | if(type == MethodType.GET) { |
| 200 | 58 | process(part, read); |
| 201 | } | |
| 202 | 119 | if(type == MethodType.IS) { |
| 203 | 0 | process(part, read); |
| 204 | } | |
| 205 | 119 | if(type == MethodType.SET) { |
| 206 | 61 | process(part, write); |
| 207 | } | |
| 208 | 119 | } |
| 209 | ||
| 210 | /** | |
| 211 | * This is used to determine whether the specified method can be | |
| 212 | * inserted into the given <code>PartMap</code>. This ensures | |
| 213 | * that only the most specialized method is considered, which | |
| 214 | * enables annotated methods to be overridden in subclasses. | |
| 215 | * | |
| 216 | * @param method this is the method part that is to be inserted | |
| 217 | * @param map this is the part map used to contain the method | |
| 218 | */ | |
| 219 | private void process(MethodPart method, PartMap map) { | |
| 220 | 119 | String name = method.getName(); |
| 221 | ||
| 222 | 119 | if(name != null) { |
| 223 | 119 | map.put(name, method); |
| 224 | } | |
| 225 | 119 | } |
| 226 | ||
| 227 | /** | |
| 228 | * This method is used to pair the get methods with a matching set | |
| 229 | * method. This pairs methods using the Java Bean method name, the | |
| 230 | * names must match exactly, meaning that the case and value of | |
| 231 | * the strings must be identical. Also in order for this to succeed | |
| 232 | * the types for the methods and the annotation must also match. | |
| 233 | * | |
| 234 | * @throws Exception thrown if there is a problem matching methods | |
| 235 | */ | |
| 236 | private void build() throws Exception { | |
| 237 | 305 | for(String name : read) { |
| 238 | 48 | MethodPart part = read.get(name); |
| 239 | ||
| 240 | 48 | if(part != null) { |
| 241 | 48 | build(part, name); |
| 242 | } | |
| 243 | 46 | } |
| 244 | 303 | } |
| 245 | ||
| 246 | /** | |
| 247 | * This method is used to pair the get methods with a matching set | |
| 248 | * method. This pairs methods using the Java Bean method name, the | |
| 249 | * names must match exactly, meaning that the case and value of | |
| 250 | * the strings must be identical. Also in order for this to succeed | |
| 251 | * the types for the methods and the annotation must also match. | |
| 252 | * | |
| 253 | * @param read this is a get method that has been extracted | |
| 254 | * @param name this is the Java Bean methos name to be matched | |
| 255 | * | |
| 256 | * @throws Exception thrown if there is a problem matching methods | |
| 257 | */ | |
| 258 | private void build(MethodPart read, String name) throws Exception { | |
| 259 | 48 | MethodPart match = write.take(name); |
| 260 | 48 | Method method = read.getMethod(); |
| 261 | ||
| 262 | 48 | if(match == null) { |
| 263 | 0 | throw new MethodException("No matching set method for %s in %s", method, type); |
| 264 | } | |
| 265 | 48 | build(read, match); |
| 266 | 46 | } |
| 267 | ||
| 268 | /** | |
| 269 | * This method is used to pair the get methods with a matching set | |
| 270 | * method. This pairs methods using the Java Bean method name, the | |
| 271 | * names must match exactly, meaning that the case and value of | |
| 272 | * the strings must be identical. Also in order for this to succeed | |
| 273 | * the types for the methods and the annotation must also match. | |
| 274 | * | |
| 275 | * @param read this is a get method that has been extracted | |
| 276 | * @param write this is the write method to compare details with | |
| 277 | * | |
| 278 | * @throws Exception thrown if there is a problem matching methods | |
| 279 | */ | |
| 280 | private void build(MethodPart read, MethodPart write) throws Exception { | |
| 281 | 48 | Annotation label = read.getAnnotation(); |
| 282 | 48 | String name = read.getName(); |
| 283 | ||
| 284 | 48 | if(!write.getAnnotation().equals(label)) { |
| 285 | 1 | throw new MethodException("Annotations do not match for '%s' in %s", name, type); |
| 286 | } | |
| 287 | 47 | Class type = read.getType(); |
| 288 | ||
| 289 | 47 | if(type != write.getType()) { |
| 290 | 1 | throw new MethodException("Method types do not match for %s in %s", name, type); |
| 291 | } | |
| 292 | 46 | add(new MethodContact(read, write)); |
| 293 | 46 | } |
| 294 | ||
| 295 | /** | |
| 296 | * This is used to validate the object once all the get methods | |
| 297 | * have been matched with a set method. This ensures that there | |
| 298 | * is not a set method within the object that does not have a | |
| 299 | * match, therefore violating the contract of a property. | |
| 300 | * | |
| 301 | * @throws Exception thrown if there is a unmatched set method | |
| 302 | */ | |
| 303 | private void validate() throws Exception { | |
| 304 | 303 | for(String name : write) { |
| 305 | 1 | MethodPart part = write.get(name); |
| 306 | ||
| 307 | 1 | if(part != null) { |
| 308 | 1 | validate(part, name); |
| 309 | } | |
| 310 | 0 | } |
| 311 | 302 | } |
| 312 | ||
| 313 | /** | |
| 314 | * This is used to validate the object once all the get methods | |
| 315 | * have been matched with a set method. This ensures that there | |
| 316 | * is not a set method within the object that does not have a | |
| 317 | * match, therefore violating the contract of a property. | |
| 318 | * | |
| 319 | * @param write this is a get method that has been extracted | |
| 320 | * @param name this is the Java Bean methods name to be matched | |
| 321 | * | |
| 322 | * @throws Exception thrown if there is a unmatched set method | |
| 323 | */ | |
| 324 | private void validate(MethodPart write, String name) throws Exception { | |
| 325 | 1 | MethodPart match = read.take(name); |
| 326 | 1 | Method method = write.getMethod(); |
| 327 | ||
| 328 | 1 | if(match == null) { |
| 329 | 1 | throw new MethodException("No matching get method for %s in %s", method, type); |
| 330 | } | |
| 331 | 0 | } |
| 332 | ||
| 333 | /** | |
| 334 | * The <code>PartMap</code> is used to contain method parts using | |
| 335 | * the Java Bean method name for the part. This ensures that the | |
| 336 | * scanned and extracted methods can be acquired using a common | |
| 337 | * name, which should be the parsed Java Bean method name. | |
| 338 | * | |
| 339 | * @see org.simpleframework.xml.load.MethodPart | |
| 340 | */ | |
| 341 | 1224 | private class PartMap extends LinkedHashMap<String, MethodPart> implements Iterable<String>{ |
| 342 | ||
| 343 | /** | |
| 344 | * This returns an iterator for the Java Bean method names for | |
| 345 | * the <code>MethodPart</code> objects that are stored in the | |
| 346 | * map. This allows names to be iterated easily in a for loop. | |
| 347 | * | |
| 348 | * @return this returns an iterator for the method name keys | |
| 349 | */ | |
| 350 | public Iterator<String> iterator() { | |
| 351 | 608 | return keySet().iterator(); |
| 352 | } | |
| 353 | ||
| 354 | /** | |
| 355 | * This is used to acquire the method part for the specified | |
| 356 | * method name. This will remove the method part from this map | |
| 357 | * so that it can be checked later to ensure what remains. | |
| 358 | * | |
| 359 | * @param name this is the method name to get the method with | |
| 360 | * | |
| 361 | * @return this returns the method part for the given key | |
| 362 | */ | |
| 363 | public MethodPart take(String name) { | |
| 364 | 49 | return remove(name); |
| 365 | } | |
| 366 | } | |
| 367 | } |