/* * The MIT License (MIT) * * Copyright (c) 2015 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.data; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataPortable; import com.wm.data.IDataUtil; import com.wm.util.coder.IDataCodable; import com.wm.util.coder.ValuesCodable; import java.io.Serializable; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; /** * Wraps an IData document in an implementation of the Iterable, Comparable, and Map interfaces. */ public class IDataMap extends IDataAdapter implements Iterable<Map.Entry<String, Object>>, Comparable<IData>, Map<String, Object>, Cloneable, Serializable { private static final long serialVersionUID = 1; /** * The default comparator used when no other comparator or comparison criteria is specified. */ public static final IDataComparator DEFAULT_COMPARATOR = BasicIDataComparator.getInstance(); protected IDataComparator comparator = DEFAULT_COMPARATOR; /** * Construct a new IDataMap object. */ public IDataMap() { super(); } /** * Construct a new IDataMap object. * * @param document The IData document to be wrapped. */ public IDataMap(IData document) { super(document); } /** * Construct a new IDataMap object. * * @param document The IData document to be wrapped. * @param comparator The IDataComparator to be used to compare IData objects. */ public IDataMap(IData document, IDataComparator comparator) { this(document); setComparator(comparator); } /** * Constructs a new IDataMap wrapping the given IDataCodable object. * * @param codable The IDataCodable object to be wrapped. */ public IDataMap(IDataCodable codable) { super(codable); } /** * Constructs a new IDataMap wrapping the given IDataCodable object. * * @param codable The IDataCodable object to be wrapped. * @param comparator The IDataComparator to be used to compare IData objects. */ public IDataMap(IDataCodable codable, IDataComparator comparator) { this(codable); setComparator(comparator); } /** * Constructs a new IDataMap wrapping the given IDataPortable object. * * @param portable The IDataPortable object to be wrapped. */ public IDataMap(IDataPortable portable) { super(portable); } /** * Constructs a new IDataMap wrapping the given IDataPortable object. * * @param portable The IDataPortable object to be wrapped. * @param comparator The IDataComparator to be used to compare IData objects. */ public IDataMap(IDataPortable portable, IDataComparator comparator) { this(portable); setComparator(comparator); } /** * Constructs a new IDataMap wrapping the given ValuesCodable object. * * @param codable The ValuesCodable object to be wrapped. */ public IDataMap(ValuesCodable codable) { super(codable); } /** * Constructs a new IDataMap wrapping the given ValuesCodable object. * * @param codable The ValuesCodable object to be wrapped. * @param comparator The IDataComparator to be used to compare IData objects. */ public IDataMap(ValuesCodable codable, IDataComparator comparator) { this(codable); setComparator(comparator); } /** * Constructs a new IDataMap seeded with the given Map of key value entries. * * @param map The map to see this new object with. */ public IDataMap(Map<?, ?> map) { this(IDataHelper.toIData(map)); } /** * Constructs a new IDataMap seeded with the given Map of key value entries. * * @param map The map to see this new object with. * @param comparator The IDataComparator to be used to compare IData objects. */ public IDataMap(Map<?, ?> map, IDataComparator comparator) { this(map); setComparator(comparator); } /** * Returns a Collection view of the values contained in this map. * * @return A collection view of the values contained in this map. */ @Override public Collection<Object> values() { return Arrays.asList(IDataHelper.getValues(this)); } /** * Returns a Set view of the keys contained in this map. * * @return A set view of the keys contained in this map. */ @Override public Set<String> keySet() { String[] keys = IDataHelper.getKeys(this); Set<String> keySet = new LinkedHashSet<String>(keys.length); keySet.addAll(Arrays.asList(keys)); return keySet; } /** * Returns true if this map contains a mapping for the specified key. * * @param key A key whose presence in this map is to be tested. * @return True if this map contains a mapping for the specified key. */ @Override public boolean containsKey(Object key) { IDataCursor cursor = this.getCursor(); boolean contains = cursor.next((String)key); cursor.destroy(); return contains; } /** * Returns true if this map maps one or more keys to the specified value. * * @param value The value whose presence in this map is to be tested. * @return True if this map maps one or more keys to the specified value. */ @Override public boolean containsValue(Object value) { return Arrays.binarySearch(IDataHelper.getValues(this), value) >= 0; } /** * Removes the mapping for a key from this map if it is present (optional operation). * * @param key A key whose mapping is to be removed from the map. * @return The previous value associated with key, or null if there was no mapping for key. */ @Override public Object remove(Object key) { IDataCursor cursor = this.getCursor(); Object value = get(key); IDataUtil.remove(cursor, (String)key); return value; } /** * Returns true if this map contains no key-value mappings. * * @return True if this map contains no key-value mappings. */ @Override public boolean isEmpty() { return size() == 0; } /** * Returns the number of key-value mappings in this map. * * @return The number of key-value mappings in this map. */ @Override public int size() { return IDataHelper.size(this); } /** * Returns a Set view of the mappings contained in this map. * * @return A set view of the mappings contained in this map. */ @Override public Set<Map.Entry<String, Object>> entrySet() { Set<Map.Entry<String, Object>> set = new LinkedHashSet<Map.Entry<String, Object>>(size()); for (Map.Entry<String, Object> entry : this) { set.add(entry); } return set; } /** * Associates the specified value with the specified key in this map. * * @param key Key with which the specified value is to be associated. * @param value Value to be associated with the specified key. * @param includeNull If true, null values will be inserted into the map. * @return The previous value associated with key, or null if there was no mapping for key. */ public Object put(String key, Object value, boolean includeNull) { Object previousValue = get(key); if (includeNull || value != null) { IDataCursor cursor = this.getCursor(); IDataUtil.put(cursor, key, value); cursor.destroy(); } else { IDataCursor cursor = this.getCursor(); IDataUtil.remove(cursor, key); cursor.destroy(); } return previousValue; } /** * Associates the specified value with the specified key in this map. * * @param key Key with which the specified value is to be associated. * @param value Value to be associated with the specified key. * @return The previous value associated with key, or null if there was no mapping for key. */ @Override public Object put(String key, Object value) { return put(key, value, true); } /** * Copies all of the mappings from the specified map to this map. * * @param map Mappings to be stored in this map. */ @Override public void putAll(Map<? extends String, ?> map) { for (Map.Entry<? extends String, ?> entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } /** * Copies all of the mappings from the specified document to this map. * * @param document Mappings to be stored in this map. */ public void putAll(IData document) { putAll((Map)IDataMap.of(document)); } /** * Copies all of the mappings from the specified document to this map. * * @param document Mappings to be stored in this map. */ public void merge(IData document) { putAll(document); } /** * Copies all of the mappings from the specified Map to this IDataMap. * * @param map A Map containing key value pairs to be stored in this IDataMap. */ public void merge(Map<? extends String, ?> map) { putAll(map); } /** * Returns a new IDataMap wrapping the given IData document. * * @param document The document to be wrapped. * @return A new IDataMap wrapping the given IData document. */ public static IDataMap of(IData document) { return new IDataMap(document); } /** * Returns a new IDataMap[] representation of the given IData[] document list. * * @param array An IData[] document list. * @return A new IDataMap[] representation of the given IData[] document list. */ public static IDataMap[] of(IData[] array) { IDataMap[] output = null; if (array != null) { output = new IDataMap[array.length]; for (int i = 0; i < array.length; i++) { output[i] = new IDataMap(array[i]); } } return output; } /** * Returns a new IDataMap that includes all the key value pairs from the given Map. * * @param map The Map to seed the new IDataMap with. * @return A new IDataMap that includes all the key value pairs from the given Map. */ public static IDataMap of(Map<? extends String, ?> map) { return new IDataMap(map); } /** * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key. * * @param key The key whose associated value is to be returned. * @return The value to which the specified key is mapped, or null if this map contains no mapping for the key */ @Override public Object get(Object key) { IDataCursor cursor = getCursor(); Object value = IDataUtil.get(cursor, (String)key); cursor.destroy(); return value; } /** * Removes all of the mappings from this map. The map will be empty after this call returns. */ @Override public void clear() { IDataHelper.clear(this); } /** * Returns an iterator over a set of elements of type Map.Entry. * * @return An iterator. */ @Override public IDataIterator iterator() { return new IDataIteratorImplementation(getCursor()); } /** * Compares this object with the specified object for order. * * @param other The object to be compared with this object. * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater * than the specified object. */ @Override public int compareTo(IData other) { return comparator.compare(document, other); } /** * Returns true if this object is equal to the given object. * * @param other The object to compare to. * @return True if this object is equal to the given object. */ @Override public boolean equals(Object other) { if (!(other instanceof IData)) return false; return this == other || DEFAULT_COMPARATOR.compare(document, (IData)other) == 0; } /** * Returns the IDataComparator used to compare IData objects. * * @return The IDataComparator used to compare IData objects. */ public IDataComparator getComparator() { return comparator; } /** * Sets the IDataComparator to be used when comparing IData objects. * * @param comparator The IDataComparator to be used when comparing IData objects. */ public void setComparator(IDataComparator comparator) { if (comparator == null) throw new IllegalArgumentException("comparator must not be null"); this.comparator = comparator; } /** * Returns a newly created IData object. * * @return A newly created IData object. */ public static IData create() { return new IDataMap(); } /** * Returns a clone of this IData object. * * @return A clone of this IData object. */ @Override public IDataMap clone() { return new IDataMap(IDataHelper.duplicate(document)); } /** * Implementation class for an IDataIterator. */ private static class IDataIteratorImplementation implements IDataIterator { protected IDataCursor cursor; /** * Constructs a new IDataIterator object for iterating with the given IDataCursor. * * @param cursor The cursor to be iterated with. */ public IDataIteratorImplementation(IDataCursor cursor) { this.cursor = cursor; } /** * Returns true if the iteration has more elements. (In other words, returns true if next() would return an * element rather than throwing an exception.) * * @return True if the iteration has more elements. */ @Override public boolean hasNext() { return cursor != null && cursor.hasMoreData(); } /** * Returns the next element in the iteration. * * @return The next element in the iteration. * @throws NoSuchElementException If the iteration has no more elements. */ @Override public Map.Entry<String, Object> next() throws NoSuchElementException { if (cursor != null && cursor.next()) { return new AbstractMap.SimpleImmutableEntry<String, Object>(cursor.getKey(), cursor.getValue()); } else { throw new NoSuchElementException("No more elements were available for iteration in IData document"); } } /** * Throws an UnsupportedOperationException because the remove operation is not supported by this iterator. * * @throws UnsupportedOperationException The remove operation is not supported by this iterator. */ @Override public void remove() { throw new UnsupportedOperationException("remove method is not implemented by this iterator class"); } } }