aoqi@0: /* aoqi@0: * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. aoqi@0: * aoqi@0: * This code is free software; you can redistribute it and/or modify it aoqi@0: * under the terms of the GNU General Public License version 2 only, as aoqi@0: * published by the Free Software Foundation. Oracle designates this aoqi@0: * particular file as subject to the "Classpath" exception as provided aoqi@0: * by Oracle in the LICENSE file that accompanied this code. aoqi@0: * aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that aoqi@0: * accompanied this code). aoqi@0: * aoqi@0: * You should have received a copy of the GNU General Public License version aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation, aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. aoqi@0: * aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA aoqi@0: * or visit www.oracle.com if you need additional information or have any aoqi@0: * questions. aoqi@0: */ aoqi@0: aoqi@0: package com.sun.xml.internal.txw2; aoqi@0: aoqi@0: import com.sun.xml.internal.txw2.annotation.XmlAttribute; aoqi@0: import com.sun.xml.internal.txw2.annotation.XmlElement; aoqi@0: import com.sun.xml.internal.txw2.annotation.XmlNamespace; aoqi@0: import com.sun.xml.internal.txw2.annotation.XmlValue; aoqi@0: import com.sun.xml.internal.txw2.annotation.XmlCDATA; aoqi@0: aoqi@0: import javax.xml.namespace.QName; aoqi@0: import java.lang.reflect.InvocationHandler; aoqi@0: import java.lang.reflect.InvocationTargetException; aoqi@0: import java.lang.reflect.Method; aoqi@0: import java.lang.reflect.Proxy; aoqi@0: aoqi@0: /** aoqi@0: * Dynamically implements {@link TypedXmlWriter} interfaces. aoqi@0: * aoqi@0: * @author Kohsuke Kawaguchi aoqi@0: */ aoqi@0: final class ContainerElement implements InvocationHandler, TypedXmlWriter { aoqi@0: aoqi@0: final Document document; aoqi@0: aoqi@0: /** aoqi@0: * Initially, point to the start tag token, but aoqi@0: * once we know we are done with the start tag, we will reset it to null aoqi@0: * so that the token sequence can be GC-ed. aoqi@0: */ aoqi@0: StartTag startTag; aoqi@0: final EndTag endTag = new EndTag(); aoqi@0: aoqi@0: /** aoqi@0: * Namespace URI of this element. aoqi@0: */ aoqi@0: private final String nsUri; aoqi@0: aoqi@0: /** aoqi@0: * When this element can accept more child content, this value aoqi@0: * is non-null and holds the last child {@link Content}. aoqi@0: * aoqi@0: * If this element is committed, this parameter is null. aoqi@0: */ aoqi@0: private Content tail; aoqi@0: aoqi@0: /** aoqi@0: * Uncommitted {@link ContainerElement}s form a doubly-linked list, aoqi@0: * so that the parent can close them recursively. aoqi@0: */ aoqi@0: private ContainerElement prevOpen; aoqi@0: private ContainerElement nextOpen; aoqi@0: private final ContainerElement parent; aoqi@0: private ContainerElement lastOpenChild; aoqi@0: aoqi@0: /** aoqi@0: * Set to true if the start eleent is blocked. aoqi@0: */ aoqi@0: private boolean blocked; aoqi@0: aoqi@0: public ContainerElement(Document document,ContainerElement parent,String nsUri, String localName) { aoqi@0: this.parent = parent; aoqi@0: this.document = document; aoqi@0: this.nsUri = nsUri; aoqi@0: this.startTag = new StartTag(this,nsUri,localName); aoqi@0: tail = startTag; aoqi@0: aoqi@0: if(isRoot()) aoqi@0: document.setFirstContent(startTag); aoqi@0: } aoqi@0: aoqi@0: private boolean isRoot() { aoqi@0: return parent==null; aoqi@0: } aoqi@0: aoqi@0: private boolean isCommitted() { aoqi@0: return tail==null; aoqi@0: } aoqi@0: aoqi@0: public Document getDocument() { aoqi@0: return document; aoqi@0: } aoqi@0: aoqi@0: boolean isBlocked() { aoqi@0: return blocked && !isCommitted(); aoqi@0: } aoqi@0: aoqi@0: public void block() { aoqi@0: blocked = true; aoqi@0: } aoqi@0: aoqi@0: public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { aoqi@0: if(method.getDeclaringClass()==TypedXmlWriter.class || method.getDeclaringClass()==Object.class) { aoqi@0: // forward to myself aoqi@0: try { aoqi@0: return method.invoke(this,args); aoqi@0: } catch (InvocationTargetException e) { aoqi@0: throw e.getTargetException(); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: XmlAttribute xa = method.getAnnotation(XmlAttribute.class); aoqi@0: XmlValue xv = method.getAnnotation(XmlValue.class); aoqi@0: XmlElement xe = method.getAnnotation(XmlElement.class); aoqi@0: aoqi@0: aoqi@0: if(xa!=null) { aoqi@0: if(xv!=null || xe!=null) aoqi@0: throw new IllegalAnnotationException(method.toString()); aoqi@0: aoqi@0: addAttribute(xa,method,args); aoqi@0: return proxy; // allow method chaining aoqi@0: } aoqi@0: if(xv!=null) { aoqi@0: if(xe!=null) aoqi@0: throw new IllegalAnnotationException(method.toString()); aoqi@0: aoqi@0: _pcdata(args); aoqi@0: return proxy; // allow method chaining aoqi@0: } aoqi@0: aoqi@0: return addElement(xe,method,args); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Writes an attribute. aoqi@0: */ aoqi@0: private void addAttribute(XmlAttribute xa, Method method, Object[] args) { aoqi@0: assert xa!=null; aoqi@0: aoqi@0: checkStartTag(); aoqi@0: aoqi@0: String localName = xa.value(); aoqi@0: if(xa.value().length()==0) aoqi@0: localName = method.getName(); aoqi@0: aoqi@0: _attribute(xa.ns(),localName,args); aoqi@0: } aoqi@0: aoqi@0: private void checkStartTag() { aoqi@0: if(startTag==null) aoqi@0: throw new IllegalStateException("start tag has already been written"); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Writes a new element. aoqi@0: */ aoqi@0: private Object addElement(XmlElement e, Method method, Object[] args) { aoqi@0: Class rt = method.getReturnType(); aoqi@0: aoqi@0: // the last precedence: default name aoqi@0: String nsUri = "##default"; aoqi@0: String localName = method.getName(); aoqi@0: aoqi@0: if(e!=null) { aoqi@0: // then the annotation on this method aoqi@0: if(e.value().length()!=0) aoqi@0: localName = e.value(); aoqi@0: nsUri = e.ns(); aoqi@0: } aoqi@0: aoqi@0: if(nsUri.equals("##default")) { aoqi@0: // look for the annotation on the declaring class aoqi@0: Class c = method.getDeclaringClass(); aoqi@0: XmlElement ce = c.getAnnotation(XmlElement.class); aoqi@0: if(ce!=null) { aoqi@0: nsUri = ce.ns(); aoqi@0: } aoqi@0: aoqi@0: if(nsUri.equals("##default")) aoqi@0: // then default to the XmlNamespace aoqi@0: nsUri = getNamespace(c.getPackage()); aoqi@0: } aoqi@0: aoqi@0: aoqi@0: aoqi@0: if(rt==Void.TYPE) { aoqi@0: // leaf element with just a value aoqi@0: aoqi@0: boolean isCDATA = method.getAnnotation(XmlCDATA.class)!=null; aoqi@0: aoqi@0: StartTag st = new StartTag(document,nsUri,localName); aoqi@0: addChild(st); aoqi@0: for( Object arg : args ) { aoqi@0: Text text; aoqi@0: if(isCDATA) text = new Cdata(document,st,arg); aoqi@0: else text = new Pcdata(document,st,arg); aoqi@0: addChild(text); aoqi@0: } aoqi@0: addChild(new EndTag()); aoqi@0: return null; aoqi@0: } aoqi@0: if(TypedXmlWriter.class.isAssignableFrom(rt)) { aoqi@0: // sub writer aoqi@0: return _element(nsUri,localName,(Class)rt); aoqi@0: } aoqi@0: aoqi@0: throw new IllegalSignatureException("Illegal return type: "+rt); aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Decides the namespace URI of the given package. aoqi@0: */ aoqi@0: private String getNamespace(Package pkg) { aoqi@0: if(pkg==null) return ""; aoqi@0: aoqi@0: String nsUri; aoqi@0: XmlNamespace ns = pkg.getAnnotation(XmlNamespace.class); aoqi@0: if(ns!=null) aoqi@0: nsUri = ns.value(); aoqi@0: else aoqi@0: nsUri = ""; aoqi@0: return nsUri; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Appends this child object to the tail. aoqi@0: */ aoqi@0: private void addChild(Content child) { aoqi@0: tail.setNext(document,child); aoqi@0: tail = child; aoqi@0: } aoqi@0: aoqi@0: public void commit() { aoqi@0: commit(true); aoqi@0: } aoqi@0: aoqi@0: public void commit(boolean includingAllPredecessors) { aoqi@0: _commit(includingAllPredecessors); aoqi@0: document.flush(); aoqi@0: } aoqi@0: aoqi@0: private void _commit(boolean includingAllPredecessors) { aoqi@0: if(isCommitted()) return; aoqi@0: aoqi@0: addChild(endTag); aoqi@0: if(isRoot()) aoqi@0: addChild(new EndDocument()); aoqi@0: tail = null; aoqi@0: aoqi@0: // _commit predecessors if so told aoqi@0: if(includingAllPredecessors) { aoqi@0: for( ContainerElement e=this; e!=null; e=e.parent ) { aoqi@0: while(e.prevOpen!=null) { aoqi@0: e.prevOpen._commit(false); aoqi@0: // e.prevOpen should change as a result of committing it. aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // _commit all children recursively aoqi@0: while(lastOpenChild!=null) aoqi@0: lastOpenChild._commit(false); aoqi@0: aoqi@0: // remove this node from the link aoqi@0: if(parent!=null) { aoqi@0: if(parent.lastOpenChild==this) { aoqi@0: assert nextOpen==null : "this must be the last one"; aoqi@0: parent.lastOpenChild = prevOpen; aoqi@0: } else { aoqi@0: assert nextOpen.prevOpen==this; aoqi@0: nextOpen.prevOpen = this.prevOpen; aoqi@0: } aoqi@0: if(prevOpen!=null) { aoqi@0: assert prevOpen.nextOpen==this; aoqi@0: prevOpen.nextOpen = this.nextOpen; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: this.nextOpen = null; aoqi@0: this.prevOpen = null; aoqi@0: } aoqi@0: aoqi@0: public void _attribute(String localName, Object value) { aoqi@0: _attribute("",localName,value); aoqi@0: } aoqi@0: aoqi@0: public void _attribute(String nsUri, String localName, Object value) { aoqi@0: checkStartTag(); aoqi@0: startTag.addAttribute(nsUri,localName,value); aoqi@0: } aoqi@0: aoqi@0: public void _attribute(QName attributeName, Object value) { aoqi@0: _attribute(attributeName.getNamespaceURI(),attributeName.getLocalPart(),value); aoqi@0: } aoqi@0: aoqi@0: public void _namespace(String uri) { aoqi@0: _namespace(uri,false); aoqi@0: } aoqi@0: aoqi@0: public void _namespace(String uri, String prefix) { aoqi@0: if(prefix==null) aoqi@0: throw new IllegalArgumentException(); aoqi@0: checkStartTag(); aoqi@0: startTag.addNamespaceDecl(uri,prefix,false); aoqi@0: } aoqi@0: aoqi@0: public void _namespace(String uri, boolean requirePrefix) { aoqi@0: checkStartTag(); aoqi@0: startTag.addNamespaceDecl(uri,null,requirePrefix); aoqi@0: } aoqi@0: aoqi@0: public void _pcdata(Object value) { aoqi@0: // we need to allow this method even when startTag has already been completed. aoqi@0: // checkStartTag(); aoqi@0: addChild(new Pcdata(document,startTag,value)); aoqi@0: } aoqi@0: aoqi@0: public void _cdata(Object value) { aoqi@0: addChild(new Cdata(document,startTag,value)); aoqi@0: } aoqi@0: aoqi@0: public void _comment(Object value) throws UnsupportedOperationException { aoqi@0: addChild(new Comment(document,startTag,value)); aoqi@0: } aoqi@0: aoqi@0: public T _element(String localName, Class contentModel) { aoqi@0: return _element(nsUri,localName,contentModel); aoqi@0: } aoqi@0: aoqi@0: public T _element(QName tagName, Class contentModel) { aoqi@0: return _element(tagName.getNamespaceURI(),tagName.getLocalPart(),contentModel); aoqi@0: } aoqi@0: aoqi@0: public T _element(Class contentModel) { aoqi@0: return _element(TXW.getTagName(contentModel),contentModel); aoqi@0: } aoqi@0: aoqi@0: public T _cast(Class facadeType) { aoqi@0: return facadeType.cast(Proxy.newProxyInstance(facadeType.getClassLoader(),new Class[]{facadeType},this)); aoqi@0: } aoqi@0: aoqi@0: public T _element(String nsUri, String localName, Class contentModel) { aoqi@0: ContainerElement child = new ContainerElement(document,this,nsUri,localName); aoqi@0: addChild(child.startTag); aoqi@0: tail = child.endTag; aoqi@0: aoqi@0: // update uncommitted link list aoqi@0: if(lastOpenChild!=null) { aoqi@0: assert lastOpenChild.parent==this; aoqi@0: aoqi@0: assert child.prevOpen==null; aoqi@0: assert child.nextOpen==null; aoqi@0: child.prevOpen = lastOpenChild; aoqi@0: assert lastOpenChild.nextOpen==null; aoqi@0: lastOpenChild.nextOpen = child; aoqi@0: } aoqi@0: aoqi@0: this.lastOpenChild = child; aoqi@0: aoqi@0: return child._cast(contentModel); aoqi@0: } aoqi@0: }