Gridarta Editor
Storage.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.preferences;
21 
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.LineNumberReader;
30 import java.io.OutputStreamWriter;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.TreeMap;
34 import java.util.TreeSet;
35 import java.util.prefs.BackingStoreException;
36 import java.util.regex.Pattern;
37 import net.sf.japi.swing.action.ActionBuilder;
38 import net.sf.japi.swing.action.ActionBuilderFactory;
39 import org.apache.log4j.Category;
40 import org.apache.log4j.Logger;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 
48 public class Storage {
49 
53  @NotNull
54  private static final Pattern PATTERN_EQUAL = Pattern.compile("=");
55 
59  @NotNull
60  private static final Category LOG = Logger.getLogger(Storage.class);
61 
65  @NotNull
66  private final String defaultPath;
67 
71  @Nullable
72  private final File file;
73 
77  private boolean noSave = true;
78 
82  @NotNull
83  private static final Pattern PATTERN_IGNORE = Pattern.compile("[\\[].*");
84 
88  @NotNull
89  private final Map<String, Map<String, String>> values = new TreeMap<>();
90 
97  public Storage(@NotNull final String defaultPath, @Nullable final File file) {
98  if (LOG.isDebugEnabled()) {
99  LOG.debug("new");
100  }
101 
102  this.defaultPath = defaultPath;
103  this.file = file;
104 
105  loadValues();
106  noSave = false;
107  }
108 
113  public void newNode(@NotNull final String path) {
114  if (LOG.isDebugEnabled()) {
115  LOG.debug("newNode(" + path + ")");
116  }
117 
118  if (!values.containsKey(path)) {
119  values.put(path, new TreeMap<>());
120  }
121  }
122 
129  @NotNull
130  public String[] childrenNames(@NotNull final String path) {
131  if (LOG.isDebugEnabled()) {
132  LOG.debug("childrenNames(" + path + ")");
133  }
134 
135  final String prefix = path + "/";
136  final Set<String> result = new TreeSet<>();
137  for (final String key : values.keySet()) {
138  if (key.startsWith(prefix)) {
139  result.add(key.substring(prefix.length()));
140  }
141  }
142  return result.toArray(new String[result.size()]);
143  }
144 
152  @Nullable
153  public String getValue(@NotNull final String path, @NotNull final String key) {
154  if (LOG.isDebugEnabled()) {
155  LOG.debug("getValue(" + path + ", " + key + ")");
156  }
157 
158  final Map<String, String> map = values.get(path);
159  assert map != null; // AbstractPreferences.getSpi() ensures this
160  return map.get(key);
161  }
162 
169  @NotNull
170  public String[] getKeys(@NotNull final String path) {
171  if (LOG.isDebugEnabled()) {
172  LOG.debug("getKeys(" + path + ")");
173  }
174 
175  final Map<String, String> map = values.get(path);
176  assert map != null; // AbstractPreferences.keysSpi() ensures this
177  final Set<String> keys = map.keySet();
178  return keys.toArray(new String[keys.size()]);
179  }
180 
187  public void putValue(@NotNull final String path, @NotNull final String key, @NotNull final String value) {
188  if (LOG.isDebugEnabled()) {
189  LOG.debug("putValue(" + path + ", " + key + ", " + value + ")");
190  }
191 
192  final Map<String, String> map = values.get(path);
193  assert map != null; // AbstractPreferences.putSpi() ensures this
194  final String oldValue = map.put(key, value);
195  if (oldValue == null || !oldValue.equals(value)) {
196  setChanged();
197  }
198  }
199 
204  public void removeNode(@NotNull final String path) {
205  if (LOG.isDebugEnabled()) {
206  LOG.debug("removeNode(" + path + ")");
207  }
208 
209  if (values.remove(path) != null) {
210  setChanged();
211  }
212  }
213 
219  public void removeValue(@NotNull final String path, @NotNull final String key) {
220  if (LOG.isDebugEnabled()) {
221  LOG.debug("removeValue(" + path + ", " + key + ")");
222  }
223 
224  final Map<String, String> map = values.get(path);
225  assert map != null; // AbstractPreferences.removeSpi() ensures this
226  if (map.remove(key) != null) {
227  setChanged();
228  }
229  }
230 
237  public void sync(final boolean sync) throws BackingStoreException {
238  if (LOG.isDebugEnabled()) {
239  LOG.debug("sync(" + sync + ")");
240  }
241 
242  try {
243  saveValues();
244  } catch (final IOException ex) {
245  throw new BackingStoreException(ex);
246  }
247  }
248 
253  private void setChanged() {
254  if (noSave) {
255  return;
256  }
257 
258  try {
259  saveValues();
260  } catch (final IOException ex) {
261  LOG.warn(file + ": " + ex.getMessage());
262  }
263  }
264 
268  private void loadValues() {
269  if (file == null) {
270  return;
271  }
272 
273  if (LOG.isDebugEnabled()) {
274  LOG.debug("loadValues: " + file);
275  }
276 
277  values.clear();
278 
279  try {
280  final FileInputStream fis = new FileInputStream(file);
281  try {
282  final InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
283  try {
284  final LineNumberReader lnr = new LineNumberReader(isr);
285  try {
286  loadValues(lnr);
287  } finally {
288  lnr.close();
289  }
290  } finally {
291  isr.close();
292  }
293  } finally {
294  fis.close();
295  }
296  } catch (final FileNotFoundException ignored) {
297  // ignore
298  } catch (final IOException ex) {
299  LOG.warn(file + ": " + ex.getMessage());
300  }
301  }
302 
308  private void loadValues(@NotNull final LineNumberReader lnr) throws IOException {
309  String path = defaultPath;
310  while (true) {
311  final String line2 = lnr.readLine();
312  if (line2 == null) {
313  break;
314  }
315  final String line = Codec.decode(line2.trim());
316  if (line.startsWith("#") || line.isEmpty()) {
317  continue;
318  }
319 
320  if (line.startsWith("[") && line.endsWith("]")) {
321  path = line.substring(1, line.length() - 1);
322  continue;
323  }
324 
325  final String[] tmp = PATTERN_EQUAL.split(line, 2);
326  if (tmp.length != 2) {
327  LOG.warn(file + ":" + lnr.getLineNumber() + ": syntax error");
328  continue;
329  }
330  final String key = tmp[0];
331  final String value = tmp[1];
332 
333  newNode(path);
334  putValue(path, key, value);
335  }
336  }
337 
342  private void saveValues() throws IOException {
343  if (file == null) {
344  return;
345  }
346 
347  if (LOG.isDebugEnabled()) {
348  LOG.debug("saveValues: " + file);
349  }
350 
351  final File tmpFile = new File(file.getPath() + ".tmp");
352  final FileOutputStream fos = new FileOutputStream(tmpFile);
353  try {
354  final OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
355  try {
356  final BufferedWriter bw = new BufferedWriter(osw);
357  try {
358  final Map<String, String> defaultNode = values.get(defaultPath);
359  if (defaultNode != null) {
360  saveNode(bw, null, defaultNode);
361  }
362 
363  for (final Map.Entry<String, Map<String, String>> e : values.entrySet()) {
364  if (!e.getKey().equals(defaultPath)) {
365  saveNode(bw, e.getKey(), e.getValue());
366  }
367  }
368  } finally {
369  bw.close();
370  }
371  } finally {
372  osw.close();
373  }
374  } finally {
375  fos.close();
376  }
377 
378  //noinspection ResultOfMethodCallIgnored
379  file.delete(); // Windows cannot overwrite destination file on rename
380  if (!tmpFile.renameTo(file)) {
381  throw new IOException("cannot rename " + tmpFile + " to " + file);
382  }
383  }
384 
392  private static void saveNode(@NotNull final BufferedWriter writer, @Nullable final String path, @NotNull final Map<String, String> node) throws IOException {
393  if (node.isEmpty()) {
394  return;
395  }
396 
397  final ActionBuilder actionBuilder = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
398 
399  if (path != null) {
400  writer.newLine();
401  writer.write("[");
402  writer.write(Codec.encode(path));
403  writer.write("]");
404  writer.newLine();
405  }
406 
407  for (final Map.Entry<String, String> entry : node.entrySet()) {
408  writer.newLine();
409 
410  final String comment = actionBuilder.getString("prefs." + PATTERN_IGNORE.matcher(entry.getKey()).replaceAll(""));
411  if (comment != null) {
412  writer.write("# ");
413  writer.write(comment);
414  writer.newLine();
415  }
416  writer.write(Codec.encode(entry.getKey()));
417  writer.write("=");
418  writer.write(Codec.encode(entry.getValue()));
419  writer.newLine();
420  }
421  }
422 
423 }
static String encode(@NotNull final String str)
Encode a string to make it fit into one line.
Definition: Codec.java:73
String [] getKeys(@NotNull final String path)
Return all of the keys that have an associated value in a node.
Definition: Storage.java:170
static String decode(@NotNull final String str)
Decode a string which was encoded by encode(String).
Definition: Codec.java:89
static final Pattern PATTERN_EQUAL
The pattern that matches a single equal sign ("=").
Definition: Storage.java:54
final String defaultPath
The default key name for loading/saving values.
Definition: Storage.java:66
void newNode(@NotNull final String path)
Make sure a node exists.
Definition: Storage.java:113
String getValue(@NotNull final String path, @NotNull final String key)
Return the value associated with the specified key at a node, or.
Definition: Storage.java:153
void removeValue(@NotNull final String path, @NotNull final String key)
Remove the association (if any) for the specified key at a node.
Definition: Storage.java:219
Maintains a set of preference values.
Definition: Storage.java:48
Utility class to encode arbitrary Strings to fit in a single text line.
Definition: Codec.java:32
Storage(@NotNull final String defaultPath, @Nullable final File file)
Create a new instance.
Definition: Storage.java:97
void putValue(@NotNull final String path, @NotNull final String key, @NotNull final String value)
Put the given key-value association into a node.
Definition: Storage.java:187
boolean noSave
If set, do not save changes into file.
Definition: Storage.java:77
static final Pattern PATTERN_IGNORE
Pattern to ignore in path names.
Definition: Storage.java:83
static void saveNode(@NotNull final BufferedWriter writer, @Nullable final String path, @NotNull final Map< String, String > node)
Save one node.
Definition: Storage.java:392
static final Category LOG
The Logger for printing log messages.
Definition: Storage.java:60
String [] childrenNames(@NotNull final String path)
Return the names of the children of a node.
Definition: Storage.java:130
void setChanged()
This function is called whenever the contents of values has changed.
Definition: Storage.java:253
void removeNode(@NotNull final String path)
Remove a preference node including all preferences that it contains.
Definition: Storage.java:204
final Map< String, Map< String, String > > values
The stored values.
Definition: Storage.java:89
void loadValues(@NotNull final LineNumberReader lnr)
Load the values from a LineNumberReader.
Definition: Storage.java:308
void sync(final boolean sync)
Save changes to the underlying file.
Definition: Storage.java:237
void loadValues()
Load the values from the backing file.
Definition: Storage.java:268
void saveValues()
Save the values to the backing file.
Definition: Storage.java:342
final File file
The file for loading/saving values.
Definition: Storage.java:72