// Copyright 2017 JanusGraph Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.janusgraph.util.datastructures; import java.util.*; /** * CompactMap is compact representation of the {@link Map} interface which is immutable. * * @author Matthias Broecheler (me@matthiasb.com) */ public class CompactMap implements Map<String,Object> { private final String[] keys; private final Object[] values; private CompactMap(final String[] keys, final Object[] values) { checkKeys(keys); if (values==null || values.length<1) throw new IllegalArgumentException("Invalid values"); if (values.length!=keys.length) throw new IllegalArgumentException("Keys and values do not match in length"); this.keys= deduplicateKeys(keys); this.values=values; } public static final CompactMap of(final String[] keys, final Object[] values) { return new CompactMap(keys,values); } private static final void checkKeys(final String[] keys) { if (keys==null || keys.length<1) throw new IllegalArgumentException("Invalid keys"); for (int i=0;i<keys.length;i++) if (keys[i]==null) throw new IllegalArgumentException("Key cannot be null at position " + i); } private static final Map<KeyContainer,KeyContainer> KEY_CACHE = new HashMap<KeyContainer, KeyContainer>(100); private static final KeyContainer KEY_HULL = new KeyContainer(); /** * Deduplicates keys arrays to keep the memory footprint on CompactMap to a minimum. * * This implementation is blocking for simplicity. To improve performance in multi-threaded * environments, use a thread-local KEY_HULL and a concurrent hash map for KEY_CACHE. * * @param keys String array to deduplicate by checking against KEY_CACHE * @return A deduplicated version of the given keys array */ private static final String[] deduplicateKeys(String[] keys) { synchronized (KEY_CACHE) { KEY_HULL.setKeys(keys); KeyContainer retrieved = KEY_CACHE.get(KEY_HULL); if (retrieved==null) { retrieved = new KeyContainer(keys); KEY_CACHE.put(retrieved, retrieved); } return retrieved.getKeys(); } } @Override public int size() { return keys.length; } @Override public boolean isEmpty() { return false; } @Override public boolean containsKey(Object o) { return indexOf(keys,o)>=0; } @Override public boolean containsValue(Object o) { return indexOf(values,o)>=0; } private static int indexOf(Object[] arr, Object o) { for (int i=0;i<arr.length;i++) if (arr[i].equals(o)) return i; return -1; } @Override public Object get(Object o) { int pos = indexOf(keys,o); if (pos>=0) return values[pos]; else return null; } @Override public Object put(String s, Object o) { throw new UnsupportedOperationException("This map is immutable"); } @Override public Object remove(Object o) { throw new UnsupportedOperationException("This map is immutable"); } @Override public void putAll(Map<? extends String, ? extends Object> map) { throw new UnsupportedOperationException("This map is immutable"); } @Override public void clear() { throw new UnsupportedOperationException("This map is immutable"); } @Override public Set<String> keySet() { return new Set<String>() { @Override public int size() { return keys.length; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return indexOf(keys,o)>=0; } @Override public Iterator<String> iterator() { return new Iterator<String>() { private int currentPos = -1; @Override public boolean hasNext() { return currentPos<keys.length-1; } @Override public String next() { if (!hasNext()) throw new NoSuchElementException(); currentPos++; return keys[currentPos]; } @Override public void remove() { throw new UnsupportedOperationException("This map is immutable"); } }; } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] ts) { throw new UnsupportedOperationException(); } @Override public boolean add(String s) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean containsAll(Collection<?> objects) { for (Object o : objects) if (!contains(o)) return false; return true; } @Override public boolean addAll(Collection<? extends String> strings) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean retainAll(Collection<?> objects) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean removeAll(Collection<?> objects) { throw new UnsupportedOperationException("This map is immutable"); } @Override public void clear() { throw new UnsupportedOperationException("This map is immutable"); } }; } @Override public Collection<Object> values() { return Arrays.asList(values); } @Override public Set<Entry<String, Object>> entrySet() { return new Set<Entry<String, Object>>() { @Override public int size() { return keys.length; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<Entry<String, Object>> iterator() { return new Iterator<Entry<String, Object>>() { int currentPos = -1; @Override public boolean hasNext() { return currentPos <keys.length-1; } @Override public Entry<String, Object> next() { if (!hasNext()) throw new NoSuchElementException(); currentPos++; return new Entry<String, Object>() { private final int position = currentPos; @Override public String getKey() { return keys[position]; } @Override public Object getValue() { return values[position]; } @Override public Object setValue(Object o) { throw new UnsupportedOperationException("This map is immutable"); } }; } @Override public void remove() { throw new UnsupportedOperationException("This map is immutable"); } }; } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] ts) { throw new UnsupportedOperationException(); } @Override public boolean add(Entry<String, Object> stringObjectEntry) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean containsAll(Collection<?> objects) { for (Object o : objects) if (!contains(o)) return false; return true; } @Override public boolean addAll(Collection<? extends Entry<String, Object>> entries) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean retainAll(Collection<?> objects) { throw new UnsupportedOperationException("This map is immutable"); } @Override public boolean removeAll(Collection<?> objects) { throw new UnsupportedOperationException("This map is immutable"); } @Override public void clear() { throw new UnsupportedOperationException("This map is immutable"); } }; } private static class KeyContainer { private String[] keys; private int hashcode; KeyContainer(final String[] keys) { setKeys(keys); } KeyContainer() {} void setKeys(final String[] keys) { checkKeys(keys); this.keys = keys; this.hashcode= Arrays.hashCode(keys); } public String[] getKeys() { return keys; } @Override public int hashCode() { return hashcode; } @Override public boolean equals(Object other) { if (this==other) return true; else if (!(other instanceof KeyContainer)) return false; return Arrays.deepEquals(keys,((KeyContainer)other).keys); } public static final KeyContainer of(String[] header) { return new KeyContainer(header); } } }