001 // Copyright 2005 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.tapestry.markup; 016 017 import java.io.PrintWriter; 018 import java.util.ArrayList; 019 import java.util.List; 020 021 import org.apache.hivemind.ApplicationRuntimeException; 022 import org.apache.hivemind.util.Defense; 023 import org.apache.tapestry.IMarkupWriter; 024 import org.apache.tapestry.NestedMarkupWriter; 025 026 /** 027 * Completely revised (for 4.0) implementation of {@link org.apache.tapestry.IMarkupWriter}. No 028 * longer does internal buffering (since the servlet/portlet APIs support that natively) and wraps 029 * around a {@link java.io.PrintWriter} (rather than an {@link java.io.OutputStream}). 030 * 031 * @author Howard M. Lewis Ship 032 * @since 4.0 033 */ 034 public class MarkupWriterImpl implements IMarkupWriter 035 { 036 /** 037 * The underlying {@link PrintWriter}that output is sent to. 038 */ 039 040 private PrintWriter _writer; 041 042 /** 043 * Filter used to "escape" characters that need any kind of special encoding for the output 044 * content type. 045 */ 046 047 private MarkupFilter _filter; 048 049 /** 050 * Indicates whether a tag is open or not. A tag is opened by {@link #begin(String)}or 051 * {@link #beginEmpty(String)}. It stays open while calls to the <code>attribute()</code> 052 * methods are made. It is closed (the '>' is written) when any other method is invoked. 053 */ 054 055 private boolean _openTag = false; 056 057 /** 058 * Indicates that the tag was opened with {@link #beginEmpty(String)}, which affects how the 059 * tag is closed (a slash is added to indicate the lack of a body). This is compatible with 060 * HTML, but reflects an XML/XHTML leaning. 061 */ 062 063 private boolean _emptyTag = false; 064 065 private String _contentType; 066 067 /** 068 * A Stack of Strings used to track the active tag elements. Elements are active until the 069 * corresponding close tag is written. The {@link #push(String)}method adds elements to the 070 * stack, {@link #pop()}removes them. 071 */ 072 073 private List _activeElementStack; 074 075 public MarkupWriterImpl(String contentType, PrintWriter writer, MarkupFilter filter) 076 { 077 Defense.notNull(contentType, "contentType"); 078 Defense.notNull(writer, "writer"); 079 Defense.notNull(filter, "filter"); 080 081 _contentType = contentType; 082 _writer = writer; 083 _filter = filter; 084 } 085 086 public void attribute(String name, int value) 087 { 088 checkTagOpen(); 089 090 _writer.print(' '); 091 _writer.print(name); 092 _writer.print("=\""); 093 _writer.print(value); 094 _writer.print('"'); 095 } 096 097 public void attribute(String name, boolean value) 098 { 099 checkTagOpen(); 100 101 _writer.print(' '); 102 _writer.print(name); 103 _writer.print("=\""); 104 _writer.print(value); 105 _writer.print('"'); 106 } 107 108 public void attribute(String name, String value) 109 { 110 attribute(name, value, false); 111 } 112 113 public void attribute(String name, String value, boolean raw) 114 { 115 checkTagOpen(); 116 117 _writer.print(' '); 118 119 // Could use a check here that name contains only valid characters 120 121 _writer.print(name); 122 _writer.print("=\""); 123 124 if (value != null) 125 { 126 char[] data = value.toCharArray(); 127 maybePrintFiltered(data, 0, data.length, raw, true); 128 } 129 130 _writer.print('"'); 131 } 132 133 /** 134 * Prints the value, if non-null. May pass it through the filter, unless raw is true. 135 */ 136 137 private void maybePrintFiltered(char[] data, int offset, int length, boolean raw, 138 boolean isAttribute) 139 { 140 if (data == null || length <= 0) 141 return; 142 143 if (raw) 144 { 145 _writer.write(data, offset, length); 146 return; 147 } 148 149 _filter.print(_writer, data, offset, length, isAttribute); 150 } 151 152 public void attributeRaw(String name, String value) 153 { 154 attribute(name, value, true); 155 } 156 157 public void begin(String name) 158 { 159 if (_openTag) 160 closeTag(); 161 162 push(name); 163 164 _writer.print('<'); 165 _writer.print(name); 166 167 _openTag = true; 168 _emptyTag = false; 169 } 170 171 public void beginEmpty(String name) 172 { 173 if (_openTag) 174 closeTag(); 175 176 _writer.print('<'); 177 _writer.print(name); 178 179 _openTag = true; 180 _emptyTag = true; 181 } 182 183 public boolean checkError() 184 { 185 return _writer.checkError(); 186 } 187 188 public void close() 189 { 190 if (_openTag) 191 closeTag(); 192 193 // Close any active elements. 194 195 while (!stackEmpty()) 196 { 197 _writer.print("</"); 198 _writer.print(pop()); 199 _writer.print('>'); 200 } 201 202 _writer.close(); 203 204 _writer = null; 205 _filter = null; 206 _activeElementStack = null; 207 } 208 209 public void closeTag() 210 { 211 if (_emptyTag) 212 _writer.print('/'); 213 214 _writer.print('>'); 215 216 _openTag = false; 217 _emptyTag = false; 218 } 219 220 public void comment(String value) 221 { 222 if (_openTag) 223 closeTag(); 224 225 _writer.print("<!-- "); 226 _writer.print(value); 227 _writer.println(" -->"); 228 } 229 230 public void end() 231 { 232 if (_openTag) 233 closeTag(); 234 235 if (stackEmpty()) 236 throw new ApplicationRuntimeException(MarkupMessages.endWithEmptyStack()); 237 238 _writer.print("</"); 239 _writer.print(pop()); 240 _writer.print('>'); 241 } 242 243 public void end(String name) 244 { 245 if (_openTag) 246 closeTag(); 247 248 if (_activeElementStack == null || !_activeElementStack.contains(name)) 249 throw new ApplicationRuntimeException(MarkupMessages.elementNotOnStack( 250 name, 251 _activeElementStack)); 252 253 while (true) 254 { 255 String tagName = pop(); 256 257 _writer.print("</"); 258 _writer.print(tagName); 259 _writer.print('>'); 260 261 if (tagName.equals(name)) 262 break; 263 } 264 } 265 266 public void flush() 267 { 268 _writer.flush(); 269 } 270 271 public NestedMarkupWriter getNestedWriter() 272 { 273 return new NestedMarkupWriterImpl(this, _filter); 274 } 275 276 public void print(char[] data, int offset, int length) 277 { 278 print(data, offset, length, false); 279 } 280 281 public void printRaw(char[] buffer, int offset, int length) 282 { 283 print(buffer, offset, length, true); 284 } 285 286 public void print(char[] buffer, int offset, int length, boolean raw) 287 { 288 if (_openTag) 289 closeTag(); 290 291 maybePrintFiltered(buffer, offset, length, raw, false); 292 } 293 294 public void print(String value) 295 { 296 print(value, false); 297 } 298 299 public void printRaw(String value) 300 { 301 print(value, true); 302 } 303 304 public void print(String value, boolean raw) 305 { 306 if (value == null || value.length() == 0) 307 { 308 print(null, 0, 0, raw); 309 return; 310 } 311 312 char[] buffer = value.toCharArray(); 313 314 print(buffer, 0, buffer.length, raw); 315 } 316 317 public void print(char value) 318 { 319 char[] data = new char[] 320 { value }; 321 322 print(data, 0, 1); 323 } 324 325 public void print(int value) 326 { 327 if (_openTag) 328 closeTag(); 329 330 _writer.print(value); 331 } 332 333 public void println() 334 { 335 if (_openTag) 336 closeTag(); 337 338 _writer.println(); 339 } 340 341 public String getContentType() 342 { 343 return _contentType; 344 } 345 346 private void checkTagOpen() 347 { 348 if (!_openTag) 349 throw new IllegalStateException(MarkupMessages.tagNotOpen()); 350 } 351 352 private void push(String name) 353 { 354 if (_activeElementStack == null) 355 _activeElementStack = new ArrayList(); 356 357 _activeElementStack.add(name); 358 } 359 360 private String pop() 361 { 362 int lastIndex = _activeElementStack.size() - 1; 363 364 return (String) _activeElementStack.remove(lastIndex); 365 } 366 367 private boolean stackEmpty() 368 { 369 return _activeElementStack == null || _activeElementStack.isEmpty(); 370 } 371 }