1/* 2 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.xml.internal.txw2; 27 28import com.sun.xml.internal.txw2.annotation.XmlAttribute; 29import com.sun.xml.internal.txw2.annotation.XmlElement; 30import com.sun.xml.internal.txw2.annotation.XmlNamespace; 31import com.sun.xml.internal.txw2.annotation.XmlValue; 32import com.sun.xml.internal.txw2.annotation.XmlCDATA; 33 34import javax.xml.namespace.QName; 35import java.lang.reflect.InvocationHandler; 36import java.lang.reflect.InvocationTargetException; 37import java.lang.reflect.Method; 38import java.lang.reflect.Proxy; 39 40/** 41 * Dynamically implements {@link TypedXmlWriter} interfaces. 42 * 43 * @author Kohsuke Kawaguchi 44 */ 45final class ContainerElement implements InvocationHandler, TypedXmlWriter { 46 47 final Document document; 48 49 /** 50 * Initially, point to the start tag token, but 51 * once we know we are done with the start tag, we will reset it to null 52 * so that the token sequence can be GC-ed. 53 */ 54 StartTag startTag; 55 final EndTag endTag = new EndTag(); 56 57 /** 58 * Namespace URI of this element. 59 */ 60 private final String nsUri; 61 62 /** 63 * When this element can accept more child content, this value 64 * is non-null and holds the last child {@link Content}. 65 * 66 * If this element is committed, this parameter is null. 67 */ 68 private Content tail; 69 70 /** 71 * Uncommitted {@link ContainerElement}s form a doubly-linked list, 72 * so that the parent can close them recursively. 73 */ 74 private ContainerElement prevOpen; 75 private ContainerElement nextOpen; 76 private final ContainerElement parent; 77 private ContainerElement lastOpenChild; 78 79 /** 80 * Set to true if the start eleent is blocked. 81 */ 82 private boolean blocked; 83 84 public ContainerElement(Document document,ContainerElement parent,String nsUri, String localName) { 85 this.parent = parent; 86 this.document = document; 87 this.nsUri = nsUri; 88 this.startTag = new StartTag(this,nsUri,localName); 89 tail = startTag; 90 91 if(isRoot()) 92 document.setFirstContent(startTag); 93 } 94 95 private boolean isRoot() { 96 return parent==null; 97 } 98 99 private boolean isCommitted() { 100 return tail==null; 101 } 102 103 public Document getDocument() { 104 return document; 105 } 106 107 boolean isBlocked() { 108 return blocked && !isCommitted(); 109 } 110 111 public void block() { 112 blocked = true; 113 } 114 115 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 116 if(method.getDeclaringClass()==TypedXmlWriter.class || method.getDeclaringClass()==Object.class) { 117 // forward to myself 118 try { 119 return method.invoke(this,args); 120 } catch (InvocationTargetException e) { 121 throw e.getTargetException(); 122 } 123 } 124 125 XmlAttribute xa = method.getAnnotation(XmlAttribute.class); 126 XmlValue xv = method.getAnnotation(XmlValue.class); 127 XmlElement xe = method.getAnnotation(XmlElement.class); 128 129 130 if(xa!=null) { 131 if(xv!=null || xe!=null) 132 throw new IllegalAnnotationException(method.toString()); 133 134 addAttribute(xa,method,args); 135 return proxy; // allow method chaining 136 } 137 if(xv!=null) { 138 if(xe!=null) 139 throw new IllegalAnnotationException(method.toString()); 140 141 _pcdata(args); 142 return proxy; // allow method chaining 143 } 144 145 return addElement(xe,method,args); 146 } 147 148 /** 149 * Writes an attribute. 150 */ 151 private void addAttribute(XmlAttribute xa, Method method, Object[] args) { 152 assert xa!=null; 153 154 checkStartTag(); 155 156 String localName = xa.value(); 157 if(xa.value().length()==0) 158 localName = method.getName(); 159 160 _attribute(xa.ns(),localName,args); 161 } 162 163 private void checkStartTag() { 164 if(startTag==null) 165 throw new IllegalStateException("start tag has already been written"); 166 } 167 168 /** 169 * Writes a new element. 170 */ 171 private Object addElement(XmlElement e, Method method, Object[] args) { 172 Class<?> rt = method.getReturnType(); 173 174 // the last precedence: default name 175 String nsUri = "##default"; 176 String localName = method.getName(); 177 178 if(e!=null) { 179 // then the annotation on this method 180 if(e.value().length()!=0) 181 localName = e.value(); 182 nsUri = e.ns(); 183 } 184 185 if(nsUri.equals("##default")) { 186 // look for the annotation on the declaring class 187 Class<?> c = method.getDeclaringClass(); 188 XmlElement ce = c.getAnnotation(XmlElement.class); 189 if(ce!=null) { 190 nsUri = ce.ns(); 191 } 192 193 if(nsUri.equals("##default")) 194 // then default to the XmlNamespace 195 nsUri = getNamespace(c.getPackage()); 196 } 197 198 199 200 if(rt==Void.TYPE) { 201 // leaf element with just a value 202 203 boolean isCDATA = method.getAnnotation(XmlCDATA.class)!=null; 204 205 StartTag st = new StartTag(document,nsUri,localName); 206 addChild(st); 207 for( Object arg : args ) { 208 Text text; 209 if(isCDATA) text = new Cdata(document,st,arg); 210 else text = new Pcdata(document,st,arg); 211 addChild(text); 212 } 213 addChild(new EndTag()); 214 return null; 215 } 216 if(TypedXmlWriter.class.isAssignableFrom(rt)) { 217 // sub writer 218 return _element(nsUri,localName,(Class)rt); 219 } 220 221 throw new IllegalSignatureException("Illegal return type: "+rt); 222 } 223 224 /** 225 * Decides the namespace URI of the given package. 226 */ 227 private String getNamespace(Package pkg) { 228 if(pkg==null) return ""; 229 230 String nsUri; 231 XmlNamespace ns = pkg.getAnnotation(XmlNamespace.class); 232 if(ns!=null) 233 nsUri = ns.value(); 234 else 235 nsUri = ""; 236 return nsUri; 237 } 238 239 /** 240 * Appends this child object to the tail. 241 */ 242 private void addChild(Content child) { 243 tail.setNext(document,child); 244 tail = child; 245 } 246 247 public void commit() { 248 commit(true); 249 } 250 251 public void commit(boolean includingAllPredecessors) { 252 _commit(includingAllPredecessors); 253 document.flush(); 254 } 255 256 private void _commit(boolean includingAllPredecessors) { 257 if(isCommitted()) return; 258 259 addChild(endTag); 260 if(isRoot()) 261 addChild(new EndDocument()); 262 tail = null; 263 264 // _commit predecessors if so told 265 if(includingAllPredecessors) { 266 for( ContainerElement e=this; e!=null; e=e.parent ) { 267 while(e.prevOpen!=null) { 268 e.prevOpen._commit(false); 269 // e.prevOpen should change as a result of committing it. 270 } 271 } 272 } 273 274 // _commit all children recursively 275 while(lastOpenChild!=null) 276 lastOpenChild._commit(false); 277 278 // remove this node from the link 279 if(parent!=null) { 280 if(parent.lastOpenChild==this) { 281 assert nextOpen==null : "this must be the last one"; 282 parent.lastOpenChild = prevOpen; 283 } else { 284 assert nextOpen.prevOpen==this; 285 nextOpen.prevOpen = this.prevOpen; 286 } 287 if(prevOpen!=null) { 288 assert prevOpen.nextOpen==this; 289 prevOpen.nextOpen = this.nextOpen; 290 } 291 } 292 293 this.nextOpen = null; 294 this.prevOpen = null; 295 } 296 297 public void _attribute(String localName, Object value) { 298 _attribute("",localName,value); 299 } 300 301 public void _attribute(String nsUri, String localName, Object value) { 302 checkStartTag(); 303 startTag.addAttribute(nsUri,localName,value); 304 } 305 306 public void _attribute(QName attributeName, Object value) { 307 _attribute(attributeName.getNamespaceURI(),attributeName.getLocalPart(),value); 308 } 309 310 public void _namespace(String uri) { 311 _namespace(uri,false); 312 } 313 314 public void _namespace(String uri, String prefix) { 315 if(prefix==null) 316 throw new IllegalArgumentException(); 317 checkStartTag(); 318 startTag.addNamespaceDecl(uri,prefix,false); 319 } 320 321 public void _namespace(String uri, boolean requirePrefix) { 322 checkStartTag(); 323 startTag.addNamespaceDecl(uri,null,requirePrefix); 324 } 325 326 public void _pcdata(Object value) { 327 // we need to allow this method even when startTag has already been completed. 328 // checkStartTag(); 329 addChild(new Pcdata(document,startTag,value)); 330 } 331 332 public void _cdata(Object value) { 333 addChild(new Cdata(document,startTag,value)); 334 } 335 336 public void _comment(Object value) throws UnsupportedOperationException { 337 addChild(new Comment(document,startTag,value)); 338 } 339 340 public <T extends TypedXmlWriter> T _element(String localName, Class<T> contentModel) { 341 return _element(nsUri,localName,contentModel); 342 } 343 344 public <T extends TypedXmlWriter> T _element(QName tagName, Class<T> contentModel) { 345 return _element(tagName.getNamespaceURI(),tagName.getLocalPart(),contentModel); 346 } 347 348 public <T extends TypedXmlWriter> T _element(Class<T> contentModel) { 349 return _element(TXW.getTagName(contentModel),contentModel); 350 } 351 352 public <T extends TypedXmlWriter> T _cast(Class<T> facadeType) { 353 return facadeType.cast(Proxy.newProxyInstance(facadeType.getClassLoader(),new Class[]{facadeType},this)); 354 } 355 356 public <T extends TypedXmlWriter> T _element(String nsUri, String localName, Class<T> contentModel) { 357 ContainerElement child = new ContainerElement(document,this,nsUri,localName); 358 addChild(child.startTag); 359 tail = child.endTag; 360 361 // update uncommitted link list 362 if(lastOpenChild!=null) { 363 assert lastOpenChild.parent==this; 364 365 assert child.prevOpen==null; 366 assert child.nextOpen==null; 367 child.prevOpen = lastOpenChild; 368 assert lastOpenChild.nextOpen==null; 369 lastOpenChild.nextOpen = child; 370 } 371 372 this.lastOpenChild = child; 373 374 return child._cast(contentModel); 375 } 376} 377