Initial commit
This commit is contained in:
commit
2a34df16f4
|
@ -0,0 +1,29 @@
|
||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Default formatting Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# do not trim trailing whitespace in markdown files
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
# explicit 4 space indentation
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# explicit 2 space indentation
|
||||||
|
[*.{json, yml, yaml, xml, ddl, sql}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# windows specific files
|
||||||
|
[*.{bat, cmd}]
|
||||||
|
end_of_line = crlf
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# These are explicitly windows files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"name": "Run PA unit tests",
|
||||||
|
"setup": "",
|
||||||
|
"run": "gradle :PictureDB:test --info",
|
||||||
|
"input": "",
|
||||||
|
"output": "",
|
||||||
|
"comparison": "included",
|
||||||
|
"timeout": 10,
|
||||||
|
"points": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
name: GitHub Classroom Workflow
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Autograding
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
- uses: education/autograding@v1
|
|
@ -0,0 +1,77 @@
|
||||||
|
# ignore files generated in exercises
|
||||||
|
copy-*-rmz450.jpg
|
||||||
|
copy-*-rmz450-spec.txt
|
||||||
|
CharSetEvaluation_ASCII.txt
|
||||||
|
CharSetEvaluation_Default.txt
|
||||||
|
CharSetEvaluation_WIN1552.txt
|
||||||
|
|
||||||
|
# InelliJ IDEA files
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.ids
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Eclipse files
|
||||||
|
.project
|
||||||
|
.metadata
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# Netbeans
|
||||||
|
nbactions.xml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
|
||||||
|
# gradle files
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
|
||||||
|
# ignore logfiles
|
||||||
|
*.log*
|
||||||
|
|
||||||
|
# OS dependant files
|
||||||
|
.DS_Store
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
*~
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# compiled files
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# packages
|
||||||
|
*.7z
|
||||||
|
#*.jar
|
||||||
|
*.rar
|
||||||
|
*.zip
|
||||||
|
*.gz
|
||||||
|
*.bzip
|
||||||
|
*.xz
|
||||||
|
*.lzma
|
||||||
|
*~$*
|
||||||
|
|
||||||
|
# package managment formats
|
||||||
|
*.dmg
|
||||||
|
*.xpi
|
||||||
|
*.gem
|
||||||
|
*.egg
|
||||||
|
*.deb
|
||||||
|
*.rpm
|
||||||
|
|
||||||
|
# databases
|
||||||
|
*.sqlite
|
|
@ -0,0 +1,266 @@
|
||||||
|
:source-highlighter: coderay
|
||||||
|
:icons: font
|
||||||
|
:experimental:
|
||||||
|
:!sectnums:
|
||||||
|
:imagesdir: ./images/
|
||||||
|
:handout: ./code/
|
||||||
|
|
||||||
|
:logo: IT.PROG2 -
|
||||||
|
ifdef::backend-html5[]
|
||||||
|
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
|
||||||
|
endif::[]
|
||||||
|
ifdef::backend-pdf[]
|
||||||
|
:logo:
|
||||||
|
endif::[]
|
||||||
|
ifdef::env-github[]
|
||||||
|
:tip-caption: :bulb:
|
||||||
|
:note-caption: :information_source:
|
||||||
|
:important-caption: :heavy_exclamation_mark:
|
||||||
|
:caution-caption: :fire:
|
||||||
|
:warning-caption: :warning:
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
= {logo} Praktikum Input / Output
|
||||||
|
|
||||||
|
== Einleitung
|
||||||
|
|
||||||
|
Ziele dieses Praktikums sind:
|
||||||
|
|
||||||
|
* Sie üben den Umgang mit Dateien und Dateiattributen.
|
||||||
|
* Sie verstehen Zeichensätze und können Sie anwenden.
|
||||||
|
* Sie beherrschen den Unterschied zwischen Byte- und Character-orientierten Streams.
|
||||||
|
* Sie können Inhalte aus Dateien lesen und schreiben.
|
||||||
|
* Sie können das Java Logger Framework anwenden.
|
||||||
|
|
||||||
|
=== Voraussetzungen
|
||||||
|
* Vorlesung Input/Output 1 und 2
|
||||||
|
|
||||||
|
=== Tooling
|
||||||
|
|
||||||
|
* Installiertes JDK 17+
|
||||||
|
* Gradle 7.4+
|
||||||
|
|
||||||
|
=== Struktur
|
||||||
|
|
||||||
|
Ein Praktikum kann verschiedene Arten von Aufgaben enthalten, die wie folgt gekennzeichnet sind:
|
||||||
|
|
||||||
|
[TU] – Theoretische Übung::
|
||||||
|
Dient der Repetition bzw. Vertiefung des Stoffes aus der Vorlesung und als Vorbereitung für die nachfolgenden Übungen.
|
||||||
|
|
||||||
|
[PU] – Praktische Übung::
|
||||||
|
Übungsaufgaben zur praktischen Vertiefung von Teilaspekten des behandelten Themas.
|
||||||
|
|
||||||
|
[PA] – Pflichtaufgabe::
|
||||||
|
Übergreifende Aufgabe zum Abschluss. Das Lösen dieser Aufgaben ist Pflicht. Sie muss bis zum definierten Zeitpunkt abgegeben werden, wird bewertet und ist Teil der Vornote.
|
||||||
|
|
||||||
|
=== Zeit und Bewertung
|
||||||
|
|
||||||
|
Für dieses Praktikum stehen 2 Wochen in den Praktikumslektionen und im Selbststudium zur Verfügung. +
|
||||||
|
Je nach Kenntniss- und Erfahrungsstufe benötigen Sie mehr oder weniger Zeit.
|
||||||
|
Nutzen Sie die Gelegenheit den Stoff zu vertiefen, Auszuprobieren, Fragen zu stellen und Lösungen zu diskutieren (Intensive-Track). +
|
||||||
|
Falls Sie das Thema schon beherrschen, müssen Sie nur die Pflichtaufgaben lösen und bis zum angegebenen Zeitpunkt abgeben (Fast-Track).
|
||||||
|
|
||||||
|
Die Pflichtaufgabe wird mit 0 bis 2 Punkten bewertet (siehe _Leistungsnachweise_ auf Moodle).
|
||||||
|
|
||||||
|
=== Referenzen
|
||||||
|
|
||||||
|
* link:{handout}[Praktikumsverzeichnis – Quellcode, Projektstruktur]
|
||||||
|
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 2
|
||||||
|
// Beginn des Aufgabenblocks
|
||||||
|
|
||||||
|
== Dateien und Attribute [PU]
|
||||||
|
|
||||||
|
Im link:{handout}[Praktikumsverzeichnis] finden Sie das Projekt `FileAttributes`.
|
||||||
|
Hier soll das Lesen von Dateien und Dateiattributen geübt werden.
|
||||||
|
|
||||||
|
Der Klasse `DirList` kann beim Starten ein Dateipfad übergeben werden.
|
||||||
|
Falls der übergebene Pfad eine Datei ist, sollen die Attribute der Datei in einer Zeile ausgegeben werden.
|
||||||
|
Falls es sich um ein Verzeichnis handelt, sollen die Attribute aller Dateien dieses Verzeichnisses zeilenweise ausgegeben werden.
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
----
|
||||||
|
> java ch.zhaw.prog2.io.DirList .
|
||||||
|
frw-h 2020-02-24 16:49:57 630 .editorconfig
|
||||||
|
drwx- 2020-02-24 16:49:57 96 gradle
|
||||||
|
frw-- 2020-04-23 06:53:39 6392 README.adoc
|
||||||
|
frwx- 2020-02-24 16:49:57 5764 gradlew
|
||||||
|
...
|
||||||
|
----
|
||||||
|
|
||||||
|
Im ersten Block haben die Spalten folgende Bedeutung:
|
||||||
|
|
||||||
|
* Typ (File: `f` oder Directory: `d`),
|
||||||
|
* Leserecht (Read: `r` oder `-` falls nicht)
|
||||||
|
* Schreibrecht (Write: `w` oder `-` falls nicht)
|
||||||
|
* Ausführrecht (Execute: `x` oder `-` falls nicht)
|
||||||
|
* Versteckte Datei (Hidden: `h` oder `-` falls nicht)
|
||||||
|
|
||||||
|
Die nachfolgenden Blöcke enthalten:
|
||||||
|
|
||||||
|
* Datum und Uhrzeit der letzten Änderung
|
||||||
|
* Grösse der Datei in Bytes
|
||||||
|
* Name der Datei
|
||||||
|
|
||||||
|
Falls die übergebene Datei nicht existiert, soll eine Fehlermeldung ausgegeben werden.
|
||||||
|
|
||||||
|
Was bedeuteten die Attribute Lesen (`r`), Schreiben (`w`) und Ausführen (`x`) bei einem Verzeichnis?
|
||||||
|
|
||||||
|
|
||||||
|
== Verstehen von Zeichensätzen [PU]
|
||||||
|
In der Vorlesung haben Sie gelernt, dass Java Unicode zum Speichern von Zeichen (Character) verwendet.
|
||||||
|
Nun ist Unicode aber nicht der einzige Zeichensatz und Java unterstützt durchaus Alternativen zum Lesen und Schreiben.
|
||||||
|
Welche Zeichensätze auf einem System konkret verwendet werden hängt von der Konfiguration des Betriebssystems und der JVM ab.
|
||||||
|
|
||||||
|
Im link:{handout}[Praktikumsverzeichnis] finden Sie das Projekt `Charsets`.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Ergänzen Sie in der Klasse `UnderstandingCharsets` den Code, um alle von der JVM unterstützten Zeichensätze auf der Konsole (`System.out`), sowie den für Ihr System definierten Standardzeichensatz auszugeben. +
|
||||||
|
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html
|
||||||
|
|
||||||
|
. Ergänzen Sie die Klasse so, dass sie einzelne Zeichen (also Zeichen für Zeichen) im Standardzeichensatz von der Konsole einliest und in zwei Dateien schreibt einmal im Standardzeichensatz und einmal im Zeichensatz `US-ASCII`.
|
||||||
|
* Die Eingabe des Zeichens `q` soll das Program ordentlich beenden.
|
||||||
|
* Die Dateien sollen `CharSetEvaluation_Default.txt` und `CharSetEvaluation_ASCII.txt` genannt werden und werden entweder erzeugt oder, falls sie bereits existieren, geöffnet und der Inhalt überschrieben.
|
||||||
|
* Testen Sie Ihr Program mit den folgenden Zeichen: a B c d € f ü _ q
|
||||||
|
* Öffnen Sie die Textdateien nach Ausführung des Programs mit einem Texteditor
|
||||||
|
und erklären Sie das Ergebnis.
|
||||||
|
* Öffnen Sie die Dateien anschliessend mit einem HEX-Viewer/Editor und vergleichen Sie.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
Mit einem HEX-Viewer/Editor können die Bytes einer beliebigen Datei als Folge von Hexadezimalzahlen dargestellt und editiert werden.
|
||||||
|
In der Regel könnend die Bytefolgen auch in Binär, Oktal oder als Zeichenkodierung angezeigt werden.
|
||||||
|
Für die meisten IDE gibt es Hex-Viewer/Editoren als Plugins (z.B. BinEd).
|
||||||
|
Alternativ können Sie diese auch unabhängig installieren
|
||||||
|
(https://en.wikipedia.org/wiki/Comparison_of_hex_editors)
|
||||||
|
====
|
||||||
|
|
||||||
|
|
||||||
|
== Byte- vs. Zeichenorientierte Streams [PU]
|
||||||
|
Im Unterricht haben Sie zwei Typen von IO-Streams kennengelernt; Byte- und Zeichenorientierte-Streams.
|
||||||
|
In dieser Übung soll deren Verwendung geübt und analysiert werden was passiert, wenn der falsche Typ verwendet wird.
|
||||||
|
|
||||||
|
Im link:{handout}[Praktikumsverzeichnis] finden Sie das Projekt `ByteCharStream`,
|
||||||
|
welches unter anderem das Verzeichnis `files` mit den Dateien `rmz450.jpg` und `rmz450-spec.txt` enthält.
|
||||||
|
Ergänzen Sie die Klasse `FileCopy` mit folgender Funktionalität.
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Verzeichnis-Struktur verifizieren: Methode `verifySourceDir()`
|
||||||
|
* Das Quell-Verzeichnis soll auf Korrektheit überprüft werden.
|
||||||
|
* Korrekt bedeutet, dass das Verzeichnis existiert und ausser zwei Dateien mit den Namen
|
||||||
|
`rmz450.jpg` und `rmz450-spec.txt` nichts weiter enthält.
|
||||||
|
* Im Fehlerfall werden Exceptions geworfen.
|
||||||
|
|
||||||
|
. Dateien kopieren: Methode `copyFiles()`
|
||||||
|
- Jede Datei im Quell-Verzeichnis soll zweimal kopiert werden, einmal zeichen- und einmal byte-orientiert.
|
||||||
|
- Dazu soll die jeweilige Datei geöffnet und Element für Element (d.h. byte- bzw. charakterweise) von der Originaldatei gelesen und in die Zieldatei geschrieben werden.
|
||||||
|
- Die Kopien sollen so benannt werden, dass aus dem Dateinamen hervorgeht, mit welcher Methode sie erstellt wurde.
|
||||||
|
|
||||||
|
. Öffnen Sie die Kopien anschliessend mit einem entsprechenden Programm und erklären Sie die entstandenen Effekte.
|
||||||
|
|
||||||
|
. Öffnen Sie die Kopien anschliessend mit einem HEX-Viewer/Editor und erklären Sie die Gründe für die Effekte.
|
||||||
|
|
||||||
|
|
||||||
|
== Picture File Datasource [PA]
|
||||||
|
In Programmen will man oft die Anwendungslogik von der Datenhaltung (Persistenzschicht) abstrahieren,
|
||||||
|
in dem ein Technologie-unabhängiges Interface zum Schreiben und Lesen der Daten verwendet wird.
|
||||||
|
Dies ermöglicht den Wechsel zwischen verschiedenen Speichertechnologien (Datenbank, Dateien, Netzwerkserver, ...), ohne dass die Anwendungslogik angepasst werden muss.
|
||||||
|
|
||||||
|
In der Übung `PictureDB` verwenden wir ein Interface `PictureDatasource` zum Speichern und Lesen von Bildinformationen (Klasse `Picture`).
|
||||||
|
`PictureDatasource` enthält Methoden, um auf eine Datenquelle zuzugreifen, welche Informationen zu Bildern speichert.
|
||||||
|
|
||||||
|
Vereinfacht sieht das Interface wie folgt aus:
|
||||||
|
[source, Java]
|
||||||
|
----
|
||||||
|
public interface PictureDatasource {
|
||||||
|
// inherited from GenericDatasource<T>
|
||||||
|
public void insert(Picture picture);
|
||||||
|
public void update(Picture picture) throws RecordNotFoundException;
|
||||||
|
public void delete(Picture picture) throws RecordNotFoundException;
|
||||||
|
public int count();
|
||||||
|
public Picture findById(int id);
|
||||||
|
public Collection<Picture> findAll();
|
||||||
|
|
||||||
|
// extended finder method for looking up picture records
|
||||||
|
public Collection<Picture>findByPosition(float longitude, float latitude, float deviation);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
In Realität erweitert `PictureDataSource` das generische Interface `GenericDatasource`, welches die Methoden enthält, die für alle Datenobjekttypen gleich sind, und definiert eine zusätzliche Picture spezifischen finder-Methode `findByPosition`.
|
||||||
|
====
|
||||||
|
Anhand der Methoden ist nicht ersichtlich, wie diese Informationen gespeichert werden.
|
||||||
|
Es können somit unterschiedliche Implementationen für unterschiedliche Datenquellen implementiert werden (z.B. diverse Datenbanktypen SQL/No-SQL, Dateien, ...).
|
||||||
|
|
||||||
|
Ziel dieser Aufgabe ist es die Klasse `FilePictureDatasource` umzusetzen, welche Datensätze des Typs `Picture` in einer Datei verwaltet.
|
||||||
|
|
||||||
|
In der Datendatei (`db/picture-data.csv`) sollen die Daten der `Picture`-Objekte im _Character Separated Value_ Format (CSV) gespeichert werden.
|
||||||
|
|
||||||
|
Das heisst jeder Datensatz wird in einer Zeile gespeichert. Die Felder werden mit einem Trennzeichen (`DELIMITER`), in unserem Fall der Strichpunkt (`;`) getrennt.
|
||||||
|
Die Reihenfolge der Felder wird durch den bestehenden Inhalt der Datei vorgegeben.
|
||||||
|
----
|
||||||
|
id;date;longitude;latitude;title;url
|
||||||
|
----
|
||||||
|
|
||||||
|
Damit die Datensätze eindeutig identifiziert werden können, muss jeder Eintrag eine eindeutige Identifikation (`id`) besitzen, die sich, sobald gespeichert, nicht mehr ändern darf.
|
||||||
|
Die `id` wird beim ersten Speichern in die Datasource von dieser bestimmt und im Datenobjekt gesetzt.
|
||||||
|
Da jedes Datenobjekt diese Anforderung hat, wurde dies in der abstrakten Klasse `Record` implementiert, von welcher `Picture` abgeleitet ist.
|
||||||
|
[NOTE]
|
||||||
|
====
|
||||||
|
`Record` hat nichts mit Java-Records zu tun.
|
||||||
|
Es ist eine normale abstrakte Klasse, ist nicht final, d.h. kann / soll erweitert werden, und die Klassen benötigen einen Default-Konstruktor.
|
||||||
|
====
|
||||||
|
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Studieren Sie abgegebenen generischen und abstrakten Klassen, sowie die Klasse `Picture`, die bereits komplett implementiert ist.
|
||||||
|
. Überlegen Sie sich, wie die einzelnen Operationen (insert, update, delete, ...) umgesetzt werden können, wenn sie mit zeilenweisen Records in einer Textdatei arbeiten:
|
||||||
|
* Wie kann bei einem Insert die nächste noch nicht verwendete `id` bestimmt werden? +
|
||||||
|
Bedenken Sie:
|
||||||
|
** Es kann sein das von verschiedenen Stellen auf die Datei zugegriffen wird.
|
||||||
|
Sie können sich also nicht auf eine statische Variable verlassen.
|
||||||
|
** Es können und dürfen beim Löschen von Records Lücken bei den id's entstehen
|
||||||
|
** Die Zeilen müssen nicht geordnet sein, d.h. es muss nicht sein, dass der Record mit der grössten id am Ende steht.
|
||||||
|
** Die Anzahl Zeilen ist kein guter Indikator, da wie gesagt die ids nicht immer fortlaufend sein müssen (d.h. Lücken von gelöschten Records haben kann).
|
||||||
|
* Wie aktualisieren Sie eine einzelne Zeile bei einem Update?
|
||||||
|
* Wie entfernen Sie eine ganze Zeile bei einem Delete?
|
||||||
|
+
|
||||||
|
[IMPORTANT]
|
||||||
|
====
|
||||||
|
Die Lösung muss mit einer minimalen, deterministischen Menge Speicher auskommen, d.h. Sie können nicht einfach die ganze Datei in den Speicher laden, da die Datei sehr gross sein könnte.
|
||||||
|
====
|
||||||
|
+
|
||||||
|
[TIP]
|
||||||
|
====
|
||||||
|
Da sie nicht gleichzeitig in der gleichen Datei lesen und schreiben können, hilft es gegebenenfalls mit zwei Dateien zu arbeiten (lesen -> schreiben).
|
||||||
|
|
||||||
|
Die Klasse `java.nio.file.Files` bietet statische Hilfsmethoden zum Erstellen temporärer Dateien.
|
||||||
|
====
|
||||||
|
|
||||||
|
. Implementieren sie die Methoden der Klasse `FilePictureDatasource`
|
||||||
|
* Nutzen sie die vorhandenen Konstanten und Hilfsobjekte (z.B, Dateformat)
|
||||||
|
* Beachten Sie die JavaDoc-Beschreibung der Methoden. Die Signatur der Methoden soll nicht verändert werden.
|
||||||
|
* Testen Sie ihre Implementation mit Hilfe der Klasse `PictureImport`, in welcher Bildinformationen von der Konsole abgefragt, als Picture-Record gespeichert und wieder ausgelesen werden.
|
||||||
|
* Stellen Sie sicher, dass die Unit-Tests `FilePictureDataSourceTest` erfolgreich ausgeführt werden.
|
||||||
|
. Ergänzen Sie die Klasse `FilePictureDatasource` mit Logger-Meldungen.
|
||||||
|
* Die Initialisierung der Logger erfolgt über die Klasse `LogConfiguration`.
|
||||||
|
Analysieren Sie die Konfiguration.
|
||||||
|
** Welche Konfigurationsdatei wird geladen?
|
||||||
|
** Welche Log-Handler werden erzeugt und welche Meldungen wo ausgegeben?
|
||||||
|
** Wie kann das Format der Log-Meldungen angepasst werden?
|
||||||
|
** Wie können Sie die Konfiguration für die folgenden Anforderungen anpassen?
|
||||||
|
* Verwenden Sie verschiedene Level von Log-Meldungen (INFO, WARNING, SEVERE, FINE,...).
|
||||||
|
Zum Beispiel:
|
||||||
|
** Statusmeldungen → INFO (Record saved)
|
||||||
|
** Fehlermeldungen → WARNING oder SEVERE (Failed to save record)
|
||||||
|
** Debugmeldungen → FINE, FINER (New id=..., File opened/closed/copied/deleted)
|
||||||
|
* Passen Sie die Logger-Konfiguration an
|
||||||
|
** Auf der Konsole sollen Meldungen des Levels INFO und höher ausgegeben werden.
|
||||||
|
** In eine zusätzliche Log-Datei `picturedb.log` sollen alle Meldungen (inkl. FINE, FINER) zeilenweise ausgegeben werden.
|
||||||
|
|
||||||
|
// Ende des Aufgabenblocks
|
||||||
|
:!sectnums:
|
||||||
|
== Abschluss
|
||||||
|
|
||||||
|
Stellen Sie sicher, dass die Pflichtaufgaben mittels `gradle run` gestartet werden können und die Tests mit `gradle test` erfolgreich laufen und pushen Sie die Lösung vor der Deadline in Ihr Abgaberepository.
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 ByteCharStream'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.FileCopy'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
ENGINE
|
||||||
|
Engine 449cc, 4-stroke, liquid-cooled, single cylinder, DOHC
|
||||||
|
Bore Stroke 96.0 x 62.1 mm (3.78 x 2.4 in)
|
||||||
|
Compression Ratio 12.5:1
|
||||||
|
Fuel System Fuel Injection
|
||||||
|
Starter Primary kick
|
||||||
|
Lubrication Semi-dry sump
|
||||||
|
DRIVE TRAIN
|
||||||
|
Transmission 5-speed constant mesh
|
||||||
|
Clutch Wet multi-plate type, manual release
|
||||||
|
Final Drive Chain, DID520MXV4, 114 links
|
||||||
|
CHASSIS
|
||||||
|
Suspension Front Inverted telescopic, air spring, oil damped
|
||||||
|
Suspension Rear Link type, coil spring, oil damped
|
||||||
|
Brakes Front Disc brake, single rotor
|
||||||
|
Brakes Rear Disc brake, single rotor
|
||||||
|
Tires Front 80/100-21 51M, tube type
|
||||||
|
Tires Rear 110/90-19 62M, tube type
|
||||||
|
Fuel Tank Capacity 6.2 L (1.6 US gallons)
|
||||||
|
Color Champion Yellow No.2 / Solid Black
|
||||||
|
ELECTRICAL
|
||||||
|
Ignition Electronic Ignition (CDI)
|
||||||
|
DIMENSIONS
|
||||||
|
Overall Length 2190 mm (86.2 in)
|
||||||
|
Overall Width 830 mm (32.7 in)
|
||||||
|
Overall Height 1270 mm (50.0 in)
|
||||||
|
Wheelbase 1495 mm (58.9 in)
|
||||||
|
Ground Clearance 325 mm (12.8 in)
|
||||||
|
Seat Height 955 mm (37.6 in)
|
||||||
|
Curb Weight 112 kg (247 lbs)
|
Binary file not shown.
After Width: | Height: | Size: 134 KiB |
|
@ -0,0 +1,71 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class FileCopy {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
|
||||||
|
// get the filename from the arguments. By default, use 'files'-directory in current working directory.
|
||||||
|
String sourceDirPath = args.length >= 1 ? args[0] : "./files";
|
||||||
|
File sourceDir = new File(sourceDirPath);
|
||||||
|
|
||||||
|
// Part a – Verify the directory structure
|
||||||
|
// Implement the method 'verifySourceDir()'
|
||||||
|
System.out.format("Verifying Source Directory: %s%n", sourceDirPath);
|
||||||
|
try {
|
||||||
|
verifySourceDir(sourceDir);
|
||||||
|
} catch (FileNotFoundException error) {
|
||||||
|
System.err.format("Directory %s does not comply with predefined structure: %s%n", sourceDir.getPath(), error.getMessage());
|
||||||
|
System.err.println("Terminating programm!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
System.out.printf("Source Directory verified successfully.");
|
||||||
|
|
||||||
|
|
||||||
|
// Part b – Copy the files byte resp. char wise.
|
||||||
|
// Implement the method 'verifySourceDir()'
|
||||||
|
System.out.println("Initiating file copies.");
|
||||||
|
try {
|
||||||
|
copyFiles(sourceDir);
|
||||||
|
} catch (IOException error) {
|
||||||
|
System.err.format("Error creating file copies: %s%n", error.getMessage());
|
||||||
|
System.err.println("Terminating programm!");
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
System.out.println("Files copied successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part a – directory structure
|
||||||
|
*
|
||||||
|
* Verify the directory structure for correctness.
|
||||||
|
* Correct means, that the directory exists and beside the two files rmz450.jpg and rmz450-spec.txt does not contain
|
||||||
|
* any other files or directories.
|
||||||
|
*
|
||||||
|
* @param sourceDir File source directory to verify it contains the correct structure
|
||||||
|
* @throws FileNotFoundException if the source directory or required file are missing
|
||||||
|
* @throws InvalidObjectException if the source directory contains invalid files or directories.
|
||||||
|
*/
|
||||||
|
private static void verifySourceDir(File sourceDir) throws FileNotFoundException, InvalidObjectException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teilaufgabe b – Kopieren von Dateien
|
||||||
|
*
|
||||||
|
* Copies each file of the source directory twice, once character-oriented and once byte-oriented.
|
||||||
|
* Source and target files should be opened and copied byte by byte respectively char by char.
|
||||||
|
* The target files should be named, so the type of copy can be identified.
|
||||||
|
*
|
||||||
|
* @param sourceDir File representing the source directory containing the files to copy
|
||||||
|
* @throws IOException if an error is happening while copying the files
|
||||||
|
*/
|
||||||
|
private static void copyFiles(File sourceDir) throws IOException {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 UnderstandingCharsets'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.UnderstandingCharsets'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
public class UnderstandingCharsets {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
/* Teilaufgabe a
|
||||||
|
* In der Vorlesung haben Sie gelernt, dass Java-Klassen fuer Unicode entworfen wurden.
|
||||||
|
* Nun ist Unicode aber nicht der einzige Zeichensatz und Java unterstuetz durchaus Alternativen.
|
||||||
|
* Welche Zeichensaetze auf einem System konkret unterstuetzt werden haengt von der Konfiguration des Betriebssystems JVM ab.
|
||||||
|
* Schreiben Sie ein Programm, welches alle Unterstuetzten Zeichensaetze auf der Konsole (System.out) ausgibt,
|
||||||
|
* zusammen mit dem Standardzeichensatz.
|
||||||
|
* https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ToDo: Print default character set
|
||||||
|
|
||||||
|
|
||||||
|
// Todo: Print all available character sets
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Ende Teilaufgabe a */
|
||||||
|
|
||||||
|
|
||||||
|
/* Teilaufgabe b
|
||||||
|
* Ergänzen Sie die Klasse so, dass sie einzelne Zeichen (also Zeichen für Zeichen) im Standardzeichensatz
|
||||||
|
* von der Konsole einliest und in zwei Dateien schreibt einmal im Standardzeichensatz und einmal im
|
||||||
|
* Zeichensatz `US-ASCII`.
|
||||||
|
* Die Eingabe des Zeichens `q` soll das Program ordentlich beenden.
|
||||||
|
* Die Dateien sollen `CharSetEvaluation_Default.txt` und `CharSetEvaluation_ASCII.txt` genannt und
|
||||||
|
* werden entweder erzeugt oder, falls sie bereits existieren, geöffnet und der Inhalt überschrieben.
|
||||||
|
* Testen Sie Ihr Program mit den folgenden Zeichen: a B c d € f ü _ q
|
||||||
|
* Öffnen Sie die Textdateien nach Ausführung des Programs mit einem Texteditor und erklären Sie das Ergebnis.
|
||||||
|
* Öffnen Sie die Dateien anschliessend mit einem HEX-Editor und vergleichen Sie.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 FileAttributes'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.DirList'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
public class DirList {
|
||||||
|
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String pathName = args.length >= 1 ? args[0] : ".";
|
||||||
|
File file = new File(pathName);
|
||||||
|
// Write metadata of given file, resp. all of its files if it is a directory
|
||||||
|
// Whith each file on one line in the following format.
|
||||||
|
// - type of file ('d'=directory, 'f'=file)
|
||||||
|
// - readable 'r', '-' otherwise
|
||||||
|
// - writable 'w', '-' otherwise
|
||||||
|
// - executable 'x', '-' otherwise
|
||||||
|
// - hidden 'h', '-' otherwise
|
||||||
|
// - modified date in format 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
// - length in bytes
|
||||||
|
// - name of the file
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 PictureDB'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Junit 5 dependencies
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test task configuration
|
||||||
|
test {
|
||||||
|
// Use JUnit platform for unit tests
|
||||||
|
useJUnitPlatform()
|
||||||
|
// Output results of individual tests
|
||||||
|
testLogging {
|
||||||
|
events "PASSED", "SKIPPED", "FAILED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.picturedb.PictureImport'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run task configuration
|
||||||
|
run {
|
||||||
|
// enable console input when running with gradle
|
||||||
|
standardInput = System.in
|
||||||
|
// set system property to load log configuration using class (takes precedence; if not set or fails, file is used)
|
||||||
|
systemProperty 'java.util.logging.config.class', 'ch.zhaw.prog2.io.picturedb.LogConfiguration'
|
||||||
|
// set system property to load log configuration from properties
|
||||||
|
systemProperty 'java.util.logging.config.file', 'log.properties'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
1;2014-03-17 14:30:05;2.324744;48.864506;Another Monkey;http://www.codemonkey.in/images/Code-Monkey.png
|
||||||
|
13;2014-04-01 02:17:33;77.598736;12.979842;Bête à coder;http://www2.craven.fr/blojsom/resources/default/codemonkey.jpg
|
||||||
|
14;2013-05-07 13:45:13;-71.098270;42.302583;Need a coder;http://blog.stackoverflow.com/wp-content/uploads/code_monkey_colour.jpg
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
## Console handler configuration
|
||||||
|
java.util.logging.ConsoleHandler.level = ALL
|
||||||
|
|
||||||
|
## File handler configuration
|
||||||
|
## see https://docs.oracle.com/en/java/javase/11/docs/api/java.logging/java/util/logging/FileHandler.html
|
||||||
|
java.util.logging.FileHandler.level = ALL
|
||||||
|
# %g = generation number, %u = unique number to resolve conflicts
|
||||||
|
java.util.logging.FileHandler.pattern = log-%g-%u.log
|
||||||
|
# use SimpleFormatter instead of default XMLFormatter
|
||||||
|
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
|
||||||
|
java.util.logging.FileHandler.encoding = UTF-8
|
||||||
|
# max log file size in byte before switching to next generation (=10`kB); 0=unlimited
|
||||||
|
java.util.logging.FileHandler.limit = 10240
|
||||||
|
# max number of generations (%g) before overwriting (5 -> 0..4)
|
||||||
|
java.util.logging.FileHandler.count=5
|
||||||
|
java.util.logging.FileHandler.append=true
|
||||||
|
|
||||||
|
## Configure format of log messages
|
||||||
|
# arguments see https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/util/logging/SimpleFormatter.html
|
||||||
|
# formats see https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Formatter.html
|
||||||
|
# format "[date] [level] message exception"
|
||||||
|
java.util.logging.SimpleFormatter.format = [%1$tF %1$tT %1tZ] [%4$s] %5$s%6$s%n
|
||||||
|
# format "[date] [level] [logger] message exception"
|
||||||
|
#java.util.logging.SimpleFormatter.format = [%1$tF %1$tT %1tZ] [%4$-7s] [%3$s] %5$s%6$s%n
|
||||||
|
# format "[date] [level] [position in source] message exception"
|
||||||
|
#java.util.logging.SimpleFormatter.format = [%1$tF %1$tT %1tZ] [%4$-7s] [%2$s] %5$s%6$s%n
|
||||||
|
|
||||||
|
## configure default log level (for all loggers, if not overwritten below)
|
||||||
|
.level = INFO
|
||||||
|
|
||||||
|
## configure root logger ""
|
||||||
|
handlers = java.util.logging.ConsoleHandler
|
||||||
|
level = INFO
|
||||||
|
|
||||||
|
## Application specific logger configuration
|
||||||
|
# loggers starting with "ch.zhaw.prog2.io.picturedb" -> use console and file handler
|
||||||
|
ch.zhaw.prog2.io.picturedb.handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler
|
||||||
|
# do not forward to parent handlers
|
||||||
|
ch.zhaw.prog2.io.picturedb.useParentHandlers = false
|
||||||
|
# Set log levels for specific packages/classes
|
||||||
|
ch.zhaw.prog2.io.picturedb.level = INFO
|
||||||
|
#ch.zhaw.prog2.io.picturedb.FilePictureDatasource.level = FINER
|
|
@ -0,0 +1,67 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic data source interface to persist items of type T extending {@link Record}
|
||||||
|
* What kind of persistence media is used, is defined by the concrete implementation.
|
||||||
|
* e.g. InMemory, Files, Database, ...
|
||||||
|
* The datatype T to be persisted, must extend {@link Record} which contains the id field to uniquely identify the record.
|
||||||
|
*
|
||||||
|
* @param <T extends Record> data type to persist
|
||||||
|
*/
|
||||||
|
public interface Datasource<T extends Record> {
|
||||||
|
/**
|
||||||
|
* Insert a new record to the data source.
|
||||||
|
* The id field of the record is ignored, and a new unique id has to be generated, which will be set in the record.
|
||||||
|
* This id is used to identify the record in the dataset by the other methods (i.e. find, update or delete methods)
|
||||||
|
*
|
||||||
|
* @param record of type T to insert into the data set.
|
||||||
|
*/
|
||||||
|
void insert(T record);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the content of an existing record in the data set, which is identified by the unique identifier,
|
||||||
|
* with the new values from the given record object.
|
||||||
|
* If the identifier can not be found in the data set, an {@link RecordNotFoundException} is thrown.
|
||||||
|
*
|
||||||
|
* @param record to be updated in the dataset
|
||||||
|
* @throws RecordNotFoundException if the record is not existing
|
||||||
|
*/
|
||||||
|
void update(T record) throws RecordNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the record, identified by the id of the given record from the data set.
|
||||||
|
* All other fields of the record are ignored.
|
||||||
|
* If the identifier can not be found in the data set, an {@link RecordNotFoundException} is thrown.
|
||||||
|
*
|
||||||
|
* @param record to be deleted
|
||||||
|
* @throws RecordNotFoundException if the record is not existing
|
||||||
|
*/
|
||||||
|
void delete(T record) throws RecordNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of records in the data set
|
||||||
|
* @return number of records
|
||||||
|
*/
|
||||||
|
long count();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an instance of the record identified by the given id.
|
||||||
|
* If the record can not be found, null is returned.
|
||||||
|
* (better return type would be an {@link java.util.Optional} which is covered in part Functional Programming)
|
||||||
|
* An empty result is not an error. Therefore, we do not throw an exception.
|
||||||
|
*
|
||||||
|
* @param id of the record to be retrieved
|
||||||
|
* @return record of type T or null if not found
|
||||||
|
*/
|
||||||
|
T findById(long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all records of the data set.
|
||||||
|
* If the dataset is empty an empty collection is returned.
|
||||||
|
*
|
||||||
|
* @return collection of all records of the data set
|
||||||
|
*/
|
||||||
|
Collection<T> findAll();
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the PictureDatasource Interface storing the data in
|
||||||
|
* Character Separated Values (CSV) format, where each line consists of a record
|
||||||
|
* whose fields are separated by the DELIMITER value ";"
|
||||||
|
* See example file: db/picture-data.csv
|
||||||
|
*/
|
||||||
|
public class FilePictureDatasource implements PictureDatasource {
|
||||||
|
// Charset to use for file encoding.
|
||||||
|
protected static final Charset CHARSET = StandardCharsets.UTF_8;
|
||||||
|
// Delimiter to separate record fields on a line
|
||||||
|
protected static final String DELIMITER = ";";
|
||||||
|
// Date format to use for date specific record fields
|
||||||
|
protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
private final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the FilePictureDatasource object with the given file path as datafile.
|
||||||
|
* Creates the file if it does not exist.
|
||||||
|
* Also creates an empty temp file for write operations.
|
||||||
|
*
|
||||||
|
* @param filepath of the file to use as database file.
|
||||||
|
* @throws IOException if accessing or creating the file fails
|
||||||
|
*/
|
||||||
|
public FilePictureDatasource(String filepath) throws IOException {
|
||||||
|
// ToDo: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void insert(Picture picture) {
|
||||||
|
// ToDo: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(Picture picture) throws RecordNotFoundException {
|
||||||
|
// ToDo: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void delete(Picture picture) throws RecordNotFoundException {
|
||||||
|
// ToDo: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long count() {
|
||||||
|
// ToDo: Correct Implementation
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Picture findById(long id) {
|
||||||
|
// ToDo: Correct Implementation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Picture> findAll() {
|
||||||
|
// ToDo: Correct Implementation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Picture> findByPosition(float longitude, float latitude, float deviation) {
|
||||||
|
// ToDo: Correct Implementation
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration class for Java Logging
|
||||||
|
* Reads configuration from property file, specified in the following order:
|
||||||
|
* - System property "java.util.logging.config.file" (also used by LogManager)
|
||||||
|
* - file "log.properties" in working directory
|
||||||
|
* - file "log.properties" in project resources
|
||||||
|
* If this class is specified in system property "java.util.logging.config.class" it
|
||||||
|
* is loaded automatically at system startup.
|
||||||
|
* Otherwise, it can also be called at startup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LogConfiguration {
|
||||||
|
private static final Logger logger = Logger.getLogger(LogConfiguration.class.getCanonicalName());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static class configuration.
|
||||||
|
* Only executed once when class is loaded.
|
||||||
|
* Load Java logger configuration from config file
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
Locale.setDefault(Locale.ROOT); // show log messages in english
|
||||||
|
// get log config file (default uses log.properties in working directory)
|
||||||
|
String logConfigFile = System.getProperty("java.util.logging.config.file", "log.properties");
|
||||||
|
Path logConfigPath = Path.of(logConfigFile);
|
||||||
|
try {
|
||||||
|
InputStream configFileStream;
|
||||||
|
if (Files.isReadable(logConfigPath)) {
|
||||||
|
// if available and readable use specified file
|
||||||
|
configFileStream = Files.newInputStream(logConfigPath);
|
||||||
|
} else {
|
||||||
|
// otherwise use minimal config from resources
|
||||||
|
logConfigFile="resources:/log.properties";
|
||||||
|
configFileStream = ClassLoader.getSystemClassLoader().getResourceAsStream("log.properties");
|
||||||
|
}
|
||||||
|
if (configFileStream != null) {
|
||||||
|
LogManager.getLogManager().readConfiguration(configFileStream);
|
||||||
|
logger.fine("Log configuration read from " + logConfigFile);
|
||||||
|
} else {
|
||||||
|
logger.warning("No log configuration found. Using system default settings.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.log(Level.WARNING, "Error loading log configuration", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getProperty(String name) {
|
||||||
|
return LogManager.getLogManager().getProperty(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLogLevel(Class clazz, Level level) {
|
||||||
|
Logger.getLogger(clazz.getCanonicalName()).setLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Level getLogLevel(Class clazz) {
|
||||||
|
return Logger.getLogger(clazz.getCanonicalName()).getLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Picture extends Record {
|
||||||
|
|
||||||
|
private final URL url;
|
||||||
|
private final Date date;
|
||||||
|
private final String title;
|
||||||
|
private final float longitude;
|
||||||
|
private final float latitude;
|
||||||
|
|
||||||
|
public Picture(URL url, Date date, String title, float longitude, float latitude) {
|
||||||
|
this.url = url;
|
||||||
|
this.date = date;
|
||||||
|
this.title = title;
|
||||||
|
this.longitude = longitude;
|
||||||
|
this.latitude = latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Picture(URL url, String title) {
|
||||||
|
this(url, new Date(), title, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Picture(long id, URL url, Date date, String title, float longitude, float latitude) {
|
||||||
|
this(url,date, title, longitude, latitude);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLongitude() {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLatitude() {
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Picture picture = (Picture) o;
|
||||||
|
return Float.compare(picture.longitude, longitude) == 0 &&
|
||||||
|
Float.compare(picture.latitude, latitude) == 0 &&
|
||||||
|
url.equals(picture.url) &&
|
||||||
|
date.equals(picture.date) &&
|
||||||
|
title.equals(picture.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(url, date, title, longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Picture{" +
|
||||||
|
"id=" + id +
|
||||||
|
", url=" + url +
|
||||||
|
", date=" + date +
|
||||||
|
", title='" + title + '\'' +
|
||||||
|
", longitude=" + longitude +
|
||||||
|
", latitude=" + latitude +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration of a Datasource to store Picture records.
|
||||||
|
* Beside the default Datasource methods, it declares an additional Picture specific query method.
|
||||||
|
*/
|
||||||
|
public interface PictureDatasource extends Datasource<Picture> {
|
||||||
|
/**
|
||||||
|
* Retrieves all images close to a a certain position.
|
||||||
|
* All images with a deviation from the exact coordinates are returned.
|
||||||
|
* This includes all objects in a square range
|
||||||
|
* from [longitude - deviation / latitude - deviation]
|
||||||
|
* to [longitude + deviation / latitude + deviation]
|
||||||
|
*
|
||||||
|
* @param longitude longitude coordinate of the center of the square area
|
||||||
|
* @param latitude latitude coordinate of the center of the square area
|
||||||
|
* @param deviation deviation from the center of the area in longitude and latitude direction
|
||||||
|
* @return Collection of all Picture records in the area
|
||||||
|
*/
|
||||||
|
Collection<Picture>findByPosition(float longitude, float latitude, float deviation);
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
/* This demo-application reads some picture data from terminal,
|
||||||
|
* saves it to the datasource, read it from the DB and prints the result
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class PictureImport {
|
||||||
|
private static final String PICTURE_DB = "db/picture-data.csv";
|
||||||
|
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
private static final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
private static final PrintWriter out = new PrintWriter(System.out, true);
|
||||||
|
|
||||||
|
|
||||||
|
public static void main (String[] args) throws IOException {
|
||||||
|
// initialize logger level
|
||||||
|
LogConfiguration.setLogLevel(FilePictureDatasource.class, Level.FINE);
|
||||||
|
|
||||||
|
// create datasource
|
||||||
|
PictureDatasource dataSource = new FilePictureDatasource(PICTURE_DB);
|
||||||
|
// read picture data from the terminal
|
||||||
|
Picture picture = createPicture();
|
||||||
|
// save the picture to the data source
|
||||||
|
dataSource.insert(picture);
|
||||||
|
// read the picture back from file
|
||||||
|
Picture readPicture = dataSource.findById(picture.getId());
|
||||||
|
if (readPicture != null) {
|
||||||
|
out.println("The following pictures has been saved: ");
|
||||||
|
out.println(readPicture);
|
||||||
|
} else {
|
||||||
|
out.println("Picture with id=" + picture.getId() + " not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// read all pictures and list them on the console
|
||||||
|
Collection<Picture> pictures = dataSource.findAll();
|
||||||
|
out.println("Pictures:");
|
||||||
|
for (Picture pict : pictures) {
|
||||||
|
out.println(pict.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Picture createPicture() {
|
||||||
|
// asks the values for the objects
|
||||||
|
out.println("** Create a new picture **");
|
||||||
|
URL url = null;
|
||||||
|
do {
|
||||||
|
String urlString = prompt("Picture URL: ");
|
||||||
|
try {
|
||||||
|
url = new URL(urlString);
|
||||||
|
} catch (MalformedURLException e1) {
|
||||||
|
out.println("Malformed URL: " + e1.getMessage());
|
||||||
|
}
|
||||||
|
} while (url == null);
|
||||||
|
|
||||||
|
String title = prompt("Picture title: ");
|
||||||
|
|
||||||
|
Date date = new Date(); // now
|
||||||
|
try {
|
||||||
|
date = dateFormat.parse(prompt("Picture time ("+DATE_FORMAT+") Default = now: "));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
out.println("Unknown date format. Using "+ dateFormat.format(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
float longitude = 0.0f;
|
||||||
|
try {
|
||||||
|
longitude = Float.parseFloat(prompt("Picture position longitude: "));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
out.println("Unknown number format. Using " + longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
float latitude = 0.0f;
|
||||||
|
try {
|
||||||
|
latitude = Float.parseFloat(prompt("Picture position latitude: "));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
out.println("Unknown number format. Using " + latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Picture(url, date, title, longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt function -- to read input string
|
||||||
|
static String prompt(String prompt) {
|
||||||
|
try {
|
||||||
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
out.print(prompt);
|
||||||
|
out.flush();
|
||||||
|
return scanner.nextLine().strip();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class to be used as base class for data records, which can stored in a {@link Datasource}
|
||||||
|
* When a record is created it gets the default id of -1, indicating that it is a new record, which is not stored
|
||||||
|
* in a DataSource.
|
||||||
|
*/
|
||||||
|
public abstract class Record {
|
||||||
|
/**
|
||||||
|
* Default id for new records, indicating, that it is not yet stored in a datasource.
|
||||||
|
*/
|
||||||
|
private static final long DEFAULT_ID = -1L;
|
||||||
|
/**
|
||||||
|
* Identifier of the record.
|
||||||
|
*/
|
||||||
|
protected long id = DEFAULT_ID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the id for this record. This method is used by the specific data set handler when adding the record to a
|
||||||
|
* {@link Datasource}. It should not be used directly by the user.
|
||||||
|
* @param id new id of the record, when the record is added to the data source
|
||||||
|
*/
|
||||||
|
protected void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identifier of the record
|
||||||
|
* @return identifier of the record
|
||||||
|
*/
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of the record.
|
||||||
|
* @return true, if the record is not stored to the data source, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isNew() {
|
||||||
|
return id == DEFAULT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception indicating that a record could not be found in a datasource.
|
||||||
|
* The reason why is given as a text message.
|
||||||
|
*/
|
||||||
|
public class RecordNotFoundException extends Exception {
|
||||||
|
public RecordNotFoundException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordNotFoundException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordNotFoundException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordNotFoundException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecordNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
## Minimal log configuration setting decent defaults
|
||||||
|
## Console handler configuration
|
||||||
|
java.util.logging.ConsoleHandler.level = ALL
|
||||||
|
|
||||||
|
## Configure format of log messages
|
||||||
|
java.util.logging.SimpleFormatter.format = [%1$tF %1$tT %1tZ] [%4$s] [%3$s] %5$s%6$s%n
|
||||||
|
|
||||||
|
## configure default log level (for all loggers, if not overwritten below)
|
||||||
|
.level = INFO
|
||||||
|
|
||||||
|
## configure root logger ""
|
||||||
|
handlers = java.util.logging.ConsoleHandler
|
||||||
|
level = INFO
|
|
@ -0,0 +1,279 @@
|
||||||
|
package ch.zhaw.prog2.io.picturedb;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.LogManager;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static ch.zhaw.prog2.io.picturedb.FilePictureDatasource.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
|
|
||||||
|
class FilePictureDatasourceTest {
|
||||||
|
private static final long EXISTING_ID = 13;
|
||||||
|
private static final long INEXISTENT_ID = 0;
|
||||||
|
|
||||||
|
private final DateFormat df = new SimpleDateFormat(DATE_FORMAT);
|
||||||
|
private final Random random = new Random(new Date().getTime());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static class configuration.
|
||||||
|
* Only executed once when class is loaded.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
// logger configuration
|
||||||
|
try {
|
||||||
|
// show log messages in english
|
||||||
|
Locale.setDefault(Locale.ROOT);
|
||||||
|
// load default minimal log configuration
|
||||||
|
LogManager.getLogManager().readConfiguration(ClassLoader.getSystemResourceAsStream("log.properties"));
|
||||||
|
// set level for class to test to WARNING (minimize log messages)
|
||||||
|
Logger.getLogger(FilePictureDatasource.class.getCanonicalName()).setLevel(Level.WARNING);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed to read log configuration: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Path dbTemplatePath; // path of template database
|
||||||
|
Path dbPath; // path of temporary test database
|
||||||
|
|
||||||
|
PictureDatasource datasource = null; // datasource instance to test
|
||||||
|
|
||||||
|
FilePictureDatasourceTest() {
|
||||||
|
URL dbTemplateUrl = FilePictureDatasourceTest.class.getClassLoader().getResource("db");
|
||||||
|
Objects.requireNonNull(dbTemplateUrl, "Test database directory not found");
|
||||||
|
String dbDir = new File(dbTemplateUrl.getPath()).getAbsolutePath(); // required for Windows to remove leading '/'
|
||||||
|
String dbDirRaw = URLDecoder.decode(dbDir, CHARSET); // replace urlencoded characters, e.g. %20 -> " "
|
||||||
|
dbTemplatePath = Path.of(dbDirRaw, "test-data-template.csv");
|
||||||
|
dbPath = Path.of(dbDirRaw, "test-data.csv");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws IOException {
|
||||||
|
// initialize test database file
|
||||||
|
Files.copy(dbTemplatePath, dbPath);
|
||||||
|
// setup datasource
|
||||||
|
datasource = new FilePictureDatasource(dbPath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() throws IOException {
|
||||||
|
// cleanup test database file
|
||||||
|
Files.deleteIfExists(dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insert() {
|
||||||
|
Picture testPicture = createPicture("http://test.url/hallo.img", "Test picture");
|
||||||
|
assertEquals(-1, testPicture.getId(), "New picture must have Id -1");
|
||||||
|
datasource.insert(testPicture);
|
||||||
|
assertNotEquals(-1, testPicture.getId(), "Insert must set new id to picture");
|
||||||
|
assertTrue(testPicture.getId() > 14, "Id must be larger than last existing");
|
||||||
|
assertEquals(15, testPicture.getId(), "Id must be 1 larger than current highest");
|
||||||
|
try {
|
||||||
|
String insertedLine = readLineNo(4);
|
||||||
|
assertNotNull(insertedLine, "Inserted line does not exist");
|
||||||
|
assertEquals(pictureToCsvLine(testPicture), insertedLine);
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("Failed reading inserted picture record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void insertNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> datasource.insert(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void delete() {
|
||||||
|
long pictureIdToDelete = EXISTING_ID;
|
||||||
|
Picture pictureToDelete = datasource.findById(pictureIdToDelete);
|
||||||
|
assumeTrue(pictureToDelete != null, "Picture to delete not found");
|
||||||
|
|
||||||
|
try {
|
||||||
|
datasource.delete(pictureToDelete);
|
||||||
|
} catch (RecordNotFoundException e) {
|
||||||
|
fail("Failed to delete picture", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String deletedRecord = readLineWithId(pictureIdToDelete);
|
||||||
|
assertNull(deletedRecord, "Record still found after delete");
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("Failed to read deleted record");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> datasource.delete(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteInexistent() {
|
||||||
|
Picture testPicture = createPicture("http://test.url/hallo.img", "Test picture");
|
||||||
|
testPicture.setId(INEXISTENT_ID);
|
||||||
|
assertThrows(RecordNotFoundException.class, () -> datasource.delete(testPicture));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update() {
|
||||||
|
Picture originalPicture = datasource.findById(EXISTING_ID);
|
||||||
|
assumeTrue(originalPicture != null);
|
||||||
|
assumeTrue(originalPicture.id == EXISTING_ID);
|
||||||
|
|
||||||
|
Date updatedDate = new Date(originalPicture.getDate().getTime() + 60_000);
|
||||||
|
URL updatedURL = originalPicture.getUrl();
|
||||||
|
try {
|
||||||
|
updatedURL = new URL(originalPicture.getUrl() + "/updated");
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
fail("Invalid URL format", e);
|
||||||
|
}
|
||||||
|
Picture updatedPicture = new Picture(
|
||||||
|
originalPicture.id,
|
||||||
|
updatedURL,
|
||||||
|
updatedDate,
|
||||||
|
originalPicture.getTitle()+" (updated)",
|
||||||
|
originalPicture.getLongitude() + 10,
|
||||||
|
originalPicture.getLatitude() + 10);
|
||||||
|
|
||||||
|
try {
|
||||||
|
datasource.update(updatedPicture);
|
||||||
|
} catch (RecordNotFoundException e) {
|
||||||
|
fail("Test record not found for update", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Picture readUpdatedPicture = datasource.findById(originalPicture.getId());
|
||||||
|
assertNotNull(readUpdatedPicture, "updated picture not found");
|
||||||
|
assertEquals(updatedPicture.id, readUpdatedPicture.id);
|
||||||
|
assertEquals(updatedPicture.getUrl(), readUpdatedPicture.getUrl());
|
||||||
|
assertEquals(updatedPicture.getDate(), readUpdatedPicture.getDate());
|
||||||
|
assertEquals(updatedPicture.getTitle(), readUpdatedPicture.getTitle());
|
||||||
|
assertEquals(updatedPicture.getLatitude(), readUpdatedPicture.getLatitude());
|
||||||
|
assertEquals(updatedPicture.getLongitude(), readUpdatedPicture.getLongitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> datasource.update(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateInexistent() {
|
||||||
|
Picture testPicture = createPicture("http://test.url/hallo.img", "Test picture");
|
||||||
|
testPicture.setId(INEXISTENT_ID);
|
||||||
|
assertThrows(RecordNotFoundException.class, () -> datasource.update(testPicture));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void count() {
|
||||||
|
assertEquals(3, datasource.count(), "Count for initial datasource not correct");
|
||||||
|
datasource.insert(createPicture("http://test.url/hallo.img", "Test picture"));
|
||||||
|
assertEquals(4, datasource.count(), "Count for updated datasource not correct");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findById() {
|
||||||
|
Picture foundPicture = datasource.findById(EXISTING_ID);
|
||||||
|
assertNotNull(foundPicture, "Picture not found");
|
||||||
|
assertEquals("2013-05-07 13:45:13", df.format(foundPicture.getDate()));
|
||||||
|
assertEquals("http://blog.stackoverflow.com/wp-content/uploads/code_monkey_colour.jpg",
|
||||||
|
foundPicture.getUrl().toExternalForm());
|
||||||
|
assertEquals("Need a coder", foundPicture.getTitle());
|
||||||
|
assertEquals(-71.098270, foundPicture.getLongitude(), 0.00001);
|
||||||
|
assertEquals(42.302583, foundPicture.getLatitude(), 0.00001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByIdInexistent() {
|
||||||
|
Picture foundPicture = datasource.findById(INEXISTENT_ID);
|
||||||
|
assertNull(foundPicture, "Inexistent Id found: " + INEXISTENT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findAll() {
|
||||||
|
Collection<Picture> pictures = datasource.findAll();
|
||||||
|
assertNotNull(pictures, "Collection of pictures must not be null");
|
||||||
|
assertEquals(countLines(), pictures.size(), "Number of records does not match number of found pictures");
|
||||||
|
for (Picture picture : pictures) {
|
||||||
|
assertNotNull(picture, "Found <null> picture in collection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findByPosition() {
|
||||||
|
Collection<Picture> pictures = datasource.findByPosition(-75, 41, 4);
|
||||||
|
assertNotNull(pictures);
|
||||||
|
assertEquals(2, pictures.size(), "Not correct amount of items found at position");
|
||||||
|
|
||||||
|
pictures = datasource.findByPosition(55, 23, 1);
|
||||||
|
assertEquals(0, pictures.size(), "Found items not to be found");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper methods
|
||||||
|
*/
|
||||||
|
private Picture createPicture(String url, String title) {
|
||||||
|
URL testURL = null;
|
||||||
|
try {
|
||||||
|
testURL = new URL(url);
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
fail("Invalid URL format", e);
|
||||||
|
}
|
||||||
|
float longitude = -180 + random.nextFloat() * 360; // range [-180..+180[
|
||||||
|
float latitude = -90 + random.nextFloat() * 180; // range [-90..+90[
|
||||||
|
return new Picture(testURL, new Date(), title, longitude, latitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readLineNo(int lineNo) throws IOException {
|
||||||
|
try (Stream<String> lineStream = Files.lines(dbPath, CHARSET)) {
|
||||||
|
return lineStream.skip(lineNo-1).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readLineWithId(long id) throws IOException {
|
||||||
|
try (Stream<String> lineStream = Files.lines(dbPath, CHARSET)) {
|
||||||
|
return lineStream.filter(line -> line.strip().startsWith(id+DELIMITER)).findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long countLines() {
|
||||||
|
try (Stream<String> lineStream = Files.lines(dbPath, CHARSET)) {
|
||||||
|
return lineStream.filter(Predicate.not(String::isBlank)).count();
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail("Failed to count lines in db file", e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String pictureToCsvLine(Picture picture) {
|
||||||
|
assertNotNull(picture, "Picture must not be null");
|
||||||
|
return String.join(DELIMITER,
|
||||||
|
String.valueOf(picture.getId()),
|
||||||
|
df.format(picture.getDate()),
|
||||||
|
String.valueOf(picture.getLongitude()),
|
||||||
|
String.valueOf(picture.getLatitude()),
|
||||||
|
picture.getTitle(),
|
||||||
|
picture.getUrl().toExternalForm()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
1;2014-03-17 14:30:05;2.324744;48.864506;Another Monkey;http://www.codemonkey.in/images/Code-Monkey.png
|
||||||
|
14;2014-04-01 02:17:33;77.598736;40.979842;Bête à coder;http://www2.craven.fr/blojsom/resources/default/codemonkey.jpg
|
||||||
|
13;2013-05-07 13:45:13;-71.098270;42.302583;Need a coder;http://blog.stackoverflow.com/wp-content/uploads/code_monkey_colour.jpg
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Used to set properties for gradle builds
|
||||||
|
# (see https://dev.to/jmfayard/configuring-gradle-with-gradle-properties-211k)
|
||||||
|
|
||||||
|
# gradle configuration
|
||||||
|
# (https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties)
|
||||||
|
#org.gradle.warning.mode=(all,fail,summary,none) default: summary
|
||||||
|
org.gradle.warning.mode=all
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Dynamic Multi-Module project structure
|
||||||
|
* automatically adds each exercise as a sub-project (module)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// use current directory name as root project name
|
||||||
|
rootProject.name = file('.').name
|
||||||
|
|
||||||
|
// dynamically add sub-projects in handout folder
|
||||||
|
File handoutDir = file('code')
|
||||||
|
if (handoutDir.isDirectory()) {
|
||||||
|
handoutDir.eachDir { dir ->
|
||||||
|
String subProjectName = ":${dir.name}"
|
||||||
|
include(subProjectName)
|
||||||
|
project(subProjectName).projectDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamically add sub-projects in solutions* folders
|
||||||
|
//List<File> solutionDirs = List.of(file('.').listFiles((File dir, String name) -> name.startsWith("solutions")))
|
||||||
|
file('.').eachDirMatch( name -> name.startsWith('solutions')) { solutionDir ->
|
||||||
|
if (solutionDir.isDirectory()) {
|
||||||
|
solutionDir.eachDir { dir ->
|
||||||
|
if (!dir.name.equals('images')) {
|
||||||
|
String subProjectName = ":${dir.name}-sol"
|
||||||
|
include(subProjectName)
|
||||||
|
project(subProjectName).projectDir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lab preparation tasks
|
||||||
|
File classroomDir = file('classroom')
|
||||||
|
if (classroomDir.isDirectory()) {
|
||||||
|
String subProjectName = ":${classroomDir.name}"
|
||||||
|
include(subProjectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: manually adding sub-project with name == folder
|
||||||
|
//include 'module1'
|
||||||
|
|
||||||
|
// Example: manually adding sub-projects with different name & folder
|
||||||
|
//include(':lab00-module1')
|
||||||
|
//project(':lab00-module1').projectDir = file('handout/module1')
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 ByteCharStream Solution'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.FileCopy'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
ENGINE
|
||||||
|
Engine 449cc, 4-stroke, liquid-cooled, single cylinder, DOHC
|
||||||
|
Bore Stroke 96.0 x 62.1 mm (3.78 x 2.4 in)
|
||||||
|
Compression Ratio 12.5:1
|
||||||
|
Fuel System Fuel Injection
|
||||||
|
Starter Primary kick
|
||||||
|
Lubrication Semi-dry sump
|
||||||
|
DRIVE TRAIN
|
||||||
|
Transmission 5-speed constant mesh
|
||||||
|
Clutch Wet multi-plate type, manual release
|
||||||
|
Final Drive Chain, DID520MXV4, 114 links
|
||||||
|
CHASSIS
|
||||||
|
Suspension Front Inverted telescopic, air spring, oil damped
|
||||||
|
Suspension Rear Link type, coil spring, oil damped
|
||||||
|
Brakes Front Disc brake, single rotor
|
||||||
|
Brakes Rear Disc brake, single rotor
|
||||||
|
Tires Front 80/100-21 51M, tube type
|
||||||
|
Tires Rear 110/90-19 62M, tube type
|
||||||
|
Fuel Tank Capacity 6.2 L (1.6 US gallons)
|
||||||
|
Color Champion Yellow No.2 / Solid Black
|
||||||
|
ELECTRICAL
|
||||||
|
Ignition Electronic Ignition (CDI)
|
||||||
|
DIMENSIONS
|
||||||
|
Overall Length 2190 mm (86.2 in)
|
||||||
|
Overall Width 830 mm (32.7 in)
|
||||||
|
Overall Height 1270 mm (50.0 in)
|
||||||
|
Wheelbase 1495 mm (58.9 in)
|
||||||
|
Ground Clearance 325 mm (12.8 in)
|
||||||
|
Seat Height 955 mm (37.6 in)
|
||||||
|
Curb Weight 112 kg (247 lbs)
|
Binary file not shown.
After Width: | Height: | Size: 134 KiB |
|
@ -0,0 +1,111 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class FileCopy {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
|
||||||
|
// get the filename from the arguments. By default, use 'files'-directory in current working directory.
|
||||||
|
String sourceDirPath = args.length >= 1 ? args[0] : "./files";
|
||||||
|
File sourceDir = new File(sourceDirPath);
|
||||||
|
|
||||||
|
// Part a – Verify the directory structure
|
||||||
|
// Implement the method 'verifySourceDir()'
|
||||||
|
System.out.format("Verifying Source Directory: %s%n", sourceDirPath);
|
||||||
|
try {
|
||||||
|
verifySourceDir(sourceDir);
|
||||||
|
} catch (FileNotFoundException error) {
|
||||||
|
System.err.format("Directory %s does not comply with predefined structure: %s%n", sourceDir.getPath(), error.getMessage());
|
||||||
|
System.err.println("Terminating programm!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
System.out.printf("Source Directory verified successfully.");
|
||||||
|
|
||||||
|
|
||||||
|
// Part b – Copy the files byte resp. char wise.
|
||||||
|
// Implement the method 'verifySourceDir()'
|
||||||
|
System.out.println("Initiating file copies.");
|
||||||
|
try {
|
||||||
|
copyFiles(sourceDir);
|
||||||
|
} catch (IOException error) {
|
||||||
|
System.err.format("Error creating file copies: %s%n", error.getMessage());
|
||||||
|
System.err.println("Terminating programm!");
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
System.out.println("Files copied successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Part a – directory structure
|
||||||
|
*
|
||||||
|
* Verify the directory structure for correctness.
|
||||||
|
* Correct means, that the directory exists and beside the two files rmz450.jpg and rmz450-spec.txt does not contain
|
||||||
|
* any other files or directories.
|
||||||
|
*
|
||||||
|
* @param sourceDir File source directory to verify it contains the correct structure
|
||||||
|
* @throws FileNotFoundException if the source directory or required file are missing
|
||||||
|
* @throws InvalidObjectException if the source directory contains invalid files or directories.
|
||||||
|
*/
|
||||||
|
private static void verifySourceDir(File sourceDir) throws FileNotFoundException, InvalidObjectException {
|
||||||
|
if (sourceDir.isDirectory()) {
|
||||||
|
System.out.format("Directory %s exists.", sourceDir.getPath());
|
||||||
|
} else {
|
||||||
|
throw new FileNotFoundException("Directory %s does not exist.".formatted(sourceDir.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutable list of required files
|
||||||
|
List<String> requiredFiles = new ArrayList<>(List.of("rmz450.jpg", "rmz450-spec.txt" ));
|
||||||
|
|
||||||
|
// check all files in the directory
|
||||||
|
for (File file : sourceDir.listFiles()) {
|
||||||
|
// if found, remove file from required list, otherwise throw an error (invalid file)
|
||||||
|
if (!requiredFiles.remove(file.getName())) {
|
||||||
|
throw new InvalidObjectException("Directory %s contains invalid element %s".formatted(sourceDir.getPath(), file.getName()));
|
||||||
|
}
|
||||||
|
// check for valid file type
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
throw new InvalidObjectException("File %s is a directory. Must be a standard file.".formatted(file.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// verify all required files are available
|
||||||
|
if (!requiredFiles.isEmpty()) {
|
||||||
|
throw new FileNotFoundException("Required file(s) not found:" + String.join(", ", requiredFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teilaufgabe b – Kopieren von Dateien
|
||||||
|
*
|
||||||
|
* Copies each file of the source directory twice, once character-oriented and once byte-oriented.
|
||||||
|
* Source and target files should be opened and copied byte by byte respectively char by char.
|
||||||
|
* The target files should be named, so the type of copy can be identified.
|
||||||
|
*
|
||||||
|
* @param sourceDir File representing the source directory containing the files to copy
|
||||||
|
* @throws IOException if an error is happening while copying the files
|
||||||
|
*/
|
||||||
|
private static void copyFiles(File sourceDir) throws IOException {
|
||||||
|
Objects.requireNonNull(sourceDir, "Source directory must not be null");
|
||||||
|
for (File file : sourceDir.listFiles()) {
|
||||||
|
try (FileInputStream inputStream = new FileInputStream(file);
|
||||||
|
FileOutputStream outputStream = new FileOutputStream("copy-bin-" + file.getName());
|
||||||
|
FileReader reader = new FileReader(file);
|
||||||
|
FileWriter writer = new FileWriter("copy-char-" + file.getName()))
|
||||||
|
{
|
||||||
|
int byteValue;
|
||||||
|
System.out.println("Binary copy.");
|
||||||
|
while ((byteValue = inputStream.read()) >= 0) {
|
||||||
|
outputStream.write(byteValue);
|
||||||
|
}
|
||||||
|
int charValue;
|
||||||
|
System.out.println("Character-oriented copy.");
|
||||||
|
while ((charValue = reader.read()) >= 0) {
|
||||||
|
writer.write(charValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
a
|
||||||
|
|
||||||
|
B
|
||||||
|
|
||||||
|
c
|
||||||
|
|
||||||
|
d
|
||||||
|
|
||||||
|
€
|
||||||
|
|
||||||
|
f
|
||||||
|
|
||||||
|
ü
|
||||||
|
|
||||||
|
_
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 UnderstandingCharsets Solution'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.UnderstandingCharsets'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
public class UnderstandingCharsets {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
/* Teilaufgabe a
|
||||||
|
* In der Vorlesung haben Sie gelernt, dass Java-Klassen fuer Unicode entworfen wurden.
|
||||||
|
* Nun ist Unicode aber nicht der einzige Zeichensatz und Java unterstuetz durchaus Alternativen.
|
||||||
|
* Welche Zeichensaetze auf einem System konkret unterstuetzt werden haengt von der Konfiguration des Betriebssystems JVM ab.
|
||||||
|
* Schreiben Sie ein Programm, welches alle Unterstuetzten Zeichensaetze auf der Konsole (System.out) ausgibt,
|
||||||
|
* zusammen mit dem Standardzeichensatz.
|
||||||
|
* https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Print default character set
|
||||||
|
System.out.println("Default Charset = " + Charset.defaultCharset());
|
||||||
|
|
||||||
|
// Print all available character sets
|
||||||
|
System.out.println("Available Charsets:");
|
||||||
|
for (Charset charset : Charset.availableCharsets().values()) {
|
||||||
|
System.out.println("- " + charset);
|
||||||
|
}
|
||||||
|
/* Ende Teilaufgabe a */
|
||||||
|
|
||||||
|
|
||||||
|
/* Teilaufgabe b
|
||||||
|
* Schreiben Sie ein Program welches im Standardzeichensatz einzele Zeichen (also Zeichen fuer Zeichen) von der
|
||||||
|
* Konsole einliest und ebenso im Zeichensatz US_ASCII in eine Datei schreibt.
|
||||||
|
* Die Eingabe des Zeichens 'q' soll das Program ordentlich beenden.
|
||||||
|
* Die Datei soll "CharSetEvaluation.txt" genannt werden und wird entweder erzeugt oder wenn Sie bereits
|
||||||
|
* existiert, einfach geoeffnet und der Inhalt uebeschrieben werden.
|
||||||
|
* Lesen von der Konsole und Schreiben in die Datei soll leistungsoptimiert geschehen, also vom jeweiligen
|
||||||
|
* Input-/Output-Medium entkoppelt.
|
||||||
|
* Testen Sie Ihr Program mit den folgenden Eingabereihenfolge und Zeichen: a B c d € f _ q
|
||||||
|
* Oeffnen Sie die Textdatei nach Durchfuehrung des Programs mit einem Texteditor und erklaeren Sie das Ergebnis.
|
||||||
|
* Oeffnen Sie die Datei anschliessend mit einem HEX-Editor und vergleichen Sie.
|
||||||
|
*/
|
||||||
|
|
||||||
|
char c;
|
||||||
|
try (FileOutputStream fosDefault = new FileOutputStream("CharSetEvaluation_Default.txt");
|
||||||
|
FileOutputStream fosAscii = new FileOutputStream("CharSetEvaluation_ASCII.txt");
|
||||||
|
FileOutputStream fosUTF8 = new FileOutputStream("CharSetEvaluation_UTF8.txt");
|
||||||
|
FileOutputStream fosWin = new FileOutputStream("CharSetEvaluation_WIN1252.txt");
|
||||||
|
BufferedWriter bwdefault = new BufferedWriter(new OutputStreamWriter(fosDefault));
|
||||||
|
BufferedWriter bwascii = new BufferedWriter(new OutputStreamWriter(fosAscii, StandardCharsets.US_ASCII));
|
||||||
|
BufferedWriter bwutf8 = new BufferedWriter(new OutputStreamWriter(fosUTF8, StandardCharsets.UTF_8));
|
||||||
|
BufferedWriter bwwin = new BufferedWriter(new OutputStreamWriter(fosWin, Charset.forName("windows-1252")));
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)))
|
||||||
|
{
|
||||||
|
System.out.println("Enter characters, 'q' to quit."); // read characters
|
||||||
|
boolean reading = true;
|
||||||
|
while (reading) {
|
||||||
|
c = (char) br.read();
|
||||||
|
/* returns character read, as an integer (32bit) in the range 0 to 65535
|
||||||
|
(0x00-0xffff, 16bit), or -1 (0xffff-ffff) if the end of the stream has been reached */
|
||||||
|
if (c == '\n') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == 'q') {
|
||||||
|
System.out.println("The End");
|
||||||
|
reading = false;
|
||||||
|
} else {
|
||||||
|
System.out.println("== Output using Default Encoding");
|
||||||
|
bwdefault.write(c);
|
||||||
|
bwdefault.newLine();
|
||||||
|
bwdefault.flush();
|
||||||
|
System.out.println("== Output using ASCII Encoding");
|
||||||
|
bwascii.write(c);
|
||||||
|
bwascii.newLine();
|
||||||
|
bwascii.flush();
|
||||||
|
System.out.println("== Output using UTF-8 Encoding");
|
||||||
|
bwutf8.write(c);
|
||||||
|
bwutf8.newLine();
|
||||||
|
bwutf8.flush();
|
||||||
|
System.out.println("== Output using Windows-1252 Encoding");
|
||||||
|
bwwin.write(c);
|
||||||
|
bwwin.newLine();
|
||||||
|
bwwin.flush();
|
||||||
|
}
|
||||||
|
//int dummy = br.read(); //clear CRNL
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Abbruch wegen IO-Exception" + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Gradle build configuration for specific lab module / exercise
|
||||||
|
*/
|
||||||
|
// enabled plugins
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
// Apply the application plugin to add support for building a CLI application.
|
||||||
|
id 'application'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project/Module information
|
||||||
|
description = 'Lab05 FileAttributes Solution'
|
||||||
|
group = 'ch.zhaw.prog2'
|
||||||
|
version = '2022.1'
|
||||||
|
|
||||||
|
// Dependency configuration
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for Application plugin
|
||||||
|
application {
|
||||||
|
// Define the main class for the application.
|
||||||
|
mainClass = 'ch.zhaw.prog2.io.DirList'
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable console input when running with gradle
|
||||||
|
run {
|
||||||
|
standardInput = System.in
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Java plugin configuration
|
||||||
|
java {
|
||||||
|
// By default the Java version of the gradle process is used as source/target version.
|
||||||
|
// This can be overridden, to ensure a specific version. Enable only if required.
|
||||||
|
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
|
||||||
|
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
|
||||||
|
|
||||||
|
// Java compiler specific options
|
||||||
|
compileJava {
|
||||||
|
// source files should be UTF-8 encoded
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ch.zhaw.prog2.io;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
public class DirList {
|
||||||
|
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String pathName = args.length >= 1 ? args[0] : ".";
|
||||||
|
File file = new File(pathName);
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
for (File subFile : file.listFiles()) {
|
||||||
|
System.out.println(printFileMetadata(subFile));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println(printFileMetadata(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String printFileMetadata(File file) {
|
||||||
|
return String.format("%c%c%c%c%c %s %8d %s",
|
||||||
|
file.isDirectory()? 'd' : 'f',
|
||||||
|
file.canRead()? 'r' : '-',
|
||||||
|
file.canWrite()? 'w' : '-',
|
||||||
|
file.canExecute()? 'x' : '-',
|
||||||
|
file.isHidden()? 'h' : '-',
|
||||||
|
dateFormat.format(file.lastModified()),
|
||||||
|
file.length(),
|
||||||
|
file.getName());
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
|
@ -0,0 +1,127 @@
|
||||||
|
:source-highlighter: coderay
|
||||||
|
:icons: font
|
||||||
|
:experimental:
|
||||||
|
:!sectnums:
|
||||||
|
:imagesdir: ./images/
|
||||||
|
:handout: ./
|
||||||
|
|
||||||
|
:logo: IT.PROG2 -
|
||||||
|
ifdef::backend-html5[]
|
||||||
|
:logo: image:PROG2-300x300.png[IT.PROG2,100,100,role=right,fit=none,position=top right]
|
||||||
|
endif::[]
|
||||||
|
ifdef::backend-pdf[]
|
||||||
|
:logo:
|
||||||
|
endif::[]
|
||||||
|
ifdef::env-github[]
|
||||||
|
:tip-caption: :bulb:
|
||||||
|
:note-caption: :information_source:
|
||||||
|
:important-caption: :heavy_exclamation_mark:
|
||||||
|
:caution-caption: :fire:
|
||||||
|
:warning-caption: :warning:
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
= {logo} Lösungen zu den Übungsaufgaben Input / Output
|
||||||
|
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 2
|
||||||
|
// Beginn des Aufgabenblocks
|
||||||
|
|
||||||
|
== Dateien und Attribute [PU]
|
||||||
|
|
||||||
|
****
|
||||||
|
Siehe Musterlösung im link:{handout}/FileAttributes[FileAttributes] Modul
|
||||||
|
|
||||||
|
Dies ist ein gutes Beispiel, wie mit Hilfe der `java.io.File`-Klasse eine hierarchische Baumstruktur (Verzeichnisbaum) traversiert und Attribute der einzelnen Knoten (Dateien/Verzeichnisse) ausgelesen werden können.
|
||||||
|
Problemlos könnte die Anwendung erweitert werden und ein ganzer Teilbaum traversiert und ausgegeben. Dies könnte auch als rekursive Methode ausgeführt werden.
|
||||||
|
|
||||||
|
Alternativ könnte das Auslesen des Verzeichnisses und bestimmen der Dateiattribute auch mittels der statischen Methoden in `java.nio.file.Files` erfolgen. Anstelle der `File`-Objekte würde dann mit `Path`-Objekten gearbeitet und die Attribute dieser ermittelt.
|
||||||
|
`Files` erlaubt auch erweiterte Filesystemattribute abzufragen (erweiterte Berechtigungen, Links). +
|
||||||
|
Auch wenn Functional-Streams verwendet werden sollen (siehe PROG2 Functional-Programming), bietet `Files` mehr Möglichkeiten.
|
||||||
|
****
|
||||||
|
|
||||||
|
|
||||||
|
Was bedeuteten die Attribute Lesen (`r`), Schreiben (`w`) und Ausführen (`x`) bei einem Verzeichnis?
|
||||||
|
|
||||||
|
****
|
||||||
|
Ein Verzeichnis kann man sich als spezielle Datei vorstellen, welche eine Liste von Referenzen (`Inodes`) und Metainformationen (Name, Typ, Zugriffsrechte, Grösse, ...) zu den darin enthaltenen Dateien im Filesystem enthält (Inhaltsverzeichnis). +
|
||||||
|
Die Rechte auf Verzeichnisse können entsprechend der Zugriffsrechte auf diese Datei interpretiert werden:
|
||||||
|
|
||||||
|
Lesen (`r`):: Das Inhaltsverzeichnis kann gelesen werden, d.h. die Metadaten (Namen & Attribute) der Dateien abgefragt werden.
|
||||||
|
Schreiben (`w`):: Das Inhaltsverzeichnis kann geschrieben / verändert werden, d.h. es können neue Dateien erstellt oder Dateien gelöscht werden.
|
||||||
|
Ausführen (`x`):: Es ist möglich das Verzeichnis zu traversieren, d.h. man kann (z.B. mittels `cd`) ins Verzeichnis bzw. durch das Verzeichnis hindurch in Unterverzeichnisse gelangen.
|
||||||
|
****
|
||||||
|
|
||||||
|
== Verstehen von Zeichensätzen [PU]
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Ergänzen Sie in der Klasse `UnderstandingCharsets` den Code, um alle von der JVM unterstützten Zeichensätze auf der Konsole (`System.out`), sowie den für Ihr System definierten Standardzeichensatz auszugeben. +
|
||||||
|
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Siehe Musterlösung im link:{handout}/Charsets[Charsets] Modul - Teilaufgabe (a)
|
||||||
|
****
|
||||||
|
. Ergänzen Sie die Klasse so, dass sie einzelne Zeichen (also Zeichen für Zeichen) im Standardzeichensatz von der Konsole einliest und in zwei Dateien schreibt einmal im Standardzeichensatz und einmal im Zeichensatz `US-ASCII`.
|
||||||
|
* Die Eingabe des Zeichens `q` soll das Program ordentlich beenden.
|
||||||
|
* Die Dateien sollen `CharSetEvaluation_Default.txt` und `CharSetEvaluation_ASCII.txt` genannt werden und werden entweder erzeugt oder, falls sie bereits existieren, geöffnet und der Inhalt überschrieben.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Siehe Musterlösung im link:{handout}/Charsets[Charsets] Modul - Teilaufgabe (b)
|
||||||
|
|
||||||
|
Die Musterlösung wurde erweitert, dass sie neben dem Default und ASCII auch explizit Dateien mit den zwei häufigsten Default-Zeichensätze (UTF-8, Windows-1252) generiert.
|
||||||
|
****
|
||||||
|
* Testen Sie Ihr Program mit den folgenden Zeichen: a B c d € f ü _ q
|
||||||
|
* Öffnen Sie die Textdateien nach Ausführung des Programs mit einem Texteditor
|
||||||
|
und erklären Sie das Ergebnis.
|
||||||
|
* Öffnen Sie die Dateien anschliessend mit einem HEX-Viewer/Editor und vergleichen Sie.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Im Texteditor sieht man, dass das Zeichen in der Datei mit dem Standardzeichensatz korrekt dargestellt wird, bei der US-ASCII-Datei hingegen wurden die erweiterten Zeichen (€,ü) durch ein Platzhalterzeichen ? ersetzt, da diese in US-ASCII nicht enthalten sind.
|
||||||
|
|
||||||
|
Im Hex-Editor ist ersichtlich, dass in der Default-Codierung die Zeichen einen erweiterten Code haben. Entweder ist es ein Wert in der oberen Hälfte der Code-Page (Windows-1252), d.h. grösser 127~dec~ / 7F~hex~ (€ -> 80~hex~, ü -> FC~hex~) oder es wird gemäss UTF-8 Codierung zu mehreren Bytes erweitert (€ -> E2 82 AC~hex~, ü -> C3 BC~hex~)
|
||||||
|
****
|
||||||
|
|
||||||
|
|
||||||
|
== Byte- vs. Zeichenorientierte Streams [PU]
|
||||||
|
|
||||||
|
[loweralpha]
|
||||||
|
. Verzeichnis-Struktur verifizieren: Methode `verifySourceDir()`
|
||||||
|
* Das Quell-Verzeichnis soll auf Korrektheit überprüft werden.
|
||||||
|
* Korrekt bedeutet, dass das Verzeichnis existiert und ausser zwei Dateien mit den Namen
|
||||||
|
`rmz450.jpg` und `rmz450-spec.txt` nichts weiter enthält.
|
||||||
|
* Im Fehlerfall werden Exceptions geworfen.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Siehe Musterlösung im link:{handout}/ByteCharStream[ByteCharStream] Modul - Teilaufgabe (a)
|
||||||
|
****
|
||||||
|
. Dateien kopieren: Methode `copyFiles()`
|
||||||
|
- Jede Datei im Quell-Verzeichnis soll zweimal kopiert werden, einmal zeichen- und einmal byte-orientiert.
|
||||||
|
- Dazu soll die jeweilige Datei geöffnet und Element für Element (d.h. byte- bzw. charakterweise) von der Originaldatei gelesen und in die Zieldatei geschrieben werden.
|
||||||
|
- Die Kopien sollen so benannt werden, dass aus dem Dateinamen hervorgeht, mit welcher Methode sie erstellt wurde.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Siehe Musterlösung im link:{handout}/ByteCharStream[ByteCharStream] Modul - Teilaufgabe (b)
|
||||||
|
****
|
||||||
|
. Öffnen Sie die Kopien anschliessend mit einem entsprechenden Programm und erklären Sie die entstandenen Effekte.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Die Textdokumente können mit jedem Editor geöffnet werden und sind identisch, egal ob sie als byte- oder char-Stream kopiert wurden.
|
||||||
|
Bei der JPG-Datei sieht das anders aus. Die als byte-stream kopierte Datei kann auch weiterhin mit einer Bildanwendung (z.B. Bildvorschau OS, IDE) geöffnet werden.
|
||||||
|
Bei der als character kopierten Datei verweigern die Anwendungen das Bild wegen Kodierfehler zu öffnen.
|
||||||
|
****
|
||||||
|
|
||||||
|
. Öffnen Sie die Kopien anschliessend mit einem HEX-Viewer/Editor und erklären Sie die Gründe für die Effekte.
|
||||||
|
+
|
||||||
|
****
|
||||||
|
Der Vergleich der kopierten JPG Dateien im Hex-Editor zeigt, dass die Bytes von Beginn an unterschiedlich sind.
|
||||||
|
|
||||||
|
Der Grund ist, dass einige Bitfolgen keine gültigen Zeichen aus der Code-Page darstellen.
|
||||||
|
Ungültige Zeichen werden in ein Standard-Zeichen für umgewandelt (bei UTF-8 ist das '<27>' resp. 'EF BF BD~hex~')
|
||||||
|
Damit wird die Datei verändert und entspricht nicht mehr der für JPG gültigen Kodierung.
|
||||||
|
****
|
||||||
|
|
||||||
|
|
||||||
|
== Picture File Datasource [PA]
|
||||||
|
|
||||||
|
****
|
||||||
|
Die Lösungen zu den bewerteten Pflichtaufgaben erhalten Sie nach der Abgabe und Bewertung aller Klassen.
|
||||||
|
****
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue