001    // Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.MarkupWriter;
018    import org.apache.tapestry5.MarkupWriterListener;
019    import org.apache.tapestry5.dom.*;
020    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
021    import org.apache.tapestry5.ioc.internal.util.Defense;
022    
023    import java.io.PrintWriter;
024    import java.util.Collection;
025    import java.util.List;
026    
027    public class MarkupWriterImpl implements MarkupWriter
028    {
029        private final Document document;
030    
031        private Element current;
032    
033        private Text currentText;
034    
035        private List<MarkupWriterListener> listeners;
036    
037        /**
038         * Creates a new instance of the MarkupWriter with a {@link org.apache.tapestry5.dom.DefaultMarkupModel}.
039         */
040        public MarkupWriterImpl()
041        {
042            this(new DefaultMarkupModel());
043        }
044    
045        public MarkupWriterImpl(MarkupModel model)
046        {
047            this(model, null);
048        }
049    
050        public MarkupWriterImpl(MarkupModel model, String encoding)
051        {
052            document = new Document(model, encoding);
053        }
054    
055        public void toMarkup(PrintWriter writer)
056        {
057            document.toMarkup(writer);
058        }
059    
060        @Override
061        public String toString()
062        {
063            return document.toString();
064        }
065    
066        public Document getDocument()
067        {
068            return document;
069        }
070    
071        public Element getElement()
072        {
073            return current;
074        }
075    
076        public void cdata(String content)
077        {
078            currentText = null;
079    
080            if (current == null)
081            {
082                document.cdata(content);
083            }
084            else
085            {
086                current.cdata(content);
087            }
088        }
089    
090        public void write(String text)
091        {
092            if (text == null) return;
093    
094            if (currentText == null)
095            {
096                currentText =
097                        current == null
098                        ? document.text(text)
099                        : current.text(text);
100    
101                return;
102            }
103    
104            currentText.write(text);
105        }
106    
107        public void writef(String format, Object... args)
108        {
109            // A bit of a cheat:
110    
111            write("");
112            currentText.writef(format, args);
113        }
114    
115        public void attributes(Object... namesAndValues)
116        {
117            ensureCurrentElement();
118    
119            int i = 0;
120    
121            while (i < namesAndValues.length)
122            {
123                // name should never be null.
124    
125                String name = namesAndValues[i++].toString();
126                Object value = namesAndValues[i++];
127    
128                if (value == null) continue;
129    
130                current.attribute(name, value.toString());
131            }
132        }
133    
134        private void ensureCurrentElement()
135        {
136            if (current == null)
137                throw new IllegalStateException(ServicesMessages.markupWriterNoCurrentElement());
138        }
139    
140        public Element element(String name, Object... namesAndValues)
141        {
142            if (current == null)
143            {
144                Element existingRootElement = document.getRootElement();
145    
146                if (existingRootElement != null)
147                    throw new IllegalStateException(String.format(
148                            "A document must have exactly one root element. Element <%s> is already the root element.",
149                            existingRootElement.getName()));
150    
151                current = document.newRootElement(name);
152            }
153            else
154            {
155                current = current.element(name);
156            }
157    
158            attributes(namesAndValues);
159    
160            currentText = null;
161    
162            fireElementDidStart();
163    
164            return current;
165        }
166    
167        public void writeRaw(String text)
168        {
169            currentText = null;
170    
171            if (current == null)
172            {
173                document.raw(text);
174            }
175            else
176            {
177                current.raw(text);
178            }
179        }
180    
181        public Element end()
182        {
183            ensureCurrentElement();
184    
185            fireElementDidEnd();
186    
187            current = current.getParent();
188    
189            currentText = null;
190    
191            return current;
192        }
193    
194        public void comment(String text)
195        {
196            currentText = null;
197    
198            if (current == null)
199            {
200                document.comment(text);
201            }
202            else
203            {
204                current.comment(text);
205            }
206        }
207    
208        public Element attributeNS(String namespace, String attributeName, String attributeValue)
209        {
210            ensureCurrentElement();
211    
212            current.attribute(namespace, attributeName, attributeValue);
213    
214            return current;
215        }
216    
217        public Element defineNamespace(String namespace, String namespacePrefix)
218        {
219            ensureCurrentElement();
220    
221            current.defineNamespace(namespace, namespacePrefix);
222    
223            return current;
224        }
225    
226        public Element elementNS(String namespace, String elementName)
227        {
228            if (current == null) current = document.newRootElement(namespace, elementName);
229            else current = current.elementNS(namespace, elementName);
230    
231            currentText = null;
232    
233            fireElementDidStart();
234    
235            return current;
236        }
237    
238        public void addListener(MarkupWriterListener listener)
239        {
240            Defense.notNull(listener, "listener");
241    
242            if (listeners == null) listeners = CollectionFactory.newList();
243    
244            listeners.add(listener);
245        }
246    
247        public void removeListener(MarkupWriterListener listener)
248        {
249            if (listeners != null)
250                listeners.remove(listener);
251        }
252    
253        private void fireElementDidStart()
254        {
255            if (isEmpty(listeners)) return;
256    
257            for (MarkupWriterListener l : CollectionFactory.newList(listeners))
258            {
259                l.elementDidStart(current);
260            }
261        }
262    
263        private static boolean isEmpty(Collection<?> collection)
264        {
265            return collection == null || collection.isEmpty();
266        }
267    
268        private void fireElementDidEnd()
269        {
270            if (isEmpty(listeners)) return;
271    
272            for (MarkupWriterListener l : CollectionFactory.newList(listeners))
273            {
274                l.elementDidEnd(current);
275            }
276        }
277    }
278