eigene Annotation erstellen die einen Konstruktor erzeugt:

MJannek

Aktives Mitglied
Ich habe in einer Java Library (lombok) gesehen, dass mit Annotations Konstruktoren erstellen kann. Das gefällt mir sehr. Jedoch besteht in dieser Bibliothek, nicht die Möglichkeit, mit einer Annotation folgendes zu erzeugen.
Java:
 // Constructor for setting id
    public ClassName(Integer id) {
        super(id);
    }
Ich habe mir dazu folgendes erstellt.
Java:
package org.mjannek.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateConstructor {
}


package org.mjannek.annotation;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;

@SupportedAnnotationTypes("org.mjannek.annotation.GenerateConstructor")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GenerateConstructorProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(GenerateConstructor.class)) {
            if (element.getKind().isClass()) {
                TypeElement typeElement = (TypeElement) element;
                String className = typeElement.getSimpleName().toString();
                String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();

                if (!hasConstructor(typeElement)) {
                    String constructor = String.format("public %s(Integer id) { super(id); }", className);
                    String source = String.format("package %s;\n\npublic class %s {\n%s\n}", packageName, className, constructor);

                    try {
                        JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + className);
                        try (Writer writer = file.openWriter()) {
                            writer.write(source);
                        }
                    } catch (IOException e) {
                        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Error generating constructor: " + e.getMessage());
                    }
                }
            }
        }
        return true;
    }

    private boolean hasConstructor(TypeElement typeElement) {
        return ElementFilter.constructorsIn(typeElement.getEnclosedElements())
                .stream()
                .anyMatch(constructor -> constructor.getModifiers().contains(Modifier.PUBLIC));
    }
}

Ich habe folgende Basisklasse:
Java:
package org.mjannek.database.entity;


import java.io.*;
import java.sql.*;
import java.time.*;
import javax.persistence.*;
import javax.persistence.ForeignKey;
import javax.persistence.Table;
import lombok.*;
import org.hibernate.annotations.*;
import org.mjannek.sport.buli.database.table.*;

@MappedSuperclass
@FilterDef(name = "deletedFilter", parameters = @ParamDef(name = "isDeleted", type = "boolean"))
@Filter(name = "deletedFilter", condition = "deleted = :isDeleted")
@Getter
@Setter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public abstract class BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @EqualsAndHashCode.Include
    private Integer id;

    @Column(name = "createdat", updatable = false, nullable = false)
    private Timestamp createdAt;

    @Column(name = "updatedat", nullable = false)
    private Timestamp updatedAt;

    @Column(name = "deleted")
    private Boolean deleted = false;

    @ManyToOne
    @JoinColumn(name = "createdby", updatable = false, foreignKey = @ForeignKey(name = "fk_baseentity_createdby"))
    private User createdBy;

    @ManyToOne
    @JoinColumn(name = "updatedby", foreignKey = @ForeignKey(name = "fk_baseentity_updatedby"))
    private User updatedBy;

    @Column(name = "deletedat")
    private Timestamp deletedAt;

    @ManyToOne
    @JoinColumn(name = "deletedby", foreignKey = @ForeignKey(name = "fk_baseentity_deletedby"))
    private User deletedBy;

    @Version
    private Integer version;

    // Basis-Konstruktor
    public BaseEntity() {
        this.deleted = false;
    }

    public BaseEntity(Integer id) {
        this.id = id;
        this.deleted = false;
    }

    @PrePersist
    protected void onCreate() {
        this.createdAt = Timestamp.valueOf(LocalDateTime.now());
        this.updatedAt = Timestamp.valueOf(LocalDateTime.now());
    }

    @PreUpdate
    protected void onUpdate() {
        this.updatedAt = Timestamp.valueOf(LocalDateTime.now());
    }


    // Utility Methods
    public void markAsDeleted(User deletedByUser) {
        this.deleted = true;
        this.deletedAt = Timestamp.valueOf(LocalDateTime.now());
        this.deletedBy = deletedByUser;
    }

    public void restore() {
        this.deleted = false;
        this.deletedAt = null;
        this.deletedBy = null;
        this.updatedAt = Timestamp.valueOf(LocalDateTime.now());
    }
    public String getTableName() {
        // Überprüfen, ob die Annotation @Table vorhanden ist
        Table tableAnnotation = this.getClass().getAnnotation(Table.class);
        if (tableAnnotation != null) {
            return tableAnnotation.name(); // Gibt den Tabellennamen zurück
        }
        return this.getClass().getSimpleName(); // Fallback, falls keine @Table-Annotation vorhanden ist
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BaseEntity that = (BaseEntity) o;
        return id != null && id.equals(that.id);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

Meine Klasse, die BaseEntity erweitert sieht wie folgt aus:
Java:
package org.mjannek.sport.buli.database.table;

import javax.persistence.*;
import lombok.*;
import org.mjannek.annotation.*;
import org.mjannek.database.entity.*;

@GenerateConstructor
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
@Entity
@Table(name = "clubtypes")
public class ClubType extends BaseEntity {

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "description", columnDefinition = "varchar(255) CHARACTER SET utf8mb4")
    private String description;

    // Constructor for setting id
    public ClubType(Integer id) {
        super(id);
    }
}

Ich habe außerdem och im Verzeichnis main/ressources/META-INF/services: eine Datei namens: javax.annotation.processing.Processor, die folgendes enthält:
Java:
org.mjannek.annotation.GenerateConstructorProcessor
Meine Build.gradle.kts sieht wiefolgt aus:
Code:
plugins {
    id("java")
    id("application")
}

group = "org.mjannek"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.hibernate:hibernate-core:5.+")
    implementation("org.reflections:reflections:0.10.2")
    implementation("javax.persistence:javax.persistence-api:2.2")
    implementation("org.jsoup:jsoup:1.18.3")
    implementation("com.sun.mail:javax.mail:1.6.2")
    implementation("com.mchange:c3p0:0.9.5.5")
    implementation("jakarta.validation:jakarta.validation-api:2.0.2")
    implementation("ch.qos.logback:logback-classic:1.4.12")
    implementation("mysql:mysql-connector-java:8.+")
    implementation("org.projectlombok:lombok:1.18.22")
    testImplementation(platform("org.junit:junit-bom:5.10.0"))
    testImplementation("org.junit.jupiter:junit-jupiter")
}

tasks.test {
    useJUnitPlatform()
}

Wenn ich in der Main Klasse folgendes aufrufe erhalte ich trotz mehrfachem build folgende Fehlermeldung:
Java:
package org.mjannek;

import org.mjannek.sport.buli.database.table.*;

public class Main {
    public static void main(String[] args) {
        ClubType ct = new ClubType(1);
}
}

Fehlerneldung:
Fehler: Konstruktor ClubType in Klasse ClubType kann nicht auf die angegebenen Typen angewendet werden.
ClubType ct = new ClubType(1);
^
Erforderlich: keine Argumente
Ermittelt: int
Grund: Liste der tatsächlichen Argumente hat eine andere Länge als die der formalen Argumente

Wo ist mein Fehler?
Vielen Dank im Voraus.
 

Marinek

Bekanntes Mitglied
Bei dir ist irgendwie etwas falsch ;) - Ich sage es einfach, wie es ist.

Du findest eine LIP, die dir ENORM viel Tipparbeit abnimmt und schaffst es dann eine Funktion zu finden, die diese API wiederrum sehr aufwändig nur schafft umsusetzen.

Falls man möchte: https://projectlombok.org/features/constructor

Wie dem auh sei: Deine Kentnisse von Java würde ich eher als "besser" einsortieren. Immerhin produzierst du innerhalb kürzerster Zeit sehr viel Quellcode, der auch nicht mega trivial ist.

Jetzt möchtest du einen KOnstruktur, der nur EIN Paramter hat. Und jetzt nimmst du das komplizierterste Verfahren, um so einen Konstruktor zu erzeugen, den es gibt.

Entweder du schreibst: Mein Projekt ist mir egal: Ich möchte alles über Annotationen heraussuchen, was geht.

oder (und das habe ich verstanden) soll auch das Projekt irgendwie den Fokus haben.

Dann schreib den Konstruktor doch einfach hin. In der IDE: Rechter Mausklick -> Refactoring -> Create Construcor.

Feddich.

----

In dem anderen Fall: Das ist kompliziert: Das funktioniert bei Lombok nicht über Reflections, sondern einem Zusatz beim Compiler, der dann actually byte-Code hinzufügt. Dies dann wiederrum im Tandem mit der IDE, die dann diese nicht existierenden Methoden auch finden kann.

Ehrlich gesagt das ist alles , was ich weiß dazu.
 

KonradN

Super-Moderator
Mitarbeiter
Das Problem, welches ich mit so einer Annotation, die Code generieren soll, habe, ist einfach, dass die Entwicklungsumgebungen dieses erst einmal nicht unterstützen. Angenommen Du machst, was Lombok macht und dann hast Du ein Compiler Plugin, welches den Code für Dich generiert:
Das wird technisch funktionieren.

Aber:
Alle Tools, die das eben nicht kennen, werden schimpfen. Du rufst einen Konstruktor auf, den es aus Ihrer Sicht nicht gibt.

Also müsstest Du dann auch entsprechende Unterstützung schreiben - so wie es das auch bei Lombok gibt.

Da wäre es dann eher interessant, wenn man etwas baut, das Code generell baut. Du hast also keine Java Klassen in dem eigentlichen Sinne sondern irgend etwas aus das dann zur Build Zeit erst die Klassen werden. Das kann natürlich Java sein aber auch beliebige andere Formate wie XML oder YAML oder was auch immer Du haben willst. (Das ist gar nicht so unüblich. Bei Entities, die über Webservices übertragen werden, hat man dann eine Beschreibung und aus der kann man dann sehr viel generieren. SOAP Webservices (veraltet) hatten dazu das WSDL und heutzutage hat man die OpenAPI / Swagger, das man nutzen kann um alles zu beschreiben und um dann Code zu generieren.

Und dann ist die Fragestellung natürlich auch die fachliche Seite. Was soll ein Konstruktor, der nur eine id entgegen nimmt. Wie ist denn da das Szenario bezüglich Vergabe der IDs? Da Du etwas eigenes baust, sollte man das durchaus betrachten. Bei Hibernate und Co ist es oft so, dass Ids von der Datenbank durch eine Sequence vergeben wird. Eine Entität hat keine id bis diese dann gespeichert wurde und ab da gibt es dann die id.
Denn man muss ja sicher stellen, dass ids sauber vergeben werden. Wenn Du jetzt eine id schon zur Erstellung einer Entity brauchst, ist die Frage: Woher nimmst Du diese?
Wenn man keine zentral verwalteten Ids haben möchte, dann wäre ein anderer Datentyp wie UUID evtl. sinnvoller, so dass mehrere Systeme paralle IDs vergeben können ohne dass es zu Problemen durch doppelte IDs führt.
 

KonradN

Super-Moderator
Mitarbeiter
Bezüglich deinem Ansatz: Was Du machen willst, ist doch eine Transformation von dem Code, ehe er analysiert wird. Was Du aber statt dessen versuchst, ist eine Datei zu schreiben, die aber doch schon da ist und was - so Du alles richtig machen würdest - zu einer Exception wie
javax.annotation.processing.FilerException: Attempt to recreate a file for type test.DerivedEntity

Wie so eine AST Transformation aussehen könnte im Rahmen des Annotation Processing ist z.B. hier beschrieben:

Das kann man aber auch bei Lombok näher ansehen, aber da ist das halt alles etwas modularisiert mit diversen Handlern, die auch über Services eingebunden werden. Aber das würde man z.B. hier finden:
Aber es gibt halt viele zusätzliche Klassen, die diverse Aspekte behandeln wie z.B.
nur um da mal ein paar erste Ansätze aufzuzeigen.

Aber das nur um den technischen Weg etwas aufzuzeigen, wie es gehen würde. Der Weg von Dir mit JavaFileObject wäre nur dann interessant, wenn Du ein neues Sourcefile schreiben wolltest.
 

MJannek

Aktives Mitglied
Bei dir ist irgendwie etwas falsch ;) - Ich sage es einfach, wie es ist.

Du findest eine LIP, die dir ENORM viel Tipparbeit abnimmt und schaffst es dann eine Funktion zu finden, die diese API wiederrum sehr aufwändig nur schafft umsusetzen.

Falls man möchte: https://projectlombok.org/features/constructor

Wie dem auh sei: Deine Kentnisse von Java würde ich eher als "besser" einsortieren. Immerhin produzierst du innerhalb kürzerster Zeit sehr viel Quellcode, der auch nicht mega trivial ist.

Jetzt möchtest du einen KOnstruktur, der nur EIN Paramter hat. Und jetzt nimmst du das komplizierterste Verfahren, um so einen Konstruktor zu erzeugen, den es gibt.

Entweder du schreibst: Mein Projekt ist mir egal: Ich möchte alles über Annotationen heraussuchen, was geht.

oder (und das habe ich verstanden) soll auch das Projekt irgendwie den Fokus haben.

Dann schreib den Konstruktor doch einfach hin. In der IDE: Rechter Mausklick -> Refactoring -> Create Construcor.

Feddich.

----

In dem anderen Fall: Das ist kompliziert: Das funktioniert bei Lombok nicht über Reflections, sondern einem Zusatz beim Compiler, der dann actually byte-Code hinzufügt. Dies dann wiederrum im Tandem mit der IDE, die dann diese nicht existierenden Methoden auch finden kann.

Ehrlich gesagt das ist alles , was ich weiß dazu.
Ich nutze projectlombok. Mir ist lediglich auf, dass der von mir gewünschte Konstruktor, den ich in jeder Subklasse von BaseEntity benötige, nicht über eine Annotation dieser Bibliothek erzeugt werden kann. Daher habe ich versucht es selbst zu implementieren, bin dabei auf beschrieben Probleme gestoßen und habe deshalb nach Hinweisen gefragt. Es tut mir leid, wenn ich das falsch kommuniziert habe.
 

Oneixee5

Top Contributor
Wenn du lombok verwendest, dann kannst du Builder verwenden. Somit brauchst du den Aufwand für die "Vererbung" der Konstruktoren nicht zu betreiben:
MyEntity entity = MyEntity.builder().id(1)...build();
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
X Eigene Annotation - mit Bedingung für ganze Klassen oder Methoden Allgemeine Java-Themen 2
M Eigene Datenstruktur um eine Menge zu speichern Allgemeine Java-Themen 3
_user_q Was brauche ich, um eine eigene "Search for updates"-Funktion einzubauen? Allgemeine Java-Themen 1
pizza_dox_9999 Wie füge ich eine "eigene" ScriptEngine dem ScriptEngineManager? Allgemeine Java-Themen 3
E Input/Output Eigene Datei mit java öffnen Allgemeine Java-Themen 9
missy72 Klassen Eigene Klasse MessageWindow ähnlich der Alert Klasse Allgemeine Java-Themen 2
J Eigene Api erstellen und dann auch verwenden - Ordnerstruktur Allgemeine Java-Themen 1
RalleYTN Eigene ScriptEngine? Allgemeine Java-Themen 14
L Eigene Dependency Injections definieren? Allgemeine Java-Themen 4
K Eigene API erstellen? Allgemeine Java-Themen 13
T Best Practice Eigene GUI programmieren | MouseMotion Detection Allgemeine Java-Themen 3
M Eigene forEach()-Methode funktioniert nicht. Allgemeine Java-Themen 2
J eigene Java Bibliothek Allgemeine Java-Themen 2
M Output einer anderen/externen .jar in eigene JTextArea Allgemeine Java-Themen 4
B Klassen Eigene Klasse als Array benutzen Allgemeine Java-Themen 3
D Eigene/r Collection/Container Allgemeine Java-Themen 3
F Eigene .jar mit anderer .jar öffnen Allgemeine Java-Themen 7
H Best Practice Ideensuche: Flexibel hinzufügbare eigene Parser Allgemeine Java-Themen 6
T OOP Die eigene Bruchklasse Allgemeine Java-Themen 11
P Eigene Exception Klasse Allgemeine Java-Themen 7
J Java - Eigene DLL importieren Allgemeine Java-Themen 25
B JTable eigene Daten Add Methode Allgemeine Java-Themen 2
S OOP Problembereichsmodell: Bestehende Framework Klasse in eigene Klassenstruktur einbinden Allgemeine Java-Themen 9
E Eigene Exception Klasse erstellen Allgemeine Java-Themen 3
S Methoden Eigene Klasse als Rückgabewert Allgemeine Java-Themen 6
S eigene regEx schreiben Allgemeine Java-Themen 4
O Collections Eigene Methodenzusicherung bei Collections als Parameter Allgemeine Java-Themen 2
M Eigene Klasse verschlüsselt in Datei speichern Allgemeine Java-Themen 13
N Geodaten für eigene Routenplanung Allgemeine Java-Themen 5
M Problem beim schreiben einer eigene generische Klasse Hashtable Allgemeine Java-Themen 11
C Eigene Sounddateien schreiben Allgemeine Java-Themen 12
M Problem beim schreiben einer eigene generische Klasse LinkedList Allgemeine Java-Themen 34
M Best Practices Exception Handling für eigene library Allgemeine Java-Themen 8
M Eigene Factory Klasse Allgemeine Java-Themen 21
M eigene Klasse durch Composition mit java.io.File erweitern Allgemeine Java-Themen 3
S eigene Update Funktion Allgemeine Java-Themen 5
H2SO3- getMethods(): eigene methoden rausfinden Allgemeine Java-Themen 4
C eine eigene Javadoc erstelen Allgemeine Java-Themen 3
Kr0e Eigene RMI Implementierung Allgemeine Java-Themen 3
S Eigene Bibliothek Allgemeine Java-Themen 2
M Eigene Dateiendung? Allgemeine Java-Themen 3
M Plugins für eigene Programme Allgemeine Java-Themen 3
S Eigene Events feuern Allgemeine Java-Themen 3
S Instanz in einer Klasse erstellen, und dem Konstruktor die eigene Klasse mitgeben Allgemeine Java-Themen 4
A Eigene Java Plugins entwickeln - wie optimal? Allgemeine Java-Themen 14
R Eigene ArrayList vom Typ Short, Integer oder Double Allgemeine Java-Themen 4
U Eigene "Dining Philosopher" Interpretation weckt Philos nie auf Allgemeine Java-Themen 2
0x7F800000 Wovon eigene Events für spezialisierte JComponents ableiten? Allgemeine Java-Themen 2
SuperSeppel13 Packete der Java Bibliothek ins eigene Prjekt integrieren Allgemeine Java-Themen 4
P Eigene Klasse kopieren die auf sich selbst refferenziert Allgemeine Java-Themen 8
G Umgebungsvariabeln auslesen (Path, Temp, oder eigene) Allgemeine Java-Themen 2
T Eigene PID ermitteln! Allgemeine Java-Themen 3
D Icon für eigene Dateiendung Allgemeine Java-Themen 7
G Eigene "Speicherverwaltung" Allgemeine Java-Themen 5
H eigene Annotations Allgemeine Java-Themen 2
S eigene Klasse die JButton extended - als Liste! Allgemeine Java-Themen 6
D Performance: ArrayList vs. Array vs. "Eigene Liste&quot Allgemeine Java-Themen 8
A Eigene Wrapper-Klassen Allgemeine Java-Themen 2
J Kann eigene Klasse nicht ermitteln Allgemeine Java-Themen 4
M Wann verwendet man PropertyChangedEvents, wann eigene? Allgemeine Java-Themen 3
J OpenOffice.org Events in eigene Anwendung umleiten Allgemeine Java-Themen 4
U eigene Datenstruktur ArrayList<String> nach Object [][ Allgemeine Java-Themen 2
N Observer/Observable der JAVA-API od. eigene Implementierung Allgemeine Java-Themen 2
B eigene Klassen verkaufen Allgemeine Java-Themen 2
W eigene event-Klasse, event posten Allgemeine Java-Themen 2
reibi Eigene exception mit feld "serialVersionUID" Allgemeine Java-Themen 3
M Eigene .jar in anderes Programm importieren[eclipse] Allgemeine Java-Themen 6
T Eigene Libary Allgemeine Java-Themen 6
E Eigene Exception Allgemeine Java-Themen 11
Q Bibliotheken mit ins eigene Jar aufnehmen Allgemeine Java-Themen 4
M Eigene Exception Klasse Allgemeine Java-Themen 4
M eigene Objekte vergleichen Allgemeine Java-Themen 6
K log4j - eigene Info-Ausgaben Allgemeine Java-Themen 5
G Performance JDOM - DOM - eigene HashMap (SAX) Allgemeine Java-Themen 2
G JWindow verschieben bei mousedragged auf eigene titelleiste Allgemeine Java-Themen 3
N Methoden in eigene Dateien auslagern? Allgemeine Java-Themen 10
B Eigene Datentypen Allgemeine Java-Themen 5
G Brauche hilfe bei JMF: Wie eigene Streaming Data Source! Allgemeine Java-Themen 4
G eigene klassen die ein jar verwenden als neues jar erstellen Allgemeine Java-Themen 4
O Externe Jars in eigene JAr packen in Eclipse Allgemeine Java-Themen 5
R Updateprozedur ü. Internet fürs eigene Programm? Allgemeine Java-Themen 24
G eigene uhrzeit in einer java applikation führen Allgemeine Java-Themen 19
R Eigene Konfigurationsdatei für größere Projekte. Allgemeine Java-Themen 4
J Eigene Callback Methode? Allgemeine Java-Themen 3
S eigene evetns erstellen Allgemeine Java-Themen 5
K eigene GUI Komponente wird nicht angezeigt Allgemeine Java-Themen 3
D Eigene Classes aus anderen .java-Dateien Allgemeine Java-Themen 2
G Eigene PrintService Implementierung. Allgemeine Java-Themen 5
C Die Konsole ins eigene Programm einbauen Allgemeine Java-Themen 5
G @PostConstruct Annotation nicht mehr gültig ? Allgemeine Java-Themen 7
S Validation Annotation Funktionsparameter vs Funktion vs Attribut Allgemeine Java-Themen 0
S Eclipse Annotation Processor in Eclipse einbinden Allgemeine Java-Themen 0
N Automatisches einfügen einer selbst generierten ID in Klasse mit Annotation Allgemeine Java-Themen 8
6 Annotation Allgemeine Java-Themen 1
A Annotation einer Subklasse im static-Block auslesen. Allgemeine Java-Themen 6
Schandro Annotation vs Javadoc bei Konstanten Allgemeine Java-Themen 2
reibi Annotation @Override Allgemeine Java-Themen 6
G konstanter String[] laesst sich nicht in Annotation nutzen Allgemeine Java-Themen 2
S Annotation für "NativeAccess" Allgemeine Java-Themen 6
T Annotation oder Markerinterface? Allgemeine Java-Themen 10

Ähnliche Java Themen


Oben