#25 Initial implementation of the JsonGardenPlan

Updated and renamed the Crop type to accommodate storage
This commit is contained in:
David Guler 2022-10-24 19:19:45 +02:00
parent aceb6aa1e6
commit 72b0d029d5
5 changed files with 212 additions and 14 deletions

View File

@ -1,12 +1,42 @@
package ch.zhaw.gartenverwaltung.io; package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.UserPlanting; import ch.zhaw.gartenverwaltung.types.Crop;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface GardenPlan { public interface GardenPlan {
List<UserPlanting> getPlantings(); /**
void savePlanting(UserPlanting planting) throws IOException; * Yields a list of all {@link Crop}s in the database.
void removePlanting(UserPlanting planting) throws IOException; *
* @return A list of all {@link Crop}s in the database
* @throws IOException If there is a problem reading from or writing to the database
*/
List<Crop> getCrops() throws IOException;
/**
* Attempts to retrieve the {@link Crop} with the specified cropId.
*
* @param id The {@link Crop#cropId} to look for
* @return {@link Optional} of the found {@link Crop}, {@link Optional#empty()} if no entry matched the criteria
* @throws IOException If there is a problem reading from or writing to the database
*/
Optional<Crop> getCropById(long id) throws IOException;
/**
* Saves a Crop to the Database.
*
* @param crop The {@link Crop} to be saved
* @throws IOException If there is a problem reading from or writing to the database
*/
void saveCrop(Crop crop) throws IOException;
/**
* Removes a Crop from the Database.
*
* @param crop The {@link Crop} to be removed
* @throws IOException If there is a problem reading from or writing to the database
*/
void removeCrop(Crop crop) throws IOException;
} }

View File

@ -0,0 +1,9 @@
package ch.zhaw.gartenverwaltung.io;
import java.io.IOException;
class InvalidJsonException extends IOException {
public InvalidJsonException(String reason) {
super(reason);
}
}

View File

@ -0,0 +1,134 @@
package ch.zhaw.gartenverwaltung.io;
import ch.zhaw.gartenverwaltung.types.Crop;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JsonGardenPlan implements GardenPlan {
private final URL dataSource = getClass().getResource("user-crops.json");
private IdProvider idProvider;
private Map<Long, Crop> cropMap = Collections.emptyMap();
/**
* Creating constant objects required to deserialize the {@link LocalDate} classes
*/
private final static JavaTimeModule timeModule = new JavaTimeModule();
private final static String INVALID_DATASOURCE_MSG = "Invalid datasource specified!";
static {
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yy-MM-dd");
LocalDateDeserializer dateDeserializer = new LocalDateDeserializer(dateFormat);
timeModule.addDeserializer(LocalDate.class, dateDeserializer);
}
/**
* @see GardenPlan#getCrops()
*/
@Override
public List<Crop> getCrops() throws IOException {
if (idProvider == null) {
loadCropList();
}
return cropMap.values().stream().toList();
}
/**
* @see GardenPlan#getCropById(long)
*/
@Override
public Optional<Crop> getCropById(long id) throws IOException {
if (idProvider == null) {
loadCropList();
}
return Optional.ofNullable(cropMap.get(id));
}
/**
* @see GardenPlan#saveCrop(Crop)
*
* Saves a crop to the database.
* If no {@link Crop#cropId} is set, one will be generated and
* the Crop is stored as a new entry.
*/
@Override
public void saveCrop(Crop crop) throws IOException {
if (idProvider == null) {
loadCropList();
}
Long cropId = crop.getCropId().orElse(idProvider.incrementAndGet());
cropMap.put(cropId, crop.withId(cropId));
writeCropList();
}
/**
* @see GardenPlan#removeCrop(Crop)
*/
@Override
public void removeCrop(Crop crop) throws IOException {
if (idProvider == null) {
loadCropList();
}
Optional<Long> cropId = crop.getCropId();
if (cropId.isPresent()) {
cropMap.remove(cropId.get());
writeCropList();
}
}
/**
* Loads the database from {@link #dataSource} and updates the cached data.
*
* @throws IOException If the database cannot be accessed or invalid data was read.
*/
private void loadCropList() throws IOException {
if (dataSource != null) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(timeModule);
List<Crop> result;
result = mapper.readerForListOf(Crop.class).readValue(dataSource);
// Turn list into a HashMap with structure cropId => Crop
cropMap = new HashMap<>(result.size());
for (Crop crop : result) {
Long id = crop.getCropId()
.orElseThrow(() -> new InvalidJsonException(String.format("Invalid value for \"cropId\" in %s!", crop)));
cropMap.put(id, crop);
}
Long maxId = cropMap.isEmpty() ? 0L : Collections.max(cropMap.keySet());
idProvider = new IdProvider(maxId);
}
}
/**
* Writes the values from {@link #cropMap} to the {@link #dataSource}
*
* @throws IOException If the database cannot be accessed
*/
private void writeCropList() throws IOException {
if (dataSource != null) {
try {
new ObjectMapper()
.registerModule(timeModule)
.writeValue(new File(dataSource.toURI()), cropMap.values());
} catch (URISyntaxException e) {
// TODO: Log
throw new IOException(INVALID_DATASOURCE_MSG, e);
}
}
}
}

View File

@ -0,0 +1,35 @@
package ch.zhaw.gartenverwaltung.types;
import java.time.LocalDate;
import java.util.Optional;
public class Crop {
private Long cropId = null;
private final long plantId;
private final LocalDate startDate;
private int area = 1;
public Crop(long plantId, LocalDate startDate) {
this.plantId = plantId;
this.startDate = startDate;
}
// Builder-Style setter
public Crop withId(long cropId) {
this.cropId = cropId;
return this;
}
public Crop withArea(int area) {
this.area = area;
return this;
}
// Getters
public Optional<Long> getCropId() {
return Optional.ofNullable(cropId);
}
public long getPlantId() { return plantId; }
public LocalDate getStartDate() { return startDate; }
public int getArea() { return area; }
}

View File

@ -1,10 +0,0 @@
package ch.zhaw.gartenverwaltung.types;
import java.util.Date;
public record UserPlanting(
long plantId,
Date startDate,
int area
) {
}