PropertyChangeSupport bei statischer Klasse

Hey Leute,

ich impelementiere gerade eine Klasde namens ImageProvider, die verschiedene Bilder zur Verfügung stellt. Nun brauche ich einen PropertyChangeSupporr fuer diese Klasse um bei Aenderung der Icons allen Listenern bescheidzusagen damit die das neue Icon laden koennen.
Die Klasse ImageProvider soll nur statische Methoden besitzen. Das Problem nun ist, dass man ein Objekt uebergeben muss das nicht null ist. Ich hoffe ihr habt ne Idee…
Hier der Code:

Code
[spoiler]

package de.zerotask.application.ui;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.Icon;

// TODO: List with all registered IconSets. Could add possibility to choose from
// the preferences.
public final class ImageProvider implements IImageNames {

private static IIconSet activeIconSet;

private static final IIconSet DEFAULT_SET = new DefaultIconSet();

// Hier sollte eigentlich eine Instanz der Klasse ImageProvider ergeben
// werden. Da null leider nicht geht(es wird eine NullPointerException
// geworfen), muss ich einen anderen Weg finden.
// Man koennte anstatt 'null' 'new Object()' schreiben, scheint mir aber 
// was unsauber zu sein...
private static PropertyChangeSupport pcs = new PropertyChangeSupport(null);

public static final String PROPERTY_ICONSET_CHANGED = "iconsetChanged";

public static void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}

public static void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
pcs.addPropertyChangeListener(propertyName, listener);
}

public static IIconSet getActiveIconSet() {
if (activeIconSet == null) {
activeIconSet = DEFAULT_SET;
}
return activeIconSet;
}

public static Icon getIcon(String name) {
return getActiveIconSet().getIcon(name);
}

public static void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}

public static final void removePropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
pcs.removePropertyChangeListener(propertyName, listener);
}

public static void setActiveIconSet(IIconSet set) {
if (set != null) {
IIconSet old = getActiveIconSet();
activeIconSet = set;
// Gebraucht um alle Icons dann neu zu laden und zu verwenden...
pcs.firePropertyChange(PROPERTY_ICONSET_CHANGED, old, set);
}
}
}
package de.zerotask.application.ui;

import javax.swing.Icon;

// Falls jmd einen nesseren Namen hat, nur raus damit :D ich hasse solche doppelten I's
public interface IIconSet {

Icon getIcon(String name);

String getIconEncoding();
}
package de.zerotask.application.ui;

interface IImageNames {

String BACK = "back";

String BOOKMARK = "bookmark";

String CLOSE = "close";

String CLOSE_ALL = "closeall";

String CONSOLE = "console";

String COPY = "copy";

String CUT = "cut";

String DELETE = "delete";

String EXPORT = "export";

String FIND = "find";

String FIND_NEXT = "findnext";

String FORWARD = "forward";

String GOTO = "goto";

String HELP = "help";

String IMPORT = "import";

String INFORMATION = "information";

String NEW = "new";

String OPEN = "open";

String OPTIONS = "options";

String PASTE = "paste";

String PRINT = "print";

String PRINT_ALL = "printall";

String PRINT_PREVIEW = "printpreview";

String REDO = "redo";

String REFRESH = "refresh";

String REPLACE = "replace";

String REPLACE_NEXT = "replacenext";

String SAVE = "save";

String SAVE_ALL = "saveall";

String SAVE_AS = "saveas";

String SAVE_WEBPAGE = "savewebpage";

String TIME_DATE = "timedate";

String UNDO = "undo";

String WORLD_WIDE_WEB = "www";
}

[/spoiler]

Btw: im letzten Code block wird auch NEW uppercase lila gefaerbt, was nicht sein sollte oder?

Die Klasse ImageProvider soll nur statische Methoden besitzen.

… und warum?

Irgendwie habe ich das Gefuehl, dass das hier bald zur Singleton/DI Diskussion wird :wink:

[QUOTE=maki]… und warum?

Irgendwie habe ich das Gefuehl, dass das hier bald zur Singleton/DI Diskussion wird ;)[/QUOTE]

warum sollte ein Provider einen public Const. haben um ihn jedes man neu zu initialisieren? ToolProvider ist auch nicht public

Ja an Singleton habe ich auch gedacht, nur finde ich fas der Aufruf ImageProvider.getIcon(...) besser ist als ImageProvider.getInstance().getIcon(...)

Also was PropertyChangeSupport macht weist du hoffentlich. Obwohl… anscheinend nicht, sonst wüsstest du ja, warum es in Utility-Klassen (also statischen) keinen Sinn macht.
Wenn ich besser überschauen könnte, was du vorhast, würde ich sagen, das geht anders besser.
Mal raten: Anwendungen, Fenster uwwi sollen sich ihre Icons aus einer gemeinsamen Sammlung holen und darüber informiert weden können, wenn sich diese Icons ändern?

[QUOTE=groggy]warum sollte ein Provider einen public Const. haben um ihn jedes man neu zu initialisieren? ToolProvider ist auch nicht public
[/quote]
Warum denn nicht?

Wohl, weil du nur einen einzigen ImageProvider haben willst, sonst bringt der ja wenig…

Ja an Singleton habe ich auch gedacht, nur finde ich fas der Aufruf ImageProvider.getIcon(...) besser ist als ImageProvider.getInstance().getIcon(...)

Wieso erzeugt nicht einfach einen einzigen ImageProvider und uebergibst ihn dann an alle Klassen die einen brauchen?

Das Problem dass du dann schildern wirst, ist bereits geloest :wink:

Das Problem mit solchen Singletons oder statischen Methoden ist immer die Testbarkeit. Die geht bei den meisten Frameworks dann verloren bzw ist nicht vernünftig möglich.

[QUOTE=Spacerat]Also was PropertyChangeSupport macht weist du hoffentlich. Obwohl… anscheinend nicht, sonst wüsstest du ja, warum es in Utility-Klassen (also statischen) keinen Sinn macht.
Wenn ich besser überschauen könnte, was du vorhast, würde ich sagen, das geht anders besser.
Mal raten: Anwendungen, Fenster uwwi sollen sich ihre Icons aus einer gemeinsamen Sammlung holen und darüber informiert weden können, wenn sich diese Icons ändern?[/QUOTE]

Jop das ist mein Vorhaben. Naja PropertychangeSupport macht doch eigentlich nur die ‚Drecksarbeit‘ im Hintergrund oder?

Weil diese Klasse nicht nur intern gebraucht wird, sondern auch extern verfùgbar sein soll

[QUOTE=groggy]Jop das ist mein Vorhaben. Naja PropertychangeSupport macht doch eigentlich nur die ‘Drecksarbeit’ im Hintergrund oder?[/QUOTE]Im Hintergrund ist gut… XD
Das passiert alles auf dem EDT… und genau das wäre auch mein Ansatz… ein eigenes Eventsystem oder mit Observern (Der Unterschied ist anscheinend ein Thread). PropertyChange verwendet man ja eigentlich nur, wenn man verschiedene Properties hat, die sich ändern können, bei dir scheint es aber nur ein Bild zu sein.

Du meinst alao dass das ändern der Bilder und das feuern der Events alles auf den EDT passiert? Und ich soll nun einen neuen Support schreiben(sowas eie PropertyStateChange oder so) damit es eben nicht auf den EDT passiert oder?

Hmm… Also ich hab noch nicht herausgefunden, wie man eigene Events auf den EDT bekommt. Aber genau darum geht es ja… du willst Listener bzw. Observer darüber informieren, dass sich ein Bild geändert hat und hast deswegen mit einem EDT-Event ein Problem. Deswegen verwende entweder Observer oder implementiere deinen eigenen EDT. Ganz abstrakt etwa so:


import java.util.*;

public final class EventQueue {
	private static final TreeSet<EventObject> queue = new TreeSet<>();
	private static final Map<Object, List<EventListener>> listener = new IdentityHashMap<>();
	private static Thread thread;

	private EventQueue() {
		// nothing
	}

	public static void addEventListener(EventListener mel, Object object) {
		if (mel != null && object != null) {
			List<EventListener> l = null;
			synchronized (listener) {
				if (listener.containsKey(object)) {
					l = listener.get(object);
				} else {
					l = new LinkedList<EventListener>() {
						private static final long serialVersionUID = 1L;

						@Override
						public boolean contains(Object o) {
							for (Object l : this) {
								if (l == o) {
									return true;
								}
							}
							return false;
						}
					};
					listener.put(object, l);
				}
			}
			synchronized (l) {
				if (!l.contains(mel)) {
					l.add(mel);
				}
			}
		}
	}

	public static void removeEventListener(EventListener mel, Object object) {
		if (mel != null && object != null) {
			List<EventListener> l = null;
			synchronized (listener) {
				l = listener.get(object);
			}
			if (l != null) {
				l.remove(mel);
				if (l.isEmpty()) {
					synchronized (listener) {
						listener.remove(object);
					}
				}
			}
		}
	}

	public static void dispatchEvent(EventObject event, Object object) {
		if (event.dispatcher != object) {
			return;
		}
		synchronized (listener) {
			if (listener.containsKey(object)) {
				synchronized (queue) {
					queue.add(event);
				}
			}
		}
		if (!queue.isEmpty()) {
			synchronized (queue) {
				if (thread == null) {
					thread = new Thread("Datatype Event Queue") {
						{
							setDaemon(true);
						}

						@Override
						public void run() {
							List<EventListener> tmplistener;
							EventObject e;
							while (!isInterrupted()) {
								while (!queue.isEmpty()) {
									synchronized (queue) {
										e = queue.first();
										queue.remove(e);
									}
									synchronized (listener) {
										tmplistener = listener.get(e.dispatcher);
									}
									if (tmplistener != null) {
										tmplistener = new ArrayList<>(tmplistener);
										for (EventListener l : tmplistener) {
											e.dispatch(l);
										}
									}
								}
								synchronized (this) {
									try {
										wait();
									} catch (InterruptedException ie) {
										interrupt();
									}
								}
							}
						}
					};
					thread.start();
				}
			}
			synchronized (thread) {
				thread.notify();
			}
		}
	}
}```
```package eventsystem;

import java.util.*;

public abstract class EventObject extends java.util.EventObject implements Comparable<EventObject> {
	private static final long serialVersionUID = 1L;

	private final long timestamp = System.currentTimeMillis();
	final Object dispatcher;

	protected EventObject(Object source, Object dispatcher) {
		super(source);
		if (dispatcher == null) {
			throw new IllegalArgumentException("dispatcher may not be null");
		}
		this.dispatcher = dispatcher;
	}

	public long getTimeStamp() {
		return timestamp;
	}

	@Override
	public int compareTo(EventObject o) {
		return (timestamp < o.timestamp) ? 1 : (timestamp > o.timestamp) ? -1 : 0;
	}

	public long getWhen() {
		return getTimeStamp();
	}

	protected abstract void dispatch(EventListener l);
}```Jetzt musst du nur noch konkrete EventListener und EventObjekte für deine Applikation bauen und sie in deiner Klasse entsprechend verwenden:
```package eventsystem;

import java.util.EventListener;

public interface ImageUpdateListener extends EventListener {
	void imageUpdated(ImageUpdateEvent e);
}```
```package eventsystem;

import java.awt.Image;
import java.util.EventListener;

public class ImageUpdateEvent extends EventObject {
	private static final long serialVersionUID = -8461717671852844140L;

	public ImageUpdateEvent(Image source, Object dispatcher) {
		super(source, dispatcher);
	}

	public Image getImage() {
		return (Image) super.getSource();
	}

	@Override
	protected void dispatch(EventListener l) {
		if(l instanceof ImageUpdateListener) {
			((ImageUpdateListener) l).imageUpdated(this);
		}
	}
}```
```package eventsystem;

import java.awt.Image;

public class ImageUpdateTest {
	private final Object dispatchMarker = new Object(); // markiert Events dieser Instanz

	public void addImageUpdateListener(ImageUpdateListener iul) {
		EventQueue.addEventListener(iul, dispatchMarker);
	}

	public void removeImageUpdateListener(ImageUpdateListener iul) {
		EventQueue.removeEventListener(iul, dispatchMarker);
	}

	private void somethingHappens(Image img) {
		EventQueue.dispatchEvent(new ImageUpdateEvent(img, dispatchMarker), dispatchMarker);
	}
}```Das geht nun natürlich auch mit statischen Klassen, aber das ist (im Allgemeinen) keine so gute Idee, weil dann stets alle Listener benachrichtigt würden, statt nur jene die betroffen sind. Dieses EventMarkerObjekt sollte auf jeden Fall etwas sein, was man von ausserhalb der Klasse nicht erreichen kann.

In dem Fall werden abet immer alle Listener benachrichtigt. Koenntest du mir genauer erklaeren was das markerObject bringen soll? Hab es nicht wirklich verstanden

[QUOTE=groggy]In dem Fall werden abet immer alle Listener benachrichtigt. Koenntest du mir genauer erklaeren was das markerObject bringen soll? Hab es nicht wirklich verstanden[/QUOTE]:o Hab’ ich das nicht erklärt? Oh man… nein, hab’s vergessen.
Wie du siehst sind die relevanten Methoden der EventQueue public static. Daraus folgt, dass im Prinzip jeder irgendwelche Events für andere Objekte von überall her werfen könnte. Dieses private Identifikations-Objekt verhindert dies (solange man nicht mit Reflection rumfuhrwerkt).
Aber eines habe ich ja bereits schon angemerkt… wenn du den ImageProvider statisch machst, werden stets alle Listener benachrichtigt. Also entweder machst du pro IconSet einen ImageProvider oder (was noch besser ist) du ermöglichst es die Listener direkt am Icon zu registrieren.

Ich will jetzt nicht nerven, aber ich kapiere gerade herzlich wenig :frowning: alao meine Idee war halt einen ImageProvider zu haben der eine Liste beinhaltet die alle IconSets haelt. Es gibt immer ein aktives Set aus dem die Icons stammen. Falls das aktive Set geaendert wird sollen alle Listener aufgerufen werden. Es ist dabei egal um welches neue Set es sich handelt. Die Listener werden aufgerufen, die Klassen die diese implementieren wissen dann Bescheid dass sich die Icons geaendeft haben und holen sich mif ihren passenden Key halt ihr Icon wieder.
Koennte man das mit dem von ueberall nicht verhindern indem die einzige Instanz von ImageProvider eben nur eine EventQueue haelt?

Wo der EDT da jetzt herkommt, kapiere ich auch nicht ganz. Der PropertyChangeSupport benachrichtigt seine Listener auf dem Thread, von dem auch die Änderung kommt. Solange da kein Swing dabei ist, hat das mit dem EDT nichts zu tun. Es hängt also an der Frage, wer das “setIconSet” aufruft (und selbst wenn das - was vermutlich zu erwarten ist - vom EDT gemacht wird, ist das nicht per se schlecht oder falsch. Man sollte nur IN den PropertryChangeListenern wissen (oder ggf. überprüfen) auf welchem Thread man benachrichtigt wird, bevor man die neuen Icons in irgendwelche Components einfügt - DAS darf nämlich nur auf dem EDT gemacht werden).

Ich bin zwar auch ein “fan” von statischen Methoden, aber das bezieht sich NUR und ausschließlich auf Utility-Methoden, die NICHTS mit irgendeinem Zustand zu tun haben. (Und nebenbei … das “…implements IImageNames” bei einer Klasse, die nur statische Methoden haben soll, irritiert mich etwas ;)). Ich bin auch kürzlich mal wieder in die Falle getappt: “Ja, ich brauche eine globale Registry - da kann ich das doch mit static methods machen, wo man Objekte registrieren und deregistrieren kann”. Ich hatte schon ein etwas ungutes Gefühl dabei. Ja, drauf gekackt, jetzt habe ich das wieder mühsam rausgebaut.

Vielleicht mal weiter oben ansetzen: Das soll ja wohl so eine Art “Theming-Funktionalität” werden, für irgendeine Applikation, die irgendwo viele Icons verwendet, die von dieser zentralen Stelle aus nachträglich geändert werden können sollen…?!

[QUOTE=groggy]Ich will jetzt nicht nerven, aber ich kapiere gerade herzlich wenig :frowning: alao meine Idee war halt einen ImageProvider zu haben der eine Liste beinhaltet die alle IconSets haelt. Es gibt immer ein aktives Set aus dem die Icons stammen. Falls das aktive Set geaendert wird sollen alle Listener aufgerufen werden. Es ist dabei egal um welches neue Set es sich handelt. Die Listener werden aufgerufen, die Klassen die diese implementieren wissen dann Bescheid dass sich die Icons geaendeft haben und holen sich mif ihren passenden Key halt ihr Icon wieder.
Koennte man das mit dem von ueberall nicht verhindern indem die einzige Instanz von ImageProvider eben nur eine EventQueue haelt?[/QUOTE]Natürlich kann man das machen… wenn sich einem aber noch andere Gebiete für eigene EventQueues erschliessen, muss man das Ganze nochmal neu aufziehen.
Du kannst auch andere Listener und EventObjekte erstellen, z.B. IconSetListener und -Event. Die EventQueue ist da sehr flexibel. Das ist auf jeden Fall besser (der ImageProvider bleibt dadurch auch übersichtlicher), als sich nun durch meinen undokumentierten Code zu wuseln, um zu versuchen, die EventQueue fest in den ImageProvider zu integrieren. Statisch sähe das Ganze so aus:


import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.Icon;

public class IconProvider {
	public static final String DEFAULT_SET_NAME = "DEFAULT_SET";

	private static final Object dispatchMarker = new Object(); // markiert Events dieser Klasse
	private static final Map<String, Map<String, Icon>> SETS = new TreeMap<>();
	private static Map<String, Icon> CURRENT_SET;

	static {
		CURRENT_SET = Collections.unmodifiableMap(createDefaultSet());
		SETS.put(DEFAULT_SET_NAME, CURRENT_SET);
	}

	public static void addIconSetListener(IconSetListener iul) {
		EventQueue.addEventListener(iul, dispatchMarker);
	}

	public static void removeIconSetListener(IconSetListener iul) {
		EventQueue.removeEventListener(iul, dispatchMarker);
	}

	public static Icon getIcon(String name) {
		synchronized (SETS) {
			Icon rc = CURRENT_SET.get(name);
			if(rc == null) {
				throw new IllegalArgumentException("invalid name");
			}
			return rc;
		}
	}

	public static void setCurrentIconSet(String name) {
		if(name == null || name.trim().length() == 0) {
			name = DEFAULT_SET_NAME;
		}
		synchronized (SETS) {
			Map<String, Icon> currentSet = SETS.get(name);
			if(currentSet != null) {
				CURRENT_SET = currentSet;
				somethingHappens(currentSet);
			}
		}
	}

	public static Map<String, Icon> defineIconSet(String name, Map<String, Icon> icons) {
		if(name == null || name.trim().length() == 0 || DEFAULT_SET_NAME.equals(name)) {
			throw new IllegalArgumentException("invalid name");
		}
		// icons pruefen
		synchronized (SETS) {
			Map<String, Icon> newSet = Collections.unmodifiableMap(icons);
			Map<String, Icon> oldSet = SETS.put(name, newSet);
			if(oldSet != null) {
				somethingHappens(newSet);
			}
			return oldSet;
		}
	}

	private static void somethingHappens(Map<String, Icon> newSet) {
		EventQueue.dispatchEvent(new IconSetEvent(newSet, dispatchMarker), dispatchMarker);
	}

	private static Map<String, Icon> createDefaultSet() {
		// defaultset laden
		return null; // und zurueckgeben
	}
}```
```package eventsystem;

import java.util.EventListener;

import javax.swing.Icon;

public class IconSetEvent extends EventObject {
	private static final long serialVersionUID = -8461717671852844140L;

	IconSetEvent(Object source, Object dispatcher) {
		super(source, dispatcher);
	}

	public Icon getIcon(String name) {
		return IconProvider.getIcon(name);
	}

	@Override
	protected void dispatch(EventListener l) {
		if(l instanceof IconSetListener) {
			((IconSetListener) l).iconSetChanged(this);
		}
	}
}```
```package eventsystem;

import java.util.EventListener;

public interface IconSetListener extends EventListener {
	void iconSetChanged(IconSetEvent e);
}```Sieht das nicht um einiges kompakter aus? So wie es jetzt ist, können die Listener auch noch ihr neues Icon über das EventObjekt beziehen. Der Konstruktor eines IconSetEvents ist, wie du siehst, package private, dass ist ein weiterer Weg, wie man verhindern kann, das Hinz und Kunz Events werfen kann. Diese MarkerObjekt-Geschichte lasse ich aber trotzdem drin, bis mir (oder jemand anders) was besseres dazu einfällt. Diese EventQueue stammt rein zufällig aus meiner Datentyp-Erkennung. Im Übrigen habe ich Maps verwendet, weil ich keinen Zugriff auf deine IconSet-Klassen hatte. Strings scheinen mir für das Ganze auch 'ne gute Idee zu sein.
    @Marco13 : Icons kannst du auch in anderen Threads ändern, nur evtl. im Main-Thread nicht (wieso eigentlich nicht? Funktioniert doch). EDT heisst EventDispatchThread, richtig? Ein solcher hat nur bedingt (eher gewohnheitsmässig) etwas mit GUI-Frameworks zu tun. Normalerweise wird also auch PropertyChange in die AWT-EventQueue geworfen (wie z.B. bei "<Window>.setIcon()").

Die IconSet klasee laedt bei Befaff eben die Icons und speichert diese dann in einer Map. Da aber Icons nicht nur aus der jar kommen in der der IconProvider liegt(das sind die defaults) hab ich eben ein interface gemacht. So ist es auch moeglich die icons aus einer anderen jar zu laden oder aus einem Verzeichnis
@Marco13 ja das ist richtig. Die Icons eerden von dort geholt und koennen vom Benutzer geändert werden. Dann aollen sich diese Icons automatisch aendern

Ist zwar was her, aber hab es vergessen xD
Habe nun eine einfache List genommen in der ich die Listener halte. Es gibt eine add, eine remove und eine fireIconSetChangedEvent methode. Spricht da was gegen weil das scheint die simpelste Loesung zu sein.

[QUOTE=groggy]Ist zwar was her, aber hab es vergessen xD
Habe nun eine einfache List genommen in der ich die Listener halte. Es gibt eine add, eine remove und eine fireIconSetChangedEvent methode. Spricht da was gegen weil das scheint die simpelste Loesung zu sein.[/QUOTE]

Ich weiß nicht ob es gut ist, alle Bilder einer Anwendung in einer statischen Liste zu halten…

Why not? Als globaler Zugriff damit man ueberall dran kommen kann…

Speicherleak?
Eine zentrale Klasse, die die Bilder laden okay, am schönsten über Enums, aber alle im Speicher zu halten? Ich weiß nicht ob das ne gute Idee ist…