Passed
Push — master ( e0b928...937cde )
by Julian
03:04 queued 12s
created

get(OptMap,Integer)   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
dl 0
loc 6
rs 10
1
/*
2
 *
3
 * Copyright (C) 2009-2017 Julian Mendez
4
 *
5
 *
6
 * This file is part of jcel.
7
 *
8
 *
9
 * The contents of this file are subject to the GNU Lesser General Public License
10
 * version 3
11
 *
12
 *
13
 * This program is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Lesser General Public License as published by
15
 * the Free Software Foundation, either version 3 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU Lesser General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Lesser General Public License
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25
 *
26
 *
27
 * Alternatively, the contents of this file may be used under the terms
28
 * of the Apache License, Version 2.0, in which case the
29
 * provisions of the Apache License, Version 2.0 are applicable instead of those
30
 * above.
31
 *
32
 *
33
 * Licensed under the Apache License, Version 2.0 (the "License");
34
 * you may not use this file except in compliance with the License.
35
 * You may obtain a copy of the License at
36
 *
37
 *     http://www.apache.org/licenses/LICENSE-2.0
38
 *
39
 * Unless required by applicable law or agreed to in writing, software
40
 * distributed under the License is distributed on an "AS IS" BASIS,
41
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42
 * See the License for the specific language governing permissions and
43
 * limitations under the License.
44
 *
45
 */
46
47
package de.tudresden.inf.lat.jcel.core.graph;
48
49
import java.util.Collection;
50
import java.util.Collections;
51
import java.util.HashSet;
52
import java.util.Objects;
53
import java.util.Optional;
54
import java.util.Set;
55
import java.util.TreeMap;
56
import java.util.TreeSet;
57
58
import de.tudresden.inf.lat.util.map.OptMap;
59
import de.tudresden.inf.lat.util.map.OptMapImpl;
60
61
/**
62
 * This class implements the algorithm that computes the class hierarchy from
63
 * the subsumer set. This implementation starts finding direct subsumees from
64
 * top, marks them, and then looks for their direct subsumees. In this way, it
65
 * builds the new graph in levels according to the distance to top.
66
 * 
67
 * @author Julian Mendez
68
 */
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
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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() {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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
		});
423
	}
424
425
}
426