001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025: package com.sun.xml.internal.txw2;
026:
027: import com.sun.xml.internal.txw2.annotation.XmlAttribute;
028: import com.sun.xml.internal.txw2.annotation.XmlElement;
029: import com.sun.xml.internal.txw2.annotation.XmlNamespace;
030: import com.sun.xml.internal.txw2.annotation.XmlValue;
031: import com.sun.xml.internal.txw2.annotation.XmlCDATA;
032:
033: import javax.xml.namespace.QName;
034: import java.lang.reflect.InvocationHandler;
035: import java.lang.reflect.InvocationTargetException;
036: import java.lang.reflect.Method;
037: import java.lang.reflect.Proxy;
038:
039: /**
040: * Dynamically implements {@link TypedXmlWriter} interfaces.
041: *
042: * @author Kohsuke Kawaguchi
043: */
044: final class ContainerElement implements InvocationHandler,
045: TypedXmlWriter {
046:
047: final Document document;
048:
049: /**
050: * Initially, point to the start tag token, but
051: * once we know we are done with the start tag, we will reset it to null
052: * so that the token sequence can be GC-ed.
053: */
054: StartTag startTag;
055: final EndTag endTag = new EndTag();
056:
057: /**
058: * Namespace URI of this element.
059: */
060: private final String nsUri;
061:
062: /**
063: * When this element can accept more child content, this value
064: * is non-null and holds the last child {@link Content}.
065: *
066: * If this element is committed, this parameter is null.
067: */
068: private Content tail;
069:
070: /**
071: * Uncommitted {@link ContainerElement}s form a doubly-linked list,
072: * so that the parent can close them recursively.
073: */
074: private ContainerElement prevOpen;
075: private ContainerElement nextOpen;
076: private final ContainerElement parent;
077: private ContainerElement lastOpenChild;
078:
079: /**
080: * Set to true if the start eleent is blocked.
081: */
082: private boolean blocked;
083:
084: public ContainerElement(Document document, ContainerElement parent,
085: String nsUri, String localName) {
086: this .parent = parent;
087: this .document = document;
088: this .nsUri = nsUri;
089: this .startTag = new StartTag(this , nsUri, localName);
090: tail = startTag;
091:
092: if (isRoot())
093: document.setFirstContent(startTag);
094: }
095:
096: private boolean isRoot() {
097: return parent == null;
098: }
099:
100: private boolean isCommitted() {
101: return tail == null;
102: }
103:
104: public Document getDocument() {
105: return document;
106: }
107:
108: boolean isBlocked() {
109: return blocked && !isCommitted();
110: }
111:
112: public void block() {
113: blocked = true;
114: }
115:
116: public Object invoke(Object proxy, Method method, Object[] args)
117: throws Throwable {
118: if (method.getDeclaringClass() == TypedXmlWriter.class
119: || method.getDeclaringClass() == Object.class) {
120: // forward to myself
121: try {
122: return method.invoke(this , args);
123: } catch (InvocationTargetException e) {
124: throw e.getTargetException();
125: }
126: }
127:
128: XmlAttribute xa = method.getAnnotation(XmlAttribute.class);
129: XmlValue xv = method.getAnnotation(XmlValue.class);
130: XmlElement xe = method.getAnnotation(XmlElement.class);
131:
132: if (xa != null) {
133: if (xv != null || xe != null)
134: throw new IllegalAnnotationException(method.toString());
135:
136: addAttribute(xa, method, args);
137: return proxy; // allow method chaining
138: }
139: if (xv != null) {
140: if (xe != null)
141: throw new IllegalAnnotationException(method.toString());
142:
143: _pcdata(args);
144: return proxy; // allow method chaining
145: }
146:
147: return addElement(xe, method, args);
148: }
149:
150: /**
151: * Writes an attribute.
152: */
153: private void addAttribute(XmlAttribute xa, Method method,
154: Object[] args) {
155: assert xa != null;
156:
157: checkStartTag();
158:
159: String localName = xa.value();
160: if (xa.value().length() == 0)
161: localName = method.getName();
162:
163: _attribute(xa.ns(), localName, args);
164: }
165:
166: private void checkStartTag() {
167: if (startTag == null)
168: throw new IllegalStateException(
169: "start tag has already been written");
170: }
171:
172: /**
173: * Writes a new element.
174: */
175: private Object addElement(XmlElement e, Method method, Object[] args) {
176: Class<?> rt = method.getReturnType();
177:
178: // the last precedence: default name
179: String nsUri = "##default";
180: String localName = method.getName();
181:
182: if (e != null) {
183: // then the annotation on this method
184: if (e.value().length() != 0)
185: localName = e.value();
186: nsUri = e.ns();
187: }
188:
189: if (nsUri.equals("##default")) {
190: // look for the annotation on the declaring class
191: Class<?> c = method.getDeclaringClass();
192: XmlElement ce = c.getAnnotation(XmlElement.class);
193: if (ce != null) {
194: nsUri = ce.ns();
195: }
196:
197: if (nsUri.equals("##default"))
198: // then default to the XmlNamespace
199: nsUri = getNamespace(c.getPackage());
200: }
201:
202: if (rt == Void.TYPE) {
203: // leaf element with just a value
204:
205: boolean isCDATA = method.getAnnotation(XmlCDATA.class) != null;
206:
207: StartTag st = new StartTag(document, nsUri, localName);
208: addChild(st);
209: for (Object arg : args) {
210: Text text;
211: if (isCDATA)
212: text = new Cdata(document, st, arg);
213: else
214: text = new Pcdata(document, st, arg);
215: addChild(text);
216: }
217: addChild(new EndTag());
218: return null;
219: }
220: if (TypedXmlWriter.class.isAssignableFrom(rt)) {
221: // sub writer
222: return _element(nsUri, localName, (Class) rt);
223: }
224:
225: throw new IllegalSignatureException("Illegal return type: "
226: + rt);
227: }
228:
229: /**
230: * Decides the namespace URI of the given package.
231: */
232: private String getNamespace(Package pkg) {
233: if (pkg == null)
234: return "";
235:
236: String nsUri;
237: XmlNamespace ns = pkg.getAnnotation(XmlNamespace.class);
238: if (ns != null)
239: nsUri = ns.value();
240: else
241: nsUri = "";
242: return nsUri;
243: }
244:
245: /**
246: * Appends this child object to the tail.
247: */
248: private void addChild(Content child) {
249: tail.setNext(document, child);
250: tail = child;
251: }
252:
253: public void commit() {
254: commit(true);
255: }
256:
257: public void commit(boolean includingAllPredecessors) {
258: _commit(includingAllPredecessors);
259: document.flush();
260: }
261:
262: private void _commit(boolean includingAllPredecessors) {
263: if (isCommitted())
264: return;
265:
266: addChild(endTag);
267: if (isRoot())
268: addChild(new EndDocument());
269: tail = null;
270:
271: // _commit predecessors if so told
272: if (includingAllPredecessors) {
273: for (ContainerElement e = this ; e != null; e = e.parent) {
274: while (e.prevOpen != null) {
275: e.prevOpen._commit(false);
276: // e.prevOpen should change as a result of committing it.
277: }
278: }
279: }
280:
281: // _commit all children recursively
282: while (lastOpenChild != null)
283: lastOpenChild._commit(false);
284:
285: // remove this node from the link
286: if (parent != null) {
287: if (parent.lastOpenChild == this ) {
288: assert nextOpen == null : "this must be the last one";
289: parent.lastOpenChild = prevOpen;
290: } else {
291: assert nextOpen.prevOpen == this ;
292: nextOpen.prevOpen = this .prevOpen;
293: }
294: if (prevOpen != null) {
295: assert prevOpen.nextOpen == this ;
296: prevOpen.nextOpen = this .nextOpen;
297: }
298: }
299:
300: this .nextOpen = null;
301: this .prevOpen = null;
302: }
303:
304: public void _attribute(String localName, Object value) {
305: _attribute("", localName, value);
306: }
307:
308: public void _attribute(String nsUri, String localName, Object value) {
309: checkStartTag();
310: startTag.addAttribute(nsUri, localName, value);
311: }
312:
313: public void _attribute(QName attributeName, Object value) {
314: _attribute(attributeName.getNamespaceURI(), attributeName
315: .getLocalPart(), value);
316: }
317:
318: public void _namespace(String uri) {
319: _namespace(uri, false);
320: }
321:
322: public void _namespace(String uri, String prefix) {
323: if (prefix == null)
324: throw new IllegalArgumentException();
325: checkStartTag();
326: startTag.addNamespaceDecl(uri, prefix, false);
327: }
328:
329: public void _namespace(String uri, boolean requirePrefix) {
330: checkStartTag();
331: startTag.addNamespaceDecl(uri, null, requirePrefix);
332: }
333:
334: public void _pcdata(Object value) {
335: // we need to allow this method even when startTag has already been completed.
336: // checkStartTag();
337: addChild(new Pcdata(document, startTag, value));
338: }
339:
340: public void _cdata(Object value) {
341: addChild(new Cdata(document, startTag, value));
342: }
343:
344: public void _comment(Object value)
345: throws UnsupportedOperationException {
346: addChild(new Comment(document, startTag, value));
347: }
348:
349: public <T extends TypedXmlWriter> T _element(String localName,
350: Class<T> contentModel) {
351: return _element(nsUri, localName, contentModel);
352: }
353:
354: public <T extends TypedXmlWriter> T _element(QName tagName,
355: Class<T> contentModel) {
356: return _element(tagName.getNamespaceURI(), tagName
357: .getLocalPart(), contentModel);
358: }
359:
360: public <T extends TypedXmlWriter> T _element(Class<T> contentModel) {
361: return _element(TXW.getTagName(contentModel), contentModel);
362: }
363:
364: public <T extends TypedXmlWriter> T _cast(Class<T> facadeType) {
365: return facadeType.cast(Proxy.newProxyInstance(facadeType
366: .getClassLoader(), new Class[] { facadeType }, this ));
367: }
368:
369: public <T extends TypedXmlWriter> T _element(String nsUri,
370: String localName, Class<T> contentModel) {
371: ContainerElement child = new ContainerElement(document, this ,
372: nsUri, localName);
373: addChild(child.startTag);
374: tail = child.endTag;
375:
376: // update uncommitted link list
377: if (lastOpenChild != null) {
378: assert lastOpenChild.parent == this;
379:
380: assert child.prevOpen == null;
381: assert child.nextOpen == null;
382: child.prevOpen = lastOpenChild;
383: assert lastOpenChild.nextOpen == null;
384: lastOpenChild.nextOpen = child;
385: }
386:
387: this.lastOpenChild = child;
388:
389: return child._cast(contentModel);
390: }
391: }
|