/*
* 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 permafrost.tundra.lang.LocaleHelper;
import java.io.Serializable;
import java.util.Collection;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* A case-insensitive IData implementation.
*
* @param <V> The class of values held by this IData implementation.
*/
public class CaseInsensitiveElementList<V> extends ElementList<String, V> implements Serializable {
/**
* The serialization identity for this class and version.
*/
private static final long serialVersionUID = 1;
/**
* The locale used for case comparison.
*/
protected Locale locale;
/**
* Constructs a new CaseInsensitiveElementList.
*/
public CaseInsensitiveElementList() {
this((Locale)null);
}
/**
* Constructs a new CaseInsensitiveElementList.
*
* @param locale The locale to use for case comparison.
*/
public CaseInsensitiveElementList(Locale locale) {
setLocale(locale);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given IData document.
*
* @param document The IData document to seed the CaseInsensitiveElementList with.
*/
public CaseInsensitiveElementList(IData document) {
this(document, null);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given IData document.
*
* @param document The IData document to seed the CaseInsensitiveElementList with.
* @param locale The locale to use for case comparison.
*/
public CaseInsensitiveElementList(IData document, Locale locale) {
this(locale);
addAll(document);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given Map.
*
* @param map The Map to seed the CaseInsensitiveElementList with.
*/
public CaseInsensitiveElementList(Map<String, ? extends V> map) {
this(map, null);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given Map.
*
* @param map The Map to seed the CaseInsensitiveElementList with.
* @param locale The locale to use for case comparison.
*
*/
public CaseInsensitiveElementList(Map<String, ? extends V> map, Locale locale) {
this(locale);
addAll(map);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given Collection.
*
* @param collection The Collection to seed the CaseInsensitiveElementList with.
*/
public CaseInsensitiveElementList(Collection<Element<String, V>> collection) {
this(collection, null);
}
/**
* Constructs a new CaseInsensitiveElementList seeded with the elements in the given Collection.
*
* @param collection The Collection to seed the CaseInsensitiveElementList with.
* @param locale The locale to use for case comparison.
*/
public CaseInsensitiveElementList(Collection<Element<String, V>> collection, Locale locale) {
this(locale);
addAll(collection);
}
/**
* Sets the locale to use for case comparison.
*
* @param locale The locale to use for case comparison.
*/
protected void setLocale(Locale locale) {
this.locale = LocaleHelper.normalize(locale);
}
/**
* 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.
*/
@Override
protected Element<String, V> normalize(Element<String, V> element) {
return new CaseInsensitiveElement<V>(element, locale);
}
/**
* 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.
*/
@Override
protected IData normalize(IData document) {
return new CaseInsensitiveElementList<Object>(document);
}
/**
* Returns a newly created IData object.
*
* @return A newly created IData object.
*/
public static IData create() {
return new CaseInsensitiveElementList<Object>();
}
/**
* 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 CaseInsensitiveElementListIDataCursor();
}
/**
* Recursively builds a clone of the given IData document as a new CaseInsensitiveElementList.
*
* @param document The document to clone.
* @param locale The locale used for case comparison.
* @return A new CaseInsensitiveElementList which is a recursive clone of the given IData document.
*/
public static IData of(IData document, Locale locale) {
return new CaseInsensitiveElementList<Object>(document, locale);
}
/**
* Recursively builds a clone of the given IData[] document list as a new CaseInsensitiveElementList[].
*
* @param documents The documents to clone.
* @param locale The locale used for case comparison.
* @return A new CaseInsensitiveElementList[] which is a recursive clone of the given IData[] documents.
*/
public static IData[] of(IData[] documents, Locale locale) {
IData[] output = new IData[documents == null ? 0 : documents.length];
if (documents != null) {
for (int i = 0; i < documents.length; i++) {
output[i] = of(documents[i], locale);
}
}
return output;
}
/**
* IDataCursor implementation for an ElementList object.
*/
protected class CaseInsensitiveElementListIDataCursor implements IDataCursor {
/**
* This cursor's position in the list.
*/
protected int position;
/**
* This cursor's list iterator.
*/
protected ListIterator<Element<String, V>> iterator;
/**
* The element at the cursor's current position.
*/
protected Element<String, V> element = null;
/**
* Constructs a new cursor.
*/
public CaseInsensitiveElementListIDataCursor() {
initialize();
}
/**
* Constructs a new cursor initialized to the given position.
*
* @param position The initial position of the cursor.
*/
public CaseInsensitiveElementListIDataCursor(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();
}
/**
* Sets the key at the cursor's current position.
*
* @param key The key to be set.
*/
public void setKey(String key) {
if (element != null) {
element.setKey(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 CaseInsensitiveElement<V>(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 CaseInsensitiveElement<V>(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 CaseInsensitiveElementList<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 CaseInsensitiveElementList<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.
*/
public boolean next(String key) {
while(next()) {
if (element.keyEquals(new CaseInsensitiveElement<V>(key, null))) 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.
*/
public boolean previous(String key) {
while(previous()) {
if (element.equals(new CaseInsensitiveElement<V>(key, null))) 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 CaseInsensitiveElementListIDataCursor(this.position);
}
}
}