PR Software 2
Angabe und Lösungen vom Test aus dem Sommersemester 2025.

Frage 1 – Annotations

2 Punkte

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

Die Annotation soll über Reflection zur Laufzeit auslesbar sein.

Definieren Sie auch die Retention und das Target der Annotation.

@Docs(name = "MyClass",
      url = "https://ssw.jku.at/javadoc/MyClass")
class C {

    @Docs(name = "MyField",
          url = "https://ssw.jku.at/javadoc/MyClass/MyField")
    private int f = 0;

    @Docs(name = "MyMethod",
          url = "https://ssw.jku.at/javadoc/MyClass/MyMethod")
    public void m() { }
}

Hilfreiche APIs:

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

public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

Frage 2 – Reflection

2 Punkte

Welchen Text gibt das nachfolgende Programm auf die Konsole aus?

(Reihenfolge ist nicht relevant)

class A {

    private int a;
    public int b;

    private void m() { ... }

    public void n() { ... }
}

class B extends A {

    private int c;
    public int d;

    private void o() { ... }

    public void p() { ... }
}

class Main {

    private static void printClass(Class<?> klass) {

        for (Field f : klass.getFields()) {
            System.out.print(f.getName() + " ");
        }

        for (Method m : klass.getDeclaredMethods()) {
            System.out.print(m.getName() + " ");
        }
    }

    public static void main(String[] args) {
        printClass(B.class);
    }
}

Frage 3 – JavaFX Properties und Bindings

4 Punkte

Implementieren Sie ein Binding zwischen der TextArea textArea und dem Label label (TODO in der unten gegebenen Klasse).

Der Text (textProperty) des Labels soll die Anzahl an Großbuchstaben im Text (textProperty) der TextArea anzeigen und sich automatisch updaten, sobald sich der Text der TextArea ändert.

class TestController {

    @FXML
    public TextArea textArea;

    @FXML
    public Label label;

    @FXML
    public void initialize() {

        // TODO
    }
}

Hilfreiche APIs:

public interface Property {

    public void bind(ObservableValue observable);
}

public abstract class StringBinding {

    public void bind(
            ObservableValue<String> value) {
        ...
    }

    protected abstract String computeValue();
}

public final class String {

    public char[] toCharArray() {
        ...
    }
}

public final class Character {

    public static boolean isUpperCase(char ch) {
        ...
    }
}

Frage 4 – Parallele Ausführung

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

  • Das generische Interface Problem für ein Problem in einer parallelen Ausführung:
public interface Problem<R, P extends Problem<R, P>> {
    boolean isSmall();
    Pair<P, P> split();
    R combine(R result1, R result2);
    R solveSequential();
}
  • dazu die Record-Klasse Pair:
public record Pair<A, B>(A fst, B snd) {}
  • und eine generische Task-Implementierung auf Basis von Problem:
public class ProblemRecursiveTask<R, P extends Problem<R, P>> extends RecursiveTask<R> {
    private final P problem;

    public ProblemRecursiveTask(P problem) {
        this.problem = problem;
    }

    @Override
    protected R compute() {
        if (problem.isSmall()) {
            R result = problem.solveSequential();
            return result;
        } else {
            Pair<P, P> problems = problem.split();
            ProblemRecursiveTask<R, P> task1 = new ProblemRecursiveTask<>(problems.fst());
            ProblemRecursiveTask<R, P> task2 = new ProblemRecursiveTask<>(problems.snd());
            task1.fork();
            task2.fork();
            R result1 = task1.join();
            R result2 = task2.join();
            R result = problem.combine(result1, result2);
            return result;
        }
    }
}

4.1 SumProblem

5 Punkte

Implementieren Sie eine Klasse SumProblem, die das Interface Problem implementiert und eine parallele Summenbildung für ein int-Array ermöglicht.

SumProblem soll folgende Felder haben:

  • eine Referenz auf das Array vom Typ int[]
  • einen unteren Index from (inklusive)
  • einen oberen Index to (exklusive)

Damit ist ein Objekt vom Typ SumProblem für die Summenbildung im Array von Index from bis Index to zuständig.

Ist die Anzahl der Elemente kleiner als 10, soll die Summenbildung sequentiell erfolgen, sonst parallel mit einer Aufteilung in zwei Unterprobleme.

Frage 5 – Non-blocking Server mit Selector

Folgend ist die Main-Methode einer Server-Implementierung gegeben, in der ein Selector verwendet wird. Damit wird eine nicht-blockierende Kommunikation mit einem Client realisiert.

Clients können sich beim Server anmelden und werden dann für Read-Ereignisse registriert. Die Clients schicken Kommandozeilen, die vom Server gelesen und in Methode handleCommand behandelt werden (Art der Behandlung ist nicht relevant).

public class NonBlockingServer {

    public static void main(String[] args) {

        Thread serverThread = new Thread(() -> {

            try {

                ByteBuffer buffer = ByteBuffer.allocate(128);

                ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
                serverSocketChannel.bind(new InetSocketAddress(PORT));
                serverSocketChannel.configureBlocking(false);

                Selector selector = Selector.open();

                SelectionKey acceptKey =
                        serverSocketChannel.register(selector, OP_ACCEPT);

                while (!terminate) {

                    int n = selector.select();

                    if (n > 0) {

                        Set<SelectionKey> keys =
                                selector.selectedKeys();

                        Iterator<SelectionKey> keyIt =
                                keys.iterator();

                        while (keyIt.hasNext()) {

                            SelectionKey key = keyIt.next();

                            if (key.isAcceptable()) {

                                SocketChannel clientChannel =
                                        serverSocketChannel.accept();

                                clientChannel.configureBlocking(false);

                                // TODO 5.1:
                                // Register the client channel for read events

                            } else if (key.isReadable()) {

                                SocketChannel channel =
                                        (SocketChannel) key.channel();

                                // TODO 5.2:
                                // Read line from client using buffer

                                String command =
                                        CSET.decode(buffer).toString();

                                handleCommand(command);
                            }

                            keyIt.remove();
                        }
                    }
                }

            } catch (IOException e) {
                ...
            }

        });

        serverThread.start();
    }

    ...

    private static void handleCommand(String command) {
        ...
    }
}

5.1 Registrierung

1 Punkt

Registrieren Sie den clientChannel beim Selector.


5.2 Lesen

2 Punkte

Lesen Sie die Daten vom Client. Verwenden Sie den ByteBuffer buffer.

API-Auszug

public class ByteBuffer {

    public static ByteBuffer allocate(int capacity);

    ByteBuffer clear();

    ByteBuffer reset();

    ByteBuffer flip();

    ByteBuffer rewind();
}

public class Selector implements Closeable {

    public static Selector open() throws IOException;

    public Set<SelectionKey> keys();

    public Set<SelectionKey> selectedKeys();

    public int selectNow() throws IOException;

    public int select();

    public int select(long timeout);
}

public class ServerSocketChannel {

    public static ServerSocketChannel open() throws IOException;

    public final ServerSocketChannel bind(SocketAddress local)
            throws IOException;

    public abstract SocketChannel accept()
            throws IOException;
}

public class SocketChannel {

    public static SocketChannel open();

    public abstract boolean connect(SocketAddress remote)
            throws IOException;

    public final SelectionKey register(
            Selector sel,
            int ops)
            throws ClosedChannelException;

    public abstract int read(ByteBuffer dst)
            throws IOException;

    public abstract int write(ByteBuffer src)
            throws IOException;

    public abstract long write(
            ByteBuffer[] srcs,
            int offset,
            int length)
            throws IOException;

    public final long write(ByteBuffer[] srcs)
            throws IOException;
}

public abstract class SelectionKey {

    public static final int OP_READ;

    public static final int OP_WRITE;

    public static final int OP_CONNECT;

    public static final int OP_ACCEPT;

    public final boolean isReadable();

    public final boolean isWritable();

    public final boolean isConnectable();

    public final boolean isAcceptable();

    public abstract SelectableChannel channel();

    public abstract void cancel();
}

Frage 6 – Websockets

4 Punkte

Nehmen Sie an, Sie haben bereits eine Java-Klasse Info implementiert, die bestimmte Informationen beinhaltet, sowie dazu passende Encoder-Klassen InfoDecoder und InfoEncoder.

Weiters ist ein Broadcast-Serverendpoint gegeben. Vervollständigen Sie die 4 mit //TODO markierten Stellen (a bis d), sodass die folgenden Bedingungen erfüllt sind:

  • Von Clients eingehende Info-Objekte sollen automatisch an alle anderen aktiv verbundenen Clients weitergeleitet werden. Clients sollen ihre eigenen Info-Objekte nicht erneut vom Server erhalten.
  • Zukünftig verbundene Clients erhalten aktuelle Daten nicht, d.h. ein Abspeichern/Nachsenden von Info-Objekten am Server ist nicht notwendig ("wer nicht da ist, hat Pech gehabt").
  • Mit Session::getAsyncRemote können Sie sich das entsprechende Remote-Objekt holen. Remote-Objekte haben folgende Methoden zum Senden von Daten:
Remote::sendBinary(ByteBuffer)
Remote::sendObject(Object)
Remote::sendPing(ByteBuffer)
Remote::sendPong(ByteBuffer)
Remote::sendText(String)
  • Beachten Sie Encoder/Decoder.
@ServerEndpoint(value="/info", /* TODO a */)
public class BroadcastServerEndpoint {
    private static final Set<Session> activeClients = /* assume a threadsafe set here */

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

    @OnMessage
    public void onMessage(Session session, Info info) {
        // TODO c
    }

    @OnError
    public void onError(...) { /* assume this is correctly implemented */ }

    @OnClose
    public void onClose(Session session) {
        // TODO d
    }
}

Frage 7 – Foreign Function & Memory

4 Punkte

Gegeben ist folgender Java-Code, der die Summe des gegebenen double-Arrays array mittels FFM API in C berechnen soll:

double[] array = ... // given

try (Arena offHeap = Arena.ofConfined()) {

    SymbolLookup lib =
            SymbolLookup.libraryLookup(Paths.get("myLib.dll"), offHeap);

    MemorySegment sumPointer =
            lib.findOrThrow("array_sum");

    FunctionDescriptor sumDescriptor =
            FunctionDescriptor.of(/* TODO of task c */);

    MethodHandle sumHandle =
            Linker.nativeLinker().downcallHandle(sumPointer, sumDescriptor);

    // TODO of task b

    double sum =
            (double) sumHandle.invoke(/* TODO of task d */);

    System.out.println("Array sum is: " + sum);

} catch (Throwable t) {

    ...
}

Beantworten Sie folgende Fragen a) - d):


7.a)

1 Punkt

Die C-Funktion array_sum berechnet die Summe eines beliebigen double-Arrays und gibt diese Summe anschließend zurück.

Schreiben Sie eine passende Signatur für diese Funktion.


7.b)

1 Punkt

Im gegebenen Code fehlt an der TODO b-Stelle ein wichtiger Schritt noch zur Gänze.

Schreiben Sie diesen Schritt als Code/Pseudocode oder beschreiben Sie in Worten, was bei diesem Schritt passieren muss.

7.c)

1 Punkt

Vervollständigen Sie den FunctionDescriptor passend zur C-Signatur aus Teilaufgabe a). Verwenden Sie ValueLayout.JAVA_DOUBLE, ValueLayout.ADDRESS und ValueLayout.JAVA_INT.

7.d)

1 Punkt

Vervollständigen Sie den Aufruf von sumHandle.invoke(...) mit den richtigen Argumenten.

Last updated: