NetBeans: schema2beans library - User Documentation
Version: 1.3.2
Last Updated: 2004/2/18
Author: Cliff Draper, Sun Microsystems/Forte Tools
Abstract:
The schema2beans library (schema2beans.jar file) allows you to
generate a set of java bean classes from a DTD or XML Schema file. This set
of beans can be used to represent an XML file, as a graph of java beans.
You can add, change and remove elements of the graph, merge and compare
graphs and also get events on any change that happens in the graph. Afterwards,
you can write back the graph as an XML document.
The package of this library is org.netbeans.modules.schema2beans
and is composed of a schema2beans runtime (schema2beans.jar) and schema2beans
generator (schema2beansdev.jar).
Contents:
The schema2beans library allows you to:
Generation
To generate a set of beans (which uses the schema2beans runtime)
from a DTD or XML Schema file, use the GenBeans class:
java org.netbeans.modules.schema2beansdev.GenBeans -f myDtdFile.dtd
If you run this from the command line, I highly recommend creating an alias similar to the following csh alias:
alias GenBeans java org.netbeans.modules.schema2beansdev.GenBeans
All generated beans will inherit from a schema2beans class called BaseBean.
If you run the generator without any argument, you get the following
help:
usage:
GenBeans -f filename [-d docRoot] [-t] [-p package] [-r rootDir]
[-sp number] [-mdd filename] [-noe] [-ts] [-veto] [-version]
[-st] [-throw] [-dtd|-xmlschema] [-javabeans] [-validate]
[-propertyevents] [-attrprop] [-delegator] [-commoninterface]
[-premium] [-compile] [-defaultsAccessable] [-auto]
[-useinterfaces] [-geninterfaces] [-keepelementpositions]
[-dumpbeantree filename] [-removeUnreferencedNodes]
[-genDotGraph filename] [-comments] [-checkUpToDate]
[-writeBeanGraph filename] [-readBeanGraph filename]*
where:
-f file name of the DTD
-d DTD root element name (for example webapp or ejb-jar)
-p package name
-r base root directory (root of the package path)
-sp set the indentation to use 'number' spaces instead of
the default tab (\t) value
-mdd provides extra information that the dtd cannot provide.
If the file doesn't exist, a skeleton file is created and
no bean generation happens.
-noe do not throw the NoSuchElement exception when a scalar property
has no value, return a default '0' value instead.
-ts the toString() of the bean returns the full content
of the bean sub-tree instead of its simple name.
-veto generate vetoable properties (only for non-bean properties).
-st standalone mode - do not generate NetBeans dependencies
-throw generate code that prefers to pass exceptions
through instead of converting them to RuntimeException (recommended).
-dtd DTD input mode (default)
-xmlschema XML Schema input mode
-javabeans Generate pure JavaBeans that do not need
any runtime library support (no BaseBean).
-validate Generate a validate method for doing validation.
-propertyevents Generate methods for dealing with property events (always on for BaseBean type).
-attrprop Attributes become like any other property
-delegator Generate a delegator class for every bean generated.
-commoninterface Generate a common interface between all beans.
-premium The "Premium" Package. Turn on what ought to be the default switches (but can't be the default due to backwards compatibility).
-compile Compile all generated classes using javac.
-defaultsAccessable Generate methods to be able to get at default values.
-useInterfaces Getters and setters signatures would any defined interface on the bean.
-genInterfaces For every bean generated, generate an interfaces for it's accessors.
-keepElementPositions Keep track of the positions of elements (no BaseBean support).
-dumpBeanTree filename Write out the bean tree to filename.
-removeUnreferencedNodes Do not generate unreferenced nodes from the bean graph.
-writeBeanGraph Write out a beangraph XML file. Useful for connecting separate bean graphs.
-readBeanGraph Read in and use the results of another bean graph.
-genDotGraph Generate a .dot style file for use with GraphViz.
-comments Process and keep comments (always on for BaseBean type).
-doctype Process and keep Document Types (always on for BaseBean type).
-checkUpToDate Only do generation if the source files are newer than the to be generated files.
-t [parse|gen|all] tracing.
-finder Add a finder method. Format: "on {start} find {selector} by {key}". Example: "on /ejb-jar/enterprise-beans find session by ejb-name".
The bean classes are generated in the directory rootDir/packagePath, where packagePath is built using the package name specified. If the package name is not specified, the doc root element value is used as the default package name. Use the empty string to get no (default) package.
examples: java GenBeans -f ejb.dtd
java GenBeans -f webapp.dtd -d webapp -p myproject.webapp -r /myPath/src
java GenBeans -f webapp.xsd -xmlschema -r /myPath/src -premium
Most of the parameters are optional. Only the file name is mandatory.
With only the file name specified, the generator uses the current directory, and uses the schema docroot value as the package name.
BaseBean style beans
This style of beans generation requires the use of the schema2beans.jar
library at runtime. All generated beans inherit from org.netbeans.modules.schema2beans.BaseBean.
BaseBean is pretty generic and consequently slower and eats more memory
than the
alternative. This is the
default generation style.
Generation of runtimeless beans
To generate a set of beans that do not depend on the schema2beans runtime,
use the GenBeans class.
GenBeans -f myFile.xsd
-javabeans
I've been calling them "pure java beans" since the generated beans are
the sort of beans that you would write if you wrote them by hand. They
tend to be faster and take up less memory than the ones that require the
runtime, since the runtime needs to be more general. However, not
all runtime features are available on these bean that are available on the
BaseBean beans; for instance, merge is only available on BaseBean.
GenBeans options
The only mandatory option is -f, to specify the name of the DTD or
XML Schema file. The generator figures out by itself what is the root
of the DTD or XML Schema graph and uses it as the package name. The current
directory is used as the default root directory, where the generated files
are created.
You might want to specify the exact name to use for the package. To do
that, uses the -p option.
GenBeans -f myDtdFile.dtd
-p org.myProject.myPackage
This creates the bean classes under the directory
./org/myProject/myPackage
(Since the root directory is not specified, the current directory is
used).
To create the package/classes under a specific directory (and not the
current directory), uses the -r option. It specifies the
root directory.
GenBeans -f myDtdFile.dtd
-p org.myProject.myPackage -r /mySrc
This creates the bean classes under the directory
/mySrc/org/myProject/myPackage.
It might happen that the dtd file contains more than one possible doc-root
element (any element that has no parent). For example, the J2EE web-app
DTD has two possible doc-root elements: webapp and url
. When the generator doesn't know which element is the doc-root, it asks
the user to choose one:
GenBeans -f web-app_1_2.dtd
Building the DTD object graph.
DTD Object graph built.
The following elements could be the root of the
document:
1. web-app
2. url
Enter the element that should be used as the root:
Then, the user enters either 1 or 2, to specify the root of the document:
Enter the element that should be used as the root: 1
Using web-app as the root of the document.
created directory .\webapp
Generating class ...
If you already know the doc-root element, you might specify it using
the -d option.
GenBeans -f web-app_1_2.dtd
-d web-app
Building the DTD object graph.
DTD Object graph built.
The following elements could be the root of
the document:
1. web-app <= parameter value
2. url
Using web-app as the root of the document.
Generating class ....
By default, the generator uses one Tab for the indentation. If you
want spaces instead of one Tab, uses the
-sp option.
GenBeans -f myDtdFile.dtd
-p org.myProject.myPackage -sp 3
Generates the classes with an indentation of 3 spaces.
Sometimes, you would like to specify extra information that the dtd cannot
describe. For example, a property might have only a set of well defined
values that the generated class could know and takes advantage of. Any extra
information that cannot be defined in the dtd file is specified in the
extra information file, using the
-mdd option.
When a property has no element set (typically an element tag in the XML
file is not specified), the getter method returns null. When the property
is of scalar type, such as int, the getter method cannot return null. The
default behavior is for the getter method to throw a NoSuchElementException
exception, when there is no value for the property. If you do not want
an exception to be thrown, use the -noe option. The getter method
will return a default zero value instead. Note that the
isNull() method on the BaseBean
class allows to check if a property is null, before trying to get its value.
The default toString() method is implemented in the BaseBean class, and
simply returns the name of the bean. With the -ts option, the generator
adds a toString() method to the generated beans, calling the public method
dumpBeanNode(). This method returns the content of the whole bean sub-tree,
as a String. Therefore the user has the choice between two toString() versions:
the default implementation returning the name of the bean, and a verbose
version returning the whole content of the bean (all its sub-tree content).
The -veto option allows the use of VetoableChangeSupport.
The -st option generates beans that do not need NetBeans (recommended).
The -throw option will generate code that prefers to pass exceptions
through instead of converint them to RuntimeException (recommended).
The -xmlschema option tells GenBeans to parse the schema file
as an XML Schema file.
The -javabeans option turns on generation of java beans that do
not need a runtime (see Generation of runtimeless
beans, above) (recommended).
The -validate option makes it generate
a validate method in every bean which will test how well that bean conforms
to the schema. If it does not conform, then an exception is thrown.
The -propertyevents option makes it generate PropertyChangeEvents.
This is on by default for BaseBean beans.
The -attrProp option makes it generate
attributes into normal java bean properties; that is, the attribute will
have a getter and setter (recommended).
The -delegator option makes it generate an additional class for
every bean where the bean is a delegate of that class. You can set
the superclass to whatever you want it to be (see mdd file).
The -commoninterface option makes it generate a CommonBean.java
interface which is the intersection of all beans' methods. This is
very useful when you want to refer to any particular bean in your graph.
The -premium option is there so that you don't have to type in
10 arguments to get all of the recomended features. This is the "Premium"
Package of arguments. All arguments that are deemed good and most
people should use them will be turned on here. All of the premium
arguments ought to be the default ones, but due to backwards compatibility
can not be the default ones. In the future, additional arguments may
be added to the Premium Package. This means that compatibility is not
guarenteed in the generated beans between version x of schema2bean's -premium
and version y's (recommended).
The -defaultsAccessable option will generate extra methods for the
properties that have defaults and are not indexed. The method name is
fetchDefault{propertyName}.
The -useInterfaces option will have the getters and setters of bean
properties use the first interface that that bean implements.
The -genInterfaces option will generate for every bean an interface
that has all of the getter and setter methods on it. Some developers
may prefer to work with the beans through just these interfaces. One
might use this option with the -useInterfaces option.
The -keepElementPositions option will have the bean keep track of
relative element positions. This can be important if you have a schema
that allows 2 or more elements to be listed interspersed with each other and
their order is significant. E.g., (menu-item | separator)*.
Additional methods for getting at elements by position are added:
- Object fetchChildByPosition(int position)
- int fetchChildCount()
Currently, turning on this option will slow things down, since every add
to an indexed property is preceded by a search to figure out where to put
the value. Note, this is not available for BaseBean.
The -dumpBeanTree filename option will
write the bean tree to filename. This tree can be helpful in understanding
the structure of the schema. Note that this tree is automatically included
in comments in the root element's binding class.
The -genDotGraph filename option will
generate a .dot file for use with GraphViz.
This helps to visualize the structure of the data.
The -removeUnreferencedNodes option will go through and remove any
types defined in the schema that are not referencable from the root. This
will cut down on classes that get generated but not actually used.
The -comments option
will give access to comments in the generated java beans (always on for
the BaseBean type). Without this option on, comments from source
XML are ignored.
The -checkUpToDate
option will make java bean generation occur only if the source files are
newer than the files to be generated. Note that a fair amount of
the schema is processed before it knows the exact file name of all
potentially generated files.
The -writeBeanGraph option will make it write out all types found in the schema. See here.
The -readBeanGraph option will read 1 or more bean graphs and use them to override the types from the schema. See here.
Generated classes
If you look at the generated classes, you'll see that only some elements
of the schema have a corresponding bean class. Basically, if you draw
a tree of the schema elements (see
-dumpBeanTree
or
-genDotGraph if you really want to), each node
of the tree has a bean class and each leaf of the tree is a simple typed
property (String, Boolean, ...). Any bean might be a property of another
bean, following the schema.
The element of the schema which has no parent is, by default, choosen as
the root. The GenBeans command line prints out during generation time which
element has been choosen to be the root. If you look at the bean class generated
for this root element, you'll see in the header comments the tree of the
bean classes, expressed as a tree of beans. This might be useful to
get an idea of what has bean generated.
A generated bean for BaseBean is composed
of the following parts:
- package name
This is the name specified by the -p option or the name of the doc-root
element if the -p option was not specified.
- import section
import org.w3c.dom.*;
import org.netbeans.modules.schema2beans.*;
import java.beans.*;
import java.util.*;
import java.io.*;
import com.sun.xml.tree.*;
- class name
The name of the dtd element, removing any - character and having any
first letter upper-cased. Any bean extends the class BaseBean, and can
therefore takes advantage of its methods. See the BaseBean section for more information.
- declaration
The names of all the properties of the bean expressed as constants.
- constructor
There is only one default constructor.
- accessor methods
See the section about the accessors.
- event methods
See the section about the event.
- graph creation methods
See the section about the graph creation.
- utility methods
The toString() method returns either the subtree content
(option -ts of the generator, or the simple name of the bean).
The addComparator() and removeComparator() static
methods provide a way to customize the comparison of
graphs .
Extra information file
The -
mdd option of the generator is used to specify extra
information about the bean classes. Some information that we might want
to specify to generate the bean classes cannot be expresed in the dtd (for
example the list of valid values that a property can get, or the user-comparator
class to use to merge and compare two graphs). The -mdd option allows to
specify the name of the file containing such extra information.
The extra information file is an XML file that intends to complement what
cannot be expressed in the dtd. Following is the dtd of this extra information
file:
<!ELEMENT metaDD (meta-element*, implements?, extends?,
import*, vetoable?, throw-exceptions?, schemaLocation?, finder*)>
<!ELEMENT meta-element (dtd-name, namespace?, bean-name?, bean-class?,
wrapper-class?, default-value*, known-value*, meta-property*, comparator-class*,
implements?, extends?, import*, user-code?, vetoable?, skip-generation?,
delegator-name?, delegator-extends?, bean-interface-extends?>
<!ELEMENT meta-property (bean-name, default-value*, known-value*,
key?, vetoable?)>
<!ELEMENT dtd-name (#PCDATA)>
<!ELEMENT
namespace (#PCDATA)>
<!ELEMENT bean-name (#PCDATA)>
<!ELEMENT default-value
(#PCDATA)>
<!ELEMENT key EMPTY>
<!ELEMENT known-value
(#PCDATA)>
<!ELEMENT bean-class (#PCDATA)>
<!ELEMENT wrapper-class
(#PCDATA)>
<!ELEMENT comparator-class (#PCDATA)>
<!ELEMENT extends (#PCDATA)>
<!ELEMENT implements (#PCDATA)>
<!ELEMENT user-code
(#PCDATA)>
<!ELEMENT vetoable EMPTY>
<!ELEMENT throw-exceptions EMPTY>
<!-- Automatically set the schemaLocation -->
<!ELEMENT schemaLocation (#PCDATA)>
<!ELEMENT finder (#PCDATA)>
<!ELEMENT bean-interface-extends (#PCDATA)>
The extra information file should contain a meta-element tag for each
dtd element that we want to provide more information about. For
an XML Schema, dtd-name corresponds to the types of elements and not the
element names.
If the generator doesn't find the file specified by the -mdd option, it
generates a default extra information file, based on the dtd.
GenBeans -f myDtdFile.dtd
-mdd myDtdFile.mdd
Building the DTD object graph.
DTD Object graph built.
Using myElement as the root of the document.
The mdd file myDtdFile.mdd doesn't exist.
Should we create it (y/n) ?y
Writing metaDD XML file
If the generator finds the file, it uses it. Therefore, if we run
the same command again:
GenBeans -f myDtdFile.dtd
-mdd myDtdFile.mdd
Building the DTD object graph.
DTD Object graph built.
Using myElement as the root of the
document.
Using the mdd information from myDtdFile.mdd
Generating class .....
The idea is to edit this file, adding any extra information that we
want in the generated bean classes, and run GenBeans using these information.
Be careful if you have an old mdd file an upgrade the schema file. The
meanings of some types may have changed or gone away.
User defined comparators
The
comparator-class element specifies the comparator class
to use when two graphs are compared and merged. Several comparators can
be specified, see the
merge section for more details.
Extending a bean class
The
bean-class element specifies the class that the schema2beans
library should instantiate for this bean, when it builds the bean graph
from an XML file. If this element is not specified, the class name is simply
the name of the bean. If you plan to extend the bean class, you can specify
the extended bean class name.
For example, if the Book bean element has a bean property named Chapter,
the schema2beans generates Book.java and Chapter.java. Therefore, the two
bean classes are Book.class and Chapter.class. If you want to extend the
Chapter bean, you can do the following:
// Define your own Chapter class
public class MyChapter extends Chapter
{ ... }
// Use the new Chapter class as the default Chapter
class to instanciate
<metaDD>
<meta-element>
<dtd-name>chapter</dtd-name>
<bean-name>Chapter</bean-name>
<bean-class>MyChapter</bean-class>
</meta-element>
</metaDD>
Note that you have to generate the beans to make your new extended
class part of the generated beans. If you simply extend a bean class
without regenerating th bean classes, you will be able to create them by
yourself (new MyChapter()), but, when reading an XML stream, the
schema2beans runtime will create the default beans and not your new class.
So, you do need to regenerate the beans to make sure that the new information
you added to the extra information file is part of the generated beans.
This remark is valid for any information you add to this extra information
file: you need to regenerate the beans to make these information part of
the beans.
Another approach to doing this is to create a class (which inherits from
BaseBean if you're using that generation style) and use the
extends property in the
mdd file.
Default value
The
default-value element is used to specify the default value
of a property. Only nonbean properties can use this feature. You might
specify more than one value. If the property is an indexed property, all
the default values will be used to build the default content of the indexed
property. If the property is a single property value, only the first default
value is used. More generally, any property which is not a bean property
(not a node of the graph of beans), should be able to use this element to
specify a default value.
<meta-element>
<bean-name>Book</bean-name>
<dtd-name>book</dtd-name>
<meta-property>
<bean-name>Title</bean-name>
<default-value>no title</default value>
</meta-property>
<meta-property>
<bean-name>Author</bean-name>
<default-value>Smith</default value>
<default-value>Barnes</default value>
</meta-property>
</meta-element>
Let's assume that the generated Book bean has some default values. Doing
the following,
Book b = new Book();
creates a book bean with the default values specified by the default-value
element. If you specified default values at generation time and don't
want to use them when you instanciate the bean, use the following constructor
(only applies to
BaseBean style):
Book b = new Book(Common.NO_DEFAULT_VALUE);
When the schema2beans runtime instanciates a graph from an XML file,
it doesn't use the default values. Only the content of the XML file is
part of the graph (only applies to
BaseBean
style).
The default value element might be specified in the property meta-element
declaration (this default value is then used any time this property is
used), and/or in the meta-property declaration of a property within an element
declaration (this declaration takes precedence over the meta-element declaration).
Well-known values
The
known-value element is used to specify know values that
a property might get (only applies to
BaseBean
style). The idea is to specify these values at generation time, and get
the list of these values at runtime, through a BaseBean method call.
Like the default-value tag, the known-value tag might be specified in
the meta-element declaration and/or in the meta-property declaration. The
latter taking precedence. To get the list of well-know values at runtime,
use the following method call from the BaseBean class:
public Object[] knownValues(String name)
This returns the list of values specified by the known-value elements,
for the property named
name.
These known values are added as an enumeration restriction to the type.
So, if
validation is turned on, the value
of that property must be one of the enumeration values or the validate call
will fail.
Wrapper
The
wrapper-class element is used to tell the schema2beans
generator and runtime which class should be used to represent a single
or indexed property in the graph of beans. A wrapper class can be used only
for #PCDATA dtd-elements (which defaults to the java.lang.String class).
If the property is a bean, it is possible to change the class that gets used in the graph. Also, see
extending a
bean class.
For example, if there is the following declaration in the dtd,
<!ELEMENT date #PCDATA>
The following accessors are generated:
public String getDate();
public
void setDate(String value);
while the default generated Mdd contains:
<meta-element>
<bean-name>Date</bean-name>
<dtd-name>date</dtd-name>
<wrapper-class>String<wrapper-class>
...
</meta-element>
Assuming you created your own MyDate class to handle this element, you
can use it instead of the default String class. To do that, change the
Mdd file:
<meta-element>
<bean-name>Date</bean-name>
<dtd-name>date</dtd-name>
<wrapper-class>MyPackage.MyDate<wrapper-class>
...
</meta-element>
And regenerate the bean classes using this modified mdd file. You'll get
the following accessors:
public MyPackage.MyDate getDate();
public
void setDate(MyPackage.MyDate value);
It is also possible to generate a scalar data type instead of a class type.
Simply specify the scalar type you want to generate. For example, we
could decide that the date element of the above example should be represented
as an integer:
<meta-element>
<bean-name>Date</bean-name>
<dtd-name>date</dtd-name>
<wrapper-class>int<wrapper-class>
...
</meta-element>
After regeneration, we would get:
public int getDate();
public
void setDate(int value);
The wrapper-class can only by used for properties. You cannot use a wrapper
for an attribute. The attributes are always of type String.
The wrapper class represents, in the bean graph, a String element of the
DOM document: when the DOM document is read, the string element is converted
into a wrapper object; when a wrapper object is written in the DOM document
the wrapper object is converted into a String. There are two ways this
can happen, and you have to make sure that the wrapper class you provide
support one of these two ways.
1. Wrapper interface
public interface Wrapper
{
public
String getWrapperValue();
public
void setWrapperValue(String value);
}
If your wrapper class implements this interface (part of the schema2beans
package), the schema2beans runtime uses it to set and get the object value.
2. String constructor
If your wrapper object doesn't implement the Wrapper interface, the
schema2beans runtime looks for a String constructor. If there is one, this
is used to instanciate the object with the proper value. The toString()
method is used to get a String value of the object.
public class_name(String
value)
public
String toString()
If none of these two mechanisms is available, the schema2beans cannot use
the specified wrapper class and throws a runtime exception.
Customizing the class
definition
If you want a generated bean to extend a specific class, and/or to
implement a specific interface, you might specify the class name and
interface name(s) using the
extends and
implements
element names.
You can define the values for all the generated beans (if specified in
the metaDD element declaration) or for some specific elements (specified
in the appropriate meta-element declaration). Any meta-element declaration
takes precedence over the metaDD element declaration. If you specify another
base class for a generated bean, this new base class must derived from the
BaseBean class.
<metaDD>
<extends>MyBaseBean</extends>
<meta-element>
<dtd-name>chapter</dtd-name>
<bean-name>Chapter</bean-name>
<implements>MyInterface1, MyInterface2</implements>
<bean-class>MyChapter</bean-class>
</meta-element>
</metaDD>
Comparison keys
If a meta-property is marked with the key tag, the property is used
by the default comparison algorithm when comparing beans.
The key tag is an empty tag ( <key/>) used to mark a meta-property
element as a key . If there is no meta-property defined as a key
for a meta-element (this is the default scenario), then all meta-property
of the meta-element are considered as keys. However, as soon as one meta-property
is marked as a key, the other meta-properties (not marked as keys) are no
more considered keys.
If the property is not a key, the property is not used when comparing beans.
This tag is a simple way to specify the significant elements that should
be part of the beans comparison, without implementing your own Comparator
bean.
For example, you might consider that the description property of a bean
is not relevant when comparing beans. You might declare keys all properties
but the description property.
The same comparison algorithm and keys are used to compare and merge bean
graphs, therefore, specifying keys for comparison purpose will also impact
how two graphs are merged. If you want to use specific keys for comparison,
but not use these keys to merge bean graphs, you might want to use the
method BeanComparator.enableComparatorsKey() to enable/disable the mdd keys
usage (see the Merge section for more details).
User code
The
user-code element is used to specify java code
that is directly inserted into the generated beans. The generator simply
copy the value of the user-code element and paste the content into the
generated bean. The user-code element can be used within any meta-element
property which is a bean. User code part of a final meta-property element
is ignored and not inserted into the generated beans.
<metaDD>
<meta-element>
<dtd-name>chapter</dtd-name>
<bean-name>Chapter</bean-name>
<bean-class>MyChapter</bean-class>
<user-code>
public String myOwnToString() {
return "Method code specified from the mdd file";
}
</user-code>
</meta-element>
</metaDD>
Vetoable properties
The
vetoable element is used to specify which property
should support the vetoable event. By default, the schema2beans doesn't
generate any vetoable information for the properties. If the generator
is run with the -veto option, it generates vetoable information for any
property which is not a bean (any property that corresponds to a
#PCDATA
element in the
DTD file).
If you wish to generate vetoable information for only some specific properties,
you should use the mdd file and its vetoable element. As soon as you specify
one <vetoable/> element in the mdd file, the -veto option is ignored.
You might specify the </vetoable> element in three different places:
in the metaDD, meta-element or meta-property
.
The metaDD element applies for the whole graph. If you specify
the <vetoable/> element here, any non-bean property of the graph will
be generated with vetoable information. This is the equivalent of the -veto
option.
<metaDD>
<vetoable/>
<meta-element>
<dtd-name>name</dtd-name>
<bean-name>Name</bean-name>
</meta-element>
<meta-element>
...
</meta-element>
</metaDD>
The meta-element information corresponds to either a bean-node
in the graph or a final property. If you specify the <vetoable/>
element and this is a bean-node element, the information will be ignored
(no vetoable information is generated for bean properties). However, if
the element is a final property (which corresponds to a #PCDATA
element in the DTD), vetoable information will be generated every
time this property is used in the graph. For example, if you have a meta-element
'name' used in different places of your graph, specifying the
<vetoable/> element here would make sure that any generated
'name' setter methods would have vetoable information.
<metaDD>
<meta-element>
<dtd-name>name</dtd-name>
<bean-name>Name</bean-name>
<vetoable/>
</meta-element>
</metaDD>
The meta-property information corresponds to a final property.
Specify the <vetoable/> information here if you wish to
generate vetoable information for this specific property.
When the schema2beans generates vetoable information for a property, it
flags the property as possibly using vetaoble events, generates the '
throws PropertyVetoException' clause for the setter methods of this
property (set, add and remove methods) and also makes sure that the appropriate
add/remove listener methods are generated. It is up to the user to create
listeners and register them.
throw-exceptions properties
The
throw-exceptions element tells the software to generate
methods that will throw exceptions. When this property is not in
the mdd file, no exceptions are declared to be thrown, but RuntimeExceptions
are still possible. It is recommended that you turn throw-exceptions
on; this way, you can better see any exceptions coming at you.
Using -wrtieBeanGraph and -readBeanGraph
-readBeanGraph allows
the user to override the types in the schema. For instance,
normally
xsd:integer is mapped to
java.math.BigInteger, but if you
wanted to make it an
int instead, create the following XML:
<?xml version='1.0' encoding='UTF-8' ?>
<bean-graph>
<schema-type-mapping>
<schema-type-namespace>http://www.w3.org/2001/XMLSchema</schema-type-namespace>
<schema-type-name>integer</schema-type-name>
<java-type>int</java-type>
</schema-type-mapping>
</bean-graph>
The
-writeBeanGraph
option will write out all types found in the schema in the format shown
above. This includes the name of the schema element along with
it's namespace, and the java type that was used for it. That file
can then be used as input to another GenBeans -readBeanGraph.
This can be very useful if you want to share types from the first
class generation.
For example, say you've got an XML Schemas a.xsd, b.xsd, and c.xsd.
Both c.xsd & b.xsd imports a.xsd. So, rather than
generate 3 completely separate groups of classes, you could do:
GenBeans -f a.xsd -premium -writeBeanGraph a_beangraph.xml
GenBeans -f b.xsd -premium -readBeanGraph a_beangraph.xml
GenBeans -f c.xsd -premium -readBeanGraph a_beangraph.xml
This will save space and help if
you want to use common objects between graphs. Note that if you
don't use the same generation options in the shared java beans, you
might get compile errors (for example, if you told b.xsd to do
validation, it would assume that the classes from a.xsd have a validate
method too).
Creating the java bean graph
If we were looking for analogy, creating the graph could be compared
to deserializing the XML file into a graph of beans, and writting, as
serializing the graph of beans into an XML file. This section describes
the different ways to create the graph of beans.
There are two ways to create the bean graph for BaseBean: dynamically using
the DDFactory class or using the
createGraph() static method on the root of the beans.
createGraph() method
Let's say, for the following examples, that you generated a set of
beans and that the class
Book is the root bean.
From an InputStream
You might create the graph using an input stream object:
try
{
FileInputStream in = new FileInputStream("mybook.xml");
Book book = Book.createGraph(in);
System.out.println(book.dumpBeanNode());
in.close();
}
catch(IOException
...
By default, the createGraph() method doesn't invoke the validating
parser. Therefore, the parsed XML document is not validated before the
graph is built. If you want to make sure that the XML document is valid
before the graph is built, use the second form of the createGraph() method:
try
{
FileInputStream in = new FileInputStream("mybook.xml");
// Use the validating parser
Book book = Book.createGraph(in, true);
System.out.println(book.dumpBeanNode());
in.close();
}
catch(IOException
...
From a DOM node
This current implementation uses the Sun Java project X (XML technology
services), with its XMLDocument class. Note that the createGraph(doc)
call simply needs a DOM node interface to build the graph (a standard
DOM interface). However, the doc object parameter has to be in fact an
XMLDocument class object (which implements the DOM Node interface) in order
to create new elements. Following is a complete example for building a
graph:
try
{
FileInputStream in = new FileInputStream("mybook.xml");
XmlDocument doc = XmlDocument.createXmlDocument(in, false);
Book book = Book.createGraph(doc);
System.out.println(book.dumpBeanNode());
in.close();
}
catch(IOException
...
From scratch
To create an empty brand new graph:
// Create a brand new graph from scratch
Book book
= Book.createGraph();
System.out.println(book.toString());
or. you can simply new the bean root:
// Create also a brand new graph from
scratch
Book
book = new Book();
System.out.println(book.toString());
Cloning
You can also create a graph from another one, cloning it:
Book book = Book.createGraph(in);
//
Create a new graph using clone()
Book
book2 = (Book)book.clone();
DDFactory
The other way to create the bean graph is using the DDFactory. This
approach consists in registering the doc-root name and bean class in the
DDFactory registry, then asking the DDFactory to build the graph from an
input stream, giving the docroot you are intersted in.
This method is more generic and dynamic, since you can register several
name/classe pairs in the DDFactory registry, and ask for a graph, only
specifying its logical name and the input stream. The DDFactory.create()
method returns a BaseBean object, base class for any generated bean.
DDFactory.register("book", "book.Book");
BaseBean
book = DDFactory.create(in, "book");
System.out.println(book.toString());
Creating bean graph (no runtime style)
The root class has several read methods on it that are all static and return
a filled in root class:
- read(java.io.InputStream in)
- read(org.xml.sax.InputSource in, boolean validate, org.xml.sax.EntityResolver
er, org.xml.sax.ErrorHandler eh)
- readNoEntityResolver(java.io.InputStream in)
The first read method just call the second read method with validate turned
off and no EntityResolvers or ErrorHanlders. The thrid read method lets
you read in the XML without looking up DTDs (which might be done across a
slow network).
Also, every bean has a readNode(org.w3c.dom.Node) method that will read
into the bean the data from that DOM Node.
Editing the bean graph
Properties
You can edit any property of the beans, using the method provided
in the generated beans.
- To set a property, use the setter methods.
void setPropertyName(PropertyType value)
// for nonindexed
void
setPropertyName(int index, PropertyType value)
// for indexed
void
setPropertyName(PropertyType value[]) //
for indexed
Setting a property to null removes the property. If this is an indexed
property, the number of elements of the indexed property remains unchanged
and a getter call on the element returns null. Note that the property
type might be a scalar type, for example int or float (see
Wrapper classes).
- To get a property value, call the getter methods:
PropertyType getPropertyName()
boolean
isPropertyName()
PropertyType getPropertyName(int index)
// for indexed
PropertyType[] getPropertyName() //
for indexed
If the property type is of type boolean, the getter method generated
is
isPropertyName() instead of
getPropertyName().
- To remove/add a property for an indexed property, use the remove/add
method:
int removePropertyName(PropertyType value)
int addPropertyName(PropertyType value)
The remove() method removes an element from an indexed property.
The elements following the removed element will shift to the left, decrementing
their index value by 1.
The add() method adds an element at the end of the indexed property array.
Both methods return the index of the removed/added element.
- To get the number of elements in an indexed property, use the
size method:
int sizePropertyName()
For BaseBean sytle, if you want to copy an element of the graph to insert
it into any other graph (even the same graph), you have to clone it before.
Otherwise, you'll get an exception.
Attributes
I highly recommend using the
option to turn on
generation of getters and setters for attributes which will make things easier
to use.
You can set or get an attribute value using the generic attribute accessor
methods of the BaseBean class (any generated bean derives from this class).
// Attribute setter method for single
and indexed properties
void setAttributeValue(String propName, String name, String value)
void setAttributeValue(String propName, int index, String name,
String value)
// Attribute setter method on the current bean
void setAttributeValue(String name, String value)
// Attribute getter method for single
and indexed properties
String getAttributeValue(String propName, String name)
String getAttributeValue(String propName, int index, String
name)
// Attribute getter method on the current bean
String getAttributeValue(String name)
For example, let's define the following DTD:
<!ELEMENT book (title, chapter+)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT chapter (summary?, paragraph*)>
<!ELEMENT summary (#PCDATA)>
<!ELEMENT paragraph (#PCDATA)>
<!ATTLIST book instock (yes | no) "yes">
<!ATTLIST title lang CDATA #FIXED "en">
<!ATTLIST chapter length CDATA #IMPLIED>
We have defined the following graph:
Book
<instock>
Title - String
<lang>
Chapter[1,n]
<length>
Summary? - String
Paragraph[0,n] -
String
Where,
instock is a enumerated attribute on single type property
(here, on the root of the bean graph),
lang a fixed attribute on
a single type property, and
length an optional attribute on an indexed
property.
The following code reads the attributes:
Book book = Book.createGraph(some input stream for the XML
file...);
String inStock = book.getAttributeValue("Instock");
String inStock3 = book.getAttributeValue("INSTOCK"); // This
throws an exception (attribute not known)
book.setAttributeValue("Instock", "no");
// Set the attribute value to no
book.setAttributeValue("Instock", "maybe");
// This throws an exception (not enumerated value)
String lang = book.getAttributeValue("Title", "Lang");
book.setAttributeValue("Title", "Lang", "fr"); // This throws
en exception (fixed value)
if (book.sizeChapter() > 0)
{
String length = book.getAttributeValue("Chapter",
0, "Length"); // One way to get the attribute
Chapter c = book.getChapter(0);
String length2 = c.getAttributeValue("Length");
// another way to get the attribute
c.setAttributeValue("Length", "200");
String length3 = book.getAttributeValue("Chapter",
0, "Length"); // returns "200"
}
To remove an attribute, set the attribute with the value null.
When an attribute is added, changed or removed, a PropertyChangeEvent is fired (if any PropertyChangeListener
has been defined). The event name contains the name of the property and
the name of the attribute. To extract the property and attribute names from
the fired event, use the event utility methods
. Note that the VetoableChangeListener support is only for properties,
not for attributes. A VetoableChangeListener will never get a PropertyChangeEvent
when an attribute is changed.
When specifying the name of the property or attribute, you can use either
the dtd name or the bean modified
name of the property or attribute.
It is possible to dynamically get the list of all the attributes (set
and non-set) of a property, using the following methods:
String[] getAttributeNames(String propName)
String[] getAttributeNames()
Following the above example, we would have:
String[] a = book.getAttributeNames(); // returns a[0] =
"Instock"
a = book.getAttributes("Title"); // returns a[0] = "Lang"
a = book.getAttributes("Chapter"); // returns a[0] = "Length"
Chapter c = book.getChapter(0);
c.getAttributes(); // returns a[o] = "Length"
Transient attributes
A transient attribute is an attribute that has been added to the
bean structure dynamically, on the fly, when building the bean graph from
the XML document. A transient attribute is an attribute that is not defined
in the DTD file but used in the XML file. Such attributes are marked
TRANSIENT and are always
CDATA and
IMPLIED.
Besides the fact that they are marked transients, they are considered
exactly like the other attributes, created from the DTD file. Both are
part of the list of BaseAttribute returned by the BaseBean.listAttributes()
or BaseProperty.getAttributes() methods. Also, both might generate events
if their values change.
Introspection on BaseBean style
You can dynamically get the list of the properties and attributes
of any bean, using the following methods:
BaseProperty[] listProperties()
BaseProperty getProperty()
BaseProperty getProperty(String propName)
BaseProperty[] listChoiceProperties(String propName)
Iterator listChoiceProperties()
BaseAttribute[] listAttributes()
BaseProperty
BaseProperty is an interface providing the following methods:
String
getName();
// the bean generated name
String
getDtdName();
// the dtd name as it appears in the DTD file
Class
getPropertyClass(); // the class of the property
(for example java.lang.String)
boolean
isIndexed();
// return true if this is an indexed property
boolean
isBean();
// return true if the property is a bean (node in the graph)
int
size();
// number of elts, if this is an indexed property
String[]
getAttributeNames(); // the attributes of the property,
if any
BaseAttribute[] getAttributes();
// the attribute list of this property, if any
String
getFullName(int index); // the full path name of one element of the indexed
the property
String
getFullName(); //
the full path name of the property
int
getInstanceType(); // the type of instance
as declared in the DTD
boolean
isChoiceProperty(); // true if this prop is among
a set of choice properties
BaseProperty[] getChoiceProperties();
// return the whole set of choice properties (if applicable)
boolean
hasName(String name); // true if parameter name matches the
dtd or bean name
The method
getInstanceType() can return one of the
following values:
BaseProperty.INSTANCE_OPTIONAL_ELT
// DTD option ?
BaseProperty.INSTANCE_MANDATORY_ELT
// no option specified in the DTD
BaseProperty.INSTANCE_OPTIONAL_ARRAY
// DTD option *
BaseProperty.INSTANCE_MANDATORY_ARRAY
// DTD option +
If the property is a choice property, the following methods can be
used:
boolean isChoiceProperty();
BaseProperty[] getChoiceProperties()
For example, if the DTD has the following declaration:
<!ELEMENT flavor ((vanilla | berry), extra-sugar?)>
we would get:
BaseProperty bp = ...(BaseProperty)flavor prop...
bp.isChoiceProperty("vanilla");
// true
bp.isChoiceProperty("berry");
// true
bp.isChoiceProperty("extra-sugar");
// false
Also,
BaseProperty bp = (BaseProperty)flavor...
// The array
has two BaseProperty elements: vanilla & berry
BaseProperty[] bps = bp.getChoiceProperties();
Similar methods are defined on the BaseBean class:
// True if the property name
is a choice property
// Use listChoiceProperty(propName)
to get the other properties
boolean isChoiceProperty(String
propName);
// Might return null if not
a choice property
BaseProperty[] listChoiceProperties(String
propName);
// Return an Iterator on a list
of BaseProperty arrays
// This returns
the list of all the choice properties of the current bean property
Iterator listChoiceProperties();
For example:
// Print the list of all the
choice properties of the flavor bean property
Flavor f = myIceCream.getFlavor();
Iterator it = f.listChoiceProperties();
while (it.hasNext())
{
BaseProperty[]
bps = (BaseProperty[])it.next();
for
(int i=0; i<bps.length; i++)
System.out.println(bps[i].getDtdName);
}
BaseAttribute
You have two ways to get the list of the attributes of a property.
The first one is to call
BaseBean.listAttributes(), the
second one is to call
BaseProperty.getAttributes(). Both
method are identicals. They return the list of all the attributes associated
to a property, as an array of BaseAttribute interfaces. The returned array
might be empty but cannot be null.
BaseAttribute[] BaseBean.listAttributes();
BaseAttribute[] BaseBean.listAttributes(String
propName);
BaseAttribute[] BaseProperty.getAttributes();
The
BaseAttribute interface provides the following methods
to get information on an attribute:
public String getName();
// the bean generated name
public String getDtdName();
// the attribute DTD name
public boolean hasName(String name);
// true if the name param match the bean or DTD name
public String[] getValues();
// list of possible values for enum attribute
public String getDefaultValue();
// default value used when the attribute is created
public boolean isEnum();
// same as getType == BaseAttribute.TYPE_ENUM
public boolean isFixed();
// same as getOption == BaseAttribute.OPTION_FIXED
public int getOption();
// see values below
public int getType();
// see values below
public boolean isTransient();
// true if the attribute is transient
The method getOption() can return:
BaseAttribute.OPTION_REQUIRED
BaseAttribute.OPTION_IMPLIED
BaseAttribute.OPTION_FIXED
The method
getType() can return:
BaseAttribute.TYPE_CDATA
BaseAttribute.TYPE_ENUM
BaseAttribute.TYPE_NMTOKEN
BaseAttribute.TYPE_ID
BaseAttribute.TYPE_IDREF
BaseAttribute.TYPE_IDREFS
BaseAttribute.TYPE_ENTITY
BaseAttribute.TYPE_ENTITIES
BaseAttribute.TYPE_NOTATION
The listProperties, listAttributes, getAttributes
methods, BaseProperty and BaseAttribute interfaces
provide enough information to parse any bean graph dynamically, without
prior knowledge of its structure. (See the treeParser() method in the examples section).
Introspection (runtimeless style)
Of course, java.beans.Introspector can be used just like any other
java bean. Here are some additional methods:
- public void changePropertyByName(String name, Object value)
- public Object fetchPropertyByName(String name)
- public {CommonBean}[] childBeans(boolean recrusive) // return
all child beans that are set
- public void childBeans(boolean recursive, java.util.List beans) //
place into the beans list all child beans that are set.
Writing the graph as an XML document (BaseBean
style)
A write() call on any bean will write the entire bean graph as an
XML document on the output stream specified.
book.write(System.out);
Note that there is no method called "read". Instead, the graph is built through
the
createGraph()
method call.
Writing the graph as an XML document (runtimeless style)
The root class has a few write methods on it:
- write(java.io.OutputStream out)
- write(java.io.OutputStream out, String encoding)
- write(java.io.Writer out, String encoding)
If the first 2 write methods are called then an encoding converter is used
to write out the text (if encoding is null, then UTF-8) is used. The
third method trusts that the encoding style used is same as what is passed
in.
Also note that every bean class has a writeNode method that can be used
to write out that bean and all of it's children.
Handling events
Listeners
You can define a
PropertyChangeListener for property events
on any property of the bean graph (single or indexed property, bean or
final type property). You can listen for a specific property or for a set
of properties. For example, listening on the root triggers an event each
time any element of the tree has changed. Note that an event is triggered
if the property content has change, or if any of its attribute has changed
(if there is any).
To listen for a specific property, use the addPropertyChangeListener()
method call. Use either the generic one
to listen on any event or use the method with the property name parameter
to listen for a specific property.
void addPropertyChangeListener(PropertyChangeListener
l)
void addPropertyChangeListener(String
n, PropertyChangeListener l)
To remove a listener, use the remove methods:
void removePropertyChangeListener(PropertyChangeListener
l)
void removePropertyChangeListener(String
n, PropertyChangeListener l)
You can define a VetoableChangeListener for property events
on any final-non-bean property (String, scalar, Wrappers, in other words
#PCDATA in the DTD) of the bean graph (single
or indexed property). You can listen for a specific property or for a set
of properties. For example, listening on the root of the graph fires an
event on the VetoableChangeListener whenever a final property is changed
in the graph. No event is sent when an attribute is changed.
Note that VetoableChangeListener are called before any change
takes place, and PropertyChangeListener are called after the changes
took place.
To listen for a specific property, use the addPropertyChangeListener()
method call. Use either the generic
one to listen on any event or use the method with the property name parameter
to listen for a specific property. The property you wish to listen to
must have been generated to support the vetoable events (either using
the GenBeans -veto option or the mdd <vetoable/> element. See the
mdd vetoable section for details).
void addVetoableChangeListener(VetoableChangeListener
l)
void addVetoableChangeListener(String
n, VetoableChangeListener l)
To remove a listener, use the remove methods:
void removeVetoableChangeListener(VetoableChangeListener
l)
void removeVetoableChangeListener(String
n, VetoableChangeListener l)
So, if can do something like:
myListener = new MyVetoableChangeListener();
myGraphRoot = GraphBeanRoot.createGraph(someXMLInputStream);
// Any change on a vetoable property anywhere
in the graph