View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * @author Jacob Hookom
40   * @version $Id: TextUnit.java 1390212 2012-09-25 23:17:29Z lu4242 $
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.size() == 0)
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                     //Do it as usual.
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                             this.instructionBuffer.add(new TextInstruction(this.alias, txt ));
160                         }
161                     }
162                 }
163                 else
164                 {
165                     // First check if the text contains EL before build something, and if contains 
166                     // an EL expression, compress it before build the ELText.
167                     if (s != null && s.length() > 0)
168                     {
169                         if (ELText.isLiteral(s))
170                         {
171                             if (escapeInlineText)
172                             {
173                                 this.instructionBuffer.add(new LiteralTextInstruction(s));
174                             }
175                             else
176                             {
177                                 this.instructionBuffer.add(new LiteralNonExcapedTextInstruction(s));
178                             }
179                         }
180                         else
181                         {
182                             s = compressELText(s);
183                             this.instructionBuffer.add(new TextInstruction(this.alias, ELText.parse(s) ));
184                         }
185                     }
186                 }
187             }
188 
189         }
190         this.textBuffer.setLength(0);
191     }
192 
193     public void write(String text)
194     {
195         this.finishStartTag();
196         this.textBuffer.append(text);
197         this.buffer.append(text);
198     }
199 
200     public void writeInstruction(String text)
201     {
202         this.finishStartTag();
203         ELText el = ELText.parse(text);
204         if (el.isLiteral())
205         {
206             this.addInstruction(new LiteralXMLInstruction(text));
207         }
208         else
209         {
210             this.addInstruction(new XMLInstruction(el));
211         }
212         this.buffer.append(text);
213     }
214 
215     public void writeComment(String text)
216     {
217         this.finishStartTag();
218 
219         ELText el = ELText.parse(text);
220         if (el.isLiteral())
221         {
222             this.addInstruction(new LiteralCommentInstruction(text));
223         }
224         else
225         {
226             this.addInstruction(new CommentInstruction(el));
227         }
228 
229         this.buffer.append("<!--" + text + "-->");
230     }
231 
232     public void startTag(Tag tag)
233     {
234 
235         // finish any previously written tags
236         this.finishStartTag();
237 
238         // push this tag onto the stack
239         this.tags.push(tag);
240 
241         // write it out
242         this.buffer.append('<');
243         this.buffer.append(tag.getQName());
244 
245         this.addInstruction(new StartElementInstruction(tag.getQName()));
246 
247         TagAttribute[] attrs = tag.getAttributes().getAll();
248         if (attrs.length > 0)
249         {
250             for (int i = 0; i < attrs.length; i++)
251             {
252                 String qname = attrs[i].getQName();
253                 String value = attrs[i].getValue();
254                 this.buffer.append(' ').append(qname).append("=\"").append(value).append("\"");
255 
256                 ELText txt = ELText.parse(value);
257                 if (txt != null)
258                 {
259                     if (txt.isLiteral())
260                     {
261                         this.addInstruction(new LiteralAttributeInstruction(qname, txt.toString()));
262                     }
263                     else
264                     {
265                         this.addInstruction(new AttributeInstruction(this.alias, qname, txt));
266                     }
267                 }
268             }
269         }
270         
271         if (!messages.isEmpty())
272         {
273             for (Iterator<Object> it = messages.iterator(); it.hasNext();)
274             {
275                 Object[] message = (Object[])it.next();
276                 this.addInstruction(new AddFacesMessageInstruction((FacesMessage.Severity) message[0],
277                                                                    (String)message[1], (String)message[2]));
278                 it.remove();
279             }
280         }
281 
282         // notify that we have an open tag
283         this.startTagOpen = true;
284     }
285 
286     private void finishStartTag()
287     {
288         if (this.tags.size() > 0 && this.startTagOpen)
289         {
290             this.buffer.append(">");
291             this.startTagOpen = false;
292         }
293     }
294 
295     public void endTag()
296     {
297         Tag tag = (Tag) this.tags.pop();
298 
299         if (HTML.BODY_ELEM.equalsIgnoreCase(tag.getQName()))
300         {
301             this.addInstruction(new BodyEndElementInstruction(tag.getQName()));
302         }
303         else
304         {
305             this.addInstruction(new EndElementInstruction(tag.getQName()));            
306         }
307 
308         if (this.startTagOpen)
309         {
310             this.buffer.append("/>");
311             this.startTagOpen = false;
312         }
313         else
314         {
315             this.buffer.append("</").append(tag.getQName()).append('>');
316         }
317     }
318 
319     public void addChild(CompilationUnit unit)
320     {
321         // if we are adding some other kind of unit
322         // then we need to capture our buffer into a UITextHandler
323         this.finishStartTag();
324         this.flushBufferToConfig(true);
325         this.children.add(unit);
326     }
327 
328     protected void flushBufferToConfig(boolean child)
329     {
330 
331         // NEW IMPLEMENTATION
332         if (true)
333         {
334 
335             this.flushTextBuffer(child);
336 
337             int size = this.instructionBuffer.size();
338             if (size > 0)
339             {
340                 try
341                 {
342                     String s = this.buffer.toString();
343                     if (child)
344                     {
345                         s = trimRight(s);
346                     }
347                     ELText txt = ELText.parse(s);
348                     if (txt != null)
349                     {
350                         if (compressSpaces)
351                         {
352                             // Use the logic behind the instructions to remove unnecessary instructions
353                             // containing only spaces, or recreating new ones containing only the necessary
354                             // spaces.
355                             size = compressSpaces(instructionBuffer, size);
356                         }
357                         Instruction[] instructions = (Instruction[]) this.instructionBuffer
358                                 .toArray(new Instruction[size]);
359                         this.children.add(new UIInstructionHandler(this.alias, this.id, instructions, txt));
360                         this.instructionBuffer.clear();
361                     }
362 
363                 }
364                 catch (ELException e)
365                 {
366                     if (this.tags.size() > 0)
367                     {
368                         throw new TagException((Tag) this.tags.peek(), e.getMessage());
369                     }
370                     else
371                     {
372                         throw new ELException(this.alias + ": " + e.getMessage(), e.getCause());
373                     }
374                 }
375             }
376 
377             // KEEP THESE SEPARATE SO LOGIC DOESN'T GET FUBARED
378         }
379         else if (this.buffer.length() > 0)
380         {
381             String s = this.buffer.toString();
382             if (s.trim().length() > 0)
383             {
384                 if (child)
385                 {
386                     s = trimRight(s);
387                 }
388                 if (s.length() > 0)
389                 {
390                     try
391                     {
392                         ELText txt = ELText.parse(s);
393                         if (txt != null)
394                         {
395                             if (txt.isLiteral())
396                             {
397                                 this.children.add(new UILiteralTextHandler(txt.toString()));
398                             }
399                             else
400                             {
401                                 this.children.add(new UITextHandler(this.alias, txt));
402                             }
403                         }
404                     }
405                     catch (ELException e)
406                     {
407                         if (this.tags.size() > 0)
408                         {
409                             throw new TagException((Tag) this.tags.peek(), e.getMessage());
410                         }
411                         else
412                         {
413                             throw new ELException(this.alias + ": " + e.getMessage(), e.getCause());
414                         }
415                     }
416                 }
417             }
418         }
419 
420         // ALWAYS CLEAR FOR BOTH IMPL
421         this.buffer.setLength(0);
422     }
423 
424     public boolean isClosed()
425     {
426         return this.tags.empty();
427     }
428 
429     private final static String trimRight(String s)
430     {
431         int i = s.length() - 1;
432         while (i >= 0 && Character.isWhitespace(s.charAt(i)))
433         {
434             i--;
435         }
436         if (i >= 0)
437         {
438             return s;
439         }
440         else
441         {
442             return "";
443         }
444         /*
445         if (i == s.length() - 1)
446         {
447             return s;
448         }
449         else
450         {
451             return s.substring(0, i + 1);
452         }*/
453     }
454     
455     final static String compressELText(String text)
456     {
457         int firstCharLocation = getFirstTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
458         int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
459         if (firstCharLocation == 0 && lastCharLocation == text.length()-1)
460         {
461             return text;
462         }
463         else
464         {
465             if (lastCharLocation+1 < text.length())
466             {
467                 lastCharLocation = lastCharLocation+1;
468             }
469             if (firstCharLocation == 0)
470             {
471                 return text.substring(firstCharLocation, lastCharLocation+1);
472             }
473             else
474             {
475                 return text.substring(0,1)+text.substring(firstCharLocation, lastCharLocation+1);
476             }
477         }
478     }
479     
480     /*
481     final static ELText compressELText(ELText parsedText, String text)
482     {
483         int firstCharLocation = getFirstTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
484         int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
485         if (firstCharLocation == 0 && lastCharLocation == text.length()-1)
486         {
487             return parsedText;
488         }
489         else
490         {
491             if (lastCharLocation+1 < text.length())
492             {
493                 lastCharLocation = lastCharLocation+1;
494             }
495             if (firstCharLocation == 0)
496             {
497                 return ELText.parse(text.substring(firstCharLocation, lastCharLocation+1));
498             }
499             else
500             {
501                 return ELText.parse(text.substring(0,1)+text.substring(firstCharLocation, lastCharLocation+1));
502             }
503         }
504     }
505     */
506     
507     final static int compressSpaces(List<Instruction> instructionBuffer, int size)
508     {
509         boolean addleftspace = true;
510         boolean addrightspace = false;
511         for (int i = 0; i < size; i++)
512         {
513             Instruction ins = instructionBuffer.get(i);
514             if (i+1 == size)
515             {
516                 addrightspace = true;
517             }
518             //boolean isNextStartExpression = i+1<size ? 
519             //        (this.instructions[i+1] instanceof StartElementInstruction) : false;
520             if (ins instanceof LiteralTextInstruction)
521             {
522                 String text = ((LiteralTextInstruction)ins).getText();
523                 int firstCharLocation = getFirstTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
524                 if (firstCharLocation == text.length() && text.length() > 1)
525                 {
526                     // All the instruction is space, replace with an instruction 
527                     // with only one space
528                     if (addleftspace || addrightspace)
529                     {
530                         instructionBuffer.set(i, new LiteralTextInstruction(text.substring(0,1)));
531                     }
532                     else
533                     {
534                         instructionBuffer.remove(i);
535                         i--;
536                         size--;
537                     }
538                 }
539                 else if (firstCharLocation > 0)
540                 {
541                     int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
542                     // If right space, increment in 1
543                     if (lastCharLocation+1 < text.length())
544                     {
545                         lastCharLocation = lastCharLocation+1;
546                     }
547                     instructionBuffer.set(i, new LiteralTextInstruction(
548                             text.substring(0,1)+text.substring(firstCharLocation, lastCharLocation+1)));
549                 }
550                 else
551                 {
552                     int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
553                     // If right space, increment in 1
554                     if (lastCharLocation+1 < text.length())
555                     {
556                         lastCharLocation = lastCharLocation+1;
557                     }
558                     instructionBuffer.set(i, new LiteralTextInstruction(
559                             text.substring(firstCharLocation, lastCharLocation+1)));
560                 }
561             }
562             else if (ins instanceof LiteralNonExcapedTextInstruction)
563             {
564                 String text = ((LiteralTextInstruction)ins).getText();
565                 int firstCharLocation = getFirstTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
566                 if (firstCharLocation == text.length())
567                 {
568                     // All the instruction is space, replace with an instruction 
569                     // with only one space
570                     if (addleftspace || addrightspace)
571                     {
572                         instructionBuffer.set(i, new LiteralNonExcapedTextInstruction(text.substring(0,1)));
573                     }
574                     else
575                     {
576                         instructionBuffer.remove(i);
577                         i--;
578                         size--;
579                     }                    
580                 }
581                 else if (firstCharLocation > 1)
582                 {
583                     int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
584                     // If right space, increment in 1
585                     if (lastCharLocation+1 < text.length())
586                     {
587                         lastCharLocation = lastCharLocation+1;
588                     }
589                     instructionBuffer.set(i, new LiteralNonExcapedTextInstruction(
590                             text.substring(0,1)+text.substring(firstCharLocation, lastCharLocation+1)));
591                 }
592                 else
593                 {
594                     int lastCharLocation = getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(text);
595                     // If right space, increment in 1
596                     if (lastCharLocation+1 < text.length())
597                     {
598                         lastCharLocation = lastCharLocation+1;
599                     }
600                     instructionBuffer.set(i, new LiteralNonExcapedTextInstruction(
601                             text.substring(firstCharLocation, lastCharLocation+1)));
602                 }
603             }
604             addleftspace = false;
605         }
606         return size;
607     }
608     
609     private static int getFirstTextCharLocationIgnoringSpacesTabsAndCarriageReturn(String text)
610     {
611         for (int i = 0; i < text.length(); i++)
612         {
613             if (Character.isWhitespace(text.charAt(i)))
614             {
615                 continue;
616             }
617             else
618             {
619                 return i;
620             }
621         }
622         return text.length();
623     }
624     
625     private static int getLastTextCharLocationIgnoringSpacesTabsAndCarriageReturn(String text)
626     {
627         for (int i = text.length()-1; i >= 0; i--)
628         {
629             if (Character.isWhitespace(text.charAt(i)))
630             {
631                 continue;
632             }
633             else
634             {
635                 return i;
636             }
637         }
638         return 0;
639     }
640 
641     public String toString()
642     {
643         return "TextUnit[" + this.children.size() + "]";
644     }
645     
646     public void addMessage(FacesMessage.Severity severity, String summary, String detail)
647     {
648         this.messages.add(new Object[]{severity, summary, detail});
649     }
650 }