1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.view.facelets.compiler;
20
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Stack;
25
26 import javax.el.ELException;
27 import javax.faces.application.FacesMessage;
28 import javax.faces.view.facelets.CompositeFaceletHandler;
29 import javax.faces.view.facelets.FaceletHandler;
30 import javax.faces.view.facelets.Tag;
31 import javax.faces.view.facelets.TagAttribute;
32 import javax.faces.view.facelets.TagException;
33
34 import org.apache.myfaces.shared.renderkit.html.HTML;
35 import org.apache.myfaces.view.facelets.el.ELText;
36
37
38
39
40
41
42 final class TextUnit extends CompilationUnit
43 {
44
45 private final StringBuffer buffer;
46
47 private final StringBuffer textBuffer;
48
49 private final List<Instruction> instructionBuffer;
50
51 private final Stack<Tag> tags;
52
53 private final List<Object> children;
54
55 private boolean startTagOpen;
56
57 private final String alias;
58
59 private final String id;
60
61 private final List<Object> messages;
62
63 private final boolean escapeInlineText;
64
65 private final boolean compressSpaces;
66
67 public TextUnit(String alias, String id)
68 {
69 this(alias,id,true);
70 }
71
72 public TextUnit(String alias, String id, boolean escapeInlineText)
73 {
74 this(alias,id,escapeInlineText,false);
75 }
76
77 public TextUnit(String alias, String id, boolean escapeInlineText, boolean compressSpaces)
78 {
79 this.alias = alias;
80 this.id = id;
81 this.buffer = new StringBuffer();
82 this.textBuffer = new StringBuffer();
83 this.instructionBuffer = new ArrayList<Instruction>();
84 this.tags = new Stack<Tag>();
85 this.children = new ArrayList<Object>();
86 this.startTagOpen = false;
87 this.messages = new ArrayList<Object>(4);
88 this.escapeInlineText = escapeInlineText;
89 this.compressSpaces = compressSpaces;
90 }
91
92 public FaceletHandler createFaceletHandler()
93 {
94 this.flushBufferToConfig(true);
95
96 if (this.children.isEmpty())
97 {
98 return LEAF;
99 }
100
101 FaceletHandler[] h = new FaceletHandler[this.children.size()];
102 Object obj;
103 for (int i = 0; i < h.length; i++)
104 {
105 obj = this.children.get(i);
106 if (obj instanceof FaceletHandler)
107 {
108 h[i] = (FaceletHandler) obj;
109 }
110 else
111 {
112 h[i] = ((CompilationUnit) obj).createFaceletHandler();
113 }
114 }
115 if (h.length == 1)
116 {
117 return h[0];
118 }
119 return new CompositeFaceletHandler(h);
120 }
121
122 private void addInstruction(Instruction instruction)
123 {
124 this.flushTextBuffer(false);
125 this.instructionBuffer.add(instruction);
126 }
127
128 private void flushTextBuffer(boolean child)
129 {
130 if (this.textBuffer.length() > 0)
131 {
132 String s = this.textBuffer.toString();
133
134 if (child)
135 {
136 s = trimRight(s);
137 }
138 if (s.length() > 0)
139 {
140 if (!compressSpaces)
141 {
142
143 ELText txt = ELText.parse(s);
144 if (txt != null)
145 {
146 if (txt.isLiteral())
147 {
148 if (escapeInlineText)
149 {
150 this.instructionBuffer.add(new LiteralTextInstruction(txt.toString()));
151 }
152 else
153 {
154 this.instructionBuffer.add(new LiteralNonExcapedTextInstruction(txt.toString()));
155 }
156 }
157 else
158 {
159 if (escapeInlineText)
160 {
161 this.instructionBuffer.add(new TextInstruction(this.alias, txt ));
162 }
163 else
164 {
165
166
167 ELText[] splitText = ELText.parseAsArray(s);
168 if (splitText.length > 1)
169 {
170 Instruction[] array = new Instruction[splitText.length];
171 for (int i = 0; i < splitText.length; i++)
172 {
173 ELText selText = splitText[i];
174 if (selText.isLiteral())
175 {
176 array[i] = new LiteralNonExcapedTextInstruction(selText.toString());
177 }
178 else
179 {
180 array[i] = new TextInstruction(this.alias, selText );
181 }
182 }
183 this.instructionBuffer.add(new CompositeTextInstruction(array));
184 }
185 else
186 {
187 this.instructionBuffer.add(new TextInstruction(this.alias, txt ));
188 }
189 }
190 }
191 }
192 }
193 else
194 {
195
196
197 if (s != null && s.length() > 0)
198 {
199 if (ELText.isLiteral(s))
200 {
201 if (escapeInlineText)
202 {
203 this.instructionBuffer.add(new LiteralTextInstruction(s));
204 }
205 else
206 {
207 this.instructionBuffer.add(new LiteralNonExcapedTextInstruction(s));
208 }
209 }
210 else
211 {
212 if (instructionBuffer.size() > 0 &&
213 !(instructionBuffer.get(instructionBuffer.size()-1) instanceof LiteralXMLInstruction))
214 {
215 s = compressELText(s);
216 }
217
218
219 ELText[] splitText = ELText.parseAsArray(s);
220 if (splitText.length > 1)
221 {
222 Instruction[] array = new Instruction[splitText.length];
223 for (int i = 0; i < splitText.length; i++)
224 {
225 ELText selText = splitText[i];
226 if (selText.isLiteral())
227 {
228 array[i] = new LiteralNonExcapedTextInstruction(selText.toString());
229 }
230 else
231 {
232 array[i] = new TextInstruction(this.alias, selText );
233 }
234 }
235 this.instructionBuffer.add(new CompositeTextInstruction(array));
236 }
237 else
238 {
239 this.instructionBuffer.add(new TextInstruction(this.alias, ELText.parse(s)));
240 }
241 }
242 }
243 }
244 }
245
246 }
247 this.textBuffer.setLength(0);
248 }
249
250 public void write(String text)
251 {
252 this.finishStartTag();
253 this.textBuffer.append(text);
254 this.buffer.append(text);
255 }
256
257 public void writeInstruction(String text)
258 {
259 this.finishStartTag();
260 ELText el = ELText.parse(text);
261 if (el.isLiteral())
262 {
263 this.addInstruction(new LiteralXMLInstruction(text));
264 }
265 else
266 {
267 this.addInstruction(new XMLInstruction(el));
268 }
269 this.buffer.append(text);
270 }
271
272 public void writeComment(String text)
273 {
274 this.finishStartTag();
275
276 ELText el = ELText.parse(text);
277 if (el.isLiteral())
278 {
279 this.addInstruction(new LiteralCommentInstruction(text));
280 }
281 else
282 {
283 this.addInstruction(new CommentInstruction(el));
284 }
285
286 this.buffer.append("<!--" + text + "-->");
287 }
288
289 public void startTag(Tag tag)
290 {
291
292
293 this.finishStartTag();
294
295
296 this.tags.push(tag);
297
298
299 this.buffer.append('<');
300 this.buffer.append(tag.getQName());
301
302 this.addInstruction(new StartElementInstruction(tag.getQName()));
303
304 TagAttribute[] attrs = tag.getAttributes().getAll();
305 if (attrs.length > 0)
306 {
307 for (int i = 0; i < attrs.length; i++)
308 {
309 String qname = attrs[i].getQName();
310 String value = attrs[i].getValue();
311 this.buffer.append(' ').append(qname).append("=\"").append(value).append("\"");
312
313 ELText txt = ELText.parseAllowEmptyString(value);
314 if (txt != null)
315 {
316 if (txt.isLiteral())
317 {
318 this.addInstruction(new LiteralAttributeInstruction(qname, txt.toString()));
319 }
320 else
321 {
322 this.addInstruction(new AttributeInstruction(this.alias, qname, txt));
323 }
324 }
325 }
326 }
327
328 if (!messages.isEmpty())
329 {
330 for (Iterator<Object> it = messages.iterator(); it.hasNext();)
331 {
332 Object[] message = (Object[])it.next();
333 this.addInstruction(new AddFacesMessageInstruction((FacesMessage.Severity) message[0],
334 (String)message[1], (String)message[2]));
335 it.remove();
336 }
337 }
338
339
340 this.startTagOpen = true;
341 }
342
343 private void finishStartTag()
344 {
345 if (this.tags.size() > 0 && this.startTagOpen)
346 {
347 this.buffer.append(">");
348 this.startTagOpen = false;
349 }
350 }
351
352 public void endTag()
353 {
354 Tag tag = (Tag) this.tags.pop();
355
356 if (HTML.BODY_ELEM.equalsIgnoreCase(tag.getQName()))
357 {
358 this.addInstruction(new BodyEndElementInstruction(tag.getQName()));
359 }
360 else
361 {
362 this.addInstruction(new EndElementInstruction(tag.getQName()));
363 }
364
365 if (this.startTagOpen)
366 {
367 this.buffer.append("/>");
368 this.startTagOpen = false;
369 }
370 else
371 {
372 this.buffer.append("</").append(tag.getQName()).append('>');
373 }
374 }
375
376 public void addChild(CompilationUnit unit)
377 {
378
379
380 this.finishStartTag();
381 this.flushBufferToConfig(true);
382 this.children.add(unit);
383 }
384
385 protected void flushBufferToConfig(boolean child)
386 {
387 this.flushTextBuffer(child);
388
389 int size = this.instructionBuffer.size();
390 if (size > 0)
391 {
392 try
393 {
394 String s = this.buffer.toString();
395 if (child)
396 {
397 s = trimRight(s);
398 }
399 ELText txt = ELText.parse(s);
400 if (txt != null)
401 {
402 if (compressSpaces)
403 {
404
405
406
407 size = compressSpaces(instructionBuffer, size);
408 }
409 Instruction[] instructions = (Instruction[]) this.instructionBuffer
410 .toArray(new Instruction[size]);
411 this.children.add(new UIInstructionHandler(this.alias, this.id, instructions, txt));
412 this.instructionBuffer.clear();
413 }
414
415 }
416 catch (ELException e)
417 {
418 if (this.tags.size() > 0)
419 {
420 throw new TagException((Tag) this.tags.peek(), e.getMessage());
421 }
422 else
423 {
424 throw new ELException(this.alias + ": " + e.getMessage(), e.getCause());
425 }
426 }
427 }
428
429
430 this.buffer.setLength(0);
431 }
432
433 public boolean isClosed()
434 {
435 return this.tags.empty();
436 }
437
438 private final static String trimRight(String s)
439 {
440 int i = s.length() - 1;
441 while (i >= 0 && Character.isWhitespace(s.charAt(i)))
442 {
443 i--;
444 }
445 if (i >= 0)
446 {
447 return s;
448 }
449 else
450 {
451 return "";
452 }
453
454
455
456
457
458
459
460
461
462 }
463
464 final static String compressELText(String text)
465 {
466
467 int firstCharLocation = -1;
468 int leftChar = 0;
469 int lenght = text.length();
470 String leftText = null;
471 for (int j = 0; j < lenght; j++)
472 {
473 char c = text.charAt(j);
474 if (leftChar == 0)
475 {
476 if (c == '\r')
477 {
478 leftChar = 2;
479 if (j+1 < lenght)
480 {
481 if (text.charAt(j+1) == '\n')
482 {
483 leftChar = 3;
484 }
485 }
486 }
487 if (c == '\n')
488 {
489 leftChar = 1;
490 }
491 }
492 if (Character.isWhitespace(c))
493 {
494 continue;
495 }
496 else
497 {
498 firstCharLocation = j;
499 break;
500 }
501 }
502 if (firstCharLocation == -1)
503 {
504 firstCharLocation = lenght;
505 }
506
507 if (firstCharLocation > 0)
508 {
509 switch (leftChar)
510 {
511 case 1:
512 leftText = "\n";
513 break;
514 case 2:
515 leftText = "\r";
516 break;
517 case 3:
518 leftText = "\r\n";
519 break;
520 default:
521 leftText = (lenght > 1) ? text.substring(0,1) : text;
522 break;
523 }
524 }
525 else
526 {
527 leftText = "";
528 }
529
530 int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
531 if (firstCharLocation == 0 && lastCharLocation == text.length()-1)
532 {
533 return text;
534 }
535 else
536 {
537 if (lastCharLocation+1 < text.length())
538 {
539 lastCharLocation = lastCharLocation+1;
540 }
541 if (firstCharLocation == 0)
542 {
543 return text.substring(firstCharLocation, lastCharLocation+1);
544 }
545 else
546 {
547 return leftText+text.substring(firstCharLocation, lastCharLocation+1);
548 }
549 }
550 }
551
552
553
554
555
556
557
558
559
560
561 final static int compressSpaces(List<Instruction> instructionBuffer, int size)
562 {
563 boolean addleftspace = true;
564 boolean addrightspace = false;
565 boolean skipnext = false;
566 for (int i = 0; i < size; i++)
567 {
568 String text = null;
569 String newText = null;
570 int instructionType = 0;
571 if (skipnext)
572 {
573 skipnext = false;
574 continue;
575 }
576 Instruction ins = instructionBuffer.get(i);
577 if (i+1 == size)
578 {
579 addrightspace = true;
580 }
581
582 if (ins instanceof LiteralTextInstruction)
583 {
584 text = ((LiteralTextInstruction)ins).getText();
585 instructionType = 1;
586 }
587 else if (ins instanceof LiteralNonExcapedTextInstruction)
588 {
589 text = ((LiteralTextInstruction)ins).getText();
590 instructionType = 2;
591 }
592 else if (ins instanceof LiteralXMLInstruction)
593 {
594 skipnext = true;
595 continue;
596 }
597
598 if (text != null && text.length() > 0)
599 {
600 int firstCharLocation = -1;
601 int leftChar = 0;
602 int lenght = text.length();
603 String leftText = null;
604 for (int j = 0; j < lenght; j++)
605 {
606 char c = text.charAt(j);
607 if (leftChar == 0)
608 {
609 if (c == '\r')
610 {
611 leftChar = 2;
612 if (j+1 < lenght)
613 {
614 if (text.charAt(j+1) == '\n')
615 {
616 leftChar = 3;
617 }
618 }
619 }
620 if (c == '\n')
621 {
622 leftChar = 1;
623 }
624 }
625 if (Character.isWhitespace(c))
626 {
627 continue;
628 }
629 else
630 {
631 firstCharLocation = j;
632 break;
633 }
634 }
635 if (firstCharLocation == -1)
636 {
637 firstCharLocation = lenght;
638 }
639
640 if (firstCharLocation > 0)
641 {
642 switch (leftChar)
643 {
644 case 1:
645 leftText = "\n";
646 break;
647 case 2:
648 leftText = "\r";
649 break;
650 case 3:
651 leftText = "\r\n";
652 break;
653 default:
654 leftText = (lenght > 1) ? text.substring(0,1) : text;
655 break;
656 }
657 }
658 else
659 {
660 leftText = "";
661 }
662
663 if (firstCharLocation == lenght && lenght > 1)
664 {
665
666
667 if (addleftspace || addrightspace)
668 {
669 newText = leftText;
670 }
671 else
672 {
673 instructionBuffer.remove(i);
674 i--;
675 size--;
676 }
677 }
678 else
679 {
680 int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
681
682 if (lastCharLocation+1 < text.length())
683 {
684 lastCharLocation = lastCharLocation+1;
685 }
686 if (firstCharLocation > 0)
687 {
688 newText = leftText+
689 text.substring(firstCharLocation, lastCharLocation+1);
690 }
691 else
692 {
693 newText = text.substring(firstCharLocation, lastCharLocation+1);
694 }
695 }
696
697 if (newText != null)
698 {
699 if (instructionType == 1)
700 {
701 instructionBuffer.set(i, new LiteralTextInstruction(newText));
702 }
703 else if (instructionType == 2)
704 {
705 instructionBuffer.set(i, new LiteralNonExcapedTextInstruction(newText));
706 }
707 }
708 }
709 addleftspace = false;
710 }
711 return size;
712 }
713
714 private static int getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(String text)
715 {
716 for (int i = text.length()-1; i >= 0; i--)
717 {
718 if (Character.isWhitespace(text.charAt(i)))
719 {
720 continue;
721 }
722 else
723 {
724 return i;
725 }
726 }
727 return 0;
728 }
729
730 public String toString()
731 {
732 return "TextUnit[" + this.children.size() + "]";
733 }
734
735 public void addMessage(FacesMessage.Severity severity, String summary, String detail)
736 {
737 this.messages.add(new Object[]{severity, summary, detail});
738 }
739 }