001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.util; 019 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.Map; 024import java.util.Set; 025 026/** 027 * An implementation of a set whose values are case-insensitive strings. All operations match 028 * values in a case-insensitive manner. The original cases of values are retained, so the 029 * {@link #iterator() iterator()} method for example returns the originally values. 030 * <p> 031 * <strong>Note:</strong> The behavior of this class is undefined when wrapping a set that 032 * has keys that would result in duplicate case-insensitive values. 033 */ 034public class CaseInsensitiveSet extends SetDecorator<String> { 035 036 /** Maps lowercase elements to the real string elements. */ 037 private final Map<String, String> lc; 038 039 /** 040 * Constructs a new empty case-insensitive set. The backing set is a new {@link HashSet} 041 * with default initial capacity and load factor. 042 */ 043 public CaseInsensitiveSet() { 044 super(new HashSet<String>()); 045 lc = new HashMap<String, String>(); 046 } 047 048 /** 049 * Constructs a new case-insensitive set containing the elements in the specified 050 * collection. The {@code HashSet} is created with default load factor and an initial 051 * capacity sufficient to contain the elements in the specified collection. 052 * 053 * @param c the collection whose elements are to be placed into this set. 054 * @throws NullPointerException if the specified collection is {@code null}. 055 */ 056 public CaseInsensitiveSet(Collection<String> c) { 057 super(c instanceof Set ? (Set<String>) c : new HashSet<String>(c)); 058 lc = new HashMap<String, String>(c.size()); 059 for (String e : c) { 060 lc.put(e.toLowerCase(), e); 061 } 062 } 063 064 /** 065 * Constructs a new, empty case-insensitive set; the backing {@code HashSet} instance has 066 * the specified initial capacity and the specified load factor. 067 * 068 * @param initialCapacity the initial capacity of the hash set. 069 * @param loadFactor the load factor of the hash set. 070 * @throws IllegalArgumentException if the initial capacity is less than zero, or if the load factor is nonpositive. 071 */ 072 public CaseInsensitiveSet(int initialCapacity, float loadFactor) { 073 super(new HashSet<String>(initialCapacity, loadFactor)); 074 lc = new HashMap<String, String>(initialCapacity, loadFactor); 075 } 076 077 /** 078 * Constructs a new, empty case-insensitive set; the backing {@code HashSet} instance has 079 * the specified initial capacity and default load factor. 080 * 081 * @param initialCapacity the initial capacity of the hash set. 082 * @throws IllegalArgumentException if the initial capacity is less than zero. 083 */ 084 public CaseInsensitiveSet(int initialCapacity) { 085 super(new HashSet<String>(initialCapacity)); 086 lc = new HashMap<String, String>(initialCapacity); 087 } 088 089 private Object translate(Object element) { 090 if (element instanceof String) { 091 String e = lc.get(((String) element).toLowerCase()); 092 if (e != null) { 093 // found a mapped-equivalent 094 element = e; 095 } 096 } 097 return element; 098 } 099 100 @Override 101 public boolean contains(Object o) { 102 return super.contains(translate(o)); 103 } 104 105 @Override 106 public boolean add(String e) { 107 if (contains(e)) { 108 return false; 109 } 110 lc.put(e.toLowerCase(), e); 111 return super.add(e); 112 } 113 114 @Override 115 public boolean remove(Object o) { 116 boolean removed = super.remove(translate(o)); 117 if (o instanceof String) { 118 lc.remove(((String) o).toLowerCase()); 119 } 120 return removed; 121 } 122 123 @Override 124 public boolean containsAll(Collection<?> c) { 125 for (Object o : c) { 126 if (!contains(o)) { 127 return false; 128 } 129 } 130 return true; 131 } 132 133 @Override 134 public boolean addAll(Collection<? extends String> c) { 135 boolean changed = false; 136 for (String e : c) { 137 if (add(e)) { 138 changed = true; 139 } 140 } 141 return changed; 142 } 143 144 @Override 145 public boolean retainAll(Collection<?> c) { 146 boolean changed = false; 147 for (String e : this) { 148 if (!c.contains(e)) { 149 remove(e); 150 changed = true; 151 } 152 } 153 return changed; 154 } 155 156 @Override 157 public boolean removeAll(Collection<?> c) { 158 boolean changed = false; 159 for (String e : this) { 160 if (c.contains(e)) { 161 remove(e); 162 changed = true; 163 } 164 } 165 return changed; 166 } 167 168 @Override 169 public void clear() { 170 lc.clear(); 171 super.clear(); 172 } 173}