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}