Classpath Enumerationen + Reflexion

White_Fox

Top Contributor
Guten Abend allerseits.

Folgendes: Ich will einen String entgegennehmen (kommt aus class.getCanonicalName() bzw. getClass.getCanonicalName()) und wissen, ob dieser String zu einer Enumeration gehört.

Java:
void method(String classname){
    //...
    try{
        Class clazz = Class.forName(classname);
        if(clazz.isEnum()){
            //...
        }
    }
    catch(Exception e){
        throw new RuntimeException(classname + ": Class is unknown");
    }
}

Dabei kommt aber ständig die Exception. Ich teste mit einer inneren Enumeration in der Testklasse. Hat jemand eine Idee, die besser funtioniert?
 
Beste Antwort
Geht doch?

Java:
    public static void main(String[] args) throws ClassNotFoundException {
        
        Class<?> arrayClass = byte[].class;
        System.out.println(arrayClass.getName());
        
        Class<?> forName = Class.forName(arrayClass.getName());
        System.out.println(forName.isArray());
        
    }

Ausgabe::
Code:
[B
true

LimDul

Top Contributor
Java:
package de.limdul.javaforum;

public class EnumTest {
    
    private enum InnerEnum {
        A,B;
    }
    
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(InnerEnum.class.getName());
        System.out.println(InnerEnum.class.getCanonicalName());
        Class<?> c = Class.forName(InnerEnum.class.getName());
        System.out.println(c.isEnum());
    }

}

Ausgabe:
de.limdul.javaforum.EnumTest$InnerEnum
de.limdul.javaforum.EnumTest.InnerEnum
true
 

LimDul

Top Contributor
Hier noch was aus Stackoverlow dazu:

  • the name is the name that you'd use to dynamically load the class with, for example, a call to Class.forName with the default ClassLoader. Within the scope of a certain ClassLoader, all classes have unique names.
  • the canonical name is the name that would be used in an import statement. It might be useful during toString or logging operations. When the javac compiler has complete view of a classpath, it enforces uniqueness of canonical names within it by clashing fully qualified class and package names at compile time. However JVMs must accept such name clashes, and thus canonical names do not uniquely identifies classes within a ClassLoader. (In hindsight, a better name for this getter would have been getJavaName; but this method dates from a time when the JVM was used solely to run Java programs.)
  • the simple name loosely identifies the class, again might be useful during toString or logging operations but is not guaranteed to be unique.
  • the type name returns "an informative string for the name of this type", "It's like toString(): it's purely informative and has no contract value"
 

White_Fox

Top Contributor
Hm...wenn ich meinen Test - abweichend von meinem sonstigen Verfahren - etwas umstricke (getName() anstelle von getCanonicalName()), funktoniert(e) es.
Also dachte ich mir, ich filtere einfach den classname ($ -> .) und testest mit dem Filtrat. Das funktoniert jedoch auch nicht.

Ich poste hier mal die ganze Methode:
Java:
public static ValueType getMemberTypeFromClassname(String classname) {
        if (classname.equals("")) {
            return NULL;
        }
        
        if (classname.contains("[]")) {
            return ARRAY;
        }
        
        try{
            String s = classname.replace("$", ".");
            Class<?> cls = Class.forName(s);
            if(cls.isEnum()){
                return ENUMERATION;
            }
        }
        catch(Exception e){
            throw new RuntimeException(classname + ": Class is unknown", e);
        }
        
        for (ValueType type : values()) {
            for (Class cls : type.classes) {
                if (cls.getCanonicalName().equals(classname)) {
                    return type;
                }
            }
        }
        return OBJECT;
    }

Hier ist der Test:
Java:
public class ValueTypeTest {

    public ValueTypeTest() {
    }
    
    enum TestEnum{
        ONE,
        TWO,
        THREE;
    }
    
    @Test
    public void testGetMemberTypeFromClassname() {
        ValueType result;
        ValueType expResult;
        String canonicalClassname;
        
        canonicalClassname = TestEnum.TWO.getClass().getCanonicalName();
        expResult = ValueType.ENUMERATION;
        result = ValueType.getMemberTypeFromClassname(canonicalClassname);
        assertEquals(expResult, result);
    }
}

Sieht jemand vielleicht, was ich da nicht sehe?
 

LimDul

Top Contributor
Warum verwendest du überhaupt getCanonicalName? Der ist dem Kontext schlicht falsch. Wenn du Klassen laden willst, brauchst du das was getName() liefert. Du kannst nicht von CanonicalName auf den ladbaren Klassennamen kommen, das klappt nicht.
 

White_Fox

Top Contributor
Ja...ich hab auch grad bemerkt daß das mit dem Filtern Müll ist.

Warum ich getCanonicalName verwende...ehrlich gesagt, das weiß ich nicht mehr, das ist jetzt ungefähr 9 Monate her. :D
Ich hab mich damals zwar etwas damit befasst welche Methode ich nehmen sollte und ein paar Tests dazu gemacht, aber warum ich mich dann so entschieden habe weiß ich nicht mehr.

Aber Danke, dann werde ich mal auf getName() umstellen und mal schauen ob dann noch alles so gut funktioniert.

Was machen eigentlich die Entwickler, die so komplett auf Unittests verzichten? Und sowas dann erst auffällt, wenn man den ganzen Klassenwust das erste Mal anwerfen will? Sind die so gut und ich einfach nur schlecht, oder machen die nur wahnsinnig einfaches, kleines und überschaubares Zeug und ich etwas Hochkomplexes? Ich glaube ja eigentlich nicht das ich irre Raketensoftware schreibe...aber irgendwie macht mich das gerade mal wieder stutzig.
 

White_Fox

Top Contributor
Wenn ich mit getName() arbeite, habe ich an anderer Stelle Probleme:

Java:
byte[].class.getName() -> "[B"
byte[].class.getCanonicalName() -> "byte[]"

Das Instanzieren eines Arrays hat mit dem CanonicalName jedenfalls funktioniert.
 

LimDul

Top Contributor
Geht doch?

Java:
    public static void main(String[] args) throws ClassNotFoundException {
        
        Class<?> arrayClass = byte[].class;
        System.out.println(arrayClass.getName());
        
        Class<?> forName = Class.forName(arrayClass.getName());
        System.out.println(forName.isArray());
        
    }

Ausgabe::
Code:
[B
true
 
Beste Antwort

mrBrown

Super-Moderator
Mitarbeiter
Was machen eigentlich die Entwickler, die so komplett auf Unittests verzichten? Und sowas dann erst auffällt, wenn man den ganzen Klassenwust das erste Mal anwerfen will? Sind die so gut und ich einfach nur schlecht, oder machen die nur wahnsinnig einfaches, kleines und überschaubares Zeug und ich etwas Hochkomplexes? Ich glaube ja eigentlich nicht das ich irre Raketensoftware schreibe...aber irgendwie macht mich das gerade mal wieder stutzig.
Gibt doch hier bestimmt einige, die keine Unit-Tests nutzen, frag die mal :p

Aber zumindest die, die ich kenne, probieren einfach per Hand rum...
 

White_Fox

Top Contributor
Naja, die Frage war durchaus ernst gemeint.

Ich hänge es ja gern an die ganz große Glocke, daß ich kein Programmierer bin und vom Programmieren keine Ahnung habe. Da kann ich es mir leisten, mit blöden Ideen und Fragen zu kommen. :D

Aber auch Leute mit Ahnung lernen doch immer mal wieder etwas Neues...oder nicht? Mein Programm hat aktuell nicht ganz 9k Codezeilen und >150 Klassen. Und die minimale Basisfunktionalität (Daten erstellen, kompilieren, als CSV exportieren (ja, CSV) und seine Arbeit speichern können), also daß was mindestens sein muß damit ich das Programm einigen Leuten mal zum Pre-Alphatest geben kann, ist noch nichtmal fertig.

Wenn ich mir vorstelle das Programm jetzt zum ersten Mal zu testen und einen Fehler durch 25 Klassenaufrufe hindurch zu verfolgen, dann ist der irgendwann behoben und dann kommt sofort der nächste...da wird doch niemand fertig. Und dann wäre das garantiert nicht nur ein Fehler, sondern eher etwas Richtung hundert. Immer begleitet von dem Problem, das die Behebung eines Fehlers möglicherweise einen neuen hervorruft weil das Konzept mit sich selbst kollidiert (hatte ich auch schon mindestens einmal, es dank ausgiebigem Probieren recht schnell bemerkt).

Sowas wie meine ObjectProcessor-Klasse, die ich hier neulich mal reingestellt habe (der ich jetzt übrigens die Verarbeitung von Enumeratioinen beibringen will, das fehlte bisher noch und allein dieses Package besteht aus fast 2k Codezeilen und 25 Klassen), wie will man so etwas ohne Unittest jemals auf die Beine stellen? Ich glaube einfach nicht daß man so etwas ohne entsprechend viel Testerei hinbekommt.
 
K

kneitzel

Gast
Also ich selbst will dazu einfach einmal meine Sichtweise abgeben, denn ich habe ja auch ein paar Jahrzehnte auf dem Buckel und als ich angefangen habe, da gab es so Unit Tests noch nicht...

Eine Vorgehensweise ist durchaus, dass man das, was fertig ist, jeweils immer testet. Das hat gewisse Nachteile:
- Einer wurde schon genannt: Mit dem Debugger ist es eine harte Arbeit, durch den Code durch zu gehen, bis an die Stelle gefunden hat, an der der Fehler ist.
- Tests sind immer nur manuell zu wiederholen
- Es ist leicht, dass eben nicht alle Fälle getestet wurden (Coverage schlecht - alleine schon, da nicht kontrolliert)

Aber es gibt natürlich auch Vorgehen, die etwas Richtung Unit Tests gehen. Ich nenne es mal Modularisierung: Gewisse Funktionalitäten wurden losgelöst entwickelt. Das hatte dann den Vorteil, dass man einfacher genau diesen Teil prüfen konnte. Mit dem Verschieben in die eigentliche Applikation war es dann aber in der Regel hinfällig, denn ich kenne es etwas so, dass dies dann nicht als Test weiter gepflegt wurde. Es lief dann also auf den ersten Punkt hinaus.

Und das, was das Testen dann aber massiv ausmachte: Riesen Dokumente mit Testplänen und ich kann mich an Diskussionen erinnern, wie viele Tester auf einen Entwickler kommen sollten ...
==> Es wurde also eine neue Version geschnürt und diese ist dann an die Testabteilung gegangen. Diese haben dann Testpläne ausgedruckt und sind dann die Tests durchgegangen. Das waren dann Pläne a.la.
- Applikation mit Shortcut MyCoolApp starten.
- Splash-Screen erscheint mit Version a.b.c.d vom xx.yy.zzzz und Logo der Applikation.
- Splash Screen geht nach 5 Sekunden weg
==> Das hat dann also ein Tester auf einem System gemacht incl. Stoppuht, um zu schauen, ob der Splash Screen nach 5 Sekunden weg ist und nicht schon nach 4 oder erst nach 6..

Somit ein massiver Manueller Aufwand....
 

LimDul

Top Contributor
Naja, die Frage war durchaus ernst gemeint.

Ich hänge es ja gern an die ganz große Glocke, daß ich kein Programmierer bin und vom Programmieren keine Ahnung habe. Da kann ich es mir leisten, mit blöden Ideen und Fragen zu kommen. :D

Aber auch Leute mit Ahnung lernen doch immer mal wieder etwas Neues...oder nicht? Mein Programm hat aktuell nicht ganz 9k Codezeilen und >150 Klassen. Und die minimale Basisfunktionalität (Daten erstellen, kompilieren, als CSV exportieren (ja, CSV) und seine Arbeit speichern können), also daß was mindestens sein muß damit ich das Programm einigen Leuten mal zum Pre-Alphatest geben kann, ist noch nichtmal fertig.

Wenn ich mir vorstelle das Programm jetzt zum ersten Mal zu testen und einen Fehler durch 25 Klassenaufrufe hindurch zu verfolgen, dann ist der irgendwann behoben und dann kommt sofort der nächste...da wird doch niemand fertig. Und dann wäre das garantiert nicht nur ein Fehler, sondern eher etwas Richtung hundert. Immer begleitet von dem Problem, das die Behebung eines Fehlers möglicherweise einen neuen hervorruft weil das Konzept mit sich selbst kollidiert (hatte ich auch schon mindestens einmal, es dank ausgiebigem Probieren recht schnell bemerkt).

Sowas wie meine ObjectProcessor-Klasse, die ich hier neulich mal reingestellt habe (der ich jetzt übrigens die Verarbeitung von Enumeratioinen beibringen will, das fehlte bisher noch und allein dieses Package besteht aus fast 2k Codezeilen und 25 Klassen), wie will man so etwas ohne Unittest jemals auf die Beine stellen? Ich glaube einfach nicht daß man so etwas ohne entsprechend viel Testerei hinbekommt.
Auf Tests komplett verzichtet hoffentlich keiner :)

Aber anstelle von Unit-Tests gibt es ja auch noch andere Tests:
* Entwickler-Tests (aka der Entwickler hat getestet das das was er entwickelt hat, funktioniert)
* Manuelle, funktionale Tests (Tester klicken in der Anwendung rum)

Ohne Unit-Tests werden die Tests halt wichtiger.

Ansonsten versucht man natürlich seine Anwendung möglichst so zu bauen, dass die einzelnen Teile möglichst autark sind und Änderungen am einen Teil den anderen nicht beeinflussen. (In der Theorie...).

Das klappt bis zu in einem gewissen Grad vor allem für Anwendungen, die sehr viel mit Nutzer-Interaktionen arbeiten und wenig komplexe Fachlogik haben. Den bei denen braucht man so oder so manuelle Tests (Automatische UI-Tests sind egal wie man es dreht immer schwierig/aufwendig). Das was du baust ist extrem techniklastig und komplex, ich kann mir nicht vorstellen wie man das ohne automatische Tests (egal ob Unit-Tests oder handgeschriebene Tests die per main() ausgeführt werden) sinnvoll absichern kann.
 

thecain

Top Contributor
Sowas wie meine ObjectProcessor-Klasse, die ich hier neulich mal reingestellt habe (der ich jetzt übrigens die Verarbeitung von Enumeratioinen beibringen will, das fehlte bisher noch und allein dieses Package besteht aus fast 2k Codezeilen und 25 Klassen), wie will man so etwas ohne Unittest jemals auf die Beine stellen?
Sowas macht ja auch fast niemand. So ein riesen Reflection gefummel wie du da machst habe ich noch nie für was machen müssen. Müsstest du wohl auch nicht, aber kann zum Üben sicher spannend sein.
Aber je komplexer, desto mehr braucht man Tests.

Der Fehler kommt jedes mal bei Node? Mmn müsste es java.util.HashMap$Node sein. Kann aber auch sein, dass ich mich Irre...
 

White_Fox

Top Contributor
Ja, das ist richtig, aber das Problem scheint mir noch woanders zu liegen. Wie gesagt, ich hab es noch nicht durchschaut.

Edit: Ein (größeres) Problem habe ich jetzt. Und zwar habe ich ein plötzlich Problem, Arrays zu casten.

Zum Hintergrund: Es gibt eine Klasse um ein Array zu beschreiben, die Klasse ArrayDescriptor. Diese Klasse enthält eine ArrayList<ValueDescriptor>, die das Array nachbildet. Die Klasse ValueDescriptor ist eine abstrakte Klasse, mit deren Vererbungen ich alle möglichen Feldinhalte beschreiben will. ArrayDescriptor erbt z.B. auch von ValueDescriptor.

Wenn ein ArrayDescriptor erstellt wird, will ich über das Array iterieren und von jedem Arrayelement einen ValueDescriptor erstellen. Dazu muß ich auch zwischen byte[] und Byte[] unterscheiden (analoges zu allen anderen primitiven Datentypen).
Mein Plan war, zum vereinfachen, alles zu Object[] zu casten. Byte[] caste ich direkt zu Objekt, byte[] wird erst zu Byte[] und dann zu Object[] gecastet. Für byte[] -> Byte[] gibt es eine Bibliothek von Apache.

Java:
class ArrayDescriptor extends ValueDescriptor<ArrayList<ValueDescriptor>> {

    private ArrayList<ValueDescriptor> elements;

    ArrayDescriptor(ValueType type, Object value, Class fieldClass) {
        super(type, fieldClass);
        investigateArray(value, fieldClass);
    }

    private void investigateArray(Object value, Class fieldClass) {
        Object[] castedArray;
        ValueType arrayType;
        elements = new ArrayList<>();
        String declaredElementsClassname = value.getClass().getName();
        declaredElementsClassname = StringUtils.removeEnd(declaredElementsClassname, "[]").trim();

        arrayType = ValueType.getMemberTypeFromClassname(declaredElementsClassname);

        switch (arrayType) {
            case NULL:
                castedArray = new Object[0];
                break;
            case BYTE:
                castedArray = (declaredElementsClassname.equals("byte"))
                        ? ArrayUtils.toObject((byte[]) value)
                        : (Byte[]) value;
                break;
            case SHORT:
                castedArray = (declaredElementsClassname.equals("short"))
                        ? ArrayUtils.toObject((short[]) value)
                        : (Short[]) value;
                break;
            case INTEGER:
                castedArray = (declaredElementsClassname.equals("int"))
                        ? ArrayUtils.toObject((int[]) value)
                        : (Integer[]) value;
                break;
            case LONG:
                castedArray = (declaredElementsClassname.equals("long"))
                        ? ArrayUtils.toObject((long[]) value)
                        : (Long[]) value;
                break;
            case FLOAT:
                castedArray = (declaredElementsClassname.equals("float"))
                        ? ArrayUtils.toObject((float[]) value)
                        : (Float[]) value;
                break;
            case DOUBLE:
                castedArray = (declaredElementsClassname.equals("double"))
                        ? ArrayUtils.toObject((double[]) value)
                        : (Double[]) value;
                break;
            case BOOLEAN:
                castedArray = (declaredElementsClassname.equals("boolean"))
                        ? ArrayUtils.toObject((boolean[]) value)
                        : (Boolean[]) value;
                break;
            case CHAR:
                castedArray = (declaredElementsClassname.equals("char"))
                        ? ArrayUtils.toObject((char[]) value)
                        : (Character[]) value;
                break;
            default:
                castedArray = value != null
                        ? (Object[]) value : new Object[0]; //Exception hier
        }

        for (Object o : castedArray) {
            Class cls;
            cls = o != null ? o.getClass() : fieldClass;
            ValueDescriptor d = ValueDescriptor.getInstanceFromObject(o, cls);
            elements.add(d);
        }
    }
    //...
}

Ich verstehe nicht warum, aber das Casten funktioniert jetzt nicht mehr. An der markierten Stelle kommt eine ClassCastException. [B can not be castet to [Ljava.lang.Object.

Kriege ich das auch einfacher repariert, oder muß ich doch alle Arraytypen durchiterieren? Und warum funktioniert das mit getCanonicalClassname() (anscheinend) besser als mit getName()?

Edit:
Zeile 15: Die Gewinnung der deklarierten Elementklasse habe ich bereits korrigiert.
 
Zuletzt bearbeitet:

mrBrown

Super-Moderator
Mitarbeiter
Ohne den Code zu sehen schwierig zu sagen, aber ich würde auf Fehler in ValueType.getMemberTypeFromClassname tippen. Falls das nicht schon gemacht ist würd ich nur das mal mit Unit-Tests abdecken, und gucken ob du den Fehler näher kommst.


ValueType ist ein Enum, oder? Dann als kleiner Tipp, den Switch kannst du mit einer Methode auf dem Enum ersetzen, dass macht den Code oft aufgeräumter und übersichtlicher, und in dem Fall vernutluc
 

White_Fox

Top Contributor
Ja, ValueType ist eine Enumeration.
Ich habe gerade einen Fehler in einer ähnlichen Methode gefunden (macht im Wesentlichen das Gleiche, nimmt nur ein Class-Objekt entgegen anstelle eines Strings). Danke für den Tipp.

Irgendwie läuft es immer noch nicht so richtig rund, aber das werde ich erst heute nachmittag testen können.
 

White_Fox

Top Contributor
So liebe Leute, ich denke ich habe den Fehler aus meinem vorherigen Post gefunden:

Ich habe eine Methode, die mir den Klassennamen eines Arrays auf dessen Elemente reduziert. Bei betCanonicalName() war das einfach, da mußte ich lediglich ein "[]" je einmal entfernen. Für "byte[]" lieferte die Methode dann "byte", für "Byte[]" kam "Byte" zurück, für "byte[][]" natürlich "byte[]", usw.

Jetzt habe ich Arrayklassennamen wie "[B" und "[[B", und habe naiver Weise einfach nur das erste Zeichen entfernt. Jetzt habe ich in der ValueType-Enumeration ja diese Methode zu stehen:
Java:
static ValueType getMemberTypeFromClassname(String classname) {
    if (classname.equals("")) {
        return NULL;
    }

    if (classname.startsWith("[")) {
        return ARRAY;
    }

    try {
        Class<?> cls = Class.forName(classname);
        if (cls.isEnum()) {
            return ENUMERATION;
        }
    }
    catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e); //Exception wird geworfen
    }

    for (ValueType type : values()) {
        for (Class cls : type.classes) {
            if (cls.getName().equals(classname)) {
                return type;
            }
        }
    }
    return OBJECT;
}

Und wenn ich jetzt Klassennamen von Arrays derart reduzieren, daß ich einfach nur das erste Zeichen entferne, und mit diesem Klassennamen dann ein ValueType-Enum gewinnen will, laufe ich irgendwann in meine Exception rein - mit Klassennamen wie "Ljava.util.HashMap$Node". Und schon gibt es eine ClassNotFoundException.

Ich hab nach kurzer Suche eine Arraymethode dafür gefunden - getComponentType() - aber die arbeitet auf Classobjekten. Ich suche etwas, das den Klassennamen verwursten kann. Und bevor ich das Rad neu erfinde (ist ja eigentlich schon schlimm, die Javaserialisierung nachzubauen) - kennt jemand etwas Fertiges?

Edit:
Ich hab die Methode doch fix geschrieben, ging schneller als gedacht:
Java:
static String getArrayElementsClassname(String arrayClassname) {
    if (arrayClassname.startsWith("[[")) {
        return arrayClassname.substring(1);
    }
    else if (arrayClassname.startsWith("[")) {
        return arrayClassname.substring(2);
    }
    else {
        return arrayClassname;
    }
}

Aber ich lande wieder an derselben Stelle in einer Exception, diesmal mit dem einem classname "java.util.HashMap$Node". Warum...?
 
Zuletzt bearbeitet:

mrBrown

Super-Moderator
Mitarbeiter
Ich hab nach kurzer Suche eine Arraymethode dafür gefunden - getComponentType() - aber die arbeitet auf Classobjekten. Ich suche etwas, das den Klassennamen verwursten kann. Und bevor ich das Rad neu erfinde (ist ja eigentlich schon schlimm, die Javaserialisierung nachzubauen) - kennt jemand etwas Fertiges?
Warum willst du denn mit Klassennamen und nicht einfach Class-Objekten arbeiten? Wenn ich das richtig sehe hast du das Class-Objekt ja sowieso, der Weg über den Klassennamen scheint dann eher ein Umweg zu sein
 

White_Fox

Top Contributor
Naja, nach der Deserialisierung liegen mir die Klassen erstmal als Strings vor. Ich hab mir schon notiert daß ich viel mehr mit Classobjekten arbeiten sollte, aber für jetzt würde ich das gerne erstmal mit Strings weitermachen.
Ich habe da sowieso noch einige Änderungen vor, u.a. will ich die Verarbeitung auf mehrere Threads aufteilen, transiente Felder werden gnadenlos überfahren, und und und...die Anpassung auf Classanwendung würde sich beim Umbau auf Multithreading gut machen, aber das habe ich auf das zweite Majorelease verschoben.
Wenn ich gewisse Dinge nicht nach hinten rausschiebe, bleibt das auf Dauer eine akademische Übung für mich, ich will mit dem Programm aber wirklich gerne arbeiten (bzw. andere arbeiten lassen, da ich aktuell leider keine CAD-Lizenz für Altium habe und deshalb relativ wenig damit anfangen kann, außer meine bestehenden Bibliotheken da mal einzupflegen). Und aktuell bin ich mit dem Programm noch nichtmal pre-alphatestfähig.

Manchmal überlege ich ob es sinnvoll ist, das ganze ObjectProcessorpackage als eigene Bibliothek zu veröffentlichen. Ein oder zwei Dinge mehr soll die Klasse auch noch können, z.B. Kopien von Objekten erzeugen. Das ist mit den bisher vorhandenen Methoden überhaupt kein Problem.


Aber etwas anderes:
Ich habe gerade noch einen anderen Fehler gefunden: Wenn ich im Debugger an der Stelle, wo die Exception geworfen wird, anhalte, steht am Ende von classname immer ein Semikolon. Ich verstehe nicht, warum. Erst habe ich es für ein Debuggerartefakt gehalten, aber wenn ich es aktiv selber entferne kommt die Exception nicht. Ich sehe auch sonst nicht, wie das Semikolon an das Ende des Strings kommt obwohl es vorher (also bevor ich auf getName() umgestellt habe) nicht da war.
 

White_Fox

Top Contributor
Hm...ich verstehe das absolut nicht. Kann sich eine unverbrauchte Seele das mal bitte ansehen? Der aktuelle Code ist hier:

Code:

Tests:

Das Problem ist, das an Klassennamen plötzlich ";" hinten anhängen die da nicht hingehören. Soweit ich das bisher sehe, ist nach der Deserialisierung noch alles ok. Um den Fehler nachzustellen, einfach den ObjectProcessorTest laufen lassen, bei Bedarf in der Datei "ValueType" zwei Haltepunkte an den Zeilen 174 und 180 setzen.
 

mrBrown

Super-Moderator
Mitarbeiter
Die settings.gradle ist nicht korrekt, da ist das Modul falsch eingetragen (app statt MainApp), dann sind packages zT falsch (objectprocessor als Package-Angabe, liegt aber im Ordner (data.dto), und ein paar Klassen fehlen (zb DescriptorContainer, ProcessingSessionManager, ..) – testen klappt daher noch nicht :/
 

White_Fox

Top Contributor
Verdammt, das hab ich mir schon fast gedacht, hatte da aber meinen Rechenr schon aus...ich checke das heute nachmittag nach der Arbeit nochmal ein.

Edit:
Ceterum censeo subversion esse delendam

Naja...lieber nicht. Ich stimme dir aber zu, das ich git mittlerweile auch besser finde - ich hab damit aber kaum Erfahrung sodaß mir die Arbeit mit SVN einfach leichter von der Hand geht, aber ich arbeite aktuell daran das zu ändern.
Ich finde aber dennoch, das SVN trotz git seine Daseinsberechtigung hat, für Elektronikprojekte nutze ich das sehr gerne, auch meine Bauteilbibliothek hab ich in einem SVN-Repository und im Motorsportteam an meiner Hochschule haben wir es auch genutzt, da habe ich das überhaupt erst kennengelernt.
Da kann man das, was git im Wesentlichen von SVN unterscheidet, einfach nicht ausnutzen. Man baut nicht mehrere Rennwagenversionen parallel. Man baut auch nicht mehrere Versionen derselben Elektronikbaugruppe parallel. Es gibt zwar auch da so etwas wie Varianten, aber git hilft dir da trotzdem nichts. Das muß dein CAD-Werkzeug übernehmen.

Ich habe in den letzten Monaten, wenn ich besonders frustriert über den Berg an Arbeit war, mir schonmal gewünscht einfach einen anderen Branch aufzumachen und an anderen Funktionen zu arbeiten...aber andererseits ist das nur Einladung zur Ablenkung, und meinen ObjectProcessor hätte ich heute nicht ansatzweise so weit wie heute. Und mein Programm wäre noch weit davon entfernt, Dateien speichern und laden zu können (das funktoiniert nämlich bereits. :) ).
Insofern ist git sogar gefährlich.
 
Zuletzt bearbeitet:

White_Fox

Top Contributor
Also...mich hat jetzt doch der Ehrgeiz gepackt und den Fehler habe ich bereits beheben können. Jetzt kommt dafür ein anderer Fehler (ein Verabeitungsschritt später, eigentlich dasselbe Problem nur rückwärts, ich habs auch schon im Code lokalisiert aber noch nicht vollständig nachvollziehen können, wahrscheinlich wird das auch erst morgen etwas). Aber ich denke, ich kriege den auch noch. Wenn nicht, komme ich gewiss wieder. :)

Trotzdem vielen Dank für deine Hilfsbereitschaft @mrBrown . :)
 

mrBrown

Super-Moderator
Mitarbeiter
Ein paar Sachen die mir nebenbei gestern aufgefallen sind:
  • du nutzt aktuell für alles Junit 4, für manche Tests könnte Junit 5 mit Parametrisierten Tests für dich super praktisch sein, lässt sich beides Parallel nutzen und ist vielleicht einen Blick wert
  • Deine Array-Verarbeitung erkennt bisher nur hardcoded ein- und zwei-dimensionale Arrays, für alle tiefer verschachtelten würde es fehlschlagen, u.U. wäre da ein rekursiver Ansatz sinnvoller
  • Du solltest irgendeine CI-Lösung nutzen, das vermeidet die ganze "works on my machine"-Fehler ;) gibt einige kostenlose gehostete Lösungen (allerdings weiß ich nicht, ob irgendwas davon mit SourceForge und svn klar kommt...)
 

White_Fox

Top Contributor
Deine Array-Verarbeitung erkennt bisher nur hardcoded ein- und zwei-dimensionale Arrays, für alle tiefer verschachtelten würde es fehlschlagen, u.U. wäre da ein rekursiver Ansatz sinnvoller
Hm...der sollte doch aber rekursiv sein? Ein ArrayDeskriptor enthält als Datensatz ja lauter ValueDescriptorobjekte, was alles mögliche sein kann. Ein Objekt, ein String (jaja, eigentlich auch ein Objekt), etwas primitives...oder eben auch ein Array von irgend etwas. Und in letzterem Fall geht das Spiel von Neuem los. Oder hab ich da was übersehen? Aber es stimmt schon, ich teste nur mit zweidimensionalen Arrays.

JUnit5...ich weiß, ich schieb das schon eine Weile vor mir her, es wäre auch Zeit für eine höhere Javaversion. Ich krieg ja oft genug dafür Haue. ;)
Ich denke ich werde das machen wenn ich mal etwas Luft habe, z.B. wenn ich das Programm mal ein paar Leuten zum Testen gebe. Da hängt leider auch z.B. eine neue Netbeansversion dran, und die neuren Apacheversionen übernehmen leider Einstellungen, Addons, usw. von der alten Version nicht. Ich meine bei Netbeans 9 haben die das noch übernommen, aber ab NB10 nicht mehr. Hauptsächlich graut es mir vor diesem Gefummel, das wird richtig viel Arbeit da ich bei mir ziemlich viel umgestellt habe. Aber mir ist ja auch bewußt daß das mal sein muß...

Was meinst du damit? Das Problem mit dem Committ gestern ist kein "works on my machine"-Fehler. Es ist ein "works NOT on my machine but others"-Fehler. Irgendwie läuft Gradle bei mir nicht richtig und ich hab bisher noch nicht herausfinden können, warum. Bei meinem ersten Committ hab ich @mihe7 ein paar Tage genervt damit er das für mich testen konnte...
 

mrBrown

Super-Moderator
Mitarbeiter
Hm...der sollte doch aber rekursiv sein? Ein ArrayDeskriptor enthält als Datensatz ja lauter ValueDescriptorobjekte, was alles mögliche sein kann. Ein Objekt, ein String (jaja, eigentlich auch ein Objekt), etwas primitives...oder eben auch ein Array von irgend etwas. Und in letzterem Fall geht das Spiel von Neuem los. Oder hab ich da was übersehen? Aber es stimmt schon, ich teste nur mit zweidimensionalen Arrays.
Mir ist nur die data.dto.Statics#getArrayElementsClassname aufgefallen:
Java:
    static String getArrayElementsClassname(String arrayClassname) {
        if (arrayClassname.startsWith("[[")) {
            return arrayClassname.substring(1);
        }
        else if (arrayClassname.startsWith("[")) {
            return arrayClassname.substring(2);
        }
        else {
            return arrayClassname;
        }
    }

Zugegeben, ich hab nicht geguckt wo es benutzt wird, aber die beiden Bedingungen und auch die Indizes für substring sehen komisch aus.

Was meinst du damit? Das Problem mit dem Committ gestern ist kein "works on my machine"-Fehler. Es ist ein "works NOT on my machine but others"-Fehler. Irgendwie läuft Gradle bei mir nicht richtig und ich hab bisher noch nicht herausfinden können, warum. Bei meinem ersten Committ hab ich @mihe7 ein paar Tage genervt damit er das für mich testen konnte...
In der IDE kannst du doch die Tests zumindest kompilieren und auch ausführen?

Bei mir scheiterte es 1. am Import in der IDE (build.gradle nicht korrekt), dann daran, dass 2. die Packages nicht stimmten, und 3. dann an fehlenden Klassen.

1. dürftest du bei dir in Netbeans halt irgendwann mal gelöst haben
2. könnte auch in Netbeans passend eingestellt sein und dort kein Problem machen
3. dürfte an nicht comitteten Klassen liegen (oder an anderweitig an gradle vorbei über Netbeans eingebundenen)

Alles die typischen "Works on my machine"-Fehler ;)

Gradle lief bei mir mit dem Projekt recht problemlos (gut, abgesehen von 1-3), hast du dazu noch irgendeine Fehlerbeschreibung, was bei dir nicht klappte?
 

White_Fox

Top Contributor
Ok, ich habs nochmal hochgeladen, jetzt sollte es gehen.

Edit: Ich habe lediglich das neue Paket hinzugefügt, der Rest des Projekts ist noch auf dem älteren Stand und daher hat sich da noch nicht viel mehr als bei meiner ersten Reviewanfrage hier getan. Nicht daß du dich wunderst... ;)

Edit 2:
hast du dazu noch irgendeine Fehlerbeschreibung, was bei dir nicht klappte?

Im Prinzip wieder derselbe Fehler (Klassennamen von Arrays bzw. deren Elementen kommen mit einem ";" am Ende). Diesmal allerdings nicht vor dem Serialisieren, sondern nach dem Deserialisieren. An der Serialisierung selber liegt es jedoch nicht, sondern der Fehler muß irgendwo beim Instanzieren der Klassen liegen. Dort, wo ich die Elementklasse eines Arrays ermitteln will.
Setz dir z.B. mal einen Haltepunkt in der Klasse ObjectAssembler, Zeile 323 (Methode instanciateArray()). In der Variable elementclass steht dann Mist...
 
Zuletzt bearbeitet:

mrBrown

Super-Moderator
Mitarbeiter
Ok, ich habs nochmal hochgeladen, jetzt sollte es gehen.
Zumindest besser als gestern ;)

Damit der gradle-Build problemlos läuft muss:

in settings.gradle:
* include ':app' zu include ':MainApp' ändern

in MainApp/build.gradle:
* mavenCentral() unter repositories hinzufügen
* compile group: 'org.objenesis', name: 'objenesis', version: '3.1' den depenedencies hinzufügen


Danach schlägt dann noch der MultiLangSupportTest fehl, nur die Datei für Deutsch wird mit Inhalt gefüllt, und da die anderen leer sind schlägt der Test fehl. Bei dir sind die Dateien vermutlich vorhanden? Tipp dafür: wenn Dateien erstellt werden müssen immer einen temporären Ordner dafür nutzen (lässt sich mit File btw Files erzeugen), dann verhindert man Probleme mit bestehenden Dateien. Den Inhalt kann man vorher auch schon mal als Datei anlegen, dann muss es nur an die passende Stelle kopieren :)


Dann kommt man endlich einen Haufen Fehler, die zum Großteil in objectprocessor.ObjectProcessorTest fliegen (und ein paar in anderen Klassen) – das sind vermutlich die, um die es hier eigentlich geht?
 

White_Fox

Top Contributor
Danke, das Gradle-Skript baue ich dann nochmal um.

Aber den multilang-Kram kannst du schonmal vergessen - der ist mittlerweile rausgeflogen, nachdem ihr mir damals .properties schmackhaft gemacht habt...war ja schon etwas länger her. Wie gesagt - ich hab nur das objectprocessorpaket hochgeladen, der Rest ist noch wie in der ersten Version. :)

Und genau genommen geht es da auch nur um einen einzigen Test: ObjectProcessorTest.
 

White_Fox

Top Contributor
Ich denke ich habe das Problem gefunden. Es steckt in der Klasse ClassDescriptor:

Java:
class ClassDescriptor {

    private String classname;
    private DoubleKeyHashmap<Integer, String, ClassfieldDescriptor> fields;

    ClassDescriptor(Class<?> cls) {
        this.fields = new DoubleKeyHashmap<>();
        this.classname = cls.getName(); //Hier: liefert z.B. "[Ljava.util.HashMap$Node;"

        Field[] fields = cls.getDeclaredFields();

        for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
            String fieldname = fields[fieldIndex].getName();
            String classname = fields[fieldIndex].getType().getName();

            ClassfieldDescriptor cd = new ClassfieldDescriptor(fieldname, classname);

            this.fields.put(this.fields.size(), cd.getFieldname(), cd);
        }
    }
   
    //...
}

Ich werde hier jedoch aus den Java Docs nicht so ganz schlau. Wenn getName() mit ";" endet, repräsentiert das Class-Objekt eine Klasse oder ein Interface - aber das ist doch praktisch immer der Fall.

Irgendeine Idee, wie ich das am sinnvollsten auflösen kann? Kann ich einfach das Semikolon - sofern vorhanden - entfernen? Oder zieht das evt. Probleme nach sich?

Edit: Wenn ich so darüber nachdenke, dann ist das Problem nicht der classname. Wieso kann Objenesis kein Objekt bauen, wenn ich den Auswurf von getName() übergebe - mit getClassname() ging das aber?
 
Zuletzt bearbeitet:

mrBrown

Super-Moderator
Mitarbeiter
Nur bei Arrays von nicht-primitiven ist der Klassenname von 'L' und ';' umschlossen. Das kannst du dabei auch gefahrlos entfernen, das sollte keinerlei Probleme machen.
 

White_Fox

Top Contributor
Dann kriege ich ja aber kein Array mehr - das will ich ja aber.

Aber wie gesagt - wieso kann Objenesis mit dem Auswurf von getCanonicalClassname() - aber nicht mit getName()?
 

White_Fox

Top Contributor
So...jetzt funktioniert es wieder wie gehabt. Enumerationen gehen zwar noch nicht (aber daran arbeite ich jetzt), aber alles was vorher lief läuft wieder.

Das Problem war letztendlich doch, daß ich ein Array so nicht mehr instanzieren konnte wie ich es vorher tat. Ich brauche dazu ein Class-Objekt der Arrayelemente, und dieses Class-Objekt zu erstellen hat Exceptions geworfen. Mit Class.getComponentType() läuft es wieder. :)

Nachtrag:
Ich wundere mich gerade, daß mein Test nochmal um Faktor 10 schneller läuft...ich werde, wenn Enumerationen auch funktionieren, komplett auf Classobjekte umstellen und weniger mit Klassennamen als Strings arbeiten. :)
 

White_Fox

Top Contributor
Hm...möglich. Class.forName nutze ich sicherlich auch mittlerweise irgendwo, aber hauptsächlich habe ich ClassUtils.getClass aus einer Apachebibliothek benutzt.

Wie auch immer, ich denke ich werde das Ganze Gebilde gleich derart überarbeiten, daß ich weitgehend ohne Klassennamen auskomme und lieber mit Classobjekten arbeite.
 

Ähnliche Java Themen

Neue Themen


Oben