001 package org.apache.myfaces.tobago.renderkit.css; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one or more 005 * contributor license agreements. See the NOTICE file distributed with 006 * this work for additional information regarding copyright ownership. 007 * The ASF licenses this file to You under the Apache License, Version 2.0 008 * (the "License"); you may not use this file except in compliance with 009 * the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 import org.apache.commons.collections.map.MultiKeyMap; 021 import org.apache.commons.lang.StringUtils; 022 import org.apache.myfaces.tobago.component.SupportsMarkup; 023 import org.apache.myfaces.tobago.context.Markup; 024 import org.apache.myfaces.tobago.context.Theme; 025 import org.apache.myfaces.tobago.internal.util.Deprecation; 026 import org.apache.myfaces.tobago.util.VariableResolverUtils; 027 import org.slf4j.Logger; 028 import org.slf4j.LoggerFactory; 029 030 import javax.faces.component.UIComponent; 031 import javax.faces.context.FacesContext; 032 033 /** 034 * Builds the CSS class attribute of tags. 035 * The names will be generated in a formal way, so generic name (and abbrevation) are possible. 036 * The class works like a factory, so caching will be possible. 037 * <p/> 038 * The default naming conventions allow these values:<br/> 039 * 040 * <ul> 041 * <li>tobago-<rendererName></li> 042 * <li>tobago-<rendererName>-markup-<markupName></li> 043 * <li>tobago-<rendererName>-<subElement></li> 044 * <li>tobago-<rendererName>-<subElement>-markup-<markupName></li> 045 * </ul> 046 * 047 * where 048 * <ul> 049 * <li><rendererName>, <subElement> and <markupName> must only contain ASCII-chars and -numbers</li> 050 * <li><rendererName> is the rendererType with a lower case char as first char</li> 051 * <li><subElement> is a sub element of the main tag in the output language (e.g. HTML)</li> 052 * <li><markupName> is the name of an existing markup</li> 053 * </ul> 054 * If the markup contains more than one name, there will be generated more than one output string. 055 * E.g.: UIIn with Markup [readonly, error] will get the class 056 * "tobago-in tobago-in-markup-readonly tobago-in-markup-error". 057 * 058 */ 059 public final class Classes { 060 061 private static final Logger LOG = LoggerFactory.getLogger(Classes.class); 062 063 private static final MultiKeyMap CACHE = new MultiKeyMap(); 064 065 private final String stringValue; 066 067 public static Classes create(UIComponent component) { 068 return create(component, true, null, null, false); 069 } 070 071 public static Classes create(UIComponent component, String sub) { 072 return create(component, true, sub, null, false); 073 } 074 075 public static Classes create(UIComponent component, Markup explicit) { 076 return create(component, false, null, explicit, false); 077 } 078 079 public static Classes create(UIComponent component, String sub, Markup explicit) { 080 return create(component, false, sub, explicit, false); 081 } 082 083 // XXX optimize synchronized 084 private static synchronized Classes create( 085 UIComponent component, boolean markupFromComponent, String sub, Markup explicit, boolean ignoreCheck) { 086 final String rendererName = StringUtils.uncapitalize(component.getRendererType()); 087 final Markup markup = markupFromComponent ? ((SupportsMarkup) component).getCurrentMarkup() : explicit; 088 Classes value = (Classes) CACHE.get(rendererName, markup, sub); 089 if (value == null) { 090 value = new Classes(rendererName, markup, sub, ignoreCheck); 091 CACHE.put(rendererName, markup, sub, value); 092 if (LOG.isDebugEnabled()) { 093 LOG.debug("Element added (size={}) to cache (renderName='{}', markup='{}', sub='{}')", 094 new Object[] {CACHE.size(), rendererName, markup, sub}); 095 } 096 } 097 return value; 098 } 099 100 private Classes(String rendererName, Markup markup, String sub, boolean ignoreMarkupCheck) { 101 102 assert sub == null || StringUtils.isAlphanumeric(sub) : "Invalid sub element name: '" + sub + "'"; 103 104 // These values are statistically tested length of the html class attribute 105 StringBuilder builder = new StringBuilder(markup != null ? 80 : 32); 106 builder.append("tobago-"); 107 builder.append(rendererName); 108 if (sub != null) { 109 builder.append('-'); 110 builder.append(sub); 111 } 112 if (markup != null) { 113 Theme theme = VariableResolverUtils.resolveClientProperties(FacesContext.getCurrentInstance()).getTheme(); 114 for (String markupString : markup) { 115 if (ignoreMarkupCheck || theme.getRenderersConfig().isMarkupSupported(rendererName, markupString)) { 116 builder.append(' '); 117 builder.append("tobago-"); 118 builder.append(rendererName); 119 if (sub != null) { 120 builder.append('-'); 121 builder.append(sub); 122 } 123 builder.append("-markup-"); 124 builder.append(markupString); 125 } else if ("none".equals(markupString)) { 126 Deprecation.LOG.warn("Markup 'none' is deprecated, please use a NULL pointer instead."); 127 } else { 128 LOG.warn("Ignoring unknown markup='" + markupString + "' for rendererName='" + rendererName + "'"); 129 } 130 } 131 } 132 this.stringValue = builder.toString(); 133 } 134 135 public String getStringValue() { 136 return stringValue; 137 } 138 139 /** @deprecated This workaround will be removed later */ 140 @Deprecated 141 public static String requiredWorkaround(UIComponent component) { 142 final String rendererName = StringUtils.uncapitalize(component.getRendererType()); 143 return "tobago-" + rendererName + "-markup-required"; 144 } 145 146 /** 147 * @deprecated This workaround will be removed later 148 */ 149 @Deprecated 150 public static Classes createWorkaround(String rendererName, String sub, Markup explicit) { 151 return new Classes(rendererName, explicit, sub, false); 152 } 153 154 /** 155 * @deprecated This workaround will be removed later 156 */ 157 @Deprecated 158 public static Classes createWorkaround(String rendererName, Markup explicit) { 159 return new Classes(rendererName, explicit, null, false); 160 } 161 162 }