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-2023 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;
33 import java.util.concurrent.CopyOnWriteArrayList;
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
55  private final Collection<IndexListener<V>> indexListeners = new CopyOnWriteArrayList<>();
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("no transaction is active");
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) {
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) {
170  listener.valueAdded(value);
171  }
172  }
173  if (notifyPending) {
174  for (final IndexListener<V> listener : indexListeners) {
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) {
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) {
205  listener.valueAdded(value);
206  }
207  }
208  if (notifyPending) {
209  for (final IndexListener<V> listener : indexListeners) {
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) {
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) {
329  listener.indexingFinished();
330  }
331  }
332 
333 }
name
name
Definition: ArchetypeTypeSetParserTest-ignoreDefaultAttribute1-result.txt:2
net.sf.gridarta.model.index.AbstractIndex.modified
boolean modified
Whether the state (timestamps or names) was modified since last save.
Definition: AbstractIndex.java:82
net.sf.gridarta.model.index.AbstractIndex.removeIndexListener
void removeIndexListener(@NotNull final IndexListener< V > listener)
Removes an IndexListener to be notified of changes.
Definition: AbstractIndex.java:274
net.sf.gridarta.model.index.AbstractIndex.add
void add(@NotNull final V value, final long timestamp)
Adds a value to the cache.
Definition: AbstractIndex.java:154
net.sf.gridarta.model.index.AbstractIndex.addIndexListener
void addIndexListener(@NotNull final IndexListener< V > listener)
Adds an IndexListener to be notified of changes.
Definition: AbstractIndex.java:269
net.sf.gridarta.model.index.AbstractIndex.transaction
boolean transaction
Whether a transaction is active.
Definition: AbstractIndex.java:87
net.sf.gridarta.model.index.AbstractIndex.names
final Map< V, String > names
Maps value to name.
Definition: AbstractIndex.java:69
net.sf.gridarta.model.index.AbstractIndex.setName
void setName(@NotNull final V value, final long timestamp, @NotNull final String name)
Associates a value with a name.
Definition: AbstractIndex.java:217
net.sf.gridarta.model.index.AbstractIndex.setPending
void setPending(@NotNull final V value)
Marks a value as pending.
Definition: AbstractIndex.java:198
net.sf.gridarta.model.index.AbstractIndex.indexListeners
final Collection< IndexListener< V > > indexListeners
The registered listeners.
Definition: AbstractIndex.java:55
net.sf.gridarta.model.index.AbstractIndex.beginUpdate
void beginUpdate()
Starts an update.
Definition: AbstractIndex.java:118
net.sf.gridarta.model.index.AbstractIndex.removePending
V removePending()
Returns one pending value.
Definition: AbstractIndex.java:241
net.sf.gridarta.model.index.AbstractIndex.getName
String getName(@NotNull final V value)
Returns the name associated with a value.
Definition: AbstractIndex.java:233
net.sf.gridarta.model.index.AbstractIndex.save
void save(@NotNull final ObjectOutputStream objectOutputStream)
Saves the state to an ObjectOutputStream.
Definition: AbstractIndex.java:279
net.sf.gridarta.model.index.AbstractIndex.timestamps
final Map< V, Long > timestamps
Maps value to timestamp.
Definition: AbstractIndex.java:62
net.sf.gridarta.model.index.AbstractIndex.transactionDelete
final Collection< V > transactionDelete
The values to delete at the end of the current transaction.
Definition: AbstractIndex.java:95
net.sf.gridarta.model.index.AbstractIndex.isModified
boolean isModified()
Returns whether the state was modified since last save.
Definition: AbstractIndex.java:262
net.sf.gridarta.model.index.AbstractIndex.findPartialName
Collection< V > findPartialName(@NotNull final String name)
Returns all matching values for a (possibly partial) key name.
Definition: AbstractIndex.java:104
net.sf.gridarta.model.index.AbstractIndex
Abstract base class for Index implementations.
Definition: AbstractIndex.java:42
net.sf.gridarta.model.index.IndexListener
Interface for listeners interested in Index related events.
Definition: IndexListener.java:30
net.sf.gridarta.model.index.AbstractIndex.clear
void clear()
Clears all values from the index.
Definition: AbstractIndex.java:316
net.sf.gridarta.model.index.Index
An index of values.
Definition: Index.java:41
net.sf.gridarta.model.index.AbstractIndex.pending
final Collection< V > pending
Pending values.
Definition: AbstractIndex.java:76
net.sf.gridarta.model.index.AbstractIndex.indexingFinished
void indexingFinished()
Should be called after indexing has finished.
Definition: AbstractIndex.java:327
net.sf.gridarta.model.index.AbstractIndex.size
int size()
Returns the number of values in this cache.
Definition: AbstractIndex.java:98
net.sf.gridarta.model.index.AbstractIndex.hasPending
boolean hasPending()
Returns whether at least one pending value exists.
Definition: AbstractIndex.java:255
net.sf.gridarta.model.index.AbstractIndex.load
void load(@NotNull final ObjectInputStream objectInputStream)
@noinspection unchecked
Definition: AbstractIndex.java:292
net.sf.gridarta.model.index.AbstractIndex.endUpdate
void endUpdate()
Ends an update.
Definition: AbstractIndex.java:127
net.sf.gridarta.model.index.AbstractIndex.sync
final Object sync
Objects used to synchronize accesses to other fields.
Definition: AbstractIndex.java:49
it
This document describes some hints and requirements for general development on the CrossfireEditor If you plan to make changes to the editor code or setup please read the following and keep it in derived from a basic editor application called Gridder by Pasi Ker�nen so please communicate with best through the cf devel mailing before considering any fundamental changes About code DO NOT USE TABS No matter what Java development platform you are please configure insert indent Tabs are displayed totally different in every editor and there are millions of different editors out there The insertion of tabs in the source code is messing up the syntax formatting in a way that is UNREPAIRABLE Apart from please keep code indentation accurate This is not just good it helps to keep code readable and in that way dramatically decreases the chance for overlooked bugs Everyone is welcomed to correct indentation errors wherever they are spotted Before you start to do this please double check that your editor is really configured to insert spaces Line feeds may be checked in either in windows or in unix linux style All reasonable text and java editors can deal with both linefeed formats Converting line feeds is but in this case please make sure that only linefeed characters are changed and nothing else is affected Due to the platform independent nature of the editor has the potential to run on almost any given operating system the build process differs greatly between systems as well as java environments In the several people have attempted to add build scripts along with structural changes to optimize the setup on one particular system environment which has led to conflict Please do *not *attempt to change the structure or any directories for the mere purpose of improving a build process or performance in a java environment Build scripts may be placed in the root it would be especially fine if it is just one or two files but the latter is not required Please excuse me for placing such restriction I and many users of the editor greatly appreciate build scripts We just had some real troubles over this issue in the past and I don t want to have them repeated the editor has relatively high performance requirements I ve spent a lot of extra work to keep everything as fast and memory efficient as possible when you add new data fields or calculations in the archetype please make sure they are as efficient as possible and worth both the time and space they consume Now don t be afraid too much No development would be possible without adding calculations and data at all Just bear in mind unlike for many other open source performance does make a difference for the CrossfireEditor The for as many systems as possible In case you are unexperienced with java and note that the graphics look different on every and with every font They also have different sizes proportions and behave different A seemingly trivial and effectless change can wreck havoc for the same GUI run on another system please don t be totally afraid of it
Definition: Developer_README.txt:76