diff --git a/README.md b/README.md index c25cad20..2f70a292 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ A time recording tool [![Build](https://github.com/itsallcode/white-rabbit/workflows/Build/badge.svg)](https://github.com/itsallcode/white-rabbit/actions?query=workflow%3ABuild) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=white-rabbit&metric=alert_status)](https://sonarcloud.io/dashboard?id=white-rabbit) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=white-rabbit&metric=coverage)](https://sonarcloud.io/dashboard?id=white-rabbit) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode.whiterabbit%3Awhite-rabbit&metric=alert_status)](https://sonarcloud.io/dashboard?id=org.itsallcode.whiterabbit%3Awhite-rabbit) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode.whiterabbit%3Awhite-rabbit&metric=coverage)](https://sonarcloud.io/dashboard?id=org.itsallcode.whiterabbit%3Awhite-rabbit) * [Features](#features) * [Usage](#usage) diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/Plugin.java b/api/src/main/java/org/itsallcode/whiterabbit/api/Plugin.java index 1704a9df..4e86e64f 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/Plugin.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/Plugin.java @@ -1,14 +1,64 @@ package org.itsallcode.whiterabbit.api; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.features.PluginFeature; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; + +/** + * Implement this interface to create a plugin for WhiteRabbit. Register your + * plugin by adding file + * {@code META-INF/services/org.itsallcode.whiterabbit.api.Plugin} containing + * the full qualified class name to the jar. + * + * Available features: + * + */ public interface Plugin { + /** + * Called once when loading the plugin. + * + * @param config + * the configuration of the plugin. + */ void init(PluginConfiguration config); + /** + * The ID of this plugin. IDs must be unique for all plugins. The ID is used + * as prefix for {@link PluginConfiguration#getMandatoryValue(String)}. + * + * @return the ID of this plugin. + */ String getId(); - void close(); + /** + * Check if this plugin supports the given feature. + * + * @param featureType + * the feature type. + * @return true if this plugin supports the given feature, else + * false. + */ + boolean supports(Class featureType); - boolean supports(Class featureType); + /** + * Get an instance of the given feature type. + * + * @param featureType + * the feature type. + * @param + * the type of the feature. + * @return the instance of the given feature. May return null + * or throw an exception if the feature is not supported. + */ + T getFeature(Class featureType); - T getFeature(Class featureType); + /** + * Called before closing the plugin. The plugin should cleanup any resources + * in this method. + */ + void close(); } diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/PluginConfiguration.java b/api/src/main/java/org/itsallcode/whiterabbit/api/PluginConfiguration.java index fc882823..05ba3caa 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/PluginConfiguration.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/PluginConfiguration.java @@ -1,6 +1,21 @@ package org.itsallcode.whiterabbit.api; +/** + * The configuration of a {@link Plugin} that allows retrieving configuration + * values from the WhiteRabbit properties file + * {@code ~/.whiterabbit.properties}. + */ public interface PluginConfiguration { - String getMandatoryValue(String string); + /** + * Get property {@code pluginId.propertyName} from the config file. Throws + * an exception if the property is not available. + * + * @param propertyName + * the property name to get. + * @return the value of the property. + * @throws RuntimeException + * if the property is not available in the config file. + */ + String getMandatoryValue(String propertyName); } diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/ProgressMonitor.java b/api/src/main/java/org/itsallcode/whiterabbit/api/ProgressMonitor.java deleted file mode 100644 index 9a9425e3..00000000 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/ProgressMonitor.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.itsallcode.whiterabbit.api; - -public interface ProgressMonitor -{ - boolean isCanceled(); - - void beginTask(String name, int totalWork); - - void setTaskName(String name); - - void worked(int work); -} diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/ProjectReportExporter.java b/api/src/main/java/org/itsallcode/whiterabbit/api/ProjectReportExporter.java deleted file mode 100644 index e55263dc..00000000 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/ProjectReportExporter.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.itsallcode.whiterabbit.api; - -import org.itsallcode.whiterabbit.api.model.ProjectReport; - -public interface ProjectReportExporter -{ - void export(ProjectReport report, ProgressMonitor progressMonitor); -} diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/features/MonthDataStorage.java b/api/src/main/java/org/itsallcode/whiterabbit/api/features/MonthDataStorage.java new file mode 100644 index 00000000..df376ae4 --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/features/MonthDataStorage.java @@ -0,0 +1,71 @@ +package org.itsallcode.whiterabbit.api.features; + +import java.time.YearMonth; +import java.util.List; +import java.util.Optional; + +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; +import org.itsallcode.whiterabbit.api.model.MonthData; + +/** + * {@link PluginFeature} that provides a storage backend for month data. This + * class may keep the data on the local disk or on a backend server. + * {@link YearMonth} is used as key for loading and storing entries. + */ +public interface MonthDataStorage extends PluginFeature +{ + /** + * @param month + * the month for which to load the data. + * @return the data for the given month. + */ + Optional load(YearMonth month); + + /** + * Store the data for a given month. + * + * @param month + * the month. + * @param data + * the data to store. + */ + void store(YearMonth month, MonthData data); + + /** + * @return all months for that data is available in the store. + */ + List getAvailableDataMonths(); + + /** + * @return all available data. + */ + List loadAll(); + + /** + * @return the model factory that can be used to create instances of the + * model. + */ + ModelFactory getModelFactory(); + + /** + * A {@link ModelFactory} allows creating new instances of the data model. + */ + public interface ModelFactory + { + /** + * @return a new {@link MonthData} instance. + */ + MonthData createMonthData(); + + /** + * @return a new {@link DayData} instance. + */ + DayData createDayData(); + + /** + * @return a new {@link ActivityData} instance. + */ + ActivityData createActivityData(); + } +} \ No newline at end of file diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/features/PluginFeature.java b/api/src/main/java/org/itsallcode/whiterabbit/api/features/PluginFeature.java new file mode 100644 index 00000000..85060dac --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/features/PluginFeature.java @@ -0,0 +1,11 @@ +package org.itsallcode.whiterabbit.api.features; + +import org.itsallcode.whiterabbit.api.Plugin; + +/** + * Super interface for all features supported by a {@link Plugin}. + */ +public interface PluginFeature +{ + +} diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProgressMonitor.java b/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProgressMonitor.java new file mode 100644 index 00000000..28143ab5 --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProgressMonitor.java @@ -0,0 +1,41 @@ +package org.itsallcode.whiterabbit.api.features; + +/** + * A progress monitor passed to a {@link PluginFeature} that allows reporting + * the progress of process. + */ +public interface ProgressMonitor +{ + /** + * Call this method repeatedly to check, if the user wants to cancel the + * process. Abort the process if this returns true. + * + * @return true if the user has cancelled the process. + */ + boolean isCanceled(); + + /** + * Notifies that the main task is beginning. + * + * @param name + * the name of the current task shown to the user. + * @param totalWork + * the total number of work units that will be done by the task. + */ + void beginTask(String name, int totalWork); + + /** + * @param name + * the name of the current task shown to the user. + */ + void setTaskName(String name); + + /** + * Notifies that a given number of work unit of the main task has been + * completed. + * + * @param work + * the number of work units just completed. + */ + void worked(int work); +} diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProjectReportExporter.java b/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProjectReportExporter.java new file mode 100644 index 00000000..b29afbae --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/features/ProjectReportExporter.java @@ -0,0 +1,20 @@ +package org.itsallcode.whiterabbit.api.features; + +import org.itsallcode.whiterabbit.api.model.ProjectReport; + +/** + * A {@link PluginFeature} that allows exporting a monthly {@link ProjectReport} + * to another system. + */ +public interface ProjectReportExporter extends PluginFeature +{ + /** + * Start the export. + * + * @param report + * the report to export. + * @param progressMonitor + * a progress monitor for reporting the export progress. + */ + void export(ProjectReport report, ProgressMonitor progressMonitor); +} diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ActivityData.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ActivityData.java new file mode 100644 index 00000000..1d477e17 --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ActivityData.java @@ -0,0 +1,24 @@ +package org.itsallcode.whiterabbit.api.model; + +import java.time.Duration; + +/** + * Model for a project activity on a given {@link DayData day} including project + * id, duration and comment. + */ +public interface ActivityData +{ + String getProjectId(); + + void setProjectId(String id); + + Duration getDuration(); + + boolean isRemainder(); + + void setDuration(Duration duration); + + String getComment(); + + void setComment(String comment); +} \ No newline at end of file diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayData.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayData.java new file mode 100644 index 00000000..1fbe72a3 --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayData.java @@ -0,0 +1,45 @@ +package org.itsallcode.whiterabbit.api.model; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +/** + * Model class representing a day with date, time for begin and end of work, + * activities etc. + */ +public interface DayData +{ + LocalDate getDate(); + + DayType getType(); + + LocalTime getBegin(); + + LocalTime getEnd(); + + Duration getInterruption(); + + Duration getWorkingHours(); + + String getComment(); + + void setDate(LocalDate date); + + void setType(DayType type); + + void setBegin(LocalTime begin); + + void setEnd(LocalTime end); + + void setWorkingHours(Duration workingHours); + + void setComment(String comment); + + void setInterruption(Duration interruption); + + List getActivities(); + + void setActivities(List activities); +} \ No newline at end of file diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayType.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayType.java index 323a24a8..24f9e152 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayType.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/DayType.java @@ -1,5 +1,8 @@ package org.itsallcode.whiterabbit.api.model; +/** + * The type of a {@link DayData day}. + */ public enum DayType { HOLIDAY(false), VACATION(false), FLEX_TIME(true), SICK(false), WORK(true), WEEKEND(false); @@ -11,6 +14,9 @@ public enum DayType this.workDay = workDay; } + /** + * @return true if you need to work on a day of this type. + */ public boolean isWorkDay() { return workDay; diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/MonthData.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/MonthData.java new file mode 100644 index 00000000..af40d9e6 --- /dev/null +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/MonthData.java @@ -0,0 +1,27 @@ +package org.itsallcode.whiterabbit.api.model; + +import java.time.Duration; +import java.time.Month; +import java.util.List; + +/** + * Model class for data of a single month containing year, month, day data etc. + */ +public interface MonthData +{ + int getYear(); + + void setYear(int year); + + Month getMonth(); + + void setMonth(Month month); + + Duration getOvertimePreviousMonth(); + + void setOvertimePreviousMonth(Duration overtimePreviousMonth); + + List getDays(); + + void setDays(List days); +} \ No newline at end of file diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/Project.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/Project.java index 56c9d721..ae7d836e 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/model/Project.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/Project.java @@ -1,5 +1,9 @@ package org.itsallcode.whiterabbit.api.model; +/** + * The project you work on during an {@link ProjectReportActivity activity} + * containing an ID, label and cost carrier id. + */ public interface Project { String getProjectId(); diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReport.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReport.java index e4c4e2e5..92bebde8 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReport.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReport.java @@ -3,6 +3,9 @@ import java.time.YearMonth; import java.util.List; +/** + * A monthly project report. + */ public interface ProjectReport { YearMonth getMonth(); diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportActivity.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportActivity.java index a7ecf8cc..787d1127 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportActivity.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportActivity.java @@ -2,6 +2,10 @@ import java.time.Duration; +/** + * An activity during a {@link ProjectReportDay day} where you worked for a + * specific time duration on a {@link Project}. + */ public interface ProjectReportActivity { Project getProject(); diff --git a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportDay.java b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportDay.java index 3500d617..50dc8a60 100644 --- a/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportDay.java +++ b/api/src/main/java/org/itsallcode/whiterabbit/api/model/ProjectReportDay.java @@ -3,6 +3,10 @@ import java.time.LocalDate; import java.util.List; +/** + * The projects worked on during a single day of the {@link ProjectReport} + * including date and {@link ProjectReportActivity activities}. + */ public interface ProjectReportDay { LocalDate getDate(); diff --git a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/ProjectReportViewer.java b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/ProjectReportViewer.java index 26cbbb8d..36d5a37c 100644 --- a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/ProjectReportViewer.java +++ b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/ProjectReportViewer.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.itsallcode.whiterabbit.api.ProjectReportExporter; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; import org.itsallcode.whiterabbit.api.model.DayType; import org.itsallcode.whiterabbit.api.model.ProjectReport; import org.itsallcode.whiterabbit.api.model.ProjectReportActivity; diff --git a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/widget/ProgressDialog.java b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/widget/ProgressDialog.java index b054814a..b19d0d92 100644 --- a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/widget/ProgressDialog.java +++ b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/ui/widget/ProgressDialog.java @@ -2,7 +2,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.itsallcode.whiterabbit.api.ProgressMonitor; +import org.itsallcode.whiterabbit.api.features.ProgressMonitor; import org.itsallcode.whiterabbit.jfxui.JavaFxUtil; import org.itsallcode.whiterabbit.jfxui.ui.UiResources; diff --git a/jfxui/src/test/java/org/itsallcode/whiterabbit/jfxui/uistate/model/UiStateModelTest.java b/jfxui/src/test/java/org/itsallcode/whiterabbit/jfxui/uistate/model/UiStateModelTest.java index 3ace3917..3c5a380c 100644 --- a/jfxui/src/test/java/org/itsallcode/whiterabbit/jfxui/uistate/model/UiStateModelTest.java +++ b/jfxui/src/test/java/org/itsallcode/whiterabbit/jfxui/uistate/model/UiStateModelTest.java @@ -118,13 +118,11 @@ void deserializeSplitPaneState() private UiStateModel deserialize(String json) { - final Jsonb jsonb = JsonbBuilder.create(); return jsonb.fromJson(json, UiStateModel.class); } private void assertSerialized(String expectedJson) { - final Jsonb jsonb = JsonbBuilder.create(); final String json = jsonb.toJson(model); assertThat(json).isEqualTo(expectedJson); } diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTest.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTest.java index 3b84d7a5..f40f1270 100644 --- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTest.java +++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTest.java @@ -11,9 +11,9 @@ import java.time.YearMonth; import java.util.Locale; +import org.itsallcode.whiterabbit.api.model.DayData; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.jfxui.testutil.TestUtil; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testfx.api.FxRobot; @@ -97,12 +97,12 @@ void jsonFileWrittenAfterMinuteTick() time().tickMinute(); final LocalTime end = time().getCurrentTimeMinutes(); - final JsonMonth month = loadMonth(today); + final MonthData month = loadMonth(today); assertAll( () -> assertThat(month.getDays()).hasSize(1), - () -> assertThat(month.getDays()).extracting(JsonDay::getBegin).containsExactly(begin), - () -> assertThat(month.getDays()).extracting(JsonDay::getEnd).containsExactly(end)); + () -> assertThat(month.getDays()).extracting(DayData::getBegin).containsExactly(begin), + () -> assertThat(month.getDays()).extracting(DayData::getEnd).containsExactly(end)); } @Test diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTestBase.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTestBase.java index 6234203c..ab8ce20f 100644 --- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTestBase.java +++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/JavaFxAppUiTestBase.java @@ -18,17 +18,17 @@ import java.util.Objects; import javax.json.bind.Jsonb; -import javax.json.bind.JsonbBuilder; -import javax.json.bind.JsonbConfig; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.jfxui.testutil.TableRowExpectedContent; import org.itsallcode.whiterabbit.jfxui.testutil.TimeUtil; import org.itsallcode.whiterabbit.jfxui.testutil.model.ApplicationHelper; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; -import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; import org.itsallcode.whiterabbit.logic.service.project.ProjectConfig; +import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; +import org.itsallcode.whiterabbit.logic.storage.data.JsonMonth; +import org.itsallcode.whiterabbit.logic.storage.data.JsonbFactory; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.junit.jupiter.MockitoExtension; @@ -44,7 +44,7 @@ abstract class JavaFxAppUiTestBase { private static final Logger LOG = LogManager.getLogger(JavaFxAppUiTestBase.class); - private static final Jsonb JSONB = JsonbBuilder.create(new JsonbConfig().withFormatting(true)); + private static final Jsonb JSONB = new JsonbFactory().create(); private JavaFxApp javaFxApp; @@ -181,7 +181,7 @@ protected static ProjectImpl project(final String id, final String label) return project; } - protected JsonMonth loadMonth(final LocalDate date) + protected MonthData loadMonth(final LocalDate date) { final Path file = this.dataDir.resolve(String.valueOf(date.getYear())) .resolve(YearMonth.from(date).toString() + ".json"); diff --git a/logic/build.gradle b/logic/build.gradle index def2d479..e4a035a5 100644 --- a/logic/build.gradle +++ b/logic/build.gradle @@ -6,7 +6,7 @@ dependencies { api project(':api') implementation "org.apache.logging.log4j:log4j-api:$log4jVersion" implementation "javax.json.bind:javax.json.bind-api:${jsonBindApiVersion}" - runtimeOnly "org.eclipse:yasson:${yassonVersion}" + implementation "org.eclipse:yasson:${yassonVersion}" } ext.generatedResourcesFolder = file("$buildDir/generated-resources") diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/ConfigLoader.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/ConfigLoader.java index 02bcbe06..50f9325a 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/ConfigLoader.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/ConfigLoader.java @@ -13,8 +13,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; import org.itsallcode.whiterabbit.logic.service.project.ProjectConfig; +import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; public class ConfigLoader { diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/Activity.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/Activity.java index b25fd4b4..93b7620e 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/Activity.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/Activity.java @@ -3,19 +3,19 @@ import java.time.Duration; import java.util.Objects; +import org.itsallcode.whiterabbit.api.model.ActivityData; import org.itsallcode.whiterabbit.api.model.Project; -import org.itsallcode.whiterabbit.logic.model.json.JsonActivity; import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; public class Activity implements RowRecord { - final JsonActivity jsonActivity; + final ActivityData jsonActivity; private final DayActivities day; private final int index; private final ProjectService projectService; - public Activity(int index, JsonActivity jsonActivity, DayActivities day, ProjectService projectService) + public Activity(int index, ActivityData jsonActivity, DayActivities day, ProjectService projectService) { this.index = index; this.jsonActivity = jsonActivity; diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java index 57905e98..29200672 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java @@ -13,8 +13,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.itsallcode.whiterabbit.logic.model.json.JsonActivity; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; public class DayActivities @@ -22,11 +23,13 @@ public class DayActivities private static final Logger LOG = LogManager.getLogger(DayActivities.class); private final ProjectService projectService; - private final JsonDay day; + private final ModelFactory modelFactory; + private final DayData day; final DayRecord dayRecord; - DayActivities(DayRecord dayRecord, ProjectService projectService) + DayActivities(DayRecord dayRecord, ProjectService projectService, ModelFactory modelFactory) { + this.modelFactory = modelFactory; this.day = dayRecord.getJsonDay(); this.dayRecord = dayRecord; this.projectService = Objects.requireNonNull(projectService); @@ -40,7 +43,7 @@ public Activity add() } final boolean isFirstActivity = getActivities().findAny().isEmpty(); - final JsonActivity jsonActivity = new JsonActivity(); + final ActivityData jsonActivity = modelFactory.createActivityData(); jsonActivity.setProjectId(""); jsonActivity.setDuration(Duration.ZERO); final int newRowIndex = day.getActivities().size(); @@ -53,7 +56,7 @@ public Activity add() public List getAll() { - final List jsonActivities = getActivities().collect(toList()); + final List jsonActivities = getActivities().collect(toList()); return IntStream.range(0, jsonActivities.size()) .mapToObj(i -> wrapActivity(jsonActivities.get(i), i)) .collect(toList()); @@ -65,14 +68,14 @@ public boolean isEmpty() .findFirst().orElse(true); } - private Stream getActivities() + private Stream getActivities() { return Optional.ofNullable(day.getActivities()) .orElse(emptyList()) .stream(); } - private Activity wrapActivity(JsonActivity activity, int index) + private Activity wrapActivity(ActivityData activity, int index) { return new Activity(index, activity, this, projectService); } @@ -98,7 +101,7 @@ public void remove(int index) } } - public void setRemainderActivity(JsonActivity activity, boolean remainder) + public void setRemainderActivity(ActivityData activity, boolean remainder) { if (activity.isRemainder() == remainder) { @@ -113,9 +116,9 @@ public void setRemainderActivity(JsonActivity activity, boolean remainder) return; } - final List currentRemainders = getRemainderActivities(); + final List currentRemainders = getRemainderActivities(); LOG.debug("Found {} remainder activities: deactivate them", currentRemainders.size()); - for (final JsonActivity remainderActivity : currentRemainders) + for (final ActivityData remainderActivity : currentRemainders) { final Duration unallocatedDuration = getUnallocatedDuration(); remainderActivity.setDuration(unallocatedDuration); @@ -127,7 +130,7 @@ public void setRemainderActivity(JsonActivity activity, boolean remainder) private Duration getUnallocatedDuration() { - final Duration allocatedDuration = getActivities().map(JsonActivity::getDuration) + final Duration allocatedDuration = getActivities().map(ActivityData::getDuration) .filter(Objects::nonNull) .reduce((d1, d2) -> d1.plus(d2)) .orElse(Duration.ZERO); @@ -136,7 +139,7 @@ private Duration getUnallocatedDuration() public boolean isValidAllocation() { - final List remainderActivities = getActivities() + final List remainderActivities = getActivities() .filter(a -> a.getDuration() == null) .collect(toList()); if (remainderActivities.size() > 1) @@ -159,7 +162,7 @@ public boolean isValidAllocation() return true; } - private List getRemainderActivities() + private List getRemainderActivities() { return getActivities().filter(a -> a.getDuration() == null).collect(toList()); } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayRecord.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayRecord.java index 29bd7c6d..42dbc350 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayRecord.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayRecord.java @@ -8,8 +8,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.api.model.DayType; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; @@ -19,18 +20,20 @@ public class DayRecord implements RowRecord private final ContractTermsService contractTerms; private final ProjectService projectService; - private final JsonDay day; + private final ModelFactory modelFactory; + private final DayData day; private final MonthIndex month; private final DayRecord previousDay; - public DayRecord(ContractTermsService contractTerms, JsonDay day, DayRecord previousDay, MonthIndex month, - ProjectService projectService) + public DayRecord(ContractTermsService contractTerms, DayData day, DayRecord previousDay, MonthIndex month, + ProjectService projectService, ModelFactory modelFactory) { this.contractTerms = contractTerms; + this.projectService = Objects.requireNonNull(projectService); + this.modelFactory = modelFactory; this.day = day; this.previousDay = previousDay; this.month = month; - this.projectService = Objects.requireNonNull(projectService); } public Duration getMandatoryBreak() @@ -148,7 +151,7 @@ public void setInterruption(Duration interruption) day.setInterruption(interruption.isZero() ? null : interruption); } - JsonDay getJsonDay() + DayData getJsonDay() { return day; } @@ -184,7 +187,7 @@ public boolean isDummyDay() public DayActivities activities() { - return new DayActivities(this, projectService); + return new DayActivities(this, projectService, modelFactory); } @Override diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/MonthIndex.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/MonthIndex.java index 1f35581d..c8de5a8a 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/MonthIndex.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/MonthIndex.java @@ -10,33 +10,38 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.api.model.DayType; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; public class MonthIndex { - private final JsonMonth record; + private final ModelFactory modelFactory; + private final MonthData record; private final Map days; - private MonthIndex(JsonMonth record, Map days) + private MonthIndex(ModelFactory modelFactory, MonthData record, Map days) { + this.modelFactory = Objects.requireNonNull(modelFactory, "modelFactory"); this.record = record; this.days = days; } - public static MonthIndex create(ContractTermsService contractTerms, JsonMonth record, ProjectService projectService) + public static MonthIndex create(ContractTermsService contractTerms, ProjectService projectService, + ModelFactory modelFactory, MonthData record) { - final Map jsonDays = record.getDays().stream() - .collect(toMap(JsonDay::getDate, Function.identity())); + final Map jsonDays = record.getDays().stream() + .collect(toMap(DayData::getDate, Function.identity())); final Map days = new HashMap<>(); - final MonthIndex monthIndex = new MonthIndex(record, days); + final MonthIndex monthIndex = new MonthIndex(modelFactory, record, days); final YearMonth yearMonth = YearMonth.of(record.getYear(), record.getMonth()); @@ -44,8 +49,9 @@ public static MonthIndex create(ContractTermsService contractTerms, JsonMonth re for (int day = 1; day <= yearMonth.lengthOfMonth(); day++) { final LocalDate date = yearMonth.atDay(day); - final JsonDay jsonDay = jsonDays.computeIfAbsent(date, d -> createDummyDay(d, contractTerms)); - final DayRecord dayRecord = new DayRecord(contractTerms, jsonDay, previousDay, monthIndex, projectService); + final DayData jsonDay = jsonDays.computeIfAbsent(date, d -> createDummyDay(d, contractTerms, modelFactory)); + final DayRecord dayRecord = new DayRecord(contractTerms, jsonDay, previousDay, monthIndex, projectService, + modelFactory); days.put(dayRecord.getDate(), dayRecord); previousDay = dayRecord; } @@ -53,9 +59,9 @@ public static MonthIndex create(ContractTermsService contractTerms, JsonMonth re return monthIndex; } - private static JsonDay createDummyDay(LocalDate date, ContractTermsService contractTerms) + private static DayData createDummyDay(LocalDate date, ContractTermsService contractTerms, ModelFactory modelFactory) { - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(date); if (contractTerms.getContractedWorkingTimePerDay() != null && !contractTerms.getContractedWorkingTimePerDay().equals(contractTerms.getCurrentWorkingTimePerDay())) @@ -80,13 +86,19 @@ public void put(DayRecord day) this.days.put(day.getDate(), day); } - public JsonMonth getMonthRecord() + public MonthData getMonthRecord() { - final List sortedNonDummyJsonDays = getSortedDays() // + final List sortedNonDummyJsonDays = getSortedDays() // .filter(d -> !d.isDummyDay()) // .map(DayRecord::getJsonDay) // .collect(toList()); - return JsonMonth.create(record, sortedNonDummyJsonDays); + + final MonthData month = modelFactory.createMonthData(); + month.setOvertimePreviousMonth(record.getOvertimePreviousMonth()); + month.setYear(record.getYear()); + month.setMonth(record.getMonth()); + month.setDays(sortedNonDummyJsonDays); + return month; } public Duration getOvertimePreviousMonth() @@ -126,7 +138,7 @@ public List getVacationDays() { return record.getDays().stream() .filter(day -> day.getType() == DayType.VACATION) - .map(JsonDay::getDate) + .map(DayData::getDate) .collect(toList()); } } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGenerator.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGenerator.java index 91aa8ad9..eb0ee679 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGenerator.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGenerator.java @@ -43,7 +43,7 @@ public VacationReport generateReport() private class Calculator { - final List availableDataYearMonth = storage.getAvailableDataYearMonth(); + final List availableDataYearMonth = storage.getAvailableDataMonths(); final Map monthData = availableDataYearMonth.stream() // .map(storage::loadMonth) // .map(Optional::orElseThrow) // diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/AppService.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/AppService.java index 3bcaa2fd..da70bcc1 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/AppService.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/AppService.java @@ -1,6 +1,5 @@ package org.itsallcode.whiterabbit.logic.service; -import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import java.io.Closeable; @@ -17,6 +16,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; import org.itsallcode.whiterabbit.api.model.ProjectReport; import org.itsallcode.whiterabbit.logic.Config; import org.itsallcode.whiterabbit.logic.autocomplete.AutocompleteService; @@ -37,6 +37,7 @@ import org.itsallcode.whiterabbit.logic.service.singleinstance.SingleInstanceService; import org.itsallcode.whiterabbit.logic.storage.CachingStorage; import org.itsallcode.whiterabbit.logic.storage.Storage; +import org.itsallcode.whiterabbit.logic.storage.data.JsonFileStorage; public class AppService implements Closeable { @@ -94,8 +95,10 @@ public static AppService create(final Config config, Clock clock, ScheduledExecu { final SingleInstanceService singleInstanceService = SingleInstanceService.create(config); final ProjectService projectService = new ProjectService(config); + final PluginManager pluginManager = PluginManager.create(config); - final CachingStorage storage = CachingStorage.create(config.getDataDir(), new ContractTermsService(config), + final MonthDataStorage dataStorage = createMonthDataStorage(config, pluginManager); + final CachingStorage storage = CachingStorage.create(dataStorage, new ContractTermsService(config), projectService); final ClockService clockService = new ClockService(clock); final AutocompleteService autocompleteService = new AutocompleteService(storage, clockService); @@ -106,13 +109,24 @@ public static AppService create(final Config config, Clock clock, ScheduledExecu final ProjectReportGenerator projectReportGenerator = new ProjectReportGenerator(storage); final ActivityService activityService = new ActivityService(storage, autocompleteService, appServiceCallback); final FormatterService formatterService = new FormatterService(config.getLocale(), clock.getZone()); - final PluginManager pluginManager = PluginManager.create(config); return new AppService(workingTimeService, storage, formatterService, clockService, schedulingService, singleInstanceService, appServiceCallback, activityService, projectService, autocompleteService, new AppPropertiesService(), vacationReportGenerator, projectReportGenerator, pluginManager); } + private static MonthDataStorage createMonthDataStorage(final Config config, final PluginManager pluginManager) + { + final Optional dataStorage = pluginManager.getMonthDataStorage(); + if (dataStorage.isPresent()) + { + LOG.info("Using storage plugin {}", dataStorage.get().getClass().getName()); + return dataStorage.get(); + } + LOG.debug("No storage plugin found, using default json file storage"); + return JsonFileStorage.create(config.getDataDir()); + } + public void setUpdateListener(AppServiceCallback callback) { this.appServiceCallback.setDelegate(callback); @@ -155,6 +169,10 @@ public SchedulingService scheduler() return this.schedulingService; } + /** + * @deprecated will be removed in the next version. + */ + @Deprecated public void report() { final DayReporter reporter = new DayReporter(formatterService); @@ -182,21 +200,9 @@ public ClockService getClock() return clock; } - public List getRecords(YearMonth yearMonth) - { - return getMonth(yearMonth) // - .map(record -> record.getSortedDays().collect(toList())) // - .orElse(emptyList()); - } - public List getAvailableDataYearMonth() { - return storage.getAvailableDataYearMonth(); - } - - public Optional getMonth(YearMonth yearMonth) - { - return storage.loadMonth(yearMonth); + return storage.getAvailableDataMonths(); } public MonthIndex getOrCreateMonth(YearMonth yearMonth) diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/DayReporter.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/DayReporter.java index 0649e0d6..ce8f63bc 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/DayReporter.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/DayReporter.java @@ -6,6 +6,10 @@ import org.apache.logging.log4j.Logger; import org.itsallcode.whiterabbit.logic.model.DayRecord; +/** + * @deprecated will be removed in the next version. + */ +@Deprecated public class DayReporter { private static final Logger LOG = LogManager.getLogger(DayReporter.class); diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/FormatterService.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/FormatterService.java index 585f30ee..358c443d 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/FormatterService.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/FormatterService.java @@ -30,6 +30,10 @@ public FormatterService(Locale locale, ZoneId timeZoneId) .withLocale(locale).withZone(timeZoneId); } + /** + * @deprecated will be removed in the next version. + */ + @Deprecated public String format(DayRecord day) { final String dayOfWeek = day.getDate().getDayOfWeek().getDisplayName(TextStyle.SHORT_STANDALONE, locale); @@ -44,6 +48,10 @@ public String format(DayRecord day) format(day.getOverallOvertime())); } + /** + * @deprecated will be removed in the next version. + */ + @Deprecated private String formatDayType(DayType type) { final String formatPattern = "%1$-" + MAX_DAY_TYPE_LENGTH + "s"; diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManager.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManager.java index ba03bda6..f04f0ab6 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManager.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManager.java @@ -3,10 +3,13 @@ import static java.util.stream.Collectors.toList; import java.util.List; +import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.itsallcode.whiterabbit.api.ProjectReportExporter; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.features.PluginFeature; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; import org.itsallcode.whiterabbit.logic.Config; public class PluginManager @@ -32,7 +35,7 @@ public List getProjectReportExporterPlugins() return findPluginsSupporting(ProjectReportExporter.class); } - private List findPluginsSupporting(Class featureType) + private List findPluginsSupporting(Class featureType) { return pluginRegistry.getAllPlugins().stream() .filter(plugin -> plugin.supports(featureType)) @@ -45,9 +48,38 @@ public ProjectReportExporter getProjectReportExporter(String id) return getFeature(id, ProjectReportExporter.class); } - private T getFeature(String id, final Class featureType) + public Optional getMonthDataStorage() { - return pluginRegistry.getPlugin(id).getFeature(featureType); + return getUniqueFeature(MonthDataStorage.class); + } + + private Optional getUniqueFeature(Class featureType) + { + final List pluginIds = findPluginsSupporting(featureType); + if (pluginIds.isEmpty()) + { + return Optional.empty(); + } + if (pluginIds.size() > 1) + { + throw new IllegalStateException("Found multiple plugins supporting " + featureType.getName() + + ": " + pluginIds + ". Please add only one storage plugin to the classpath."); + } + return Optional.of(getFeature(pluginIds.get(0), featureType)); + } + + private T getFeature(String id, final Class featureType) + { + final PluginWrapper plugin = pluginRegistry.getPlugin(id); + if (plugin == null) + { + throw new IllegalStateException("Plugin '" + id + "' not found"); + } + if (!plugin.supports(featureType)) + { + throw new IllegalStateException("Plugin '" + id + "' does not support feature " + featureType.getName()); + } + return plugin.getFeature(featureType); } public void close() diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistry.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistry.java index ec0af087..f34d7806 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistry.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistry.java @@ -95,7 +95,7 @@ private ClassLoader createClassLoader(Path jar) private List getPluginJars() { final Path pluginDir = config.getPluginDir(); - if (!Files.exists(pluginDir)) + if (pluginDir == null || !Files.exists(pluginDir)) { LOG.info("Plugin directory {} does not exist", pluginDir); return emptyList(); diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginWrapper.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginWrapper.java index 4b47d789..a1a002cb 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginWrapper.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginWrapper.java @@ -4,6 +4,7 @@ import org.itsallcode.whiterabbit.api.Plugin; import org.itsallcode.whiterabbit.api.PluginConfiguration; +import org.itsallcode.whiterabbit.api.features.PluginFeature; import org.itsallcode.whiterabbit.logic.Config; class PluginWrapper @@ -34,12 +35,12 @@ String getId() return plugin.getId(); } - boolean supports(Class featureType) + boolean supports(Class featureType) { return plugin.supports(featureType); } - T getFeature(Class featureType) + T getFeature(Class featureType) { return plugin.getFeature(featureType); } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/ProjectService.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/ProjectService.java index d5ed6041..cda8d919 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/ProjectService.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/ProjectService.java @@ -41,7 +41,7 @@ public ProjectService(Config config) this.jsonb = jsonb; final Path projectConfigFile = config.getProjectFile(); - if (Files.exists(projectConfigFile)) + if (projectConfigFile != null && Files.exists(projectConfigFile)) { final List allProjects = loadAvailableProjects(projectConfigFile); projectsById = groupBy(allProjects, ProjectImpl::getProjectId); diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorage.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorage.java index 41b7b245..61382120 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorage.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorage.java @@ -1,24 +1,19 @@ package org.itsallcode.whiterabbit.logic.storage; +import java.time.LocalDate; +import java.util.List; + +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; import org.itsallcode.whiterabbit.logic.model.DayRecord; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; -import javax.json.bind.Jsonb; -import javax.json.bind.JsonbBuilder; -import javax.json.bind.JsonbConfig; -import java.nio.file.Path; -import java.time.LocalDate; -import java.util.List; - public interface CachingStorage extends Storage { - static CachingStorage create(Path dataDir, ContractTermsService contractTerms, ProjectService projectService) + static CachingStorage create(MonthDataStorage dataStorage, ContractTermsService contractTerms, + ProjectService projectService) { - final DateToFileMapper dateToFileMapper = new DateToFileMapper(dataDir); - final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true)); - final JsonFileStorage fileStorage = new JsonFileStorage(jsonb, dateToFileMapper); - final MonthIndexStorage monthIndexStorage = new MonthIndexStorage(contractTerms, projectService, fileStorage); + final MonthIndexStorage monthIndexStorage = new MonthIndexStorage(contractTerms, projectService, dataStorage); return new CachingStorageImpl(monthIndexStorage); } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImpl.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImpl.java index c2d2f8f8..114e18cb 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImpl.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImpl.java @@ -68,9 +68,9 @@ private MonthIndex updateCache(MonthIndex month) } @Override - public List getAvailableDataYearMonth() + public List getAvailableDataMonths() { - return delegateStorage.getAvailableDataYearMonth(); + return delegateStorage.getAvailableDataMonths(); } @Override @@ -95,7 +95,7 @@ void ensureLatestDaysCached(LocalDate maxAge) List getRequiredYearMonths(LocalDate maxAge) { final YearMonth oldestYearMonth = YearMonth.from(maxAge); - return delegateStorage.getAvailableDataYearMonth().stream() + return delegateStorage.getAvailableDataMonths().stream() .filter(month -> !month.isBefore(oldestYearMonth)) .collect(toList()); } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorage.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorage.java index 37687b84..8416517f 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorage.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorage.java @@ -11,9 +11,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.model.MonthIndex; import org.itsallcode.whiterabbit.logic.model.MultiMonthIndex; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; @@ -23,10 +24,10 @@ class MonthIndexStorage implements Storage private final ContractTermsService contractTerms; private final ProjectService projectService; - private final JsonFileStorage fileStorage; + private final MonthDataStorage fileStorage; MonthIndexStorage(ContractTermsService contractTerms, ProjectService projectService, - JsonFileStorage fileStorage) + MonthDataStorage fileStorage) { this.contractTerms = contractTerms; this.projectService = projectService; @@ -36,7 +37,7 @@ class MonthIndexStorage implements Storage @Override public Optional loadMonth(YearMonth date) { - return fileStorage.loadMonthRecord(date).map(this::createMonthIndex); + return fileStorage.load(date).map(this::createMonthIndex); } @Override @@ -49,7 +50,7 @@ public MonthIndex loadOrCreate(final YearMonth yearMonth) @Override public void storeMonth(MonthIndex month) { - fileStorage.writeToFile(month.getYearMonth(), month.getMonthRecord()); + fileStorage.store(month.getYearMonth(), month.getMonthRecord()); } @Override @@ -61,14 +62,14 @@ public MultiMonthIndex loadAll() } @Override - public List getAvailableDataYearMonth() + public List getAvailableDataMonths() { - return fileStorage.getAvailableDataYearMonth(); + return fileStorage.getAvailableDataMonths(); } private MonthIndex createNewMonth(YearMonth date) { - final JsonMonth month = new JsonMonth(); + final MonthData month = fileStorage.getModelFactory().createMonthData(); month.setYear(date.getYear()); month.setMonth(date.getMonth()); month.setDays(new ArrayList<>()); @@ -76,9 +77,9 @@ private MonthIndex createNewMonth(YearMonth date) return createMonthIndex(month); } - private MonthIndex createMonthIndex(final JsonMonth jsonMonth) + private MonthIndex createMonthIndex(final MonthData jsonMonth) { - return MonthIndex.create(contractTerms, jsonMonth, projectService); + return MonthIndex.create(contractTerms, projectService, fileStorage.getModelFactory(), jsonMonth); } Duration loadPreviousMonthOvertime(YearMonth date) diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/Storage.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/Storage.java index 011ad687..b1f1af50 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/Storage.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/Storage.java @@ -17,6 +17,6 @@ public interface Storage MultiMonthIndex loadAll(); - List getAvailableDataYearMonth(); + List getAvailableDataMonths(); } \ No newline at end of file diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapper.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapper.java similarity index 96% rename from logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapper.java rename to logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapper.java index 663e4c65..f130ba51 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapper.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapper.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.storage; +package org.itsallcode.whiterabbit.logic.storage.data; import java.io.IOException; import java.io.UncheckedIOException; @@ -15,7 +15,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -public class DateToFileMapper +class DateToFileMapper { private static final Logger LOG = LogManager.getLogger(DateToFileMapper.class); diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonActivity.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonActivity.java similarity index 78% rename from logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonActivity.java rename to logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonActivity.java index 7ed8f794..07cb58b2 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonActivity.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonActivity.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.model.json; +package org.itsallcode.whiterabbit.logic.storage.data; import java.time.Duration; @@ -6,9 +6,12 @@ import javax.json.bind.annotation.JsonbPropertyOrder; import javax.json.bind.annotation.JsonbVisibility; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.logic.model.json.FieldAccessStrategy; + @JsonbPropertyOrder({ "projectId", "duration", "comment" }) @JsonbVisibility(FieldAccessStrategy.class) -public class JsonActivity +public class JsonActivity implements ActivityData { @JsonbProperty("projectId") private String projectId; @@ -17,43 +20,43 @@ public class JsonActivity @JsonbProperty("comment") private String comment; - public JsonActivity() - { - this.projectId = null; - this.duration = null; - this.comment = null; - } - + @Override public String getProjectId() { return projectId; } + @Override public void setProjectId(String id) { this.projectId = id; } + @Override public Duration getDuration() { return duration; } + @Override public boolean isRemainder() { return getDuration() == null; } + @Override public void setDuration(Duration duration) { this.duration = duration; } + @Override public String getComment() { return comment; } + @Override public void setComment(String comment) { this.comment = comment; diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonDay.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonDay.java similarity index 80% rename from logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonDay.java rename to logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonDay.java index a1be07af..4eadc4c6 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonDay.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonDay.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.model.json; +package org.itsallcode.whiterabbit.logic.storage.data; import java.time.Duration; import java.time.LocalDate; @@ -8,10 +8,12 @@ import javax.json.bind.annotation.JsonbProperty; import javax.json.bind.annotation.JsonbPropertyOrder; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.api.model.DayType; @JsonbPropertyOrder({ "date", "type", "begin", "end", "interruption", "workingHours", "comment", "activities" }) -public class JsonDay +public class JsonDay implements DayData { @JsonbProperty("date") private LocalDate date; @@ -28,84 +30,100 @@ public class JsonDay @JsonbProperty("comment") private String comment; @JsonbProperty("activities") - private List activities; + private List activities; + @Override public LocalDate getDate() { return date; } + @Override public DayType getType() { return type; } + @Override public LocalTime getBegin() { return begin; } + @Override public LocalTime getEnd() { return end; } + @Override public Duration getInterruption() { return interruption; } + @Override public Duration getWorkingHours() { return workingHours; } + @Override public String getComment() { return comment; } + @Override public void setDate(LocalDate date) { this.date = date; } + @Override public void setType(DayType type) { this.type = type; } + @Override public void setBegin(LocalTime begin) { this.begin = begin; } + @Override public void setEnd(LocalTime end) { this.end = end; } + @Override public void setWorkingHours(Duration workingHours) { this.workingHours = workingHours; } + @Override public void setComment(String comment) { this.comment = comment; } + @Override public void setInterruption(Duration interruption) { this.interruption = interruption; } - public List getActivities() + @Override + public List getActivities() { return activities; } - public void setActivities(List activities) + @Override + public void setActivities(List activities) { this.activities = activities; } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorage.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorage.java similarity index 77% rename from logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorage.java rename to logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorage.java index aed94d00..f97dcb9f 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorage.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorage.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.storage; +package org.itsallcode.whiterabbit.logic.storage.data; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; @@ -18,36 +18,33 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.Config; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; -public class JsonFileStorage +public class JsonFileStorage implements MonthDataStorage { private static final Logger LOG = LogManager.getLogger(JsonFileStorage.class); private final Jsonb jsonb; private final DateToFileMapper dateToFileMapper; + private final ModelFactory modelFactory; - JsonFileStorage(Jsonb jsonb, DateToFileMapper dateToFileMapper) + JsonFileStorage(Jsonb jsonb, DateToFileMapper dateToFileMapper, ModelFactory modelFactory) { this.jsonb = jsonb; this.dateToFileMapper = dateToFileMapper; + this.modelFactory = modelFactory; } - private JsonMonth loadFromFile(Path file) + public static MonthDataStorage create(Path dataDir) { - LOG.trace("Reading file {}", file); - try (InputStream stream = Files.newInputStream(file)) - { - return jsonb.fromJson(stream, JsonMonth.class); - } - catch (final IOException e) - { - throw new UncheckedIOException("Error reading file " + file, e); - } + final Jsonb jsonb = new JsonbFactory().createNonFormatting(); + return new JsonFileStorage(jsonb, new DateToFileMapper(dataDir), new JsonModelFactory()); } - Optional loadMonthRecord(YearMonth date) + @Override + public Optional load(YearMonth date) { final Path file = dateToFileMapper.getPathForDate(date); if (file.toFile().exists()) @@ -65,7 +62,21 @@ Optional loadMonthRecord(YearMonth date) return Optional.empty(); } - void writeToFile(YearMonth yearMonth, JsonMonth record) + private JsonMonth loadFromFile(Path file) + { + LOG.trace("Reading file {}", file); + try (InputStream stream = Files.newInputStream(file)) + { + return jsonb.fromJson(stream, JsonMonth.class); + } + catch (final IOException e) + { + throw new UncheckedIOException("Error reading file " + file, e); + } + } + + @Override + public void store(YearMonth yearMonth, MonthData record) { final Path file = dateToFileMapper.getPathForDate(yearMonth); LOG.trace("Write month {} to file {}", yearMonth, file); @@ -97,12 +108,14 @@ private void createDirectory(Path dir) } } - List getAvailableDataYearMonth() + @Override + public List getAvailableDataMonths() { return dateToFileMapper.getAllYearMonths().sorted().collect(toList()); } - List loadAll() + @Override + public List loadAll() { return dateToFileMapper.getAllFiles() .filter(file -> !file.getFileName().toString().equals(Config.PROJECTS_JSON)) @@ -110,4 +123,10 @@ List loadAll() .sorted(comparing(JsonMonth::getYear).thenComparing(JsonMonth::getMonth)) .collect(toList()); } + + @Override + public ModelFactory getModelFactory() + { + return modelFactory; + } } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonModelFactory.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonModelFactory.java new file mode 100644 index 00000000..c6418b3e --- /dev/null +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonModelFactory.java @@ -0,0 +1,27 @@ +package org.itsallcode.whiterabbit.logic.storage.data; + +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; +import org.itsallcode.whiterabbit.api.model.MonthData; + +public class JsonModelFactory implements ModelFactory +{ + @Override + public MonthData createMonthData() + { + return new JsonMonth(); + } + + @Override + public DayData createDayData() + { + return new JsonDay(); + } + + @Override + public ActivityData createActivityData() + { + return new JsonActivity(); + } +} diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonMonth.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonMonth.java similarity index 66% rename from logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonMonth.java rename to logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonMonth.java index 521de128..ba204c78 100644 --- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/json/JsonMonth.java +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonMonth.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.model.json; +package org.itsallcode.whiterabbit.logic.storage.data; import java.time.Duration; import java.time.Month; @@ -7,8 +7,11 @@ import javax.json.bind.annotation.JsonbProperty; import javax.json.bind.annotation.JsonbPropertyOrder; +import org.itsallcode.whiterabbit.api.model.DayData; +import org.itsallcode.whiterabbit.api.model.MonthData; + @JsonbPropertyOrder({ "year", "month", "overtimePreviousMonth", "days" }) -public class JsonMonth +public class JsonMonth implements MonthData { @JsonbProperty("year") private int year; @@ -17,54 +20,52 @@ public class JsonMonth @JsonbProperty("overtimePreviousMonth") private Duration overtimePreviousMonth; @JsonbProperty("days") - private List days; - - public static JsonMonth create(JsonMonth record, List sortedDays) - { - final JsonMonth month = new JsonMonth(); - month.setOvertimePreviousMonth(record.getOvertimePreviousMonth()); - month.setYear(record.getYear()); - month.setMonth(record.getMonth()); - month.setDays(sortedDays); - return month; - } + private List days; + @Override public int getYear() { return year; } + @Override public void setYear(int year) { this.year = year; } + @Override public Month getMonth() { return month; } + @Override public void setMonth(Month month) { this.month = month; } + @Override public Duration getOvertimePreviousMonth() { return overtimePreviousMonth; } + @Override public void setOvertimePreviousMonth(Duration overtimePreviousMonth) { this.overtimePreviousMonth = overtimePreviousMonth; } - public List getDays() + @Override + public List getDays() { return days; } - public void setDays(List days) + @Override + public void setDays(List days) { this.days = days; } diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonbFactory.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonbFactory.java new file mode 100644 index 00000000..263912d1 --- /dev/null +++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/storage/data/JsonbFactory.java @@ -0,0 +1,35 @@ +package org.itsallcode.whiterabbit.logic.storage.data; + +import java.util.Map; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; + +import org.eclipse.yasson.YassonConfig; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; + +public class JsonbFactory +{ + public Jsonb create() + { + return createWithFormatting(true); + } + + public Jsonb createNonFormatting() + { + return createWithFormatting(false); + } + + private Jsonb createWithFormatting(boolean formatting) + { + final Map, Class> userTypeMapping = Map.of( + DayData.class, JsonDay.class, + ActivityData.class, JsonActivity.class); + final JsonbConfig config = new JsonbConfig() + .withFormatting(formatting) + .setProperty(YassonConfig.USER_TYPE_MAPPING, userTypeMapping); + return JsonbBuilder.create(config); + } +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayActivitiesTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayActivitiesTest.java index 298ceb6e..0181918c 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayActivitiesTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayActivitiesTest.java @@ -8,10 +8,12 @@ import java.time.Duration; import java.util.ArrayList; -import org.itsallcode.whiterabbit.logic.model.json.JsonActivity; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,13 +27,15 @@ class DayActivitiesTest private ProjectService projectServiceMock; @Mock private ContractTermsService contractTermsMock; - private JsonDay jsonDay; + private ModelFactory modelFactory; + private DayData jsonDay; private DayRecord dayRecord; @BeforeEach void setup() { - jsonDay = new JsonDay(); + modelFactory = new JsonModelFactory(); + jsonDay = modelFactory.createDayData(); } @Test @@ -282,7 +286,7 @@ void setRemainderToFalse() when(contractTermsMock.getWorkingTime(same(dayRecord))).thenReturn(Duration.ofHours(7)); - final JsonActivity activity = new JsonActivity(); + final ActivityData activity = modelFactory.createActivityData(); activity.setDuration(null); activities.setRemainderActivity(activity, false); @@ -299,7 +303,7 @@ void setRemainderToFalseSetsRemainingDuration() when(contractTermsMock.getWorkingTime(same(dayRecord))).thenReturn(Duration.ofHours(7)); activities.add().setDuration(Duration.ofHours(3)); - final JsonActivity activity = new JsonActivity(); + final ActivityData activity = modelFactory.createActivityData(); activity.setDuration(null); activities.setRemainderActivity(activity, false); @@ -317,7 +321,7 @@ void setRemainderToFalseSetsRemainingDurationMultipleActivities() activities.add().setDuration(Duration.ofHours(3)); activities.add().setDuration(Duration.ofMinutes(30)); - final JsonActivity activity = new JsonActivity(); + final ActivityData activity = modelFactory.createActivityData(); activity.setDuration(null); activities.setRemainderActivity(activity, false); @@ -330,7 +334,7 @@ private DayActivities create() { final DayRecord previousDay = null; final MonthIndex month = null; - dayRecord = new DayRecord(contractTermsMock, jsonDay, previousDay, month, projectServiceMock); - return new DayActivities(dayRecord, projectServiceMock); + dayRecord = new DayRecord(contractTermsMock, jsonDay, previousDay, month, projectServiceMock, modelFactory); + return new DayActivities(dayRecord, projectServiceMock, modelFactory); } } diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayRecordTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayRecordTest.java index c55a74cf..afda7931 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayRecordTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/DayRecordTest.java @@ -8,12 +8,14 @@ import java.time.LocalDate; import java.time.LocalTime; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.api.model.DayType; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.Config; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.itsallcode.whiterabbit.logic.test.TestingConfig; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +27,7 @@ class DayRecordTest { @Mock private ProjectService projectServiceMock; + private final ModelFactory modelFactory = new JsonModelFactory(); @Test void mandatoryWorkingTimeIsZeroOnWeekend() @@ -323,7 +326,7 @@ void setInterruptionFailsForNullValue() @Test void setInterruptionToZeroUsesNullForJsonRecord() { - final JsonDay day1 = new JsonDay(); + final DayData day1 = modelFactory.createDayData(); day1.setInterruption(Duration.ofHours(1)); final DayRecord day = dayRecord(day1, null, null); @@ -336,7 +339,7 @@ void setInterruptionToZeroUsesNullForJsonRecord() @Test void setCommentToEmptyStringUsesNullForJsonRecord() { - final JsonDay day1 = new JsonDay(); + final DayData day1 = modelFactory.createDayData(); day1.setComment("comment"); final DayRecord day = dayRecord(day1, null, null); @@ -349,7 +352,7 @@ void setCommentToEmptyStringUsesNullForJsonRecord() @Test void setCommentToValueSetsCommentForJsonRecord() { - final JsonDay day1 = new JsonDay(); + final DayData day1 = modelFactory.createDayData(); final DayRecord day = dayRecord(day1, null, null); day.setComment("comment"); @@ -399,41 +402,41 @@ void getMonthReturnsMonth() } @Test - void getJsonDayReturnsMonth() + void getDayDataReturnsMonth() { - final JsonDay jsonDay = new JsonDay(); - final DayRecord day = createDay(jsonDay); - assertThat(day.getJsonDay()).isSameAs(jsonDay); + final DayData dayData = modelFactory.createDayData(); + final DayRecord day = createDay(dayData); + assertThat(day.getJsonDay()).isSameAs(dayData); } @Test void getCustomWorkingTimeReturnsEmptyOptionalByDefault() { - final JsonDay jsonDay = new JsonDay(); - jsonDay.setWorkingHours(null); + final DayData DayData = modelFactory.createDayData(); + DayData.setWorkingHours(null); - final DayRecord day = createDay(jsonDay); + final DayRecord day = createDay(DayData); assertThat(day.getCustomWorkingTime()).isEmpty(); } @Test void getCustomWorkingTimeReturnsRealValue() { - final JsonDay jsonDay = new JsonDay(); - jsonDay.setWorkingHours(Duration.ofHours(5)); + final DayData DayData = modelFactory.createDayData(); + DayData.setWorkingHours(Duration.ofHours(5)); - final DayRecord day = createDay(jsonDay); + final DayRecord day = createDay(DayData); assertThat(day.getCustomWorkingTime()).isPresent().contains(Duration.ofHours(5)); } - private MonthIndex month(LocalDate date, Duration overtimePreviousMonth, JsonDay... days) + private MonthIndex month(LocalDate date, Duration overtimePreviousMonth, DayData... days) { - final JsonMonth jsonMonth = new JsonMonth(); + final MonthData jsonMonth = modelFactory.createMonthData(); jsonMonth.setDays(asList(days)); jsonMonth.setMonth(date.getMonth()); jsonMonth.setYear(date.getYear()); jsonMonth.setOvertimePreviousMonth(overtimePreviousMonth); - return MonthIndex.create(contractTerms(), jsonMonth, projectServiceMock); + return MonthIndex.create(contractTerms(), projectServiceMock, modelFactory, jsonMonth); } private void assertOvertime(LocalDate date, LocalTime begin, LocalTime end, Duration expectedOvertime) @@ -517,7 +520,7 @@ private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayT private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayType type, Duration interruption, DayRecord previousDay, MonthIndex month) { - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setBegin(begin); day.setEnd(end); day.setDate(date); @@ -527,15 +530,15 @@ private DayRecord createDay(LocalDate date, LocalTime begin, LocalTime end, DayT return dayRecord(day, previousDay, month); } - private DayRecord createDay(final JsonDay jsonDay) + private DayRecord createDay(final DayData DayData) { - return new DayRecord(null, jsonDay, null, null, projectServiceMock); + return new DayRecord(null, DayData, null, null, projectServiceMock, modelFactory); } - private DayRecord dayRecord(JsonDay day, DayRecord previousDay, MonthIndex month) + private DayRecord dayRecord(DayData day, DayRecord previousDay, MonthIndex month) { final ContractTermsService contractTerms = contractTerms(); - return new DayRecord(contractTerms, day, previousDay, month, projectServiceMock); + return new DayRecord(contractTerms, day, previousDay, month, projectServiceMock, modelFactory); } private ContractTermsService contractTerms() diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/MonthIndexTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/MonthIndexTest.java index 32d1ac33..4269f6f2 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/MonthIndexTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/model/MonthIndexTest.java @@ -9,12 +9,14 @@ import java.time.Month; import java.time.YearMonth; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.DayData; import org.itsallcode.whiterabbit.api.model.DayType; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.Config; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.itsallcode.whiterabbit.logic.test.TestingConfig; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,6 +29,8 @@ class MonthIndexTest @Mock private ProjectService projectServiceMock; + private final ModelFactory modelFactory = new JsonModelFactory(); + @Test void testCalculateTotalOvertimeOvertimeNoDays() { @@ -76,7 +80,7 @@ void testCalculateTotalOvertimeMultipleDaysNegativeOvertime() @Test void testSetOvertimePreviousMonthUpdatesJsonRecord() { - final JsonMonth jsonMonth = jsonMonth(Duration.ofHours(1)); + final MonthData jsonMonth = jsonMonth(Duration.ofHours(1)); final MonthIndex monthIndex = create(jsonMonth); monthIndex.setOvertimePreviousMonth(Duration.ofHours(2)); @@ -86,7 +90,7 @@ void testSetOvertimePreviousMonthUpdatesJsonRecord() @Test void testSetOvertimePreviousMonthUpdatesTotalOvertime() { - final JsonMonth jsonMonth = jsonMonth(Duration.ofHours(1)); + final MonthData jsonMonth = jsonMonth(Duration.ofHours(1)); final MonthIndex monthIndex = create(jsonMonth); assertThat(monthIndex.getTotalOvertime()).isEqualTo(Duration.ofHours(1)); @@ -127,7 +131,7 @@ void gettingNewDayReturnsEmptyDay() void gettingDaysDoesNotAddThemToJson() { final LocalDate date = LocalDate.of(2020, 5, 4); - final JsonMonth jsonMonth = jsonMonth(YearMonth.from(date), null); + final MonthData jsonMonth = jsonMonth(YearMonth.from(date), null); final MonthIndex monthIndex = create(TestingConfig.builder().build(), jsonMonth); final DayRecord day = monthIndex.getDay(date); @@ -162,24 +166,24 @@ void newDaysHaveCustomWorkingTimeWhenCurrentHoursPerDayAreConfigured() assertThat(day.getCustomWorkingTime()).isPresent().contains(Duration.ofHours(5)); } - private Duration calculateTotalOvertime(JsonDay... days) + private Duration calculateTotalOvertime(DayData... days) { return calculateTotalOvertime(null, days); } - private Duration calculateTotalOvertime(Duration overtimePreviousMonth, JsonDay... days) + private Duration calculateTotalOvertime(Duration overtimePreviousMonth, DayData... days) { return create(jsonMonth(overtimePreviousMonth, days)).getTotalOvertime(); } - private JsonMonth jsonMonth(Duration overtimePreviousMonth, JsonDay... days) + private MonthData jsonMonth(Duration overtimePreviousMonth, DayData... days) { return jsonMonth(YearMonth.of(2019, Month.MAY), overtimePreviousMonth, days); } - private JsonMonth jsonMonth(YearMonth yearMonth, Duration overtimePreviousMonth, JsonDay... days) + private MonthData jsonMonth(YearMonth yearMonth, Duration overtimePreviousMonth, DayData... days) { - final JsonMonth month = new JsonMonth(); + final MonthData month = modelFactory.createMonthData(); month.setYear(yearMonth.getYear()); month.setMonth(yearMonth.getMonth()); month.setDays(asList(days)); @@ -187,12 +191,12 @@ private JsonMonth jsonMonth(YearMonth yearMonth, Duration overtimePreviousMonth, return month; } - private JsonDay day(Duration overtime, int dayOfMonth) + private DayData day(Duration overtime, int dayOfMonth) { final LocalTime begin = LocalTime.of(8, 0); final LocalTime end = begin.plus(Duration.ofHours(8)).plus(Duration.ofMinutes(45).plus(overtime)); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(LocalDate.of(2019, Month.MAY, dayOfMonth)); day.setBegin(begin); day.setEnd(end); @@ -200,13 +204,13 @@ private JsonDay day(Duration overtime, int dayOfMonth) return day; } - private MonthIndex create(JsonMonth record) + private MonthIndex create(MonthData record) { return create(TestingConfig.builder().build(), record); } - private MonthIndex create(Config config, JsonMonth record) + private MonthIndex create(Config config, MonthData record) { - return MonthIndex.create(new ContractTermsService(config), record, projectServiceMock); + return MonthIndex.create(new ContractTermsService(config), projectServiceMock, modelFactory, record); } } diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGeneratorTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGeneratorTest.java index f669d991..bd2cc007 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGeneratorTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/report/vacation/VacationReportGeneratorTest.java @@ -254,7 +254,7 @@ private void simulateMonths(MonthIndex... months) { final List availableDataYearMonth = Arrays.stream(months).map(MonthIndex::getYearMonth) .collect(toList()); - when(storageMock.getAvailableDataYearMonth()).thenReturn(availableDataYearMonth); + when(storageMock.getAvailableDataMonths()).thenReturn(availableDataYearMonth); for (final MonthIndex monthIndex : months) { diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ActivityServiceTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ActivityServiceTest.java index 6f2ba3d5..954ac473 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ActivityServiceTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ActivityServiceTest.java @@ -14,13 +14,14 @@ import java.util.NoSuchElementException; import java.util.Optional; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.autocomplete.AutocompleteService; import org.itsallcode.whiterabbit.logic.model.MonthIndex; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectImpl; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; import org.itsallcode.whiterabbit.logic.storage.Storage; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,6 +42,7 @@ class ActivityServiceTest private ProjectService projectService; @Mock private AutocompleteService autocompleteServiceMock; + private JsonModelFactory modelFactory; private ActivityService service; @@ -48,6 +50,7 @@ class ActivityServiceTest void setUp() { service = new ActivityService(storageMock, autocompleteServiceMock, appServiceCallbackMock); + modelFactory = new JsonModelFactory(); } @Test @@ -126,10 +129,10 @@ void removeActivity() private MonthIndex createMonth() { - final JsonMonth monthRecord = new JsonMonth(); + final MonthData monthRecord = modelFactory.createMonthData(); monthRecord.setDays(new ArrayList<>()); monthRecord.setYear(DATE.getYear()); monthRecord.setMonth(DATE.getMonth()); - return MonthIndex.create(contractTermsServiceMock, monthRecord, projectService); + return MonthIndex.create(contractTermsServiceMock, projectService, modelFactory, monthRecord); } } diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/AppServiceTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/AppServiceTest.java index 35811c61..6bdd6d9e 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/AppServiceTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/AppServiceTest.java @@ -1,5 +1,6 @@ package org.itsallcode.whiterabbit.logic.service; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -7,6 +8,7 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -17,16 +19,23 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Month; import java.time.YearMonth; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Locale; +import org.itsallcode.whiterabbit.api.model.DayData; +import org.itsallcode.whiterabbit.api.model.MonthData; +import org.itsallcode.whiterabbit.api.model.ProjectReport; import org.itsallcode.whiterabbit.logic.Config; import org.itsallcode.whiterabbit.logic.autocomplete.AutocompleteService; import org.itsallcode.whiterabbit.logic.model.DayRecord; import org.itsallcode.whiterabbit.logic.model.MonthIndex; -import org.itsallcode.whiterabbit.logic.model.json.JsonDay; +import org.itsallcode.whiterabbit.logic.model.MultiMonthIndex; import org.itsallcode.whiterabbit.logic.report.project.ProjectReportGenerator; +import org.itsallcode.whiterabbit.logic.report.vacation.VacationReport; import org.itsallcode.whiterabbit.logic.report.vacation.VacationReportGenerator; import org.itsallcode.whiterabbit.logic.service.AppPropertiesService.AppProperties; import org.itsallcode.whiterabbit.logic.service.AppServiceCallback.InterruptionDetectedDecision; @@ -39,6 +48,7 @@ import org.itsallcode.whiterabbit.logic.service.singleinstance.RunningInstanceCallback; import org.itsallcode.whiterabbit.logic.service.singleinstance.SingleInstanceService; import org.itsallcode.whiterabbit.logic.storage.Storage; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.itsallcode.whiterabbit.logic.test.TestingConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -82,20 +92,41 @@ class AppServiceTest private AutocompleteService autocompleteServiceMock; @Mock private PluginManager pluginManagerMock; + @Mock + private WorkingTimeService workingTimeServiceMock; + + private ContractTermsService contractTerms; private AppService appService; + private JsonModelFactory modelFactory; + @BeforeEach void setUp() { final DelegatingAppServiceCallback appServiceCallback = new DelegatingAppServiceCallback(); final WorkingTimeService workingTimeService = new WorkingTimeService(storageMock, clockMock, appServiceCallback); - appService = new AppService(workingTimeService, storageMock, formatterServiceMock, clockMock, + appService = createAppService(appServiceCallback, workingTimeService); + appService.setUpdateListener(updateListenerMock); + modelFactory = new JsonModelFactory(); + contractTerms = new ContractTermsService(configMock); + } + + private AppService createAppService(final DelegatingAppServiceCallback appServiceCallback, + final WorkingTimeService workingTimeService) + { + return new AppService(workingTimeService, storageMock, formatterServiceMock, clockMock, schedulingServiceMock, singleInstanceService, appServiceCallback, activityService, projectServiceMock, autocompleteServiceMock, appPropertiesServiceMock, vacationReportGeneratorMock, projectReportGeneratorMock, pluginManagerMock); - appService.setUpdateListener(updateListenerMock); + } + + @Test + void create() + { + when(configMock.getLocale()).thenReturn(Locale.ENGLISH); + assertThat(AppService.create(configMock)).isNotNull(); } @Test @@ -103,7 +134,7 @@ void testNoUpdateWhenNotWorkingDay() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 9); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); updateNow(now, day); @@ -117,7 +148,7 @@ void testUpdateListenerNotCalledWhenNoUpdate() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 9); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); updateNow(now, day); @@ -130,7 +161,7 @@ void testUpdateListenerCalledWhenDayRecordUpdated() { final LocalTime now = LocalTime.of(8, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); final DayRecord updatedRecord = updateNow(now, day); @@ -143,7 +174,7 @@ void testUpdateNewDayOnWorkingDay() { final LocalTime now = LocalTime.of(8, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); updateNow(now, day); @@ -158,7 +189,7 @@ void testUpdateExistingDayAfter1Min() final LocalTime begin = LocalTime.of(8, 0); final LocalTime now = LocalTime.of(8, 1); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(begin); day.setEnd(begin); @@ -175,7 +206,7 @@ void testUpdateExistingDayAfter2Min() final LocalTime begin = LocalTime.of(8, 0); final LocalTime now = LocalTime.of(8, 2); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(begin); day.setEnd(begin); @@ -196,7 +227,7 @@ void testNoUpdateIfEndAfterNow() final LocalTime end = LocalTime.of(16, 0); final LocalTime now = LocalTime.of(9, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(begin); day.setEnd(end); @@ -213,7 +244,7 @@ void testUpdateExistingDayAfter3Min() final LocalTime begin = LocalTime.of(8, 0); final LocalTime now = LocalTime.of(8, 3); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(begin); day.setEnd(begin); @@ -232,7 +263,7 @@ void testUpdateNowAddsInterruption() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(LocalTime.of(13, 0)); @@ -251,7 +282,7 @@ void testUpdateNowDoesNotAddInterruptionWhenCallbackSaysNo() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(LocalTime.of(13, 0)); @@ -271,7 +302,7 @@ void testUpdateNowDoesNotAddInterruptionWhenCallbackStopWorkForToday() final LocalTime beforeInterruption = LocalTime.of(13, 0); final LocalTime now = beforeInterruption.plusHours(1); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(beforeInterruption); @@ -290,7 +321,7 @@ void testUpdateDoesNotUpdateBeginIfInThePast() { final LocalTime now = LocalTime.of(9, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); final LocalTime begin = LocalTime.of(8, 0); day.setBegin(begin); @@ -307,7 +338,7 @@ void testUpdateDoesUpdateBeginIfInTheFuture() { final LocalTime now = LocalTime.of(9, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); final LocalTime begin = LocalTime.of(10, 0); day.setBegin(begin); @@ -324,7 +355,7 @@ void testUpdateNowDoesNotAddInterruptionIfAlreadyRunning() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(LocalTime.of(13, 0)); @@ -344,7 +375,7 @@ void addInterruptionCallback() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(LocalTime.of(13, 0)); @@ -368,7 +399,7 @@ void cancelInterruptionCallback() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(LocalTime.of(13, 0)); @@ -434,7 +465,7 @@ void toggleStopWorkForTodayCurrentlyNotStopped() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(now); @@ -450,7 +481,7 @@ void toggleStopWorkForTodayAlreadyStopped() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(now); @@ -468,7 +499,7 @@ void endTimeNotUpdatedWhenWorkStoppedForToday() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(now); @@ -491,7 +522,7 @@ void endTimeNotUpdatedWhenWorkNotStoppedForToday() { final LocalTime now = LocalTime.of(14, 0); final LocalDate today = LocalDate.of(2019, 3, 8); - final JsonDay day = new JsonDay(); + final DayData day = modelFactory.createDayData(); day.setDate(today); day.setBegin(LocalTime.of(8, 0)); day.setEnd(now); @@ -519,16 +550,221 @@ void loadAppProperties() assertThat(appService.getAppProperties()).isSameAs(appPropertiesMock); } - private DayRecord updateNow(final LocalTime now, final JsonDay day) + @Test + void updatePreviousMonthOvertimeField_noMonth() + { + simulateStorageLoadAll(); + appService.updatePreviousMonthOvertimeField(); + verify(storageMock, never()).storeMonth(any()); + } + + @Test + void updatePreviousMonthOvertimeField_singleMonthNoDays() + { + final MonthIndex month1 = monthIndex(YearMonth.of(2021, 1)); + simulateStorageLoadAll(month1); + appService.updatePreviousMonthOvertimeField(); + + verify(storageMock).storeMonth(same(month1)); + assertThat(month1.getOvertimePreviousMonth()).isZero(); + } + + @Test + void updatePreviousMonthOvertimeField_singleMonthWithDays() + { + final MonthIndex month1 = monthIndex(YearMonth.of(2021, 1), + day(LocalDate.of(2021, 1, 1), Duration.ofMinutes(10))); + simulateStorageLoadAll(month1); + appService.updatePreviousMonthOvertimeField(); + + assertThat(month1.getOvertimePreviousMonth()).isZero(); + } + + @Test + void updatePreviousMonthOvertimeField_twoMonthWithSingleDay() + { + final MonthIndex month1 = monthIndex(YearMonth.of(2021, 1), + day(LocalDate.of(2021, 1, 1), Duration.ofMinutes(10))); + final MonthIndex month2 = monthIndex(YearMonth.of(2021, 2), + day(LocalDate.of(2021, 2, 1), Duration.ofMinutes(20))); + simulateStorageLoadAll(month1, month2); + appService.updatePreviousMonthOvertimeField(); + + assertThat(month1.getOvertimePreviousMonth()).isZero(); + assertThat(month2.getOvertimePreviousMonth()).isEqualTo(Duration.ofMinutes(10)); + } + + @Test + void updatePreviousMonthOvertimeField_twoMonthWithMultipleDays() + { + final MonthIndex month1 = monthIndex(YearMonth.of(2021, 1), + day(LocalDate.of(2021, 1, 1), Duration.ofMinutes(10)), + day(LocalDate.of(2021, 1, 4), Duration.ofMinutes(20))); + final MonthIndex month2 = monthIndex(YearMonth.of(2021, 2), + day(LocalDate.of(2021, 2, 1), Duration.ofMinutes(5))); + simulateStorageLoadAll(month1, month2); + appService.updatePreviousMonthOvertimeField(); + + assertThat(month1.getOvertimePreviousMonth()).isZero(); + assertThat(month2.getOvertimePreviousMonth()).isEqualTo(Duration.ofMinutes(30)); + } + + private void simulateStorageLoadAll(final MonthIndex... months) + { + when(storageMock.loadAll()).thenReturn(new MultiMonthIndex(asList(months))); + } + + private DayData day(LocalDate date, Duration overtime) + { + final DayData day = modelFactory.createDayData(); + day.setDate(date); + day.setBegin(LocalTime.of(8, 0)); + day.setEnd(day.getBegin().plus(Duration.ofHours(8).plus(Duration.ofMinutes(45)).plus(overtime))); + return day; + } + + private MonthIndex monthIndex(YearMonth month, DayData... days) + { + final MonthData monthData = modelFactory.createMonthData(); + monthData.setYear(month.getYear()); + monthData.setMonth(month.getMonth()); + monthData.setDays(asList(days)); + return MonthIndex.create(contractTerms, projectServiceMock, modelFactory, monthData); + } + + private DayRecord updateNow(final LocalTime now, final DayData day) { when(clockMock.getCurrentDate()).thenReturn(day.getDate()); when(clockMock.getCurrentTime()).thenReturn(now); when(storageMock.loadOrCreate(YearMonth.from(day.getDate()))).thenReturn(monthIndexMock); final DayRecord dayRecord = new DayRecord(new ContractTermsService(TestingConfig.builder().build()), day, null, - monthIndexMock, projectServiceMock); + monthIndexMock, projectServiceMock, modelFactory); when(monthIndexMock.getDay(day.getDate())).thenReturn(dayRecord); appService.updateNow(); return dayRecord; } + + @Test + void store() + { + final DayData day = modelFactory.createDayData(); + day.setDate(LocalDate.of(2021, 1, 5)); + final DayRecord record = new DayRecord(contractTerms, day, null, monthIndexMock, + projectServiceMock, modelFactory); + final MonthData monthData = modelFactory.createMonthData(); + monthData.setYear(2021); + monthData.setMonth(Month.JANUARY); + monthData.setDays(new ArrayList<>()); + final MonthIndex monthIndex = MonthIndex.create(contractTerms, projectServiceMock, modelFactory, + monthData); + when(storageMock.loadOrCreate(YearMonth.of(2021, 1))).thenReturn(monthIndex); + + appService.store(record); + + assertThat(monthIndex.getDay(record.getDate())).isSameAs(record); + verify(storageMock).storeMonth(same(monthIndex)); + verify(updateListenerMock).recordUpdated(same(record)); + } + + @Test + void getAvailableDataYearMonth() + { + final ArrayList list = new ArrayList<>(); + when(storageMock.getAvailableDataMonths()).thenReturn(list); + assertThat(appService.getAvailableDataYearMonth()).isSameAs(list); + } + + @Test + void getOrCreateMonth() + { + final YearMonth month = YearMonth.of(2021, 1); + when(storageMock.loadOrCreate(month)).thenReturn(monthIndexMock); + assertThat(appService.getOrCreateMonth(month)).isSameAs(monthIndexMock); + } + + @Test + void addInterruption() + { + final DelegatingAppServiceCallback appServiceCallback = new DelegatingAppServiceCallback(); + appService = createAppService(appServiceCallback, workingTimeServiceMock); + final Duration interruption = Duration.ofMinutes(10); + final LocalDate date = LocalDate.of(2021, 1, 1); + + appService.addInterruption(date, interruption); + + verify(workingTimeServiceMock).addInterruption(date, interruption); + } + + @Test + void generateProjectReport() + { + final YearMonth month = YearMonth.of(2021, 1); + final ProjectReport projectReport = mock(ProjectReport.class); + when(projectReportGeneratorMock.generateReport(month)).thenReturn(projectReport); + + assertThat(appService.generateProjectReport(month)).isSameAs(projectReport); + verify(projectReportGeneratorMock).generateReport(month); + } + + @Test + void getVacationReport() + { + final VacationReport vacationReport = mock(VacationReport.class); + when(vacationReportGeneratorMock.generateReport()).thenReturn(vacationReport); + assertThat(appService.getVacationReport()).isSameAs(vacationReport); + } + + @Test + void activities() + { + assertThat(appService.activities()).isSameAs(activityService); + } + + @Test + void projects() + { + assertThat(appService.projects()).isSameAs(projectServiceMock); + } + + @Test + void autocomplete() + { + assertThat(appService.autocomplete()).isSameAs(autocompleteServiceMock); + } + + @Test + void formatter() + { + assertThat(appService.formatter()).isSameAs(formatterServiceMock); + } + + @Test + void pluginManager() + { + assertThat(appService.pluginManager()).isSameAs(pluginManagerMock); + } + + @Test + void registerSingleInstance_otherInstanceAlreadyRunning() + { + final RunningInstanceCallback callback = mock(RunningInstanceCallback.class); + simulateOtherInstance(callback, true); + assertThat(appService.registerSingleInstance(callback)).isPresent(); + } + + private void simulateOtherInstance(final RunningInstanceCallback callback, boolean otherInstanceRunning) + { + final RegistrationResult registrationResultMock = mock(RegistrationResult.class); + when(registrationResultMock.isOtherInstanceRunning()).thenReturn(otherInstanceRunning); + when(singleInstanceService.tryToRegisterInstance(same(callback))).thenReturn(registrationResultMock); + } + + @Test + void registerSingleInstance_noInstanceAlreadyRunning() + { + final RunningInstanceCallback callback = mock(RunningInstanceCallback.class); + simulateOtherInstance(callback, false); + assertThat(appService.registerSingleInstance(callback)).isEmpty(); + } } diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ClockServiceTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ClockServiceTest.java new file mode 100644 index 00000000..31ab7e96 --- /dev/null +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/ClockServiceTest.java @@ -0,0 +1,57 @@ +package org.itsallcode.whiterabbit.logic.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.YearMonth; +import java.time.ZoneId; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ClockServiceTest +{ + private static final Instant NOW = Instant.parse("2007-12-03T10:15:30.00Z"); + private ClockService clockService; + + @BeforeEach + void setUp() + { + clockService = new ClockService( + Clock.fixed(NOW, ZoneId.of("Europe/Berlin"))); + } + + @Test + void testGetCurrentDate() + { + assertThat(clockService.getCurrentDate()).isEqualTo(LocalDate.of(2007, 12, 3)); + } + + @Test + void testGetCurrentYearMonth() + { + assertThat(clockService.getCurrentYearMonth()).isEqualTo(YearMonth.of(2007, 12)); + } + + @Test + void testGetCurrentTime() + { + assertThat(clockService.getCurrentTime()).isEqualTo(LocalTime.of(11, 15)); + } + + @Test + void testGetDurationUntilNextFullMinute() + { + assertThat(clockService.getDurationUntilNextFullMinute()).isEqualTo(Duration.ofSeconds(30)); + } + + @Test + void testInstant() + { + assertThat(clockService.instant()).isSameAs(NOW); + } +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/FormatterServiceTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/FormatterServiceTest.java new file mode 100644 index 00000000..cdf590bf --- /dev/null +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/FormatterServiceTest.java @@ -0,0 +1,35 @@ +package org.itsallcode.whiterabbit.logic.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Locale; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FormatterServiceTest +{ + private FormatterService formatter; + + @BeforeEach + void setUp() + { + formatter = new FormatterService(Locale.GERMAN, ZoneId.of("Europe/Berlin")); + } + + @Test + void testFormatDuration() + { + assertThat(formatter.format(Duration.ofHours(4).plusMinutes(42).plusSeconds(31))).isEqualTo("04:42"); + } + + @Test + void testFormatDateAndTime() + { + assertThat(formatter.formatDateAndTime(Instant.parse("2007-12-03T10:15:30.00Z"))) + .isEqualTo("03.12.07, 11:15:30"); + } +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManagerTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManagerTest.java new file mode 100644 index 00000000..19a845d4 --- /dev/null +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginManagerTest.java @@ -0,0 +1,151 @@ +package org.itsallcode.whiterabbit.logic.service.plugin; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; +import org.itsallcode.whiterabbit.logic.Config; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PluginManagerTest +{ + @Mock + private PluginRegistry pluginRegistryMock; + + @Mock + private PluginWrapper plugin1; + @Mock + private PluginWrapper plugin2; + + private PluginManager pluginManager; + + @BeforeEach + void setUp() + { + pluginManager = new PluginManager(pluginRegistryMock); + lenient().when(plugin1.getId()).thenReturn("plugin1"); + lenient().when(plugin2.getId()).thenReturn("plugin2"); + } + + @Test + void create() + { + final Config config = mock(Config.class); + assertThat(PluginManager.create(config)).isNotNull(); + } + + @Test + void getProjectReportExporterPlugins_noPluginAvailable() + { + simulatePlugins(); + assertThat(pluginManager.getProjectReportExporterPlugins()).isEmpty(); + } + + @Test + void getProjectReportExporterPlugins_unsupportedPluginAvailable() + { + simulatePlugins(plugin1); + when(plugin1.supports(ProjectReportExporter.class)).thenReturn(false); + assertThat(pluginManager.getProjectReportExporterPlugins()).isEmpty(); + } + + @Test + void getProjectReportExporterPlugins_supportedPluginAvailable() + { + simulatePlugins(plugin1); + when(plugin1.supports(ProjectReportExporter.class)).thenReturn(true); + assertThat(pluginManager.getProjectReportExporterPlugins()).containsExactly("plugin1"); + } + + private void simulatePlugins(PluginWrapper... plugins) + { + lenient().when(pluginRegistryMock.getAllPlugins()).thenReturn(asList(plugins)); + for (final PluginWrapper plugin : plugins) + { + lenient().when(pluginRegistryMock.getPlugin(plugin.getId())).thenReturn(plugin); + } + } + + @Test + void getProjectReportExporter_noPluginAvailable_throwsException() + { + simulatePlugins(); + assertThatThrownBy(() -> pluginManager.getProjectReportExporter("unknown")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Plugin 'unknown' not found"); + } + + @Test + void getProjectReportExporter_availableButNotSupported() + { + simulatePlugins(plugin1); + when(plugin1.supports(ProjectReportExporter.class)).thenReturn(false); + assertThatThrownBy(() -> pluginManager.getProjectReportExporter("plugin1")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Plugin 'plugin1' does not support feature " + ProjectReportExporter.class.getName()); + } + + @Test + void getProjectReportExporter_availableAndSupported() + { + simulatePlugins(plugin1); + when(plugin1.supports(ProjectReportExporter.class)).thenReturn(true); + final ProjectReportExporter featureMock = mock(ProjectReportExporter.class); + when(plugin1.getFeature(ProjectReportExporter.class)).thenReturn(featureMock); + + assertThat(pluginManager.getProjectReportExporter("plugin1")).isNotNull().isSameAs(featureMock); + } + + @Test + void getMonthDataStorage_singleInstanceAvailable() + { + simulatePlugins(plugin1); + when(plugin1.supports(MonthDataStorage.class)).thenReturn(true); + final MonthDataStorage featureMock = mock(MonthDataStorage.class); + when(plugin1.getFeature(MonthDataStorage.class)).thenReturn(featureMock); + + assertThat(pluginManager.getMonthDataStorage()).isPresent().containsSame(featureMock); + } + + @Test + void getMonthDataStorage_noInstanceAvailable() + { + simulatePlugins(plugin1); + when(plugin1.supports(MonthDataStorage.class)).thenReturn(false); + + assertThat(pluginManager.getMonthDataStorage()).isEmpty(); + } + + @Test + void getMonthDataStorage_multipleInstancesAvailable() + { + simulatePlugins(plugin1, plugin2); + when(plugin1.supports(MonthDataStorage.class)).thenReturn(true); + when(plugin2.supports(MonthDataStorage.class)).thenReturn(true); + + assertThatThrownBy(() -> pluginManager.getMonthDataStorage()) + .isInstanceOf(IllegalStateException.class) + .hasMessage( + "Found multiple plugins supporting " + MonthDataStorage.class.getName() + + ": [plugin1, plugin2]. Please add only one storage plugin to the classpath."); + } + + @Test + void close() + { + pluginManager.close(); + verify(pluginRegistryMock).close(); + } + +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistryTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistryTest.java new file mode 100644 index 00000000..b6452bef --- /dev/null +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/PluginRegistryTest.java @@ -0,0 +1,136 @@ +package org.itsallcode.whiterabbit.logic.service.plugin; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; + +import org.itsallcode.whiterabbit.logic.Config; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PluginRegistryTest +{ + @Mock + private Config configMock; + private PluginRegistry pluginRegistry; + + @BeforeEach + void setUp() + { + pluginRegistry = new PluginRegistry(configMock); + } + + @Test + void loadFromEmptyPluginDir(@TempDir Path tempDir) + { + when(configMock.getPluginDir()).thenReturn(tempDir); + pluginRegistry.load(); + assertThat(pluginRegistry.getAllPlugins()).hasSize(1); + } + + @Test + void loadFromNonExistingPluginDir() + { + when(configMock.getPluginDir()).thenReturn(Paths.get("no-such-directory")); + pluginRegistry.load(); + assertThat(pluginRegistry.getAllPlugins()).hasSize(1); + } + + @Test + void loadInvalidJar(@TempDir Path tempDir) throws IOException + { + when(configMock.getPluginDir()).thenReturn(tempDir); + Files.writeString(tempDir.resolve("invalid-plugin.jar"), ""); + pluginRegistry.load(); + assertThat(pluginRegistry.getAllPlugins()).hasSize(1); + } + + @Test + void getAllPlugins_returnsEmptyList_WhenNotLoadedYet() + { + assertThat(pluginRegistry.getAllPlugins()).isEmpty(); + } + + @Test + void getAllPlugins_returnsPlugins_WhenLoaded() + { + pluginRegistry.load(); + final Collection plugins = pluginRegistry.getAllPlugins(); + assertThat(plugins).hasSize(1); + final PluginWrapper plugin = plugins.iterator().next(); + verifyTestingPlugin(plugin); + } + + private void verifyTestingPlugin(final PluginWrapper plugin) + { + final TestingPlugin pluginInstance = plugin.getFeature(TestingPlugin.class); + assertThat(pluginInstance).isInstanceOf(TestingPlugin.class); + assertThat(pluginInstance.getId()).isEqualTo(TestingPlugin.PLUGIN_ID); + assertThat(pluginInstance.isClosed()).isFalse(); + } + + @Test + void getPlugin_failsWhenNotLoadedAndPluginNotAvailable() + { + assertThatThrownBy(() -> pluginRegistry.getPlugin("unknown")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Plugin 'unknown' not found. Available plugins: []"); + } + + @Test + void getPlugin_failsWhenLoadedAndPluginNotAvailable() + { + pluginRegistry.load(); + assertThatThrownBy(() -> pluginRegistry.getPlugin("unknown")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Plugin 'unknown' not found. Available plugins: [" + TestingPlugin.PLUGIN_ID + "]"); + } + + @Test + void getPlugin_returnsPluginWhenAvailable() + { + pluginRegistry.load(); + verifyTestingPlugin(pluginRegistry.getPlugin(TestingPlugin.PLUGIN_ID)); + } + + @Test + void pluginConfig_getMandatoryValue_ReturnsValue() + { + pluginRegistry.load(); + final TestingPlugin pluginInstance = pluginRegistry.getPlugin(TestingPlugin.PLUGIN_ID) + .getFeature(TestingPlugin.class); + + when(configMock.getMandatoryValue(TestingPlugin.PLUGIN_ID + ".property")).thenReturn("propertyValue"); + assertThat(pluginInstance.getConfig().getMandatoryValue("property")).isEqualTo("propertyValue"); + } + + @Test + void close_whenNoPluginLoaded() + { + assertDoesNotThrow(() -> pluginRegistry.close()); + } + + @Test + void close_closesPlugins() + { + pluginRegistry.load(); + final TestingPlugin plugin = pluginRegistry.getPlugin(TestingPlugin.PLUGIN_ID).getFeature(TestingPlugin.class); + assertThat(plugin.isClosed()).isFalse(); + + pluginRegistry.close(); + + assertThat(plugin.isClosed()).isTrue(); + } +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/TestingPlugin.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/TestingPlugin.java new file mode 100644 index 00000000..f73e709d --- /dev/null +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/service/plugin/TestingPlugin.java @@ -0,0 +1,52 @@ +package org.itsallcode.whiterabbit.logic.service.plugin; + +import org.itsallcode.whiterabbit.api.Plugin; +import org.itsallcode.whiterabbit.api.PluginConfiguration; +import org.itsallcode.whiterabbit.api.features.PluginFeature; + +public class TestingPlugin implements Plugin, PluginFeature +{ + static final String PLUGIN_ID = "testingPlugin"; + private PluginConfiguration config; + private boolean closed = false; + + @Override + public void init(PluginConfiguration config) + { + this.config = config; + } + + @Override + public String getId() + { + return PLUGIN_ID; + } + + @Override + public void close() + { + this.closed = true; + } + + @Override + public boolean supports(Class featureType) + { + return TestingPlugin.class.isAssignableFrom(featureType); + } + + @Override + public T getFeature(Class featureType) + { + return featureType.cast(this); + } + + public PluginConfiguration getConfig() + { + return config; + } + + public boolean isClosed() + { + return closed; + } +} diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImplTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImplTest.java index 81934975..1dbb03be 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImplTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/CachingStorageImplTest.java @@ -99,7 +99,7 @@ void loadAll_doesNotUpdateCache_whenNoEntriesFound() @Test void getRequiredYearMonths_returnsEmptyList_whenNoDataAvailable() { - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(emptyList()); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(emptyList()); assertThat(storage.getRequiredYearMonths(LocalDate.of(2020, Month.JANUARY, 1))).isEmpty(); } @@ -111,7 +111,7 @@ void getRequiredYearMonths_returnsRequiredMonths() final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); final YearMonth march = YearMonth.of(2020, Month.MARCH); final YearMonth april = YearMonth.of(2020, Month.APRIL); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february, march, april)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february, march, april)); assertThat(storage.getRequiredYearMonths(LocalDate.of(2020, Month.JANUARY, 1))) .containsExactly(january, february, march, april); @@ -124,7 +124,7 @@ void getRequiredYearMonths_includesMonthForLastDayOfTheMonth() final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); final YearMonth march = YearMonth.of(2020, Month.MARCH); final YearMonth april = YearMonth.of(2020, Month.APRIL); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february, march, april)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february, march, april)); assertThat(storage.getRequiredYearMonths(LocalDate.of(2020, Month.JANUARY, 31))) .containsExactly(january, february, march, april); @@ -137,7 +137,7 @@ void getRequiredYearMonths_omitsOlderMonths() final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); final YearMonth march = YearMonth.of(2020, Month.MARCH); final YearMonth april = YearMonth.of(2020, Month.APRIL); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february, march, april)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february, march, april)); assertThat(storage.getRequiredYearMonths(LocalDate.of(2020, Month.FEBRUARY, 1))) .containsExactly(february, march, april); @@ -148,7 +148,7 @@ void ensureLatestDaysCached_dataAvailable() { final YearMonth january = YearMonth.of(2020, Month.JANUARY); final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february)); when(cacheMock.contains(january)).thenReturn(false); when(cacheMock.contains(february)).thenReturn(false); @@ -172,7 +172,7 @@ void ensureLatestDaysCached_dataAvailable_alreadyCached() { final YearMonth january = YearMonth.of(2020, Month.JANUARY); final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february)); when(cacheMock.contains(january)).thenReturn(true); when(cacheMock.contains(february)).thenReturn(true); @@ -188,7 +188,7 @@ void ensureLatestDaysCached_cacheNotUpdatedWhenMonthNotFound() { final YearMonth january = YearMonth.of(2020, Month.JANUARY); final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february)); when(delegateStorageMock.loadMonth(january)).thenReturn(Optional.empty()); when(delegateStorageMock.loadMonth(february)).thenReturn(Optional.empty()); @@ -204,7 +204,7 @@ void getLatestDays_delegatesToCache() final YearMonth january = YearMonth.of(2020, Month.JANUARY); final YearMonth february = YearMonth.of(2020, Month.FEBRUARY); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(List.of(january, february)); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(List.of(january, february)); when(cacheMock.contains(january)).thenReturn(false); when(cacheMock.contains(february)).thenReturn(false); @@ -237,7 +237,7 @@ private void verifyListenerUpdated() void getAvailableDataYearMonth_delegates() { final List list = new ArrayList<>(); - when(delegateStorageMock.getAvailableDataYearMonth()).thenReturn(list); - assertThat(storage.getAvailableDataYearMonth()).isSameAs(list); + when(delegateStorageMock.getAvailableDataMonths()).thenReturn(list); + assertThat(storage.getAvailableDataMonths()).isSameAs(list); } } diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorageTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorageTest.java index f010aa1a..cfb920a0 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorageTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/MonthIndexStorageTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -14,11 +15,13 @@ import java.util.List; import java.util.Optional; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.itsallcode.whiterabbit.logic.model.MonthIndex; import org.itsallcode.whiterabbit.logic.model.MultiMonthIndex; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; import org.itsallcode.whiterabbit.logic.service.contract.ContractTermsService; import org.itsallcode.whiterabbit.logic.service.project.ProjectService; +import org.itsallcode.whiterabbit.logic.storage.data.JsonModelFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,7 +39,7 @@ class MonthIndexStorageTest @Mock ProjectService projectServiceMock; @Mock - JsonFileStorage fileStorageMock; + MonthDataStorage fileStorageMock; @Mock MonthIndex monthIndexMock; @@ -46,12 +49,13 @@ class MonthIndexStorageTest void setUp() { storage = new MonthIndexStorage(contractTermsMock, projectServiceMock, fileStorageMock); + lenient().when(fileStorageMock.getModelFactory()).thenReturn(new JsonModelFactory()); } @Test void loadPreviousMonthOvertime_returnsZero_whenNoPreviousMonth() { - when(fileStorageMock.loadMonthRecord(YearMonth.of(2020, Month.OCTOBER))).thenReturn(Optional.empty()); + when(fileStorageMock.load(YearMonth.of(2020, Month.OCTOBER))).thenReturn(Optional.empty()); assertThat(storage.loadPreviousMonthOvertime(YEAR_MONTH)).isZero(); } @@ -59,7 +63,7 @@ void loadPreviousMonthOvertime_returnsZero_whenNoPreviousMonth() @Test void loadPreviousMonthOvertime_returnsPreviousMonthOvertime() { - when(fileStorageMock.loadMonthRecord(PREVIOUS_MONTH)) + when(fileStorageMock.load(PREVIOUS_MONTH)) .thenReturn(Optional.of(jsonMonth(PREVIOUS_MONTH, Duration.ofMinutes(5)))); assertThat(storage.loadPreviousMonthOvertime(YEAR_MONTH)).hasMinutes(5); @@ -68,7 +72,7 @@ void loadPreviousMonthOvertime_returnsPreviousMonthOvertime() @Test void loadPreviousMonthOvertime_truncatesToMinute() { - when(fileStorageMock.loadMonthRecord(PREVIOUS_MONTH)) + when(fileStorageMock.load(PREVIOUS_MONTH)) .thenReturn(Optional.of(jsonMonth(PREVIOUS_MONTH, Duration.ofMinutes(5).plusSeconds(50)))); assertThat(storage.loadPreviousMonthOvertime(YEAR_MONTH)).hasMinutes(5); @@ -77,7 +81,7 @@ void loadPreviousMonthOvertime_truncatesToMinute() @Test void loadOrCreate_createsNewMonthIfNotExists() { - when(fileStorageMock.loadMonthRecord(YEAR_MONTH)).thenReturn(Optional.empty()); + when(fileStorageMock.load(YEAR_MONTH)).thenReturn(Optional.empty()); final MonthIndex newMonth = storage.loadOrCreate(YEAR_MONTH); @@ -90,8 +94,8 @@ void loadOrCreate_createsNewMonthIfNotExists() @Test void loadOrCreate_loadsExistingMonth() { - final JsonMonth month = jsonMonth(YEAR_MONTH, Duration.ofMinutes(4)); - when(fileStorageMock.loadMonthRecord(YEAR_MONTH)).thenReturn(Optional.of(month)); + final MonthData month = jsonMonth(YEAR_MONTH, Duration.ofMinutes(4)); + when(fileStorageMock.load(YEAR_MONTH)).thenReturn(Optional.of(month)); final MonthIndex newMonth = storage.loadOrCreate(YEAR_MONTH); @@ -105,13 +109,13 @@ void loadOrCreate_loadsExistingMonth() @Test void storeMonth() { - final JsonMonth month = jsonMonth(YEAR_MONTH, Duration.ofMinutes(4)); + final MonthData month = jsonMonth(YEAR_MONTH, Duration.ofMinutes(4)); when(monthIndexMock.getMonthRecord()).thenReturn(month); when(monthIndexMock.getYearMonth()).thenReturn(YEAR_MONTH); storage.storeMonth(monthIndexMock); - verify(fileStorageMock).writeToFile(eq(YEAR_MONTH), same(month)); + verify(fileStorageMock).store(eq(YEAR_MONTH), same(month)); } @Test @@ -140,14 +144,14 @@ void loadAll_nonEmpty() void getAvailableDataYearMonth() { final List availableYearMonths = List.of(YEAR_MONTH); - when(fileStorageMock.getAvailableDataYearMonth()).thenReturn(availableYearMonths); + when(fileStorageMock.getAvailableDataMonths()).thenReturn(availableYearMonths); - assertThat(storage.getAvailableDataYearMonth()).isSameAs(availableYearMonths); + assertThat(storage.getAvailableDataMonths()).isSameAs(availableYearMonths); } - private JsonMonth jsonMonth(YearMonth yearMonth, Duration overtimePreviousMonth) + private MonthData jsonMonth(YearMonth yearMonth, Duration overtimePreviousMonth) { - final JsonMonth month = new JsonMonth(); + final MonthData month = new JsonModelFactory().createMonthData(); month.setYear(yearMonth.getYear()); month.setMonth(yearMonth.getMonth()); month.setOvertimePreviousMonth(overtimePreviousMonth); diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapperTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapperTest.java similarity index 98% rename from logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapperTest.java rename to logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapperTest.java index cbf7af96..8d303f72 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/DateToFileMapperTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/DateToFileMapperTest.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.storage; +package org.itsallcode.whiterabbit.logic.storage.data; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorageTest.java b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorageTest.java similarity index 63% rename from logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorageTest.java rename to logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorageTest.java index 863c9f98..bcc36a84 100644 --- a/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/JsonFileStorageTest.java +++ b/logic/src/test/java/org/itsallcode/whiterabbit/logic/storage/data/JsonFileStorageTest.java @@ -1,4 +1,4 @@ -package org.itsallcode.whiterabbit.logic.storage; +package org.itsallcode.whiterabbit.logic.storage.data; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -16,10 +16,10 @@ import java.util.stream.Stream; import javax.json.bind.Jsonb; -import javax.json.bind.JsonbBuilder; -import javax.json.bind.JsonbConfig; -import org.itsallcode.whiterabbit.logic.model.json.JsonMonth; +import org.itsallcode.whiterabbit.api.features.MonthDataStorage.ModelFactory; +import org.itsallcode.whiterabbit.api.model.ActivityData; +import org.itsallcode.whiterabbit.api.model.MonthData; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,6 +35,8 @@ class JsonFileStorageTest Path tempDir; @Mock DateToFileMapper dateToFileMapperMock; + @Mock + private ModelFactory modelFactoryMock; JsonFileStorage jsonFileStorage; Jsonb jsonb; @@ -42,8 +44,8 @@ class JsonFileStorageTest @BeforeEach void setUp() { - jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(false)); - jsonFileStorage = new JsonFileStorage(jsonb, dateToFileMapperMock); + jsonb = new JsonbFactory().createNonFormatting(); + jsonFileStorage = new JsonFileStorage(jsonb, dateToFileMapperMock, modelFactoryMock); } @Test @@ -53,14 +55,14 @@ void sortsAvailableYearMonths() final YearMonth january2020 = YearMonth.of(2020, Month.JANUARY); final YearMonth december2019 = YearMonth.of(2019, Month.DECEMBER); when(dateToFileMapperMock.getAllYearMonths()).thenReturn(Stream.of(april2020, january2020, december2019)); - assertThat(jsonFileStorage.getAvailableDataYearMonth()).containsExactly(december2019, january2020, april2020); + assertThat(jsonFileStorage.getAvailableDataMonths()).containsExactly(december2019, january2020, april2020); } @Test void returnsEmptyAvailableMonths() { when(dateToFileMapperMock.getAllYearMonths()).thenReturn(Stream.empty()); - assertThat(jsonFileStorage.getAvailableDataYearMonth()).isEmpty(); + assertThat(jsonFileStorage.getAvailableDataMonths()).isEmpty(); } @Test @@ -68,19 +70,19 @@ void loadMonthReturnsEmptyOptional_WhenLegacyFileDoesNotExist() { when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(tempDir.resolve("does-not-exist")); when(dateToFileMapperMock.getLegacyPathForDate(YEAR_MONTH)).thenReturn(tempDir.resolve("does-not-exist")); - assertThat(jsonFileStorage.loadMonthRecord(YEAR_MONTH)).isEmpty(); + assertThat(jsonFileStorage.load(YEAR_MONTH)).isEmpty(); } @Test void loadMonthReturnsMonth_WhenLegacyFileExists() throws IOException { - final JsonMonth month = new JsonMonth(); + final MonthData month = new JsonMonth(); month.setYear(2020); final Path file = writeTempFile(month); when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(tempDir.resolve("does-not-exist")); when(dateToFileMapperMock.getLegacyPathForDate(YEAR_MONTH)).thenReturn(file); - final Optional loadedMonth = jsonFileStorage.loadMonthRecord(YEAR_MONTH); + final Optional loadedMonth = jsonFileStorage.load(YEAR_MONTH); assertThat(loadedMonth).isNotEmpty(); assertThat(loadedMonth.get().getYear()).isEqualTo(2020); } @@ -88,25 +90,60 @@ void loadMonthReturnsMonth_WhenLegacyFileExists() throws IOException @Test void loadMonthReturnsMonth_WhenFileExists() throws IOException { - final JsonMonth month = new JsonMonth(); + final MonthData month = new JsonMonth(); month.setYear(2020); final Path file = writeTempFile(month); when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(file); - final Optional loadedMonth = jsonFileStorage.loadMonthRecord(YEAR_MONTH); + final Optional loadedMonth = jsonFileStorage.load(YEAR_MONTH); assertThat(loadedMonth).isNotEmpty(); assertThat(loadedMonth.get().getYear()).isEqualTo(2020); } + @Test + void loadMonthReturnsMonth_WithDays() throws IOException + { + final MonthData month = new JsonMonth(); + month.setYear(2020); + final JsonDay day = new JsonDay(); + day.setComment("comment"); + month.setDays(List.of(day)); + final Path file = writeTempFile(month); + + when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(file); + final Optional loadedMonth = jsonFileStorage.load(YEAR_MONTH); + assertThat(loadedMonth.get().getDays()).hasSize(1); + assertThat(loadedMonth.get().getDays().get(0).getComment()).isEqualTo("comment"); + } + + @Test + void loadMonthReturnsMonth_WithActivities() throws IOException + { + final MonthData month = new JsonMonth(); + month.setYear(2020); + final JsonDay day = new JsonDay(); + final JsonActivity activity = new JsonActivity(); + activity.setProjectId("project"); + day.setActivities(List.of(activity)); + month.setDays(List.of(day)); + final Path file = writeTempFile(month); + + when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(file); + final Optional loadedMonth = jsonFileStorage.load(YEAR_MONTH); + final List activities = loadedMonth.get().getDays().get(0).getActivities(); + assertThat(activities).hasSize(1); + assertThat(activities.get(0).getProjectId()).isEqualTo("project"); + } + @Test void writeToFile_writesFile() throws IOException { final Path file = createTempFile(); when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(file); - final JsonMonth month = new JsonMonth(); + final MonthData month = new JsonMonth(); month.setYear(2020); - jsonFileStorage.writeToFile(YEAR_MONTH, month); + jsonFileStorage.store(YEAR_MONTH, month); assertThat(file).exists().hasContent("{\"year\":2020}"); } @@ -117,9 +154,9 @@ void writeToFile_createsDirectory() throws IOException final Path file = tempDir.resolve("sub-dir1").resolve("sub-dir2").resolve("file.json"); when(dateToFileMapperMock.getPathForDate(YEAR_MONTH)).thenReturn(file); - final JsonMonth month = new JsonMonth(); + final MonthData month = new JsonMonth(); month.setYear(2020); - jsonFileStorage.writeToFile(YEAR_MONTH, month); + jsonFileStorage.store(YEAR_MONTH, month); assertThat(file).exists().hasContent("{\"year\":2020}"); } @@ -148,12 +185,12 @@ void loadAll_returnsFiles() throws IOException final Path file2 = tempDir.resolve("file2.json"); when(dateToFileMapperMock.getAllFiles()).thenReturn(Stream.of(file1, file2)); - final JsonMonth month1 = month(2019, Month.DECEMBER); - final JsonMonth month2 = month(2020, Month.JANUARY); + final MonthData month1 = month(2019, Month.DECEMBER); + final MonthData month2 = month(2020, Month.JANUARY); writeMonth(month1, file1); writeMonth(month2, file2); - final List months = jsonFileStorage.loadAll(); + final List months = jsonFileStorage.loadAll(); assertThat(months).hasSize(2); assertMonth(months.get(0), month1); @@ -168,14 +205,14 @@ void loadAll_sortsByYearMonth() throws IOException final Path file3 = tempDir.resolve("file3.json"); when(dateToFileMapperMock.getAllFiles()).thenReturn(Stream.of(file3, file2, file1)); - final JsonMonth month1 = month(2019, Month.DECEMBER); - final JsonMonth month2 = month(2020, Month.JANUARY); - final JsonMonth month3 = month(2020, Month.APRIL); + final MonthData month1 = month(2019, Month.DECEMBER); + final MonthData month2 = month(2020, Month.JANUARY); + final MonthData month3 = month(2020, Month.APRIL); writeMonth(month1, file1); writeMonth(month2, file2); writeMonth(month3, file3); - final List months = jsonFileStorage.loadAll(); + final List months = jsonFileStorage.loadAll(); assertThat(months).hasSize(3); assertMonth(months.get(0), month1); @@ -183,28 +220,34 @@ void loadAll_sortsByYearMonth() throws IOException assertMonth(months.get(2), month3); } - private void assertMonth(JsonMonth actual, JsonMonth expected) + @Test + void getModelFactory() + { + assertThat(jsonFileStorage.getModelFactory()).isSameAs(modelFactoryMock); + } + + private void assertMonth(MonthData actual, MonthData expected) { assertAll(() -> assertThat(actual.getYear()).isEqualTo(expected.getYear()), () -> assertThat(actual.getMonth()).isEqualTo(expected.getMonth())); } - private JsonMonth month(int year, Month month) + private MonthData month(int year, Month month) { - final JsonMonth jsonMonth = new JsonMonth(); + final MonthData jsonMonth = new JsonMonth(); jsonMonth.setYear(year); jsonMonth.setMonth(month); return jsonMonth; } - private Path writeTempFile(JsonMonth month) throws IOException + private Path writeTempFile(MonthData month) throws IOException { final Path file = createTempFile(); writeMonth(month, file); return file; } - private void writeMonth(JsonMonth month, final Path file) throws IOException + private void writeMonth(MonthData month, final Path file) throws IOException { Files.writeString(file, jsonb.toJson(month)); } diff --git a/logic/src/test/resources/META-INF/services/org.itsallcode.whiterabbit.api.Plugin b/logic/src/test/resources/META-INF/services/org.itsallcode.whiterabbit.api.Plugin new file mode 100644 index 00000000..f5e3c6e3 --- /dev/null +++ b/logic/src/test/resources/META-INF/services/org.itsallcode.whiterabbit.api.Plugin @@ -0,0 +1 @@ +org.itsallcode.whiterabbit.logic.service.plugin.TestingPlugin diff --git a/plugins/build.gradle b/plugins/build.gradle index d343eb8e..df89f59d 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -11,6 +11,12 @@ subprojects { archiveClassifier.set(null) archiveVersion.set("") } + + eclipse { + classpath { + plusConfigurations += [ configurations.compileOnly ] + } + } } def pluginBuildTasks = project.subprojects.collect {it.tasks['shadowJar']} diff --git a/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoPlugin.java b/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoPlugin.java index 467b7ad8..73fcfe96 100644 --- a/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoPlugin.java +++ b/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoPlugin.java @@ -2,6 +2,7 @@ import org.itsallcode.whiterabbit.api.Plugin; import org.itsallcode.whiterabbit.api.PluginConfiguration; +import org.itsallcode.whiterabbit.api.features.PluginFeature; public class DemoPlugin implements Plugin { @@ -24,13 +25,13 @@ public void close() } @Override - public boolean supports(Class featureType) + public boolean supports(Class featureType) { return featureType.isAssignableFrom(DemoProjectReportExporter.class); } @Override - public T getFeature(Class featureType) + public T getFeature(Class featureType) { if (featureType.isAssignableFrom(DemoProjectReportExporter.class)) { diff --git a/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoProjectReportExporter.java b/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoProjectReportExporter.java index f0099073..c3d22113 100644 --- a/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoProjectReportExporter.java +++ b/plugins/demo/src/main/java/org/itsallcode/whiterabbit/plugin/demo/DemoProjectReportExporter.java @@ -2,8 +2,8 @@ import java.time.Duration; -import org.itsallcode.whiterabbit.api.ProgressMonitor; -import org.itsallcode.whiterabbit.api.ProjectReportExporter; +import org.itsallcode.whiterabbit.api.features.ProgressMonitor; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; import org.itsallcode.whiterabbit.api.model.ProjectReport; import org.itsallcode.whiterabbit.api.model.ProjectReportActivity; import org.itsallcode.whiterabbit.api.model.ProjectReportDay; diff --git a/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExportPlugin.java b/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExportPlugin.java index a36fa781..563d661a 100644 --- a/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExportPlugin.java +++ b/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExportPlugin.java @@ -2,6 +2,7 @@ import org.itsallcode.whiterabbit.api.Plugin; import org.itsallcode.whiterabbit.api.PluginConfiguration; +import org.itsallcode.whiterabbit.api.features.PluginFeature; import org.itsallcode.whiterabbit.plugin.pmsmart.web.WebDriverFactory; public class PMSmartExportPlugin implements Plugin @@ -15,13 +16,13 @@ public void init(PluginConfiguration config) } @Override - public boolean supports(Class featureType) + public boolean supports(Class featureType) { return featureType.isAssignableFrom(PMSmartExporter.class); } @Override - public T getFeature(Class featureType) + public T getFeature(Class featureType) { if (featureType.isAssignableFrom(PMSmartExporter.class)) { diff --git a/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExporter.java b/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExporter.java index 38a7508b..eb11e7ed 100644 --- a/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExporter.java +++ b/plugins/pmsmart/src/main/java/org/itsallcode/whiterabbit/plugin/pmsmart/PMSmartExporter.java @@ -3,8 +3,8 @@ import java.util.Map; import org.itsallcode.whiterabbit.api.PluginConfiguration; -import org.itsallcode.whiterabbit.api.ProgressMonitor; -import org.itsallcode.whiterabbit.api.ProjectReportExporter; +import org.itsallcode.whiterabbit.api.features.ProgressMonitor; +import org.itsallcode.whiterabbit.api.features.ProjectReportExporter; import org.itsallcode.whiterabbit.api.model.ProjectReport; import org.itsallcode.whiterabbit.api.model.ProjectReportActivity; import org.itsallcode.whiterabbit.api.model.ProjectReportDay;