# PropertyChangeListener generieren mit JAXB (xjc)



## brookman (7. Sep 2011)

Hallo zusammen!

Da ich jetzt schon seit vielen Stunden mit dem obigen Thema am kämpfen bin und nicht mehr weiter weiss, dachte ich, ich könnte es ja mal hier probieren. 

Ich würde gerne aus XML-Schemata Java-Beans generieren, die PropertyChangeListeners enthalten. Dazu benutze ich Maven mit einem JAXB plugin und einem "listener code injector".

Bis jetzt funktioniert auch alles, bis auf Tatsache, dass der injector VetoableChangeListeners statt PropertyChangeListeners generiert.
Beispiel:

```
public void setCol(Integer value) {
        try {
            support.fireVetoableChange("col", this.col, value);
        } catch (PropertyVetoException _x) {
            return;
        }
        this.col = value;
    }
```

Auf Around the World in Java: Java XML Binding with Property Change Support habe ich zwar eine Anleitung gefunden (li:listener>java.beans.PropertyChangeListener</li:listener> im XML einfügen). Allerdings führt dies zu solchen listenern:


```
public void setCol(Integer value) {
        support.firePropertyChange("col", this.col, value);
        this.col = value;
    }
```
anstatt korrekterweise:

```
public void setCol(Integer value) {
        Integer oldValue;
        oldValue = this.col;
        this.col = value;
        support.firePropertyChange("col", oldValue, value);
    }
```


Hier mein POM-Snipplet:
[XML]
<!-- JAXB class generation plugin configuration (XSD -> Java) -->
			<plugin>
				<groupId>org.jvnet.jaxb2.maven2</groupId>
				<artifactId>maven-jaxb2-plugin</artifactId>
			<!--  <version>0.7.3</version>-->
				<executions>
					<execution>
						<id>generate-java-classes</id>
						<phase>generate-sources</phase>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>

				<configuration>
					<episode>false</episode>
					<extension>true</extension>
					<removeOldOutput>true</removeOldOutput>
					<clearOutputDir>true</clearOutputDir>
					<forceRegenerate>true</forceRegenerate>
					<strict>true</strict>

					<args>
						<arg>-Xinject-listener-code</arg>
						<arg>-Xboolean-getter</arg>
					</args>
					<plugins>
						<plugin>
							<groupId>org.jvnet.jaxb2_commons</groupId>
							<artifactId>gettersetter</artifactId>
							<version>0.4.1.5</version>
						</plugin>
					</plugins>


					<includeBindings>
						<includeBinding>src/main/resources/schemas/bindings.xjb</includeBinding>
					</includeBindings>
					<schemaDirectory>src/main/resources/schemas</schemaDirectory>
					<schemaIncludes>
						<include>**/*.xsd</include>
					</schemaIncludes>

				</configuration>

				<dependencies>
					<dependency>
						<groupId>com.sun.xml.bind</groupId>
						<artifactId>jaxb-xjc</artifactId>
						<version>2.1.13</version>
					</dependency>
					<dependency>
						<groupId>org.jvnet.jaxb2-commons</groupId>
						<artifactId>property-listener-injector</artifactId>
						<version>1.1-SNAPSHOT</version>
						<exclusions>
							<exclusion>
								<groupId>com.sun.xml.bind</groupId>
								<artifactId>jaxb-xjc</artifactId>
							</exclusion>
						</exclusions>
					</dependency>
				</dependencies>
			</plugin>
[/XML]

Hier das "general.xsd", welches von allen anderen XSDs eingebunden wird (mit besagter Listener-konfiguration):
[XML]<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:ci="http://jaxb.dev.java.net/plugin/listener-injector" jaxb:version="2.1"
	jaxb:extensionBindingPrefixes="xjc ci" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="general" xmlns:general="general">

	<xs:annotation>
		<xs:appinfo>
                        <ci:listener>java.beans.PropertyChangeListener</ci:listener>
			<jaxb:globalBindings localScoping="toplevel" collectionType="com.abc.asdf.binding.AdvancedArrayList" generateIsSetMethod="false" enableJavaNamingConventions="false">
				<jaxb:javaType name="java.lang.Integer" xmlType="xs:integer" />
				<jaxb:javaType name="java.lang.Double" xmlType="xs:double" />
				<jaxb:javaType name="java.lang.Boolean" xmlType="xs:boolean" />
				<jaxb:javaType name="java.lang.Long" xmlType="xs:long" />
			</jaxb:globalBindings>
		</xs:appinfo> ...[/XML]

Und schliesslich noch das "bindings.xjb"-File:
[XML]<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="2.0">

	<jaxb:bindings schemaLocation="general.xsd" node="/xsd:schema">
		<jaxb:schemaBindings>
			<jaxbackage name="com.abc.asdf.package" />
		</jaxb:schemaBindings>
	</jaxb:bindings>

...

</jaxb:bindings>
[/XML]

Ich wäre extrem froh, wenn mir jemand helfen könnte. Ich bin dankbar für jeden noch so kleinen Tipp!!!

-brookman


----------



## Wildcard (7. Sep 2011)

JAXB ist derartig umständlich... :autsch:
Zu deinem Problem kann ich dir leider nicht weiterhelfen, aber wenn das bei dir im Bereich des Möglichen ist, würde ich vorschlagen statt JAXB EMF zu verwenden.
EMF ist unter anderem auch ein XML Binding Framework, aber wesentlich komfortabler und mächtiger als JAXB. Listener Support bekommst du dort auch frei Haus (eine elegantere Implementierung als PropertyChangeSupport).
Eclipse Modeling - EMF - Home


----------



## brookman (9. Sep 2011)

Danke für den Tipp!
Leider ist EMF für meine Anwendung nicht wirklich geeignet und völlig übertrieben. Ausserdem bräuchte es wieder Tage fürs customizing (Bean-Super-Klasse, "get-getter" für boolean, etc).
Meine (extrem hässliche) Abhilfe ist nun ein "Skript", welches nach dem Generieren einfach die PropertyChangeListeners ersetzt/umstellt.
In Perl wärs ein 3-Zeiler, hier der Java-Code 

```
public class ChangeListenerConverter {

   public ChangeListenerConverter(File dir) {

      try {
         new FileTraversal() {
            @Override
            public void onFile(final File f) {
               if (f.getAbsolutePath().endsWith(".java")) {
                  String fileContent = readFileAsString(f);

                  String regex = "\\((.*)\\s+value\\)\\s*\\{\\s*support\\.firePropertyChange\\(\"(.*)\".*;\\s*this.*\\s*}";
                  Pattern pattern = Pattern.compile(regex);
                  Matcher matcher = pattern.matcher(fileContent);

                  while (matcher.find()) {
                     String newContent = "(" + matcher.group(1) + " value) {\n        " + matcher.group(1) + " oldValue = this." + matcher.group(2) + ";\n        this." + matcher.group(2)
                           + " = value;\n        support.firePropertyChange(\"" + matcher.group(2) + "\", oldValue, value);\n    }";
                     fileContent = fileContent.replaceFirst(regex, newContent);
                  }

                  writeStringToFile(fileContent, f);
               }
            }
         }.traverse(dir);
      } catch (IOException ignored) {
         // NOP
      }

   }

   public static void main(String[] args) {
      new ChangeListenerConverter(new File(args[0]));
   }

   public class FileTraversal {
      public final void traverse(final File f) throws IOException {
         if (f.isDirectory()) {
            // onDirectory(f);
            for (File child : f.listFiles()) {
               traverse(child);
            }
            return;
         }
         onFile(f);
      }

      // public void onDirectory(final File d) {
      // }

      public void onFile(final File f) {
      }
   }

   public static String readFileAsString(File file) {
      byte[] buffer = new byte[(int) file.length()];
      BufferedInputStream f = null;
      try {
         f = new BufferedInputStream(new FileInputStream(file));
         f.read(buffer);
      } catch (IOException e) {
         return "";
      } finally {
         if (f != null) {
            try {
               f.close();
            } catch (IOException ignored) {
               // NOP
            }
         }
      }
      return new String(buffer);
   }

   public static void writeStringToFile(String string, File file) {
      BufferedOutputStream f = null;
      try {
         f = new BufferedOutputStream(new FileOutputStream(file));
         f.write(string.getBytes());
         f.flush();
         f.close();
      } catch (IOException e) {
      } finally {
         if (f != null) {
            try {
               f.close();
            } catch (IOException ignored) {
               // NOP
            }
         }
      }
   }
}
```

PS:
Das Programm ersetzt dieses Muster:

```
...(XXX value) {
        support.firePropertyChange("YYY", this.YYY, value);
        this.col = value;...
```
Durch dieses:

```
...(XXX value) {
        XXX oldValue = this.YYY;
        this.YYY = value;
        support.firePropertyChange("YYY", oldValue, value);...
```


----------



## mvitz (9. Sep 2011)

Worin genau besteht eigentlich der Vorteil, wenn der listener erst nach dem setzen und nicht davor aufgerufen wird?


----------

