1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.webapp;
21
22 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
23 import org.apache.myfaces.tobago.renderkit.html.HtmlTypes;
24 import org.apache.myfaces.tobago.renderkit.html.MarkupLanguageAttributes;
25 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import javax.faces.component.UIComponent;
30 import java.io.IOException;
31 import java.io.Writer;
32 import java.lang.invoke.MethodHandles;
33 import java.net.URI;
34 import java.nio.charset.Charset;
35 import java.nio.charset.StandardCharsets;
36
37 public abstract class TobagoResponseWriterBase extends TobagoResponseWriter {
38
39 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
40
41 protected static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
42
43 protected static final char[] XML_VERSION_1_0_ENCODING_UTF_8_CHARS = XML_VERSION_1_0_ENCODING_UTF_8.toCharArray();
44
45 private int level = 0;
46
47 private int inlineStack = 0;
48
49 private UIComponent component;
50
51 private boolean startStillOpen;
52
53 private final Writer writer;
54
55 private final String contentType;
56
57 private final Charset charset;
58
59
60
61
62 @Deprecated
63 protected TobagoResponseWriterBase(final Writer writer, final String contentType, final String characterEncoding) {
64 this(writer, contentType, characterEncoding != null ? Charset.forName(characterEncoding) : StandardCharsets.UTF_8);
65 }
66
67 protected TobagoResponseWriterBase(final Writer writer, final String contentType, final Charset charset) {
68 this.writer = writer;
69 this.contentType = contentType;
70 this.charset = charset != null ? charset : StandardCharsets.UTF_8;
71 }
72
73 protected final Writer getWriter() {
74 return writer;
75 }
76
77 protected final UIComponent getComponent() {
78 return component;
79 }
80
81 protected final void setComponent(final UIComponent component) {
82 this.component = component;
83 }
84
85 protected final boolean isStartStillOpen() {
86 return startStillOpen;
87 }
88
89 protected final void setStartStillOpen(final boolean startStillOpen) {
90 this.startStillOpen = startStillOpen;
91 }
92
93 protected final String findValue(final Object value, final String property) {
94 if (value != null) {
95 return value instanceof String ? (String) value : value.toString();
96 } else if (property != null) {
97 if (component != null) {
98 final Object object = component.getAttributes().get(property);
99 if (object != null) {
100 return object instanceof String ? (String) object : object.toString();
101 } else {
102 return null;
103 }
104 } else {
105 final String trace = getCallingClassStackTraceElementString();
106 LOG.warn("Don't know what to do! "
107 + "Property defined, but no component to get a value. (value=null, property='" + property + "') "
108 + trace.substring(trace.indexOf('(')));
109 return null;
110 }
111 } else {
112 final String trace = getCallingClassStackTraceElementString();
113 LOG.warn("Don't know what to do! "
114 + "No value and no property defined. (value=null, property=null)"
115 + trace.substring(trace.indexOf('(')));
116 return null;
117 }
118 }
119
120 @Override
121 public void write(final char[] cbuf, final int off, final int len)
122 throws IOException {
123 writer.write(cbuf, off, len);
124 }
125
126 @Override
127 public void write(final String string) throws IOException {
128 writeInternal(writer, string);
129 }
130
131 protected final void writeInternal(final Writer sink, final String string) throws IOException {
132 closeOpenTag();
133 sink.write(string);
134 }
135
136 @Override
137 public void write(final int j) throws IOException {
138 closeOpenTag();
139 writer.write(j);
140 }
141
142 @Override
143 public void write(final char[] chars) throws IOException {
144 closeOpenTag();
145 writer.write(chars);
146 }
147
148 @Override
149 public void write(final String string, final int j, final int k) throws IOException {
150 closeOpenTag();
151 writer.write(string, j, k);
152 }
153
154 @Override
155 public void close() throws IOException {
156 closeOpenTag();
157 writer.close();
158 }
159
160 @Override
161 public void flush() throws IOException {
162
163
164
165
166
167
168 closeOpenTag();
169 }
170
171 protected void closeOpenTag() throws IOException {
172 if (startStillOpen) {
173 writer.write('>');
174 startStillOpen = false;
175 }
176 }
177
178 @Override
179 public void startDocument() throws IOException {
180
181 }
182
183 @Override
184 public void endDocument() throws IOException {
185
186 }
187
188 @Override
189 public String getContentType() {
190 return contentType;
191 }
192
193 @Override
194 public String getCharacterEncoding() {
195 return charset.name();
196 }
197
198 @Override
199 public void startElement(final String name, final UIComponent currentComponent) throws IOException {
200 final boolean inline = HtmlElements.isInline(name);
201 if (inline) {
202 inlineStack++;
203 }
204 this.component = currentComponent;
205 startElementInternal(writer, name, HtmlElements.isInline(name));
206 }
207
208 @Override
209 public void startElement(final HtmlElements name) throws IOException {
210 final boolean inline = name.isInline();
211 if (inline) {
212 inlineStack++;
213 }
214 startElementInternal(writer, name.getValue(), name.isInline());
215 if (!name.isVoid()) {
216 level++;
217 }
218 }
219
220 protected void startElementInternal(final Writer sink, final String name, final boolean inline)
221 throws IOException {
222 if (startStillOpen) {
223 sink.write('>');
224 }
225 if (inlineStack <= 1) {
226 sink.write('\n');
227 for (int i = 0; i < level; i++) {
228 sink.write(' ');
229 }
230 }
231 sink.write('<');
232 sink.write(name);
233 startStillOpen = true;
234 }
235
236 @Override
237 public void endElement(final String name) throws IOException {
238 final boolean inline = HtmlElements.isInline(name);
239 if (HtmlElements.isVoid(name)) {
240 closeEmptyTag();
241 } else {
242 endElementInternal(writer, name, inline);
243 }
244 startStillOpen = false;
245 if (inline) {
246 inlineStack--;
247 assert inlineStack >= 0;
248 }
249 }
250
251 @Override
252 public void endElement(final HtmlElements name) throws IOException {
253 final boolean inline = name.isInline();
254 if (name.isVoid()) {
255 closeEmptyTag();
256 } else {
257 if (!name.isVoid()) {
258 level--;
259 }
260 endElementInternal(writer, name.getValue(), inline);
261 }
262 startStillOpen = false;
263 if (inline) {
264 inlineStack--;
265 assert inlineStack >= 0;
266 }
267 }
268
269 @Override
270 public void writeComment(final Object obj) throws IOException {
271 closeOpenTag();
272 final String comment = obj.toString();
273 writer.write('\n');
274 for (int i = 0; i < level; i++) {
275 writer.write(' ');
276 }
277 write("<!--");
278 write(comment);
279 write("-->");
280 }
281
282
283
284
285 @Override
286 @Deprecated
287 public void writeAttribute(final String name, final Object value, final String property)
288 throws IOException {
289
290 final String attribute = findValue(value, property);
291 writeAttribute(new MarkupLanguageAttributes() {
292 @Override
293 public String getValue() {
294 return name;
295 }
296 }, attribute, true);
297 }
298
299 protected final String getCallingClassStackTraceElementString() {
300 final StackTraceElement[] stackTrace = new Exception().getStackTrace();
301 int j = 1;
302 while (stackTrace[j].getClassName().contains("ResponseWriter")) {
303 j++;
304 }
305 return stackTrace[j].toString();
306 }
307
308 @Override
309 public void writeURIAttribute(final String name, final Object value, final String property)
310 throws IOException {
311 if (value != null) {
312 final URI uri = URI.create(value.toString());
313 writeAttribute(name, uri.toASCIIString(), property);
314 }
315 }
316
317
318
319 @Override
320 public void writeAttribute(final MarkupLanguageAttributes name, final String value, final boolean escape)
321 throws IOException {
322 writeAttributeInternal(writer, name, value, escape);
323 }
324
325 @Override
326 public void writeAttribute(final MarkupLanguageAttributes name, final HtmlTypes types) throws IOException {
327 writeAttributeInternal(writer, name, types.getValue(), false);
328 }
329
330 @Override
331 public void writeURIAttribute(final MarkupLanguageAttributes name, final String value)
332 throws IOException {
333 if (value != null) {
334 final URI uri = URI.create(value);
335 writeAttribute(name, uri.toASCIIString(), true);
336 }
337 }
338
339 protected void endElementInternal(final Writer sink, final String name, final boolean inline) throws IOException {
340 if (startStillOpen) {
341 sink.write('>');
342 }
343 if (inline) {
344 sink.write("</");
345 } else {
346 sink.write('\n');
347 for (int i = 0; i < level; i++) {
348 sink.write(' ');
349 }
350 sink.write("</");
351 }
352 sink.write(name);
353 sink.write('>');
354 }
355
356 protected abstract void closeEmptyTag() throws IOException;
357
358 protected void writeAttributeInternal(
359 final Writer sink, final MarkupLanguageAttributes name, final String value, final boolean escape)
360 throws IOException {
361 if (!startStillOpen) {
362 final String trace = getCallingClassStackTraceElementString();
363 final String error = "Cannot write attribute when start-tag not open. "
364 + "name = '" + name + "' "
365 + "value = '" + value + "' "
366 + trace.substring(trace.indexOf('('));
367 LOG.error(error);
368 throw new IllegalStateException(error);
369 }
370
371 if (value != null) {
372 sink.write(' ');
373 sink.write(name.getValue());
374 sink.write("='");
375 writerAttributeValue(value, escape);
376 sink.write('\'');
377 }
378 }
379
380 protected abstract void writerAttributeValue(String value, boolean escape) throws IOException;
381
382
383 }
384