/* * The MIT License (MIT) * * Copyright (c) 2016 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.DataException; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataHashCursor; import com.wm.data.IDataIndexCursor; import com.wm.data.IDataPortable; import com.wm.data.IDataSharedCursor; import com.wm.data.IDataTreeCursor; import com.wm.util.Table; import com.wm.util.coder.IDataCodable; import com.wm.util.coder.ValuesCodable; import java.io.Serializable; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.NoSuchElementException; /** * A List compatible IData implementation. * * @param <K> The key's class. * @param <V> The value's class. */ public class ElementList<K, V> extends AbstractList<Element<K, V>> implements IData, Serializable { /** * The serialization identity of this class version. */ private static final long serialVersionUID = 1; /** * The internal list of elements. */ protected List<Element<K, V>> elements; /** * Constructs a new ElementList. */ public ElementList() { elements = new ArrayList<Element<K, V>>(); } /** * Constructs a new ElementList seeded with the elements in the given IData document. * * @param document The IData document to seed the ElementList with. */ public ElementList(IData document) { this(); addAll(document); } /** * Constructs a new ElementList seeded with the elements in the given Map. * * @param map The Map to seed the ElementList with. */ public ElementList(Map<? extends K, ? extends V> map) { this(); addAll(map); } /** * Constructs a new ElementList seeded with the elements in the given Collection. * * @param collection The Collection to seed the ElementList with. */ public ElementList(Collection<? extends Element<K, V>> collection) { this(); addAll(collection); } /** * Returns the Element at the given index. * * @param i The index whose Element is to be returned. * @return The Element at the given index in the list. */ public Element<K, V> get(int i) { return elements.get(i); } /** * Returns the number of elements in the list. * * @return The number of elements in the list. */ public int size() { return elements.size(); } /** * Sets the Element at the given index. * * @param i The index whose Element is to be set. * @param e The Element to be set. * @return The previous Element at the given index. */ public final Element<K, V> set(int i, Element<K, V> e) { return elements.set(i, normalize(e)); } /** * Inserts the given Element at the given index. * * @param i The index to insert an Element into. * @param e The Element to be inserted. */ public final void add(int i, Element<K, V> e) { elements.add(i, normalize(e)); } /** * Recursively adds all the elements in the given IData document to this ElementList. * * @param document The document whose elements are to be added. */ @SuppressWarnings("unchecked") public final void addAll(IData document) { if (document != null) { IDataCursor cursor = document.getCursor(); while (cursor.next()) { add(normalize((K)cursor.getKey(), (V)normalize(cursor.getValue()))); } cursor.destroy(); } } /** * Adds all the elements in the given Map to this ElementList. * * @param map The map whose elements are to be added. */ public final void addAll(Map<? extends K, ? extends V> map) { if (map != null) { for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { add(normalize(entry)); } } } /** * Normalizes the given Element; this default implementation returns the given element unmodified. Subclasses * can override this method to return a more appropriate Element subclass if required. * * @param element The Element to be normalized. * @return The normalized Element. */ protected Element<K, V> normalize(Element<K, V> element) { return element; } /** * Converts the given Map.Entry to an Element object. * * @param entry The Map.Entry to be converted. * @return An Element representation of the given Map.Entry object. */ protected final Element<K, V> normalize(Map.Entry<? extends K, ? extends V> entry) { return normalize(entry.getKey(), entry.getValue()); } /** * Converts the given key value pair to an Element object. * * @param key The key to be converted. * @param value The value to be converted. * @return An Element representation of the given key value pair. */ protected final Element<K, V> normalize(K key, V value) { return normalize(new Element<K, V>(key, value)); } /** * Converts the given value if it is an IData or IData[] compatible object to an ElementList or * ElementList[] respectively. * * @param value The value to be normalized. * @return If the value is an IData or IData[] compatible object, a new ElementList or * ElementList[] respectively is returned which wraps the given value, otherwise * the value itself is returned unmodified. */ @SuppressWarnings("unchecked") protected Object normalize(Object value) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { value = normalize(IDataHelper.toIDataArray(value)); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = normalize(IDataHelper.toIData(value)); } return value; } /** * Normalizes the given IData; this implementation converts the IData to an ElementList. Subclasses should * override this method and return an instance of their self. * * @param document The IData to be normalized. * @return The normalized IData. */ protected IData normalize(IData document) { return new ElementList<String, Object>(document); } /** * Normalizes the given IData[]; this implementation converts the IData[] to an ElementList[]. * * @param documents The IData[] to be normalized. * @return The normalized IData[]. */ protected final IData[] normalize(IData[] documents) { IData[] output = new IData[documents == null ? 0 : documents.length]; if (documents != null) { for(int i = 0; i < documents.length; i++) { output[i] = normalize(documents[i]); } } return output; } /** * Returns a newly created IData object. * * @return A newly created IData object. */ public static IData create() { return new ElementList<String, Object>(); } /** * Removes the Element at the given index. * * @param i The index whose Element is to be removed. * @return The Element previously positioned at the given index, now removed from the list. */ public Element<K, V> remove(int i) { return elements.remove(i); } /** * Returns an IDataCursor for this IData object. An IDataCursor contains the basic methods you use to traverse an * IData object and get or set elements within it. * * @return An IDataCursor for this object. */ @Override public IDataCursor getCursor() { return new ElementListCursor(); } /** * Returns an IDataSharedCursor for this IData object. An IDataSharedCursor contains the basic methods you use to * traverse an IData object and get or set elements within it. * * @return An IDataSharedCursor for this object. */ @Override public IDataSharedCursor getSharedCursor() { return new IDataSharedCursorAdapter(getCursor()); } /** * Returns an IDataIndexCursor for traversing this IData. * * @return An IDataIndexCursor for traversing this IData. * @throws UnsupportedOperationException As this method is not implemented. * @deprecated */ @Override public IDataIndexCursor getIndexCursor() { throw new UnsupportedOperationException("getIndexCursor not implemented"); } /** * Returns an IDataTreeCursor for traversing this IData. * * @return An IDataTreeCursor for traversing this IData. * @throws UnsupportedOperationException As this method is not implemented. * @deprecated */ @Override public IDataTreeCursor getTreeCursor() { throw new UnsupportedOperationException("getTreeCursor not implemented"); } /** * Returns an IDataHashCursor for traversing this IData. * * @return An IDataHashCursor for traversing this IData. * @throws UnsupportedOperationException As this method is not implemented. * @deprecated */ @Override public IDataHashCursor getHashCursor() { throw new UnsupportedOperationException("getHashCursor not implemented"); } /** * Returns a string representation of this object. * * @return A string representation of this object. */ @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("{ "); boolean first = true; for (Element element : this) { if (!first) buffer.append(", "); buffer.append(element.toString()); first = false; } buffer.append(" }"); return buffer.toString(); } /** * IDataCursor implementation for an ElementList object. */ protected class ElementListCursor implements IDataCursor { /** * This cursor's position in the list. */ protected int position; /** * This cursor's list iterator. */ protected ListIterator<Element<K, V>> iterator; /** * The Element at the cursor's current position. */ protected Element<K, V> element = null; /** * Constructs a new cursor. */ public ElementListCursor() { initialize(); } /** * Constructs a new cursor initialized to the given position. * * @param position The initial position of the cursor. */ public ElementListCursor(int position) { initialize(position); } /** * Initializes this cursor. */ protected void initialize() { initialize(-1); } /** * Initializes this cursor with the given position. * * @param position The initial position of the cursor. */ protected void initialize(int position) { iterator = listIterator(); if (position >= 0) { for (int i = 0; i <= position; i++) { next(); } } } /** * Not implemented, does nothing. * * @param mode Not used. */ public void setErrorMode(int mode) {} /** * Not implemented, does nothing. * * @return Null. */ public DataException getLastError() { return null; } /** * Not implemented, does nothing. * * @return False. */ public boolean hasMoreErrors() { return false; } /** * Resets this cursor. */ public void home() { initialize(); } /** * Returns the key at the cursor's current position. * * @return The key at the cursor's current position. */ public String getKey() { return element == null ? null : element.getKey().toString(); } /** * Sets the key at the cursor's current position. * * @param key The key to be set. */ @SuppressWarnings("unchecked") public void setKey(String key) { if (element != null) { element.setKey((K)key); } } /** * Returns the value at the cursor's current position. * * @return The value at the cursor's current position. */ public Object getValue() { return element == null ? null : element.getValue(); } /** * Sets the value at the cursor's current position. * * @param value The value to be set. */ @SuppressWarnings("unchecked") public void setValue(Object value) { if (element != null) { element.setValue((V)value); } } /** * Deletes the Element at the cursor's current position. * * @return True if the Element was deleted. */ public boolean delete() { boolean result = true; try { iterator.remove(); } catch(IllegalStateException ex) { result = false; } return result; } /** * Inserts the key value pair before the cursor's current position. * * @param key The key to be inserted. * @param value The value to be inserted. */ @SuppressWarnings("unchecked") public void insertBefore(String key, Object value) { previous(); iterator.add(new Element((K)key, (V)value)); next(); } /** * Inserts the key value pair after the cursor's current position. * * @param key The key to be inserted. * @param value The value to be inserted. */ @SuppressWarnings("unchecked") public void insertAfter(String key, Object value) { iterator.add(new Element((K)key, (V)value)); } /** * Inserts the key with a new IData document before the cursor's current position. * * @param key The key to be inserted. * @return The new IData document inserted. */ public IData insertDataBefore(String key) { IData data = new ElementList<K, V>(); insertBefore(key, data); return data; } /** * Inserts the key with a new IData document after the cursor's current position. * * @param key The key to be inserted. * @return The new IData document inserted. */ public IData insertDataAfter(String key) { IData data = new ElementList<K, V>(); insertAfter(key, data); return data; } /** * Repositions this cursor's on the next Element. * * @return True if the cursor was repositioned. */ public boolean next() { boolean result = true; try { element = iterator.next(); } catch(NoSuchElementException ex) { result = false; } return result; } /** * Repositions this cursor's on the next occurrence of the given key. * * @param key The key to reposition the cursor to. * @return True if the key existed and the cursor was repositioned. */ @SuppressWarnings("unchecked") public boolean next(String key) { while(next()) { if (element.keyEquals((K)key)) return true; } return false; } /** * Repositions this cursor's on the previous Element. * * @return True if the cursor was repositioned. */ public boolean previous() { boolean result = true; try { element = iterator.previous(); } catch(NoSuchElementException ex) { result = false; } return result; } /** * Repositions this cursor's on the previous occurrence of the given key. * * @param key The key to reposition the cursor to. * @return True if the key existed and the cursor was repositioned. */ @SuppressWarnings("unchecked") public boolean previous(String key) { while(previous()) { if (element.keyEquals((K)key)) return true; } return false; } /** * Repositions this cursor's on the first Element. * * @return True if the cursor was repositioned. */ public boolean first() { initialize(); return next(); } /** * Repositions this cursor's on the first occurrence of the given key. * * @param key The key to reposition the cursor to. * @return True if the key existed and the cursor was repositioned. */ public boolean first(String key) { initialize(); return next(key); } /** * Repositions this cursor's on the last Element. * * @return True if the cursor was repositioned. */ public boolean last() { boolean result = false; while(iterator.hasNext()) { result = true; next(); } return result; } /** * Repositions this cursor's on the last occurrence of the given key. * * @param key The key to reposition the cursor to. * @return True if the key existed and the cursor was repositioned. */ public boolean last(String key) { last(); previous(key); return next(); } /** * Returns true if this cursor has more data to be iterated over. * * @return True if this cursor has more data to be iterated over. */ public boolean hasMoreData() { return iterator.hasNext(); } /** * Destroys this cursor. */ public void destroy() { iterator = null; } /** * Returns a clone of this cursor. * * @return A clone of this cursor. */ public IDataCursor getCursorClone() { return new ElementListCursor(this.position); } } }