Reaction.java

  1package eu.siacs.conversations.entities;
  2
  3import androidx.annotation.NonNull;
  4
  5import com.google.common.base.MoreObjects;
  6import com.google.common.base.Strings;
  7import com.google.common.collect.Collections2;
  8import com.google.common.collect.ImmutableList;
  9import com.google.common.collect.ImmutableSet;
 10import com.google.common.collect.Maps;
 11import com.google.common.collect.Multimaps;
 12import com.google.common.collect.Ordering;
 13import com.google.gson.reflect.TypeToken;
 14import com.google.gson.Gson;
 15import com.google.gson.GsonBuilder;
 16import com.google.gson.JsonSyntaxException;
 17import com.google.gson.TypeAdapter;
 18import com.google.gson.stream.JsonReader;
 19import com.google.gson.stream.JsonToken;
 20import com.google.gson.stream.JsonWriter;
 21
 22import eu.siacs.conversations.xmpp.Jid;
 23
 24import java.io.IOException;
 25import java.util.ArrayList;
 26import java.util.Arrays;
 27import java.util.Collection;
 28import java.util.Collections;
 29import java.util.Comparator;
 30import java.util.List;
 31import java.util.Map;
 32import java.util.Set;
 33
 34public class Reaction {
 35
 36    public static final List<String> SUGGESTIONS =
 37            Arrays.asList(
 38                    "\u2764\uFE0F",
 39                    "\uD83D\uDC4D",
 40                    "\uD83D\uDC4E",
 41                    "\uD83D\uDE02",
 42                    "\u2757",
 43                    "\u2753");
 44
 45    private static final Gson GSON;
 46
 47    static {
 48        GSON = new GsonBuilder().registerTypeAdapter(Jid.class, new JidTypeAdapter()).create();
 49    }
 50
 51    public final String reaction;
 52    public final boolean received;
 53    public final Jid from;
 54    public final Jid trueJid;
 55    public final String occupantId;
 56
 57    public Reaction(
 58            final String reaction,
 59            boolean received,
 60            final Jid from,
 61            final Jid trueJid,
 62            final String occupantId) {
 63        this.reaction = reaction;
 64        this.received = received;
 65        this.from = from;
 66        this.trueJid = trueJid;
 67        this.occupantId = occupantId;
 68    }
 69
 70    public static String toString(final Collection<Reaction> reactions) {
 71        return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions);
 72    }
 73
 74    public static Collection<Reaction> fromString(final String asString) {
 75        if (Strings.isNullOrEmpty(asString)) {
 76            return Collections.emptyList();
 77        }
 78        try {
 79            return GSON.fromJson(asString, new TypeToken<ArrayList<Reaction>>() {}.getType());
 80        } catch (final JsonSyntaxException e) {
 81            return Collections.emptyList();
 82        }
 83    }
 84
 85    public static Collection<Reaction> append(
 86            final Collection<Reaction> existing,
 87            final Collection<String> reactions,
 88            final boolean received,
 89            final Jid from,
 90            final Jid trueJid,
 91            final String occupantId) {
 92        final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
 93        builder.addAll(existing);
 94        builder.addAll(
 95                Collections2.transform(
 96                        reactions, r -> new Reaction(r, received, from, trueJid, occupantId)));
 97        return builder.build();
 98    }
 99
100    public static Collection<Reaction> withOccupantId(
101            final Collection<Reaction> existing,
102            final Collection<String> reactions,
103            final boolean received,
104            final Jid from,
105            final Jid trueJid,
106            final String occupantId) {
107        final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
108        builder.addAll(Collections2.filter(existing, e -> !occupantId.equals(e.occupantId)));
109        builder.addAll(
110                Collections2.transform(
111                        reactions, r -> new Reaction(r, received, from, trueJid, occupantId)));
112        return builder.build();
113    }
114
115    @NonNull
116    @Override
117    public String toString() {
118        return MoreObjects.toStringHelper(this)
119                .add("reaction", reaction)
120                .add("received", received)
121                .add("from", from)
122                .add("trueJid", trueJid)
123                .add("occupantId", occupantId)
124                .toString();
125    }
126
127    public int hashCode() {
128        return toString().hashCode();
129    }
130
131    public boolean equals(Object o) {
132        if (o == null) return false;
133        if (!(o instanceof Reaction)) return false;
134        return toString().equals(o.toString());
135    }
136
137    public static Collection<Reaction> withFrom(
138            final Collection<Reaction> existing,
139            final Collection<String> reactions,
140            final boolean received,
141            final Jid from) {
142        final ImmutableSet.Builder<Reaction> builder = new ImmutableSet.Builder<>();
143        builder.addAll(
144                Collections2.filter(existing, e -> !from.asBareJid().equals(e.from.asBareJid())));
145        builder.addAll(
146                Collections2.transform(
147                        reactions, r -> new Reaction(r, received, from, null, null)));
148        return builder.build();
149    }
150
151    private static class JidTypeAdapter extends TypeAdapter<Jid> {
152        @Override
153        public void write(final JsonWriter out, final Jid value) throws IOException {
154            if (value == null) {
155                out.nullValue();
156            } else {
157                out.value(value.toEscapedString());
158            }
159        }
160
161        @Override
162        public Jid read(final JsonReader in) throws IOException {
163            if (in.peek() == JsonToken.NULL) {
164                in.nextNull();
165                return null;
166            } else if (in.peek() == JsonToken.STRING) {
167                final String value = in.nextString();
168                return Jid.ofEscaped(value);
169            }
170            throw new IOException("Unexpected token");
171        }
172    }
173
174    public static Aggregated aggregated(final Collection<Reaction> reactions) {
175        final Map<String, Integer> aggregatedReactions =
176                Maps.transformValues(
177                        Multimaps.index(reactions, r -> r.reaction).asMap(), Collection::size);
178        final List<Map.Entry<String, Integer>> sortedList =
179                Ordering.from(
180                                Comparator.comparingInt(
181                                        (Map.Entry<String, Integer> o) -> o.getValue()))
182                        .reverse()
183                        .immutableSortedCopy(aggregatedReactions.entrySet());
184        return new Aggregated(
185                sortedList,
186                ImmutableSet.copyOf(
187                        Collections2.transform(
188                                Collections2.filter(reactions, r -> !r.received),
189                                r -> r.reaction)));
190    }
191
192    public static final class Aggregated {
193
194        public final List<Map.Entry<String, Integer>> reactions;
195        public final Set<String> ourReactions;
196
197        private Aggregated(
198                final List<Map.Entry<String, Integer>> reactions, Set<String> ourReactions) {
199            this.reactions = reactions;
200            this.ourReactions = ourReactions;
201        }
202    }
203}