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