/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.math.tree.node; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.Objects; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.math.MathUtil; import org.arakhne.afc.math.tree.TreeDataEvent; import org.arakhne.afc.math.tree.TreeNode; import org.arakhne.afc.math.tree.TreeNodeListener; /** * This is the very generic implementation of a tree node which * does not made any implementation choice about how this * node is related to its parent node. * * @param <D> is the type of the data inside the tree * @param <N> is the type of the tree nodes. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ public abstract class AbstractParentlessTreeNode<D, N extends AbstractParentlessTreeNode<D, N>> implements TreeNode<D, N>, Serializable { /** By default, does tree nodes use a linked list or not. */ public static final boolean DEFAULT_LINK_LIST_USE = false; /** By default, does the user data are copied or not. */ public static final boolean DEFAULT_COPY_USER_DATA = true; private static final long serialVersionUID = 2525868013637426514L; /** List of the event listeners on the tree. */ protected transient List<TreeNodeListener> nodeListeners; /** Temporary count of child nodes that are not null. * * @see #getNotNullChildCount() */ protected int notNullChildCount; /** Associated user data. */ private List<D> data; /** Indicates if a linked list must be used to store the data. * If <code>false</code>, an ArrayList will be used. */ private final boolean linkedList; /** Construct node. * @param useLinkedList indicates if a linked list must be used to store the data. * If <code>false</code>, an ArrayList will be used. */ public AbstractParentlessTreeNode(boolean useLinkedList) { this.linkedList = useLinkedList; if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } /** Construct node. * @param useLinkedList indicates if a linked list must be used to store the data. * If <code>false</code>, an ArrayList will be used. * @param copyDataCollection indicates if the given data collection is copied * if <code>true</code> or the inner data collection will be the given * collection itself if <code>false</code>. * @param idata are the initial user data */ public AbstractParentlessTreeNode(boolean useLinkedList, boolean copyDataCollection, List<D> idata) { this.linkedList = useLinkedList; if (idata != null) { if (!copyDataCollection) { this.data = idata; } else { if (this.linkedList) { this.data = new LinkedList<>(idata); } else { this.data = new ArrayList<>(idata); } } } else { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } } /** Construct node. * @param useLinkedList indicates if a linked list must be used to store the data. * If <code>false</code>, an ArrayList will be used. * @param idata are the initial user data */ public AbstractParentlessTreeNode(boolean useLinkedList, Collection<D> idata) { this.linkedList = useLinkedList; if (idata != null) { if (this.linkedList) { this.data = new LinkedList<>(idata); } else { this.data = new ArrayList<>(idata); } } else { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } } /** Construct node. * @param useLinkedList indicates if a linked list must be used to store the data. * If <code>false</code>, an ArrayList will be used. * @param idata are the initial user data */ public AbstractParentlessTreeNode(boolean useLinkedList, D idata) { this.linkedList = useLinkedList; if (idata != null) { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } this.data.add(idata); } else { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } } /** Replies the minimal value from the integer array. * * @param array the array. * @return the minimum value. * @deprecated see {@link MathUtil#min(int...)} */ @Deprecated protected static int minInteger(int... array) { int min = array[0]; for (int i = 0; i < array.length; ++i) { if (min > array[i]) { min = array[i]; } } return min; } /** Replies the maximal value from the integer array. * * @param array the array. * @return the maximum value. * @deprecated see {@link MathUtil#max(int...)} */ @Deprecated protected static int maxInteger(int... array) { int max = array[0]; for (int i = 0; i < array.length; ++i) { if (max < array[i]) { max = array[i]; } } return max; } /** Cast this node to N. * * @return <code>this</code> */ @SuppressWarnings("unchecked") @Pure public N toN() { return (N) this; } @Override @Pure public boolean isValid() { return true; } @Override @Pure public boolean isEmpty() { return isLeaf() && (getUserDataCount() == 0); } @Override @Pure public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append('['); if (this.data != null) { for (final D data : this.data) { buffer.append(Objects.toString(data)); } } buffer.append(']'); return buffer.toString(); } @Override public D[] getAllUserData(D[] array) { if (this.data == null) { return null; } return this.data.toArray(array); } @Override @Pure public List<D> getAllUserData() { return new DataCollection(); } /** Replies the internal data structure which is storing user datas. * * <p>Use with caution because any change applied outside * {@link AbstractParentlessTreeNode} will not be taken into * account (no event...). * * @return the internal data structure */ @Pure protected final List<D> getInternalDataStructureForUserData() { return this.data; } @Override @Pure public D getUserData() { if ((this.data == null) || (this.data.isEmpty())) { return null; } return this.data.get(0); } @Override @Pure public int getUserDataCount() { return (this.data == null) ? 0 : this.data.size(); } @Override @Pure public D getUserDataAt(int index) throws IndexOutOfBoundsException { if ((this.data == null) || (index < 0) || (index >= this.data.size())) { throw new IndexOutOfBoundsException(); } return this.data.get(index); } @Override public boolean addUserData(Collection<? extends D> data) { if ((data == null) || (data.isEmpty())) { return false; } if (this.data == null) { if (this.linkedList) { this.data = new LinkedList<>(data); } else { this.data = new ArrayList<>(data); } } else if (!this.data.addAll(data)) { return false; } firePropertyDataChanged(null, data); return true; } @Override public final boolean addUserData(int index, Collection<? extends D> data) { if ((data == null) || (data.isEmpty())) { return false; } if (this.data == null) { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } if (this.data.addAll(index, data)) { firePropertyDataChanged(null, data); return true; } return false; } @Override public final boolean addUserData(D data) { return addUserData(Collections.singleton(data)); } @Override public final void addUserData(int index, D data) { addUserData(index, Collections.singleton(data)); } @Override public boolean removeUserData(Collection<D> data) { if ((data == null) || (data.isEmpty()) || (this.data == null)) { return false; } if (this.data.removeAll(data)) { if (this.data.isEmpty()) { this.data = null; } firePropertyDataChanged(data, null); return true; } return false; } @Override public final D removeUserData(int index) { if (this.data == null) { throw new IndexOutOfBoundsException(); } final D removedElement = this.data.remove(index); if (removedElement != null) { if (this.data.isEmpty()) { this.data = null; } firePropertyDataChanged(Collections.singleton(removedElement), null); } return removedElement; } @Override public final boolean removeUserData(D data1) { return removeUserData(Collections.singleton(data1)); } @Override public void removeAllUserData() { if (this.data != null) { final List<D> oldData = this.data; this.data = null; firePropertyDataChanged(oldData, null); } } @Override public boolean setUserData(Collection<D> data) { if ((data == null) && (this.data == null)) { return false; } final List<D> oldData = this.data; if ((data == null) || (data.isEmpty())) { this.data = null; } else { this.data = new ArrayList<>(data); } firePropertyDataChanged(oldData, data); return true; } @Override public final boolean setUserData(D data1) { return setUserData(Collections.singleton(data1)); } @Override public boolean setUserDataAt(int index, D data) throws IndexOutOfBoundsException { final int count = (this.data == null) ? 0 : this.data.size(); if ((index < 0) || (index > count)) { throw new IndexOutOfBoundsException(); } if (data != null) { D oldData = null; if (index < count) { oldData = this.data.get(index); this.data.set(index, data); } else { if (this.data == null) { if (this.linkedList) { this.data = new LinkedList<>(); } else { this.data = new ArrayList<>(); } } this.data.add(data); } firePropertyDataChanged(oldData == null ? null : Collections.singleton(oldData), Collections.singleton(data)); return true; } else if (index < count) { final D oldData = this.data.get(index); this.data.remove(index); firePropertyDataChanged(oldData == null ? null : Collections.singleton(oldData), null); return true; } return false; } /** Fire the event for the changes of the user data associated to a node. * * @param oldData is the list of old values * @param newData is the list of new values */ protected final void firePropertyDataChanged(Collection<? extends D> oldData, Collection<? extends D> newData) { firePropertyDataChanged(new TreeDataEvent(this, oldData, newData, this.data)); } /** Fire the event for the changes of the user data associated to a node. * * @param delta is the difference between the size of the data list before change * and size after change. */ protected final void firePropertyDataChanged(int delta) { firePropertyDataChanged(new TreeDataEvent(this, delta, this.data)); } /** Fire the event for the changes of the user data associated to a node. * * @param event is the event ot fire. */ void firePropertyDataChanged(TreeDataEvent event) { if (this.nodeListeners != null) { for (final TreeNodeListener listener : this.nodeListeners) { if (listener != null) { listener.treeNodeDataChanged(event); } } } final N parent = getParentNode(); if (parent != null) { parent.firePropertyDataChanged(event); } } @Override public final void addTreeNodeListener(TreeNodeListener listener) { if (this.nodeListeners == null) { this.nodeListeners = new ArrayList<>(); } this.nodeListeners.add(listener); } @Override public final void removeTreeNodeListener(TreeNodeListener listener) { if (this.nodeListeners == null) { return; } this.nodeListeners.remove(listener); if (this.nodeListeners.isEmpty()) { this.nodeListeners = null; } } @Override @Pure public int compareTo(N obj) { return obj.hashCode() - hashCode(); } @Override @Pure public int getDeepNodeCount() { int count = 1; final int childCount = getChildCount(); for (int index = 0; index < childCount; ++index) { final N child = getChildAt(index); if (child != null) { count += child.getDeepNodeCount(); } } return count; } @Override @Pure public int getDeepUserDataCount() { int count = getUserDataCount(); final int childCount = getChildCount(); for (int index = 0; index < childCount; ++index) { final N child = getChildAt(index); if (child != null) { count += child.getDeepUserDataCount(); } } return count; } @Override @Pure public final int[] getHeights() { final List<Integer> list = new ArrayList<>(); getHeights(1, list); final int[] heights = new int[list.size()]; int idx = 0; for (final Integer intObject : list) { heights[idx] = intObject.intValue(); ++idx; } list.clear(); return heights; } /** Replies the heights of all the leaf nodes. * The order of the heights is given by a depth-first iteration. * * @param currentHeight is the current height of this node. * @param heights is the list of heights to fill */ protected abstract void getHeights(int currentHeight, List<Integer> heights); /** * This collection permits to store the data of a tree node * and notify the tree node when the collection changed. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ private class DataCollection implements List<D>, Serializable { private static final long serialVersionUID = -8019401625554108057L; private final List<D> backgroundList; /** Construct collection. * @param list the original list. */ DataCollection(List<D> list) { this.backgroundList = list; } /** Construct collection. */ DataCollection() { this(null); } @Override public boolean add(D element) { if (this.backgroundList == null) { return addUserData(element); } if (this.backgroundList.add(element)) { firePropertyDataChanged(null, Collections.singleton(element)); return true; } return false; } @Override public void add(int index, D element) { if (this.backgroundList == null) { addUserData(index, element); } else { this.backgroundList.add(index, element); firePropertyDataChanged(null, Collections.singleton(element)); } } @Override public boolean addAll(Collection<? extends D> collection) { if (this.backgroundList == null) { return addUserData(collection); } if (this.backgroundList.addAll(collection)) { firePropertyDataChanged(null, collection); return true; } return false; } @Override public boolean addAll(int index, Collection<? extends D> collection) { if (this.backgroundList == null) { return addUserData(index, collection); } if (this.backgroundList.addAll(index, collection)) { firePropertyDataChanged(null, collection); return true; } return false; } @Override public void clear() { if (this.backgroundList == null) { removeAllUserData(); } else if (!this.backgroundList.isEmpty()) { final List<D> removed = new ArrayList<>(this.backgroundList); this.backgroundList.clear(); firePropertyDataChanged(removed, null); } } @Override public boolean contains(Object obj) { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return false; } return backList.contains(obj); } return this.backgroundList.contains(obj); } @Override public boolean containsAll(Collection<?> collection) { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return false; } return backList.containsAll(collection); } return this.backgroundList.containsAll(collection); } @Override public D get(int index) { if (this.backgroundList == null) { return getUserDataAt(index); } return this.backgroundList.get(index); } @Override public int indexOf(Object obj) { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return -1; } return backList.indexOf(obj); } return this.backgroundList.indexOf(obj); } @Override public boolean isEmpty() { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return true; } return backList.isEmpty(); } return this.backgroundList.isEmpty(); } @Override public int lastIndexOf(Object obj) { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return -1; } return backList.lastIndexOf(obj); } return this.backgroundList.lastIndexOf(obj); } @Override @SuppressWarnings("unchecked") public boolean remove(Object obj) { if (this.backgroundList == null) { try { return removeUserData((D) obj); } catch (ClassCastException e) { return false; } } if (this.backgroundList.remove(obj)) { firePropertyDataChanged(Collections.singleton((D) obj), null); return true; } return false; } @Override public D remove(int index) { if (this.backgroundList == null) { return removeUserData(index); } final D oldElement = this.backgroundList.remove(index); if (oldElement != null) { firePropertyDataChanged(Collections.singleton(oldElement), null); } return oldElement; } @Override public boolean removeAll(Collection<?> collection) { boolean changed = false; for (final Object o : collection) { changed = remove(o) || changed; } return changed; } @Override public int size() { if (this.backgroundList == null) { return getUserDataCount(); } return this.backgroundList.size(); } @Override public Object[] toArray() { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return new Object[0]; } return backList.toArray(); } return this.backgroundList.toArray(); } @Override public <T> T[] toArray(T[] array) { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList != null) { return backList.toArray(array); } return array; } return this.backgroundList.toArray(array); } @Override public D set(int index, D element) { if (this.backgroundList == null) { setUserDataAt(index, element); } final D oldElement = this.backgroundList.set(index, element); firePropertyDataChanged(Collections.singleton(oldElement), Collections.singleton(element)); return oldElement; } @Override public boolean retainAll(Collection<?> collection) { final List<D> removed = new ArrayList<>(); final Iterator<D> iterator; if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { iterator = Collections.<D>emptyList().iterator(); } else { iterator = backList.iterator(); } } else { iterator = this.backgroundList.iterator(); } D data1; while (iterator.hasNext()) { data1 = iterator.next(); if (!collection.contains(data1)) { iterator.remove(); removed.add(data1); } } if (!removed.isEmpty()) { firePropertyDataChanged(removed, null); return true; } return false; } @Override public Iterator<D> iterator() { return listIterator(); } @Override public ListIterator<D> listIterator() { if (this.backgroundList == null) { final List<D> backList = getInternalDataStructureForUserData(); if (backList == null) { return Collections.<D>emptyList().listIterator(); } return new DataIterator(backList.listIterator()); } return new DataIterator(this.backgroundList.listIterator()); } @Override public ListIterator<D> listIterator(int index) { return subList(index, size()).listIterator(); } @Override public List<D> subList(int fromIndex, int toIndex) { return new DataCollection(this.backgroundList.subList(fromIndex, toIndex)); } } /** * This iterator permits to iterate on the data of a tree node * and notify the tree node when the collection changed. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ private class DataIterator implements ListIterator<D> { private final ListIterator<D> backgroundIterator; private D removableElement; /** Construct iterator. * @param iterator the original iterator. */ DataIterator(ListIterator<D> iterator) { this.backgroundIterator = iterator; } @Override public boolean hasNext() { return this.backgroundIterator.hasNext(); } @Override public D next() { this.removableElement = this.backgroundIterator.next(); return this.removableElement; } @Override public boolean hasPrevious() { return this.backgroundIterator.hasPrevious(); } @Override public int nextIndex() { return this.backgroundIterator.nextIndex(); } @Override public D previous() { this.removableElement = this.backgroundIterator.previous(); return this.removableElement; } @Override public int previousIndex() { return this.backgroundIterator.previousIndex(); } @Override public void set(D element) { final D re = this.removableElement; this.removableElement = null; if (re == null) { throw new NoSuchElementException(); } this.backgroundIterator.set(element); firePropertyDataChanged(Collections.singleton(re), Collections.singleton(element)); } @Override public void remove() { final D re = this.removableElement; this.removableElement = null; if (re == null) { throw new NoSuchElementException(); } this.backgroundIterator.remove(); firePropertyDataChanged(Collections.singleton(re), null); } @Override public void add(D element) { this.backgroundIterator.add(element); firePropertyDataChanged(null, Collections.singleton(element)); } } }