001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * https://jwsdp.dev.java.net/CDDLv1.0.html
009: * See the License for the specific language governing
010: * permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL
013: * HEADER in each file and include the License file at
014: * https://jwsdp.dev.java.net/CDDLv1.0.html If applicable,
015: * add the following below this CDDL HEADER, with the
016: * fields enclosed by brackets "[]" replaced with your
017: * own identifying information: Portions Copyright [yyyy]
018: * [name of copyright owner]
019: */
020:
021: package com.sun.xml.txw2;
022:
023: import com.sun.xml.txw2.annotation.XmlAttribute;
024: import com.sun.xml.txw2.annotation.XmlElement;
025: import com.sun.xml.txw2.annotation.XmlNamespace;
026: import com.sun.xml.txw2.annotation.XmlValue;
027: import com.sun.xml.txw2.annotation.XmlCDATA;
028:
029: import javax.xml.namespace.QName;
030: import java.lang.reflect.InvocationHandler;
031: import java.lang.reflect.InvocationTargetException;
032: import java.lang.reflect.Method;
033: import java.lang.reflect.Proxy;
034:
035: /**
036: * Dynamically implements {@link TypedXmlWriter} interfaces.
037: *
038: * @author Kohsuke Kawaguchi
039: */
040: final class ContainerElement implements InvocationHandler,
041: TypedXmlWriter {
042:
043: final Document document;
044:
045: /**
046: * Initially, point to the start tag token, but
047: * once we know we are done with the start tag, we will reset it to null
048: * so that the token sequence can be GC-ed.
049: */
050: StartTag startTag;
051: final EndTag endTag = new EndTag();
052:
053: /**
054: * Namespace URI of this element.
055: */
056: private final String nsUri;
057:
058: /**
059: * When this element can accept more child content, this value
060: * is non-null and holds the last child {@link Content}.
061: *
062: * If this element is committed, this parameter is null.
063: */
064: private Content tail;
065:
066: /**
067: * Uncommitted {@link ContainerElement}s form a doubly-linked list,
068: * so that the parent can close them recursively.
069: */
070: private ContainerElement prevOpen;
071: private ContainerElement nextOpen;
072: private final ContainerElement parent;
073: private ContainerElement lastOpenChild;
074:
075: /**
076: * Set to true if the start eleent is blocked.
077: */
078: private boolean blocked;
079:
080: public ContainerElement(Document document, ContainerElement parent,
081: String nsUri, String localName) {
082: this .parent = parent;
083: this .document = document;
084: this .nsUri = nsUri;
085: this .startTag = new StartTag(this , nsUri, localName);
086: tail = startTag;
087:
088: if (isRoot())
089: document.setFirstContent(startTag);
090: }
091:
092: private boolean isRoot() {
093: return parent == null;
094: }
095:
096: private boolean isCommitted() {
097: return tail == null;
098: }
099:
100: public Document getDocument() {
101: return document;
102: }
103:
104: boolean isBlocked() {
105: return blocked && !isCommitted();
106: }
107:
108: public void block() {
109: blocked = true;
110: }
111:
112: public Object invoke(Object proxy, Method method, Object[] args)
113: throws Throwable {
114: if (method.getDeclaringClass() == TypedXmlWriter.class
115: || method.getDeclaringClass() == Object.class) {
116: // forward to myself
117: try {
118: return method.invoke(this , args);
119: } catch (InvocationTargetException e) {
120: throw e.getTargetException();
121: }
122: }
123:
124: XmlAttribute xa = method.getAnnotation(XmlAttribute.class);
125: XmlValue xv = method.getAnnotation(XmlValue.class);
126: XmlElement xe = method.getAnnotation(XmlElement.class);
127:
128: if (xa != null) {
129: if (xv != null || xe != null)
130: throw new IllegalAnnotationException(method.toString());
131:
132: addAttribute(xa, method, args);
133: return proxy; // allow method chaining
134: }
135: if (xv != null) {
136: if (xe != null)
137: throw new IllegalAnnotationException(method.toString());
138:
139: _pcdata(args);
140: return proxy; // allow method chaining
141: }
142:
143: return addElement(xe, method, args);
144: }
145:
146: /**
147: * Writes an attribute.
148: */
149: private void addAttribute(XmlAttribute xa, Method method,
150: Object[] args) {
151: assert xa != null;
152:
153: checkStartTag();
154:
155: String localName = xa.value();
156: if (xa.value().length() == 0)
157: localName = method.getName();
158:
159: _attribute(xa.ns(), localName, args);
160: }
161:
162: private void checkStartTag() {
163: if (startTag == null)
164: throw new IllegalStateException(
165: "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: if (rt == Void.TYPE) {
199: // leaf element with just a value
200:
201: boolean isCDATA = method.getAnnotation(XmlCDATA.class) != null;
202:
203: StartTag st = new StartTag(document, nsUri, localName);
204: addChild(st);
205: for (Object arg : args) {
206: Text text;
207: if (isCDATA)
208: text = new Cdata(document, st, arg);
209: else
210: 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: "
222: + rt);
223: }
224:
225: /**
226: * Decides the namespace URI of the given package.
227: */
228: private String getNamespace(Package pkg) {
229: if (pkg == null)
230: return "";
231:
232: String nsUri;
233: XmlNamespace ns = pkg.getAnnotation(XmlNamespace.class);
234: if (ns != null)
235: nsUri = ns.value();
236: else
237: nsUri = "";
238: return nsUri;
239: }
240:
241: /**
242: * Appends this child object to the tail.
243: */
244: private void addChild(Content child) {
245: tail.setNext(document, child);
246: tail = child;
247: }
248:
249: public void commit() {
250: commit(true);
251: }
252:
253: public void commit(boolean includingAllPredecessors) {
254: _commit(includingAllPredecessors);
255: document.flush();
256: }
257:
258: private void _commit(boolean includingAllPredecessors) {
259: if (isCommitted())
260: return;
261:
262: addChild(endTag);
263: if (isRoot())
264: addChild(new EndDocument());
265: tail = null;
266:
267: // _commit predecessors if so told
268: if (includingAllPredecessors) {
269: for (ContainerElement e = this ; e != null; e = e.parent) {
270: while (e.prevOpen != null) {
271: e.prevOpen._commit(false);
272: // e.prevOpen should change as a result of committing it.
273: }
274: }
275: }
276:
277: // _commit all children recursively
278: while (lastOpenChild != null)
279: lastOpenChild._commit(false);
280:
281: // remove this node from the link
282: if (parent != null) {
283: if (parent.lastOpenChild == this ) {
284: assert nextOpen == null : "this must be the last one";
285: parent.lastOpenChild = prevOpen;
286: } else {
287: assert nextOpen.prevOpen == this ;
288: nextOpen.prevOpen = this .prevOpen;
289: }
290: if (prevOpen != null) {
291: assert prevOpen.nextOpen == this ;
292: prevOpen.nextOpen = this .nextOpen;
293: }
294: }
295:
296: this .nextOpen = null;
297: this .prevOpen = null;
298: }
299:
300: public void _attribute(String localName, Object value) {
301: _attribute("", localName, value);
302: }
303:
304: public void _attribute(String nsUri, String localName, Object value) {
305: checkStartTag();
306: startTag.addAttribute(nsUri, localName, value);
307: }
308:
309: public void _attribute(QName attributeName, Object value) {
310: _attribute(attributeName.getNamespaceURI(), attributeName
311: .getLocalPart(), value);
312: }
313:
314: public void _namespace(String uri) {
315: _namespace(uri, false);
316: }
317:
318: public void _namespace(String uri, String prefix) {
319: if (prefix == null)
320: throw new IllegalArgumentException();
321: checkStartTag();
322: startTag.addNamespaceDecl(uri, prefix, false);
323: }
324:
325: public void _namespace(String uri, boolean requirePrefix) {
326: checkStartTag();
327: startTag.addNamespaceDecl(uri, null, requirePrefix);
328: }
329:
330: public void _pcdata(Object value) {
331: // we need to allow this method even when startTag has already been completed.
332: // checkStartTag();
333: addChild(new Pcdata(document, startTag, value));
334: }
335:
336: public void _cdata(Object value) {
337: addChild(new Cdata(document, startTag, value));
338: }
339:
340: public void _comment(Object value)
341: throws UnsupportedOperationException {
342: addChild(new Comment(document, startTag, value));
343: }
344:
345: public <T extends TypedXmlWriter> T _element(String localName,
346: Class<T> contentModel) {
347: return _element(nsUri, localName, contentModel);
348: }
349:
350: public <T extends TypedXmlWriter> T _element(QName tagName,
351: Class<T> contentModel) {
352: return _element(tagName.getNamespaceURI(), tagName
353: .getLocalPart(), contentModel);
354: }
355:
356: public <T extends TypedXmlWriter> T _element(Class<T> contentModel) {
357: return _element(TXW.getTagName(contentModel), contentModel);
358: }
359:
360: public <T extends TypedXmlWriter> T _cast(Class<T> facadeType) {
361: return facadeType.cast(Proxy.newProxyInstance(facadeType
362: .getClassLoader(), new Class[] { facadeType }, this ));
363: }
364:
365: public <T extends TypedXmlWriter> T _element(String nsUri,
366: String localName, Class<T> contentModel) {
367: ContainerElement child = new ContainerElement(document, this ,
368: nsUri, localName);
369: addChild(child.startTag);
370: tail = child.endTag;
371:
372: // update uncommitted link list
373: if (lastOpenChild != null) {
374: assert lastOpenChild.parent == this;
375:
376: assert child.prevOpen == null;
377: assert child.nextOpen == null;
378: child.prevOpen = lastOpenChild;
379: assert lastOpenChild.nextOpen == null;
380: lastOpenChild.nextOpen = child;
381: }
382:
383: this.lastOpenChild = child;
384:
385: return child._cast(contentModel);
386: }
387: }
|