# XML Datei elegant verarbeiten



## multiholle (16. Apr 2009)

Für einen MP3 Player will ich die GUI flexibel gestaltbar machen und das aktuelle Layout in einer XML-Datei speichern. Da in der XML-Datei nur Strings gespeichert werden ist die Verarbeitung relativ unhandlich. Vergleiche sind schwer durchzuführen und dass Umwandeln von Integer und String nervt. Geht das nicht eleganter?

[highlight="java"]
public class PropertyDialogXML {
	public static void savePreferences(SplitablePanelView panel) {
		try {
			XMLOutputFactory factory = XMLOutputFactory.newInstance();
			XMLStreamWriter writer = factory.createXMLStreamWriter(new FileOutputStream("properties.xml"));

			writer.writeStartDocument();

			saveSplitPanelView(panel, writer);

			writer.writeEndDocument();
			writer.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}		
	}

	private static void saveSplitPanelView(Component component, XMLStreamWriter writer) throws Exception{
		String elementName = component.getClass().getSimpleName();

		// SplitablePanelView
		if (component instanceof SplitablePanelView) {
			SplitablePanelView panel = (SplitablePanelView) component;
			if (panel.getComponents().length == 0) {
				writer.writeStartElement("Empty");
				writer.writeEndElement();
				return;
			}
			saveSplitPanelView(panel.getComponent(0), writer);

		// JSplitPane	
		} else if (component instanceof JSplitPane) {
			JSplitPane pane = (JSplitPane) component;

			writer.writeStartElement("JSplitPane");
				writer.writeAttribute("width", 				String.valueOf(component.getWidth()));
				writer.writeAttribute("height", 			String.valueOf(component.getHeight()));
				writer.writeAttribute("orientation", 		String.valueOf(pane.getOrientation()));
				writer.writeAttribute("dividerLocation", 	String.valueOf(pane.getDividerLocation()));

			saveSplitPanelView(pane.getLeftComponent(), writer);
			saveSplitPanelView(pane.getRightComponent(), writer);

			writer.writeEndElement();

		// Player/Libraray/Playlist
		} else {
			writer.writeStartElement(elementName);
				writer.writeAttribute("width", 	String.valueOf(component.getWidth()));
				writer.writeAttribute("height", String.valueOf(component.getHeight()));
			writer.writeEndElement();
		}
	}

	public static void loadPreferences(SplitablePanelView panel) {
		try {
			XMLInputFactory factory = XMLInputFactory.newInstance();
			XMLStreamReader parser = factory.createXMLStreamReader(new FileInputStream("properties.xml"));

			loadSplitPanelLayout(panel, parser);

			// Minimale Größe, bei leerem Inhalt
			if (panel.getComponents().length == 0)
				panel.setPreferredSize(new Dimension(320,80));

			parser.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void loadSplitPanelLayout(SplitablePanelView panel, XMLStreamReader parser) throws XMLStreamException {
		// Ende
		if (!parser.hasNext()) return;

		// Kein Startelement
		if (parser.getEventType() != XMLStreamConstants.START_ELEMENT) {
			parser.next();
			loadSplitPanelLayout(panel, parser);
		}

		// SplitPane
		else if (parser.getLocalName().equals("JSplitPane")) {
			int orientation = 0;
			int dividerLocation = -1;
			for (int i = 0; i < parser.getAttributeCount(); i++) {
				if (parser.getAttributeLocalName(i).equals("orientation")) {
					orientation = Integer.parseInt(parser.getAttributeValue(i));
				} else if (parser.getAttributeLocalName(i).equals("dividerLocation")) {
					dividerLocation = Integer.parseInt(parser.getAttributeValue(i));
				}
			}

			SplitablePanelView[] returnVal = panel.addSplitPane(orientation, dividerLocation);

			parser.next();
			loadSplitPanelLayout(returnVal[0], parser);
			loadSplitPanelLayout(returnVal[1], parser);
		// Leer
		} else if (parser.getLocalName().equals("Empty")) {
			parser.next();
			return;
		// Playlist/Player/Library
		} else {
			int width = 0;
			int height = 0;
			for (int i = 0; i < parser.getAttributeCount(); i++) {
				if (parser.getAttributeLocalName(i).equals("width")) 
					width = Integer.parseInt(parser.getAttributeValue(i));
				else if (parser.getAttributeLocalName(i).equals("height")) 
					height = Integer.parseInt(parser.getAttributeValue(i));
			}
			panel.addComponent(parser.getLocalName(), width, height);
			parser.next();
			return;
		}
	}
}
[/highlight]


----------



## musiKk (16. Apr 2009)

Du könntest Dir ein Model überlegen und das dann per JAXB einfach zwischen Java und XML umwandeln.


----------



## multiholle (19. Apr 2009)

Was meinst du mit Model? Eine Art Record, oder wie geht das?


----------



## foobar (20. Apr 2009)

Um deklarative GUIs für Swing in XML zu erstellen gibts doch schon ne API: Jelly Swing - JellySwing


----------



## multiholle (20. Apr 2009)

und wenn ich das doch lieber selber umsetzen möchte? der code oben funktioniert wunderbar. ich möchte das speichern nur etwas eleganter lösen und dafür brauch ich ein "model", nach musiKk.

was ist ein model für eine xml-datei und wie erstelle ich soetwas?


----------



## musiKk (20. Apr 2009)

Nah, ich hatte halt an ein paar Objekte gedacht, die die Information beinhalten. Irgendwelche Listen mit Angaben zu Positionen und Zeugs. Das meinte ich mit Model. Dafür dann ein XML-Schema erstellen und per JAXB umwandeln.
Ich persönlich würde mir allerdings den Vorschlag von foobar wenigstens mal ansehen, vielleicht ist das ja nicht schlecht gelöst.


----------



## foobar (20. Apr 2009)

Don't reevent the wheel, unless you wanna learn something about wheels.

Von daher scheiden meines Erachtens Lowlevel-APIs wie DOM/SAX/STAX aus, wenn schon dann zumindest JAXB, Eclipe Link oder EMF.


----------



## multiholle (20. Apr 2009)

Eine XML Datei sieht jetzt im Moment z. B. so aus. Wie mache ich daraus ein Model?

[highlight="xml"]
<JSplitPane width="668" height="527" orientation="0" dividerLocation="51">
    <PlayerView width="666" height="50"></PlayerView>
    <JSplitPane width="666" height="470" orientation="1" dividerLocation="401">
        <LibraryView width="400" height="468"></LibraryView>
        <PlaylistView width="259" height="468"></PlaylistView>
    </JSplitPane>
</JSplitPane>
[/highlight]

*EDIT:* Es gibt ein JSplitPane welches linke und rechts jeweils eine Komponente besitzt zu denen folgdende gehören: ein weiteres JSplitPane, Player, Libraray, ...


----------



## foobar (21. Apr 2009)

Überleg dir welche Daten du speichern willst und wie die Entities zusammen hängen. Eine allgemeine Antwort darauf wie man ein Model erstellt wirste nicht finden, da das von Fall zu Fall variiert.


----------



## multiholle (21. Apr 2009)

Was ich speichern will weiß ich. Nur wie mache ich daraus ein Model? Wie sieht sowas aus und wie verarbeit ich das am besten?


----------



## multiholle (21. Apr 2009)

Also ich habe jetzt eine Allgemeine Klasse für alle Komponenten:
[highlight="Java"]
package main.common;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(namespace = "ComponentSettingsXML")
public class ComponentSettingsXML {
	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}


	protected int width;
	protected int height;
}
[/highlight]

Und als Koponenten zum einen ein SplitablePanel (ähnlich SplitPane)...
[highlight="Java"]
package main.gui;

import java.util.Vector;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import main.common.ComponentSettingsXML;
import main.playlist.PlaylistSettingsXML;

@XmlRootElement(name = "SplitablePanel")
public class SplitablePanelSettingsXML extends ComponentSettingsXML{
	public int getOrientation() {
		return orientation;
	}

	public void setOrientation(int orientation) {
		this.orientation = orientation;
	}

	public int getDividerLocation() {
		return dividerLocation;
	}

	public void setDividerLocation(int dividerLocation) {
		this.dividerLocation = dividerLocation;
	}

	public ComponentSettingsXML getLeftComponent() {
		return leftComponent;
	}

	public void setLeftComponent(ComponentSettingsXML leftComponent) {
		this.leftComponent = leftComponent;
	}

	public ComponentSettingsXML getRightComponent() {
		return rightComponent;
	}

	public void setRightComponent(ComponentSettingsXML rightComponent) {
		this.rightComponent = rightComponent;
	}


	private ComponentSettingsXML leftComponent;
	private ComponentSettingsXML rightComponent;
	private int orientation;
	private int dividerLocation;
}
[/highlight]

... und eine Playlist.
[highlight="Java"]
package main.playlist;

import java.util.Vector;

import javax.xml.bind.annotation.XmlRootElement;

import main.common.ComponentSettingsXML;

@XmlRootElement(name = "Playlist")
public class PlaylistSettingsXML extends ComponentSettingsXML {
	public PlaylistSettingsXML() {

	}

	public PlaylistSettingsXML(PlaylistView view) {
		width = view.getWidth();
		height = view.getHeight();
		tableColumnIds = view.getColumnIdsModel();
	}

	public void setTableColumnIds(Vector<Integer> tableColumnIds) {
		this.tableColumnIds = tableColumnIds;
	}

	public Vector<Integer> getTableColumnIds() {
		return tableColumnIds;
	}


	private Vector<Integer> tableColumnIds;
}
[/highlight]

Hiermit erzeuge ich die XML-Datei:
[highlight="Java"]
public static void main(String[] args) throws Exception {
    PlaylistSettingsXML playlist1 = new PlaylistSettingsXML();
    PlaylistSettingsXML playlist2 = new PlaylistSettingsXML();
    SplitablePanelSettingsXML panel = new SplitablePanelSettingsXML();
    SplitablePanelSettingsXML panel2 = new SplitablePanelSettingsXML();

    Vector<Integer> columnIds = new Vector<Integer>() {
        { add(3); add(-1); add(1); add(4); }
    };

    playlist1.setWidth(200);
    playlist1.setHeight(100);
    playlist1.setTableColumnIds(columnIds);

    playlist2.setWidth(300);
    playlist2.setHeight(400);
    playlist2.setTableColumnIds(columnIds);

    panel.setWidth(500);
    panel.setHeight(300);
    panel.setOrientation(1);
    panel.setDividerLocation(200);
    panel.setLeftComponent(playlist1);
    panel.setRightComponent(playlist2);

    panel2.setWidth(500);
    panel2.setHeight(300);
    panel2.setOrientation(1);
    panel2.setDividerLocation(200);
    panel2.setLeftComponent(panel);
    panel2.setRightComponent(playlist2);

    JAXBContext context = JAXBContext.newInstance(SplitablePanelSettingsXML.class);
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.marshal(panel2, System.out);
}
[/highlight]

Das Ergebnis sieht dann so aus:
[highlight="xml"]
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<SplitablePanel xmlns:ns2="ComponentSettingsXML">
    <height>300</height>
    <width>500</width>
    <dividerLocation>200</dividerLocation>
    <leftComponent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="splitablePanelSettingsXML">
        <height>300</height>
        <width>500</width>
        <dividerLocation>200</dividerLocation>
        <leftComponent>
            <height>100</height>
            <width>200</width>
        </leftComponent>
        <orientation>1</orientation>
        <rightComponent>
            <height>400</height>
            <width>300</width>
        </rightComponent>
    </leftComponent>
    <orientation>1</orientation>
    <rightComponent>
        <height>400</height>
        <width>300</width>
    </rightComponent>
</SplitablePanel>
[/highlight]

Leider kann man jetzt in der XML-Datei nicht mehr erkennen um welche Komponenten es sich bei leftComponent und bei rightComponent handelt. Zudem werden die Eigenschaften der Playlist nicht gespeichert (tableColumnIDs). Was mache ich falsch?

*EDIT:* Das Problem scheint der Typ von leftComponent und rightComponent zu sein. Da ich hier ComponentSettingsXML gewählt habe werden keine Playlist-spezifischen Eigenschaften abgefragt. PlaylistSettingsXML kann ich aber nicht wählen, da eine Komponente ja auch wieder eine SplitablePanelSettingsXML sein kann :/


----------



## JohannisderKaeufer (22. Apr 2009)

@XmlAttribute

kannst du nutzen um deine Werte height usw. als Attribute anstatt als Element zu hinterlegen.

Galileo Computing :: Java ist auch eine Insel (8. Auflage) – 15.8 Java Architecture for XML Binding (JAXB)

Der Punkt "Hierarchien einsetzen", ziemlich weit unten beschrieben, beschreibt wie man mit Hilfe von @XmlElementRefs und @XmlElementRef( type = ....class ) dein Problem mit den Vererbungen lösen kann. In dem Beispiel bezieht man sich allerdings auf listen, ob das mit einzelnen Objekten auch funktioniert weis ich nicht, aber ein Versuch wäre es Wert.


----------



## multiholle (23. Apr 2009)

Ich habe jetzt meine Klassen entsprechend dem Link angepasst. Es scheint auch zum Teil zu funktionieren, leider nicht komplett. Bei den SplitablePanelSettingsXML wird für die leftComponent folgendes ausgegeben:
[highlight="XML"]
<splitablePanelSettingsXML> ... </splitablePanelSettingsXML>
[/highlight]
Für die rightComponent hingegen wird dies geschrieben:
[highlight="XML"]
<rightComponent xsi:type="splitablePanelSettingsXML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> ... </rightComponent>
[/highlight]
Was für die rightComponent ausgegeben wird scheint richtig zu sein. Warum, jedoch ist es bei der leftComponent anders, obwohl sie in der gleichen Klasse gleich definiert untereinander stehen? Aus dem Code der leftComponent wird auch kein Objekt erzeugt beim einlesen.

[highlight="Java"]
public abstract class ComponentSettingsXML {
    public int width;
    public int height;
}


@XmlRootElement
public class PlaylistSettingsXML extends ComponentSettingsXML {    
    public @XmlList Vector<Integer> tableColumnIds;
}


@XmlRootElement
@XmlType(propOrder = {"orientation", "dividerLocation", "leftComponent", "rightComponent"})
public class SplitablePanelSettingsXML extends ComponentSettingsXML{
	@XmlElementRefs(
	{
		@XmlElementRef(type = SplitablePanelSettingsXML.class),
		@XmlElementRef(type = PlaylistSettingsXML.class)
	})

	public ComponentSettingsXML leftComponent;
	public ComponentSettingsXML rightComponent;

    public int orientation;
    public int dividerLocation;
}


public class Test {
	public static void main(String[] args) throws Exception {
		PlaylistSettingsXML playlist1 = new PlaylistSettingsXML();
		PlaylistSettingsXML playlist2 = new PlaylistSettingsXML();
		SplitablePanelSettingsXML panel0 = new SplitablePanelSettingsXML();
		SplitablePanelSettingsXML panel1 = new SplitablePanelSettingsXML();
		SplitablePanelSettingsXML panel2 = new SplitablePanelSettingsXML();

		Vector<Integer> columnIds = new Vector<Integer>() {
			{ add(3); add(-1); add(1); add(4); }
		};

		playlist1.width = 200;
		playlist1.height = 100;
		playlist1.tableColumnIds = columnIds;

		playlist1.width = 300;
		playlist1.height = 400;
		playlist1.tableColumnIds = columnIds;

		panel0.width = 300;
		panel0.height = 400;
		panel0.orientation = 1;
		panel0.dividerLocation = 200;
		panel0.leftComponent = playlist1;
		panel0.rightComponent = playlist2;

		panel1.width = 500;
		panel1.height = 300;
		panel1.orientation = 1;
		panel1.dividerLocation = 200;
		panel1.leftComponent = playlist1;
		panel1.rightComponent = playlist2;

		panel2.width = 500;
		panel2.height = 300;
		panel2.orientation = 1;
		panel2.dividerLocation = 200;
		panel2.leftComponent = panel0;
		panel2.rightComponent = panel1;

		JAXBContext context = JAXBContext.newInstance(SplitablePanelSettingsXML.class);
		Marshaller m = context.createMarshaller();
		m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
		Writer w = new FileWriter("layout.xml");
		m.marshal(panel2, w);
		w.close();

		Unmarshaller um = context.createUnmarshaller();
		SplitablePanelSettingsXML panel = (SplitablePanelSettingsXML) um.unmarshal(new FileReader("layout.xml"));
	}
}
[/highlight]

[highlight="XML"]
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<splitablePanelSettingsXML>
    <width>500</width>
    <height>300</height>
    <orientation>1</orientation>
    <dividerLocation>200</dividerLocation>
    <splitablePanelSettingsXML>
        <width>500</width>
        <height>300</height>
        <orientation>1</orientation>
        <dividerLocation>200</dividerLocation>
        <playlistSettingsXML>
            <width>300</width>
            <height>400</height>
            <tableColumnIds>3 -1 1 4</tableColumnIds>
        </playlistSettingsXML>
        <rightComponent xsi:type="playlistSettingsXML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <width>0</width>
            <height>0</height>
        </rightComponent>
    </splitablePanelSettingsXML>
    <rightComponent xsi:type="splitablePanelSettingsXML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <width>500</width>
        <height>300</height>
        <orientation>1</orientation>
        <dividerLocation>200</dividerLocation>
        <playlistSettingsXML>
            <width>300</width>
            <height>400</height>
            <tableColumnIds>3 -1 1 4</tableColumnIds>
        </playlistSettingsXML>
        <rightComponent xsi:type="playlistSettingsXML">
            <width>0</width>
            <height>0</height>
        </rightComponent>
    </rightComponent>
</splitablePanelSettingsXML>

[/highlight]


----------



## JohannisderKaeufer (23. Apr 2009)

Irgendwie merkwürdig.

In deinem Codestück ist leftComponent Annotiert, rightComponent allerdings nicht.


----------



## multiholle (24. Apr 2009)

JohannisderKaeufer hat gesagt.:


> In deinem Codestück ist leftComponent Annotiert, rightComponent allerdings nicht.


Womit hängt das zusammen, dass beide Komponenten unterschiedlich ausgegeben werden? Ich mache doch nichts anders!?


----------

