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

Frage 1

Gegeben sei ein Ausschnitt aus einem Tool, um Java Objekte zu serialisieren, also Objekte in eine textuelle Repräsentation zu bringen.

Der Inhalt von Feldern wird hierbei über die Methode writeFieldValue geschrieben: f ist das zu schreibende Feld und o das umgebende Objekt.

Felder können mit einer UseSerializer Annotation versehen sein.

@Retention( // TODO )
@Target( // TODO )
public @interface UseSerializer {

    public Class<? extends CustomSerializer> value();

}

Diese Annotation hat als Value eine CustomSerializer Sub-Klasse, die einen leeren Konstruktor und eine Methode writeValue besitzt.

Falls eine UseSerializer Annotation vorhanden ist, soll der CustomSerializer mittels leerem Konstruktor erzeugt und verwendet werden.

Falls keine UseSerializer Annotation vorhanden ist, sollen Primitives (int, float, ...) mittels String.valueOf(...) geschrieben und Felder, die Objekte enthalten, rekursiv serialisiert werden.

Vervollständigen Sie die writeFieldValue Methode. Auf Exceptions brauchen Sie keine Rücksicht nehmen.

private void writeFieldValue(Field f, Object o) {

    // make private fields readable
    // TODO

    // handle custom serializer first

    if ( /* TODO */ ) { // check for UseSerializer annotation

        // create serializer from annotation

        UseSerializer annotation =
                // TODO

        Class<? extends CustomSerializer> serializerClass =
                annotation.value();

        CustomSerializer serializer =
                // TODO

        serializer.writeValue(
                // TODO
        );

    } else if ( /* TODO */ ) { // check if primitive

        System.out.println(String.valueOf(f.get(o)));

    } else if (f.get(o) == null) {

        System.out.println("null");

    } else {

        writeObject(f.get(o));
    }
}

API:

public final class Class<T> {

    public Constructor<T> getConstructor(
            Class<?>... parameterTypes);

    public Method[] getDeclaredMethods();

    public Method[] getMethods();

    public Field getDeclaredField(String name);

    public Field getField(String name);
}

public final class Constructor<T> {

    public T newInstance(Object... initargs);
}

public final class Field {

    public Object get(Object obj);

    public int getInt(Object obj);
}

public final class Field {

    public <T extends Annotation> T getAnnotation(
            Class<T> annotationClass);

    public boolean isAnnotationPresent(
            Class<? extends Annotation> annotationClass);

    public Annotation[] getAnnotations();

    public Object get(Object obj);
}

public enum ElementType {

    ANNOTATION_TYPE,
    CONSTRUCTOR,
    FIELD,
    LOCAL_VARIABLE,
    METHOD,
    PACKAGE,
    PARAMETER,
    TYPE,
    TYPE_PARAMETER
}

public enum RetentionPolicy {

    SOURCE,
    CLASS,
    RUNTIME
}

Frage 2

Um das Abnehmen zu erleichtern, implementieren Sie ein FoodTracking System mit grafischer Benutzeroberfläche mittels JavaFX.

Eine API mit hilfreichen Methoden findet sich am Ende der Angabe.

Vervollständigen Sie das zugehörige Datenmodell mittels observable Properties:

public final class FoodItem {

    private final // TODO name; // as String

    private final // TODO kcalPer100Grams; // as double

    private final // TODO grams; // as int

    private final // TODO totalKcal; // as double
}
public final class FoodTrackerModel {

    private final // TODO items;

    public FoodTrackerModel() {

        this.items = // TODO;
    }

    public boolean removeFoodItem(FoodItem item) {...}
}

Die Gesamtkalorien totalKcal für ein FoodItem können nicht direkt gesetzt werden, sondern sollen automatisch aus der Menge (grams) und den Kalorien pro 100g (kcalPer100Grams) berechnet werden.

Erstellen Sie ein unidirektionales Binding, welches diese Beziehung herstellt:

// TODO

Die GUI stellt die Daten des Modells als Tabelle dar. Vervollständigen Sie das Setup für die Spalte gramsColumn, die editierbar sein soll:

private void setupGramsColumn() {

    // connect column "gramsColumn" to property "grams"

    // TODO

    // make column editable using StringConverter

    gramsColumn.setCellFactory(
        TextFieldTableCell.forTableColumn(
            // TODO
        )
    );
}

Bei einem Button-Klick soll die aktuell markierte Zeile in der Tabelle gelöscht werden (und der zugehörige Eintrag im Modell):

removeFoodItemButton.setOnAction(event -> {

    final FoodItem selectedItem =
            // TODO;

    if (selectedItem != null) {

        // TODO
    }
});

API:

public class TableView<S> {

    public SelectionModel<S> getSelectionModel();

    public FocusModel<S> getFocusModel();
}

public class SelectionModel<S> {

    public int getSelectedIndex();

    public S getSelectedItem();

    public boolean isEmpty();
}

public class FXCollections {

    public static <E> ObservableList<E>
            observableArrayList();
}

public class TableColumn<S, T> {

    public void setCellFactory(
            Callback<TableColumn<S,T>,
            TableCell<S,T>> value
    );

    public void setCellValueFactory(
            Callback<
                TableColumn.CellDataFeatures<S,T>,
                ObservableValue<T>
            > value
    );
}

public class PropertyValueFactory<S, T>
implements Callback<
        TableColumn.CellDataFeatures<S,T>,
        ObservableValue<T>> {

    public PropertyValueFactory(String property);

    public ObservableValue<T> call(
            TableColumn.CellDataFeatures<S,T> param);
}

public abstract class StringConverter<T> {

    public StringConverter<T>();

    abstract String toString(T object);

    abstract T fromString(String string);
}

Frage 3

Gegeben ist (langer) Text als ein String-Objekt. Es soll ermittelt werden, wie oft die Buchstaben 'a' bis 'z' vorkommen. Das Vorkommen wird in einem Array int[] freq mit 26 Elementen gezählt, also freq[0] enthält die Anzahl des Vorkommens für 'a'. Es wird nicht zwischen Groß- und Kleinschreibung unterschieden. Alle anderen Zeichen werden ignoriert.

Folgende Methode berechnet die Häufigkeit des Vorkommens der Buchstaben im Text text im Bereich from bis to (exklusive) mit einer sequentiellen For-Schleife:

private static int[] computeFreqSeq(String text, int from, int to) {
    int[] freq = new int[26];
    for (int i = from; i < to; i++) {
        char c = Character.toLowerCase(text.charAt(i));
        int idx = c - 'a';
        if (idx >= 0 && idx < 26) {
            freq[idx]++;
        }
    }
    return freq;
}

Da der Text lange ist, soll das Fork-Join-Framework mit RecursiveTasks eingesetzt werden. Folgend ist eine Klasse CharFreqTask begonnen. CharFreqTask hat ein Feld mit dem Text text und die beiden Positionen from und to, die den zu behandelnden Bereich des Textes bestimmen.

Implementieren Sie die Methode compute, die

  • bei einem Bereich [from, to) mit weniger als THRESHOLD Elementen die Berechnung sequentiell mit der Methode computeFreqSeq durchführen soll;
  • sonst den Bereich in zwei Teile teilen, für die beiden Teile zwei CharFreqTask erzeugen, diese zur Ausführung bringen, die Ergebnisse abrufen und die beiden Ergebnisse zusammenführen soll.
public class CharFreqTask extends RecursiveTask<int[]> {
    private static final int THRESHOLD = 20;
    private final String text;
    private final int from, to;

    public CharFreqTask(String text, int from, int to) {
        this.text = text;
        this.from = from;
        this.to = to;
    }

    @Override
    protected int[] compute() {
        // TODO implement
    }
}

Frage 4

Bei einem PostServer können Clients Posts publizieren. Dies soll folgend ablaufen:

  • Der Client verbindet sich und wird durch den Server akzeptiert. Der Kommunikation mit dem Client erfolgt in einem eigenen Thread.
  • Der Server meldet sich mich einem "HELO_FROM".
  • Der Client schickt "LOGIN " mit seinem Namen.
  • Der Server antwortet mit "OK".
  • Der Clint schickt seinen Post aus mehreren Zeilen und schließt mit "END" ab.
  • Der Server schickt "BYE".
  • Der Server schließt die Verbindung zum Client.

Ergänzen Sie das folgende Programm entsprechend den Kommentaren.

public class PostServer {
    private volatile boolean terminate = false;
    private final ServerSocket server;

    PostServer() throws IOException {
        server = new ServerSocket(4711);
    }

    void start() throws IOException {
        while (!terminate) {
            try {

                Socket clientSocket = // TODO;    // accept client

                // TODO;                          // create ClientHandler and start in new thread

            } catch (SocketException se) {
                System.out.println("PostServer closed with " + se.toString());
            }
        }
    }

    public void terminate() throws IOException {

        // TODO;                                  // terminate server loop

        // TODO;                                  // close server
    }

    private class ClientHandler implements Runnable {
        private final Socket socket;

        ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (
                PrintWriter out = new PrintWriter(socket.getOutputStream());
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            ) {
                String msg;

                // TODO;                          // send "HELO_FROM " + server.toString() to client

                // TODO;                          // receive "LOGIN " + client name message

                String clientName = msg.substring(6);

                // TODO;                          // SEND "OK " + clientName

                StringBuilder post = new StringBuilder();

                // TODO;                          // receive first post line

                while (// TODO) {                 // while more lines

                    post.append(msg + "\r\n");

                    // TODO;                      // receive next post line
                }

                postIt(clientName, post.toString());

                // TODO;                          // send "BYE "

            } catch (IOException e) {
                e.printStackTrace();
            }

            try {

                // TODO;                          // close socket

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String receive(BufferedReader in) throws IOException {

        return // TODO;
    }

    public static void send(PrintWriter out, String str) throws IOException {

        // TODO;
        out.flush();
    }

    private void postIt(String name, String post) {...}
}

Frage 5

Vervollständigen Sie die folgende Implementierung einer RMI-basierten Wetterstation und zugehöriger Clients. Die Wetterstation besteht aus einer Serverkomponente (WeatherStation, WeatherStationImpl, WeatherServer), die periodisch Sensordaten (WeatherEvent) an von Clients (WeatherClient) implementierte Callbacks (WeatherListener) übergibt.

WeatherEvent

// serializable current sensor results
public class WeatherEvent // TODO {

    private static final long serialVersionUID = 4852334523427580044L;

    private final double temperature;
    private final double humidity;

    public WeatherEvent(double temperature, double humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
    }

    public double getTemperature() {
        return temperature;
    }

    public double getHumidity() {
        return humidity;
    }
}

WeatherListener

public interface WeatherListener // TODO {

    void onWeatherChanged(WeatherEvent event) // TODO;
}

WeatherStation

public interface WeatherStation // TODO {

    void addWeatherListener(WeatherListener listener) // TODO;

    void removeWeatherListener(WeatherListener listener) // TODO;
}

WeatherStationImpl

public final class WeatherStationImpl // TODO implements WeatherStation {

    private final ExecutorService threadManager = Executors.newCachedThreadPool();

    private final List<WeatherListener> listeners;

    public WeatherStationImpl() throws RemoteException {
        listeners = new CopyOnWriteArrayList<>();
    }

    @Override
    public // TODO void addWeatherListener(WeatherListener listener) {
        listeners.add(listener);
    }

    @Override
    public // TODO void removeWeatherListener(WeatherListener listener) {
        listeners.remove(listener);
    }

    private void fireWeatherChanged(WeatherEvent event) {
        for (WeatherListener listener : listeners) {
            threadManager.submit(() -> {
                try {
                    listener.onWeatherChanged(event);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    ...
}

WeatherServer

public final class WeatherServer {
    public static void main(String[] args) throws RemoteException, InterruptedException, AlreadyBoundException {
        final WeatherStationImpl weatherStation = new WeatherStationImpl();

        final // TODO registry = // TODO(RMIConstants.WEATHER_STATION_HOST_PORT);

        registry.// TODO(RMIConstants.WEATHER_STATION_APPLICATION_NAME, weatherStation);

        // start weather service
        ...
    }
}

WeatherClient

public final class WeatherClient {

    private static final class WeatherClientListener // TODO implements WeatherListener {

        public WeatherClientListener() throws RemoteException {
        }

        @Override
        public void onWeatherChanged(WeatherEvent event) // TODO {
            System.out.printf(
                    "Current weather conditions: %.2f°C, %.2f%% humidity%n",
                    event.getTemperature(),
                    event.getHumidity());
        }
    }

    public static void main(String[] args) throws RemoteException, NotBoundException {

        final Registry registry = // TODO(WEATHER_STATION_HOST_NAME, WEATHER_STATION_HOST_PORT);

        final WeatherStation weatherStation = (WeatherStation) registry.// TODO(WEATHER_STATION_APPLICATION_NAME);

        final WeatherListener listener = new WeatherClientListener();
        weatherStation.addWeatherListener(listener);
    }
}

API:

public class LocateRegistry {
    public static Registry createRegistry(int port)
    public static Registry getRegistry(String host, int port)
    ...
}

public class Registry {
    public void bind(String name, Remote object)
    public Remote lookup(String name)
    public void unbind(String name, Remote object)
    ...
}

public interface Remote { }

public class RemoteException extends java.io.IOException { ... }

public interface Serializable { }

public class UnicastRemoteObject {
    public UnicastRemoteObject() throws RemoteException
    public static Remote exportObject(Remote object, int port)
    public static boolean unexportObject(Remote object, boolean force)
    ...
}

Frage 6

Vervollständigen Sie die folgende Implementierung einer Notentabelle mit REST-Schnittstelle und zugehörigen Clients. Das System besteht aus einem Servlet (ExamResultsServlet), das Klausurergebnisse (ExamResult) als XML oder als Plaintext zur Verfügung stellt. Das Servlet kann eine Exception (NoSuchStudentException) werfen, wenn eine Note für einen nicht existierenden Studenten abgefragt wird. Für die Schnittstelle gibt es auch einen Client (ExamClient).

ExamResult

// a single student's grade, serializable to XML

// TODO

public class ExamResult {

    private int matNr;
    private String studentName;
    private double points;

    // TODO

    public ExamResult() {
        this.matNr = matNr;
        this.studentName = studentName;
        this.points = points;
    }

    public ExamResult(int matNr, String studentName, double points) {
        this.matNr = matNr;
        this.studentName = studentName;
        this.points = points;
    }

    public int getMatNr() {
        return matNr;
    }

    // TODO

    public void setMatNr(int matNr) {
        this.matNr = matNr;
    }

    public String getStudentName() {
        return studentName;
    }

    // TODO

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    public double getPoints() {
        return points;
    }

    // TODO

    public void setPoints(double points) {
        this.points = points;
    }

    @Override
    public String toString() {
        return String.format("%s (%d) received %.2f points", studentName, matNr, points);
    }
}

NoSuchStudentException

public class NoSuchStudentException extends RuntimeException {
    public NoSuchStudentException(int matNr) {
        super(String.format("No result available for student with matriculation number %d", matNr));
    }
}

ExamResultsServlet

@Path("/results")
public class ExamResultsServlet {

    private static final List<ExamResult> results = new ArrayList<>(Arrays.asList(
            new ExamResult(34633456, "John Doe", 80.5),
            new ExamResult(32463476, "Jane Doe", 100.0),
            new ExamResult(76823935, "Jeremy Doe", 42.5)
    ));

    // Please answer this question: Does a client for this REST API have to be implemented in Java?
    // TODO

    // TODO

    @Produces(MediaType. // TODO )
    @Path("/all")
    public List<ExamResult> getAllResultsAsXML() {
        synchronized (results) {
            return new ArrayList<>(results);
        }
    }

    // TODO

    @Produces(MediaType. // TODO )
    @Path("/{matNr}")
    
    public String getSingleResultAsPlainText( /* TODO */ ("matNr") int matNr)
            throws NoSuchStudentException {
    
        synchronized (results) {
            for (ExamResult result : results) {
                if (result.getMatNr() == matNr) {
                    return result.toString();
                }
            }
        }
    
        throw new NoSuchStudentException(matNr);
    }

    // TODO

    public static class NoSuchStudentExceptionMapper implements ExceptionMapper<NoSuchStudentException> {
        @Override
        public Response toResponse(NoSuchStudentException e) {
            return Response.status(Response.Status.NOT_FOUND).entity(e.getMessage()).build();
        }
    }
}
Last updated: