blob: 299ee96e2b44849874b52e753a35989cc4c61ba7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.enhance;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.CodeFormat;
import org.apache.openjpa.lib.util.Files;
import org.apache.openjpa.lib.util.ParameterTemplate;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
/**
* Generates Java class code from metadata.
*
* @author Abe White
* @author Stephen Kim
* @since 0.3.0
*/
public class CodeGenerator {
private File _dir = null;
private CodeFormat _format = null;
private ClassMetaData _meta = null;
private Class _type = null;
private ParameterTemplate _code = null;
/**
* Constructor. Supply configuration and class to generate code for.
*/
public CodeGenerator(OpenJPAConfiguration conf, Class type) {
this(conf.newMetaDataRepositoryInstance().
getMetaData(type, null, true));
}
/**
* Constructor. Supply configuration and metadata to generate code for.
*/
public CodeGenerator(ClassMetaData meta) {
_meta = meta;
_type = meta.getDescribedType();
}
/**
* The directory to write source to. Defaults to the current directory.
* If the given directory does not match the package of the metadata, the
* package structure will be created below the directory.
*/
public File getCodeDirectory() {
return _dir;
}
/**
* The directory to write source to. Defaults to the current directory.
* If the given directory does not match the package of the metadata, the
* package structure will be created below the directory.
*/
public void setDirectory(File dir) {
_dir = dir;
}
/**
* The code formatter for the generated Java code.
*/
public CodeFormat getCodeFormat() {
return _format;
}
/**
* Set the code formatter for the generated Java code.
*/
public void setCodeFormat(CodeFormat format) {
_format = format;
}
/**
* Return the type being generated.
*/
public Class getType() {
return _type;
}
/**
* Return metadata for the type being generated.
*/
public ClassMetaData getMetaData() {
return _meta;
}
/**
* Return the generated code, or null if {@link #generateCode} has not
* been called.
*/
public String getCode() {
return (_code == null) ? null : _code.toString();
}
/**
* Writes the generated code to the proper directory.
*/
public void generateCode() {
// setup parameters
String className = ClassUtil.getClassName(_type);
String packageName = ClassUtil.getPackageName(_type);
String packageDec = "";
if (packageName.length() > 0)
packageDec = "package " + packageName + ";";
String extendsDec = "";
String extendsName = "";
if (!_type.getSuperclass().getName().equals(Object.class.getName())) {
extendsName = ClassUtil.getClassName(_type.getSuperclass());
extendsDec = "extends " + extendsName;
}
String imports = getImports();
String[] fieldCode = getFieldCode();
String constructor = getConstructor();
// get code template
_code = new ParameterTemplate();
String codeStr = getClassCode();
if (codeStr != null) {
_code.append(codeStr);
_code.setParameter("packageDec", packageDec);
_code.setParameter("imports", imports);
_code.setParameter("className", className);
_code.setParameter("extendsDec", extendsDec);
_code.setParameter("constructor", constructor);
_code.setParameter("fieldDecs", fieldCode[0]);
_code.setParameter("fieldCode", fieldCode[1]);
} else
_code.append(getClassCode(packageDec, imports, className,
extendsName, constructor, fieldCode[0], fieldCode[1]));
}
/**
* Write the generated code to the proper file.
*/
public void writeCode()
throws IOException {
if (_code == null)
return;
File file = getFile();
Files.backup(file, false);
_code.write(file);
}
/**
* Write the code to the specified {@link Writer}.
*/
public void writeCode(Writer out)
throws IOException {
if (_code == null)
return;
_code.write(out);
}
/**
* Return the necessary imports for the class.
*/
private String getImports() {
Set pkgs = getImportPackages();
CodeFormat imports = newCodeFormat();
String base = ClassUtil.getPackageName(_type);
String pkg;
for (Object o : pkgs) {
pkg = (String) o;
if (pkg.length() > 0 && !"java.lang".equals(pkg)
&& !base.equals(pkg)) {
if (imports.length() > 0)
imports.endl();
imports.append("import ").append(pkg).append(".*;");
}
}
return imports.toString();
}
/**
* Returns the set of packages that needs to be imported for this code.
*/
public Set getImportPackages() {
Set pkgs = new TreeSet();
pkgs.add(ClassUtil.getPackageName(_type.getSuperclass()));
FieldMetaData[] fields = _meta.getDeclaredFields();
for (FieldMetaData fieldMetaData : fields) {
pkgs.add(ClassUtil.getPackageName(fieldMetaData.getDeclaredType()));
}
fields = _meta.getPrimaryKeyFields();
for (FieldMetaData field : fields) {
pkgs.add(ClassUtil.getPackageName(field.getDeclaredType()));
}
return pkgs;
}
/**
* Return code for a primary key constructor for the given class.
*/
private String getConstructor() {
FieldMetaData[] fields = _meta.getPrimaryKeyFields();
if (fields.length == 0)
return "";
CodeFormat cons = newCodeFormat();
CodeFormat body = newCodeFormat();
// public <class> (
cons.tab().append("public ").append(ClassUtil.getClassName(_type));
cons.openParen(true);
// append args to constructor, and build up body at same time
String propertyName;
String fieldType;
for (int i = 0; i < fields.length; i++) {
propertyName = fields[i].getName();
if (propertyName.startsWith("_"))
propertyName = propertyName.substring(1);
fieldType = ClassUtil.getClassName(fields[i].getDeclaredType());
if (i > 0)
cons.append(", ");
cons.append(fieldType).append(" ").append(propertyName);
if (_meta.getPCSuperclass() == null) {
if (i > 0)
body.endl();
body.tab(2);
if (propertyName.equals(fields[i].getName()))
body.append("this.");
body.append(fields[i].getName());
body.append(" = ").append(propertyName).append(";");
} else {
// super (...);
if (i == 0)
body.tab(2).append("super").openParen(true);
else
body.append(", ");
body.append(propertyName);
if (i == fields.length - 1)
body.closeParen().append(";");
}
}
cons.closeParen();
cons.openBrace(2).endl();
cons.append(body.toString()).endl();
cons.closeBrace(2);
return cons.toString();
}
/**
* Returns the Java declaration and access method code for all declared
* fields.
*/
private String[] getFieldCode() {
CodeFormat decs = newCodeFormat();
CodeFormat code = newCodeFormat();
FieldMetaData[] fields = _meta.getDeclaredFields();
for (FieldMetaData fieldMetaData : fields) {
appendFieldCode(fieldMetaData, decs, code);
}
fields = _meta.getDeclaredUnmanagedFields();
for (FieldMetaData field : fields) {
appendFieldCode(field, decs, code);
}
return new String[]{ decs.toString(), code.toString() };
}
/**
* Append the declaration and code for the given field to the given buffers.
*/
private void appendFieldCode(FieldMetaData fmd, CodeFormat decs,
CodeFormat code) {
String fieldName = fmd.getName();
String capFieldName = StringUtil.capitalize(fieldName);
String propertyName = fieldName;
if (propertyName.startsWith("_"))
propertyName = propertyName.substring(1);
String fieldType = ClassUtil.getClassName(fmd.getDeclaredType());
String keyType = null;
String elementType = null;
String paramType = "";
if (useGenericCollections()) {
if (fmd.getDeclaredTypeCode() == JavaTypes.COLLECTION) {
Class elmCls = fmd.getElement().getDeclaredType();
elementType = ClassUtil.getClassName(elmCls);
paramType = decs.getParametrizedType(
new String[] {elementType});
} else if (fmd.getDeclaredTypeCode() == JavaTypes.MAP) {
Class keyCls = fmd.getKey().getDeclaredType();
Class elmCls = fmd.getElement().getDeclaredType();
keyType = ClassUtil.getClassName(keyCls);
elementType = ClassUtil.getClassName(elmCls);
paramType = decs.getParametrizedType(
new String[] {keyType, elementType});
}
}
String fieldValue = getInitialValue(fmd);
if (fieldValue == null) {
if ("Set".equals(fieldType))
fieldValue = "new HashSet" + paramType + decs.getParens();
else if ("TreeSet".equals(fieldType))
fieldValue = "new TreeSet" + paramType + decs.getParens();
else if ("Collection".equals(fieldType))
fieldValue = "new ArrayList" + paramType + decs.getParens();
else if ("Map".equals(fieldType))
fieldValue = "new HashMap" + paramType + decs.getParens();
else if ("TreeMap".equals(fieldType))
fieldValue = "new TreeMap" + paramType + decs.getParens();
else if (fmd.getDeclaredTypeCode() == JavaTypes.COLLECTION ||
fmd.getDeclaredTypeCode() == JavaTypes.MAP)
fieldValue = "new " + fieldType + paramType + decs.getParens();
else
fieldValue = "";
}
if (fieldValue.length() > 0)
fieldValue = " = " + fieldValue;
boolean fieldAccess = !usePropertyBasedAccess();
String custom = getDeclaration(fmd);
if (decs.length() > 0)
decs.endl();
ParameterTemplate templ;
if (custom != null) {
templ = new ParameterTemplate();
templ.append(custom);
templ.setParameter("fieldName", fieldName);
templ.setParameter("capFieldName", capFieldName);
templ.setParameter("propertyName", propertyName);
templ.setParameter("fieldType", fieldType);
templ.setParameter("keyType", keyType);
templ.setParameter("elementType", elementType);
templ.setParameter("fieldValue", fieldValue);
decs.append(templ.toString());
} else {
if (fieldAccess)
writeAnnotations(decs, getFieldAnnotations(fmd), 1);
decs.tab().append("private ").append(fieldType).
append(paramType).append(" ").append(fieldName).
append(fieldValue).append(";");
if (fieldAccess)
decs.endl();
}
custom = getFieldCode(fmd);
if (code.length() > 0)
code.afterSection();
if (custom != null) {
templ = new ParameterTemplate();
templ.append(custom);
templ.setParameter("fieldName", fieldName);
templ.setParameter("capFieldName", capFieldName);
templ.setParameter("propertyName", propertyName);
templ.setParameter("fieldType", fieldType);
templ.setParameter("keyType", keyType);
templ.setParameter("elementType", elementType);
templ.setParameter("fieldValue", fieldValue);
code.append(templ.toString());
} else {
// getter
if (!fieldAccess)
writeAnnotations(code, getFieldAnnotations(fmd), 1);
code.tab().append("public ").append(fieldType).append(paramType).
append(" ");
if ("boolean".equalsIgnoreCase(fieldType))
code.append("is");
else
code.append("get");
if (fieldName.length() > 1 && Character.isLowerCase(fieldName.charAt(0))
&& Character.isUpperCase(fieldName.charAt(1))) {
code.append(fieldName).parens();
} else {
code.append(capFieldName).parens();
}
code.openBrace(2).endl();
code.tab(2).append("return ").append(fieldName).
append(";").endl();
code.closeBrace(2).afterSection();
// setter
if (fieldName.length() > 1 && Character.isLowerCase(fieldName.charAt(0))
&& Character.isUpperCase(fieldName.charAt(1))) {
code.tab().append("public void set").append(fieldName);
} else {
code.tab().append("public void set").append(capFieldName);
}
code.openParen(true).append(fieldType).append(paramType).
append(" ").append(propertyName).closeParen();
code.openBrace(2).endl();
code.tab(2);
if (propertyName.equals(fieldName))
code.append("this.");
code.append(fieldName).append(" = ").append(propertyName).
append(";").endl();
code.closeBrace(2);
}
}
/**
* Return a code template for a generated Java class.
*/
private String getClassCode(String packageDec, String imports,
String className, String extendsName, String constructor,
String fieldDecs, String fieldCode) {
CodeFormat code = newCodeFormat();
if (packageDec.length() > 0)
code.append(packageDec).afterSection();
if (imports.length() > 0)
code.append(imports).afterSection();
code.append("/**").endl().
append(" * Auto-generated by:").endl().
append(" * ").append(getClass().getName()).endl().
append(" */").endl();
writeAnnotations(code, getClassAnnotations(), 0);
code.append("public class ").append(className);
if (extendsName.length() > 0)
code.extendsDec(1).append(" ").append(extendsName);
openClassBrace(code);
if (fieldDecs.length() > 0)
code.append(fieldDecs).afterSection();
// default constructor
code.tab().append("public ").append(className).parens();
code.openBrace(2).endl().closeBrace(2);
if (constructor.length() > 0)
code.afterSection().append(constructor);
if (fieldCode.length() > 0)
code.afterSection().append(fieldCode);
code.endl();
closeClassBrace(code);
return code.toString();
}
/**
* Appends the given list of annotations to code buffer.
*/
private void writeAnnotations (CodeFormat code, List ann,
int tabLevel) {
if (ann == null || ann.size() == 0)
return;
for (Object o : ann) {
if (tabLevel > 0)
code.tab(tabLevel);
String s = (String) o;
code.append(s).endl();
}
}
/**
* Append the opening code-level brace to the code; this can be
* overridden to add code to the top of the class.
*/
protected void openClassBrace(CodeFormat code) {
code.openBrace(1).endl();
}
/**
* Append the closing code-level brace to the code; this can be
* overridden to add code to the bottom of the class.
*/
protected void closeClassBrace(CodeFormat code) {
code.closeBrace(1);
}
/**
* Return Java file to write to.
*/
public File getFile() {
String packageName = ClassUtil.getPackageName(_type);
String fileName = ClassUtil.getClassName(_type) + ".java";
File dir = Files.getPackageFile(_dir, packageName, true);
return new File(dir, fileName);
}
/**
* Return a copy of the internal code format.
*/
protected CodeFormat newCodeFormat() {
if (_format == null)
return new CodeFormat();
return (CodeFormat) _format.clone();
}
/**
* Return a code template for the given class, or null to use the standard
* system-generated Java code. To facilitate template reuse, the
* following parameters can appear in the template; the proper values
* will be subtituted by the system:
* <ul>
* <li>${packageDec}: The package declaration, in the form
* "package &lt;package name &gt;;", or empty string if no package.</li>
* <li>${imports}: Imports for the packages used by the declared
* field types.</li>
* <li>${className}: The name of the class, without package.</li>
* <li>${extendsDec}: Extends declaration, in the form
* "extends &lt;superclass&gt;", or empty string if no superclass.</li>
* <li>${constructor}: A constructor that takes in all primary key fields
* of the class, or empty string if the class uses datastore identity.</li>
* <li>${fieldDecs}: Declarations of all the declared fields.</li>
* <li>${fieldCode}: Get/set methods for all the declared fields.</li>
* </ul> Returns null by default.
*/
protected String getClassCode() {
return null;
}
/**
* Return code for the initial value for the given field, or null to use
* the default generated by the system. Returns null by default.
*/
protected String getInitialValue(FieldMetaData field) {
return null;
}
/**
* Return a code template for the declaration of the given field, or null
* to use the system-generated default Java code.
* To facilitate template reuse, the following parameters can appear in
* your template; the proper values will be subtituted by the system:
* <ul>
* <li>${fieldName}: The name of the field.</li>
* <li>${capFieldName}: The capitalized field name.</li>
* <li>${propertyName}: The field name without leading '_', if any.</li>
* <li>${fieldType}: The field's type name.</li>
* <li>${keyType}: Key type name for maps, null otherwise.</li>
* <li>${elementType}: Element type name for collections, null otherwise.
* </li>
* <li>${fieldValue}: The field's initial value, in the form
* " = &lt;value&gt;", or empty string if none.</li>
* </ul> Returns null by default.
*/
protected String getDeclaration(FieldMetaData field) {
return null;
}
/**
* Return a code template for the get/set methods of the given field, or
* null to use the system-generated default Java code.
* To facilitate template reuse, the following parameters can appear in
* your template; the proper values will be subtituted by the system:
* <ul>
* <li>${fieldName}: The name of the field.</li>
* <li>${capFieldName}: The capitalized field name.</li>
* <li>${propertyName}: The field name without leading '_', if any.</li>
* <li>${fieldType}: The field's type name.</li>
* <li>${keyType}: Key type name for maps, null otherwise.</li>
* <li>${elementType}: Element type name for collections, null otherwise.
* </li>
* <li>${fieldValue}: The field's initial value, in the form
* "= &lt;value&gt;", or empty string if none.</li>
* </ul>
*/
protected String getFieldCode (FieldMetaData field)
{
return null;
}
/**
* Whether to use property-based access on generated code.
* Defaults to false (field-based).
*/
protected boolean usePropertyBasedAccess () {
return false;
}
/**
* Return class-level annotations. Returns null by default.
*/
protected List getClassAnnotations() {
return null;
}
/**
* Return field-level annotations. Returns null by default.
*/
protected List getFieldAnnotations(FieldMetaData field) {
return null;
}
/**
* Whether to use generic collections on one-to-many and many-to-many
* relations instead of untyped collections.
*
* Override in descendants to change default behavior.
*/
protected boolean useGenericCollections() {
return false;
}
}