/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.faces.generate; import com.sun.faces.config.beans.ComponentBean; import com.sun.faces.config.beans.FacesConfigBean; import com.sun.faces.config.beans.PropertyBean; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Iterator; /** * This class is used to generate the AttributeManager, which is used * by mainly Renderers to render passthrough attribues (via * RenderKitUtils#renderPassThruAttributes) * * <p> * The AttributeManager mainly holds a [component name/attributes] map, and the code to * create that map is the main things this generator generates. * * Prior to this generator, Mojarra maintained a static list of passthrough attributes that would * be applied to all components when rendered. The main problem with doing this is that some components * have a very small list of passthrough attributes and so we'd waste cycles processing the generic list. * * To alleviate this issue: * *<pre> * 1. Added a new code generator, AttributeManagerGenerator, which will * leverage the xml metadata in jsf-api/doc to generate a class which * has knowledge of which components have which pass through attributes. * This generated class will expose a method that will allow renderers * to lookup the set of passthrough attributes appropriate to the component * they handle. * 2. Update all renderers that render passthrough attributes to store * the attribute lookup result in a static variable * 3. Remove the RenderKitUtil.renderPassThruAttributes() methods containing * excludes. Rely on the generator to provide the correct attributes. * 4. Remove all the logic (sorting, verifying) associated with the excludes * 5. Update the RenderKitUtils.shouldRenderAttribute() logic to check * if the value to be rendered is a String (as most are). If it is, return * true and bypass the remainder of the checks. *</pre> * * <p> * After profiling RenderKitUtils.renderPassThruAttributes has dropped quite in * terms of cpu usage compared to the previous implementation. * * <p> * The following shows an example of this generated Map: * * <code> * <pre> * private static Map<String,Attribute[]> ATTRIBUTE_LOOKUP=CollectionsUtils.<String,Attribute[]>map() * .add("CommandButton",ar( * attr("accesskey") * ,attr("alt") * ,attr("dir") * ,attr("lang") * ,attr("onblur","blur") * ... * </pre> * </code> * * */ public class AttributeManagerGenerator extends AbstractGenerator { private static final String TARGET_PACKAGE = "com.sun.faces.renderkit"; private static final String TARGET_CLASSNAME = "AttributeManager"; private PropertyManager manager; private List<String> imports; // ------------------------------------------------------------ Constructors public AttributeManagerGenerator(PropertyManager manager) { this.manager = manager; } // ------------------------------------------ Methods from AbstractGenerator public void generate(FacesConfigBean configBean) { try { CodeWriter writer = getCodeWriter(); writeCopyright(writer); writePackage(writer); writeImports(writer); writeClassDocumentation(writer); writeClassDeclaration(writer); writeClassBody(writer, configBean); writeClassEnd(writer); } catch (Exception e) { throw new RuntimeException(e); } } // ------------------------------------------------------------- Main Method public static void main(String[] args) throws Exception { PropertyManager propManager = PropertyManager.newInstance(args[0]); Generator generator = new AttributeManagerGenerator(propManager); generator.generate(GeneratorUtil.getConfigBean(args[1])); } // --------------------------------------------------------- Private Methods private CodeWriter getCodeWriter() throws IOException { FileWriter fWriter = new FileWriter(new File(getClassPackageDirectory(), TARGET_CLASSNAME + ".java")); return new CodeWriter(fWriter); } private File getClassPackageDirectory() { String packagePath = TARGET_PACKAGE.replace('.', File.separatorChar); File packageDir = new File(getBaseOutputDirectory(), packagePath); if (!packageDir.exists()) { packageDir.mkdirs(); } return packageDir; } // END getClassPackageDirectory private File getBaseOutputDirectory() { File outputDir = new File(System.getProperty("user.dir") + File.separatorChar + manager .getProperty(PropertyManager.BASE_OUTPUT_DIR)); if (!outputDir.exists()) { outputDir.mkdirs(); } return outputDir; } // END getBaseOutputDirectory private void addImport(String fullyQualClassName) { if (imports == null) { imports = new ArrayList<String>(); } imports.add(fullyQualClassName); } private void writeImports(CodeWriter writer) throws Exception { addImport("java.util.Map"); // addImport("java.util.HashMap"); // addImport("java.util.Collections"); addImport("static com.sun.faces.util.CollectionsUtils.*"); addImport("com.sun.faces.util.CollectionsUtils"); addImport("static com.sun.faces.renderkit.Attribute.*"); addImport("com.sun.faces.renderkit.Attribute"); Collections.sort(imports); for (Iterator i = imports.iterator(); i.hasNext();) { writer.writeImport((String) i.next()); } writer.write('\n'); } // END writeImports private void writeCopyright(CodeWriter writer) throws Exception { writer.writeBlockComment( manager.getProperty(PropertyManager.COPYRIGHT)); writer.write('\n'); } // END writeCopyright private void writePackage(CodeWriter writer) throws Exception { // Generate the package declaration writer.writePackage(TARGET_PACKAGE); writer.write('\n'); } // END writePackage private void writeClassDocumentation(CodeWriter writer) throws Exception { writer.writeJavadocComment("This class contains mappings between the standard components\n" + "and the passthrough attributes associated with them."); } // END writeClassDocumentation private void writeClassDeclaration(CodeWriter writer) throws Exception { // Generate the class declaration writer.writePublicClassDeclaration(TARGET_CLASSNAME, null, null, false, false); } // END writeClassDeclaration private void writeClassBody(CodeWriter writer, FacesConfigBean bean) throws Exception { writer.indent(); writer.fwrite("private static Map<String,Attribute[]> ATTRIBUTE_LOOKUP=CollectionsUtils.<String,Attribute[]>map()\n"); writer.indent(); ComponentBean[] components = bean.getComponents(); List<String> keys = new ArrayList<String>(); for (int i = 0, len = components.length; i < len; i ++) { ComponentBean comp = components[i]; if (!comp.getComponentClass().contains("Html")) { continue; } String type = comp.getRendererType(); if (type != null) { String family = comp.getBaseComponentType(); type = type.substring(type.lastIndexOf('.') + 1); family = family.substring(family.lastIndexOf('.') + 1); String key = family + type; PropertyBean[] props = comp.getProperties(); boolean attributeWritten = false; for (int ii = 0, llen = props.length; ii < llen; ii++) { PropertyBean aBean = props[ii]; if (aBean.isPassThrough()) { if ((key.contains("Radio") || "SelectManyCheckbox".equals(key)) && ("style".equals(aBean.getPropertyName()) || ("border".equals(aBean.getPropertyName())))) { continue; } if (attributeWritten) { writer.fwrite(",attr(\""); } else { keys.add(key); writer.fwrite(".add(\""); writer.write(key); writer.write("\",ar(\n"); writer.indent(); writer.fwrite("attr(\""); } writer.write(aBean.getPropertyName()); writer.write("\""); if (aBean.getBehaviors() != null && !aBean.getBehaviors().isEmpty()) { for (String behavior : aBean.getBehaviors()) { writer.write(",\""); String behaviorName; if (0 == behavior.length()) { behaviorName = aBean.getPropertyName(); // Strip leading "on" preffix. if (behaviorName.length() > 2 && behaviorName.startsWith("on")) { StringBuilder buffer = new StringBuilder( behaviorName.substring(2, 3) .toLowerCase()); buffer.append(behaviorName.substring(3)); behaviorName = buffer.toString(); } } else { behaviorName = behavior; } writer.write(behaviorName); writer.write("\""); } } writer.write(")\n"); attributeWritten = true; } if (key.contains("Button") && "onclick".equals(aBean.getPropertyName())) { // reset to the original state aBean.setPassThrough(false); } } if (attributeWritten) { writer.outdent(); writer.fwrite("))\n"); } } } writer.fwrite(".fix();\n"); // writer.fwrite("ATTRIBUTE_LOOKUP = Collections.unmodifiableMap(map);\n"); // writer.outdent(); // writer.fwrite("}\n\n"); writer.outdent(); writer.fwrite("public enum Key {\n"); writer.indent(); for (int i = 0, len = keys.size(); i < len; i++) { String key = keys.get(i); writer.fwrite(key.toUpperCase() + "(\"" + key + "\")"); if (i == (len - 1)) { writer.write(";\n"); } else { writer.write(",\n"); } } writer.fwrite("private String key;\n"); writer.fwrite("Key(String key) {\n"); writer.indent(); writer.fwrite("this.key = key;\n"); writer.outdent(); writer.fwrite("}\n"); writer.fwrite("public String value() {\n"); writer.indent(); writer.fwrite("return this.key;\n"); writer.outdent(); writer.fwrite("}\n"); writer.outdent(); writer.fwrite("}\n"); writer.write("\n\n"); writer.fwrite("public static Attribute[] getAttributes(Key key) {\n"); writer.indent(); writer.fwrite("return ATTRIBUTE_LOOKUP.get(key.value());\n"); writer.outdent(); writer.fwrite("}\n"); } private void writeClassEnd(CodeWriter writer) throws Exception { writer.outdent(); writer.fwrite("}\n"); writer.flush(); writer.close(); } }