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.io.IOException;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import java.util.Set;
30  import javax.el.ValueExpression;
31  import javax.faces.FacesException;
32  import javax.faces.component.ContextCallback;
33  import javax.faces.component.UIComponent;
34  import javax.faces.component.UINamingContainer;
35  import javax.faces.component.UIViewRoot;
36  import javax.faces.component.UniqueIdVendor;
37  import javax.faces.component.visit.VisitCallback;
38  import javax.faces.component.visit.VisitContext;
39  import javax.faces.context.FacesContext;
40  import javax.faces.el.ValueBinding;
41  import javax.faces.event.AbortProcessingException;
42  import javax.faces.event.ComponentSystemEvent;
43  import javax.faces.event.FacesEvent;
44  import javax.faces.event.FacesListener;
45  import javax.faces.render.Renderer;
46  
47  import javax.faces.view.Location;
48  import org.apache.commons.collections.iterators.EmptyIterator;
49  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
50  
51  class UILeaf extends UIComponent implements Map<String, Object>
52  {
53      //-------------- START TAKEN FROM UIComponentBase ----------------
54      private static final String _STRING_BUILDER_KEY
55              = "javax.faces.component.UIComponentBase.SHARED_STRING_BUILDER";
56      
57      private String _clientId = null;
58      
59      private String _id = null;
60  
61      public String getClientId(FacesContext context)
62      {
63          if (context == null)
64          {
65              throw new NullPointerException("context");
66          }
67  
68          if (_clientId != null)
69          {
70              return _clientId;
71          }
72  
73          //boolean idWasNull = false;
74          String id = getId();
75          if (id == null)
76          {
77              // Although this is an error prone side effect, we automatically create a new id
78              // just to be compatible to the RI
79              
80              // The documentation of UniqueIdVendor says that this interface should be implemented by
81              // components that also implements NamingContainer. The only component that does not implement
82              // NamingContainer but UniqueIdVendor is UIViewRoot. Anyway we just can't be 100% sure about this
83              // fact, so it is better to scan for the closest UniqueIdVendor. If it is not found use 
84              // viewRoot.createUniqueId, otherwise use UniqueIdVendor.createUniqueId(context,seed).
85              UniqueIdVendor parentUniqueIdVendor = _ComponentUtils.findParentUniqueIdVendor(this);
86              if (parentUniqueIdVendor == null)
87              {
88                  UIViewRoot viewRoot = context.getViewRoot();
89                  if (viewRoot != null)
90                  {
91                      id = viewRoot.createUniqueId();
92                  }
93                  else
94                  {
95                      // The RI throws a NPE
96                      String location = getComponentLocation(this);
97                      throw new FacesException("Cannot create clientId. No id is assigned for component"
98                              + " to create an id and UIViewRoot is not defined: "
99                              + getPathToComponent(this)
100                             + (location != null ? " created from: " + location : ""));
101                 }
102             }
103             else
104             {
105                 id = parentUniqueIdVendor.createUniqueId(context, null);
106             }
107             setId(id);
108             // We remember that the id was null and log a warning down below
109             // idWasNull = true;
110         }
111 
112         UIComponent namingContainer = _ComponentUtils.findParentNamingContainer(this, false);
113         if (namingContainer != null)
114         {
115             String containerClientId = namingContainer.getContainerClientId(context);
116             if (containerClientId != null)
117             {
118                 StringBuilder bld = _getSharedStringBuilder(context);
119                 _clientId = bld.append(containerClientId).append(
120                                       UINamingContainer.getSeparatorChar(context)).append(id).toString();
121             }
122             else
123             {
124                 _clientId = id;
125             }
126         }
127         else
128         {
129             _clientId = id;
130         }
131 
132         Renderer renderer = getRenderer(context);
133         if (renderer != null)
134         {
135             _clientId = renderer.convertClientId(context, _clientId);
136         }
137 
138         // -=Leonardo Uribe=- In jsf 1.1 and 1.2 this warning has sense, but in jsf 2.0 it is common to have
139         // components without any explicit id (UIViewParameter components and UIOuput resource components) instances.
140         // So, this warning is becoming obsolete in this new context and should be removed.
141         //if (idWasNull && log.isLoggable(Level.WARNING))
142         //{
143         //    log.warning("WARNING: Component " + _clientId
144         //            + " just got an automatic id, because there was no id assigned yet. "
145         //            + "If this component was created dynamically (i.e. not by a JSP tag) you should assign it an "
146         //            + "explicit static id or assign it the id you get from "
147         //            + "the createUniqueId from the current UIViewRoot "
148         //            + "component right after creation! Path to Component: " + getPathToComponent(this));
149         //}
150 
151         return _clientId;
152     }
153     
154     public String getId()
155     {
156         return _id;
157     }
158     
159     @Override
160     public void setId(String id)
161     {
162         isIdValid(id);
163         _id = id;
164         _clientId = null;
165     }
166     
167     private void isIdValid(String string)
168     {
169 
170         // is there any component identifier ?
171         if (string == null)
172         {
173             return;
174         }
175 
176         // Component identifiers must obey the following syntax restrictions:
177         // 1. Must not be a zero-length String.
178         if (string.length() == 0)
179         {
180             throw new IllegalArgumentException("component identifier must not be a zero-length String");
181         }
182 
183         // If new id is the same as old it must be valid
184         if (string.equals(_id))
185         {
186             return;
187         }
188 
189         // 2. First character must be a letter or an underscore ('_').
190         if (!Character.isLetter(string.charAt(0)) && string.charAt(0) != '_')
191         {
192             throw new IllegalArgumentException("component identifier's first character must be a letter "
193                                                + "or an underscore ('_')! But it is \""
194                                                + string.charAt(0) + "\"");
195         }
196         for (int i = 1; i < string.length(); i++)
197         {
198             char c = string.charAt(i);
199             // 3. Subsequent characters must be a letter, a digit, an underscore ('_'), or a dash ('-').
200             if (!Character.isLetterOrDigit(c) && c != '-' && c != '_')
201             {
202                 throw new IllegalArgumentException("Subsequent characters of component identifier must be a letter, "
203                                                    + "a digit, an underscore ('_'), or a dash ('-')! "
204                                                    + "But component identifier contains \""
205                                                    + c + "\"");
206             }
207         }
208     }
209     
210     private String getComponentLocation(UIComponent component)
211     {
212         Location location = (Location) component.getAttributes()
213                 .get(UIComponent.VIEW_LOCATION_KEY);
214         if (location != null)
215         {
216             return location.toString();
217         }
218         return null;
219     }
220 
221     private String getPathToComponent(UIComponent component)
222     {
223         StringBuffer buf = new StringBuffer();
224 
225         if (component == null)
226         {
227             buf.append("{Component-Path : ");
228             buf.append("[null]}");
229             return buf.toString();
230         }
231 
232         getPathToComponent(component, buf);
233 
234         buf.insert(0, "{Component-Path : ");
235         buf.append("}");
236 
237         return buf.toString();
238     }
239 
240     private void getPathToComponent(UIComponent component, StringBuffer buf)
241     {
242         if (component == null)
243         {
244             return;
245         }
246 
247         StringBuffer intBuf = new StringBuffer();
248 
249         intBuf.append("[Class: ");
250         intBuf.append(component.getClass().getName());
251         if (component instanceof UIViewRoot)
252         {
253             intBuf.append(",ViewId: ");
254             intBuf.append(((UIViewRoot) component).getViewId());
255         }
256         else
257         {
258             intBuf.append(",Id: ");
259             intBuf.append(component.getId());
260         }
261         intBuf.append("]");
262 
263         buf.insert(0, intBuf.toString());
264 
265         getPathToComponent(component.getParent(), buf);
266     }
267     
268     static StringBuilder _getSharedStringBuilder(FacesContext facesContext)
269     {
270         Map<Object, Object> attributes = facesContext.getAttributes();
271 
272         StringBuilder sb = (StringBuilder) attributes.get(_STRING_BUILDER_KEY);
273 
274         if (sb == null)
275         {
276             sb = new StringBuilder();
277             attributes.put(_STRING_BUILDER_KEY, sb);
278         }
279         else
280         {
281 
282             // clear out the stringBuilder by setting the length to 0
283             sb.setLength(0);
284         }
285 
286         return sb;
287     }
288 
289     //-------------- END TAKEN FROM UICOMPONENTBASE ------------------
290     
291 
292     private static Map<String, UIComponent> facets = new HashMap<String, UIComponent>()
293     {
294 
295         @Override
296         public void putAll(Map<? extends String, ? extends UIComponent> map)
297         {
298             // do nothing
299         }
300 
301         @Override
302         public UIComponent put(String name, UIComponent value)
303         {
304             return null;
305         }
306     };
307 
308     private UIComponent parent;
309     
310     //private _ComponentAttributesMap attributesMap;
311 
312     @Override
313     public Map<String, Object> getAttributes()
314     {
315         // Since all components extending UILeaf are only transient references
316         // to text or instructions, we can do the following simplifications:  
317         // 1. Don't use reflection to retrieve properties, because it will never
318         // be done
319         // 2. Since the only key that will be saved here is MARK_ID, we can create
320         // a small map of size 2. In practice, this will prevent create a lot of
321         // maps, like the ones used on state helper
322         return this;
323     }
324 
325     @Override
326     public void clearInitialState()
327     {
328        //this component is transient, so it can be marked, because it does not have state!
329     }
330 
331     @Override
332     public boolean initialStateMarked()
333     {
334         //this component is transient, so it can be marked, because it does not have state!
335         return false;
336     }
337 
338     @Override
339     public void markInitialState()
340     {
341         //this component is transient, so no need to do anything
342     }
343 
344     
345     @Override
346     public void processEvent(ComponentSystemEvent event)
347             throws AbortProcessingException
348     {
349         //Do nothing, because UILeaf will not need to handle "binding" property
350     }
351 
352     @Override
353     @SuppressWarnings("deprecation")
354     public ValueBinding getValueBinding(String binding)
355     {
356         return null;
357     }
358 
359     @Override
360     @SuppressWarnings("deprecation")
361     public void setValueBinding(String name, ValueBinding binding)
362     {
363         // do nothing
364     }
365 
366     @Override
367     public ValueExpression getValueExpression(String name)
368     {
369         return null;
370     }
371 
372     @Override
373     public void setValueExpression(String name, ValueExpression arg1)
374     {
375         // do nothing
376     }
377 
378     public String getFamily()
379     {
380         return "facelets.LiteralText";
381     }
382 
383     @Override
384     public UIComponent getParent()
385     {
386         return this.parent;
387     }
388 
389     @Override
390     public void setParent(UIComponent parent)
391     {
392         this.parent = parent;
393     }
394 
395     @Override
396     public boolean isRendered()
397     {
398         return true;
399     }
400 
401     @Override
402     public void setRendered(boolean rendered)
403     {
404         // do nothing
405     }
406 
407     @Override
408     public String getRendererType()
409     {
410         return null;
411     }
412 
413     @Override
414     public void setRendererType(String rendererType)
415     {
416         // do nothing
417     }
418 
419     @Override
420     public boolean getRendersChildren()
421     {
422         return true;
423     }
424 
425     @Override
426     public List<UIComponent> getChildren()
427     {
428         List<UIComponent> children = Collections.emptyList();
429         return children;
430     }
431 
432     @Override
433     public int getChildCount()
434     {
435         return 0;
436     }
437 
438     @Override
439     public UIComponent findComponent(String id)
440     {
441         return null;
442     }
443 
444     @Override
445     public Map<String, UIComponent> getFacets()
446     {
447         return facets;
448     }
449 
450     @Override
451     public int getFacetCount()
452     {
453         return 0;
454     }
455 
456     @Override
457     public UIComponent getFacet(String name)
458     {
459         return null;
460     }
461 
462     @SuppressWarnings("unchecked")
463     @Override
464     public Iterator<UIComponent> getFacetsAndChildren()
465     {
466         // Performance: Collections.emptyList() is Singleton,
467         // but .iterator() creates new instance of AbstractList$Itr every invocation, because
468         // emptyList() extends AbstractList.  Therefore we cannot use Collections.emptyList() here. 
469         return EmptyIterator.INSTANCE;
470     }
471 
472     @Override
473     public void broadcast(FacesEvent event) throws AbortProcessingException
474     {
475         // do nothing
476     }
477 
478     @Override
479     public void decode(FacesContext faces)
480     {
481         // do nothing
482     }
483 
484     @Override
485     public void encodeBegin(FacesContext faces) throws IOException
486     {
487         // do nothing
488     }
489 
490     @Override
491     public void encodeChildren(FacesContext faces) throws IOException
492     {
493         // do nothing
494     }
495 
496     @Override
497     public void encodeEnd(FacesContext faces) throws IOException
498     {
499         // do nothing
500     }
501 
502     @Override
503     public void encodeAll(FacesContext faces) throws IOException
504     {
505         this.encodeBegin(faces);
506     }
507 
508     @Override
509     protected void addFacesListener(FacesListener faces)
510     {
511         // do nothing
512     }
513 
514     @Override
515     protected FacesListener[] getFacesListeners(Class faces)
516     {
517         return null;
518     }
519 
520     @Override
521     protected void removeFacesListener(FacesListener faces)
522     {
523         // do nothing
524     }
525 
526     @Override
527     public void queueEvent(FacesEvent event)
528     {
529         // do nothing
530     }
531 
532     @Override
533     public void processRestoreState(FacesContext faces, Object state)
534     {
535         // do nothing
536     }
537 
538     @Override
539     public void processDecodes(FacesContext faces)
540     {
541         // do nothing
542     }
543 
544     @Override
545     public void processValidators(FacesContext faces)
546     {
547         // do nothing
548     }
549 
550     @Override
551     public void processUpdates(FacesContext faces)
552     {
553         // do nothing
554     }
555 
556     @Override
557     public Object processSaveState(FacesContext faces)
558     {
559         return null;
560     }
561 
562     @Override
563     protected FacesContext getFacesContext()
564     {
565         return FacesContext.getCurrentInstance();
566     }
567 
568     @Override
569     protected Renderer getRenderer(FacesContext faces)
570     {
571         return null;
572     }
573 
574     public Object saveState(FacesContext faces)
575     {
576         return null;
577     }
578 
579     public void restoreState(FacesContext faces, Object state)
580     {
581         // do nothing
582     }
583 
584     public boolean isTransient()
585     {
586         return true;
587     }
588 
589     public void setTransient(boolean tranzient)
590     {
591         // do nothing
592     }
593 
594     @Override
595     public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
596             throws FacesException
597     {
598         //this component will never be a target for a callback, so always return false.
599         return false;
600     }
601 
602     @Override
603     public boolean visitTree(VisitContext context, VisitCallback callback)
604     {
605         // the visiting is complete and it shouldn't affect the visiting of the other
606         // children of the parent component, therefore return false
607         return false;
608     }
609 
610     //-------------- START ATTRIBUTE MAP IMPLEMENTATION ----------------
611 
612     private Map<String, Object> _attributes = null;
613     private String _markCreated = null;
614 
615     public void setMarkCreated(String markCreated)
616     {
617         _markCreated = markCreated;
618     }
619 
620     public int size()
621     {
622         return _attributes == null ? 0 : _attributes.size();
623     }
624          
625     public void clear()
626     {
627         if (_attributes != null)
628         {
629          _attributes.clear();
630         _markCreated = null;
631         }
632     }
633          
634     public boolean isEmpty()
635     {
636         if (_markCreated == null)
637         {
638             return _attributes == null ? false : _attributes.isEmpty();
639         }
640         else
641         {
642             return false;
643         }
644     }
645          
646     public boolean containsKey(Object key)
647     {
648         checkKey(key);
649 
650         if (ComponentSupport.MARK_CREATED.equals(key))
651         {
652             return _markCreated != null;
653         }
654         else
655         {
656             return (_attributes == null ? false :_attributes.containsKey(key));
657         }
658     }
659          
660     public boolean containsValue(Object value)
661     {
662         if (_markCreated != null && _markCreated.equals(value))
663         {
664             return true;
665         }
666         return (_attributes == null) ? false : _attributes.containsValue(value);
667     }
668 
669     public Collection<Object> values()
670     {
671         return getUnderlyingMap().values();
672     }
673 
674     public void putAll(Map<? extends String, ? extends Object> t)
675     {
676         for (Map.Entry<? extends String, ? extends Object> entry : t.entrySet())
677         {
678             put(entry.getKey(), entry.getValue());
679         }
680     }
681 
682     public Set<Entry<String, Object>> entrySet()
683     {
684         return getUnderlyingMap().entrySet();
685     }
686 
687     public Set<String> keySet()
688     {
689         return getUnderlyingMap().keySet();
690     }
691 
692     public Object get(Object key)
693     {
694         checkKey(key);
695 
696         if ("rendered".equals(key))
697         {
698             return true;
699         }
700         if ("transient".equals(key))
701         {
702             return true;
703         }
704         if (ComponentSupport.MARK_CREATED.equals(key))
705         {
706             return _markCreated;
707         }
708         return (_attributes == null) ? null : _attributes.get(key);
709     }
710 
711     public Object remove(Object key)
712     {
713         checkKey(key);
714 
715         if (ComponentSupport.MARK_CREATED.equals(key))
716         {
717             _markCreated = null;
718         }
719          return (_attributes == null) ? null : _attributes.remove(key);
720     }
721          
722     public Object put(String key, Object value)
723     {
724         checkKey(key);
725 
726         if (ComponentSupport.MARK_CREATED.equals(key))
727         {
728             String old = _markCreated;
729             _markCreated = (String) value;
730             return old;
731         }
732         return getUnderlyingMap().put(key, value);
733     }
734 
735     private void checkKey(Object key)
736     {
737         if (key == null)
738         {
739             throw new NullPointerException("key");
740         }
741         if (!(key instanceof String))
742         {
743             throw new ClassCastException("key is not a String");
744         }
745     }
746 
747     Map<String, Object> getUnderlyingMap()
748     {
749         if (_attributes == null)
750         {
751             _attributes = new HashMap<String, Object>(2,1);
752         }
753         return _attributes;
754     }
755 
756 }