dev@javaserverfaces.java.net

Re: Components opting into attributesThatAreSet feature

From: Mark Collette <mark.collette_at_icesoft.com>
Date: Thu, 26 Feb 2009 10:31:03 -0700

Sorry, I've had my modification to HtmlComponentGenerator.java sitting
here for a while, as I've been laboriously modifying our components to
make use of the feature, so that I can properly test that everything
works well together. I also wanted to test that the combination of
change plus fix doesn't slow down the stock JSF scenario, but our
testing people are a bit busy with our imminent release. Perhaps you
guys can run any pre-existing tests of yours to verify this? The only
modification is at the bottom of the file, where I've swapped in a new
block of code for generating the handleAttribute(String,Object) methods.

- Mark Collette


Ryan Lubke wrote:

> On 2/10/09 11:10 AM, Mark Collette wrote:
>
>> Without any changes to Mojarra, I was able to update our rendering of
>> stock h: components to use attributesThatAreSet, which saw, in one
>> test application, a reduction of 8.4% in the time to render, and a
>> reduction of 7.7% in the time it takes to do a server push lifecycle!
>
> Hey Mark,
>
> Any update on this?
>
> Thanks,
> -rl
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_javaserverfaces.dev.java.net
> For additional commands, e-mail: dev-help_at_javaserverfaces.dev.java.net
>



/*
 * $Id: HtmlComponentGenerator.java,v 1.27.4.6 2008/09/25 21:15:55 rlubke Exp $
 */

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. 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.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [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 java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.faces.config.beans.ComponentBean;
import com.sun.faces.config.beans.DescriptionBean;
import com.sun.faces.config.beans.FacesConfigBean;
import com.sun.faces.config.beans.PropertyBean;
import com.sun.faces.util.ToolsUtil;


/**
 * <p>Generate concrete HTML component classes. Classes will be generated for
 * each <code>&lt;component&gt;</code> element in the specified configuration
 * file whose absolute class name is in package <code>javax.faces.component.html</code>.</p>
 * <p/>
 * <p>This application requires the following command line options:</p> <ul>
 * <li><strong>--config</strong> Absolute pathname to an input configuration
 * file that will be parsed by the <code>parse()</code> method.</li>
 * <li><strong>--copyright</strong> Absolute pathname to a file containing the
 * copyright material for the top of each Java source file.</li>
 * <li><strong>--dir</strong> Absolute pathname to the directory into which
 * generated Java source code will be created.</li> <li><strong>--dtd</strong>
 * Pipe delimited list of public identifiers and absolute pathnames to files
 * containing the DTDs used to validate the input configuration files.
 * PRECONDITION: The list is the sequence: <public id>|<dtd path>|<public
 * id>|<dtd path>...</li> </ul>
 */

public class HtmlComponentGenerator extends AbstractGenerator {


    // -------------------------------------------------------- Static Variables


    private static final Logger logger = Logger.getLogger(ToolsUtil.FACES_LOGGER +
            ToolsUtil.GENERATE_LOGGER, ToolsUtil.TOOLS_LOG_STRINGS);

    // The component configuration bean for the component class to be generated
    private ComponentBean cb;


    // Base object of the configuration beans
    private FacesConfigBean configBean;

    // List of relevant properties for the component class to be generated
    private List<PropertyBean> properties;

    // The Writer for each component class to be generated
    private CodeWriter writer;

    private PropertyManager propManager;

    // ------------------------------------------------------------ Constructors


    public HtmlComponentGenerator(PropertyManager propManager) {

        this.propManager = propManager;

    }


    // ---------------------------------------------------------- Public Methods


    /**
     * <p>Generate the concrete HTML component class based on the current
     * component configuration bean.</p>
     */
    public void generate(FacesConfigBean configBean) {

        this.configBean = configBean;

        try {
            generateClasses();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


    // ---------------------------------------------------------- Private Method

    private void generateClasses() throws Exception {

        final String compPackage = "javax.faces.component.html";

        // Component generator doesn't use the TARGET_PACKAGE property
        String packagePath = compPackage.replace('.', File.separatorChar);
        File dir = new File(System.getProperty("user.dir") +
            File.separatorChar +
            propManager.getProperty(PropertyManager.BASE_OUTPUT_DIR) +
            File.separatorChar + packagePath);
        ComponentBean[] cbs = configBean.getComponents();
        for (ComponentBean cb1 : cbs) {

            String componentClass = cb1.getComponentClass();
            if (componentClass.startsWith(compPackage)) {
                cb = cb1;

                if (logger.isLoggable(Level.FINE)) {
                    logger.log(Level.FINE,
                               "Generating concrete HTML component class " +
                               cb.getComponentClass());
                }

                // Initialize all per-class static variables
                properties = new ArrayList<PropertyBean>();

                if (!dir.exists()) {
                    dir.mkdirs();
                }

                String fileName = cb.getComponentClass();
                fileName = fileName.substring(fileName.lastIndexOf('.') + 1) +
                           ".java";
                File file = new File(dir, fileName);
                writer = new CodeWriter(new FileWriter(file));
                // Generate the various portions of each class
                prefix();
                properties();
                suffix();

                // Flush and close the Writer for this class
                writer.flush();
                writer.close();
            }


        }
    }


    /**
     * <p>Generate the prefix for this component class, down to (and including)
     * the class declaration.</p>
     */
    private void prefix() throws Exception {

        // Acquire the config bean for our base component
        ComponentBean base = configBean.getComponent(cb.getBaseComponentType());
        if (base == null) {
            throw new IllegalArgumentException("No base component type for '" +
                cb.getComponentType() + "'");
        }

        // Generate the copyright information
        writer.writeBlockComment(
            propManager.getProperty(PropertyManager.COPYRIGHT));

        // Generate the package declaration
        writer.writePackage("javax.faces.component.html");

        writer.write('\n');

        // Generate the imports
        writer.writeImport("java.io.IOException");
        writer.writeImport("java.util.List");
        writer.writeImport("java.util.ArrayList");
        writer.writeImport("java.util.Arrays");
        writer.write('\n');
        writer.writeImport("javax.faces.context.FacesContext");
        writer.writeImport("javax.el.MethodExpression");
        writer.writeImport("javax.el.ValueExpression");
        writer.write("\n\n");

        writer.writeBlockComment("******* GENERATED CODE - DO NOT EDIT *******");
        writer.write("\n\n");

        // Generate the class JavaDocs (if any)
        DescriptionBean db = cb.getDescription("");
        String rendererType = cb.getRendererType();

        String description = null;
        if (db != null) {
            description = db.getDescription().trim();
        }

        if (rendererType != null) {
            if (description == null) {
                description = "";
            }
            description +=
            "\n<p>By default, the <code>rendererType</code> property must be set to \"<code>" +
                rendererType +
                "</code>\".\nThis value can be changed by calling the <code>setRendererType()</code> method.</p>\n";
        }

        if (description != null && description.length() > 0) {
            writer.writeJavadocComment(description);
        }

        // Generate the class declaration
        writer.writePublicClassDeclaration(shortName(cb.getComponentClass()),
                                           base.getComponentClass(),
                                           null, false, false);

        writer.write("\n\n");

        writer.indent();

        writer.fwrite("private static final String OPTIMIZED_PACKAGE = \"javax.faces.component.\";\n\n");

        // Generate the constructor

        writer.fwrite("public ");
        writer.write(shortName(cb.getComponentClass()));
        writer.write("() {\n");
        writer.indent();
        writer.fwrite("super();\n");
        if (rendererType != null) {
            writer.fwrite("setRendererType(\"");
            writer.write(rendererType);
            writer.write("\");\n");
        }

        PropertyBean[] pbs = cb.getProperties();
        for (PropertyBean pb : pbs) {
            if (pb.isPassThrough() && pb.getDefaultValue() != null) {
                writer.fwrite("handleAttribute(\"");
                writer.write(pb.getPropertyName());
                writer.write("\", ");
                writer.write(pb.getDefaultValue());
                writer.write(");\n");

            }
        }

        writer.outdent();
        writer.fwrite("}\n\n\n");

        // Generate the manifest constant for the component type
        writer.writeJavadocComment(
            "<p>The standard component type for this component.</p>\n");
        writer.fwrite("public static final String COMPONENT_TYPE = \"");
        writer.write(cb.getComponentType());
        writer.write("\";\n\n\n");

    }


    /**
     * <p>Generate the property instance variable, and getter and setter
     * methods, for all non-duplicate properties of this component class.</p>
     */
    private void properties() throws Exception {

        ComponentBean base = configBean.getComponent(cb.getBaseComponentType());
        PropertyBean[] pbs = cb.getProperties();
        for (PropertyBean pb : pbs) {

            // Should we generate this property?
            if (base.getProperty(pb.getPropertyName()) != null) {
                if (logger.isLoggable(Level.FINER)) {
                    logger.log(Level.FINER, "Skipping base class property '" +
                                            pb.getPropertyName() + "'");
                }
                continue;
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE,
                           "Generating property variable/getter/setter for '" +
                           pb.getPropertyName() + "'");
            }
            properties.add(pb);
            String type = pb.getPropertyClass();
            String var = mangle(pb.getPropertyName());

            // Generate the instance variable
            writer.fwrite("private ");
            writer.write(primitive(type) ? GeneratorUtil.convertToObject(type) : type);
            writer.write(' ');
            writer.write(var);
            writer.write(";\n");

            // Document getter method
            String description = "<p>Return the value of the <code>" +
                                 pb.getPropertyName() + "</code> property.</p>";
            DescriptionBean db = pb.getDescription("");
            if (db != null) {
                String temp = db.getDescription().trim();
                if (temp != null && temp.length() > 0) {
                    description += '\n' + "<p>Contents: " + temp;
                }
            }
            writer.writeJavadocComment(description.trim());

            // Generate the getter method
            writer.fwrite("public ");
            writer.write(type);
            if ("boolean".equals(type)) {
                writer.write(" is");
            } else {
                writer.write(" get");
            }
            writer.write(capitalize(pb.getPropertyName()));
            writer.write("() {\n");
            writer.indent();
            writer.fwrite("if (null != this.");
            writer.write(var);
            writer.write(") {\n");
            writer.indent();
            writer.fwrite("return this.");
            writer.write(var);
            writer.write(";\n");
            writer.outdent();
            writer.fwrite("}\n");
            writer.fwrite("ValueExpression _ve = getValueExpression(\"");
            writer.write(pb.getPropertyName());
            writer.write("\");\n");
            writer.fwrite("if (_ve != null) {\n");
            writer.indent();
            writer.fwrite("return (");
            writer.write(primitive(type)
                         ? GeneratorUtil.convertToObject(type)
                         : type);
            writer.write(
                  ") _ve.getValue(getFacesContext().getELContext());\n");
            writer.outdent();
            writer.fwrite("} else {\n");
            writer.indent();
            if (primitive(type) || (pb.getDefaultValue() != null)) {
                writer.fwrite("return ");
                writer.write(pb.getDefaultValue() != null
                             ? pb.getDefaultValue()
                             : TYPE_DEFAULTS.get(type));
                writer.write(";\n");
            } else {
                writer.fwrite("return null;\n");
            }
            writer.outdent();
            writer.fwrite("}\n");
            writer.outdent();
            writer.fwrite("}\n\n");

            // Generate the setter method
            writer.writeJavadocComment("<p>Set the value of the <code>"
                                       +
                                       pb.getPropertyName()
                                       + "</code> property.</p>\n");

            writer.fwrite("public void set");
            writer.write(capitalize(pb.getPropertyName()));
            writer.write("(");
            writer.write(type);
            writer.write(' ');
            writer.write(var);
            writer.write(") {\n");
            writer.indent();
            writer.fwrite("this.");
            writer.write(var);
            writer.write(" = ");
            writer.write(var);
            writer.write(";\n");
            if ((pb.isPassThrough() && pb.getDefaultValue() == null)
                  || (cb.getComponentClass().contains("HtmlCommandButton")
                        && "onclick".equals(pb.getPropertyName()))) {
                writer.fwrite("handleAttribute(\"");
                writer.write(pb.getPropertyName());
                 writer.write("\", ");
                writer.write(var);
                writer.write(");\n");
            }

            writer.outdent();
            writer.fwrite("}\n\n");

            // Generate spacing between properties
            writer.write("\n");

        }

    }


    /**
     * <p>Generate the suffix for this component class.</p>
     */
    private void suffix() throws Exception {

        writer.fwrite("private Object[] _values;\n\n");
        // Generate the saveState() method
        writer.fwrite("public Object saveState(FacesContext _context) {\n");
        writer.indent();
        writer.fwrite("if (_values == null) {\n");
        writer.indent();
        writer.fwrite("_values = new Object[");
        writer.write(String.valueOf((properties.size() + 1)));
        writer.write("];\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.fwrite("_values[0] = super.saveState(_context);\n");

        int n = 1; // Index into values array
        for (int i = 0; i < properties.size(); i++) {
            PropertyBean pb = properties.get(i);
            String name = mangle(pb.getPropertyName());
            writer.fwrite("_values[");
            writer.write(String.valueOf(n++));
            writer.write("] = ");
            writer.write(name);
            writer.write(";\n");
        }
        writer.fwrite("return _values;\n");
        writer.outdent();
        writer.write("}\n\n\n");

        // Generate the restoreState() method
        writer.fwrite(
            "public void restoreState(FacesContext _context, Object _state) {\n");
        writer.indent();
        writer.fwrite("_values = (Object[]) _state;\n");
        writer.fwrite("super.restoreState(_context, _values[0]);\n");
        n = 1;
        for (int i = 0, size = properties.size(); i < size; i++) {
            PropertyBean pb = properties.get(i);
            String name = mangle(pb.getPropertyName());
            String type = pb.getPropertyClass();
            writer.fwrite("this.");
            writer.write(name);
            writer.write(" = ");

            writer.write("(");
            writer.write(primitive(type)
                         ? GeneratorUtil.convertToObject(type)
                         : type);
            writer.write(") _values[");
            writer.write(String.valueOf(n++));
            writer.write("]");

            writer.write(";\n");

        }
        writer.outdent();
        writer.fwrite("}\n\n\n");

/*
        writer.fwrite("private void handleAttribute(String name, Object value) {\n");
        writer.indent();
        writer.fwrite("List<String> setAttributes = null;\n");
        writer.fwrite("String cname = this.getClass().getName();\n");
        writer.fwrite("if (cname != null && cname.startsWith(OPTIMIZED_PACKAGE)) {\n");
        writer.indent();
        writer.fwrite("setAttributes = (List<String>) this.getAttributes().get(\"javax.faces.component.UIComponentBase.attributesThatAreSet\");\n");
        writer.fwrite("if (setAttributes == null) {\n");
        writer.indent();
        writer.fwrite("setAttributes = new ArrayList<String>(6);\n");
        writer.fwrite("this.getAttributes().put(\"javax.faces.component.UIComponentBase.attributesThatAreSet\", setAttributes);\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.fwrite("if (value == null) {\n");
        writer.indent();
        writer.fwrite("setAttributes.remove(name);\n");
        writer.outdent();
        writer.fwrite("} else if (!setAttributes.contains(name)) {\n");
        writer.indent();
        writer.fwrite("setAttributes.add(name);\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("}\n\n");
*/
        writer.fwrite("private void handleAttribute(String name, Object value) {\n");
        writer.indent();
        writer.fwrite("List<String> setAttributes = (List<String>) this.getAttributes().get(\"javax.faces.component.UIComponentBase.attributesThatAreSet\");\n");
        writer.fwrite("if (setAttributes == null) {\n");
        writer.indent();
        writer.fwrite("String cname = this.getClass().getName();\n");
        writer.fwrite("if (cname != null && cname.startsWith(OPTIMIZED_PACKAGE)) {\n");
        writer.indent();
        writer.fwrite("setAttributes = new ArrayList<String>(6);\n");
        writer.fwrite("this.getAttributes().put(\"javax.faces.component.UIComponentBase.attributesThatAreSet\", setAttributes);\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.fwrite("if (setAttributes != null) {\n");
        writer.indent();
        writer.fwrite("if (value == null) {\n");
        writer.indent();
        writer.fwrite("ValueExpression ve = getValueExpression(name);\n");
        writer.fwrite("if (ve == null) {\n");
        writer.indent();
        writer.fwrite("setAttributes.remove(name);\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("} else if (!setAttributes.contains(name)) {\n");
        writer.indent();
        writer.fwrite("setAttributes.add(name);\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("}\n");
        writer.outdent();
        writer.fwrite("}\n\n");

        // Generate the ending of this class
        writer.outdent();
        writer.write("}\n");

    }



    // ------------------------------------------------------------- Main Method


    public static void main(String[] args) throws Exception {
        PropertyManager propManager = PropertyManager.newInstance(args[0]);
        Generator generator = new HtmlComponentGenerator(propManager);
        generator.generate(GeneratorUtil.getConfigBean(args[1]));
    }

}