🔀 Merge branch 'website/epic-games' into next
[rhynodge.git] / src / main / java / net / pterodactylus / rhynodge / states / StateManager.java
1 /*
2  * Rhynodge - StateManager.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.rhynodge.states;
19
20 import static java.util.Optional.empty;
21 import static java.util.Optional.ofNullable;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.Optional;
26
27 import com.fasterxml.jackson.databind.SerializationFeature;
28 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
29 import com.fasterxml.jackson.module.kotlin.KotlinModule;
30 import jakarta.inject.Inject;
31 import jakarta.inject.Singleton;
32
33 import net.pterodactylus.rhynodge.State;
34
35 import org.apache.log4j.Logger;
36
37 import com.fasterxml.jackson.core.JsonGenerationException;
38 import com.fasterxml.jackson.core.JsonParseException;
39 import com.fasterxml.jackson.databind.JsonMappingException;
40 import com.fasterxml.jackson.databind.ObjectMapper;
41
42 /**
43  * Loads and saves {@link State}s.
44  *
45  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
46  */
47 @Singleton
48 public class StateManager {
49
50         /** The logger. */
51         private static final Logger logger = Logger.getLogger(StateManager.class);
52
53         /** Jackson object mapper. */
54         private final ObjectMapper objectMapper = new ObjectMapper();
55
56         {
57                 objectMapper.registerModule(new KotlinModule.Builder().build());
58                 objectMapper.registerModule(new JavaTimeModule());
59                 objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
60         }
61
62         /** The directory in which to store states. */
63         private final String directory;
64
65         /**
66          * Creates a new state manager. The given directory is assumed to exist.
67          *
68          * @param stateDirectory
69          *            The directory to store states in
70          */
71         @Inject
72         public StateManager(StateDirectory stateDirectory) {
73                 this.directory = stateDirectory.getDirectory();
74         }
75
76         //
77         // ACTIONS
78         //
79
80         /**
81          * Loads the last state with the given name.
82          *
83          * @param reactionName
84          *            The name of the reaction
85          * @return The loaded state, or {@link Optional#empty()} if the state could not be
86          *         loaded
87          */
88         public Optional<State> loadLastState(String reactionName) {
89                 return loadLastState(reactionName, false);
90         }
91
92         /**
93          * Loads the last state with the given name.
94          *
95          * @param reactionName
96          *            The name of the reaction
97          * @return The loaded state, or {@link Optional#empty()} if the state could not be
98          *         loaded
99          */
100         public Optional<State> loadLastSuccessfulState(String reactionName) {
101                 return loadLastState(reactionName, true);
102         }
103
104         /**
105          * Saves the given state under the given name.
106          *
107          * @param reactionName
108          *            The name of the reaction
109          * @param state
110          *            The state to save
111          */
112         public void saveState(String reactionName, State state) {
113                 File stateFile = null;
114                 try {
115                         stateFile = stateFile(reactionName, "last");
116                         objectMapper.writeValue(stateFile, state);
117                         if (state.success()) {
118                                 stateFile = stateFile(reactionName, "success");
119                                 objectMapper.writeValue(stateFile, state);
120                         }
121                 } catch (JsonGenerationException jge1) {
122                         logger.warn(String.format("State for Reaction “%s” could not be generated.", reactionName), jge1);
123                         stateFile.delete();
124                 } catch (JsonMappingException jme1) {
125                         logger.warn(String.format("State for Reaction “%s” could not be generated.", reactionName), jme1);
126                         stateFile.delete();
127                 } catch (IOException ioe1) {
128                         logger.warn(String.format("State for Reaction “%s” could not be written.", reactionName));
129                         stateFile.delete();
130                 }
131         }
132
133         //
134         // PRIVATE METHODS
135         //
136
137         /**
138          * Returns the file for the state with the given name.
139          *
140          * @param reactionName
141          *            The name of the reaction
142          * @param suffix
143          *            An additional suffix (may be {@code null}
144          * @return The file for the state
145          */
146         private File stateFile(String reactionName, String suffix) {
147                 return new File(directory, reactionName + ((suffix != null) ? "." + suffix : "") + ".json");
148         }
149
150         /**
151          * Load the given state for the reaction with the given name.
152          *
153          * @param reactionName
154          *            The name of the reaction
155          * @param successful
156          *            {@code true} to load the last successful state, {@code false}
157          *            to load the last state
158          * @return The loaded state, or {@link Optional#empty()} if the state could not be
159          *         loaded
160          */
161         private Optional<State> loadLastState(String reactionName, boolean successful) {
162                 File stateFile = stateFile(reactionName, successful ? "success" : "last");
163                 try {
164                         State state = objectMapper.readValue(stateFile, AbstractState.class);
165                         return ofNullable(state);
166                 } catch (JsonParseException jpe1) {
167                         logger.warn(String.format("State for Reaction “%s” could not be parsed.", reactionName), jpe1);
168                 } catch (JsonMappingException jme1) {
169                         logger.warn(String.format("State for Reaction “%s” could not be parsed.", reactionName), jme1);
170                 } catch (IOException ioe1) {
171                         logger.info(String.format("State for Reaction “%s” could not be found.", reactionName));
172                 }
173                 return empty();
174         }
175
176         public static class StateDirectory {
177
178                 private final String directory;
179
180                 private StateDirectory(String directory) {
181                         this.directory = directory;
182                 }
183
184                 public String getDirectory() {
185                         return directory;
186                 }
187
188                 public static StateDirectory of(String directory) {
189                         return new StateDirectory(directory);
190                 }
191
192         }
193
194 }