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