🔥 Drastically reduce Guava usage
[Sone.git] / src / main / java / net / pterodactylus / sone / freenet / wot / IdentityChangeDetector.java
1 /*
2  * Sone - IdentityChangeDetector.java - Copyright Â© 2013–2019 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.freenet.wot;
19
20 import java.util.*;
21 import java.util.Map.*;
22 import java.util.function.*;
23 import java.util.stream.*;
24
25 import com.google.common.collect.*;
26
27 /**
28  * Detects changes between two lists of {@link Identity}s. The detector can find
29  * added and removed identities, and for identities that exist in both list
30  * their contexts and properties are checked for added, removed, or (in case of
31  * properties) changed values.
32  */
33 public class IdentityChangeDetector {
34
35         private final Map<String, Identity> oldIdentities;
36         private IdentityProcessor onNewIdentity;
37         private IdentityProcessor onRemovedIdentity;
38         private IdentityProcessor onChangedIdentity;
39         private IdentityProcessor onUnchangedIdentity;
40
41         public IdentityChangeDetector(Collection<? extends Identity> oldIdentities) {
42                 this.oldIdentities = convertToMap(oldIdentities);
43         }
44
45         public void onNewIdentity(IdentityProcessor onNewIdentity) {
46                 this.onNewIdentity = onNewIdentity;
47         }
48
49         public void onRemovedIdentity(IdentityProcessor onRemovedIdentity) {
50                 this.onRemovedIdentity = onRemovedIdentity;
51         }
52
53         public void onChangedIdentity(IdentityProcessor onChangedIdentity) {
54                 this.onChangedIdentity = onChangedIdentity;
55         }
56
57         public void onUnchangedIdentity(IdentityProcessor onUnchangedIdentity) {
58                 this.onUnchangedIdentity = onUnchangedIdentity;
59         }
60
61         public void detectChanges(final Collection<? extends Identity> newIdentities) {
62                 notifyForRemovedIdentities(oldIdentities.values().stream().filter(notContainedIn(newIdentities)).collect(Collectors.toList()));
63                 notifyForNewIdentities(newIdentities.stream().filter(notContainedIn(oldIdentities.values())).collect(Collectors.toList()));
64                 notifyForChangedIdentities(newIdentities.stream().filter(containedIn(oldIdentities)).filter(hasChanged(oldIdentities)).collect(Collectors.toList()));
65                 notifyForUnchangedIdentities(newIdentities.stream().filter(containedIn(oldIdentities)).filter(hasChanged(oldIdentities).negate()).collect(Collectors.toList()));
66         }
67
68         private void notifyForRemovedIdentities(Iterable<Identity> identities) {
69                 notify(onRemovedIdentity, identities);
70         }
71
72         private void notifyForNewIdentities(Iterable<? extends Identity> newIdentities) {
73                 notify(onNewIdentity, newIdentities);
74         }
75
76         private void notifyForChangedIdentities(Iterable<? extends Identity> identities) {
77                 notify(onChangedIdentity, identities);
78         }
79
80         private void notifyForUnchangedIdentities(Iterable<? extends Identity> identities) {
81                 notify(onUnchangedIdentity, identities);
82         }
83
84         private void notify(IdentityProcessor identityProcessor, Iterable<? extends Identity> identities) {
85                 if (identityProcessor == null) {
86                         return;
87                 }
88                 for (Identity identity : identities) {
89                         identityProcessor.processIdentity(identity);
90                 }
91         }
92
93         private static Predicate<Identity> hasChanged(final Map<String, Identity> oldIdentities) {
94                 return identity -> (identity != null) && identityHasChanged(oldIdentities.get(identity.getId()), identity);
95         }
96
97         private static boolean identityHasChanged(Identity oldIdentity, Identity newIdentity) {
98                 return identityHasNewContexts(oldIdentity, newIdentity)
99                                 || identityHasRemovedContexts(oldIdentity, newIdentity)
100                                 || identityHasNewProperties(oldIdentity, newIdentity)
101                                 || identityHasRemovedProperties(oldIdentity, newIdentity)
102                                 || identityHasChangedProperties(oldIdentity, newIdentity);
103         }
104
105         private static boolean identityHasNewContexts(Identity oldIdentity, Identity newIdentity) {
106                 return newIdentity.getContexts().stream().anyMatch(notAContextOf(oldIdentity));
107         }
108
109         private static boolean identityHasRemovedContexts(Identity oldIdentity, Identity newIdentity) {
110                 return oldIdentity.getContexts().stream().anyMatch(notAContextOf(newIdentity));
111         }
112
113         private static boolean identityHasNewProperties(Identity oldIdentity, Identity newIdentity) {
114                 return newIdentity.getProperties().entrySet().stream().anyMatch(notAPropertyOf(oldIdentity));
115         }
116
117         private static boolean identityHasRemovedProperties(Identity oldIdentity, Identity newIdentity) {
118                 return oldIdentity.getProperties().entrySet().stream().anyMatch(notAPropertyOf(newIdentity));
119         }
120
121         private static boolean identityHasChangedProperties(Identity oldIdentity, Identity newIdentity) {
122                 return oldIdentity.getProperties().entrySet().stream().anyMatch(hasADifferentValueThanIn(newIdentity));
123         }
124
125         private static Predicate<Identity> containedIn(final Map<String, Identity> identities) {
126                 return identity -> (identity != null) && identities.containsKey(identity.getId());
127         }
128
129         private static Predicate<String> notAContextOf(final Identity identity) {
130                 return context -> (identity != null) && !identity.getContexts().contains(context);
131         }
132
133         private static Predicate<Identity> notContainedIn(final Collection<? extends Identity> newIdentities) {
134                 return identity -> (identity != null) && !newIdentities.contains(identity);
135         }
136
137         private static Predicate<Entry<String, String>> notAPropertyOf(final Identity identity) {
138                 return property -> (property != null) && !identity.getProperties().containsKey(property.getKey());
139         }
140
141         private static Predicate<Entry<String, String>> hasADifferentValueThanIn(final Identity newIdentity) {
142                 return property -> (property != null) && !newIdentity.getProperty(property.getKey()).equals(property.getValue());
143         }
144
145         private static Map<String, Identity> convertToMap(Collection<? extends Identity> identities) {
146                 ImmutableMap.Builder<String, Identity> mapBuilder = ImmutableMap.builder();
147                 for (Identity identity : identities) {
148                         mapBuilder.put(identity.getId(), identity);
149                 }
150                 return mapBuilder.build();
151         }
152
153         public interface IdentityProcessor {
154
155                 void processIdentity(Identity identity);
156
157         }
158
159 }