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