/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You 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 com.esri.gpt.framework.collection; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import com.esri.gpt.framework.util.Val; /** * Maintains an ordered set of strings. * <p> * Null values are treated as zero-length strings (empty). * <br/>Empty values are only added if flagged as allowed. * <br/>Values will be trimmed if flagged. */ public class StringSet extends LinkedHashSet<String> { // class variables ============================================================= // instance variables ========================================================== private boolean _allowEmptyValues = false; private boolean _isCaseSensitive = false; private HashMap<String,String> _mapKeys = new HashMap<String,String>(); private boolean _trimValues = true; // constructors ================================================================ /** * Default constructor. * <br/>Empty values will not be allowed. * <br/>The list will not be case sensitive. * <br/>Values will be trimmed. */ public StringSet() { this(false,false,true); } /** * Constructs with an empty values allowed flag, a case sensitivity flag * and a trimming preference. * @param allowEmptyValues true if empty values are allowed * @param isCaseSensitive true if the set is case sensitive * @param trimValues true if values will be trimmed */ public StringSet(boolean allowEmptyValues, boolean isCaseSensitive, boolean trimValues) { setAllowEmptyValues(allowEmptyValues); setIsCaseSensitive(isCaseSensitive); setTrimValues(trimValues); } // properties ================================================================== /** * Gets the status indicating whether empty strings are allowed. * @return true if empty strings are allowed */ protected boolean getAllowEmptyValues() { return _allowEmptyValues; } /** * Sets the status indicating whether empty strings are allowed. * @param allowEmptyValues true if empty strings are allowed */ private void setAllowEmptyValues(boolean allowEmptyValues) { _allowEmptyValues = allowEmptyValues; } /** * Gets the case sensitive status for a unique list. * <br/>This value is only applicable to a unique list. * @return true if the unique list is case sensitive */ protected boolean getIsCaseSensitive() { return _isCaseSensitive; } /** * Sets the case sensitive status for a unique list. * <br/>This value is only applicable to a unique list. * @param isCaseSensitive true if the unique list is case sensitive */ private void setIsCaseSensitive(boolean isCaseSensitive) { _isCaseSensitive = isCaseSensitive; } /** * Gets the flag indicating whether values will be trimmed. * @return true if values will be trimmed */ protected boolean getTrimValues() { return _trimValues; } /** * Sets the flag indicating whether values will be trimmed. * @param trimValues true if values will be trimmed */ private void setTrimValues(boolean trimValues) { _trimValues = trimValues; } // methods ===================================================================== /** * Adds a value to the collection. * <br/>Null values are treated as zero-length strings (empty). * <br/>Empty values are only added if flagged as allowed. * <br/>Values will be trimmed if required. * @param value the value to add * @return true if the value was added */ @Override public boolean add(String value) { // check the value boolean bAdded = false; value = checkValue(value); if (getAllowEmptyValues() || (value.length() > 0)) { // if the list is unique, ensure that the string is not already // contained within the map, otherwise add the entry if (!getIsCaseSensitive()) { String sKey = checkKey(value); if (!_mapKeys.containsKey(sKey)) { _mapKeys.put(sKey,value); bAdded = super.add(value); } } else { bAdded = super.add(value); } } return bAdded; } /** * Adds a delimited set of tags to the collection. * <br/>The tag is tokenized with the following 3 delimiters: * <br/> semi-colon comma space * <br/>Tokens are trimmed. * <br/>Null or zero length tokens are ignored. * <br/>Example: addDelimited("a b,c;d e") results in 5 tags a b c d e * @param demilitedTags the tag to add */ public void addDelimited(String demilitedTags) { String[] aSemi = Val.tokenize(demilitedTags,";"); for (int iSemi=0;iSemi<aSemi.length;iSemi++) { String[] aComma = Val.tokenize(aSemi[iSemi],","); for (int iComma=0;iComma<aComma.length;iComma++) { String[] aSpace = Val.tokenize(aComma[iComma]," "); for (int iSpace=0;iSpace<aSpace.length;iSpace++) { add(aSpace[iSpace]); } } } } /** * Checks the value of a key. * <br/>If the collection is not case sensitive, keys are used to * lower case entries. * <br/>The key is trimmed if required (nulls are returned as * zero-length strings). If the map is not case sensitive, the * key is returned in lower case. * @param key the key to check * @return the checked key */ private String checkKey(String key) { if (key == null) { key = ""; } else if (getTrimValues()) { key = key.trim(); } if (getIsCaseSensitive()) { return key; } else { return key.toLowerCase(); } } /** * Checks a value. * The value is trimmed if required (nulls are returned as * zero-length strings). * @param value the value to check * @return the checked value */ private String checkValue(String value) { if (value == null) { return ""; } else if (getTrimValues()) { return value.trim(); } else { return value; } } /** * Clears the collection. */ @Override public void clear() { _mapKeys.clear(); super.clear(); } /** * Determine if the collection contains a value. * @param value the value to check * @return true if the value is contained within the collection */ @Override public boolean contains(Object value) { if (value == null) { return containsString(""); } else if (value instanceof String) { return containsString((String)value); } else { return false; } } /** * Determine if the collection contains a value. * @param value the value to check * @return true if the value is contained within the collection */ public boolean containsString(String value) { value = checkValue(value); if (!getIsCaseSensitive()) { return _mapKeys.containsKey(checkKey(value)); } else { return super.contains(value); } } /** * Removes a value from the collection. * @param value the value to remove * @return <code>true</code> if the value was removed */ @Override public boolean remove(Object value) { if (value == null) { return remove(""); } else if (value instanceof String) { return remove((String)value); } else { return false; } } /** * Removes a value from the collection. * @param value the value to remove * @return <code>true</code> if the value was removed */ public boolean remove(String value) { if (!getIsCaseSensitive()) { String sCheckedKey = checkKey(value); if (_mapKeys.containsKey(sCheckedKey)) { String sRemove = _mapKeys.remove(sCheckedKey); return super.remove(sRemove); } else { return false; } } else { return super.remove(checkValue(value)); } } /** * Retains only those values that are included in the supplied collection. * @param c the collection defining the values to retain * @return <code>true</code> if the collection was modified */ @Override public boolean retainAll(Collection<?> c) { int nSize = size(); StringSet ss = new StringSet(); for (Object o: c) { if (o instanceof String) { ss.add((String)o); } } clear(); addAll(ss); return (nSize != size()); } }