Montag, 9. August 2010

Java .clone()

Man kann Objekte in Java per eineInstanz.clone() kopieren, wenn die Klasse das Interface java.lang.Cloneable() implementiert. Dieses Interface ist ein sogenanntes Marker-Interface ohne definierte Methoden. Die Methode .clone() wird hingegen von der Basisklasse Object definert undzwar als protected. Eine Klasse, welche Cloneable implementiert sollte per Konvention die Methode .clone() aus Object als public Methode überschreiben. Man beachte, das die Klasse Object selbst nicht das Cloneable Interface implementiert und man daher auf Instanzen von Object eine CloneNotSupportedException beim Aufruf von .clone() erhält.
Die überschriebene public Variante von .clone() sollte als erstes super.clone() aufrufen und dann alle Member des Klon-Objektes, welche Variablen (ohne final Schlüsselwort) auf nicht-konstante Daten (mutable) besitzt, mit Kopien solcher Daten besetzen. Denn die Konvention von .clone() sieht eine komplette Unabhängigkeit von Objekten zu ihren Klonen vor. Die Implementierung von .clone() in Object vollzieht eine nicht-rekursive Zuweisung aller Membervariablen vom Original an die entsprechenden Membervariablen vom Klon. Es findet keine Überprüfung auf Referenzwerte als Daten statt. Daher muß sich die Cloneable implementierende Klasse um diese Details kümmern.
Die Intention des Klon-Vorgangs aus Object.clone() ist es folgende Eigenschaften zu haben:
  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)

Links:

Java Serialisierung

Serialisierung ist die Möglichkeit ein Objekt aus dem Hauptspeicher in einen persistenten Zustand zu transformieren. Entweder handelt es sich um immutable (unveränderliche) Werte aus den Java Standardtypen oder die betreffende Klasse muss das Interface Serializable implementieren. Dieses Interface besitzt keine Methoden, sondern dient als sogenanntes Marker-Interface zur Freischaltung der Serialisierbarkeit. Außerdem müssen in der aufsteigenden Vererbungshierarchie (in Richtung Object) alle Superklassen ebenfalls Serializable implementieren, außer man definiert eigene Methoden für die Serialisierung, welche dann aber nicht die Default-Serialisierung (s.u.) aufrufen dürfen. Alle Sub-Klassen einer serialisierbaren Klasse sind automatisch als serialisierbar markiert. Um als Subklasse diese Serialisierbarkeit wieder zu entfernen kann man folgenden Trick anwenden:
/**
* Modified from: "Discover the secrets of the Java Serialization API"
* by Todd Greanier July 2000
*/
private void writeObject(ObjectOutputStream out) throws IOException
{
  throw new NotSerializableException("Serialisierung wird nicht unerstützt");
}

private void readObject(ObjectInputStream in) throws IOException
{
  throw new NotSerializableException("Serialisierung wird nicht unerstützt");
}

Alle Membervariablen einer Instanz einer serialisierbaren Klasse werden rekursiv serialisert. Sollte bei dieser Rekursion ein Element auftauchen, das die Serialisation nicht unterstützt, dann wird eine Exception geworfen. Um dies zu verhindern, kann man betreffende nicht zu serialiserende Member der Instanz mit transient kennzeichen. Solche Elemente werden bei der rekursiven Serialisierung ausgelassen und müssen mit entsprechendem Programmcode beim Deserialisieren manuell wiederhergestellt werden.

Beispiel:

/**
* Modified from: "Discover the secrets of the Java Serialization API"
* by Todd Greanier July 2000
*/
import java.io.Serializable;

public class PersistentAnimation implements Serializable, Runnable {
    transient private Thread animator;
    private int animationSpeed;

    public PersistentAnimation(int animationSpeed) {
        this.animationSpeed = animationSpeed;
        startAnimation();
    }

    private void startAnimation() {
        animator = new Thread(this);
        animator.start();
    }

    public void run() {
        while (true) {
            // Hier steht Code für eigentliche Animation
        }
    }

    /**
     * Hook-Methode == Inverse of Control (IOC)
     * PersistentAnimation aPA = ...;
     * ObjectOutputStream aOOS = ...;
     * 
     * aOOs.writeObject(aPA);
     *      //Pseudcode der Implementierung
     *      { if(aPA besitzt Methode "private void writeObject(ObjectOutputStream)")
     *        { 
     *              //speichere aktuelles Argument in Membervariable
     *              aktiveObject = aPA;
     *              aPA.writeObject(this);
     *         } else writeUnhooked(aPA);
     *      }
     * 
     * aOOs.defaultWriteObject();
     *      //Pseudocode
     *      { 
     *          //schreibe Aufrufer der Methode
     *          writeUnhooked(aktiveObject);
     *      }
     * 
     * @param out
     *            ObjectOutputStream, welches this serialisiert:
     *            out.writeObject(this);
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    /**
     * @param in
     *            ObjectInputStream, welches this per Deserialisierung
     *            initialisiert.
     * @throws IOException
     * @throws ClassNotFoundException
     *             Wenn Klassendatei nicht zur Verfügung steht
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        // Initialisiere this mit Zustand aus vorheriger Serialisation
        in.defaultReadObject();
        // transient Member wurde nicht serialisert, also manuell besetzen
        startAnimation();

    }
}

Man beachte, dass der Aufruf von out.defaultWriteObject() alle Felder der aktuellen Objektinstanz, unabhängig von einer manuell erfolgenden Serialisierung, serialisiert. Dabei serialisiert es alle Felder der Instanz aus den Klassen der Vererbungshierarchie beginnend mit der Basisklasse (Tiefensuche) und endend mit der Laufzeitklasse. Analog verhält es sich mit in.defaultReadObject().


Quellen:

Javas equals() und hashCode() Methoden

Die von der Basisklasse Object realisierte Implementierung von eineInstanz.equals() macht folgendes:

x.equals(y){ return this==y;}

Die Eigenschaften von .equals() sollen die Äquivalenzrelation erfüllen und pro Programmlauf konsistent sein, indem Sinne, dass bei Konstanz der für die Evaluierung der Äquivalenz herangezogenen Parameter des Objekts auch der Rückgabewert von .equals() gleich bleibt.

Die Methode .hashCode() soll bei Auswertung von .equals()==true auf den beteiligten Objekten auch den gleichen Hashwert zur Folge haben. Umgehrt bedeutet das, daß bei unterschiedlichen Hashwerten das Ergebnis von .equals() auf den beteiligten Objekten den Wert false haben muß.

Aber das heißt nicht, dass Objekte, für die .equals() auf false hinausläuft einen unterschiedlichen Hashwert haben müssen. Es wird nur zwecks Performanz von Hash-Implementierungen in Java-System-Klassen empfohlen einen unterschiedlichen Haswert zu erzeugen.

Also zusammengefasst in Pseucode:

Von .equals() ausgehend:
switch(x.equals(y)):{
case true:
  ( x.hashCode()==y.hashCode() ) == true
case false:
  ( x.hashCode()==y.hashCode() || x.hashCode()!=y.hashCode() [letzteres wird empfohlen]) == true
}

Von .hashCode() ausgehend:
switch(x.hashCode()==y.hashCode()){
case true:
  ( x.equals(y) || !(x.equals(y) ) == true
case false:
  (x.equals(y) == false) == true
} 

Also ist x.equals(y) == true hinreichend (aber nicht notwendig) für x.hashCode()==y.hashCode()==true und x.hashCode()==y.hashCode()==false hinreichend (aber nicht notwendig) für x.equals(y)==false.

Java Class Datei

Jede Klasse in Java hat ein Singelton-Objekt vom Typ Class, welches zurückgegeben werden kann durch eineInstanz.getClass() oder durch das Literal EineKlasse.class. Dieses Singleton-Objekt ist dasjenige Objekt, welches bei synchronized static Methoden der Klasse zum Einsatz kommt.

Aufgrund der Tatsache, daß das Class Objekt dem Singleton-Pattern entspricht und die .equals() Methode aus Object von der Klasse Class nicht überschrieben wird, gilt.

Class a,b;

...

a.equals(b) <==> a==b.

Dienstag, 27. Juli 2010

Java RMI - Remote Method Invocation

Um Methoden einer Instanz fernaufrufbar zu machen, muß man die gewünschten Methoden in ein Interface auslagern, welches das Interface

java.rmi.Remote
erweitert. Anschließend muß man die jeweilige Instanz für Remotezugriff exportieren. Dies geschieht entweder auf direktem Weg mittels statischer Methode
java.rmi.server.UnicastRemoteObject.exportObject()
oder indirekt für alle erzeugten Instanzen der Klasse, wenn diese von
java.rmi.server.UnicastRemoteObject
erbt. Dabei muss man aber die Konstruktoren der Klasse als
throws RemoteException
deklarieren. Im implizit oder explizit erfolgenden Aufruf des Basiskonstruktors von
UnicastRemoteObject
geschieht dann der Export der Instanz.
Man kann sich den Export einer Instanz als Mapping vorstellen, bei dem zu jedem Remote Objekt als Schlüssel ein entsprechender Proxy in Stub-Gestalt als Wert hinterlegt wird. Bei Parameterübergaben an remote aufgerufenen Methoden (per Stub Instanzen) und bei entsprechenden Rückgabewerten werden alle Objekte vom Typ Remote durch Ihre Stubs ersetzt, sofern diese Remote Objekte implizit oder explizit exportiert wurden. Falls kein Export definert wurde, dann geschieht die ganz normale Serialisierung der Remote Objekte. Im Falle der Exportfreigabe der jeweiligen Remote-Instanzen werden die zugehörigen Stubs an ihrer Stelle serialisiert. Wenn ein Client mehrfach einen serialiserten Stub für dieselbe Remote-Instanz als Rückgabewert einer Methode erhält, dann sind diese Stub Objekte bzgl. des Vergleiches mittels

==
alle unterscheidlich, aber sie kommunizieren alle mit demselben Remote Objekt. Daher sollte man mit Stub Objekten immer nur mittels
.equals()
vergleichen.

Zitate:

  • Wenn eine Methode eine lokale Referenz auf ein exportiertes Remote-Objekt zurückgibt, gibt RMI nicht das (A.d.Ü: Remote-) Objekt zurück. Stattdessen ersetzt es (A.d.Ü: dieses) mit einem anderen Objekt (dem Remote Proxy für diesen Dienst) im Rückgabestrom.

    [Übersetzung von: "When a method returns a local reference to an exported remote object, RMI does not return that object. Instead, it substitutes another object (the remote proxy for that service) in the return stream."] aus http://java.sun.com/developer/onlineTraining/rmi/RMI.html#RemoteObjectParameters

Links:

Donnerstag, 22. Juli 2010

TCP Sockets

Ein TCP Socket ist ein vollduplex Stream Kanal der es Anwendungen erlaubt miteinander zu kommunizieren. Dabei ist der Name Socket von der bildlichen Vorstellung einer Steckdosenbuchse abgeleitet, bei der eine Öffnung dem Empfang und die Andere dem Senden von Daten dient.
In C wird ein Socket über einen Filedeskriptor angesprochen, welches eine natürliche Zahl vom Typ int ist. Man kann diesen Filedeskriptor sowohl zum Lesen als auch zum Schreiben benutzen, indem man ihn als Argument in den Funktionen der stdio Bibliothek angibt. Beim Lesen kommen die Daten vom verbundenen Remote-Socket und beim Schreiben gehen Sie an den verbundenen Remote-Socket. Man benutzt aber zum Lesen und Schreiben immer den Lokalen Socket, weil nur dieser im Betriebsystem real vorhanden ist. Nur semantisch liest und schreibt man vom bzw. zum Remote-Socket.
Eine bestehende Kommunikationsverbindung von zwei Anwendungen mittels Sockets läßt sich extern als ein Socketpaar definieren.
In Windows wird ein verbundener Socket mit dem Befehl

netstat -np tcp
angezeigt.

Dabei könnte man die Remoteadresse im lokalen Socket als Remote Socket interpretieren. Der Remote-Socket ist allerdings als virtuell anzusehen, da er nur auf dem Remote System existiert. In dieser Interpretation hätte man direkt die Darstellung der Verbindung als Socket-Paar, wobei jeder Socket mit seiner Absenderadresse dargestellt wird.
Die Erzeugung und Verbindung von Sockets unterscheidet sich nach der Art der Verbindungsherstellung:

Für Server gilt:


  1. int socket(PF_INET, SOCK_STREAM, 0) -- gibt Filedskriptor für erzeugten Socket zurück (bsp 7)
  2. int bind(7,<Adresse von Struktur wo lokale IP+Port steht>,<größe der Struktur>) -- Rückgabe Fehlercode
  3. int listen(7,<Anzahl erlaubter gleichzeitiger Verbindungsgesuche von Clients>) -- Rückgabe Fehlercode
  4. int accept(7,<Adresse von Struktur wo remote IP+Port gespeichert werden soll>,<größe der Struktur>) -- gibt Filedeskriptor von neuem in Verbindung stehendem Socket zurück (bsp 40)
In Punkt 2 wird dem erzeugten Socket die lokale Protokolladresse zugeordnet und dann mit listen auf Verbindungsgesuche von Clients gewartet. In Punkt 4 wird dann ein enstprechender Verbindungsgesuch von einem Client entgegengenommen, indem man die Protokolladresse des Clients abspeichern läßt und man erhält einen neuen Socket mit einem neuen Filedeskriptor, in welchem sowohl die lokale Server Adresse wie auch die remote Client Adresse festgehalten sind.
Für Clients gilt:


  1. int socket(PF_INET, SOCK_STREAM, 0) -- gibt Filedskriptor für erzeugten Socket zurück (bsp 10)
  2. int connect(10,<Adresse von Struktur wo remote IP+Port steht>,<größe der Struktur>) -- Rückgabe Fehlercode
In Punkt 2 wird eine beiliebige lokale Client Adresse vom Betriebsystem ausgesucht und eine Verbindung mit dem Server über die angegebene Remote-Server-Adresse hergestellt. Dabei wird kein neuer Filedeskriptor für den Client-Socket generiert, sondern nur die Felder dieses gesetzt.


Wenn in Java auf einem Server folgende Notation benutzt wird
Socket clientSocket = serverSocket.accept();
dann soll mit "clientSocket" nur zum Ausdruck gebracht werden, dass die Quelle und das Ziel von Lese-Schreib-Vorgängen ein Remote-Client Socket ist. Real wird aber nur der existierende lokale Socket benutzt, der bzgl. lokaler Adresse mit
serverSocket
übereinstimmt, jedoch noch zusätzlich eine gültige remote Adresse aufweist. Es ist diesbezüglich auch ein neu erzeugtes Socket-Objekt.

Montag, 5. Juli 2010

Logging in Java

Mittels des Apache Pakets log4j 1.2 kann man bequem Log Meldungen im Code unterbringen, die unterschiedlich gewichtet sind. Man definiert zunächst ein Log Objekt über den Aufruf der Fabrikmethode Logger.getLogger(EineKlasse.class). Die Methode Logger.getLogger(String) erwartet einen String Parameter, der indirekt über .getName() von Class geliefert wird. Das ist also äquivalent zu Logger.getLogger("class vollerPfadzuEineKlasse"). Dadurch wird ein Singleton Logger für die Klasse EineKlasse erzeugt.
Mit diesem Objekt kann man dann über verschiedene Level Meldungen rausschicken.
Die Level mit ihrer Priorität sind: DEBUG < INFO < WARN < ERROR < FATAL.

Die entsprechenden Methoden meinLogger.debug("Text ....") usw. . Einem Logger wird nun ein Level zur Startzeit zugeordnet (über Konfigurationsdatei), über den die im Code eingestreuten log Aufrufe aktiviert oder deaktiviert werden.
Alle log Meldungen, deren Level mindestens genauso hoch wie der aktuelle Log Level sind sind aktiv.
Wenn der Level des Loggers auf DEBUG eingestellt wird, dann sind alle Log Methodenaufrufe aktiv. Wenn der Level des Loggers auf FATAL eingestellt ist, dann werden nur die Methodenaufrufe .fatal() aktiv. Alle anderen log Aufrufe über .debug(), .info() , .warn() und .error() werden nicht verarbeitet. Dies ist zu vergleichen mit dem Makroprozessor in C, wo über Headervariablen einzelne Codeabschnitte für die Kompilierung ein oder ausgeschaltet werden. Nur geschieht das hier rein formal nicht in der Kompilierphase, sondern zur Laufzeit. Allerdings ist der Performance-Effekt laut Apache gleichzusetzen mit dem Ein- und Ausblenden zur Kompilierzeit.

Falls einem Logger kein Level zum Programmstart zugeordnet wird, dann erbt es den Level seines Vorfahrrens. Für diesen gilt das gleiche. Da der Rootlogger immer einen Level hat (DEBUG) besitzt im Umkehrschluss jeder Logger einen Level über diesen Vererbungsmechanismus.



Die Log Meldungen können nun wiederum an verschieden Orte ausgegeben werden. Dazu werden dem Logger ein oder mehrere Appender zugewiesen. Diese können Konsolen, Dateien, Socket-Verbindungen und beliebige andere Ziele sein. Zuweisung erfolgt über log4j.appender.AppenderName[.*]=Wert Zuweisungen.


Hier ein Beispiel:

log4j.appender.SessionLogAppender=de.xxx.util.log.JIDFileAppender
log4j.appender.SessionLogAppender.file='Logs/'yyyy-MM-dd/'Logs %s_'yyyy-MM-dd_HH-mm-ss'.log'
log4j.appender.SessionLogAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.SessionLogAppender.layout.ConversionPattern=%-5p %d{yyyy-MM-dd HH:mm:ss,SSS} (%F:%L) %m%n 
 
Alle aktiven Log Meldungen vom Logger werden an jeden definierten Appender herausgeschickt und an die Appender seiner Vorfahren.
Dabei kann ein Logger durch setzen des additivity flag auf false das Erben von Appendern seiner Vorfahren verhindern.
Beispiel:
Logger a = Logger.getLogger("a");//Appender für Klassen mit Paketpräfix a
Logger b = Logger.getLogger("a.b");//Will Appender von a erben. additivy flag = true (Voreinstellung) 
Logger c = Logger.getLogger("a.c");//Will nur seine eigenen Appender und nicht die von a. Setzt dazu additivy flag = false 
 
Der Root Logger hat übrigens keinen Default Appender, sondern nur einen Default Debug Level (nämlich DEBUG).

Donnerstag, 10. Juni 2010

Extension-Points, Extensions, Adapter und AdapterFactories

Eclipse kann man sich als ein Puzzle vorstellen, deren einzelne Funktionen auf die Puzzle-Teile verteilt sind und sich gegenseitig zu einem Gesamtkonzept verbinden. Dabei entspricht einem Puzzle-Teil eine abgegrenzte Menge von Funktionalität, die über einen eigenen Klassenpfad und Sichtbarkeitsbereich verfügt. Um aber als Puzzle-Teil gebrauch von Funktionalität seitens anderer Puzzle-Teile (Plug-ins) machen zu können und seinerseits anderen Plug-ins die eigene Funktionalität zur Verfügung zu stellen, gibt es sogenannte Extension-Points.


Extension-Points

Einem Extension-Point eines Plug-ins entspricht bildlich eine Steckdosenleiste mit festgelegter spezifischer Buchsenform; so zum Beispiel Flachstecker-Buchse oder Rund-Buchse. Dieser bildlichen Umschreibung eines Extension-Points entspricht softwaretechnisch ein Interface für den jeweiligen Extension-Point. Ein Plug-in stellt über Extension-Points eine API bereit, mit der es Benutzern der Extension-Points mitteilt, welche Funktionserweiterungen eingebunden werden können. So kann ein Plug-in z.B. über einen Extension-Point anderen Plug-ins ermöglichen sich als Event-Listener zu registrieren oder aber Menüeinträge und Symbolleisten zu erweitern, indem Attribute wie Beschriftung, Symboldatei usw. als zu setzende Parameter definiert und evtl. ein Interface von Seiten des Extension-Point-Anbieters vorgegeben wird.
Die Parameter des Extension-Points definiert man in einer sogenannten *.exsd Datei, welche als Atrribut vom
<extension-point>
Element in der Datei plugin.xml verlinkt wird. Diese *.exsd Datei wird ausgelesen, wenn man sich die Beschreibung eines Extension-Points im "Plug-in Manifest Editor" der Eclipse PDE Perspektive anzeigen läßt.





Extensions

Jede Benutzung eines Extension-Points stellt eine Extension dar. In der plugin.xml kommt dies durch das xml Element extension zum Ausdruck. Im Unterschied zur Definition eines Extension-Points wird bei der Erstellung einer Extension
<extension point="...">
statt
<extension-point id="...">
geschrieben. Dabei enthält das Attribut point den Wert des Attributes id der Extension-Point Definition, allerdings in vollqualifizierter Schreibweise mit ID des den Extension-point definerenden Plug-ins.

Eine Extension wird also vom Extension-Point definerenden Plug-in fremdaufgerufen. Dies entspricht dem Hollywood Prinzip: Don't call us, we call you!


Adapter

Die zweite Möglichkeit eigenen Code in das bestehende Plug-in-Geflecht von Eclipse einzuschleusen ist in der Definition von Adaptern gegeben. Ein Adapter bietet die Möglichkeit eine bestehende Klasse im Nachhinein um Interface Implementierungen zu erweitern. Dabei wird die Signatur der Klasse also nicht verändert und es treten keine Konflikte mit davon abhängenden anderen Klassen auf. Was passiert ist vielmehr ein Eingriff zur Laufzeit. Nahezu jede öffentliche Klasse eines Plug-ins implementiert hierzu das Interface IAdaptable. Wenn man nun später weitere Interfaces unterstützen möchte ohne die Signatur der Klasse ändern zu müssen, überschreibt man einfach die einzige Methode aus IAdaptable, getAdapter(Class c).
Darin schreibt man dann sowas wie:
class B extends A
{
    private NewInterface delegate = new NewInterfaceImpl();
    class NewInterfaceImpl implements NewInterface
    {
        //Innere Klasse hat vollen Zugriff auf A mittels A.this
        public f1()
        {
            //benutze Methoden von A so, dass f1 sinnvoll realisiert wird.
        }
        ...
    }
    public Object getAdapter(Class c)
    {
        if (c == NewInterface.class)
        return delegate;
        else
        super.getAdapter(c);
    }
}
Falls nun jemand Gebrauch von dem neuen Interface machen will, so kann er ganz leicht überprüfen, ob dieses Interface unterstützt wird, indem er folgendes schreibt:
A a = ...;
Object ni = null;
if((ni=a.getAdapter(NewInterface.class))!=null) ((NewInterface) ni).f1();

Auf diese Weise kann man also die dynamische Signatur einer Klasse ändern.
Was aber hat das mit dem einbringen von eigenem Code in das bestehende Plug-in-Geflecht zu tun?
Wir können ja nicht davon ausgehen, dass wir die jeweilige Klasse überschreiben und dann statt der Originalklasse unsere instantiiert wird. Daher gibt es noch eine zweite Möglichkeit der Nutzung des Adapter Prinzips


AdapterFactories

Eine AdapterFactory ist eine Klasse, welche das Interface IAdapterFactory implementiert. Dieses Interface hat nur die beiden Methoden
//Gibt Liste aller unterstützten Adapter zurück
public Class[] getAdapterList();
/*adaptableObject teilt der Fabrik mit, welches Objekt um einen entsprechenden Adapter vom Typ adapterType gefragt wurde.
*/
public Object getAdapter(Object adaptableObject, Class adapterType);

Eine Fabrik verwaltet also eine Menge von Adaptern im Auftrag eines anderen Objektes. Die Verknüfung von Fabrik und Klient, welcher wie nachfolgend begründet vom Typ IAdaptable ist, erfolgt über einen systemweiten Adaptermanager. Dieser Adaptermanager implementiert das Interface IAdapterManager mit u.a. folgender Methode
public void registerAdapters(IAdapterFactory factory, Class client);
Auf den systemweiten Adaptermanager erhält man Zugriff über 
Platform.getAdapterManager()
und kann nun (den Objekten) einer Klasse eine entsprechende Fabrik zuordnen. Dies geschieht auf Grundlage der Nennung des entsprechenden Grundtyps, auf den hin die Fabrik aktiv werden soll.

Was jetzt geschieht ist folgendes:
Ein Objekt, welches eine Fabrik damit beauftragen will, entsprechende Adapter für ihn zu produzieren, implementiert die Methode aus IAdaptable auf folgende weise:
public Object getAdapter(Class adapterType){
    return Platform.getAdapterManager().getAdapter(this,adapterType);
}

Der Adaptermanager durchläuft nun die Vererbungshierarchie des Aufrufers von unten nach oben bis zur Klasse Object und fragt in jeder Stufe mit dem enstprechenden Klassenobjekt (Typ Class) als Schlüssel die Hashtabelle nach registrierten Fabriken. Ob der gesuchte Adapter von der Fabrik unterstützt wird erkennt der Adaptermanager durch Aufruf der Fabrikfunktion getAdapterList(). Im Erfolgsfall wird die Suche frühzeitig beendet und die entsprechende Fabrik folgendermaßen aufgerufen.
//innerhalb von getAdapter(Object adaptableObject,Class adapterType) vom IAdapterManager
...
//Nachfolgend Pseudo-Code
IAdapterFactory fab;
fab = findFactory(adaptableObject,adapterType);
return fab.getAdapter(adaptableObject,adapterType);
...
Der Grundgedanke dazu ist folgender:
Eine Klasse hat ja eine Vererbungshierarchie, welche aus Klassen und Interfaces bestehen kann. Gehen wir von folgender Vererbungshierarchie aus:
public class C extends B implements IC_1, IC_2 {...}

public class B extends A implements IF {...}

public class A {...}


Wenn wir nun ein Objekt
Object obj = new C();
erzeugen, dann ist folgendes wahr:
(obj instanceof C) && (obj instanceof B) && (obj instanceof A) &&
(obj instanceof Object) && (obj instanceof IC_1) && (obj instanceof IC_2) && (obj instanceof IF)
== true

Wir haben also für die Registrierung der Adapterfabrik für die Klasse C folgende Möglichkeiten:
//Konkrete Fabrik muss je nach gewählter Alternative implementiert werden. 
IAdapterFactory fab = ...;

IAdapterManager man = Platform.getAdapterManager();

1. man.registerAdapters(fab,C.class);
2. man.registerAdapters(fab,B.class);
3. man.registerAdapters(fab,A.class);
4. man.registerAdapters(fab,Object.class);
5. man.registerAdapters(fab,IC_1.class);
6. man.registerAdapters(fab,IC_2.class);
7. man.registerAdapters(fab,IF.class);


Bei Variante 1 sieht die Fabrik so aus:
class C_casting_Factory implements IAdapterFactory
{
    ...
    Object getAdapter(Object client,
    Class adapterType){
        C casted = (C) client;
        if(adapterType==Adapter1.class){
            //Konstruktor: Adapter1_impl(C client);
            return new Adapter1_impl(casted);
        }
        elseif(adapterType==Adapter2.class){
            //Konstruktor: Adapter2_impl(C client);
            return new Adapter2_impl(casted);
        }
        ...
    }
}

und bei Variante 3 so:
class A_casting_Factory implements IAdapterFactory
{
    ...
    Object getAdapter(Object client,
    Class adapterType){
        A casted = (A) client;
        if(adapterType==Adapter1.class){
            //Konstruktor: Adapter1_impl(A client);
            return new Adapter1_impl(casted);
        }
        elseif(adapterType==Adapter2.class){
            //Konstruktor: Adapter2_impl(A client);
            return new Adapter2_impl(casted);
        }
        ...
    }
}

Dadurch kann es vorkommen, das für eine Klasse gleich mehrere Fabriken in Frage kommen, welche den angefragten Adapter zurückgeben können. In diesem Fall gilt die Regel. Die Fabrik, welche mit dem spezifischsten Klassenobjekt regsitriert wurde erhält den Zuschlag und darf den entsprechenden Adapter zurückgeben.


Kommen wir nun zurück zu unserem Ausgangsgedanken:
Wir wollen von externer Seite neue Adapter für eine Klasse hinzufügen oder bestehende Adapter überschreiben.
Dazu kann man den Extension-Point
org.eclipse.core.runtime.adapters
verwenden oder aber zur Laufzeit folgenden Code
Platform.getAdapterManager().registerAdapters(fab,client)
schreiben.

Eine mögliche Extension könnte folgendermaßen aussehen:
<extension point="org.eclipse.core.runtime.adapters">
  <factory adaptabletype="pfad.zum.klienten.der.fabrik"
      class="pfad.zur.fabrik.klasse">
    <adapter type="pfad.klasse.adapter1"/>
    <adapter type="pfad.klasse.adapter2"/>
  </factory>
</extension>

Dabei ist die Aufzählung der Adapter nur als deklarative Wiederholung von der Fabrik-Methode
getAdapterList()
zu verstehen. Die obige Extension Variante der Fabrikregistrierung ersetzt die Notwendigkeit bei Plug-in Aktivierung folgenden Code schreiben zu müssen:
Platform.getAdapterManager().registerAdapters(pfad.zur.fabrik.klasse,pfad.zum.klienten.der.fabrik);

Die registrierte Fabrik wird aber nur dann bei der Adaptersuche miteinbezogen, wenn das die Fabrik definierende Plug-in geladen ist. Das bloße definieren der Extension hat nicht die Aktivierung des zugehörigen Plug-ins zur Folge.


Falls noch irgend etwas unklar sein sollte, dann schreibt mir einen Kommentar dazu.