/* * Rapid Beans Framework, SDK, Ant Tasks: TaskGenDtd.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 11/05/2010 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.sdk.anttasks; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.rapidbeans.core.basic.RapidEnum; import org.rapidbeans.core.type.PropertyXmlBindingType; import org.rapidbeans.core.type.TypeProperty; import org.rapidbeans.core.type.TypePropertyBoolean; import org.rapidbeans.core.type.TypePropertyChoice; import org.rapidbeans.core.type.TypePropertyCollection; import org.rapidbeans.core.type.TypeRapidBean; import org.rapidbeans.core.util.PlatformHelper; /** * the gen classes task. * * @author Martin Bluemel */ public final class TaskGenDtd extends Task { private static final String LF = PlatformHelper.getLineFeed(); /** * force flag. */ private boolean force = false; /** * set the force flag which determines if the generation should be performed * not regarding modification dates. * * @param argForce * determines if the generation should be performed not regarding * modification dates */ public void setForce(final boolean argForce) { this.force = argForce; } /** * the (root) bean type to analyze */ private String type = null; /** * the (root) bean type to analyze. * * @param type * the (root) directory to analyze */ public void setType(final String type) { this.type = type; } /** * the output DTD file */ private File dtd = null; /** * determine the DTD file to generate * * @param dtd * the DTD file to generate */ public void setDtd(final File dtd) { this.dtd = dtd; } /** * The model root directory (folder). */ private String modelroot = null; /** * Determine the input model root directory (folder). The model files are * only used for modification date check. * * @param modelroot * the model root directory (folder). */ public void setModelroot(final String modelroot) { this.modelroot = modelroot; } /** * the execute method. */ public void execute() { // checks for attributes if (this.type == null) { throw new BuildException("No bean type to analyze defined." + " Please define value for attribute \"type\"."); } final TypeRapidBean rootType = TypeRapidBean.forName(this.type); if (this.dtd == null) { throw new BuildException("No DTD file defined. Please define value for attribute \"dtd\"."); } if (!this.dtd.getParentFile().exists()) { if (!this.dtd.getParentFile().mkdirs()) { throw new BuildException("Problems to create directory \"" + this.dtd.getParentFile() + "\"."); } } if (this.modelroot == null) { getProject().log(" [gendtd] no modelroot defined, force = true", Project.MSG_VERBOSE); force = true; } final File modelrootDir = new File(this.modelroot); getProject().log(" [gendtd] modelroot: " + this.modelroot, Project.MSG_DEBUG); getProject().log(" [gendtd] modelroot path: " + modelrootDir.getPath(), Project.MSG_DEBUG); getProject().log(" [gendtd] modelroot absolute path: " + modelrootDir.getAbsolutePath(), Project.MSG_DEBUG); if (!modelrootDir.exists()) { throw new BuildException("Model root directory \"" + this.modelroot + "\" not found"); } if (!modelrootDir.isDirectory()) { throw new BuildException("Invalid model root directory. File \"" + this.modelroot + " is not a directory"); } long dirLatestModified = 0; if (!this.force) { dirLatestModified = getLatestModificationDirDate(modelrootDir, rootType, 0); } if (!this.dtd.exists() || this.force || dirLatestModified > this.dtd.lastModified()) { this.getProject().log( " [gendtd] Processing bean type " + this.type + " to " + this.dtd.getAbsolutePath(), Project.MSG_INFO); OutputStreamWriter writer = null; try { writer = new OutputStreamWriter(new FileOutputStream(this.dtd), "UTF-8"); generateDTD(rootType, writer, this.getProject()); } catch (IOException e) { throw new BuildException(e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { throw new BuildException(e); } } } } } /** * Generate the DTD recursively * * @param typename * the type to process * @param writer * the writer * @param project * the ant project */ protected static void generateDTD(final TypeRapidBean type, final Writer writer, final Project project) { generateDTD(type, null, new HashMap<String, Object>(), writer, project); } /** * Generate the DTD recursively * * @param typename * the type to process * @param xmlname * the XML name * @param alreadydefined * the map storing XML element / type combinations already * described so far. * @param writer * the writer * @param project * the ant project */ private static void generateDTD(final TypeRapidBean type, final String xmlname, final Map<String, Object> alreadydefined, final Writer writer, final Project project) { try { // write element name String elementname = xmlname; if (elementname == null) { elementname = type.getXmlRootElement(); if (elementname == null) { throw new BuildException("No XML root element definition for bean type \"" + type.getName() + "\""); } } writer.write("<!ELEMENT " + elementname + " "); alreadydefined.put(elementname, type); // write sub element list final List<TypePropertyCollection> colProptypes = type.getColPropertyTypes(); int proptypesWithElementXmlBindingCount = 0; for (final TypeProperty proptype : type.getPropertyTypes()) { if (proptype.getXmlBindingType() == PropertyXmlBindingType.element) { proptypesWithElementXmlBindingCount++; } } if (colProptypes.size() == 0 && proptypesWithElementXmlBindingCount == 0) { writer.write("EMPTY>"); } else { writer.write("(#PCDATA"); for (final TypeProperty proptype : type.getPropertyTypes()) { if (proptype.getXmlBindingType() == PropertyXmlBindingType.element) { writer.write(" | "); writer.write(proptype.getPropName()); } } for (final TypePropertyCollection colPropType : colProptypes) { if (colPropType.isComposition() && (!colPropType.isTransient())) { for (final String subelementname : getSubelementNames(colPropType, type)) { writer.write(" | "); writer.write(subelementname); } } } writer.write(")*>"); } writer.write(LF); // count attributes int attrcount = 0; if (type.getPropertyTypes().size() > colProptypes.size()) { for (final TypeProperty proptype : type.getPropertyTypes()) { if (proptype.isTransient() || proptype.getXmlBindingType() == PropertyXmlBindingType.element) { continue; } if ((!(proptype instanceof TypePropertyCollection)) || (!((TypePropertyCollection) proptype).isComposition())) { attrcount++; } } } // write attribute list if (attrcount > 0) { writer.write("<!ATTLIST " + elementname + LF); for (final TypeProperty proptype : type.getPropertyTypes()) { if (proptype.isTransient() || proptype.getXmlBindingType() == PropertyXmlBindingType.element) { continue; } if (proptype instanceof TypePropertyBoolean) { writer.write("\t" + proptype.getPropName() + " (false | true)"); if (proptype.getMandatory()) { writer.write(" #REQUIRED"); } else { writer.write(" #IMPLIED"); } writer.write(LF); } else if ((!(proptype instanceof TypePropertyCollection)) || (!((TypePropertyCollection) proptype).isComposition())) { writer.write("\t" + proptype.getPropName() + " "); if (proptype instanceof TypePropertyChoice) { writer.write("("); int i = 0; for (final RapidEnum emumElement : ((TypePropertyChoice) proptype).getEnumType() .getElements()) { if (i > 0) { writer.write(" | "); } writer.write(emumElement.name()); i++; } writer.write(")"); } else { writer.write("CDATA"); } if (proptype.getMandatory()) { writer.write(" #REQUIRED"); } else { writer.write(" #IMPLIED"); } writer.write(LF); } } writer.write(">" + LF); } writer.write(LF); // iterate over attributes serialized as XML element for (final TypeProperty proptype : type.getPropertyTypes()) { if (proptype.getXmlBindingType() == PropertyXmlBindingType.element) { if (alreadydefined.get(proptype.getPropName()) == null) { writer.write("<!ELEMENT "); writer.write(proptype.getPropName()); alreadydefined.put(proptype.getPropName(), proptype); writer.write(" (#PCDATA)*>"); writer.write(LF); writer.write(LF); } // } else if (alreadydefined.get(proptype.getPropName()) != // proptype) { // if (alreadydefined.get(proptype.getPropName()) instanceof // TypeRapidBean) { // throw new BuildException("XML Element \"" + // proptype.getPropName() // + "\" already used for bean type \"" + proptype.getName() // + "\""); // } else { // throw new BuildException("XML Element \"" + // proptype.getPropName() // + "\" already used for property type \"" + // proptype.getPropName() + "\""); // } // } } } // recurse over composition associated types for (final TypePropertyCollection colPropType : colProptypes) { if (!colPropType.isComposition() || colPropType.isTransient()) { continue; } for (final String subelementname : getSubelementNames(colPropType, type)) { TypeRapidBean targetType = colPropType.getTargetType(); if (type.getXmlElementsTypeMap() != null && type.getXmlElementsTypeMap().get(subelementname) != null) { targetType = type.getXmlElementsTypeMap().get(subelementname); } if (alreadydefined.get(subelementname) == null) { generateDTD(targetType, subelementname, alreadydefined, writer, project); } else if (alreadydefined.get(subelementname) != targetType) { if (alreadydefined.get(subelementname) instanceof TypeRapidBean) { throw new BuildException("XML Element \"" + subelementname + "\" already used for bean type \"" + ((TypeRapidBean) alreadydefined.get(subelementname)).getName() + "\""); } else { throw new BuildException("XML Element \"" + subelementname + "\" already used for property type \"" + ((TypeProperty) alreadydefined.get(subelementname)).getPropName() + "\""); } } } } } catch (IOException e) { throw new BuildException(e); } } /** * @param colPropType * @return */ private static List<String> getSubelementNames(final TypePropertyCollection colPropType, final TypeRapidBean type) { final List<String> subelementNames = new ArrayList<String>(); if (type.getXmlElements() != null && type.getXmlElements().size() > 0) { for (final Entry<String, TypeProperty> entry : type.getXmlElements().entrySet()) { if (colPropType == entry.getValue()) { subelementNames.add(entry.getKey()); } } } if (subelementNames.size() == 0) { String subelementName = colPropType.getPropName(); if (colPropType.getMaxmult() != 1 || subelementName.endsWith("s")) { subelementName = subelementName.substring(0, subelementName.length() - 1); } subelementNames.add(subelementName); } return subelementNames; } /** * iterates recursively over a complete directory tree and determines the * newest (latest) modification date of all the directories contained in the * file system hierarchy. */ protected static long getLatestModificationDirDate(final File dir, final TypeRapidBean type, final long latest) { long lastModified = latest; final File typefile = new File(dir, type.getName().replace('.', '/')); if (typefile.lastModified() > lastModified) { lastModified = typefile.lastModified(); } // recurse over composition associated types for (final TypePropertyCollection colPropType : type.getColPropertyTypes()) { if (!colPropType.isComposition() || colPropType.isTransient()) { continue; } for (final String subelementname : getSubelementNames(colPropType, type)) { if (type.getXmlElementsTypeMap() != null && type.getXmlElementsTypeMap().get(subelementname) != null) { lastModified = getLatestModificationDirDate(dir, type.getXmlElementsTypeMap().get(subelementname), lastModified); } else if (!(colPropType.getTargetType().equals(type))) { lastModified = getLatestModificationDirDate(dir, colPropType.getTargetType(), lastModified); } } } return lastModified; } }