001package co.codewizards.cloudstore.core.dto;
002
003import static java.util.Objects.*;
004
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Comparator;
009import java.util.HashMap;
010import java.util.Iterator;
011import java.util.LinkedList;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015import java.util.SortedSet;
016import java.util.TreeSet;
017
018public class RepoFileDtoTreeNode implements Iterable<RepoFileDtoTreeNode> {
019
020        /**
021         * Create a single tree from the given {@code repoFileDtos}.
022         * <p>
023         * The given {@code repoFileDtos} must meet the following criteria:
024         * <ul>
025         * <li>It must not be <code>null</code>.
026         * <li>It may be empty.
027         * <li>If it is <i>not</i> empty, it may contain any number of elements, but:
028         * <ul>
029         * <li>It must contain exactly one root-node (with
030         * {@link RepoFileDto#getParentEntityID() RepoFileDto.parentEntityID} being <code>null</code>).
031         * <li>It must resolve completely, i.e. there must be a {@code RepoFileDto} for every
032         * referenced {@code parentEntityID}.
033         * </ul>
034         * </ul>
035         * @param repoFileDtos the Dtos to be organized in a tree structure. Must not be <code>null</code>. If
036         * empty, the method result will be <code>null</code>.
037         * @return the tree's root node. <code>null</code>, if {@code repoFileDtos} is empty.
038         * Never <code>null</code>, if {@code repoFileDtos} contains at least one element.
039         * @throws IllegalArgumentException if the given {@code repoFileDtos} does not meet the criteria stated above.
040         */
041        public static RepoFileDtoTreeNode createTree(final Collection<RepoFileDto> repoFileDtos) throws IllegalArgumentException {
042                requireNonNull(repoFileDtos, "repoFileDtos");
043                if (repoFileDtos.isEmpty())
044                        return null;
045
046                final Map<Long, RepoFileDtoTreeNode> id2RepoFileDtoTreeNode = new HashMap<Long, RepoFileDtoTreeNode>();
047                for (final RepoFileDto repoFileDto : repoFileDtos) {
048                        id2RepoFileDtoTreeNode.put(repoFileDto.getId(), new RepoFileDtoTreeNode(repoFileDto));
049                }
050
051                RepoFileDtoTreeNode rootNode = null;
052                for (final RepoFileDtoTreeNode node : id2RepoFileDtoTreeNode.values()) {
053                        final Long parentId = node.getRepoFileDto().getParentId();
054                        if (parentId == null) {
055                                if (rootNode != null)
056                                        throw new IllegalArgumentException("Multiple root nodes!");
057
058                                rootNode = node;
059                        }
060                        else {
061                                final RepoFileDtoTreeNode parentNode = id2RepoFileDtoTreeNode.get(parentId);
062                                if (parentNode == null)
063                                        throw new IllegalArgumentException("parentEntityID unknown: " + parentId);
064
065                                parentNode.addChild(node);
066                        }
067                }
068
069                if (rootNode == null)
070                        throw new IllegalArgumentException("There is no root node!");
071
072                return rootNode;
073        }
074
075        private static final Comparator<RepoFileDtoTreeNode> nodeComparatorByNameOnly = new Comparator<RepoFileDtoTreeNode>() {
076                @Override
077                public int compare(RepoFileDtoTreeNode node0, RepoFileDtoTreeNode node1) {
078                        final String name0 = node0.getRepoFileDto().getName();
079                        final String name1 = node1.getRepoFileDto().getName();
080                        return name0.compareTo(name1);
081                }
082        };
083
084        private RepoFileDtoTreeNode parent;
085        private final RepoFileDto repoFileDto;
086        private final SortedSet<RepoFileDtoTreeNode> children = new TreeSet<RepoFileDtoTreeNode>(nodeComparatorByNameOnly);
087        private List<RepoFileDtoTreeNode> flattenedTreeList;
088
089        protected RepoFileDtoTreeNode(final RepoFileDto repoFileDto) {
090                this.repoFileDto = requireNonNull(repoFileDto, "repoFileDto");
091        }
092
093        public RepoFileDto getRepoFileDto() {
094                return repoFileDto;
095        }
096
097        public RepoFileDtoTreeNode getParent() {
098                return parent;
099        }
100        protected void setParent(final RepoFileDtoTreeNode parent) {
101                this.parent = parent;
102        }
103
104        public Set<RepoFileDtoTreeNode> getChildren() {
105                return Collections.unmodifiableSet(children);
106        }
107
108        protected void addChild(final RepoFileDtoTreeNode child) {
109                child.setParent(this);
110                children.add(child);
111        }
112
113        /**
114         * Gets the path from the root to the current node.
115         * <p>
116         * The path's elements are separated by a slash ("/").
117         * @return the path from the root to the current node. Never <code>null</code>.
118         */
119        public String getPath() {
120                final RepoFileDtoTreeNode parent = getParent();
121                if (parent == null)
122                        return getRepoFileDto().getName();
123                else
124                        return parent.getPath() + '/' + getRepoFileDto().getName();
125        }
126
127        public List<RepoFileDtoTreeNode> getLeafs() {
128                final List<RepoFileDtoTreeNode> leafs = new ArrayList<RepoFileDtoTreeNode>();
129                populateLeafs(this, leafs);
130                return leafs;
131        }
132
133        private void populateLeafs(final RepoFileDtoTreeNode node, final List<RepoFileDtoTreeNode> leafs) {
134                if (node.getChildren().isEmpty()) {
135                        leafs.add(node);
136                }
137                for (final RepoFileDtoTreeNode child : node.getChildren()) {
138                        populateLeafs(child, leafs);
139                }
140        }
141
142        @Override
143        public Iterator<RepoFileDtoTreeNode> iterator() {
144                return new MyIterator(getFlattenedTreeList().iterator());
145        }
146
147        private class MyIterator implements Iterator<RepoFileDtoTreeNode> {
148                private final Iterator<RepoFileDtoTreeNode> delegate;
149                private RepoFileDtoTreeNode current;
150
151                public MyIterator(final Iterator<RepoFileDtoTreeNode> delegate) {
152                        this.delegate = delegate;
153                }
154
155                @Override
156                public boolean hasNext() {
157                        return delegate.hasNext();
158                }
159
160                @Override
161                public RepoFileDtoTreeNode next() {
162                        return current = delegate.next();
163                }
164
165                @Override
166                public void remove() {
167                        if (current != null) {
168                                RepoFileDtoTreeNode p = current.getParent();
169                                if (p != null && p.children != null) {
170                                        p.children.remove(current);
171                                }
172                        }
173                        delegate.remove();
174                }
175        }
176
177        public int size() {
178                return getFlattenedTreeList().size();
179        }
180
181        private List<RepoFileDtoTreeNode> getFlattenedTreeList() {
182                if (flattenedTreeList == null) {
183                        final List<RepoFileDtoTreeNode> list = new LinkedList<RepoFileDtoTreeNode>();
184                        flattenTree(list, this);
185                        flattenedTreeList = list;
186                }
187                return flattenedTreeList;
188        }
189
190        private void flattenTree(final List<RepoFileDtoTreeNode> result, final RepoFileDtoTreeNode node) {
191                result.add(node);
192                for (final RepoFileDtoTreeNode child : node.getChildren()) {
193                        flattenTree(result, child);
194                }
195        }
196
197        public RepoFileDtoTreeNode getRoot() {
198                final RepoFileDtoTreeNode parent = getParent();
199                if (parent == null)
200                        return this;
201                else
202                        return parent.getRoot();
203        }
204}