| Total Complexity | 48 |
| Total Lines | 353 |
| Duplicated Lines | 13.03 % |
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like de.tudresden.inf.lat.jcel.core.graph.IntegerHierarchicalGraphImpl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | /* |
||
| 69 | public class IntegerHierarchicalGraphImpl implements IntegerHierarchicalGraph { |
||
| 70 | |||
| 71 | private final Integer bottomElement; |
||
| 72 | private final OptMap<Integer, Set<Integer>> children = new OptMapImpl<>(new TreeMap<>()); |
||
| 73 | private final OptMap<Integer, Set<Integer>> equivalents = new OptMapImpl<>(new TreeMap<>()); |
||
| 74 | private final OptMap<Integer, Set<Integer>> parents = new OptMapImpl<>(new TreeMap<>()); |
||
| 75 | private final OptMap<Integer, Integer> representative = new OptMapImpl<>(new TreeMap<>()); |
||
| 76 | private final Integer topElement; |
||
| 77 | |||
| 78 | /** |
||
| 79 | * Constructs an empty hierarchical graph. |
||
| 80 | * |
||
| 81 | * @param bottom |
||
| 82 | * bottom class identifier |
||
| 83 | * @param top |
||
| 84 | * top class identifier |
||
| 85 | */ |
||
| 86 | public IntegerHierarchicalGraphImpl(Integer bottom, Integer top) { |
||
| 87 | Objects.requireNonNull(bottom); |
||
| 88 | Objects.requireNonNull(top); |
||
| 89 | this.bottomElement = bottom; |
||
| 90 | this.topElement = top; |
||
| 91 | } |
||
| 92 | |||
| 93 | private Set<Integer> get(OptMap<Integer, Set<Integer>> map, Integer key) { |
||
| 94 | Optional<Set<Integer>> optSet = map.get(key); |
||
| 95 | if (!optSet.isPresent()) { |
||
| 96 | throw new IllegalStateException("Illegal state of internal map, error retrieving '" + key + "'."); |
||
| 97 | } |
||
| 98 | return optSet.get(); |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * Constructs a hierarchical graph using another graph. |
||
| 103 | * |
||
| 104 | * @param origGraph |
||
| 105 | * a subsumer graph |
||
| 106 | */ |
||
| 107 | public IntegerHierarchicalGraphImpl(IntegerSubsumerGraph origGraph) { |
||
| 108 | Objects.requireNonNull(origGraph); |
||
| 109 | this.bottomElement = origGraph.getBottomElement(); |
||
| 110 | this.topElement = origGraph.getTopElement(); |
||
| 111 | |||
| 112 | if (origGraph.containsPair(getTopElement(), getBottomElement())) { |
||
| 113 | computeInconsistentDag(origGraph); |
||
| 114 | } else { |
||
| 115 | computeDag(origGraph); |
||
| 116 | updateParents(); |
||
| 117 | updateChildren(); |
||
| 118 | updateBottom(); |
||
| 119 | } |
||
| 120 | } |
||
| 121 | |||
| 122 | private void computeDag(IntegerSubsumerGraph setS) { |
||
| 123 | reset(setS.getElements()); |
||
| 124 | Set<Integer> sCN = new TreeSet<>(); |
||
| 125 | sCN.addAll(setS.getElements()); |
||
| 126 | sCN.remove(getBottomElement()); |
||
| 127 | sCN.remove(getTopElement()); |
||
| 128 | |||
| 129 | Set<Integer> equivToTop = new TreeSet<>(); |
||
| 130 | equivToTop.addAll(setS.getSubsumers(getTopElement())); |
||
| 131 | equivToTop.forEach(elem -> makeEquivalent(getTopElement(), elem)); |
||
| 132 | |||
| 133 | Set<Integer> classified = new TreeSet<>(); |
||
| 134 | classified.addAll(equivToTop); |
||
| 135 | |||
| 136 | sCN.forEach(cA -> { |
||
| 137 | if (!classified.contains(cA)) { |
||
| 138 | dagClassify(cA, classified, setS); |
||
| 139 | } |
||
| 140 | }); |
||
| 141 | } |
||
| 142 | |||
| 143 | private void computeInconsistentDag(IntegerSubsumerGraph setS) { |
||
| 144 | Collection<Integer> elements = setS.getElements(); |
||
| 145 | reset(elements); |
||
| 146 | elements.forEach(elem -> makeEquivalent(getBottomElement(), elem)); |
||
| 147 | } |
||
| 148 | |||
| 149 | private void dagClassify(Integer cA, Set<Integer> classified, IntegerSubsumerGraph setS) { |
||
| 150 | Set<Integer> candidates = new TreeSet<>(); |
||
| 151 | candidates.add(getTopElement()); |
||
| 152 | Set<Integer> subsumersA = new TreeSet<>(); |
||
| 153 | subsumersA.addAll(setS.getSubsumers(cA)); |
||
| 154 | subsumersA.remove(cA); |
||
| 155 | subsumersA.remove(getTopElement()); |
||
| 156 | subsumersA.forEach(cB -> { |
||
| 157 | Set<Integer> subsumersB = new TreeSet<>(); |
||
| 158 | subsumersB.addAll(setS.getSubsumers(cB)); |
||
| 159 | if (subsumersB.contains(cA)) { |
||
| 160 | classified.add(cB); |
||
| 161 | makeEquivalent(cA, cB); |
||
| 162 | } else { |
||
| 163 | if (!classified.contains(cB)) { |
||
| 164 | dagClassify(cB, classified, setS); |
||
| 165 | } |
||
| 166 | candidates.add(cB); |
||
| 167 | } |
||
| 168 | }); |
||
| 169 | dagInsert(cA, candidates); |
||
| 170 | classified.add(cA); |
||
| 171 | } |
||
| 172 | |||
| 173 | private void dagInsert(Integer cA, Set<Integer> candidates) { |
||
| 174 | Set<Integer> marked = new TreeSet<>(); |
||
| 175 | candidates.forEach(cB -> { |
||
| 176 | Set<Integer> parentSet = get(this.parents, cB); |
||
| 177 | parentSet.forEach(cX -> marked.add(cX)); |
||
| 178 | }); |
||
| 179 | Set<Integer> notMarkedCandidates = new TreeSet<>(); |
||
| 180 | notMarkedCandidates.addAll(candidates); |
||
| 181 | notMarkedCandidates.removeAll(marked); |
||
| 182 | this.parents.put(cA, notMarkedCandidates); |
||
| 183 | notMarkedCandidates.forEach(cB -> get(this.children, cB).add(cA)); |
||
| 184 | } |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Makes a disjoint union with another graph. They must share the same |
||
| 188 | * bottom and top elements. |
||
| 189 | * |
||
| 190 | * @param otherGraph |
||
| 191 | * other graph to make the union |
||
| 192 | */ |
||
| 193 | public void disjointUnion(IntegerHierarchicalGraph otherGraph) { |
||
| 194 | Objects.requireNonNull(otherGraph); |
||
| 195 | if (getBottomElement().equals(otherGraph.getBottomElement()) |
||
| 196 | && getTopElement().equals(otherGraph.getTopElement())) { |
||
| 197 | Set<Integer> set = new HashSet<>(); |
||
| 198 | set.addAll(getElements()); |
||
| 199 | set.remove(getBottomElement()); |
||
| 200 | set.remove(getTopElement()); |
||
| 201 | set.retainAll(otherGraph.getElements()); |
||
| 202 | |||
| 203 | if (!set.isEmpty()) { |
||
| 204 | throw new IllegalArgumentException("Graphs are not disjoint."); |
||
| 205 | } |
||
| 206 | |||
| 207 | Set<Integer> otherSet = new HashSet<>(); |
||
| 208 | otherSet.addAll(otherGraph.getElements()); |
||
| 209 | otherSet.forEach(elem -> { |
||
| 210 | |||
| 211 | if (!this.children.get(elem).isPresent()) { |
||
| 212 | this.children.put(elem, new HashSet<>()); |
||
| 213 | } |
||
| 214 | get(this.children, elem).addAll(otherGraph.getChildren(elem)); |
||
| 215 | |||
| 216 | if (!this.parents.get(elem).isPresent()) { |
||
| 217 | this.parents.put(elem, new HashSet<>()); |
||
| 218 | } |
||
| 219 | get(this.parents, elem).addAll(otherGraph.getParents(elem)); |
||
| 220 | |||
| 221 | if (!this.equivalents.get(elem).isPresent()) { |
||
| 222 | this.equivalents.put(elem, new HashSet<>()); |
||
| 223 | } |
||
| 224 | if (!this.representative.get(elem).isPresent()) { |
||
| 225 | this.representative.put(elem, elem); |
||
| 226 | } |
||
| 227 | otherGraph.getEquivalents(elem).forEach(otherElem -> makeEquivalent(elem, otherElem)); |
||
| 228 | |||
| 229 | }); |
||
| 230 | |||
| 231 | } else { |
||
| 232 | throw new IllegalArgumentException("Both graphs have different bottom element or different top element."); |
||
| 233 | } |
||
| 234 | } |
||
| 235 | |||
| 236 | @Override |
||
| 237 | public boolean equals(Object o) { |
||
| 238 | boolean ret = (this == o); |
||
| 239 | if (!ret && (o instanceof IntegerHierarchicalGraph)) { |
||
| 240 | IntegerHierarchicalGraph other = (IntegerHierarchicalGraph) o; |
||
| 241 | ret = getBottomElement().equals(other.getBottomElement()) && getTopElement().equals(other.getTopElement()) |
||
| 242 | && getElements().equals(other.getElements()); |
||
| 243 | |||
| 244 | ret = ret && getElements().stream() |
||
| 245 | .allMatch(elem -> getChildren(elem).equals(other.getChildren(elem)) |
||
| 246 | && getParents(elem).equals(other.getParents(elem)) |
||
| 247 | && getEquivalents(elem).equals(other.getEquivalents(elem))); |
||
| 248 | } |
||
| 249 | return ret; |
||
| 250 | } |
||
| 251 | |||
| 252 | View Code Duplication | @Override |
|
|
|
|||
| 253 | public Set<Integer> getAncestors(Integer orig) { |
||
| 254 | Objects.requireNonNull(orig); |
||
| 255 | Set<Integer> ret = new HashSet<>(); |
||
| 256 | Set<Integer> toVisit = new HashSet<>(); |
||
| 257 | toVisit.addAll(get(this.parents, orig)); |
||
| 258 | while (!toVisit.isEmpty()) { |
||
| 259 | Integer elem = toVisit.iterator().next(); |
||
| 260 | toVisit.remove(elem); |
||
| 261 | ret.add(elem); |
||
| 262 | Set<Integer> related = new HashSet<>(); |
||
| 263 | related.addAll(get(this.parents, elem)); |
||
| 264 | related.removeAll(ret); |
||
| 265 | toVisit.addAll(related); |
||
| 266 | } |
||
| 267 | return ret; |
||
| 268 | } |
||
| 269 | |||
| 270 | @Override |
||
| 271 | public Integer getBottomElement() { |
||
| 272 | return this.bottomElement; |
||
| 273 | } |
||
| 274 | |||
| 275 | @Override |
||
| 276 | public Set<Integer> getChildren(Integer elem) { |
||
| 277 | Objects.requireNonNull(elem); |
||
| 278 | return Collections.unmodifiableSet(get(this.children, elem)); |
||
| 279 | } |
||
| 280 | |||
| 281 | View Code Duplication | @Override |
|
| 282 | public Set<Integer> getDescendants(Integer orig) { |
||
| 283 | Objects.requireNonNull(orig); |
||
| 284 | Set<Integer> ret = new HashSet<>(); |
||
| 285 | Set<Integer> toVisit = new HashSet<>(); |
||
| 286 | toVisit.addAll(get(this.children, orig)); |
||
| 287 | while (!toVisit.isEmpty()) { |
||
| 288 | Integer elem = toVisit.iterator().next(); |
||
| 289 | toVisit.remove(elem); |
||
| 290 | ret.add(elem); |
||
| 291 | Set<Integer> related = new HashSet<>(); |
||
| 292 | related.addAll(get(this.children, elem)); |
||
| 293 | related.removeAll(ret); |
||
| 294 | toVisit.addAll(related); |
||
| 295 | } |
||
| 296 | return ret; |
||
| 297 | } |
||
| 298 | |||
| 299 | @Override |
||
| 300 | public Set<Integer> getElements() { |
||
| 301 | return Collections.unmodifiableSet(this.representative.keySet()); |
||
| 302 | } |
||
| 303 | |||
| 304 | @Override |
||
| 305 | public Set<Integer> getEquivalents(Integer elem) { |
||
| 306 | Objects.requireNonNull(elem); |
||
| 307 | if (!this.representative.get(elem).isPresent()) { |
||
| 308 | throw new IllegalStateException("Representative not found: '" + elem + "'."); |
||
| 309 | } |
||
| 310 | return Collections.unmodifiableSet(get(this.equivalents, this.representative.get(elem).get())); |
||
| 311 | } |
||
| 312 | |||
| 313 | /** |
||
| 314 | * Returns one vertex for each equivalence class of vertices in the graph. |
||
| 315 | * |
||
| 316 | * @return one vertex for each equivalence class of vertices in the graph |
||
| 317 | */ |
||
| 318 | public Set<Integer> getNonEquivalentElements() { |
||
| 319 | return Collections.unmodifiableSet(this.equivalents.keySet()); |
||
| 320 | } |
||
| 321 | |||
| 322 | @Override |
||
| 323 | public Set<Integer> getParents(Integer elem) { |
||
| 324 | Objects.requireNonNull(elem); |
||
| 325 | return Collections.unmodifiableSet(get(this.parents, elem)); |
||
| 326 | } |
||
| 327 | |||
| 328 | @Override |
||
| 329 | public Integer getTopElement() { |
||
| 330 | return this.topElement; |
||
| 331 | } |
||
| 332 | |||
| 333 | @Override |
||
| 334 | public int hashCode() { |
||
| 335 | return this.parents.hashCode(); |
||
| 336 | } |
||
| 337 | |||
| 338 | private void makeEquivalent(Integer cA, Integer cB) { |
||
| 339 | if (!this.representative.get(cA).isPresent()) { |
||
| 340 | throw new IllegalStateException("Representative not found: '" + cA + "'."); |
||
| 341 | } |
||
| 342 | Integer repA = this.representative.get(cA).get(); |
||
| 343 | |||
| 344 | if (!this.representative.get(cB).isPresent()) { |
||
| 345 | throw new IllegalStateException("Representative not found: '" + cB + "'."); |
||
| 346 | } |
||
| 347 | Integer repB = this.representative.get(cB).get(); |
||
| 348 | |||
| 349 | if (!repA.equals(repB)) { |
||
| 350 | Integer rep = Math.min(repA, repB); |
||
| 351 | Integer exRep = Math.max(repA, repB); |
||
| 352 | get(this.equivalents, rep).addAll(get(this.equivalents, exRep)); |
||
| 353 | get(this.equivalents, exRep).forEach(elem -> this.representative.put(elem, rep)); |
||
| 354 | this.equivalents.remove(exRep); |
||
| 355 | } |
||
| 356 | } |
||
| 357 | |||
| 358 | private void reset(Collection<Integer> elements) { |
||
| 359 | this.children.clear(); |
||
| 360 | this.parents.clear(); |
||
| 361 | this.equivalents.clear(); |
||
| 362 | this.representative.clear(); |
||
| 363 | |||
| 364 | elements.forEach(elem -> { |
||
| 365 | this.children.put(elem, new TreeSet<>()); |
||
| 366 | this.parents.put(elem, new TreeSet<>()); |
||
| 367 | Set<Integer> equiv = new TreeSet<>(); |
||
| 368 | equiv.add(elem); |
||
| 369 | this.equivalents.put(elem, equiv); |
||
| 370 | this.representative.put(elem, elem); |
||
| 371 | }); |
||
| 372 | } |
||
| 373 | |||
| 374 | @Override |
||
| 375 | public String toString() { |
||
| 376 | StringBuffer ret = new StringBuffer(); |
||
| 377 | ret.append("\n* children : "); |
||
| 378 | ret.append(this.children); |
||
| 379 | ret.append("\n* parents : "); |
||
| 380 | ret.append(this.parents); |
||
| 381 | ret.append("\n* equivalents : "); |
||
| 382 | ret.append(this.equivalents); |
||
| 383 | ret.append("\n* representative : "); |
||
| 384 | ret.append(this.representative); |
||
| 385 | ret.append("\n"); |
||
| 386 | return ret.toString(); |
||
| 387 | } |
||
| 388 | |||
| 389 | private void updateBottom() { |
||
| 390 | Set<Integer> parentsOfBottom = new HashSet<>(); |
||
| 391 | getElements().forEach(elem -> { |
||
| 392 | if (get(this.children, elem).isEmpty()) { |
||
| 393 | parentsOfBottom.add(elem); |
||
| 394 | } |
||
| 395 | }); |
||
| 396 | Set<Integer> equivToBottom = get(this.equivalents, this.bottomElement); |
||
| 397 | parentsOfBottom.removeAll(equivToBottom); |
||
| 398 | if (!equivToBottom.contains(this.topElement) && parentsOfBottom.isEmpty()) { |
||
| 399 | parentsOfBottom.add(this.topElement); |
||
| 400 | } |
||
| 401 | parentsOfBottom.forEach(elem -> get(this.children, elem).add(this.bottomElement)); |
||
| 402 | equivToBottom.forEach(elem -> this.parents.put(elem, parentsOfBottom)); |
||
| 403 | } |
||
| 404 | |||
| 405 | View Code Duplication | private void updateChildren() { |
|
| 406 | getElements().forEach(elem -> { |
||
| 407 | Set<Integer> elemChildren = new HashSet<>(); |
||
| 408 | getEquivalents(elem).forEach(index -> { |
||
| 409 | get(this.children, index).forEach(child -> elemChildren.addAll(getEquivalents(child))); |
||
| 410 | }); |
||
| 411 | getEquivalents(elem).forEach(index -> this.children.put(index, elemChildren)); |
||
| 412 | }); |
||
| 413 | } |
||
| 414 | |||
| 415 | View Code Duplication | private void updateParents() { |
|
| 416 | getElements().forEach(elem -> { |
||
| 417 | Set<Integer> elemParents = new HashSet<>(); |
||
| 418 | getEquivalents(elem).forEach(index -> { |
||
| 419 | get(this.parents, index).forEach(parent -> elemParents.addAll(getEquivalents(parent))); |
||
| 420 | }); |
||
| 421 | getEquivalents(elem).forEach(index -> this.parents.put(index, elemParents)); |
||
| 422 | }); |
||
| 426 |