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}