/*
* 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.IData;
import com.wm.data.IDataCursor;
import com.wm.util.Table;
import permafrost.tundra.lang.LocaleHelper;
import java.io.Serializable;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Wraps an IData in a case-insensitive envelope.
*/
public class CaseInsensitiveIData extends IDataEnvelope implements Serializable {
/**
* The serialization identity of this class version.
*/
private static final long serialVersionUID = 1;
/**
* Map of case-insensitive keys to preserved-case keys in the wrapped IData document.
*/
protected Map<String, String> keys = new ConcurrentHashMap<String, String>();
/**
* The locale used for case comparison.
*/
protected Locale locale;
/**
* Wraps an IData document in a case-insensitive envelope.
*/
public CaseInsensitiveIData() {
this(null, null);
}
/**
* Wraps an IData document in a case-insensitive envelope.
*
* @param locale The locale used for case comparison.
*/
public CaseInsensitiveIData(Locale locale) {
this(null, locale);
}
/**
* Wraps an IData document in an case-insensitive envelope.
*
* @param document The document to be wrapped.
*/
public CaseInsensitiveIData(IData document) {
this(document, null);
}
/**
* Wraps an IData document in an case-insensitive envelope.
*
* @param document The document to be wrapped.
* @param locale The locale used for case comparison.
*/
public CaseInsensitiveIData(IData document, Locale locale) {
super(document);
this.locale = LocaleHelper.normalize(locale);
initialize();
}
/**
* 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 CaseInsensitiveIDataCursor(super.getCursor());
}
/**
* Constructs the map of case-insensitive keys to preserved-case keys held in the wrapped IData document.
*/
protected void initialize() {
IDataCursor cursor = document.getCursor();
while(cursor.next()) {
String key = cursor.getKey();
keys.put(key.toLowerCase(locale), key);
}
}
/**
* Converts the given case-insensitive key to the actual case-preserved key.
*
* @param key A case-insensitive key.
* @return Null if the key doesn't exist, or the case-preserved key used in the wrapped IData document.
*/
protected String normalizeKey(String key) {
if (key == null) return null;
return keys.get(key.toLowerCase(locale));
}
/**
* Adds a key to the key set used for the case-insensitive feature.
*
* @param key The key to be added.
*/
protected void addKey(String key) {
if (key != null) keys.put(key.toLowerCase(locale), key);
}
/**
* Removes a key from the key set used for the case-insensitive feature.
*
* @param key The key to be removed.
*/
protected void removeKey(String key) {
if (key != null) keys.remove(key.toLowerCase(locale));
}
/**
* Replaces an existing key with a new key.
*
* @param oldKey The old key to be replaced by the new key.
* @param newKey The new key replacing the old key.
*/
protected void replaceKey(String oldKey, String newKey) {
removeKey(oldKey);
addKey(newKey);
}
/**
* Returns a new CaseInsensitiveIData wrapping the given IData document.
*
* @param document The document to be wrapped.
* @return A new CaseInsensitiveIData wrapping the given IData document.
*/
public static IData of(IData document) {
return new CaseInsensitiveIData(document);
}
/**
* Returns a new CaseInsensitiveIData[] representation of the given IData[] document list.
*
* @param array An IData[] document list.
* @return A new CaseInsensitiveIData[] representation of the given IData[] document list.
*/
public static IData[] of(IData[] array) {
if (array == null) return null;
IData[] output = new CaseInsensitiveIData[array.length];
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
output[i] = CaseInsensitiveIData.of(array[i]);
}
}
return output;
}
/**
* Static factory method used by IData XML deserialization.
*
* @return A new CaseInsensitiveIData instance.
*/
public static IData create() {
return new CaseInsensitiveIData();
}
/**
* Implementation of the case-insensitive IDataCursor.
*/
private class CaseInsensitiveIDataCursor extends IDataCursorEnvelope {
/**
* Constructs a new case-insensitive cursor.
*
* @param cursor The cursor to be wrapped.
*/
public CaseInsensitiveIDataCursor(IDataCursor cursor) {
super(cursor);
}
/**
* Returns the value at the cursor's current position.
*
* @return The value at the cursor's current position.
*/
@Override
public Object getValue() {
Object value = cursor.getValue();
if (value instanceof IData[] || value instanceof Table) {
value = of(IDataHelper.toIDataArray(value));
} else if (value instanceof IData) {
value = of(IDataHelper.toIData(value));
}
return value;
}
/**
* Sets the key at the cursor's current position.
*
* @param key The key to be set.
*/
public void setKey(String key) {
replaceKey(getKey(), key);
super.setKey(key);
}
/**
* Deletes the element at the cursor's current position.
*
* @return True if the element was deleted.
*/
public boolean delete() {
removeKey(getKey());
return super.delete();
}
/**
* Inserts the given element before the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
*/
public void insertBefore(String key, Object value) {
addKey(key);
super.insertBefore(key, value);
}
/**
* Inserts the given element after the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
*/
public void insertAfter(String key, Object value) {
addKey(key);
super.insertAfter(key, value);
}
/**
* Inserts a new IData document with the given key before the cursor's current position.
*
* @param key The key to be inserted.
* @return The newly inserted IData document.
*/
public IData insertDataBefore(String key) {
addKey(key);
return super.insertDataBefore(key);
}
/**
* Inserts a new IData document with the given key after the cursor's current position.
*
* @param key The key to be inserted.
* @return The newly inserted IData document.
*/
public IData insertDataAfter(String key) {
addKey(key);
return super.insertDataAfter(key);
}
/**
* Moves the cursor's position to the next element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
public boolean next(String key) {
return super.next(normalizeKey(key));
}
/**
* Moves the cursor's position to the previous element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
public boolean previous(String key) {
return super.previous(normalizeKey(key));
}
/**
* Moves the cursor's position to the first element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
public boolean first(String key) {
return super.first(normalizeKey(key));
}
/**
* Moves the cursor's position to the last element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
public boolean last(String key) {
return super.last(normalizeKey(key));
}
/**
* Returns a clone of this cursor.
*
* @return A clone of this cursor.
*/
@Override
public IDataCursor getCursorClone() {
return new CaseInsensitiveIDataCursor(cursor.getCursorClone());
}
}
}