Gridarta Editor
GameObjectContainer.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.baseobject;
21 
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.Serializable;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.ListIterator;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 
49 @SuppressWarnings("ClassReferencesSubclass")
50 public abstract class GameObjectContainer<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements Cloneable, Iterable<G>, Serializable {
51 
55  private static final long serialVersionUID = 1L;
56 
62  @NotNull
63  private List<G> contents;
64 
68  @NotNull
69  private transient Iterable<G> recursive;
70 
74  @NotNull
75  private transient Iterable<G> reverse;
76 
80  protected GameObjectContainer() {
81  initData();
82  }
83 
92  private void initData() {
93  contents = new ArrayList<>(0);
94  recursive = new Iterable<G>() {
95 
96  @Override
97  public Iterator<G> iterator() {
99  }
100 
101  };
102  reverse = new Iterable<G>() {
103 
104  @Override
105  public Iterator<G> iterator() {
106  return new ReverseIterator<>(contents);
107  }
108 
109  };
110  }
111 
118  @NotNull
119  @Override
120  public Iterator<G> iterator() {
121  // Do not return contents.iterator() directly because otherwise the remove() operation will not properly unlink the removed GameObject from its container.
122  return new Iterator<G>() {
123 
127  @NotNull
128  private final Iterator<G> delegate = contents.iterator();
129 
134  @Nullable
135  private G current;
136 
137  @Override
138  public boolean hasNext() {
139  return delegate.hasNext();
140  }
141 
142  @Override
143  public G next() {
144  current = delegate.next();
145  assert current != null;
146  return current;
147  }
148 
149  @Override
150  public void remove() {
151  final G tmp = current;
152  if (tmp == null) {
153  throw new IllegalStateException();
154  }
155  // keep this in sync with GameObjectContainer#remove(G)
156  // we can't simply invoke GameObjectContainer#remove(current) because that would result in a ConcurrentModificationException.
157  notifyBeginChange();
158  try {
159  if (contents.size() >= 2 && contents.get(0) == tmp) {
160  contents.get(1).propagateElevation(tmp);
161  }
162  delegate.remove();
163  tmp.setContainer(null, 0, 0);
164  current = null;
165  } finally {
166  notifyEndChange();
167  }
168  }
169  };
170  }
171 
177  @NotNull
178  public Iterable<G> reverse() {
179  return reverse;
180  }
181 
187  @NotNull
188  public Iterable<G> recursive() {
189  return recursive;
190  }
191 
196  public boolean isEmpty() {
197  return contents.isEmpty();
198  }
199 
205  @Nullable
206  public G getFirst() {
207  return contents.isEmpty() ? null : contents.get(0);
208  }
209 
216  @Nullable
217  public G getPrev(@NotNull final G gameObject) {
218  final Iterator<G> it = contents.iterator();
219  while (it.hasNext()) {
220  if (it.next() == gameObject) {
221  return it.hasNext() ? it.next() : null;
222  }
223  }
224  return null;
225  }
226 
233  @Nullable
234  public G getNext(@NotNull final G gameObject) {
235  G prevGameObject = null;
236  for (final G tmpGameObject : contents) {
237  if (tmpGameObject == gameObject) {
238  return prevGameObject;
239  }
240  prevGameObject = tmpGameObject;
241  }
242  return null;
243  }
244 
253  @Nullable
254  public G getLast() {
255  return contents.isEmpty() ? null : contents.get(contents.size() - 1);
256  }
257 
265  public void remove(@NotNull final G gameObject) {
266  // keep this in sync with iterator()
267  notifyBeginChange();
268  try {
269  if (contents.size() >= 2 && contents.get(0) == gameObject) {
270  contents.get(1).propagateElevation(gameObject);
271  }
272  if (!contents.remove(gameObject)) {
273  throw new NotInsideContainerException(this, gameObject);
274  }
275  gameObject.setContainer(null, 0, 0);
276  } finally {
277  notifyEndChange();
278  }
279  }
280 
285  public void removeAll() {
286  if (contents.size() <= 0) {
287  return;
288  }
289 
290  notifyBeginChange();
291  try {
292  for (final GameObject<G, A, R> gameObject : contents) {
293  gameObject.setContainer(null, 0, 0);
294  }
295  contents.clear();
296  } finally {
297  notifyEndChange();
298  }
299  }
300 
308  public boolean isTop(@NotNull final G gameObject) {
309  return !contents.isEmpty() && contents.get(contents.size() - 1) == gameObject;
310  }
311 
319  public boolean isBottom(@NotNull final G gameObject) {
320  return !contents.isEmpty() && contents.get(0) == gameObject;
321  }
322 
329  public void moveTop(@NotNull final G gameObject) {
330  final int oldIndex = contents.indexOf(gameObject);
331  if (oldIndex != contents.size() - 1) {
332  notifyBeginChange();
333  try {
334  if (!contents.remove(gameObject)) {
335  throw new NotInsideContainerException(this, gameObject);
336  }
337  if (oldIndex == 0) {
338  assert contents.size() >= 1;
339  contents.get(0).propagateElevation(gameObject);
340  }
341  contents.add(gameObject);
342  } finally {
343  notifyEndChange();
344  }
345  }
346  }
347 
354  public void moveUp(@NotNull final G gameObject) {
355  final int oldIndex = contents.indexOf(gameObject);
356  if (oldIndex < contents.size() - 1) {
357  notifyBeginChange();
358  try {
359  if (!contents.remove(gameObject)) {
360  throw new NotInsideContainerException(this, gameObject);
361  }
362  if (oldIndex == 0) {
363  assert contents.size() >= 1;
364  contents.get(0).propagateElevation(gameObject);
365  }
366  contents.add(oldIndex + 1, gameObject);
367  } finally {
368  notifyEndChange();
369  }
370  }
371  }
372 
379  public void moveDown(@NotNull final G gameObject) {
380  final int oldIndex = contents.indexOf(gameObject);
381  if (oldIndex > 0) {
382  notifyBeginChange();
383  try {
384  if (!contents.remove(gameObject)) {
385  throw new NotInsideContainerException(this, gameObject);
386  }
387  if (oldIndex == 1) {
388  assert contents.size() >= 1;
389  gameObject.propagateElevation(contents.get(0));
390  }
391  contents.add(oldIndex - 1, gameObject);
392  } finally {
393  notifyEndChange();
394  }
395  }
396  }
397 
404  public void moveBottom(@NotNull final G gameObject) {
405  final int oldIndex = contents.indexOf(gameObject);
406  if (oldIndex != 0) {
407  notifyBeginChange();
408  try {
409  if (!contents.remove(gameObject)) {
410  throw new NotInsideContainerException(this, gameObject);
411  }
412  assert contents.size() >= 1;
413  gameObject.propagateElevation(contents.get(0));
414  contents.add(0, gameObject);
415  } finally {
416  notifyEndChange();
417  }
418  }
419  }
420 
428  public void addLast(@NotNull final G gameObject) {
429  if (gameObject.isInContainer()) {
430  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
431  }
432 
433  notifyBeginChange();
434  try {
435  contents.add(gameObject);
436  setThisContainer(gameObject);
437  gameObject.markModified();
438  } finally {
439  notifyEndChange();
440  }
441  }
442 
450  public void addFirst(@NotNull final G gameObject) {
451  if (gameObject.isInContainer()) {
452  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
453  }
454 
455  notifyBeginChange();
456  try {
457  if (!contents.isEmpty()) {
458  gameObject.propagateElevation(contents.get(0));
459  }
460  contents.add(0, gameObject);
461  setThisContainer(gameObject);
462  gameObject.markModified();
463  } finally {
464  notifyEndChange();
465  }
466  }
467 
477  public void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject) {
478  if (gameObject.isInContainer()) {
479  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
480  }
481 
482  if (previousGameObject == null) {
483  addLast(gameObject);
484  return;
485  }
486 
487  if (!previousGameObject.isHead()) {
488  throw new IllegalArgumentException();
489  }
490 
491  notifyBeginChange();
492  try {
493  boolean added = false;
494  int index = 0;
495  for (final G tmpGameObject : contents) {
496  if (tmpGameObject.getHead() == previousGameObject) {
497  if (index == 0) {
498  gameObject.propagateElevation(contents.get(0));
499  }
500  contents.add(index, gameObject);
501  added = true;
502  break;
503  }
504  index++;
505  }
506  if (!added) {
507  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because " + previousGameObject + " is not inside");
508  }
509  setThisContainer(gameObject);
510  gameObject.markModified();
511  } finally {
512  notifyEndChange();
513  }
514  }
515 
523  public void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject) {
524  if (gameObject.isInContainer()) {
525  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
526  }
527 
528  if (nextGameObject == null) {
529  addFirst(gameObject);
530  return;
531  }
532 
533  if (!nextGameObject.isHead()) {
534  throw new IllegalArgumentException();
535  }
536 
537  notifyBeginChange();
538  try {
539  final int insertIndex = contents.indexOf(nextGameObject);
540  if (insertIndex == -1) {
541  throw new IllegalArgumentException("Can't insert " + gameObject + " before " + nextGameObject + " because that isn't inside " + this);
542  }
543  contents.add(insertIndex + 1, gameObject);
544  setThisContainer(gameObject);
545  gameObject.markModified();
546  } finally {
547  notifyEndChange();
548  }
549  }
550 
558  public void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject) {
559  if (oldGameObject.isMulti()) {
560  throw new IllegalArgumentException();
561  }
562 
563  notifyBeginChange();
564  try {
565  final int insertIndex = contents.indexOf(oldGameObject);
566  if (insertIndex == -1) {
567  throw new NotInsideContainerException(this, oldGameObject);
568  }
569  if (insertIndex == 0) {
570  newGameObject.propagateElevation(oldGameObject);
571  }
572  contents.remove(oldGameObject);
573  oldGameObject.setContainer(null, 0, 0);
574  contents.add(insertIndex, newGameObject);
575  setThisContainer(newGameObject);
576  newGameObject.markModified();
577  } finally {
578  notifyEndChange();
579  }
580  }
581 
588  @Nullable
589  public abstract MapSquare<G, A, R> getMapSquare();
590 
594  protected abstract void notifyBeginChange();
595 
599  protected abstract void notifyEndChange();
600 
601  @NotNull
602  @Override
603  @SuppressWarnings("unchecked")
604  protected Object clone() {
605  try {
606  //noinspection OverriddenMethodCallDuringObjectConstruction
608  clone.initData();
609  for (final BaseObject<G, A, R, G> gameObject : contents) {
610  final G clonedGameObject = gameObject.clone();
611  clone.contents.add(clonedGameObject);
612  clone.setThisContainer(clonedGameObject);
613  }
614  return clone;
615  } catch (final CloneNotSupportedException e) {
616  assert false : "This class must be cloneable" + e;
617  throw new AssertionError(e);
618  }
619  }
620 
621  // writeObject() is not required because this class doesn't require special handling during serialization.
622 
623  private void readObject(@NotNull final ObjectInputStream stream) throws IOException, ClassNotFoundException {
624  stream.defaultReadObject();
625  // initialize transients
626  initData();
627  }
628 
634  public boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer) {
635  if (gameObjectContainer.contents.size() != contents.size()) {
636  return false;
637  }
638 
639  for (int i = 0; i < contents.size(); i++) {
640  if (!gameObjectContainer.contents.get(i).isEqual(contents.get(i))) {
641  return false;
642  }
643  }
644 
645  return true;
646  }
647 
652  protected abstract void setThisContainer(@NotNull G gameObject);
653 
659  @Nullable
660  public abstract G asGameObject();
661 
662  @NotNull
663  @Override
664  public String toString() {
665  final StringBuilder sb = new StringBuilder();
666  sb.append("(");
667  final Iterator<G> it = contents.iterator();
668  if (it.hasNext()) {
669  sb.append(it.next());
670  while (it.hasNext()) {
671  sb.append(",");
672  sb.append(it.next());
673  }
674  }
675  sb.append(")");
676  return sb.toString();
677  }
678 
683  private static class ReverseIterator<T extends GameObject<?, ?, ?>> implements Iterator<T> {
684 
688  @NotNull
689  private final ListIterator<T> delegate;
690 
694  @NotNull
695  private final List<T> list;
696 
701  private ReverseIterator(@NotNull final List<T> list) {
702  this.list = list;
703  delegate = list.listIterator(list.size());
704  }
705 
706  @Override
707  public boolean hasNext() {
708  return delegate.hasPrevious();
709  }
710 
711  // previous can throw NoSuchElementException but InspectionGadgets doesn't know about that.
712  @SuppressWarnings("IteratorNextCanNotThrowNoSuchElementException")
713  @NotNull
714  @Override
715  public T next() {
716  return delegate.previous();
717  }
718 
719  @Override
720  public void remove() {
721  if (delegate.nextIndex() == 0 && list.size() >= 2) {
722  list.get(1).propagateElevation(list.get(0));
723  }
724  delegate.remove();
725  }
726 
727  }
728 
729 }
transient Iterable< G > recursive
Iterable implementation for recursive traversal.
Iterable< G > reverse()
Return an object that is the reverse representation.
G getNext(@NotNull final G gameObject)
Return the GameObject succeeding a given game object.
void addLast(@NotNull final G gameObject)
Add the given GameObject at the end of this Container.
boolean isEmpty()
Check whether this square is empty.
void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject)
Add a GameObject before another.
void removeAll()
Removes all GameObjects from this container.
ReverseIterator(@NotNull final List< T > list)
Create a reverse iterator.
GameObjectContainer()
Create a new GameObjectContainer.
void addFirst(@NotNull final G gameObject)
Add the given GameObject at the end of this Container.
Base class for classes that contain GameObjects as children in the sense of containment.
final ListIterator< T > delegate
The iterator used for delegation.
void moveTop(@NotNull final G gameObject)
Move an item to top.
boolean isTop(@NotNull final G gameObject)
Returns whether this game object is the top-most one.
Base package of all Gridarta classes.
boolean isBottom(@NotNull final G gameObject)
Returns whether this game object is the bottom-most one.
This exception is thrown in case a method of a GameObject without a container or the wrong container ...
Reflects a game object (object on a map).
Definition: GameObject.java:36
An iterator for iterating over a list in reverse order.
G getFirst()
Return the first GameObject contained in this container.
GameObjects are the objects based on Archetypes found on maps.
transient Iterable< G > reverse
Iterable implementation for reverse traversal.
void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject)
Add a GameObject after another.
Iterator for recursively iterating over GameObjectContainers.
void moveUp(@NotNull final G gameObject)
Move an item up.
Iterator< G > iterator()
The Iterator returned does not recurse, it only contains objects on the first level.
void readObject(@NotNull final ObjectInputStream stream)
void moveDown(@NotNull final G gameObject)
Move an item down.
G getPrev(@NotNull final G gameObject)
Return the GameObject preceding a given game object.
boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer)
Compare this object to another game object container.
List< G > contents
The contents of this container.
abstract void setThisContainer(@NotNull G gameObject)
Sets a GameObject&#39;s container to this container.
void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject)
Replace an GameObject with another one.
Iterable< G > recursive()
Return an object that is a recursive representation.
void moveBottom(@NotNull final G gameObject)
Move an item to bottom.
G getLast()
Return the last GameObject contained in this container.