Initial commit

This commit is contained in:
github-classroom[bot] 2022-04-28 08:39:46 +00:00
commit 2a34df16f4
47 changed files with 6848 additions and 0 deletions

29
.editorconfig Normal file
View File

@ -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

5
.gitattributes vendored Normal file
View File

@ -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

14
.github/classroom/autograding.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"tests": [
{
"name": "Run PA unit tests",
"setup": "",
"run": "gradle :PictureDB:test --info",
"input": "",
"output": "",
"comparison": "included",
"timeout": 10,
"points": 2
}
]
}

15
.github/workflows/classroom.yml vendored Normal file
View File

@ -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

77
.gitignore vendored Normal file
View File

@ -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

266
README.adoc Normal file
View File

@ -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.

View File

@ -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
}
}

View File

@ -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

View File

@ -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 {
}
}

View File

@ -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
}
}

View File

@ -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.
*/
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View 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
}
}

View File

@ -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
1 1 2014-03-17 14:30:05 2.324744 48.864506 Another Monkey http://www.codemonkey.in/images/Code-Monkey.png
2 13 2014-04-01 02:17:33 77.598736 12.979842 Bête à coder http://www2.craven.fr/blojsom/resources/default/codemonkey.jpg
3 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

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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);
}

View File

@ -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 "";
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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()
);
}
}

View File

@ -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
1 1 2014-03-17 14:30:05 2.324744 48.864506 Another Monkey http://www.codemonkey.in/images/Code-Monkey.png
2 14 2014-04-01 02:17:33 77.598736 40.979842 Bête à coder http://www2.craven.fr/blojsom/resources/default/codemonkey.jpg
3 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

7
gradle.properties Normal file
View File

@ -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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

234
gradlew vendored Executable file
View File

@ -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" "$@"

89
gradlew.bat vendored Normal file
View File

@ -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

BIN
images/PROG2-300x300.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

45
settings.gradle Normal file
View File

@ -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')

View File

@ -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
}
}

View File

@ -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

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,16 @@
a
B
c
d
f
ü
_

View File

@ -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
}
}

View File

@ -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();
}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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