![JAR search and dependency download from the Maven repository](/logo.png)
com.jsftoolkit.gen.ComponentGenerator Maven / Gradle / Ivy
package com.jsftoolkit.gen;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.webapp.UIComponentELTag;
import com.jsftoolkit.base.ComponentHelp;
import com.jsftoolkit.gen.info.ClassInfo;
import com.jsftoolkit.gen.info.ComponentInfo;
import com.jsftoolkit.gen.info.ConstantInfo;
import com.jsftoolkit.gen.info.PropertyInfo;
import com.jsftoolkit.gen.info.TagInfo;
import com.jsftoolkit.utils.ClassUtils;
import com.jsftoolkit.utils.DeferedFileOutputStream;
import com.jsftoolkit.utils.Utils;
/**
* Generates basic components and tag handlers. This is possible because the
* entirety of a tag handler is get/set pairs and a call to setProperties and
* much of a {@link UIComponent} is getter/setter pairs.
*
* By generating this code, we assure that code is generated in a consistent and
* error free way.
*
* This class relies upon {@link ComponentInfo} to provide the necessary
* metadata about the classes to be generated.
*
* @author noah
*
*/
public class ComponentGenerator {
private final ComponentInfo info;
/**
* Create a new component generator.
*
* @param componentInfo
* the metadata to use
*/
public ComponentGenerator(final ComponentInfo componentInfo) {
super();
this.info = componentInfo;
}
/**
* Generates both component and tag handler to the default location.
*
* @throws Exception
*/
public void generateBoth() throws Exception {
generateComponent();
generateTagHandler();
}
/**
* Writes component and tag handler to the given stream.
*
* @param out
* @throws Exception
*/
public void generateBoth(PrintStream out) throws Exception {
generateComponent(out);
generateTagHandler(out);
}
/**
* Generates the component code, outputting to the default location. see
* {@link #defaultStream(String, String)}.
*
* @throws Exception
*/
public void generateComponent() throws Exception {
generateComponent(defaultComponentStream(info));
}
/**
* Creates the default stream for the renderer for the given component.
*
* @param info
* @return
* @throws IOException
*/
public static PrintStream defaultRendererStream(ComponentInfo info)
throws IOException {
return defaultStream(info.getRenderer().getPackage(), info
.getRenderer().getClassName());
}
/**
* Creates the default stream for the tag for the given component.
*
* @param info
* @return
* @throws IOException
*/
public static PrintStream defaultTagStream(ComponentInfo info)
throws IOException {
return defaultStream(info.getTag().getPackage(), info.getTag()
.getClassName());
}
/**
* Creates the default stream for the component code.
*
* @param info
* @return
* @throws IOException
*/
public static PrintStream defaultComponentStream(ComponentInfo info)
throws IOException {
return defaultStream(info.getPackage(), info.getClassName());
}
/**
* Produces a stream that will overwrite the given class in the generated
* sources directory.
*
* @param _package
* @param className
* @return the print stream to the file, or null if it cannot be created
* @throws IOException
* if there is an error creating the file
* @throws SecurityException
* if the file cannot be created for some reason
*/
public static PrintStream defaultStream(String _package, String className)
throws IOException, SecurityException {
File file = getFile(_package, className);
if (!file.exists()) {
if (!Utils.createFileAndParents(file)) {
return null;
}
}
System.out.println("Writing to: " + file);
return new PrintStream(new DeferedFileOutputStream(file));
}
/**
*
* @param _package
* @param className
* @return
*/
public static File getFile(String _package, String className) {
String prefix = System.getProperty("jsftoolkit.generator.basedir", "");
if (!Utils.isEmpty(prefix) && !prefix.endsWith(File.pathSeparator)) {
prefix += '/';
}
return new File(prefix + "target/generated-sources/components/"
+ _package.replaceAll("\\.", "/") + '/' + className + ".java");
}
public void generateComponent(PrintStream out) throws Exception {
Map properties = new HashMap(
info.getProperties());
// add the necessary imports
info.addImport(FacesContext.class);
info.addImport(ComponentHelp.class);
info.addImport(info.getSuperClass());
// declaration
printDeclaration(out, info, info.getSuperClass());
// pass through the property constants
for (Entry prop : properties.entrySet()) {
out.printf(" public static final String %s = \"%s\";\n\n", prop
.getKey(), prop.getValue().getName());
}
// remove the properties with a null class, since we can't give them
// getters/setters
Iterator> it = properties.entrySet()
.iterator();
while (it.hasNext()) {
if (it.next().getValue().getType() == null) {
it.remove();
}
}
// pass through the component constants
for (ConstantInfo constant : info.getConstants()) {
out.printf(" public static final String %s = \"%s\";\n\n",
constant.getName(), constant.getValue());
}
// write all the type constants, if they are defined
String defaultRendererType = info.getRendererType();
String componentFamily = info.getFamily();
String componentType = info.getType();
printConstant(out, "DEFAULT_RENDERER_TYPE", defaultRendererType);
printConstant(out, "COMPONENT_FAMILY", componentFamily);
printConstant(out, "COMPONENT_TYPE", componentType);
if (componentType == null && !info.isAbstract()) {
throw new IllegalArgumentException(
"Component must be abstract or specify COMPONENT_TYPE");
}
// declare the property fields
for (Entry prop : properties.entrySet()) {
PropertyInfo info = prop.getValue();
out.printf(" private %s _%s;\n\n", info.getType()
.getSimpleName(), info.getName());
}
// create the constructor and set the default renderer
if (defaultRendererType != null) {
out.printf(" public %s(){\n"
+ " setRendererType(DEFAULT_RENDERER_TYPE);\n"
+ " }\n\n", info.getClassName());
} else if (!info.isAbstract()) {
throw new IllegalArgumentException(
"Component must be abstract or specify DEFAULT_RENDERER_TYPE");
}
if (componentFamily != null) {
out.println(" public String getFamily() {\n"
+ " return COMPONENT_FAMILY;\n" + " }\n");
} else if (!info.isAbstract()) {
throw new IllegalArgumentException(
"Component must be abstract or specify COMPONENT_FAMILY");
}
// create getters and setters for the properties
for (Entry prop : properties.entrySet()) {
PropertyInfo info = prop.getValue();
Class> pClass = info.getType();
String capName = Utils.capitalize(info.getName());
String type = pClass.getSimpleName();
out.printf(" public %s %s%s() {\n", type, getGet(pClass),
capName);
out
.printf(
" return _%1$s = getAttribute(_%1$s, %2$s, %3$s);\n",
info.getName(), info.getDefaultValue(), prop
.getKey());
out.println(" }\n");
out.printf(" public void set%s(%s _%s) {\n", capName, type, info
.getName());
out.printf(" this._%s = _%1$s;\n", info.getName());
out.println(" }\n");
}
// save and restore state
StringBuilder saveState = new StringBuilder(
" public Object saveState(FacesContext context) {\n");
saveState.append(String.format(
" Object[] state = new Object[%d];\n"
+ " state[0] = super.saveState(context);\n",
properties.size() + 1));
StringBuilder restoreState = new StringBuilder(
" public void restoreState(FacesContext context, Object state) {\n"
+ " Object[] values = (Object[]) state;\n"
+ " super.restoreState(context, values[0]);\n");
int i = 0;
for (Entry prop : properties.entrySet()) {
i++;
PropertyInfo info = prop.getValue();
saveState.append(String.format(" state[%d] = this._%s;\n",
i, info.getName()));
String type = ClassUtils.box(info.getType()).getSimpleName();
restoreState.append(String.format(
" this._%s = (%s) values[%d];\n", info.getName(),
type, i));
}
out.println(saveState.append(" return state;\n" + " }\n")
.toString());
out.println(restoreState.append(" }\n").toString());
// write the helper functions
out
.println(" protected T getAttribute(T value, T defaultValue, String attribute) {\n"
+ " return ComponentHelp.getAttribute(value, defaultValue, attribute, this,\n"
+ " getFacesContext());\n"
+ " }\n\n"
+ " protected boolean getBooleanAttribute(Boolean value, String attribute) {\n"
+ " return getAttribute(value, false, attribute);\n"
+ " }\n");
// close the class
out.println('}');
}
protected static void printDeclaration(PrintStream out, ClassInfo info,
Class> superClass) {
// package declaration
out.printf("package %s;\n\n", info.getPackage());
for (Class c : info.getInterfaces()) {
info.addImport(c);
}
// see if we need to import the super class
if (superClass != null
&& !superClass.getPackage().getName().equals(info.getPackage())) {
info.addImport(superClass);
}
printImports(out, info.getImports());
printWarning(out);
out.print("public ");
if (info.isAbstract()) {
out.print("abstract ");
}
out.printf("class %s", info.getClassName());
// superclass
if (superClass != null) {
out.printf(" extends %s", superClass.getSimpleName());
}
// interfaces
Set> implement = info.getInterfaces();
if (implement != null && !implement.isEmpty()) {
Iterator> it = implement.iterator();
out.printf(" implements %s", it.next().getSimpleName());
while (it.hasNext()) {
out.print(", ");
out.print(it.next().getSimpleName());
}
}
out.println(" {\n");
}
/**
* Writes out a warning that this class is generated code and should not be
* edited directly.
*
* @param out
*/
protected static void printWarning(PrintStream out) {
out
.println("/**\n"
+ " * **GENERATED SOURCE**\n"
+ " * You may subclass this class, but should probably not edit it.\n"
+ " * See the JSF Toolkit homepage for more information.\n"
+ " */\n");
}
/**
* Prints a constant declaration.
*
* @param out
* @param name
* @param value
*/
protected void printConstant(PrintStream out, String name, String value) {
if (value == null) {
return;
}
out.printf(" public static final String %s = \"%s\";\n\n", name,
value);
}
/**
* @see #printImports(PrintStream, Set)
* @param out
* @param imports
*/
public static void printImports(PrintStream out, Class>... imports) {
printImports(out, Utils.asSet(imports));
}
/**
* Prints import statements for the given classes, filtering out java.lang.*
* imports. Imports are printed in alphabetical order by cannonical name.
*
* @param out
* @param imports
*/
public static void printImports(PrintStream out, Set> imports) {
List> list = new ArrayList>(imports);
Collections.sort(list, new Comparator() {
public int compare(Class o1, Class o2) {
return o1.getCanonicalName().compareTo(o2.getCanonicalName());
}
});
for (Class> class1 : list) {
String className = class1.getCanonicalName();
if (!className.startsWith("java.lang") && !class1.isPrimitive()) {
out.printf("import %s;\n", className);
}
}
out.println();
}
/**
* Writes the tag handler to the default location.
*
* @see #defaultTagStream(ComponentInfo)
* @see #generateTagHandler(PrintStream)
* @throws Exception
*/
public void generateTagHandler() throws Exception {
generateTagHandler(defaultTagStream(info));
}
/**
* Writes the tag handler to the given stream.
*
* @param out
* @throws Exception
*/
public void generateTagHandler(PrintStream out) throws Exception {
TagInfo tagInfo = info.getTag();
String componentPackage = info.getPackage();
String _package = tagInfo.getPackage();
String componentClass = info.getClassName();
Map properties = info.getProperties();
Set other = info.getRenderer().getAttribs();
tagInfo.addImport(ValueExpression.class);
tagInfo.addImport(UIComponent.class);
// if we're in a different package, we need to import the component.
if (!_package.equals(componentPackage) && componentPackage != null) {
out.printf("import %s.%s;\n\n", componentPackage, componentClass);
}
// print everything up to the open '{'
printDeclaration(out, tagInfo, tagInfo.getSuperClass());
// need a member value expression for each property.
Set names = getAllProperties(properties, other, info
.getSuperClass(), info.getTag().getSuperClass());
for (String attrib : names) {
out.printf(" private ValueExpression _%s;\n\n", attrib);
}
// implement setProperties
out
.println(" protected void setProperties(UIComponent component) {\n"
+ " super.setProperties(component);\n");
for (Entry prop : properties.entrySet()) {
out.printf(" setProperty(component, %s.%s, _%s);\n",
componentClass, prop.getKey(), prop.getValue().getName());
}
for (String attrib : other) {
out.printf(" setProperty(component, \"%s\", _%1$s);\n",
attrib);
}
out.println(" }\n");
// create the getters and setters
for (String attrib : names) {
out.printf(" public ValueExpression get%1$s() {\n"
+ " return %2$s;\n" + " }\n" + "\n"
+ " public void set%1$s(ValueExpression %2$s) {\n"
+ " this.%2$s = %2$s;\n" + " }\n\n", Utils
.capitalize(attrib), '_' + attrib);
}
// write the getter for component type and default renderer
if (!tagInfo.isAbstract()) {
out.printf(" public String getComponentType() {"
+ "\n return ");
if (componentClass != null) {
out.printf("%s.COMPONENT_TYPE", componentClass);
} else {
out.print("null");
}
out.println(";\n }");
out.printf(" public String getRendererType() {"
+ "\n return ");
if (componentClass != null) {
out.printf("%s.DEFAULT_RENDERER_TYPE", componentClass);
} else {
out.print("null");
}
out.println(";\n }");
}
out.println('}');
}
/**
*
* @param properties
* the already registered properties. Will not be modified.
* @param other
* other known property names no in properties.
* @param superClass
* the component super class (cannot be null)
* @param superTag
* the tag super class (cannot be null)
* @return the names of all the properties this tag handler needs to have a
* get/set pair for.
* @throws IntrospectionException
*/
protected Set getAllProperties(
Map properties, Set other,
Class extends UIComponent> superClass,
Class extends UIComponentELTag> superTag)
throws IntrospectionException {
Set set = new HashSet();
for (PropertyInfo info : properties.values()) {
set.add(info.getName());
}
set.addAll(other);
// add superclass properties that are not set by the super tag handler
Set handled = new HashSet();
for (PropertyDescriptor pd : Introspector.getBeanInfo(superTag)
.getPropertyDescriptors()) {
handled.add(pd.getName());
}
for (PropertyDescriptor pd : Introspector.getBeanInfo(superClass,
UIComponent.class).getPropertyDescriptors()) {
String name = pd.getName();
if (pd.getReadMethod() != null && pd.getWriteMethod() != null
&& !handled.contains(name)) {
set.add(name);
}
}
return set;
}
private String getGet(Class> class1) {
return boolean.class.equals(class1) ? "is" : "get";
}
}