2016-06-20 7 views
0

Ich habe mehrere Tage auf diesem niggly Problem jetzt verbracht und kann einfach keine Lösung finden.Wie eine Callback-Methode zwischen verschiedenen Klassen zur Laufzeit übergeben

Hier ist, was ich versuche zu tun: Lesen Sie eine Zeileneingabe und ändern Sie die Interpretation der Daten, basierend auf bestimmten Schlüsselwörter. Diese Schlüsselwörter können von Clientobjekten dynamisch registriert werden. Sie registrieren ein Stichwort und "Rückruf" Funktion (für ein besseres Wort). Diese "Callback" -Funktion wird aufgerufen, wenn das Schlüsselwort angetroffen wird, um mit der Eingabezeichenfolge umzugehen und ein standardisiertes Objekt zurückzugeben.

Dafür habe ich eine HashMap mit dem Schlüsselwort als Schlüssel und einer Methodenreferenz als Wert erstellt. Während der Initialisierung des Programms registrieren sich alle Objekte, die den Dienst nutzen wollen.

Nach der Initialisierung wird die Eingabe gelesen und auf Schlüsselwörter gegen meine Hashmaps überprüft. Wenn der Typ dort vorhanden ist, wird die Wertzeichenfolge zur Verarbeitung übergeben und eine standardisierte Objektdarstellung aus den Zeichenfolgendaten zurückgegeben.

Ich schaffte es, eine Methodenreferenz zu entlocken, indem ich ihren Namen nachschaute und die Methode abrief. Der folgende Code kann die Methode übergeben, speichern und abrufen, aber beim Versuch, sie aufzurufen, wird eine IllegalArgument-Ausnahme ausgelöst.

Während meiner Recherchen fand ich mehrere Berichte von ähnlichen Problemen, unter anderem auch auf dieser Website, aber die meisten haben sich mit meinem genauen Problem nicht ganz beschäftigt.

In one case gab es den Vorschlag, dass der Methodenaufruf nicht funktionieren kann, weil keine Instanz der Methode instanziiert wurde. Es könnte behoben werden, indem die Methode newInstance aufgerufen wird, um die erforderliche Instanziierung der Methode bereitzustellen. Es hat nicht für mich funktioniert, weil ich aus dieser Methodenreferenz keine neue Instanz bekommen kann.

Verschiedene Ansätze von Interfaces über Java8 Methodenreferenzen und Listener Patterns wurden getestet. Sie alle erlaubten die Weitergabe von Methodenreferenzen, aber ich kam immer zu einem totalen Stillstand, weil ich in XSettings eine Instanz des Clients deklarieren müsste. Und das ist genau was ich nicht tun kann, weil ich nicht was zu XSettings die ganze Zeit ändern kann.

Unten ist mein Code:

Locus.java

--------------------------------- 
import java.lang.reflect.Method; 
import java.util.concurrent.Callable; 
import java.util.function.Function; 

... 

public class Locus implements StringConverter{ 
    private long x; 
    private long y; 

    public Locus(int x, int y) { 
     super(); 
     this.x = x; 
     this.y = y;  
    } 

    public Locus(long x, long y) { 
     super(); 
     this.x = x; 
     this.y = y;  
    } 

    public Locus(String s) { 
     super(); 

     Locus lc = (Locus) convert(s); 
     this.x = lc.x; 
     this.y = lc.y; 
    } 

    private long getLongFromString(String s1){ 
     long res=0L; 
     if(s1.matches("[0-9]*$"))  // Integer detected 
      res = (long)(Integer.parseInt(s1)); 
             // Long detected 
     else if(s1.matches("[0-9]*[lL]$")){ 
      s1=s1.split("[lL]")[0]; 
      if(!s1.isempty())    // don't parse empty strings 
       res = Long.parseLong(s1); 
     }        // Double detected 
     else if(s1.matches("[0-9][0-9]*\\.[0-9]*$")) 
      res = (long) Double.parseDouble(s1); 

     return res; 
    } 

    public Object convert (String s) { 
     long x=0, y=0; 

     if(s.contains(",")){ 
      String[] parts = s.split("\\,"); 
      parts[0] = parts[0].trim(); 
      parts[1] = parts[1].trim(); 

      x = getLongFromString(parts[0]); 
      y = getLongFromString(parts[1]); 
     } 
     return createLocus(x, y); 
    } 

    public static void init() { 
     Method method = null;  
     try { 
      method = Locus.class.getDeclaredMethod("convert", String.class); 
     } catch (NoSuchMethodException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (SecurityException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     }  

     if(method != null) 
      XSettings.setTypeHandler("Locus", method); 
    } 
    ... 
} 

--------------------------------- 

XSettings.java

--------------------------------- 
import java.io.BufferedReader; 
import java.io.FileInputStream; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.nio.charset.Charset; 
import java.util.HashMap; 
import java.util.Map.Entry; 
import java.util.Iterator; 

public class XSettings { 

    // ************************************************* 
    // Singleton Pattern 
    private XSettings(){} 

    private static class InstanceHolder { 
     public static final XSettings instance = new XSettings(); 
    } 

    public XSettings getInstance() { 
     return InstanceHolder.instance; 
    } 

    // ************************************************* 

    static final String cfgFileName="C:\\workspace\\Router\\route.cfg"; 
          // Map with the payload data to be queried 
    public static HashMap<String, Object> settings = new HashMap<String, Object>(); 
          // Map with Key/method-references 
    static HashMap<String, Method> typeHandler = new HashMap<String, Method>(); 

          // caller method for the object converters 
    public static Object convertToType(String type, String value){ 
     Object result = null; 
     Method method = typeHandler.get(type); 

     if(method!=null){ // method exists 
      try { 
       result = method.invoke(method, value); // <------ causes IllegalArgumentException 
      } catch (IllegalAccessException e) { 
       e.printStackTrace(); 
      } catch (IllegalArgumentException e) { 
       e.printStackTrace(); 
      } catch (InvocationTargetException e) { 
       e.printStackTrace(); 
      } 
     } else { 
      result=value; // No? Default pass back the input string 
     } 
     return result; 
    } 
         // store the method refernce in map 
    public static void setTypeHandler(String key, final Method method) { 
     typeHandler.put(key, method); 
    } 
         // Store a payload key value pair 
    public static void set(String key, Object value) { 
     settings.put(key, value); 
    } 
          // Retrieve a value by key 
    public static Object get(String key) { 
     return settings.get(key); 
    } 

    public static void init(){  // read input 
     String line; 
     try (
      InputStream fis = new FileInputStream(cfgFileName); 
      InputStreamReader isr = new InputStreamReader(fis, Charset.forName("UTF-8")); 
      BufferedReader br = new BufferedReader(isr); 
      ) 
     { 
      while ((line = br.readLine()) != null) { Parse the lines 
       String key=""; 
       String type=""; 
       String val=""; 
       line=line.trim();     // trim back empty space 
       System.out.println(line); 

       if(line.isEmpty())     // skip empty lines 
        continue; 
       if(line.charAt(0)=='#')    // skip comment lines 
        continue; 

       String[] subs = line.split("=",2); // split at assignment 
        continue; 

       subs[0]=subs[0].trim();    // key Part 
       subs[1]=subs[1].trim();    // value Part 
       String[] subValues = subs[0].split("::", 2); //Split at type, key section 
       if(subValues.length==1){    // empty value 
        key = subValues[0]; 
       } else if(subValues.length>1){  // normal key, value case 
        type = subValues[0]; 
        key = subValues[1]; 
       } else {        // no assignment operator, empty value 
        key = subs[0];    
       } 
       subValues = subs[1].split("\"");  // remove double quotes from value part 
       if(subValues.length==0){    // no quotes and no value 
        val = ""; 
       } else if(subValues.length==1){  // no quotes 
        val = subValues [0]; 
       } else if(subValues.length==2){  // quotes present, stripped 
        val = subValues [1];      
       } else        // more than two quotes treat as a string only 
        val = subs[1]; 

       if(!typeExists(type)){    // check if type is in methodHandler 
        key = subs[0];        // No: set key back to full L-Value 
        type = ""; 
       } 

       settings.put(key, convertToType(type, val)); // Call converter 
      } 

     } catch (Exception e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
    } 

    private static boolean typeExists(String type) { 
     boolean res=false; 
     if(type!=null) 
      if(!(type.isEmpty())) 
       Object m = typeHandler.get(type); 
        res=(m!=null); 

     return res; 
    } 

... 

} 
--------------------------------- 

Die Servicefunktion ist XSettings und der Kunde ist Locus. XSettings liest eine Eingabedatei Zeile für Zeile und scannt sie nach Schlüsselwertpaaren. Der Schlüssel darf den Namen der Zielklasse enthalten. Die Zeile wird also in Typ, Schlüssel und Wert aufgeteilt.

Die Interpretation des Werts unterscheidet sich je nach Typ. Standardmäßig sind nur Strings entfernt gespeichert. Wenn andere Objekte ein Schlüsselwort registrieren und die Konvertierungsmethode liefern, werden sie abgeholt und in das gewünschte Objekt konvertiert.Wenn also der Schlüssel abgefragt wird, ist das Ergebnis sofort das richtige Objekt.

Ich habe es geschafft, die Methodenreferenz den gesamten Weg zum Aufrufpunkt zu bekommen. Das Objekt sieht in Locus identisch aus, wenn es an die Methode setTypeHandler von XSettings übergeben wird, sowie nach dem Aufruf aus der HashMap und vor dem Aufruf von . Dann IllegalArgumentException ...

Dies könnte eine sehr "C" -artige Art, dies zu tun, aber ich dachte immer, es war relativ effizient und elegant. Wenn es jedoch einen viel besseren Java-ähnlichen Weg gibt, wäre ich begeistert davon zu hören.

Vielen Dank für Ihr Interesse. Ich freue mich wirklich darauf, von Ihnen zu hören.

Antwort

0

Ich denke, in XSettings.convertToType müssen Sie zunächst eine Instanz des angegebenen Typs erstellen und dann die angegebene Methode für diese Instanz aufrufen.

Das Erstellen einer Instanz dieses Typs bedeutet: Laden Sie die Klasse für diesen Typ, suchen Sie den Standardkonstruktor mit der reflection api und rufen Sie diesen Konstruktor auf, um eine Instanz dieses Typs zu erhalten.

+0

Danke Martin für den Tipp. Allerdings habe ich im Moment keine Ahnung, wie ich das machen soll. Wird dies untersuchen und zurückmelden. – Helmut

+0

hier ist das, was ich die invoke Linie ... geändert \t \t \t \t result = method.getClass() newInstance() aufrufen (Methode, Wert)..; ... nicht mehr IllegalArgumentException, jetzt ist es InstantiationException. Ich denke, das könnte sein, weil es keinen leeren Konstruktor in dieser Klasse gibt. Müssen das überprüfen. – Helmut

+0

Die Lösung ist, dass die Methode in der Tat uninstantiated ist. GetClass gibt jedoch nicht die Locus-Klasse, sondern die Reflection-Klasse zurück. – Helmut

Verwandte Themen