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("no game object available to remove");
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 
252  @Nullable
253  public G getLast() {
254  return contents.isEmpty() ? null : contents.get(contents.size() - 1);
255  }
256 
264  public void remove(@NotNull final G gameObject) {
265  // keep this in sync with iterator()
266  notifyBeginChange();
267  try {
268  if (contents.size() >= 2 && contents.get(0) == gameObject) {
269  contents.get(1).propagateElevation(gameObject);
270  }
271  if (!contents.remove(gameObject)) {
272  throw new NotInsideContainerException(this, gameObject);
273  }
274  gameObject.setContainer(null, 0, 0);
275  } finally {
276  notifyEndChange();
277  }
278  }
279 
284  public void removeAll() {
285  if (contents.size() <= 0) {
286  return;
287  }
288 
289  notifyBeginChange();
290  try {
291  for (final GameObject<G, A, R> gameObject : contents) {
292  gameObject.setContainer(null, 0, 0);
293  }
294  contents.clear();
295  } finally {
296  notifyEndChange();
297  }
298  }
299 
307  public boolean isTop(@NotNull final G gameObject) {
308  return !contents.isEmpty() && contents.get(contents.size() - 1) == gameObject;
309  }
310 
318  public boolean isBottom(@NotNull final G gameObject) {
319  return !contents.isEmpty() && contents.get(0) == gameObject;
320  }
321 
328  public void moveTop(@NotNull final G gameObject) {
329  final int oldIndex = contents.indexOf(gameObject);
330  if (oldIndex != contents.size() - 1) {
331  notifyBeginChange();
332  try {
333  if (!contents.remove(gameObject)) {
334  throw new NotInsideContainerException(this, gameObject);
335  }
336  if (oldIndex == 0) {
337  assert contents.size() >= 1;
338  contents.get(0).propagateElevation(gameObject);
339  }
340  contents.add(gameObject);
341  } finally {
342  notifyEndChange();
343  }
344  }
345  }
346 
353  public void moveUp(@NotNull final G gameObject) {
354  final int oldIndex = contents.indexOf(gameObject);
355  if (oldIndex < contents.size() - 1) {
356  notifyBeginChange();
357  try {
358  if (!contents.remove(gameObject)) {
359  throw new NotInsideContainerException(this, gameObject);
360  }
361  if (oldIndex == 0) {
362  assert contents.size() >= 1;
363  contents.get(0).propagateElevation(gameObject);
364  }
365  contents.add(oldIndex + 1, gameObject);
366  } finally {
367  notifyEndChange();
368  }
369  }
370  }
371 
378  public void moveDown(@NotNull final G gameObject) {
379  final int oldIndex = contents.indexOf(gameObject);
380  if (oldIndex > 0) {
381  notifyBeginChange();
382  try {
383  if (!contents.remove(gameObject)) {
384  throw new NotInsideContainerException(this, gameObject);
385  }
386  if (oldIndex == 1) {
387  assert contents.size() >= 1;
388  gameObject.propagateElevation(contents.get(0));
389  }
390  contents.add(oldIndex - 1, gameObject);
391  } finally {
392  notifyEndChange();
393  }
394  }
395  }
396 
403  public void moveBottom(@NotNull final G gameObject) {
404  final int oldIndex = contents.indexOf(gameObject);
405  if (oldIndex != 0) {
406  notifyBeginChange();
407  try {
408  if (!contents.remove(gameObject)) {
409  throw new NotInsideContainerException(this, gameObject);
410  }
411  assert contents.size() >= 1;
412  gameObject.propagateElevation(contents.get(0));
413  contents.add(0, gameObject);
414  } finally {
415  notifyEndChange();
416  }
417  }
418  }
419 
427  public void addLast(@NotNull final G gameObject) {
428  if (gameObject.isInContainer()) {
429  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
430  }
431 
432  notifyBeginChange();
433  try {
434  contents.add(gameObject);
435  setThisContainer(gameObject);
436  gameObject.markModified();
437  } finally {
438  notifyEndChange();
439  }
440  }
441 
449  public void addFirst(@NotNull final G gameObject) {
450  if (gameObject.isInContainer()) {
451  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
452  }
453 
454  notifyBeginChange();
455  try {
456  if (!contents.isEmpty()) {
457  gameObject.propagateElevation(contents.get(0));
458  }
459  contents.add(0, gameObject);
460  setThisContainer(gameObject);
461  gameObject.markModified();
462  } finally {
463  notifyEndChange();
464  }
465  }
466 
475  public void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject) {
476  if (gameObject.isInContainer()) {
477  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
478  }
479 
480  if (previousGameObject == null) {
481  addLast(gameObject);
482  return;
483  }
484 
485  if (!previousGameObject.isHead()) {
486  throw new IllegalArgumentException("game object must be the head part: " + previousGameObject);
487  }
488 
489  notifyBeginChange();
490  try {
491  boolean added = false;
492  int index = 0;
493  for (final G tmpGameObject : contents) {
494  if (tmpGameObject.getHead() == previousGameObject) {
495  if (index == 0) {
496  gameObject.propagateElevation(contents.get(0));
497  }
498  contents.add(index, gameObject);
499  added = true;
500  break;
501  }
502  index++;
503  }
504  if (!added) {
505  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because " + previousGameObject + " is not inside");
506  }
507  setThisContainer(gameObject);
508  gameObject.markModified();
509  } finally {
510  notifyEndChange();
511  }
512  }
513 
521  public void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject) {
522  if (gameObject.isInContainer()) {
523  throw new IllegalArgumentException("Can't add " + gameObject + " to " + this + " because it's already inside " + gameObject.getContainer());
524  }
525 
526  if (nextGameObject == null) {
527  addFirst(gameObject);
528  return;
529  }
530 
531  if (!nextGameObject.isHead()) {
532  throw new IllegalArgumentException("game object must be the head part: " + nextGameObject);
533  }
534 
535  notifyBeginChange();
536  try {
537  final int insertIndex = contents.indexOf(nextGameObject);
538  if (insertIndex == -1) {
539  throw new IllegalArgumentException("Can't insert " + gameObject + " before " + nextGameObject + " because that isn't inside " + this);
540  }
541  contents.add(insertIndex + 1, gameObject);
542  setThisContainer(gameObject);
543  gameObject.markModified();
544  } finally {
545  notifyEndChange();
546  }
547  }
548 
556  public void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject) {
557  if (oldGameObject.isMulti()) {
558  throw new IllegalArgumentException("cannot replace multi-part game object: " + oldGameObject);
559  }
560 
561  notifyBeginChange();
562  try {
563  final int insertIndex = contents.indexOf(oldGameObject);
564  if (insertIndex == -1) {
565  throw new NotInsideContainerException(this, oldGameObject);
566  }
567  if (insertIndex == 0) {
568  newGameObject.propagateElevation(oldGameObject);
569  }
570  contents.remove(oldGameObject);
571  oldGameObject.setContainer(null, 0, 0);
572  contents.add(insertIndex, newGameObject);
573  setThisContainer(newGameObject);
574  newGameObject.markModified();
575  } finally {
576  notifyEndChange();
577  }
578  }
579 
585  @NotNull
586  public abstract MapSquare<G, A, R> getMapSquare();
587 
593  @Nullable
594  public abstract MapSquare<G, A, R> getMapSquareOptional();
595 
599  protected abstract void notifyBeginChange();
600 
604  protected abstract void notifyEndChange();
605 
606  @NotNull
607  @Override
608  @SuppressWarnings("unchecked")
609  protected Object clone() {
610  try {
612  clone.initData();
613  for (final BaseObject<G, A, R, G> gameObject : contents) {
614  final G clonedGameObject = gameObject.clone();
615  clone.contents.add(clonedGameObject);
616  clone.setThisContainer(clonedGameObject);
617  }
618  return clone;
619  } catch (final CloneNotSupportedException e) {
620  assert false : "This class must be cloneable" + e;
621  throw new AssertionError(e);
622  }
623  }
624 
625  // writeObject() is not required because this class doesn't require special handling during serialization.
626 
627  private void readObject(@NotNull final ObjectInputStream stream) throws IOException, ClassNotFoundException {
628  stream.defaultReadObject();
629  // initialize transients
630  initData();
631  }
632 
638  protected boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer) {
639  if (gameObjectContainer.contents.size() != contents.size()) {
640  return false;
641  }
642 
643  for (int i = 0; i < contents.size(); i++) {
644  if (!gameObjectContainer.contents.get(i).isEqual(contents.get(i))) {
645  return false;
646  }
647  }
648 
649  return true;
650  }
651 
656  protected abstract void setThisContainer(@NotNull G gameObject);
657 
663  @Nullable
664  public abstract G asGameObject();
665 
666  @NotNull
667  @Override
668  public String toString() {
669  final StringBuilder sb = new StringBuilder();
670  sb.append("(");
671  final Iterator<G> it = contents.iterator();
672  if (it.hasNext()) {
673  sb.append(it.next());
674  while (it.hasNext()) {
675  sb.append(",");
676  sb.append(it.next());
677  }
678  }
679  sb.append(")");
680  return sb.toString();
681  }
682 
687  private static class ReverseIterator<T extends GameObject<?, ?, ?>> implements Iterator<T> {
688 
692  @NotNull
693  private final ListIterator<T> delegate;
694 
698  @NotNull
699  private final List<T> list;
700 
705  private ReverseIterator(@NotNull final List<T> list) {
706  this.list = list;
707  delegate = list.listIterator(list.size());
708  }
709 
710  @Override
711  public boolean hasNext() {
712  return delegate.hasPrevious();
713  }
714 
715  // previous can throw NoSuchElementException but InspectionGadgets doesn't know about that.
716  @NotNull
717  @Override
718  public T next() {
719  return delegate.previous();
720  }
721 
722  @Override
723  public void remove() {
724  if (delegate.nextIndex() == 0 && list.size() >= 2) {
725  list.get(1).propagateElevation(list.get(0));
726  }
727  delegate.remove();
728  }
729 
730  }
731 
732 }
net.sf.gridarta.model.baseobject.GameObjectContainer.contents
List< G > contents
Definition: GameObjectContainer.java:63
net.sf.gridarta.model.baseobject.GameObjectContainer.clone
Object clone()
Definition: GameObjectContainer.java:609
net.sf.gridarta.model.baseobject.GameObjectContainer.isBottom
boolean isBottom(@NotNull final G gameObject)
Definition: GameObjectContainer.java:318
net.sf.gridarta.model.baseobject.RecursiveGameObjectIterator
Definition: RecursiveGameObjectIterator.java:33
net.sf.gridarta.model.baseobject.GameObjectContainer.getNext
G getNext(@NotNull final G gameObject)
Definition: GameObjectContainer.java:234
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.hasNext
boolean hasNext()
Definition: GameObjectContainer.java:711
net.sf.gridarta
net.sf.gridarta.model.mapmodel.MapSquare
Definition: MapSquare.java:45
net.sf.gridarta.model.baseobject.GameObjectContainer.removeAll
void removeAll()
Definition: GameObjectContainer.java:284
net.sf.gridarta.model.baseobject.GameObjectContainer.hasSameContents
boolean hasSameContents(@NotNull final GameObjectContainer<?, ?, ?> gameObjectContainer)
Definition: GameObjectContainer.java:638
net.sf
net.sf.gridarta.model.mapmodel
Definition: AboveFloorInsertionMode.java:20
net.sf.gridarta.model.baseobject.GameObjectContainer.readObject
void readObject(@NotNull final ObjectInputStream stream)
Definition: GameObjectContainer.java:627
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.list
final List< T > list
Definition: GameObjectContainer.java:699
net.sf.gridarta.model.archetype
Definition: AbstractArchetype.java:20
net.sf.gridarta.model.gameobject.GameObject
Definition: GameObject.java:36
net.sf.gridarta.model.baseobject.GameObjectContainer.getFirst
G getFirst()
Definition: GameObjectContainer.java:206
net.sf.gridarta.model.baseobject.GameObjectContainer.moveBottom
void moveBottom(@NotNull final G gameObject)
Definition: GameObjectContainer.java:403
net.sf.gridarta.model.baseobject.GameObjectContainer.setThisContainer
abstract void setThisContainer(@NotNull G gameObject)
net.sf.gridarta.model.baseobject.GameObjectContainer.getPrev
G getPrev(@NotNull final G gameObject)
Definition: GameObjectContainer.java:217
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.ReverseIterator
ReverseIterator(@NotNull final List< T > list)
Definition: GameObjectContainer.java:705
net.sf.gridarta.model.gameobject
Definition: AbstractGameObject.java:20
net
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.delegate
final ListIterator< T > delegate
Definition: GameObjectContainer.java:693
net.sf.gridarta.model.baseobject.GameObjectContainer.insertBefore
void insertBefore(@NotNull final G gameObject, @Nullable final G nextGameObject)
Definition: GameObjectContainer.java:521
net.sf.gridarta.model.baseobject.GameObjectContainer.addLast
void addLast(@NotNull final G gameObject)
Definition: GameObjectContainer.java:427
net.sf.gridarta.model.baseobject.GameObjectContainer.iterator
Iterator< G > iterator()
Definition: GameObjectContainer.java:120
net.sf.gridarta.model.baseobject.GameObjectContainer.reverse
transient Iterable< G > reverse
Definition: GameObjectContainer.java:75
net.sf.gridarta.model.baseobject.GameObjectContainer.recursive
transient Iterable< G > recursive
Definition: GameObjectContainer.java:69
net.sf.gridarta.model.maparchobject.MapArchObject
Definition: MapArchObject.java:40
net.sf.gridarta.model.baseobject.GameObjectContainer.GameObjectContainer
GameObjectContainer()
Definition: GameObjectContainer.java:80
net.sf.gridarta.model.baseobject.BaseObject< G, A, R, G >
net.sf.gridarta.model.baseobject.GameObjectContainer.moveTop
void moveTop(@NotNull final G gameObject)
Definition: GameObjectContainer.java:328
net.sf.gridarta.model.baseobject.GameObjectContainer.insertAfter
void insertAfter(@Nullable final G previousGameObject, @NotNull final G gameObject)
Definition: GameObjectContainer.java:475
net.sf.gridarta.model.gameobject.NotInsideContainerException
Definition: NotInsideContainerException.java:34
net.sf.gridarta.model.baseobject.GameObjectContainer.toString
String toString()
Definition: GameObjectContainer.java:668
net.sf.gridarta.model
net.sf.gridarta.model.archetype.Archetype
Definition: Archetype.java:41
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator
Definition: GameObjectContainer.java:687
net.sf.gridarta.model.baseobject.GameObjectContainer.recursive
Iterable< G > recursive()
Definition: GameObjectContainer.java:188
net.sf.gridarta.model.baseobject.GameObjectContainer.isEmpty
boolean isEmpty()
Definition: GameObjectContainer.java:196
net.sf.gridarta.model.baseobject.GameObjectContainer.addFirst
void addFirst(@NotNull final G gameObject)
Definition: GameObjectContainer.java:449
net.sf.gridarta.model.baseobject.GameObjectContainer.reverse
Iterable< G > reverse()
Definition: GameObjectContainer.java:178
net.sf.gridarta.model.maparchobject
Definition: AbstractMapArchObject.java:20
net.sf.gridarta.model.baseobject.GameObjectContainer.moveUp
void moveUp(@NotNull final G gameObject)
Definition: GameObjectContainer.java:353
net.sf.gridarta.model.baseobject.GameObjectContainer
Definition: GameObjectContainer.java:50
net.sf.gridarta.model.baseobject.GameObjectContainer.moveDown
void moveDown(@NotNull final G gameObject)
Definition: GameObjectContainer.java:378
net.sf.gridarta.model.baseobject.GameObjectContainer.replace
void replace(@NotNull final G oldGameObject, @NotNull final G newGameObject)
Definition: GameObjectContainer.java:556
net.sf.gridarta.model.baseobject.GameObjectContainer.initData
void initData()
Definition: GameObjectContainer.java:92
net.sf.gridarta.model.baseobject.GameObjectContainer.isTop
boolean isTop(@NotNull final G gameObject)
Definition: GameObjectContainer.java:307
net.sf.gridarta.model.baseobject.GameObjectContainer.ReverseIterator.next
T next()
Definition: GameObjectContainer.java:718
net.sf.gridarta.model.baseobject.GameObjectContainer.getLast
G getLast()
Definition: GameObjectContainer.java:253