# JAXB / Listen durchlaufen



## Fawkes (26. Nov 2010)

Hallo,
ich hänge momentan an einem kleinen Problem.
Zur Situation, ich habe einen XML-Baum (verwende JAXB) und möchte diesen nun durchlaufen um einen Weg zu finden. (Hatte dazu einen anderen Thread: http://www.java-forum.org/softwareentwicklung/109545-weg-zwischen-2-knoten-xml-jaxb.html)

Ich habe verschiedene Objekte "Switch", jedes dieser Objekte hat eine Liste von Ports, an denen andere Switches hängen. Ich habe die Struktur mal beispielhaft skizziert:







Mein Problem ist es jetzt, das Suchen vom Weg passend zu implementieren. Ich muss den Weg von 2 Knotenpunkten zur Wurzel bestimmen. D.h. ich muss zunächst den Baum/die Listen durchlaufen um das  Element zu finden. Dann muss ich das Vaterelement von diesem herausfinden, dann das Vaterelement vom Vaterelement usw.

Die genaue Struktur wäre:
Eine Liste Ports, jeder Port hat ein Element Kabel, jedes Element Kabel hat ein Element Device oder Switch.
Ich starte also mit meiner Liste von Ports(rootSwitchPorts) vom Rootelement. Diese durchlaufe ich und untersuche für jeden Port u in rootSwitchPorts zunächst ob ein Switch oder ein Device enthalten sind und falls Device ob u.getCable().getDevice().getID() == gesuchte ID ist. Bis hierhin klappt dann auch alles. Das Problem ist jetzt das Zusammensetzen/bzw. das Herausfinden der Vaterelemente. 

Ist u.getCable().getDevice().getID() == sourceID soll dieses also mein erstes Element in der Liste werden, danach muss ich die ID des Elements herausfinden in dessen Liste u ich mich gerade befinde.
Beispielhaft nochmal an der Skizze von vorhin (Wobei hier dann nur der Weg von 5 zu 1 gesucht wird):





Und mal ein kleiner Ansatz von mir (Wobei hier wie gesagt bisher nur die Suche nach dem Element mit entsprechender sourceID richtig klappt):

```
public List<Integer> createPath(List<PortType> rootSwitchPorts, int sourceID){
		List<Integer> tempSwitchPorts = new ArrayList<Integer>();
		for(PortType u : rootSwitchPorts){ //currentSwitch = rootSwitch
			//System.out.println(u.getCable().getSwitch().getID());
			if(u.getCable().getDevice() != null)
			if(u.getCable().getDevice().getID() == sourceID)
			{
				//System.out.println(u.getCable().getDevice().getID());
				tempSwitchPorts.add(u.getCable().getDevice().getID());
				return tempSwitchPorts;
			}
			if(u.getCable().getSwitch() != null)
			{
			tempSwitchPorts = createPath(u.getCable().getSwitch().getPort(), sourceID);
			if(tempSwitchPorts.get(0) != null)
				if(tempSwitchPorts.get(0) == sourceID)
					tempSwitchPorts.add(u.getCable().getSwitch().getID());
			}

		}
		tempSwitchPorts.add(-1);
		return tempSwitchPorts;
	}
```

Und hier noch das entsprechende XML Schema:
[XML]<?xml version="1.0" encoding="ISO-8859-1"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="Switch" type="SwitchType"/>
<xsd:element name="Device" type="DeviceType"/>

<xsd:complexType name="SwitchType">
	<xsd:sequence>
		<xsd:element name="Name" type="xsd:string"/>
		<xsd:element name="Delay" type="xsd:int"/>
		<xsd:element name="Jitter" type="xsd:int"/>
		<xsd:element name="Port" type="PortType" minOccurs="3" maxOccurs="unbounded"/>
	</xsd:sequence>
	<xsd:attribute name="ID" type="xsd:int"/>
	<xsd:attribute name="IncomePortNumber" type="xsd:int"/>
</xsd:complexType>

<xsd:complexType name="PortType">
	<xsd:sequence>
		<xsd:element name="Cable" type="CableType"/>
	</xsd:sequence>
	<xsd:attribute name="PortNumber" type="xsd:int"/>
</xsd:complexType>

<xsd:complexType name="CableType">
	<xsd:sequence>
		<xsd:element name="Delay" type="xsd:int"/>
		<xsd:element ref="Switch" minOccurs="0" maxOccurs="1"/>
		<xsd:element ref="Device" minOccurs="0" maxOccurs="1"/>
	</xsd:sequence>
	<xsd:attribute name="Name" type="xsd:string"/>
</xsd:complexType>

<xsd:complexType name="DeviceType">
	<xsd:sequence>
		<xsd:element name="Name" type="xsd:string"/>
		<xsd:element name="Delay" type="xsd:int"/>
	</xsd:sequence>
	<xsd:attribute name="ID" type="xsd:int"/>
</xsd:complexType>

</xsd:schema>[/XML]
Könnte mir da jemand einen Tipp geben wie ich vorgehen könnte?
Grüße
Fawkes


----------



## Wildcard (26. Nov 2010)

Nimm doch EMF statt JaxB. EMF Objekte kennen ihren Parent von Haus aus und EMF Modelle bieten unter anderem auch direkt einen Tree Iterator um den Baum Top Down zu durchlaufen (auch wenn du das dann eigentlich gar nicht mehr brauchst).


----------



## Fawkes (26. Nov 2010)

Danke für den Tipp mit EMF, sieht auf den ersten Blick interessant aus.
Allerdings würde ich hier lieber bei JAXB bleiben, da das restliche Projekt ganz darauf basiert. Wen sich das jetzt mit JAXB aber als nicht machbar bzw. nur sehr umständlich zu realisieren herausstellt, werde ich mir EMF doch nochmal genauer ansehen.


----------



## Wildcard (26. Nov 2010)

Machbar ist es natürlich, allerdings wird diese Top Down Suchansatz extrem teuer wenn dein Modell größer wird.
Da EMF generell um Klassen besser als JAXB ist, würde ich es mir schon überlegen...
Falls du dennoch bei JAXB bleibst:
Wenn du häufiger solcher Operationen durchführen musst wird es wesentlich billiger wenn du eine Referenz auf den Parent in deine Klassen einfügst und dann einmal initial über das Modell läufst und den Parent initialisierst.
Dieses initialisieren ist nur wenig teuerer als die Top Down Suche und dafür sparst du bei jeder weiteren Suche sehr viel Rechenzeit.


----------



## Fawkes (26. Nov 2010)

Hm, da hast du wohl recht. Dann werde ich morgen mal versuchen, das ganze auf EMF umzustellen.
Hast du zufällig eine (gute) Referenz, die gerade das Thema xsd->EMF sowie umgekehrt Java->xml behandelt?
Und was noch wichtig wäre: Kann ich per EMF einfach auf bestimmte Elemente in XML-Files zugreifen(z.B. durch ID Attribut) und deren Elemente auslesen?
Ich muss auch per Javacode auf die Elemente der XML-Dateien zugreifen können(z.B. um von jedem Objekt das auf einem Weg zwischen 2 Knoten liegt die Attribute auszulesen)


----------



## Wildcard (26. Nov 2010)

EMF ist kein XML Parser, oder eine Art Dom4J. EMF ist ein Modellierungswerkzeug das unter anderem XML Binding ähnlich wie JAXB bietet, allerdings wesentlich Feature reicher ist.
Um mit XML (XML Binding ist wie gesagt nur ein Teilaspekt von EMF, aber vermutlich der, der dich zZ am meisten interessiert) loszulegen machst du folgendes:
-Eclipse herunterladen falls nicht schon geschehen
-Über den Install Software Dialog Eclipse Modeling Framework SDK und XML Schema Infoset SDK installieren
-Projekt erstellen
-Rechtsklick im Project -> new -> other -> EMF Model -> XML Schema auswählen -> Finish
Danach öffnet sich ein Editor. Mit Rechtsklick -> Generate Model Code werden dann Java Interfaces und Klassen erzeugt.

Um zu verstehen wie man nun XML Dateien lädt und speichert solltest du am besten auch den Test Code generieren lassen. Dadurch wird ein neues Projekt erstellt das Unit Test Stubs und eine Example Klasse enthält die zeigt wie man mit dem Modell arbeitet.

Da es dir ja um den Parent geht:
Jede der generierten Klassen hat eine eContainer() Methode über die du an den Parent des Objekts kommst.
Wenn du das Objekt von einem Knoten im Baum zu einem anderen bewegst, wird die Referenz auf den Parent übrigens automatisch aktualisiert, dein Modell bleibt immer in sich konsistent.
Da sich eContainer() nicht so schön liest wie zB getParent() kann man auch explizit mit 3 Klicks eine Container Referenz modellieren um die API aufzuhübschen, aber besser nicht zu viel auf einmal...
Du kannst dir auch mal den Edit und Editor Code generieren lassen. Damit werden zwei neue Projekte angelegt (Eclipse Plugins) die einen sofort lauffähigen Baumeditor für dein Modell enthalten.
Der Editor kann die Dateien einlesen, anzeigen, manipulieren, speicher, undo, redo,...
Ist ganz nützlich um zB ein paar Beispieldateien zu erstellen.


----------



## Fawkes (26. Nov 2010)

Wow, vielen Dank für die ausführliche Antwort. Ich werde mich dann morgen in die Sache einarbeiten, hört sich auf jeden Fall sehr gut an und scheint von den Funktionalitäten die geboten werden JAXB um einiges voraus zu sein.


----------



## Wildcard (26. Nov 2010)

Ich habe das mal kurz für dein Schema getan.
Die EClasses sollte man wohl umbennen (oder die Types umbennen) um das ...Type wegzubekommen.
Ausserdem scheinst du die numerischen IDs als Referenzen auf andere Objekte zu verwenden?
Das lässt sich in EMF schöner als echte Referenz modellieren, dann geht zB sowas:

```
Port port = switch1.getIncomingPort();
```
anstatt

```
int number = switch1.getIncomingPortNumber();
Port port = lookup(number);
```
Insbesondere für bidirektionale Relationen ist EMF hervorragend geeignet, da es automatisch beide Seiten einer beziehung konsistent hält. Als Beispiel:
Ein Cable referenziert ein Device und einen Switch. In EMF kannst du automatisch dafür sorgen das ein Switch und ein Device auch alle Cables kennen die sie referenzieren.

Aber nun zum Beispiel, ich habe wie gesagt dein Schema importiert und daraus Klassen erzeugt (einen Target Namespace solltest du noch vergeben).
So sieht der Code aus um das Modell zu verwenden:


```
public static void main(String[] args) throws IOException {
		// Create a resource set to hold the resources.
		//
		ResourceSet resourceSet = new ResourceSetImpl();
		
		// Register the appropriate resource factory to handle all file extensions.
		//
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
			(Resource.Factory.Registry.DEFAULT_EXTENSION, 
			 new SwitchmodelResourceFactoryImpl());

		// Register the package to ensure it is available during loading.
		//
		resourceSet.getPackageRegistry().put
			(SwitchmodelPackage.eNS_URI, 
					SwitchmodelPackage.eINSTANCE);
        
		Resource resource = resourceSet.createResource(URI.createFileURI("example.model"));
		SwitchmodelFactory factory = SwitchmodelFactory.eINSTANCE;
		SwitchType switch1 = factory.createSwitchType();
		switch1.setName("Switch1");
		
		PortType port1 = factory.createPortType();
		port1.setPortNumber(4);
		switch1.getPort().add(port1);
		
		CableType cable = factory.createCableType();
		cable.setName("MyCable");
		cable.setDelay(2);
		
		port1.setCable(cable);
		
		DeviceType device = factory.createDeviceType();
		device.setName("Device1");
		device.setID(4);
		cable.setDevice(device);


		resource.getContents().add(switch1);
		resource.save(System.out, null);
      }
```
Und das kommt dabei raus:
[XML]<?xml version="1.0" encoding="ASCII"?>
<SwitchType xmlns="http://example.org/switchmodel">
  <Name>Switch1</Name>
  <Port PortNumber="4">
    <Cable Name="MyCable">
      <Delay>2</Delay>
      <Device ID="4">
        <Name>Device1</Name>
      </Device>
    </Cable>
  </Port>
</SwitchType>
[/XML]


----------



## Wildcard (26. Nov 2010)

Habe den Beitrag korrigiert. Hatte gar nicht gemerkt das Cable ja eigentlich gar kein gülter Root wäre...


----------



## Fawkes (27. Nov 2010)

Ich habe gerade mal alles soweit gemacht wie du bereits geschrieben hattest und dein Beispiel ausprobiert, klappt super  Werde mich dann morgen genauer einarbeiten. Auf jeden Fall schonmal vielen Dank für die tolle Hilfe.

Noch eine Frage: Wie ist das bei EMF den mit dem einlesen von XML Dateien, gibt es auch etwas in der Art von unmarshal in JAXB?

Und dann noch etwas zu Eclipse: Habe gerade versucht per Generate Edit sowie Generate Editor den Baumeditor herzubekommen. Die 2 Projekte (edit/editor) werden auch erstellt, nur das ausführen klappt nicht. Soweit ich das verstanden habe, sollen diese als Eclipse Plugins laufen und man soll sie wohl als Eclipse Application oder Runtime Workbench ausführen, beides ist bei mir aber nicht vorhanden(lediglich Java Application, Java Applet, jUnit Test und Ant Build. Auch die Plugineinstellungen unter Window->Preferences finde ich bei mir nicht. Muss man da noch etwas zusätzlich installieren? (Habe bisher Eclipse 3.5(Galileo) for Java Developers und das EMF SDK 2.5.0 (EMF+XSD))


----------



## Wildcard (27. Nov 2010)

Fawkes hat gesagt.:


> Noch eine Frage: Wie ist das bei EMF den mit dem einlesen von XML Dateien, gibt es auch etwas in der Art von unmarshal in JAXB?


Auf obiges Beispiel gemünzt:


```
Resource resource = resourceSet.getResource(URI.createFileURI("example.model"),true);
SwitchType switch1 = (SwitchType)resource.getContents().get(0);
```

Zum Ausführen des Editors:
Öffne mal den Dialog mit den Launch Configurations. Dort sollte es eine Kategories 'Eclipse Applications' geben.
Anklicken, dann auf 'new' und dann 'run'.


----------



## Fawkes (27. Nov 2010)

So, ich habe jetzt mal alles im Projekt soweit umgestellt auf EMF, komplett neu erstellen funktioniert soweit auch, aber beim einlesen/bearbeiten einer XML Datei gibt es noch Probleme.

Habe versucht das nach deinem Beispiel zu machen:

```
Resource resource = resourceSet.getResource(URI.createFileURI(filename+".xml"),true);
				SwitchType rootSwitch = (SwitchType)resource.getContents().get(0);
```
Aber er bringt mir:
 org.eclipse.emf.ecore.xmi.FeatureNotFoundException: Feature 'SwitchType' not found. 

In der XML, die ich hier auslesen will, ist der "RootSwitch" vorhanden:
[XML]<?xml version="1.0" encoding="ASCII"?>
<SwitchType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="###/NetworkSchema.xsd" ID="1" IncomePortNumber="0">
  <Name>Switch_1</Name>
  <Delay>4</Delay>
  <Jitter>2</Jitter>
</SwitchType>
[/XML]


Zu der Sache mit dem Editor:
Habe eben nochmal nachgesehen, aber auch dort gibt es keine Kategorie Eclipse Applications:


----------



## Wildcard (27. Nov 2010)

Das Root Element muss doch Switch heißen und nicht SwitchType?
Hast du vorher auch diese Zeile eingetragen?

```
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
            (Resource.Factory.Registry.DEFAULT_EXTENSION, 
             new SwitchmodelResourceFactoryImpl());
```
Das sagt dem ResourceSet das es für jede Dateiendung deine Switchmodel Resource Factory verwenden soll.

Zur Sache mit dem Editor:
Ok, dann fehlt dir die Plugin Development Environment (PDE). Musst du nachinstallieren.


----------



## Fawkes (27. Nov 2010)

Ja, das ist soweit eingetragen:


```
ResourceSet resourceSet = new ResourceSetImpl();
	        resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put
	            (Resource.Factory.Registry.DEFAULT_EXTENSION, 
	             new NetworkSchemaResourceFactoryImpl());

	        resourceSet.getPackageRegistry().put
	            (NetworkSchemaPackage.eNS_URI, 
	            		NetworkSchemaPackage.eINSTANCE);
	        NetworkSchemaFactory factory = NetworkSchemaFactory.eINSTANCE;
```

Hm, ja das mit dem SwitchType als Root ist mir garnicht aufgefallen, eigentlich sollte das Switch sein, generiert wird es genau wie Cable usw. bei denen die Benennung passt.


----------



## Wildcard (27. Nov 2010)

Ja, ich sehe gerade das es bei mir auch zu SwitchType wurde. Ich hätte den Switch wohl in den Document Root einpacken müssen...
Ich muss jetzt leider weg, aber ich denke ich habe morgen kurz Zeit mir das nochmal anzusehen.


----------



## Fawkes (27. Nov 2010)

Ah super, danke für den Tipp mit dem Verpacken im Document Root, das wars. Funktioniert jetzt alles soweit, auch das auslesen  Werde mein Projekt jetzt voll soweit anpassen dass es auf dem JAXB Stand ist und dann mal versuchen das eigentliche Problem mit der Wegfindung zu lösen.

Nochmal Danke für die Hilfe.


----------



## Fawkes (28. Nov 2010)

So, jetzt ist soweit alles auf dem JAXB Stand und auch das mit dem Parent herausbekommen funktioniert.
Jetzt aber nochmal eine kurze Frage dazu: Gibt es eine Möglichkeit über EMF herauszubekommen, wieviele Parentobjekte ein Objekt insgesamt hat? (Sprich, Objekt A hat als Parent Objekt B, welches C als Parent hat usw.)


----------



## Wildcard (28. Nov 2010)

> Ah super, danke für den Tipp mit dem Verpacken im Document Root, das wars.


Ah, du hast es schon hinbekommen, umso besser.


> Gibt es eine Möglichkeit über EMF herauszubekommen, wieviele Parentobjekte ein Objekt insgesamt hat?


Du möchtest also quasi wissen auf welcher Hierarchiestufe sich ein Objekt befindet?
Das ist nicht schwierig. Ich würde zunächst mal anfangen eine Abstrakte EClass in deinem ecore anzulegen von dem du alle anderen erben lässt (ESuperTypes). Ich nenne sie jetzt mal für das Beispiel 'BaseType'.
In dieser Überklasse legst du nun eine EOpperation an. Ich nenne sie mal 'getHierarchyLevel'. Der EType (Rückgabewert) ist entweder einfach ein int (EInt), oder eine Liste die alle Ancestors des Knoten enthält, je nachdem was praktischer für dich ist.
Jetzt generierst du neu und hast jetzt in BaseType einen Methoden Stub 'getHierarchyLevel'. Dort ersetzt du in der Java Doc das @generated durch @generated NOT damit der Generator deine Implementierung in Zukunft in Ruhe lässt. Die Implementierung sieht dann zB so aus:


```
/**
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated NOT
	 */
	public int getHierarchyLevel() {
		int level = 0;
		EObject parent = this;
		while(parent!=null && !(parent instanceof DocumentRoot))
		{
			level++;
			parent = parent.eContainer();
		}
		return level;
	}
```

Benutzen kannst du die Methode dann einfach so:

```
System.out.println(device.getHierarchyLevel());
```

Übrigens, wenn du jetzt den Test Code neu generierst legt dir EMF automatisch einen Unit Test für getHierarchyLevel an, da du diese Methode ja selbst implementieren musst und sie daher auch getestet werden sollte.


----------

