Coverage Report - com.jcabi.immutable.ArrayMap
 
Classes in this File Line Coverage Branch Coverage Complexity
ArrayMap
38%
33/85
26%
11/42
2.172
ArrayMap$1
N/A
N/A
2.172
ArrayMap$Cmp
100%
5/5
100%
2/2
2.172
ArrayMap$ImmutableEntry
100%
6/6
N/A
2.172
 
 1  
 /**
 2  
  * Copyright (c) 2012-2014, jcabi.com
 3  
  * All rights reserved.
 4  
  *
 5  
  * Redistribution and use in source and binary forms, with or without
 6  
  * modification, are permitted provided that the following conditions
 7  
  * are met: 1) Redistributions of source code must retain the above
 8  
  * copyright notice, this list of conditions and the following
 9  
  * disclaimer. 2) Redistributions in binary form must reproduce the above
 10  
  * copyright notice, this list of conditions and the following
 11  
  * disclaimer in the documentation and/or other materials provided
 12  
  * with the distribution. 3) Neither the name of the jcabi.com nor
 13  
  * the names of its contributors may be used to endorse or promote
 14  
  * products derived from this software without specific prior written
 15  
  * permission.
 16  
  *
 17  
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18  
  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 19  
  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 20  
  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 21  
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 22  
  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23  
  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 24  
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 25  
  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 26  
  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 28  
  * OF THE POSSIBILITY OF SUCH DAMAGE.
 29  
  */
 30  
 package com.jcabi.immutable;
 31  
 
 32  
 import com.jcabi.aspects.Immutable;
 33  
 import com.jcabi.aspects.Loggable;
 34  
 import java.io.Serializable;
 35  
 import java.util.AbstractMap;
 36  
 import java.util.ArrayList;
 37  
 import java.util.Arrays;
 38  
 import java.util.Collection;
 39  
 import java.util.Collections;
 40  
 import java.util.Comparator;
 41  
 import java.util.LinkedHashSet;
 42  
 import java.util.Map;
 43  
 import java.util.Set;
 44  
 import java.util.TreeSet;
 45  
 import java.util.concurrent.ConcurrentHashMap;
 46  
 import java.util.concurrent.ConcurrentMap;
 47  
 
 48  
 /**
 49  
  * Map on top of array.
 50  
  *
 51  
  * <p>This class is truly immutable. This means that it never changes
 52  
  * its encapsulated values and is annotated with {@code &#64;Immutable}
 53  
  * annotation.
 54  
  *
 55  
  * @param <K> Map key type
 56  
  * @param <V> Value key type
 57  
  * @author Yegor Bugayenko (yegor@tpc2.com)
 58  
  * @version $Id$
 59  
  * @since 0.1
 60  
  */
 61  
 @Immutable
 62  
 @Loggable(Loggable.DEBUG)
 63  
 @SuppressWarnings({ "rawtypes", "unchecked", "PMD.TooManyMethods" })
 64  
 public final class ArrayMap<K, V> implements ConcurrentMap<K, V> {
 65  
 
 66  
     /**
 67  
      * All entries.
 68  
      */
 69  
     @Immutable.Array
 70  
     private final transient ImmutableEntry<K, V>[] entries;
 71  
 
 72  
     /**
 73  
      * Public ctor.
 74  
      */
 75  9
     public ArrayMap() {
 76  9
         this.entries = new ArrayMap.ImmutableEntry[0];
 77  9
     }
 78  
 
 79  
     /**
 80  
      * Public ctor.
 81  
      * @param map The original map
 82  
      */
 83  
     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
 84  25
     public ArrayMap(final Map<K, V> map) {
 85  25
         if (map == null) {
 86  0
             throw new IllegalArgumentException(
 87  
                 "argument of ArrayMap ctor can't be NULL"
 88  
             );
 89  
         }
 90  25
         final Set<ArrayMap.ImmutableEntry<K, V>> entrs =
 91  
             new TreeSet<ArrayMap.ImmutableEntry<K, V>>(
 92  
                 new ArrayMap.Cmp<K, V>()
 93  
             );
 94  25
         for (final Map.Entry<K, V> entry : map.entrySet()) {
 95  45
             entrs.add(new ArrayMap.ImmutableEntry<K, V>(entry));
 96  45
         }
 97  25
         this.entries = entrs.toArray(new ArrayMap.ImmutableEntry[entrs.size()]);
 98  25
     }
 99  
 
 100  
     /**
 101  
      * Make a new one with an extra entry.
 102  
      * @param key The key
 103  
      * @param value The value
 104  
      * @return New map
 105  
      */
 106  
     public ArrayMap<K, V> with(final K key, final V value) {
 107  22
         if (key == null) {
 108  0
             throw new IllegalArgumentException(
 109  
                 "first argument of ArrayMap#with() can't be NULL"
 110  
             );
 111  
         }
 112  22
         if (value == null) {
 113  0
             throw new IllegalArgumentException(
 114  
                 "second argument of ArrayMap#with() can't be NULL"
 115  
             );
 116  
         }
 117  22
         final ConcurrentMap<K, V> map =
 118  
             new ConcurrentHashMap<K, V>(this.entries.length);
 119  22
         map.putAll(this);
 120  22
         map.put(key, value);
 121  22
         return new ArrayMap<K, V>(map);
 122  
     }
 123  
 
 124  
     /**
 125  
      * Make a new one with these extra entries.
 126  
      * @param ents Entries
 127  
      * @return New map
 128  
      * @since 0.11
 129  
      */
 130  
     public ArrayMap<K, V> with(final Map<K, V> ents) {
 131  1
         if (ents == null) {
 132  0
             throw new IllegalArgumentException(
 133  
                 "arguments of ArrayMap#with() can't be NULL"
 134  
             );
 135  
         }
 136  1
         final ConcurrentMap<K, V> map =
 137  
             new ConcurrentHashMap<K, V>(this.entries.length);
 138  1
         map.putAll(this);
 139  1
         map.putAll(ents);
 140  1
         return new ArrayMap<K, V>(map);
 141  
     }
 142  
 
 143  
     /**
 144  
      * Make a new one without this key.
 145  
      * @param key The key
 146  
      * @return New map
 147  
      */
 148  
     public ArrayMap<K, V> without(final K key) {
 149  1
         if (key == null) {
 150  0
             throw new IllegalArgumentException(
 151  
                 "argument of ArrayMap#without() can't be NULL"
 152  
             );
 153  
         }
 154  1
         final ConcurrentMap<K, V> map =
 155  
             new ConcurrentHashMap<K, V>(this.entries.length);
 156  1
         map.putAll(this);
 157  1
         map.remove(key);
 158  1
         return new ArrayMap<K, V>(map);
 159  
     }
 160  
 
 161  
     /**
 162  
      * Make a new one without these keys.
 163  
      * @param keys The keys to remove
 164  
      * @return New map
 165  
      * @since 0.11
 166  
      */
 167  
     public ArrayMap<K, V> without(final Collection<K> keys) {
 168  0
         if (keys == null) {
 169  0
             throw new IllegalArgumentException(
 170  
                 "arguments of ArrayMap#without() can't be NULL"
 171  
             );
 172  
         }
 173  0
         final ConcurrentMap<K, V> map =
 174  
             new ConcurrentHashMap<K, V>(this.entries.length);
 175  0
         map.putAll(this);
 176  0
         for (final K key : keys) {
 177  0
             map.remove(key);
 178  0
         }
 179  0
         return new ArrayMap<K, V>(map);
 180  
     }
 181  
 
 182  
     @Override
 183  
     public int hashCode() {
 184  0
         return Arrays.hashCode(this.entries);
 185  
     }
 186  
 
 187  
     @Override
 188  
     public boolean equals(final Object object) {
 189  2
         return object instanceof ArrayMap
 190  
             && Arrays.deepEquals(
 191  
                 this.entries, ArrayMap.class.cast(object).entries
 192  
             );
 193  
     }
 194  
 
 195  
     @Override
 196  
     public String toString() {
 197  0
         final StringBuilder text = new StringBuilder(0);
 198  0
         for (final Map.Entry<K, V> item : this.entries) {
 199  0
             if (text.length() > 0) {
 200  0
                 text.append(", ");
 201  
             }
 202  0
             text.append(item);
 203  
         }
 204  0
         return text.toString();
 205  
     }
 206  
 
 207  
     @Override
 208  
     public int size() {
 209  0
         return this.entries.length;
 210  
     }
 211  
 
 212  
     @Override
 213  
     public boolean isEmpty() {
 214  0
         return this.entries.length == 0;
 215  
     }
 216  
 
 217  
     @Override
 218  
     public boolean containsKey(final Object key) {
 219  0
         boolean contains = false;
 220  0
         for (final Map.Entry<K, V> entry : this.entries) {
 221  0
             if (entry.getKey().equals(key)) {
 222  0
                 contains = true;
 223  0
                 break;
 224  
             }
 225  
         }
 226  0
         return contains;
 227  
     }
 228  
 
 229  
     @Override
 230  
     public boolean containsValue(final Object value) {
 231  0
         boolean contains = false;
 232  0
         for (final Map.Entry<K, V> entry : this.entries) {
 233  0
             if (entry.getValue().equals(value)) {
 234  0
                 contains = true;
 235  0
                 break;
 236  
             }
 237  
         }
 238  0
         return contains;
 239  
     }
 240  
 
 241  
     @Override
 242  
     public V get(final Object key) {
 243  0
         V value = null;
 244  0
         for (final Map.Entry<K, V> entry : this.entries) {
 245  0
             if (entry.getKey().equals(key)) {
 246  0
                 value = entry.getValue();
 247  0
                 break;
 248  
             }
 249  
         }
 250  0
         return value;
 251  
     }
 252  
 
 253  
     @Override
 254  
     public V put(final K key, final V value) {
 255  0
         throw new UnsupportedOperationException(
 256  
             "put(): ArrayMap is immutable"
 257  
         );
 258  
     }
 259  
 
 260  
     @Override
 261  
     public V remove(final Object key) {
 262  0
         throw new UnsupportedOperationException(
 263  
             "remove(): ArrayMap is immutable"
 264  
         );
 265  
     }
 266  
 
 267  
     @Override
 268  
     public void putAll(final Map<? extends K, ? extends V> map) {
 269  0
         throw new UnsupportedOperationException(
 270  
             "putAll(): ArrayMap is immutable"
 271  
         );
 272  
     }
 273  
 
 274  
     @Override
 275  
     public void clear() {
 276  0
         throw new UnsupportedOperationException(
 277  
             "clear(): ArrayMap is immutable"
 278  
         );
 279  
     }
 280  
 
 281  
     @Override
 282  
     public V putIfAbsent(final K key, final V value) {
 283  0
         throw new UnsupportedOperationException(
 284  
             "putIfAbsent(): ArrayMap is immutable"
 285  
         );
 286  
     }
 287  
 
 288  
     @Override
 289  
     public boolean remove(final Object key, final Object value) {
 290  0
         throw new UnsupportedOperationException(
 291  
             "remove(): ArrayMap is immutable, can't change"
 292  
         );
 293  
     }
 294  
 
 295  
     @Override
 296  
     public boolean replace(final K key, final V old, final V value) {
 297  0
         throw new UnsupportedOperationException(
 298  
             "replace(): ArrayMap is immutable"
 299  
         );
 300  
     }
 301  
 
 302  
     @Override
 303  
     public V replace(final K key, final V value) {
 304  0
         throw new UnsupportedOperationException(
 305  
             "replace(): ArrayMap is immutable, can't replace"
 306  
         );
 307  
     }
 308  
 
 309  
     @Override
 310  
     public Set<K> keySet() {
 311  1
         final Set<K> keys = new LinkedHashSet<K>(this.entries.length);
 312  5
         for (final Map.Entry<K, V> entry : this.entries) {
 313  4
             keys.add(entry.getKey());
 314  
         }
 315  1
         return Collections.unmodifiableSet(keys);
 316  
     }
 317  
 
 318  
     @Override
 319  
     public Collection<V> values() {
 320  0
         final Collection<V> values = new ArrayList<V>(this.entries.length);
 321  0
         for (final Map.Entry<K, V> entry : this.entries) {
 322  0
             values.add(entry.getValue());
 323  
         }
 324  0
         return Collections.unmodifiableCollection(values);
 325  
     }
 326  
 
 327  
     @Override
 328  
     public Set<Map.Entry<K, V>> entrySet() {
 329  32
         return Collections.unmodifiableSet(
 330  
             new LinkedHashSet<Map.Entry<K, V>>(Arrays.asList(this.entries))
 331  
         );
 332  
     }
 333  
 
 334  
     /**
 335  
      * Comparator.
 336  
      */
 337  99
     private static final class Cmp<K, V> implements
 338  
         Comparator<ArrayMap.ImmutableEntry<K, V>>, Serializable {
 339  
         /**
 340  
          * The Serial version UID.
 341  
          */
 342  
         private static final long serialVersionUID = 4064118000237204080L;
 343  
         @Override
 344  
         public int compare(final ImmutableEntry<K, V> left,
 345  
             final ImmutableEntry<K, V> right) {
 346  
             int compare;
 347  49
             if (left.getKey() instanceof Comparable) {
 348  43
                 compare = Comparable.class.cast(left.getKey())
 349  
                     .compareTo(right.getKey());
 350  
             } else {
 351  6
                 compare = left.getKey().toString()
 352  
                     .compareTo(right.getKey().toString());
 353  
             }
 354  49
             return compare;
 355  
         }
 356  
     }
 357  
 
 358  
     /**
 359  
      * Immutable map entry.
 360  
      */
 361  
     @Immutable
 362  45
     private static final class ImmutableEntry<K, V> extends
 363  
         AbstractMap.SimpleImmutableEntry<K, V> {
 364  
         /**
 365  
          * Serialization marker.
 366  
          */
 367  
         private static final long serialVersionUID = 1L;
 368  
         /**
 369  
          * Public ctor.
 370  
          * @param entry Entry to encapsulate
 371  
          */
 372  
         private ImmutableEntry(final Map.Entry<K, V> entry) {
 373  45
             this(entry.getKey(), entry.getValue());
 374  45
         }
 375  
         /**
 376  
          * Public ctor.
 377  
          * @param key The key
 378  
          * @param value The value
 379  
          */
 380  
         private ImmutableEntry(final K key, final V value) {
 381  45
             super(key, value);
 382  45
         }
 383  
         @Override
 384  
         public String toString() {
 385  4
             return String.format("%s=%s", this.getKey(), this.getValue());
 386  
         }
 387  
     }
 388  
 
 389  
 }