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
+68
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
}
}
+3
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
+42
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
@@ -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
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