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}