001/* 002 * TreeStrategy.java July 2006 003 * 004 * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net> 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 015 * implied. See the License for the specific language governing 016 * permissions and limitations under the License. 017 */ 018 019package org.simpleframework.xml.strategy; 020 021import static org.simpleframework.xml.strategy.Name.LABEL; 022import static org.simpleframework.xml.strategy.Name.LENGTH; 023 024import java.lang.reflect.Array; 025import java.util.Map; 026 027import org.simpleframework.xml.stream.Node; 028import org.simpleframework.xml.stream.NodeMap; 029 030/** 031 * The <code>TreeStrategy</code> object is used to provide a simple 032 * strategy for handling object graphs in a tree structure. This does 033 * not resolve cycles in the object graph. This will make use of the 034 * specified class attribute to resolve the class to use for a given 035 * element during the deserialization process. For the serialization 036 * process the "class" attribute will be added to the element specified. 037 * If there is a need to use an attribute name other than "class" then 038 * the name of the attribute to use can be specified. 039 * 040 * @author Niall Gallagher 041 * 042 * @see org.simpleframework.xml.strategy.CycleStrategy 043 */ 044public class TreeStrategy implements Strategy { 045 046 /** 047 * This is the loader that is used to load the specified class. 048 */ 049 private final Loader loader; 050 051 /** 052 * This is the attribute that is used to determine an array size. 053 */ 054 private final String length; 055 056 /** 057 * This is the attribute that is used to determine the real type. 058 */ 059 private final String label; 060 061 /** 062 * Constructor for the <code>TreeStrategy</code> object. This 063 * is used to create a strategy that can resolve and load class 064 * objects for deserialization using a "class" attribute. Also 065 * for serialization this will add the appropriate "class" value. 066 */ 067 public TreeStrategy() { 068 this(LABEL, LENGTH); 069 } 070 071 /** 072 * Constructor for the <code>TreeStrategy</code> object. This 073 * is used to create a strategy that can resolve and load class 074 * objects for deserialization using the specified attribute. 075 * The attribute value can be any legal XML attribute name. 076 * 077 * @param label this is the name of the attribute to use 078 * @param length this is used to determine the array length 079 */ 080 public TreeStrategy(String label, String length) { 081 this.loader = new Loader(); 082 this.length = length; 083 this.label = label; 084 } 085 086 /** 087 * This is used to resolve and load a class for the given element. 088 * Resolution of the class to used is done by inspecting the 089 * XML element provided. If there is a "class" attribute on the 090 * element then its value is used to resolve the class to use. 091 * If no such attribute exists on the element this returns null. 092 * 093 * @param type this is the type of the XML element expected 094 * @param node this is the element used to resolve an override 095 * @param map this is used to maintain contextual information 096 * 097 * @return returns the class that should be used for the object 098 * 099 * @throws Exception thrown if the class cannot be resolved 100 */ 101 public Value read(Type type, NodeMap node, Map map) throws Exception { 102 Class actual = readValue(type, node); 103 Class expect = type.getType(); 104 105 if(expect.isArray()) { 106 return readArray(actual, node); 107 } 108 if(expect != actual) { 109 return new ObjectValue(actual); 110 } 111 return null; 112 } 113 114 /** 115 * This is used to resolve and load a class for the given element. 116 * Resolution of the class to used is done by inspecting the 117 * XML element provided. If there is a "class" attribute on the 118 * element then its value is used to resolve the class to use. 119 * This also expects a "length" attribute for the array length. 120 * 121 * @param type this is the type of the XML element expected 122 * @param node this is the element used to resolve an override 123 * 124 * @return returns the class that should be used for the object 125 * 126 * @throws Exception thrown if the class cannot be resolved 127 */ 128 private Value readArray(Class type, NodeMap node) throws Exception { 129 Node entry = node.remove(length); 130 int size = 0; 131 132 if(entry != null) { 133 String value = entry.getValue(); 134 size = Integer.parseInt(value); 135 } 136 return new ArrayValue(type, size); 137 } 138 139 /** 140 * This is used to resolve and load a class for the given element. 141 * Resolution of the class to used is done by inspecting the 142 * XML element provided. If there is a "class" attribute on the 143 * element then its value is used to resolve the class to use. 144 * If no such attribute exists the specified field is returned, 145 * or if the field type is an array then the component type. 146 * 147 * @param type this is the type of the XML element expected 148 * @param node this is the element used to resolve an override 149 * 150 * @return returns the class that should be used for the object 151 * 152 * @throws Exception thrown if the class cannot be resolved 153 */ 154 private Class readValue(Type type, NodeMap node) throws Exception { 155 Node entry = node.remove(label); 156 Class expect = type.getType(); 157 158 if(entry != null) { 159 String name = entry.getValue(); 160 Class actual = loader.load(name); 161 // Arrays are annotated with the type of the element. 162 if (expect.isArray()) { 163 if (actual == expect.getComponentType()) 164 actual = expect; 165 else 166 actual = Array.newInstance(actual, 0).getClass(); 167 } 168 expect = actual; 169 } 170 return expect; 171 } 172 173 /** 174 * This is used to attach a attribute to the provided element 175 * that is used to identify the class. The attribute name is 176 * "class" and has the value of the fully qualified class 177 * name for the object provided. This will only be invoked 178 * if the object class is different from the field class. 179 * 180 * @param type this is the declared class for the field used 181 * @param value this is the instance variable being serialized 182 * @param node this is the element used to represent the value 183 * @param map this is used to maintain contextual information 184 * 185 * @return this returns true if serialization is complete 186 */ 187 public boolean write(Type type, Object value, NodeMap node, Map map){ 188 Class actual = value.getClass(); 189 Class expect = type.getType(); 190 Class real = actual; 191 192 if(actual.isArray()) { 193 real = writeArray(expect, value, node); 194 } 195 if(actual != expect) { 196 node.put(label, real.getName()); 197 } 198 return false; 199 } 200 201 /** 202 * This is used to add a length attribute to the element due to 203 * the fact that the serialized value is an array. The length 204 * of the array is acquired and inserted in to the attributes. 205 * 206 * @param field this is the field type for the array to set 207 * @param value this is the actual value for the array to set 208 * @param node this is the map of attributes for the element 209 * 210 * @return returns the array component type that is set 211 */ 212 private Class writeArray(Class field, Object value, NodeMap node){ 213 int size = Array.getLength(value); 214 215 if(length != null) { 216 node.put(length, String.valueOf(size)); 217 } 218 return field.getComponentType(); 219 } 220}