001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package ninja.leaping.configurate.util;
018
019import com.google.common.collect.ImmutableSet;
020import org.checkerframework.checker.nullness.qual.NonNull;
021
022import java.util.Collection;
023import java.util.Comparator;
024import java.util.LinkedHashMap;
025import java.util.Map;
026import java.util.Objects;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.ConcurrentMap;
030import java.util.concurrent.ConcurrentSkipListMap;
031
032import static java.util.Objects.requireNonNull;
033
034/**
035 * Default implementations of {@link MapFactory}.
036 */
037public final class MapFactories {
038    private MapFactories() {}
039
040    /**
041     * Returns a {@link MapFactory} which creates maps without an order.
042     *
043     * @return A map factory which produces unordered maps
044     */
045    public static MapFactory unordered() {
046        return DefaultFactory.UNORDERED;
047    }
048
049    /**
050     * Returns a {@link MapFactory} which creates maps which are sorted using the given comparator.
051     *
052     * @param comparator The comparator used to sort the map keys
053     * @return A map factory which produces sorted maps
054     */
055    public static MapFactory sorted(Comparator<Object> comparator) {
056        return new SortedMapFactory(requireNonNull(comparator, "comparator"));
057    }
058
059    /**
060     * Returns a {@link MapFactory} which creates maps which are naturally sorted.
061     *
062     * @return A map factory which produces naturally sorted maps
063     * @see Comparator#naturalOrder()
064     */
065    public static MapFactory sortedNatural() {
066        return DefaultFactory.SORTED_NATURAL;
067    }
068
069    /**
070     * Returns a {@link MapFactory} which creates maps which are sorted by insertion order.
071     *
072     * @return A map factory which produces maps sorted by insertion order
073     */
074    public static MapFactory insertionOrdered() {
075        return DefaultFactory.INSERTION_ORDERED;
076    }
077
078    private enum DefaultFactory implements MapFactory {
079        UNORDERED {
080            @NonNull
081            @Override
082            public <K, V> ConcurrentMap<K, V> create() {
083                return new ConcurrentHashMap<>();
084            }
085        },
086        SORTED_NATURAL {
087            @NonNull
088            @Override
089            public <K, V> ConcurrentMap<K, V> create() {
090                return new ConcurrentSkipListMap<>();
091            }
092        },
093        INSERTION_ORDERED {
094            @NonNull
095            @Override
096            public <K, V> ConcurrentMap<K, V> create() {
097                return new SynchronizedWrapper<>(new LinkedHashMap<>());
098            }
099        }
100    }
101
102    private static final class SortedMapFactory implements MapFactory {
103        private final Comparator<Object> comparator;
104
105        private SortedMapFactory(Comparator<Object> comparator) {
106            this.comparator = comparator;
107        }
108
109        @NonNull
110        @Override
111        public <K, V> ConcurrentMap<K, V> create() {
112            return new ConcurrentSkipListMap<>(comparator);
113        }
114
115        @Override
116        public boolean equals(Object obj) {
117            return obj instanceof SortedMapFactory && comparator.equals(((SortedMapFactory) obj).comparator);
118        }
119
120        @Override
121        public int hashCode() {
122            return comparator.hashCode();
123        }
124
125        @Override
126        public String toString() {
127            return "SortedMapFactory{comparator=" + comparator + '}';
128        }
129    }
130
131    private static class SynchronizedWrapper<K, V> implements ConcurrentMap<K, V> {
132        private final Map<K, V> wrapped;
133
134        private SynchronizedWrapper(Map<K, V> wrapped) {
135            this.wrapped = wrapped;
136        }
137
138        @Override
139        public V putIfAbsent(K k, V v) {
140            synchronized (wrapped) {
141                if (!wrapped.containsKey(k)) {
142                    wrapped.put(k, v);
143                } else {
144                    return wrapped.get(k);
145                }
146            }
147            return null;
148        }
149
150        @Override
151        public boolean remove(Object key, Object expected) {
152            synchronized (wrapped) {
153                if (Objects.equals(expected, wrapped.get(key))) {
154                    return wrapped.remove(key) != null;
155                }
156            }
157            return false;
158        }
159
160        @Override
161        public boolean replace(K key, V old, V replace) {
162            synchronized (wrapped) {
163                if (Objects.equals(old, wrapped.get(key))) {
164                    wrapped.put(key, replace);
165                    return true;
166                }
167            }
168            return false;
169        }
170
171        @Override
172        public V replace(K k, V v) {
173            synchronized (wrapped) {
174                if (wrapped.containsKey(k)) {
175                    return wrapped.put(k, v);
176                }
177            }
178            return null;
179        }
180
181        @Override
182        public int size() {
183            synchronized (wrapped) {
184                return wrapped.size();
185            }
186        }
187
188        @Override
189        public boolean isEmpty() {
190            synchronized (wrapped) {
191                return wrapped.isEmpty();
192            }
193        }
194
195        @Override
196        public boolean containsKey(Object o) {
197            synchronized (wrapped) {
198                return wrapped.containsKey(o);
199            }
200        }
201
202        @Override
203        public boolean containsValue(Object o) {
204            synchronized (wrapped) {
205                return wrapped.containsKey(o);
206            }
207        }
208
209        @Override
210        public V get(Object o) {
211            synchronized (wrapped) {
212                return wrapped.get(o);
213            }
214        }
215
216        @Override
217        public V put(K k, V v) {
218            synchronized (wrapped) {
219                return wrapped.put(k, v);
220            }
221        }
222
223        @Override
224        public V remove(Object o) {
225            synchronized (wrapped) {
226                return wrapped.remove(o);
227            }
228        }
229
230        @Override
231        public void putAll(Map<? extends K, ? extends V> map) {
232            synchronized (wrapped) {
233                wrapped.putAll(map);
234            }
235        }
236
237        @Override
238        public void clear() {
239            synchronized (wrapped) {
240                wrapped.clear();
241            }
242        }
243
244        @Override
245        public Set<K> keySet() {
246            synchronized (wrapped) {
247                return ImmutableSet.copyOf(wrapped.keySet());
248            }
249        }
250
251        @Override
252        public Collection<V> values() {
253            synchronized (wrapped) {
254                return ImmutableSet.copyOf(wrapped.values());
255            }
256        }
257
258        @Override
259        public Set<Entry<K, V>> entrySet() {
260            synchronized (wrapped) {
261                return ImmutableSet.copyOf(wrapped.entrySet());
262            }
263        }
264
265        @Override
266        public boolean equals(Object obj) {
267            if (obj == this) {
268                return true;
269            }
270            synchronized (wrapped) {
271                return wrapped.equals(obj);
272            }
273        }
274
275        @Override
276        public int hashCode() {
277            synchronized (wrapped) {
278                return wrapped.hashCode();
279            }
280        }
281
282        @Override
283        public String toString() {
284            synchronized (wrapped) {
285                return "SynchronizedWrapper{backing=" + wrapped.toString() + '}';
286            }
287        }
288    }
289
290}