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  
20  package org.apache.myfaces.tobago.renderkit.css;
21  
22  import org.apache.commons.collections.map.MultiKeyMap;
23  import org.apache.myfaces.tobago.component.SupportsMarkup;
24  import org.apache.myfaces.tobago.context.ClientProperties;
25  import org.apache.myfaces.tobago.context.Markup;
26  import org.apache.myfaces.tobago.context.Theme;
27  import org.apache.myfaces.tobago.internal.util.Deprecation;
28  import org.apache.myfaces.tobago.internal.util.StringUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  
35  /**
36   * Builds the CSS class attribute of tags.
37   * The names will be generated in a formal way, so generic name (and abbrevation) are possible.
38   * The class works like a factory, so caching will be possible.
39   * <p/>
40   * The default naming conventions allow these values:<br/>
41   *
42   * <ul>
43   * <li>tobago-&lt;rendererName></li>
44   * <li>tobago-&lt;rendererName>-markup-&lt;markupName></li>
45   * <li>tobago-&lt;rendererName>-&lt;subElement></li>
46   * <li>tobago-&lt;rendererName>-&lt;subElement>-markup-&lt;markupName></li>
47   * </ul>
48   *
49   * where
50   * <ul>
51   * <li>&lt;rendererName>, &lt;subElement> and &lt;markupName> must only contain ASCII-chars and -numbers</li>
52   * <li>&lt;rendererName> is the rendererType with a lower case char as first char</li>
53   * <li>&lt;subElement> is a sub element of the main tag in the output language (e.g. HTML)</li>
54   * <li>&lt;markupName> is the name of an existing markup</li>
55   * </ul>
56   * If the markup contains more than one name, there will be generated more than one output string.
57   * E.g.: UIIn with Markup [readonly, error] will get the class
58   * "tobago-in tobago-in-markup-readonly tobago-in-markup-error".
59   *
60   */
61  public final class Classes {
62  
63    private static final Logger LOG = LoggerFactory.getLogger(Classes.class);
64  
65    private static final MultiKeyMap CACHE = new MultiKeyMap();
66  
67    private final String stringValue;
68  
69    public static Classes create(final UIComponent component) {
70      return create(component, true, null, null, false);
71    }
72  
73    public static Classes create(final UIComponent component, final String sub) {
74      return create(component, true, sub, null, false);
75    }
76  
77    public static Classes create(final UIComponent component, final Markup explicit) {
78      return create(component, false, null, explicit, false);
79    }
80  
81    public static Classes create(final UIComponent component, final String sub, final Markup explicit) {
82      return create(component, false, sub, explicit, false);
83    }
84  
85    // XXX optimize synchronized
86    private static synchronized Classes create(
87        final UIComponent component, final boolean markupFromComponent, final String sub,
88        final Markup explicit, final boolean ignoreCheck) {
89      final String rendererName = StringUtils.uncapitalize(component.getRendererType());
90      final Markup markup = markupFromComponent ? ((SupportsMarkup) component).getCurrentMarkup() : explicit;
91      Classes value = (Classes) CACHE.get(rendererName, markup, sub);
92      if (value == null) {
93        value = new Classes(rendererName, markup, sub, ignoreCheck);
94        CACHE.put(rendererName, markup, sub, value);
95        if (LOG.isDebugEnabled()) {
96          LOG.debug("Element added (size={}) to cache (renderName='{}', markup='{}', sub='{}')",
97              CACHE.size(), rendererName, markup, sub);
98        }
99      }
100     return value;
101   }
102 
103   private Classes(final String rendererName, final Markup markup, final String sub, final boolean ignoreMarkupCheck) {
104 
105     assert sub == null || StringUtils.isAlphanumeric(sub) : "Invalid sub element name: '" + sub + "'";
106 
107     // These values are statistically tested length of the html class attribute
108     final StringBuilder builder = new StringBuilder(markup != null ? 80 : 32);
109     builder.append("tobago-");
110     builder.append(rendererName);
111     if (sub != null) {
112       builder.append('-');
113       builder.append(sub);
114     }
115     if (markup != null) {
116       final Theme theme = ClientProperties.getInstance(FacesContext.getCurrentInstance()).getTheme();
117       for (final String markupString : markup) {
118         if (ignoreMarkupCheck || theme.getRenderersConfig().isMarkupSupported(rendererName, markupString)) {
119           builder.append(' ');
120           builder.append("tobago-");
121           builder.append(rendererName);
122           if (sub != null) {
123             builder.append('-');
124             builder.append(sub);
125           }
126           builder.append("-markup-");
127           builder.append(markupString);
128         } else if ("none".equals(markupString)) {
129           Deprecation.LOG.warn("Markup 'none' is deprecated, please use a NULL pointer instead.");
130         } else {
131           LOG.warn("Ignoring unknown markup='" + markupString + "' for rendererName='" + rendererName + "'");
132         }
133       }
134     }
135     this.stringValue = builder.toString();
136   }
137 
138   public String getStringValue() {
139     return stringValue;
140   }
141 
142   /** @deprecated This workaround will be removed later */
143   @Deprecated
144   public static String requiredWorkaround(final UIComponent component) {
145     final String rendererName = StringUtils.uncapitalize(component.getRendererType());
146     return "tobago-" + rendererName + "-markup-required";
147   }
148 
149   /**
150    * @deprecated This workaround will be removed later
151    */
152   @Deprecated
153   public static Classes createWorkaround(
154       final String rendererName, final String sub, final Markup explicit) {
155     return new Classes(rendererName, explicit, sub, false);
156   }
157 
158   /**
159    * @deprecated This workaround will be removed later
160    */
161   @Deprecated
162   public static Classes createWorkaround(
163       final String rendererName, final Markup explicit) {
164     return new Classes(rendererName, explicit, null, false);
165   }
166 
167 }