PR Software 2
Angabe und Lösungen AI generierter Test aus 2026, v1.

Frage 1 - Annotations

2 Punkte

Implementieren Sie eine @Range-Annotation basierend auf der Verwendung im unten gegebenen Code.

Die Annotation soll über Reflection zur Laufzeit auslesbar und auf Feldern sowie Parametern erlaubt sein. Sie besitzt die verpflichtende Eigenschaft min und die optionale Eigenschaft max. Der Standardwert von max ist Integer.MAX_VALUE.

class Registration {
    @Range(min = 1, max = 120)
    private int age;

    public void setGroupSize(@Range(min = 2) int groupSize) { ... }
}

Hilfreiche APIs:

public enum ElementType {
    TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, ...
}

public enum RetentionPolicy {
    SOURCE, CLASS, RUNTIME
}

Frage 2 - Reflection

2 Punkte

Welchen Text gibt das nachfolgende Programm auf die Konsole aus?

class Box<T> { }
class StringBox extends Box<String> { }

class Main {
    public static void main(String[] args) {
        Class<?> arrayClass = StringBox[].class;

        System.out.print(arrayClass.isArray() + " ");
        System.out.print(arrayClass.getComponentType().getSimpleName() + " ");
        System.out.print(StringBox.class.getSuperclass().getSimpleName() + " ");
        System.out.print(StringBox.class.getTypeParameters().length);
    }
}

Frage 3 - JavaFX Properties und Bindings

4 Punkte

Implementieren Sie die Bindings an der TODO-Stelle in der unten gegebenen Klasse.

  • registerButton soll deaktiviert sein, wenn nameField leer oder der Wert des ageSpinner kleiner als 18 ist.
  • statusLabel soll automatisch "not eligible" anzeigen, solange der Button deaktiviert ist, und sonst "ready".
  • Verwenden Sie ausschließlich Bindings. Event-Handler und Change-Listener sind nicht erlaubt.
class RegistrationController {
    @FXML public TextField nameField;
    @FXML public Spinner<Integer> ageSpinner;
    @FXML public Button registerButton;
    @FXML public Label statusLabel;

    @FXML
    public void initialize() {
        // TODO
    }
}

Hilfreiche APIs:

public abstract class StringExpression {
    public BooleanBinding isEmpty();
}

public abstract class BooleanExpression {
    public BooleanBinding or(ObservableBooleanValue other);
}

public final class Bindings {
    public static When when(ObservableBooleanValue condition);
    public static BooleanBinding createBooleanBinding(
            Callable<Boolean> function,
            Observable... dependencies);
}

public final class When {
    public <T> Then<T> then(T value);
}

public final class Then<T> {
    public ObjectBinding<T> otherwise(T value);
}

Frage 4 - Parallele Ausführung

Aus Übung 4 zur parallelen Ausführung mit dem Fork/Join-Framework ist gegeben:

public interface Problem<R, P extends Problem<R, P>> {
    boolean isSmall();
    List<P> split();
    R combine(List<R> results);
    R solveSequential();
}

Die bereits implementierte Klasse ProblemRecursiveTask führt ein solches Problem mit dem Fork/Join-Framework aus. Ihre Implementierung ist für diese Aufgabe nicht relevant.

4.1 LongestLineProblem

5 Punkte

Implementieren Sie eine Klasse LongestLineProblem, die das Interface Problem implementiert. Das Ergebnis wird durch folgenden Record dargestellt:

public record LineResult(int index, int length) { }

Ein LongestLineProblem bearbeitet den Bereich [from, to) einer List<String>.

  • Das Resultat enthält Index und Länge der längsten Zeile im Bereich.
  • Bei gleich langen Zeilen gewinnt der kleinere Index.
  • Bei weniger als 20 Zeilen wird sequentiell gearbeitet.
  • Sonst wird der Bereich in der Mitte in zwei Teilprobleme geteilt.
  • combine wählt aus den Teilresultaten das bessere Resultat.

Sie können annehmen, dass der Bereich nie leer ist. Schreiben Sie die vollständige Klasse mit Feldern, Konstruktor und allen Methoden des Interfaces.

Frage 5 - Non-blocking Server mit Selector

Ein Server verwaltet nicht-blockierende Client-Verbindungen. Jeder Client besitzt einen ClientState, der als Attachment am zugehörigen SelectionKey gespeichert wird. Zusätzlich werden die Keys nach Channel und angemeldete Clients nach Benutzername in Maps verwaltet.

private static void acceptClient(
        ServerSocketChannel server,
        Selector selector,
        Map<SocketChannel, SelectionKey> channelKeys)
        throws IOException {

    SocketChannel channel = server.accept();
    channel.configureBlocking(false);

    ClientState state = new ClientState(channel);

    // TODO 5.1:
    // Register channel for read events and attach state

    channelKeys.put(channel, key);
}

private static void closeClient(
        SelectionKey key,
        Map<SocketChannel, SelectionKey> channelKeys,
        Map<String, ClientState> loggedInClients)
        throws IOException {

    // TODO 5.2:
    // Remove client from both maps, cancel key and close channel
}

5.1 Client registrieren

1 Punkt

Registrieren Sie den neuen channel für OP_READ beim selector. Speichern Sie den zurückgegebenen Key in einer Variable key und hängen Sie state als Attachment an diesen Key.

5.2 Client schließen

2 Punkte

Holen Sie den ClientState aus dem Attachment. Falls der Client bereits einen Namen besitzt, entfernen Sie ihn aus loggedInClients. Entfernen Sie außerdem den Channel aus channelKeys, brechen Sie den Key ab und schließen Sie den Channel.

API-Auszug

public abstract class SelectionKey {
    public static final int OP_READ;
    public abstract SelectableChannel channel();
    public final Object attachment();
    public final Object attach(Object object);
    public abstract void cancel();
}

public abstract class SocketChannel {
    public final SelectionKey register(Selector selector, int operations)
            throws IOException;
    public abstract SocketChannel configureBlocking(boolean block)
            throws IOException;
    public abstract void close() throws IOException;
}

public final class ClientState {
    public ClientState(SocketChannel channel);
    public String name();
}

Frage 6 - Websockets

4 Punkte

Nehmen Sie an, die Klasse Notification sowie passende Klassen NotificationDecoder und NotificationEncoder sind bereits implementiert.

Vervollständigen Sie die vier mit TODO markierten Stellen (a bis d) des annotationsbasierten Client-Endpoints.

  • Encoder und Decoder werden am Endpoint konfiguriert.
  • Beim Verbindungsaufbau wird die Session gespeichert.
  • Empfangene Notification-Objekte werden an den Listener weitergereicht.
  • send überträgt eine Notification asynchron als typisierte Nachricht.
@ClientEndpoint(/* TODO a */)
public final class NotificationEndpoint {
    private final Consumer<Notification> listener;
    private Session session;

    public NotificationEndpoint(Consumer<Notification> listener) {
        this.listener = listener;
    }

    @OnOpen
    public void onOpen(Session session) {
        // TODO b
    }

    @OnMessage
    public void onMessage(Notification notification) {
        // TODO c
    }

    public void send(Notification notification) {
        // TODO d
    }
}

Hilfreiche APIs:

public interface Session {
    RemoteEndpoint.Async getAsyncRemote();
}

public interface RemoteEndpoint.Async {
    Future<Void> sendObject(Object data);
}

public interface Consumer<T> {
    void accept(T value);
}

Frage 7 - Foreign Function & Memory

4 Punkte

Die native C-Funktion uppercase_ascii wandelt alle ASCII-Kleinbuchstaben in einem nullterminierten String direkt im übergebenen Speicher in Großbuchstaben um. Sie gibt die Anzahl der veränderten Zeichen zurück.

long uppercase_ascii(char *text);
String input = "Hello from Java";

try (Arena arena = Arena.ofConfined()) {
    SymbolLookup library = SymbolLookup.libraryLookup(
            Paths.get("textlib.dll"), arena);
    MemorySegment function = library.findOrThrow("uppercase_ascii");

    // TODOs der Teilaufgaben 7.a bis 7.d
}

7.a Speicher reservieren

1 Punkt

Reservieren Sie im arena einen Speicherbereich, der mit input als nullterminierter UTF-8-String initialisiert ist. Speichern Sie das MemorySegment in text.

7.b Funktionsbeschreibung

1 Punkt

Erstellen Sie den passenden FunctionDescriptor. Der C-long wird auf der verwendeten Plattform durch ValueLayout.JAVA_LONG beschrieben.

7.c Aufruf

1 Punkt

Erzeugen Sie einen Downcall-MethodHandle, rufen Sie die Funktion mit dem richtigen Argument auf und speichern Sie die Anzahl der Änderungen in long changed.

7.d Ergebnis lesen

1 Punkt

Lesen Sie den veränderten nullterminierten String aus text in String result.

Hilfreiche APIs:

public interface SegmentAllocator {
    MemorySegment allocateFrom(String string);
}

public interface MemorySegment {
    String getString(long offset);
}

public final class FunctionDescriptor {
    public static FunctionDescriptor of(
            MemoryLayout result, MemoryLayout... arguments);
}

public interface Linker {
    static Linker nativeLinker();
    MethodHandle downcallHandle(
            MemorySegment symbol, FunctionDescriptor descriptor);
}

public final class ValueLayout {
    public static final OfLong JAVA_LONG;
    public static final AddressLayout ADDRESS;
}
Last updated: