    001    // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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
    021    import java.io.PrintWriter;
    022    import java.util.Collection;
    023    import java.util.List;
    024    import java.util.concurrent.CopyOnWriteArrayList;
    025
    026    public class MarkupWriterImpl implements MarkupWriter
    027    {
    028        private final Document document;
    029
    030        private Element current;
    031
    032        private Text currentText;
    033
    034        private List<MarkupWriterListener> listeners;
    035
    036        /**
    037         * Creates a new instance of the MarkupWriter with a {@link org.apache.tapestry5.dom.DefaultMarkupModel}.
    038         */
    039        public MarkupWriterImpl()
    040        {
    041            this(new DefaultMarkupModel());
    042        }
    043
    044        public MarkupWriterImpl(MarkupModel model)
    045        {
    046            this(model, null);
    047        }
    048
    049        public MarkupWriterImpl(MarkupModel model, String encoding)
    050        {
    051            document = new Document(model, encoding);
    052        }
    053
    054        public void toMarkup(PrintWriter writer)
    055        {
    056            document.toMarkup(writer);
    057        }
    058
    059        @Override
    060        public String toString()
    061        {
    062            return document.toString();
    063        }
    064
    065        public Document getDocument()
    066        {
    067            return document;
    068        }
    069
    070        public Element getElement()
    071        {
    072            return current;
    073        }
    074
    075        public void cdata(String content)
    076        {
    077            currentText = null;
    078
    079            if (current == null)
    080            {
    081                document.cdata(content);
    082            } else
    083            {
    084                current.cdata(content);
    085            }
    086        }
    087
    088        public void write(String text)
    089        {
    090            if (text == null) return;
    091
    092            if (currentText == null)
    093            {
    094                currentText =
    095                        current == null
    096                                ? document.text(text)
    097                                : current.text(text);
    098
    099                return;
    100            }
    101
    102            currentText.write(text);
    103        }
    104
    105        public void writef(String format, Object... args)
    106        {
    107            // A bit of a cheat:
    108
    109            write("");
    110            currentText.writef(format, args);
    111        }
    112
    113        public void attributes(Object... namesAndValues)
    114        {
    115            ensureCurrentElement();
    116
    117            int i = 0;
    118
    119            int length = namesAndValues.length;
    120
    121            if (length % 2 != 0)
    122                throw new IllegalArgumentException(ServicesMessages.markupWriterAttributeNameOrValueOmitted(current.getName(), namesAndValues));
    123
    124            while (i < length)
    125            {
    126                // name should never be null.
    127
    128                String name = namesAndValues[i++].toString();
    129                Object value = namesAndValues[i++];
    130
    131                if (value == null) continue;
    132
    133                current.attribute(name, value.toString());
    134            }
    135        }
    136
    137        private void ensureCurrentElement()
    138        {
    139            if (current == null)
    140                throw new IllegalStateException(ServicesMessages.markupWriterNoCurrentElement());
    141        }
    142
    143        public Element element(String name, Object... namesAndValues)
    144        {
    145            if (current == null)
    146            {
    147                Element existingRootElement = document.getRootElement();
    148
    149                if (existingRootElement != null)
    150                    throw new IllegalStateException(String.format(
    151                            "A document must have exactly one root element. Element <%s> is already the root element.",
    152                            existingRootElement.getName()));
    153
    154                current = document.newRootElement(name);
    155            } else
    156            {
    157                current = current.element(name);
    158            }
    159
    160            attributes(namesAndValues);
    161
    162            currentText = null;
    163
    164            fireElementDidStart();
    165
    166            return current;
    167        }
    168
    169        public void writeRaw(String text)
    170        {
    171            currentText = null;
    172
    173            if (current == null)
    174            {
    175                document.raw(text);
    176            } else
    177            {
    178                current.raw(text);
    179            }
    180        }
    181
    182        public Element end()
    183        {
    184            ensureCurrentElement();
    185
    186            fireElementDidEnd();
    187
    188            current = current.getContainer();
    189
    190            currentText = null;
    191
    192            return current;
    193        }
    194
    195        public void comment(String text)
    196        {
    197            currentText = null;
    198
    199            if (current == null)
    200            {
    201                document.comment(text);
    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            assert listener != null;
    241
    242            if (listeners == null)
    243            {
    244                // TAP5-XXX: Using a copy-on-write list means we don't have to make defensive copies
    245                // while iterating the listeners (to protect against listeners that add or remove listeners).
    246                listeners = new CopyOnWriteArrayList<MarkupWriterListener>();
    247            }
    248
    249            listeners.add(listener);
    250        }
    251
    252        public void removeListener(MarkupWriterListener listener)
    253        {
    254            if (listeners != null)
    255                listeners.remove(listener);
    256        }
    257
    258        private void fireElementDidStart()
    259        {
    260            if (isEmpty(listeners)) return;
    261
    262            for (MarkupWriterListener l : listeners)
    263            {
    264                l.elementDidStart(current);
    265            }
    266        }
    267
    268        private static boolean isEmpty(Collection<?> collection)
    269        {
    270            return collection == null || collection.isEmpty();
    271        }
    272
    273        private void fireElementDidEnd()
    274        {
    275            if (isEmpty(listeners)) return;
    276
    277            for (MarkupWriterListener l : listeners)
    278            {
    279                l.elementDidEnd(current);
    280            }
    281        }
    282    }
    283