Gridarta Editor
AbstractIndex.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.model.index;
21 
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.Map.Entry;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 
42 public class AbstractIndex<V extends Serializable> implements Index<V> {
43 
48  @NotNull
49  private final Object sync = new Object();
50 
54  @NotNull
56 
61  @NotNull
62  private final Map<V, Long> timestamps = new HashMap<>();
63 
68  @NotNull
69  private final Map<V, String> names = new HashMap<>();
70 
75  @NotNull
76  private final Collection<V> pending = new HashSet<>();
77 
82  private boolean modified;
83 
87  private boolean transaction;
88 
94  @NotNull
95  private final Collection<V> transactionDelete = new HashSet<>();
96 
97  @Override
98  public int size() {
99  return timestamps.size();
100  }
101 
102  @NotNull
103  @Override
104  public Collection<V> findPartialName(@NotNull final String name) {
105  synchronized (sync) {
106  final String nameLowerCase = name.toLowerCase();
107  final Collection<V> result = new HashSet<>();
108  for (final Entry<V, String> e : names.entrySet()) {
109  if (e.getValue().toLowerCase().contains(nameLowerCase)) {
110  result.add(e.getKey());
111  }
112  }
113  return result;
114  }
115  }
116 
117  @Override
118  public void beginUpdate() {
119  synchronized (sync) {
120  transaction = true;
121  transactionDelete.clear();
122  transactionDelete.addAll(timestamps.keySet());
123  }
124  }
125 
126  @Override
127  public void endUpdate() {
128  synchronized (sync) {
129  if (!transaction) {
130  throw new IllegalStateException();
131  }
132  transaction = false;
133 
134  final Collection<V> tmp = new ArrayList<>(transactionDelete);
135  transactionDelete.clear();
136  if (!tmp.isEmpty()) {
137  modified = true;
138  for (final V value : tmp) {
139  timestamps.remove(value);
140  pending.remove(value);
141  names.remove(value);
142  }
143  for (final V value : tmp) {
144  for (final IndexListener<V> listener : indexListeners.getListeners()) {
145  listener.valueRemoved(value);
146  listener.nameChanged();
147  }
148  }
149  }
150  }
151  }
152 
153  @Override
154  public void add(@NotNull final V value, final long timestamp) {
155  synchronized (sync) {
156  final Long oldTimestamp = timestamps.put(value, timestamp);
157  final boolean notifyPending;
158  final boolean notifyAdded;
159  if (oldTimestamp == null || oldTimestamp != timestamp) {
160  notifyPending = pending.add(value) && pending.size() == 1;
161  notifyAdded = oldTimestamp == null;
162  modified = true;
163  } else {
164  notifyPending = false;
165  notifyAdded = false;
166  }
167  transactionDelete.remove(value);
168  if (notifyAdded) {
169  for (final IndexListener<V> listener : indexListeners.getListeners()) {
170  listener.valueAdded(value);
171  }
172  }
173  if (notifyPending) {
174  for (final IndexListener<V> listener : indexListeners.getListeners()) {
175  listener.pendingChanged();
176  }
177  }
178  }
179  }
180 
181  @Override
182  public void remove(@NotNull final V value) {
183  synchronized (sync) {
184  if (timestamps.remove(value) == null) {
185  return;
186  }
187  modified = true;
188  pending.remove(value);
189  names.remove(value);
190  for (final IndexListener<V> listener : indexListeners.getListeners()) {
191  listener.valueRemoved(value);
192  listener.nameChanged();
193  }
194  }
195  }
196 
197  @Override
198  public void setPending(@NotNull final V value) {
199  synchronized (sync) {
200  final boolean notifyPending = pending.add(value) && pending.size() == 1;
201  if (!timestamps.containsKey(value)) {
202  modified = true;
203  timestamps.put(value, 0L);
204  for (final IndexListener<V> listener : indexListeners.getListeners()) {
205  listener.valueAdded(value);
206  }
207  }
208  if (notifyPending) {
209  for (final IndexListener<V> listener : indexListeners.getListeners()) {
210  listener.pendingChanged();
211  }
212  }
213  }
214  }
215 
216  @Override
217  public void setName(@NotNull final V value, final long timestamp, @NotNull final String name) {
218  synchronized (sync) {
219  timestamps.put(value, timestamp);
220  final String oldName = names.put(value, name);
221  if (oldName != null && oldName.equals(name)) {
222  return;
223  }
224  modified = true;
225  for (final IndexListener<V> listener : indexListeners.getListeners()) {
226  listener.nameChanged();
227  }
228  }
229  }
230 
231  @Nullable
232  @Override
233  public String getName(@NotNull final V value) {
234  synchronized (sync) {
235  return names.get(value);
236  }
237  }
238 
239  @Nullable
240  @Override
241  public V removePending() {
242  synchronized (sync) {
243  final Iterator<V> it = pending.iterator();
244  if (!it.hasNext()) {
245  return null;
246  }
247 
248  final V result = it.next();
249  it.remove();
250  return result;
251  }
252  }
253 
254  @Override
255  public boolean hasPending() {
256  synchronized (sync) {
257  return !pending.isEmpty();
258  }
259  }
260 
261  @Override
262  public boolean isModified() {
263  synchronized (sync) {
264  return modified;
265  }
266  }
267 
268  @Override
269  public void addIndexListener(@NotNull final IndexListener<V> listener) {
270  indexListeners.add(listener);
271  }
272 
273  @Override
274  public void removeIndexListener(@NotNull final IndexListener<V> listener) {
275  indexListeners.remove(listener);
276  }
277 
278  @Override
279  public void save(@NotNull final ObjectOutputStream objectOutputStream) throws IOException {
280  synchronized (sync) {
281  objectOutputStream.writeObject(timestamps);
282  objectOutputStream.writeObject(names);
283  modified = false;
284  }
285  }
286 
290  @Override
291  @SuppressWarnings("unchecked")
292  public void load(@NotNull final ObjectInputStream objectInputStream) throws IOException {
293  synchronized (sync) {
294  final Map<V, Long> tmpTimestamps;
295  final Map<V, String> tmpNames;
296  try {
297  tmpTimestamps = (Map<V, Long>) objectInputStream.readObject();
298  tmpNames = (Map<V, String>) objectInputStream.readObject();
299  } catch (final ClassNotFoundException ex) {
300  throw new IOException(ex.getMessage(), ex);
301  }
302  if (transaction) {
303  throw new IOException("cannot restore state within active transaction");
304  }
305 
306  // drop excess elements from tmpNames to force a consistent state
307  tmpNames.keySet().retainAll(tmpTimestamps.keySet());
308 
309  clear();
310  timestamps.putAll(tmpTimestamps);
311  names.putAll(tmpNames);
312  }
313  }
314 
315  @Override
316  public void clear() {
317  synchronized (sync) {
318  timestamps.clear();
319  names.clear();
320  pending.clear();
321  transactionDelete.clear();
322  modified = false;
323  }
324  }
325 
326  @Override
327  public void indexingFinished() {
328  for (final IndexListener<V> listener : indexListeners.getListeners()) {
329  listener.indexingFinished();
330  }
331  }
332 
333 }
void removeIndexListener(@NotNull final IndexListener< V > listener)
Removes an IndexListener to be notified of changes.
T [] getListeners()
Returns an array of all the listeners.
void setPending(@NotNull final V value)
Marks a value as pending.
An index of values.
Definition: Index.java:41
boolean hasPending()
Returns whether at least one pending value exists.
Abstract base class for Index implementations.
Interface for listeners interested in Index related events.
final Map< V, Long > timestamps
Maps value to timestamp.
void load(@NotNull final ObjectInputStream objectInputStream)
unchecked
V removePending()
Returns one pending value.
void setName(@NotNull final V value, final long timestamp, @NotNull final String name)
Associates a value with a name.
void addIndexListener(@NotNull final IndexListener< V > listener)
Adds an IndexListener to be notified of changes.
Base package of all Gridarta classes.
final Collection< V > transactionDelete
The values to delete at the end of the current transaction.
boolean transaction
Whether a transaction is active.
void remove(@NotNull final T listener)
Removes a listener.
void indexingFinished()
Should be called after indexing has finished.
final Map< V, String > names
Maps value to name.
int size()
Returns the number of values in this cache.
void add(@NotNull final T listener)
Adds a listener.
void save(@NotNull final ObjectOutputStream objectOutputStream)
Saves the state to an ObjectOutputStream.
Collection< V > findPartialName(@NotNull final String name)
Returns all matching values for a (possibly partial) key name.
String getName(@NotNull final V value)
Returns the name associated with a value.
Type-safe version of EventListenerList.
boolean modified
Whether the state (timestamps or names) was modified since last save.
void add(@NotNull final V value, final long timestamp)
Adds a value to the cache.
final EventListenerList2< IndexListener< V > > indexListeners
The registered listeners.
void clear()
Clears all values from the index.
boolean isModified()
Returns whether the state was modified since last save.
final Object sync
Objects used to synchronize accesses to other fields.
final Collection< V > pending
Pending values.