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.List;
23  import java.util.Stack;
24  import java.util.logging.Level;
25  import java.util.logging.Logger;
26  
27  import javax.faces.application.FacesMessage;
28  import javax.faces.view.facelets.FaceletHandler;
29  import javax.faces.view.facelets.Tag;
30  import javax.faces.view.facelets.TagAttribute;
31  import javax.faces.view.facelets.TagAttributeException;
32  import javax.faces.view.facelets.TagDecorator;
33  import javax.faces.view.facelets.TagException;
34  
35  import org.apache.myfaces.view.facelets.tag.TagAttributesImpl;
36  import org.apache.myfaces.view.facelets.tag.TagLibrary;
37  import org.apache.myfaces.view.facelets.tag.composite.CompositeLibrary;
38  import org.apache.myfaces.view.facelets.tag.composite.ImplementationHandler;
39  import org.apache.myfaces.view.facelets.tag.composite.InterfaceHandler;
40  import org.apache.myfaces.view.facelets.tag.ui.ComponentRefHandler;
41  import org.apache.myfaces.view.facelets.tag.ui.CompositionHandler;
42  import org.apache.myfaces.view.facelets.tag.ui.UILibrary;
43  
44  /**
45   * Compilation unit for managing the creation of a single FaceletHandler based on events from an XML parser.
46   * 
47   * @see org.apache.myfaces.view.facelets.compiler.Compiler
48   * 
49   * @author Jacob Hookom
50   * @version $Id$
51   */
52  final class CompilationManager
53  {
54  
55      //private final static Logger log = Logger.getLogger("facelets.compiler");
56      private final static Logger log = Logger.getLogger(CompilationManager.class.getName());
57  
58      private final Compiler compiler;
59  
60      private final TagLibrary tagLibrary;
61  
62      private final TagDecorator tagDecorator;
63  
64      private final NamespaceManager namespaceManager;
65  
66      private final Stack<CompilationUnit> units;
67  
68      private int tagId;
69  
70      private boolean finished;
71  
72      private final String alias;
73      
74      private CompilationUnit interfaceCompilationUnit;
75      
76      private final FaceletsProcessingInstructions faceletsProcessingInstructions;
77  
78      public CompilationManager(String alias, Compiler compiler, FaceletsProcessingInstructions instructions)
79      {
80  
81          // this is our alias
82          this.alias = alias;
83  
84          // grab compiler state
85          this.compiler = compiler;
86          this.tagDecorator = compiler.createTagDecorator();
87          this.tagLibrary = compiler.createTagLibrary();
88  
89          // namespace management
90          this.namespaceManager = new NamespaceManager();
91  
92          // tag uids
93          this.tagId = 0;
94  
95          // for composition use
96          this.finished = false;
97  
98          // our compilationunit stack
99          this.units = new Stack<CompilationUnit>();
100         this.units.push(new CompilationUnit());
101         
102         this.interfaceCompilationUnit = null; 
103         this.faceletsProcessingInstructions = instructions;
104     }
105 
106     public void writeInstruction(String value)
107     {
108         if (this.finished)
109         {
110             return;
111         }
112 
113         // don't carelessly add empty tags
114         if (value.length() == 0)
115         {
116             return;
117         }
118 
119         TextUnit unit;
120         if (this.currentUnit() instanceof TextUnit)
121         {
122             unit = (TextUnit) this.currentUnit();
123         }
124         else
125         {
126             unit = new TextUnit(this.alias, this.nextTagId(), 
127                     faceletsProcessingInstructions.isEscapeInlineText(),
128                     faceletsProcessingInstructions.isCompressSpaces());
129             this.startUnit(unit);
130         }
131         unit.writeInstruction(value);
132     }
133     
134     public void writeDoctype(String name, String publicId, String systemId)
135     {
136         if (this.finished)
137         {
138             return;
139         }
140 
141         DoctypeUnit unit = new DoctypeUnit(this.alias, this.nextTagId(),
142             name, publicId, systemId, faceletsProcessingInstructions.isHtml5Doctype());
143         this.startUnit(unit);
144     }
145 
146     public void writeText(String value)
147     {
148 
149         if (this.finished)
150         {
151             return;
152         }
153 
154         // don't carelessly add empty tags
155         if (value.length() == 0)
156         {
157             return;
158         }
159 
160         TextUnit unit;
161         if (this.currentUnit() instanceof TextUnit)
162         {
163             unit = (TextUnit) this.currentUnit();
164         }
165         else
166         {
167             unit = new TextUnit(this.alias, this.nextTagId(), 
168                     faceletsProcessingInstructions.isEscapeInlineText(),
169                     faceletsProcessingInstructions.isCompressSpaces());
170             this.startUnit(unit);
171         }
172         unit.write(value);
173     }
174 
175     public void writeComment(String text)
176     {
177         if (this.compiler.isTrimmingComments())
178         {
179             return;
180         }
181 
182         if (this.finished)
183         {
184             return;
185         }
186 
187         // don't carelessly add empty tags
188         if (text.length() == 0)
189         {
190             return;
191         }
192 
193         TextUnit unit;
194         if (this.currentUnit() instanceof TextUnit)
195         {
196             unit = (TextUnit) this.currentUnit();
197         }
198         else
199         {
200             unit = new TextUnit(this.alias, this.nextTagId(), 
201                     faceletsProcessingInstructions.isEscapeInlineText(),
202                     faceletsProcessingInstructions.isCompressSpaces());
203             this.startUnit(unit);
204         }
205 
206         unit.writeComment(text);
207     }
208 
209     public void writeWhitespace(String text)
210     {
211         if (!this.compiler.isTrimmingWhitespace())
212         {
213             this.writeText(text);
214         }
215     }
216 
217     private String nextTagId()
218     {
219         return Integer.toHexString(Math.abs(this.alias.hashCode() ^ 13 * this.tagId++));
220     }
221 
222     public void pushTag(Tag orig)
223     {
224 
225         if (this.finished)
226         {
227             return;
228         }
229 
230         if (log.isLoggable(Level.FINE))
231         {
232             log.fine("Tag Pushed: " + orig);
233         }
234 
235         Tag t = this.tagDecorator.decorate(orig);
236         String[] qname = this.determineQName(t);
237         t = this.trimAttributes(t);
238 
239         if (isTrimmed(qname[0], qname[1]))
240         {
241             log.fine("Composition Found, Popping Parent Tags");
242             this.units.clear();
243             NamespaceUnit nsUnit = this.namespaceManager.toNamespaceUnit(this.tagLibrary);
244             this.units.push(nsUnit);
245             this.startUnit(new TrimmedTagUnit(this.tagLibrary, qname[0], qname[1], t, this.nextTagId()));
246             log.fine("New Namespace and [Trimmed] TagUnit pushed");
247         }
248         else if (isRemove(qname[0], qname[1]))
249         {
250             this.units.push(new RemoveUnit());
251         }
252         else if (isCompositeComponentInterface(qname[0], qname[1]))
253         {
254             // Here we have two cases when we found a <composite:interface> tag:
255             //
256             // -  If a page has a <composite:interface> tag and a <composite:implementation> tag.
257             //   In this case, we need to trim all tags outside this two tags, otherwise these
258             //   unwanted tags will be added when the composite component is applied.
259             //   Unfortunately, this is the only point we can do it, because after the compiler,
260             //   html tags are wrapped on facelets UIInstruction or UIText components as "list",
261             //   losing the original structure required to trim.
262             //
263             // -  If a page has a <composite:interface> tag and not a <composite:implementation> tag.
264             //   In this case, it is not necessary to trim, because we use the facelet only to create
265             //   metadata and the component tree created is not used (see
266             //   ViewDeclarationLanguage.getComponentMetadata() ). On InterfaceHandler, instead
267             //   there is some code that found the right component in the temporal tree to add the
268             //   generated BeanInfo, which it is retrieved later.
269             //
270             // After use Template Client API for composite components, it was found the need to
271             // gather metadata information from 
272             log.fine("Composite Component Interface Found, saving unit");
273             CompositeComponentUnit compositeRootCompilationUnit = new CompositeComponentUnit();
274             this.startUnit(compositeRootCompilationUnit);
275             interfaceCompilationUnit = new TagUnit(this.tagLibrary, qname[0], qname[1], t, this.nextTagId());
276             this.startUnit(interfaceCompilationUnit);
277         }        
278         else if (isCompositeComponentImplementation(qname[0], qname[1]))
279         {
280             log.fine("Composite component Found, Popping Parent Tags");
281             this.units.clear();
282             NamespaceUnit nsUnit = this.namespaceManager.toNamespaceUnit(this.tagLibrary);
283             this.units.push(nsUnit);
284             CompositeComponentUnit compositeRootCompilationUnit = new CompositeComponentUnit();
285             this.startUnit(compositeRootCompilationUnit);
286             if (interfaceCompilationUnit != null)
287             {
288                 this.currentUnit().addChild(interfaceCompilationUnit);
289                 interfaceCompilationUnit = null;
290             }
291             this.startUnit(new TrimmedTagUnit(this.tagLibrary, qname[0], qname[1], t, this.nextTagId()));
292             log.fine("New Namespace and TagUnit pushed");
293         }        
294         else if (this.tagLibrary.containsTagHandler(qname[0], qname[1]))
295         {
296             this.startUnit(new TagUnit(this.tagLibrary, qname[0], qname[1], t, this.nextTagId()));
297         }
298         else if (this.tagLibrary.containsNamespace(qname[0]))
299         {
300             throw new TagException(orig, "Tag Library supports namespace: " + qname[0]
301                     + ", but no tag was defined for name: " + qname[1]);
302         }
303         else
304         {
305             TextUnit unit;
306             if (this.currentUnit() instanceof TextUnit)
307             {
308                 unit = (TextUnit) this.currentUnit();
309             }
310             else
311             {
312                 unit = new TextUnit(this.alias, this.nextTagId(),
313                         faceletsProcessingInstructions.isEscapeInlineText(),
314                         faceletsProcessingInstructions.isCompressSpaces());
315                 this.startUnit(unit);
316             }
317             
318             if (this.compiler.isDevelopmentProjectStage())
319             {
320                 String qName = null;
321                 boolean isPrefixed = false;
322                 TagAttribute jsfc = t.getAttributes().get("jsfc");
323                 if (jsfc != null)
324                 {
325                     qName = jsfc.getValue();
326                     if (jsfc.getValue().indexOf(':') > 0)
327                     {
328                         isPrefixed = true;
329                     }
330                 }
331                 else if (t.getQName().indexOf(':') > 0 )
332                 {
333                     qName = t.getQName();
334                     isPrefixed = true;
335                 }
336                 if (isPrefixed)
337                 {
338                     unit.addMessage(FacesMessage.SEVERITY_WARN,
339                             "Warning: The page "+alias+" declares namespace "+qname[0]+ 
340                             " and uses the tag " + qName + " , but no TagLibrary associated to namespace.", 
341                             "Warning: The page "+alias+" declares namespace "+qname[0]+ 
342                             " and uses the tag " + qName + " , but no TagLibrary associated to namespace. "+
343                             "Please check the namespace name and if it is correct, it is probably that your " +
344                             "library .taglib.xml cannot be found on the current classpath, or if you are " +
345                             "referencing a composite component library check your library folder match with the " +
346                             "namespace and can be located by the installed ResourceHandler.");
347                 }
348             }
349             
350             unit.startTag(t);
351         }
352     }
353 
354     public void popTag()
355     {
356 
357         if (this.finished)
358         {
359             return;
360         }
361 
362         CompilationUnit unit = this.currentUnit();
363 
364         if (unit instanceof TextUnit)
365         {
366             TextUnit t = (TextUnit) unit;
367             if (t.isClosed())
368             {
369                 this.finishUnit();
370             }
371             else
372             {
373                 t.endTag();
374                 return;
375             }
376         }
377 
378         unit = this.currentUnit();
379         if (unit instanceof TagUnit)
380         {
381             TagUnit t = (TagUnit) unit;
382             if (t instanceof TrimmedTagUnit)
383             {
384                 this.finished = true;
385                 return;
386             }
387         }
388         else if (unit instanceof CompositeComponentUnit)
389         {
390             this.finished = true;
391             return;
392         }
393 
394         this.finishUnit();
395     }
396 
397     public void popNamespace(String ns)
398     {
399         this.namespaceManager.popNamespace(ns);
400         if (this.currentUnit() instanceof NamespaceUnit)
401         {
402             this.finishUnit();
403         }
404     }
405 
406     public void pushNamespace(String prefix, String uri)
407     {
408 
409         if (log.isLoggable(Level.FINE))
410         {
411             log.fine("Namespace Pushed " + prefix + ": " + uri);
412         }
413 
414         this.namespaceManager.pushNamespace(prefix, uri);
415         NamespaceUnit unit;
416         if (this.currentUnit() instanceof NamespaceUnit)
417         {
418             unit = (NamespaceUnit) this.currentUnit();
419         }
420         else
421         {
422             unit = new NamespaceUnit(this.tagLibrary);
423             this.startUnit(unit);
424         }
425         unit.setNamespace(prefix, uri);
426     }
427 
428     public FaceletHandler createFaceletHandler()
429     {
430         return ((CompilationUnit) this.units.get(0)).createFaceletHandler();
431     }
432 
433     private CompilationUnit currentUnit()
434     {
435         if (!this.units.isEmpty())
436         {
437             return (CompilationUnit) this.units.peek();
438         }
439         return null;
440     }
441 
442     private void finishUnit()
443     {
444         Object obj = this.units.pop();
445 
446         if (log.isLoggable(Level.FINE))
447         {
448             log.fine("Finished Unit: " + obj);
449         }
450     }
451 
452     private void startUnit(CompilationUnit unit)
453     {
454 
455         if (log.isLoggable(Level.FINE))
456         {
457             log.fine("Starting Unit: " + unit + " and adding it to parent: " + this.currentUnit());
458         }
459 
460         this.currentUnit().addChild(unit);
461         this.units.push(unit);
462     }
463 
464     private Tag trimAttributes(Tag tag)
465     {
466         Tag t = this.trimJSFCAttribute(tag);
467         t = this.trimNSAttributes(t);
468         return t;
469     }
470 
471     protected static boolean isRemove(String ns, String name)
472     {
473         return (UILibrary.NAMESPACE.equals(ns) || UILibrary.ALIAS_NAMESPACE.equals(ns)) && "remove".equals(name);
474     }
475 
476     protected static boolean isTrimmed(String ns, String name)
477     {
478         return (UILibrary.NAMESPACE.equals(ns) || UILibrary.ALIAS_NAMESPACE.equals(ns))
479                 && (CompositionHandler.NAME.equals(name) || ComponentRefHandler.NAME.equals(name));
480     }
481     
482     protected static boolean isCompositeComponentInterface(String ns, String name)
483     {
484         return (CompositeLibrary.NAMESPACE.equals(ns) || CompositeLibrary.ALIAS_NAMESPACE.equals(ns))
485             && InterfaceHandler.NAME.equals(name);
486     }
487 
488     protected static boolean isCompositeComponentImplementation(String ns, String name)
489     {
490         return (CompositeLibrary.NAMESPACE.equals(ns) || CompositeLibrary.ALIAS_NAMESPACE.equals(ns))
491             && ImplementationHandler.NAME.equals(name);
492     }
493 
494     private String[] determineQName(Tag tag)
495     {
496         TagAttribute attr = tag.getAttributes().get("jsfc");
497         if (attr != null)
498         {
499             if (log.isLoggable(Level.FINE))
500             {
501                 log.fine(attr + " JSF Facelet Compile Directive Found");
502             }
503             String value = attr.getValue();
504             String namespace;
505             String localName;
506             int c = value.indexOf(':');
507             if (c == -1)
508             {
509                 namespace = this.namespaceManager.getNamespace("");
510                 localName = value;
511             }
512             else
513             {
514                 String prefix = value.substring(0, c);
515                 namespace = this.namespaceManager.getNamespace(prefix);
516                 if (namespace == null)
517                 {
518                     throw new TagAttributeException(tag, attr, "No Namespace matched for: " + prefix);
519                 }
520                 localName = value.substring(c + 1);
521             }
522             return new String[] { namespace, localName };
523         }
524         else
525         {
526             return new String[] { tag.getNamespace(), tag.getLocalName() };
527         }
528     }
529 
530     private Tag trimJSFCAttribute(Tag tag)
531     {
532         TagAttribute attr = tag.getAttributes().get("jsfc");
533         if (attr != null)
534         {
535             TagAttribute[] oa = tag.getAttributes().getAll();
536             TagAttribute[] na = new TagAttribute[oa.length - 1];
537             int p = 0;
538             for (int i = 0; i < oa.length; i++)
539             {
540                 if (!"jsfc".equals(oa[i].getLocalName()))
541                 {
542                     na[p++] = oa[i];
543                 }
544             }
545             return new Tag(tag, new TagAttributesImpl(na));
546         }
547         return tag;
548     }
549 
550     private Tag trimNSAttributes(Tag tag)
551     {
552         TagAttribute[] attr = tag.getAttributes().getAll();
553         int remove = 0;
554         for (int i = 0; i < attr.length; i++)
555         {
556             if (attr[i].getQName().startsWith("xmlns") && this.tagLibrary.containsNamespace(attr[i].getValue()))
557             {
558                 remove |= 1 << i;
559                 if (log.isLoggable(Level.FINE))
560                 {
561                     log.fine(attr[i] + " Namespace Bound to TagLibrary");
562                 }
563             }
564         }
565         if (remove == 0)
566         {
567             return tag;
568         }
569         else
570         {
571             List<TagAttribute> attrList = new ArrayList<TagAttribute>(attr.length);
572             int p = 0;
573             for (int i = 0; i < attr.length; i++)
574             {
575                 p = 1 << i;
576                 if ((p & remove) == p)
577                 {
578                     continue;
579                 }
580                 attrList.add(attr[i]);
581             }
582             attr = attrList.toArray(new TagAttribute[attrList.size()]);
583             return new Tag(tag.getLocation(), tag.getNamespace(), tag.getLocalName(), tag.getQName(),
584                            new TagAttributesImpl(attr));
585         }
586     }
587 
588     /**
589      * 
590      * @since 2.1.0
591      * @return
592      */
593     public FaceletsProcessingInstructions getFaceletsProcessingInstructions()
594     {
595         return faceletsProcessingInstructions;
596     }
597 }
598