Index: src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java =================================================================== --- src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java (r‚vision 607) +++ src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java (copie de travail) @@ -29,8 +29,9 @@ import org.codehaus.modello.model.ModelClass; import org.codehaus.modello.model.ModelField; import org.codehaus.modello.plugin.AbstractModelloGenerator; -import org.codehaus.modello.plugin.model.ModelClassMetadata; +import org.codehaus.modello.plugins.xml.XmlClassMetadata; import org.codehaus.modello.plugins.xml.XmlFieldMetadata; +import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter; import org.codehaus.plexus.util.xml.XMLWriter; @@ -116,7 +117,14 @@ w.startElement( "p" ); - w.writeMarkup( objectModel.getDescription() ); + if ( objectModel.getDescription() != null ) + { + w.writeMarkup( objectModel.getDescription() ); + } + else + { + w.writeText( "No description." ); + } w.endElement(); @@ -146,50 +154,39 @@ writer.close(); } - private void writeElementDescriptor( XMLWriter w, Model objectModel, ModelClass modelClass, ModelField field, - Set written ) + /** + * Builds the table describing the given class. + * @param w the writer to output Xdoc to + * @param objectModel the current objectModel + * @param modelClass the class we are describing + * @param association the association the class is orginating from + * @param written the set of written classes + */ + private void writeElementDescriptor( XMLWriter w, Model objectModel, ModelClass modelClass, + ModelAssociation association, Set written ) { written.add( modelClass ); - ModelClassMetadata metadata = (ModelClassMetadata) modelClass.getMetadata( ModelClassMetadata.ID ); + // resolve tagName String tagName; - if ( metadata == null || metadata.getTagName() == null ) + + if ( association == null ) { - if ( field == null ) - { - tagName = uncapitalise( modelClass.getName() ); - } - else - { - tagName = field.getName(); - if ( field instanceof ModelAssociation ) - { - ModelAssociation a = (ModelAssociation) field; - if ( ModelAssociation.MANY_MULTIPLICITY.equals( a.getMultiplicity() ) ) - { - tagName = singular( tagName ); - } - } - } + tagName = resolveClassTagName( modelClass ); } - else + else if ( isWrappedAssociation( association ) ) { - tagName = metadata.getTagName(); + tagName = resolveInnerAssociationTagName( association ); } - - if ( field != null ) + else { - XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); - if ( fieldMetadata != null && fieldMetadata.getTagName() != null ) - { - tagName = fieldMetadata.getTagName(); - } + tagName = resolveOuterFieldTagName( association ); } w.startElement( "a" ); - w.addAttribute( "name", "class_" + tagName ); + w.addAttribute( "name", "class_" + modelClass.getName() ); w.endElement(); @@ -197,15 +194,19 @@ w.addAttribute( "name", tagName ); + w.startElement( "p" ); + if ( modelClass.getDescription() != null ) { - w.startElement( "p" ); - w.writeMarkup( modelClass.getDescription() ); - - w.endElement(); } + else + { + w.writeMarkup( "No description." ); + } + w.endElement(); + w.startElement( "table" ); w.startElement( "tr" ); @@ -224,50 +225,40 @@ w.endElement(); - List fields = getFieldsForClass( objectModel, modelClass ); + List fields = getFieldsForClass( modelClass ); + List associationsToWrite = new ArrayList(); + for ( Iterator j = fields.iterator(); j.hasNext(); ) { ModelField f = (ModelField) j.next(); - XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) f.getMetadata( XmlFieldMetadata.ID ); - w.startElement( "tr" ); w.startElement( "td" ); w.startElement( "code" ); - boolean flatAssociation = f instanceof ModelAssociation - && isClassInModel( ( (ModelAssociation) f ).getTo(), objectModel ) - && XmlFieldMetadata.LIST_STYLE_FLAT.equals( fieldMetadata.getListStyle() ); + w.writeText( resolveOuterFieldTagName( f ) ); - if ( flatAssociation ) + if ( isInnerAssociation( f ) ) { - - ModelAssociation association = (ModelAssociation) f; - - ModelClass associationModelClass = objectModel.getClass( association.getTo(), getGeneratedVersion() ); - - w.writeText( uncapitalise( associationModelClass.getName() ) ); - + associationsToWrite.add( (ModelAssociation) f ); } - else - { - w.writeText( f.getName() ); - - } - w.endElement(); w.endElement(); w.startElement( "td" ); - if ( flatAssociation ) + if ( isFlatAssociation( f ) ) { - w.writeMarkup( "List " ); + w.startElement( "b" ); + + w.writeText( "[List of] " ); + + w.endElement(); } if ( f.getDescription() != null ) @@ -288,24 +279,24 @@ w.endElement(); - for ( Iterator iter = fields.iterator(); iter.hasNext(); ) + for ( Iterator iter = associationsToWrite.iterator(); iter.hasNext(); ) { - ModelField f = (ModelField) iter.next(); + ModelAssociation assoc = (ModelAssociation) iter.next(); - if ( f instanceof ModelAssociation && isClassInModel( ( (ModelAssociation) f ).getTo(), objectModel ) ) + // TODO We are currently not handling the case where a given class + // would participate in 2 associations with different tagName. + + // This case would result in class information being generated + // with only the name of the association found + + if ( !written.contains( assoc.getToClass() ) ) { - ModelAssociation association = (ModelAssociation) f; - ModelClass fieldModelClass = objectModel.getClass( association.getTo(), getGeneratedVersion() ); - - if ( !written.contains( f.getName() ) ) - { - writeElementDescriptor( w, objectModel, fieldModelClass, f, written ); - } + writeElementDescriptor( w, objectModel, assoc.getToClass(), assoc, written ); } } } - private List getFieldsForClass( Model objectModel, ModelClass modelClass ) + private List getFieldsForClass( ModelClass modelClass ) { List fields = new ArrayList(); while ( modelClass != null ) @@ -314,7 +305,7 @@ String superClass = modelClass.getSuperClass(); if ( superClass != null ) { - modelClass = objectModel.getClass( superClass, getGeneratedVersion() ); + modelClass = getModel().getClass( superClass, getGeneratedVersion() ); } else { @@ -326,22 +317,19 @@ /** * Return the child attribute fields of this class. - * @param objectModel global object model * @param modelClass current class * @return the list of attribute fields of this class */ - private List getAttributeFieldsForClass( Model objectModel, ModelClass modelClass ) + private List getAttributeFieldsForClass( ModelClass modelClass ) { List attributeFields = new ArrayList(); while ( modelClass != null ) { List allFields = modelClass.getFields( getGeneratedVersion() ); - Iterator allFieldsIt = allFields.iterator(); - - while ( allFieldsIt.hasNext() ) + for ( Iterator iter = allFields.iterator(); iter.hasNext(); ) { - ModelField field = (ModelField) allFieldsIt.next(); + ModelField field = (ModelField) iter.next(); XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); if ( fieldMetadata.isAttribute() ) { @@ -352,7 +340,7 @@ String superClass = modelClass.getSuperClass(); if ( superClass != null ) { - modelClass = objectModel.getClass( superClass, getGeneratedVersion() ); + modelClass = getModel().getClass( superClass, getGeneratedVersion() ); } else { @@ -362,152 +350,143 @@ return attributeFields; } - private String getModelClassDescriptor( Model objectModel, ModelClass modelClass, ModelField field, int depth ) + private String getModelClassDescriptor( Model objectModel, ModelClass modelClass, ModelAssociation association, + int depth ) + { + return getModelClassDescriptor( objectModel, modelClass, association, depth, true ); + } + + /** + * Build the pretty tree describing the model.
+ * This method is recursive. + * @param objectModel the complete model + * @param modelClass the class we are printing the model + * @param association the association we are coming from (can be null) + * @param depth how deep we currently are (for spacers purpose) + * @param recursive are we still in recursive mode or not + * @return the String representing the tree model + * @throws ModelloRuntimeException + */ + private String getModelClassDescriptor( Model objectModel, ModelClass modelClass, ModelAssociation association, + int depth, boolean recursive ) throws ModelloRuntimeException { StringBuffer sb = new StringBuffer(); - for ( int i = 0; i < depth; i++ ) - { - sb.append( " " ); - } + generateSpacer( sb, depth ); - ModelClassMetadata metadata = (ModelClassMetadata) modelClass.getMetadata( ModelClassMetadata.ID ); + // resolve tagName String tagName; - if ( metadata == null || metadata.getTagName() == null ) + + if ( association == null ) { - if ( field == null ) - { - tagName = uncapitalise( modelClass.getName() ); - } - else - { - tagName = field.getName(); - if ( field instanceof ModelAssociation ) - { - ModelAssociation a = (ModelAssociation) field; - if ( ModelAssociation.MANY_MULTIPLICITY.equals( a.getMultiplicity() ) ) - { - tagName = singular( tagName ); - } - } - } + tagName = resolveClassTagName( modelClass ); } - else + else if ( isWrappedAssociation( association ) ) { - tagName = metadata.getTagName(); + tagName = resolveInnerAssociationTagName( association ); } - - if ( field != null ) + else { - XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); - if ( fieldMetadata != null && fieldMetadata.getTagName() != null ) - { - tagName = fieldMetadata.getTagName(); - } + tagName = resolveOuterFieldTagName( association ); } - sb.append( "<" ).append( tagName ); + // Write down tagName + sb.append( "<" ).append( tagName ); + sb.append( "" ); - List fields = getFieldsForClass( objectModel, modelClass ); + List fields = getFieldsForClass( modelClass ); - List attributeFields = getAttributeFieldsForClass( objectModel, modelClass ); + // handle the attributes + List attributeFields = getAttributeFieldsForClass( modelClass ); + if ( attributeFields.size() > 0 ) { - for ( Iterator iter = attributeFields.iterator(); iter.hasNext(); ) { ModelField f = (ModelField) iter.next(); - sb.append( " " ); + sb.append( " " ); - sb.append( uncapitalise( f.getName() ) ).append( "=.." ); + sb.append( resolveFieldTagName( f ) ).append( "=.." ); } sb.append( " " ); fields.removeAll( attributeFields ); - } + // handle the other fields of the current class + if ( fields.size() > 0 ) { sb.append( ">\n" ); - for ( Iterator iter = fields.iterator(); iter.hasNext(); ) + if ( recursive ) { - ModelField f = (ModelField) iter.next(); - XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) f.getMetadata( XmlFieldMetadata.ID ); - - ModelClass fieldModelClass; - - if ( f instanceof ModelAssociation && isClassInModel( ( (ModelAssociation) f ).getTo(), objectModel ) ) + for ( Iterator iter = fields.iterator(); iter.hasNext(); ) { - ModelAssociation association = (ModelAssociation) f; + ModelField f = (ModelField) iter.next(); - if ( XmlFieldMetadata.LIST_STYLE_FLAT.equals( fieldMetadata.getListStyle() ) ) + if ( isInnerAssociation( f ) ) { - fieldModelClass = objectModel.getClass( association.getTo(), getGeneratedVersion() ); - sb.append( getModelClassDescriptor( objectModel, fieldModelClass, f, depth + 1 ) ); + ModelAssociation assoc = (ModelAssociation) f; - } + boolean wrappedListStyle = isWrappedAssociation( f ); - else - { - - if ( ModelAssociation.MANY_MULTIPLICITY.equals( association.getMultiplicity() ) ) + if ( wrappedListStyle ) { depth++; - for ( int i = 0; i < depth; i++ ) - { - sb.append( " " ); - } + generateSpacer( sb, depth ); - sb.append( "<" ).append( uncapitalise( association.getName() ) ).append( ">\n" ); + sb.append( "<" ).append( resolveOuterFieldTagName( f ) ).append( ">\n" ); } - fieldModelClass = objectModel.getClass( association.getTo(), getGeneratedVersion() ); + if ( isNonRecursiveAssociation( modelClass, assoc ) ) + { + sb.append( getModelClassDescriptor( objectModel, assoc.getToClass(), assoc, depth + 1 ) ); + } + else + { + sb.append( getModelClassDescriptor( objectModel, assoc.getToClass(), assoc, depth + 1, + false ) ); + } - sb.append( getModelClassDescriptor( objectModel, fieldModelClass, f, depth + 1 ) ); - - if ( ModelAssociation.MANY_MULTIPLICITY.equals( association.getMultiplicity() ) ) + if ( wrappedListStyle ) { - for ( int i = 0; i < depth; i++ ) - { - sb.append( " " ); - } + generateSpacer( sb, depth ); - sb.append( "</" ).append( uncapitalise( association.getName() ) ).append( ">\n" ); + sb.append( "</" ).append( resolveOuterFieldTagName( f ) ).append( ">\n" ); depth--; } + } - - } - else - { - for ( int i = 0; i < depth + 1; i++ ) + else { - sb.append( " " ); - } + generateSpacer( sb, depth + 1 ); - sb.append( "<" ).append( uncapitalise( f.getName() ) ).append( "/>\n" ); + sb.append( "<" ).append( resolveFieldTagName( f ) ).append( "/>\n" ); + } } } + else + { + generateSpacer( sb, depth + 1 ); - for ( int i = 0; i < depth; i++ ) - { - sb.append( " " ); + sb.append( ".. infinite loop ..\n" ); } + generateSpacer( sb, depth ); + sb.append( "</" ).append( tagName ).append( ">\n" ); } else @@ -517,4 +496,182 @@ return sb.toString(); } + + /** + * Compute the tagName of a given field.
+ * This method return the first child tag name created by this field. + * This means that for a association with multiplicity * and listStyle to + * wrapped (which is the default), this method will return the plural tagName, + * while for a listStyle of flat, it will return the singular tagName. + * @param field the field we are looking for the tag name. + * @return the tag name to use + */ + private String resolveFieldTagName( ModelField field ) + { + XmlFieldMetadata metadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); + + String tagName = uncapitalise( field.getName() ); + + if ( metadata != null && StringUtils.isNotEmpty( metadata.getTagName() ) ) + { + tagName = metadata.getTagName(); + } + + return tagName; + } + + /** + * Compute the outer tagName of a given field.
+ * If this method is called with a normal field, it will just return the normal name (see {@link #resolveFieldTagName(ModelField)}). + * If the argument is a flat association it will return the tagName of the collection. + * @param field the field we are looking for the tag name. + * @return the tag name to use + */ + private String resolveOuterFieldTagName( ModelField field ) + { + String tagName = resolveFieldTagName( field ); + + if ( isFlatAssociation( field ) ) + { + tagName = resolveInnerAssociationTagName( (ModelAssociation) field ); + } + return tagName; + } + + /** + * Compute the inner tagName of a wrapped Style association.
+ * The tagName returned is the name of the nested tag in an association. + * If the given association is not a * multiplicity association, this method + * will throw an {@link IllegalArgumentException}, because inner tagName + * has no signification outside of * mutliplicity association. + * @param association the association we want the inner tagName of + * @return the inner tag name to use + */ + private String resolveInnerAssociationTagName( ModelAssociation association ) + { + XmlFieldMetadata metadata = (XmlFieldMetadata) association.getMetadata( XmlFieldMetadata.ID ); + + if ( !isManyAssociation( association ) ) + { + throw new IllegalArgumentException( + "Thid method should be called only with an association having multiplicity *." ); + } + + String innerTagName = singular( resolveFieldTagName( association ) ); + + if ( metadata != null && StringUtils.isNotEmpty( metadata.getAssociationTagName() ) ) + { + innerTagName = metadata.getAssociationTagName(); + } + return innerTagName; + } + + /** + * Compute the tagName of a modelClass.
+ * This method should only be called with the root class, since the + * tagName of a given class is given by the association it is involved in. + * @param modelClass the class we are looking for the name + * @return the tag name to use + */ + private String resolveClassTagName( ModelClass modelClass ) + { + XmlClassMetadata metadata = (XmlClassMetadata) modelClass.getMetadata( XmlClassMetadata.ID ); + + String tagName = uncapitalise( modelClass.getName() ); + + if ( metadata != null && StringUtils.isNotEmpty( metadata.getTagName() ) ) + { + tagName = metadata.getTagName(); + } + return tagName; + } + + private boolean isInnerAssociation( ModelField field ) + { + boolean innerAssociation = field instanceof ModelAssociation + && isClassInModel( ( (ModelAssociation) field ).getTo(), getModel() ); + + return innerAssociation; + } + + private boolean isManyAssociation( ModelField field ) + { + boolean manyAssociation = false; + + if ( isInnerAssociation( field ) ) + { + ModelAssociation assoc = (ModelAssociation) field; + + manyAssociation = ModelAssociation.MANY_MULTIPLICITY.equals( assoc.getMultiplicity() ); + } + + return manyAssociation; + } + + private boolean isWrappedAssociation( ModelField field ) + { + + boolean wrappedAssociation = false; + + if ( isManyAssociation( field ) ) + { + + XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); + + wrappedAssociation = XmlFieldMetadata.LIST_STYLE_WRAPPED.equals( fieldMetadata.getListStyle() ); + + } + + return wrappedAssociation; + + } + + /** + * Wether the given field is an instance of flat association or not.
+ * Also checks that to class is included in model. + * @param field the field to check + * @return true if the field is an flat association, false otherwise. + */ + private boolean isFlatAssociation( ModelField field ) + { + + boolean flatAssociation = false; + + if ( isManyAssociation( field ) ) + { + + XmlFieldMetadata fieldMetadata = (XmlFieldMetadata) field.getMetadata( XmlFieldMetadata.ID ); + + flatAssociation = XmlFieldMetadata.LIST_STYLE_FLAT.equals( fieldMetadata.getListStyle() ); + + } + + return flatAssociation; + + } + + private boolean isNonRecursiveAssociation( ModelClass modelClass, ModelAssociation association ) + { + + ModelClass fieldModelClass = getModel().getClass( association.getTo(), getGeneratedVersion() ); + + boolean recursiveAssociation = ( modelClass.getName().equals( fieldModelClass.getName() ) ) + && ( modelClass.getPackageName().equals( fieldModelClass.getPackageName() ) ); + + return !( recursiveAssociation ); + + } + + /** + * Generates the required spacers in the given Stringbuffer. + * @param stringBuffer where to generate the spacers + * @param depth the depth of spacers to generate + */ + private void generateSpacer( StringBuffer stringBuffer, int depth ) + { + for ( int i = 0; i < depth; i++ ) + { + stringBuffer.append( " " ); + } + } }