Tuesday, August 31, 2010

More Typesafe JPA2 mappedBy

I've been eating my own dog food lately, reworking some of my client projects to use the annotation processor suggested by Dan Allen. It's been working out nicely, allowing me to refactor...

public class Person {

   public String getName() { ... }

   @UiComesAfter( "name" )
   public Set<Address> getAddresses() { ... }
}

...into...

public class Person {

   public String getName() { ... }

   @UiComesAfter( Person_.name )
   public Set<Address> getAddresses() { ... }
}

But today I realised it has another cool side effect. I can refactor...

public class Person {

   public String getName() { ... }

   @UiComesAfter( Person_.name )
   @OneToMany( mappedBy = "person" )
   public Set<Address> getAddresses() { ... }
}

...into...

public class Person {

   public String getName() { ... }

   @UiComesAfter( Person_.name )
   @OneToMany( mappedBy = Address_.person )
   public Set<Address> getAddresses() { ... }
}

...giving me more typesafe (well, typo-safe :) JPA2 annotations! The existing JPA2 metamodel, being designed mainly for Criteria queries, doesn't seem to support this yet. But I don't see why it couldn't be added for a future release?

Comments welcome!

Wednesday, August 4, 2010

Customizing Which Form Fields Are Displayed

That concludes my little series on customizing which field forms are displayed in Metawidget. I've tried to show that field ordering is a suprisingly deep topic, with lots of different approaches and preferences. In short, there is no 'right' way to do field ordering. The only 'right' way is for a UI generator to have something simple out-of-the-box, then be sufficiently pluggable that people can adjust it to their architecture and tastes.

To recap, we have explored field ordering:

These are all in addition to Metawidget's out-of-the-box UiComesAfter, ComesAfterInspectionResultProcessor and Stub widgets.

Feedback, and additional field ordering preferences, very welcome!

Tuesday, August 3, 2010

Customizing Which Form Fields Are Displayed: Part 9

Following on from parts 1, 2, 3, 4, 5, 6, 7 and 8, I thought I'd blog some of the other field ordering preferences I've encountered.

This one is from Dan Allen, who wanted to enhance the type safety of @UiComesAfter by using string constants generated by an annotation processor. This is similar to the approach JPA 2 takes. It means you can do:

package com.myapp;

import org.metawidget.inspector.annotation.*;

public class Person {

   public String name;

   @UiComesAfter( Person_.name )
   public int age;

   @UiComesAfter( Person_.age )
   public boolean retired;

   @UiLarge
   public String notes;
}

Where Person_ is an interface generated by an annotation processor. Behind the scenes, the generated interface looks like this:

package com.myapp;

import javax.annotation.*;

/**
* Generated interface to allow typesafe references to property names, useful for
* Metawidget's @UiComesAfter.
*/

@Generated( "com.myapp.PropertyNameAnnotationProcessor" )
public interface Person_ {

   static final String age = "age";
   static final String name = "name";
   static final String notes = "notes";
   static final String retired = "retired";
}

A neat feature of this approach is you can integrate it tightly with your IDE. Let's begin with the hard bit, the annotation processor itself. Example implementation below. Points to note:
  • I've been a bit more thorough than in Part 8 and added support for superclasses.

  • You'll need to decide your preference for identifying your model classes, because you don't want this annotation processor generating interfaces for every class! Ideas include: annotating each model class with some top level annotation (such as @Model ) and adjusting the @SupportedAnnotationTypes( "*" ) below; or changing the implementation of the isModelClass method below to scan for your preferred package names.
package com.myapp;

import java.io.*;
import java.util.*;

import javax.annotation.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.Diagnostic.*;

@SupportedAnnotationTypes( "*" )
@SupportedSourceVersion( SourceVersion.RELEASE_6 )
public class PropertyNameAnnotationProcessor
   extends AbstractProcessor {

   private final static char CLASS_NAME_SUFFIX = '_';

   @Override
   public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv ) {

      try {
         for ( TypeElement typeElement : ElementFilter.typesIn( roundEnv.getRootElements() ) ) {

            writeType( typeElement );
         }
      } catch ( Throwable t ) {
         processingEnv.getMessager().printMessage( Kind.ERROR, t.getMessage() );
      }

      return true;
   }

   protected boolean isModelClass( TypeElement typeElement ) {

      return typeElement.getQualifiedName().toString().startsWith( "com.myapp." );
   }

   private String writeType( TypeElement typeElement )
      throws IOException {

      // If the type has properties...

      Set<String> propertyNames = readProperties( typeElement );

      if ( propertyNames == null ) {
         return null;
      }

      // ...check if needs to extend a superclass...

      String superclassName = null;

      if ( typeElement.getSuperclass().getKind() == TypeKind.DECLARED ) {

         TypeElement superclassElement = (TypeElement) ( (DeclaredType) typeElement.getSuperclass() ).asElement();

         // (note: we don't actually writeType() for the superclass, else we'll get a
         // "Attempt to recreate a file for type" from javac)

         if ( readProperties( superclassElement ) != null ) {
            superclassName = superclassElement.getQualifiedName().toString();
         }
      }

      // ...then generate a new source file..

      String qualifiedName = typeElement.getQualifiedName().toString() + CLASS_NAME_SUFFIX;
      PrintWriter writer = new PrintWriter( processingEnv.getFiler().createSourceFile( qualifiedName ).openWriter() );

      try {
         writer.write( "package " );
         int lastDot = qualifiedName.lastIndexOf( '.' );
         writer.write( qualifiedName.substring( 0, lastDot ) );
         writer.write( ";\r\n\r\nimport javax.annotation.*;\r\n\r\n/**" );
         writer.write( "\r\n * Generated interface to allow typesafe references to property names, useful for" );
         writer.write( "\r\n * Metawidget's &#064UiComesAfter." );
         writer.write( "\r\n * <p>" );
         writer.write( "\r\n * <strong>This interface is automatically generated by " );
         writer.write( getClass().getSimpleName() );
         writer.write( "!" );
         writer.write( "\r\n * You do not need to maintain it, and should never edit it.</strong>" );
         writer.write( "\r\n */" );
         writer.write( "\r\n\r\n@Generated( \"" );
         writer.write( getClass().getName() );

         // ...containing an interface...

         writer.write( "\" )\r\npublic interface " );
         writer.write( qualifiedName.substring( lastDot + 1, qualifiedName.length() ) );

         if ( superclassName != null ) {
            writer.write( "\r\n\textends " );
            writer.write( superclassName );
            writer.write( CLASS_NAME_SUFFIX );
         }

         writer.write( " {\r\n" );

         // ...write all its properties as constants...

         for ( String propertyName : propertyNames ) {

            writer.write( "\r\n\tstatic final String " );
            writer.write( propertyName );
            writer.write( " = \"" );
            writer.write( propertyName );
            writer.write( "\";" );
         }

         // ...and close it

         writer.write( "\r\n}" );
      } finally {
         writer.close();
      }

      return qualifiedName;
   }

   private Set<String> readProperties( TypeElement typeElement ) {

      // If the type is public...

      if ( !typeElement.getModifiers().contains( Modifier.PUBLIC ) ) {
         return null;
      }

      // ...and not generated...

      if ( typeElement.getAnnotation( Generated.class ) != null ) {
         return null;
      }

      // ...and not a model class...

      if ( !isModelClass( typeElement ) ) {
         return null;
      }

      // ...and not an enum...

      if ( typeElement.getKind() == ElementKind.ENUM ) {
         return null;
      }

      // ...and not an interface...

      if ( typeElement.getKind() == ElementKind.INTERFACE ) {
         return null;
      }

      // ...and has fields...

      Set<String> propertyNames = new TreeSet<String>();

      for ( VariableElement fieldElement : ElementFilter.fieldsIn( typeElement.getEnclosedElements() ) ) {

         // ...that are public...

         if ( !fieldElement.getModifiers().contains( Modifier.PUBLIC ) ) {
            continue;
         }

         // ...and not static...

         if ( fieldElement.getModifiers().contains( Modifier.STATIC ) ) {
            continue;
         }

         // ...remember them

         propertyNames.add( fieldElement.getSimpleName().toString() );
      }

      // ...or has methods...

      for ( ExecutableElement executableElement : ElementFilter.methodsIn( typeElement.getEnclosedElements() ) ) {

         // ...that are public...

         if ( !executableElement.getModifiers().contains( Modifier.PUBLIC ) ) {
            continue;
         }

         // ...and not static...

         if ( executableElement.getModifiers().contains( Modifier.STATIC ) ) {
            continue;
         }

         // ...that 'look' like a getter...

         if ( executableElement.getParameters().size() != 0 ) {
            continue;
         }

         if ( executableElement.getReturnType().getKind() == TypeKind.VOID ) {
            continue;
         }

         // ...and are not an override of a superclass getter...

         if ( executableElement.getAnnotation( Override.class ) != null ) {
            continue;
         }

         // ...that are legal property names...

         String simpleName = executableElement.getSimpleName().toString();

         if ( simpleName.startsWith( "get" ) ) {
            simpleName = simpleName.substring( "get".length() );
         } else if ( simpleName.startsWith( "is" ) ) {
            simpleName = simpleName.substring( "is".length() );
         } else {
            continue;
         }

         simpleName = Character.toLowerCase( simpleName.charAt( 0 ) ) + simpleName.substring( 1 );

         // ...remember them

         propertyNames.add( simpleName );
      }

      // If no properties remembered, do not generate anything

      if ( propertyNames.isEmpty() ) {
         return null;
      }

      return propertyNames;
   }
}

Phew! This is ready to run, but for extra kudos let's integrate it into Eclipse. First you need to create a file under META-INF/services called javax.annotation.processing.Processor. The file contains just one line:

com.myapp.PropertyNameAnnotationProcessor

Next you need to package this file and the PropertyNameAnnotationProcessor.class into a JAR. Here's an Ant script:

<project name="property-name-processor" default="pack">

   <property name="builddir" value="./build"/>
      
   <target name="pack">
      
      <delete dir="${builddir}"/>
      <mkdir dir="${builddir}"/>
      
      <javac srcdir="src" destdir="${builddir}">
         <include name="**/*Processor.java"/>
      </javac>

      <jar destfile="${builddir}/property-name-processor.jar">
         <fileset dir="src">
            <include name="META-INF/services/*"/>
         </fileset>
         <fileset dir="${builddir}">
            <include name="**/*Processor.class"/>
         </fileset>
      </jar>

   </target>
   
</project>

Then, in Eclipse, you must enable annotation processing under your Project Properties:

And add this JAR into your Factory Path:
Nearly there! Now just define the Person class:

package com.myapp;

import org.metawidget.inspector.annotation.*;

public class Person {

   public String name;

   @UiComesAfter( Person_.name )
   public int age;

   @UiComesAfter( Person_.age )
   public boolean retired;

   @UiLarge
   public String notes;
}

And the Main class:

package com.myapp;

import javax.swing.*;
import org.metawidget.swing.*;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }
}

And hit Run!

Saturday, July 31, 2010

Customizing Which Form Fields Are Displayed: Part 8

Following on from parts 1, 2, 3, 4, 5, 6 and 7, I thought I'd blog some of the other field ordering preferences I've encountered.

This time I want to look at another 'universal' ordering. Like the JavassistPropertyStyle one, this one orders fields based on the way they are declared in the source code. However this time we're going to use a Java 6 annotation processor to statically pre-generate a helper class that stores the field order. The generated helper class will look like this:

package com.myapp;

class Person_FieldOrder {

   public final static String[] FIELD_ORDER = new String[] { "name", "age", "retired", "notes" };
}

We'll then add a custom FieldOrderPropertyStyle to lookup this helper class and order the fields. Example implementation below.

First a Person class. We'll use a top-level class this time, rather than an inner class, because that way our annotation processor doesn't have to search inner classes:

package com.myapp;

import org.metawidget.inspector.annotation.UiLarge;

public class Person {

   public String name;

   public int age;

   public boolean retired;

   @UiLarge
   public String notes;
}

Next the meat of the example: the annotation processor. Despite their name, annotation processors don't require an annotation to process! By declaring our processor as @SupportedAnnotationTypes( "*" ) we get to inspect every type as it passes through the javac compiler. Then it's just a case of 'walking the tree' and generating a source file containing each type's fields in order:

package com.myapp;

import java.io.*;
import java.util.*;

import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.util.*;
import javax.tools.Diagnostic.*;

@SupportedAnnotationTypes( "*" )
@SupportedSourceVersion( SourceVersion.RELEASE_6 )
public class FieldOrderProcessor
   extends AbstractProcessor {

   @Override
   public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv ) {

      // For each public type...

      try {
         for ( TypeElement typeElement : ElementFilter.typesIn( roundEnv.getRootElements() ) ) {

            if ( !typeElement.getModifiers().contains( Modifier.PUBLIC ) ) {
               continue;
            }

            // ...if the type has fields...

            List<VariableElement> fields = ElementFilter.fieldsIn( typeElement.getEnclosedElements() );

            if ( fields.isEmpty() ) {
               continue;
            }

            // ...start a new source file..

            String qualifiedName = processingEnv.getElementUtils().getBinaryName( typeElement ).toString();
            int lastDot = qualifiedName.lastIndexOf( '.' );
            String packageName = qualifiedName.substring( 0, lastDot );
            String simpleName = qualifiedName.substring( lastDot + 1, qualifiedName.length() );
            simpleName += "_FieldOrder";
            PrintWriter writer = new PrintWriter( processingEnv.getFiler().createSourceFile( simpleName ).openWriter() );

            try {
               writer.write( "package " );
               writer.write( packageName );
               writer.write( "; class " );
               writer.write( simpleName );
               writer.write( " { public final static String[] FIELD_ORDER = new String[] { " );

               // ...write all its public fields in order...

               boolean first = true;

               for ( VariableElement fieldElement : fields ) {

                  if ( !fieldElement.getModifiers().contains( Modifier.PUBLIC ) ) {
                     continue;
                  }

                  if ( first ) {
                     first = false;
                  } else {
                     writer.write( ", " );
                  }

                  writer.write( "\"" );
                  writer.write( fieldElement.getSimpleName().toString() );
                  writer.write( "\"" );
               }

               // ...and close it

               writer.write( " }; }" );
            } finally {
               writer.close();
            }

            processingEnv.getMessager().printMessage( Kind.NOTE, getClass().getSimpleName() + " generated " + packageName + "." + simpleName );
         }
      } catch ( IOException e ) {
         processingEnv.getMessager().printMessage( Kind.ERROR, e.getMessage() );
      }

      return false;
   }
}

Phew! That's the hard bit over. Now we need a custom PropertyStyle to recognize these new xxx_FieldOrder classes. It extends JavaBeanPropertyStyle and is very similar to how JavassistPropertyStyle is implemented internally:

package com.myapp;

import java.util.Map;

import org.metawidget.inspector.iface.*;
import org.metawidget.inspector.impl.propertystyle.*;
import org.metawidget.inspector.impl.propertystyle.javabean.*;
import org.metawidget.util.*;

public class FieldOrderPropertyStyle
   extends JavaBeanPropertyStyle {

   @Override
   protected Map<String, Property> inspectProperties( Class<?> clazz ) {

      try {
         // For each set of JavaBean properties...

         Map<String, Property> properties = super.inspectProperties( clazz );

         // ...look up our annotation-processor-created class...

         Class<?> fieldOrderClass = Class.forName( clazz.getName() + "_FieldOrder" );
         String[] fieldOrders = (String[]) fieldOrderClass.getField( "FIELD_ORDER" ).get( null );

         // ...and sort them

         Map<String, Property> sortedProperties = CollectionUtils.newLinkedHashMap();

         for ( String fieldOrder : fieldOrders ) {
            Property property = properties.get( fieldOrder );

            if ( property != null ) {
               sortedProperties.put( fieldOrder, property );
            }
         }

         return sortedProperties;
      } catch ( Exception e ) {
         throw InspectorException.newException( e );
      }
   }
}

Finally we put it all together into a Swing app. This is very similar to the one in part 7, except using FieldOrderPropertyStyle instead of JavassistPropertyStyle:

package com.myapp;

import javax.swing.JFrame;

import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.SwingMetawidget;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      BaseObjectInspectorConfig config = new BaseObjectInspectorConfig().setPropertyStyle( new FieldOrderPropertyStyle() );
      metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig().setInspectors(
            new PropertyTypeInspector( config ),
            new MetawidgetAnnotationInspector( config ) ) ) );
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }
}

Now to run it! This is trickier than normal, because you have to pre-compile the annotation processor then compile the rest of the code whilst applying that annotation processor. In case your IDE doesn't support this sort of thing, here's an Ant script:

<project name="field-order-processor" default="pack">

   <property name="builddir" value="./build"/>
   <property name="classpath" value="${builddir};/metawidget-0.99/metawidget.jar"/>
      
   <target name="pack">
      
      <delete dir="${builddir}"/>
      <mkdir dir="${builddir}"/>
      
      <javac srcdir="src" destdir="${builddir}">
         <include name="**/*Processor.java"/>
      </javac>

      <javac srcdir="src" destdir="${builddir}" classpath="${classpath}" target="1.6">
         <compilerarg value="-processor"/>
         <compilerarg value="com.myapp.FieldOrderProcessor"/>
      </javac>
      
      <java classname="com.myapp.Main" classpath="${classpath}" fork="yes"/>
      
   </target>
   
</project>

Monday, July 26, 2010

Dynamic Form Generator: Metawidget v0.99

Version 0.99 of Metawidget the dynamic form generator is now available, focused on tightening the codebase ready for our v1.0 release! This release includes the following enhancements:
Special thanks to Bernhard Huber and Stuart Douglas for their help with this release!

As always, the best place to start is the Reference Documentation:

http://metawidget.org/doc/reference/en/pdf/metawidget.pdf

Your continued feedback is invaluable to us. Please download it and let us know what you think.

Breaking Changes

There have, unfortunately, been some minor breaking changes with this release compared to v0.95. Specifically:

  • The InspectionResultProcessor interface now works with Strings, not DOM Documents, to maximize the number of scenarios it is applicable to. The new DomInspectionResultProcessor interface can be used to work with Documents

Customizing Which Form Fields Are Displayed: Part 7

Following on from parts 1, 2, 3, 4, 5 and 6, I thought I'd blog some of the other field ordering preferences I've encountered.

This time I want to look at 'universal' ordering that sits not just above per-screen, but also above per-domain. An obvious one, though rarely useful, is alphabetical ordering. This is what JavaBeanPropertyStyle implements by default. Another interesting one is 'source code line number' ordering. Source code line numbers are not usually available in JVM class files, but if you have a technology like Javassist on your classpath you can plug that into Metawidget to get field ordering for free!

Example implementation below. Some points to note:
  • It's in Swing so you can just cut and paste and run it

  • It uses JavassistPropertyStyle (no annotations, Inspectors or InspectionResultProcessors this time!)

  • You must have Javassist on your classpath

  • You must compile your code in debug mode so that line numbering is included
package com.myapp;

import javax.swing.*;

import org.metawidget.inspector.annotation.*;
import org.metawidget.inspector.composite.*;
import org.metawidget.inspector.impl.*;
import org.metawidget.inspector.impl.propertystyle.javassist.*;
import org.metawidget.inspector.propertytype.*;
import org.metawidget.swing.*;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      BaseObjectInspectorConfig config = new BaseObjectInspectorConfig().setPropertyStyle( new JavassistPropertyStyle() );
      metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig()
         .setInspectors(
               new PropertyTypeInspector( config ),
               new MetawidgetAnnotationInspector( config )
         )));
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }

   static class Person {

      private String mName;
      private int mAge;
      private boolean mRetired;
      private String mNotes;

      public String getName() {
         return mName;
      }

      public void setName( String name ) {
         mName = name;
      }

      public int getAge() {
         return mAge;
      }

      public void setAge( int age ) {
         mAge = age;
      }

      public boolean isRetired() {
         return mRetired;
      }

      public void setRetired( boolean retired ) {
         mRetired = retired;
      }

      @UiLarge
      public String getNotes() {
         return mNotes;
      }

      public void setNotes( String notes ) {
         mNotes = notes;
      }
   }
}

Tuesday, July 20, 2010

Customizing Which Form Fields Are Displayed: Part 6

Following on from parts 1, 2, 3, 4 and 5, I thought I'd blog some of the other field ordering preferences I've encountered.

This one is for those who want field ordering at the global (ie. application) level, but to exclude fields on a per-screen basis. Example implementation below. Some points to note:
  • It's in Swing so you can just cut and paste and run it

  • It defines an ExcludingInspectionResultProcessor that excludes the properties/actions

  • It uses this in a chain with the usual ComesAfterInspectonResultProcessor. Note that unlike Part 2 it adds to the chain after ComesAfterInspectonResultProcessor. This is so that excluding fields does not impact the UiComesAfter ordering. You may prefer this the other way around
package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;

import javax.swing.*;

import org.metawidget.inspectionresultprocessor.iface.*;
import org.metawidget.inspectionresultprocessor.sort.*;
import org.metawidget.inspector.annotation.*;
import org.metawidget.swing.*;
import org.metawidget.util.*;
import org.w3c.dom.*;

public class Main {

   public static void main( String[] args ) {

      Person person = new Person();

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.addInspectionResultProcessor( new ComesAfterInspectionResultProcessor<SwingMetawidget>() );
      metawidget.addInspectionResultProcessor( new ExcludingInspectionResultProcessor() );
      metawidget.putClientProperty( "exclude", new String[] { "retired", "age" } );
      metawidget.setToInspect( person );

      JFrame frame = new JFrame( "Metawidget Tutorial" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }

   static class Person {

      public String   name;

      @UiComesAfter( "name" )
      public int      age;

      @UiComesAfter( "age" )
      public boolean   retired;

      @UiComesAfter( "retired" )
      @UiLarge
      public String   notes;
   }

   static class ExcludingInspectionResultProcessor
      implements InspectionResultProcessor<SwingMetawidget> {

      public String processInspectionResult( String inspectionResult, SwingMetawidget metawidget, Object toInspect, String type, String... names ) {

         String[] excludes = (String[]) metawidget.getClientProperty( "exclude" );
         Document document = XmlUtils.documentFromString( inspectionResult );
         Element entity = (Element) document.getDocumentElement().getFirstChild();

         for ( int loop = 0; loop < entity.getChildNodes().getLength(); ) {

            Element trait = (Element) entity.getChildNodes().item( loop );

            if ( !ArrayUtils.contains( excludes, trait.getAttribute( NAME )))
            {
               loop++;
               continue;
            }

            entity.removeChild( trait );
         }

         return XmlUtils.documentToString( document, false );
      }
   }

}