/* * Copyright (c) 2007-2008, Dennis M. Sosnoski All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jibx.schema.codegen; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.PackageDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.PrimitiveType.Code; import org.eclipse.jdt.core.formatter.CodeFormatter; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.text.edits.TextEdit; /** * Abstract syntax tree builder. This wraps the AST with convenience methods and added control information. */ public class SourceBuilder { /** Map from primitive type name to type code. */ private static final Map s_primitiveTypeCodes; static { s_primitiveTypeCodes = new HashMap(); s_primitiveTypeCodes.put("boolean", PrimitiveType.BOOLEAN); s_primitiveTypeCodes.put("byte", PrimitiveType.BYTE); s_primitiveTypeCodes.put("char", PrimitiveType.CHAR); s_primitiveTypeCodes.put("double", PrimitiveType.DOUBLE); s_primitiveTypeCodes.put("float", PrimitiveType.FLOAT); s_primitiveTypeCodes.put("int", PrimitiveType.INT); s_primitiveTypeCodes.put("long", PrimitiveType.LONG); s_primitiveTypeCodes.put("short", PrimitiveType.SHORT); s_primitiveTypeCodes.put("void", PrimitiveType.VOID); } /** Actual AST instance. */ private final AST m_ast; /** Package containing this source. */ private final PackageHolder m_package; /** Name of this source. */ private final String m_name; /** Compilation unit. */ private final CompilationUnit m_compilationUnit; /** Imports for source file. */ private final Set m_importsSet; /** Map from class names in imports set to names used. */ private final Map m_nameMap; /** Builders for main classes in file. */ private ArrayList m_classes; /** * Constructor. * * @param ast * @param pack * @param name * @param imports * @param unqualifieds */ public SourceBuilder(AST ast, PackageHolder pack, String name, Set imports, Map unqualifieds) { // initialize basic parameters m_ast = ast; m_package = pack; m_name = name; m_importsSet = imports; m_classes = new ArrayList(); // create file in appropriate package m_compilationUnit = ast.newCompilationUnit(); String pname = pack.getName(); if (pname.length() > 0) { PackageDeclaration packageDeclaration = ast.newPackageDeclaration(); packageDeclaration.setName(ast.newName(pname)); m_compilationUnit.setPackage(packageDeclaration); } // add all imports to file m_nameMap = new HashMap(); for (Iterator iter = imports.iterator(); iter.hasNext();) { String impname = (String)iter.next(); boolean explicit = false; if (impname.startsWith(pname) && pname.length() > 0) { int split = impname.indexOf('.', pname.length()); if (split > 0 && !impname.substring(split+1).startsWith(name + '.')) { explicit = true; } else { m_nameMap.put(impname, impname.substring(pname.length()+1)); } } else if (impname.startsWith("java.lang.") && impname.lastIndexOf('.') <= "java.lang.".length()) { m_nameMap.put(impname, impname.substring("java.lang.".length())); } else { explicit = true; } if (explicit) { ImportDeclaration imp = ast.newImportDeclaration(); imp.setName(ast.newName(impname)); m_compilationUnit.imports().add(imp); int split = impname.lastIndexOf('.') + 1; m_nameMap.put(impname, impname.substring(split)); } } // include mapping for all other local unqualified type names for (Iterator iter = unqualifieds.keySet().iterator(); iter.hasNext();) { String uqname = (String)iter.next(); String fqname = (String)unqualifieds.get(uqname); m_nameMap.put(fqname, uqname); } } /** * AST access for related classes. * * @return AST */ AST getAST() { return m_ast; } /** * Get the name of the package containing this source file. * * @return name */ public String getPackageName() { return m_package.getName(); } /** * Create a type declaration. * * @param cname class name * @param sname superclass name (null if none, i.e. java.lang.Object) * @param isenum Java 5 enum class flag * @return type declaration */ private AbstractTypeDeclaration createClass(String cname, String sname, boolean isenum) { AbstractTypeDeclaration abstype; if (isenum) { if (sname != null) { throw new IllegalArgumentException("Internal error - enumeration type cannot have a superclass"); } abstype = m_ast.newEnumDeclaration(); } else { TypeDeclaration type = m_ast.newTypeDeclaration(); type.setInterface(false); if (sname != null) { type.setSuperclassType(createType(sname)); } abstype = type; } abstype.modifiers().add(m_ast.newModifier(Modifier.ModifierKeyword.PUBLIC_KEYWORD)); abstype.setName(m_ast.newSimpleName(cname)); return abstype; } /** * Add a new main class to the file. * * @param cname class name * @param sname superclass name (null if none, i.e. java.lang.Object) * @param isenum Java 5 enum class flag * @return builder */ public ClassBuilder newMainClass(String cname, String sname, boolean isenum) { AbstractTypeDeclaration decl = createClass(cname, sname, isenum); m_compilationUnit.types().add(decl); ClassBuilder builder = new ClassBuilder(decl, this); m_classes.add(builder); return builder; } /** * Add a new inner class to the file. * * @param cname class name * @param sname superclass name (null if none, i.e. java.lang.Object) * @param outer containing class builder * @param isenum Java 5 enum class flag * @return builder */ public ClassBuilder newInnerClass(String cname, String sname, ClassBuilder outer, boolean isenum) { AbstractTypeDeclaration decl = createClass(cname, sname, isenum); decl.modifiers().add(m_ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD)); // type.superInterfaceTypes().add(createType("java.io.Serializable")); return new ClassBuilder(decl, outer); } /** * Create type name. * * @param type fully qualified type name * @return name */ protected Name createTypeName(String type) { String name = (String)m_nameMap.get(type); if (name == null) { name = type; } if (name.indexOf('.') > 0) { return m_ast.newName(name); } else { return m_ast.newSimpleName(name); } } /** * Create type definition. This uses the supplied type name, which may include array suffixes, to construct the * actual type definition. * * @param type fully qualified type name, or primitive type name * @return constructed typed definition */ public Type createType(String type) { int arraydepth = 0; while (type.endsWith("[]")) { arraydepth++; type = type.substring(0, type.length()-2); } Type tdef; Code code = (Code)s_primitiveTypeCodes.get(type); if (code == null) { tdef = getAST().newSimpleType(createTypeName(type)); } else { tdef = getAST().newPrimitiveType(code); } if (arraydepth > 0) { tdef = getAST().newArrayType(tdef, arraydepth); } return tdef; } /** * Create a parameterized type. * * @param type fully qualified type name * @param param fully qualified parameter type name * @return type */ public Type createParameterizedType(String type, String param) { ParameterizedType ptype = getAST().newParameterizedType(createType(type)); ptype.typeArguments().add(createType(param)); return ptype; } /** * Generate the actual source file. */ public void finish() { // finish building all the classes in this source for (int i = 0; i < m_classes.size(); i++) { ClassBuilder builder = (ClassBuilder)m_classes.get(i); builder.finish(); } // convert generated AST to text document Map options = new HashMap(); options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE); options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4"); options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_AFTER_PACKAGE, "1"); options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_AFTER_IMPORTS, "1"); options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BEFORE_PACKAGE, "1"); options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BEFORE_IMPORTS, "1"); options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BEFORE_METHOD, "1"); options.put(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_TYPE_DECLARATION, DefaultCodeFormatterConstants.NEXT_LINE); options.put(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_SUPERINTERFACES_IN_TYPE_DECLARATION, DefaultCodeFormatterConstants.createAlignmentValue(false, DefaultCodeFormatterConstants.WRAP_COMPACT, DefaultCodeFormatterConstants.INDENT_BY_ONE)); options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_1_5); options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5); options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_5); Document doc = new Document(m_compilationUnit.toString()); CodeFormatter fmtr = ToolFactory.createCodeFormatter(options); String text = doc.get(); TextEdit edits = fmtr.format(CodeFormatter.K_COMPILATION_UNIT, text, 0, text.length(), 0, null); if (edits == null) { System.out.println(text); } File gendir = m_package.getGenerateDirectory(); if (gendir != null) { try { edits.apply(doc); text = doc.get(); File file = new File(gendir, m_name + ".java"); FileWriter fwrit = new FileWriter(file); fwrit.write(text); fwrit.flush(); fwrit.close(); System.out.println("Generated class file " + file.getCanonicalPath()); } catch (BadLocationException e) { throw new IllegalStateException("Error in source generation", e); } catch (IOException e) { throw new IllegalStateException("Error in source generation", e); } } } }