#25 Initial implementation of the JsonGardenPlan
Updated and renamed the Crop type to accommodate storage
This commit is contained in:
parent
aceb6aa1e6
commit
72b0d029d5
|
@ -1,12 +1,42 @@
|
|||
package ch.zhaw.gartenverwaltung.io;
|
||||
|
||||
import ch.zhaw.gartenverwaltung.types.UserPlanting;
|
||||
import ch.zhaw.gartenverwaltung.types.Crop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface GardenPlan {
|
||||
List<UserPlanting> getPlantings();
|
||||
void savePlanting(UserPlanting planting) throws IOException;
|
||||
void removePlanting(UserPlanting planting) throws IOException;
|
||||
/**
|
||||
* Yields a list of all {@link Crop}s in the database.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package ch.zhaw.gartenverwaltung.io;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class InvalidJsonException extends IOException {
|
||||
public InvalidJsonException(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package ch.zhaw.gartenverwaltung.types;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public record UserPlanting(
|
||||
long plantId,
|
||||
Date startDate,
|
||||
int area
|
||||
) {
|
||||
}
|
Loading…
Reference in New Issue