alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (MapConstraints.java)

This example Java source code file (MapConstraints.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

annotation, bimap, collection, constrainedmultimap, entry, iterable, list, mapconstraint, object, override, set, sortedset, util

The MapConstraints.java Java example source code

/*
 * Copyright (C) 2007 The Guava Authors
 *
 * Licensed 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.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2objc.annotations.WeakOuter;

import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;

import javax.annotation.Nullable;

/**
 * Factory and utilities pertaining to the {@code MapConstraint} interface.
 *
 * @see Constraints
 * @author Mike Bostock
 * @since 3.0
 * @deprecated Use {@link Preconditions} for basic checks. In place of
 *     constrained maps, we encourage you to check your preconditions
 *     explicitly instead of leaving that work to the map implementation.
 *     For the specific case of rejecting null, consider {@link ImmutableMap}.
 *     This class is scheduled for removal in Guava 20.0.
 */
@Beta
@GwtCompatible
@Deprecated
public final class MapConstraints {
  private MapConstraints() {}

  /**
   * Returns a constraint that verifies that neither the key nor the value is
   * null. If either is null, a {@link NullPointerException} is thrown.
   */
  public static MapConstraint<Object, Object> notNull() {
    return NotNullMapConstraint.INSTANCE;
  }

  // enum singleton pattern
  private enum NotNullMapConstraint implements MapConstraint<Object, Object> {
    INSTANCE;

    @Override
    public void checkKeyValue(Object key, Object value) {
      checkNotNull(key);
      checkNotNull(value);
    }

    @Override
    public String toString() {
      return "Not null";
    }
  }

  /**
   * Returns a constrained view of the specified map, using the specified
   * constraint. Any operations that add new mappings will call the provided
   * constraint. However, this method does not verify that existing mappings
   * satisfy the constraint.
   *
   * <p>The returned map is not serializable.
   *
   * @param map the map to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the specified map
   */
  public static <K, V> Map constrainedMap(
      Map<K, V> map, MapConstraint constraint) {
    return new ConstrainedMap<K, V>(map, constraint);
  }

  /**
   * Returns a constrained view of the specified multimap, using the specified
   * constraint. Any operations that add new mappings will call the provided
   * constraint. However, this method does not verify that existing mappings
   * satisfy the constraint.
   *
   * <p>Note that the generated multimap's {@link Multimap#removeAll} and
   * {@link Multimap#replaceValues} methods return collections that are not
   * constrained.
   *
   * <p>The returned multimap is not serializable.
   *
   * @param multimap the multimap to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the multimap
   */
  public static <K, V> Multimap constrainedMultimap(
      Multimap<K, V> multimap, MapConstraint constraint) {
    return new ConstrainedMultimap<K, V>(multimap, constraint);
  }

  /**
   * Returns a constrained view of the specified list multimap, using the
   * specified constraint. Any operations that add new mappings will call the
   * provided constraint. However, this method does not verify that existing
   * mappings satisfy the constraint.
   *
   * <p>Note that the generated multimap's {@link Multimap#removeAll} and
   * {@link Multimap#replaceValues} methods return collections that are not
   * constrained.
   *
   * <p>The returned multimap is not serializable.
   *
   * @param multimap the multimap to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the specified multimap
   */
  public static <K, V> ListMultimap constrainedListMultimap(
      ListMultimap<K, V> multimap, MapConstraint constraint) {
    return new ConstrainedListMultimap<K, V>(multimap, constraint);
  }

  /**
   * Returns a constrained view of the specified set multimap, using the
   * specified constraint. Any operations that add new mappings will call the
   * provided constraint. However, this method does not verify that existing
   * mappings satisfy the constraint.
   *
   * <p>Note that the generated multimap's {@link Multimap#removeAll} and
   * {@link Multimap#replaceValues} methods return collections that are not
   * constrained.
   * <p>The returned multimap is not serializable.
   *
   * @param multimap the multimap to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the specified multimap
   */
  public static <K, V> SetMultimap constrainedSetMultimap(
      SetMultimap<K, V> multimap, MapConstraint constraint) {
    return new ConstrainedSetMultimap<K, V>(multimap, constraint);
  }

  /**
   * Returns a constrained view of the specified sorted-set multimap, using the
   * specified constraint. Any operations that add new mappings will call the
   * provided constraint. However, this method does not verify that existing
   * mappings satisfy the constraint.
   *
   * <p>Note that the generated multimap's {@link Multimap#removeAll} and
   * {@link Multimap#replaceValues} methods return collections that are not
   * constrained.
   * <p>The returned multimap is not serializable.
   *
   * @param multimap the multimap to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the specified multimap
   */
  public static <K, V> SortedSetMultimap constrainedSortedSetMultimap(
      SortedSetMultimap<K, V> multimap, MapConstraint constraint) {
    return new ConstrainedSortedSetMultimap<K, V>(multimap, constraint);
  }

  /**
   * Returns a constrained view of the specified entry, using the specified
   * constraint. The {@link Entry#setValue} operation will be verified with the
   * constraint.
   *
   * @param entry the entry to constrain
   * @param constraint the constraint for the entry
   * @return a constrained view of the specified entry
   */
  private static <K, V> Entry constrainedEntry(
      final Entry<K, V> entry, final MapConstraint constraint) {
    checkNotNull(entry);
    checkNotNull(constraint);
    return new ForwardingMapEntry<K, V>() {
      @Override
      protected Entry<K, V> delegate() {
        return entry;
      }

      @Override
      public V setValue(V value) {
        constraint.checkKeyValue(getKey(), value);
        return entry.setValue(value);
      }
    };
  }

  /**
   * Returns a constrained view of the specified {@code asMap} entry, using the
   * specified constraint. The {@link Entry#setValue} operation will be verified
   * with the constraint, and the collection returned by {@link Entry#getValue}
   * will be similarly constrained.
   *
   * @param entry the {@code asMap} entry to constrain
   * @param constraint the constraint for the entry
   * @return a constrained view of the specified entry
   */
  private static <K, V> Entry> constrainedAsMapEntry(
      final Entry<K, Collection entry, final MapConstraint constraint) {
    checkNotNull(entry);
    checkNotNull(constraint);
    return new ForwardingMapEntry<K, Collection() {
      @Override
      protected Entry<K, Collection delegate() {
        return entry;
      }

      @Override
      public Collection<V> getValue() {
        return Constraints.constrainedTypePreservingCollection(
            entry.getValue(),
            new Constraint<V>() {
              @Override
              public V checkElement(V value) {
                constraint.checkKeyValue(getKey(), value);
                return value;
              }
            });
      }
    };
  }

  /**
   * Returns a constrained view of the specified set of {@code asMap} entries,
   * using the specified constraint. The {@link Entry#setValue} operation will
   * be verified with the constraint, and the collection returned by {@link
   * Entry#getValue} will be similarly constrained. The {@code add} and {@code
   * addAll} operations simply forward to the underlying set, which throws an
   * {@link UnsupportedOperationException} per the multimap specification.
   *
   * @param entries the entries to constrain
   * @param constraint the constraint for the entries
   * @return a constrained view of the entries
   */
  private static <K, V> Set>> constrainedAsMapEntries(
      Set<Entry> entries, MapConstraint constraint) {
    return new ConstrainedAsMapEntries<K, V>(entries, constraint);
  }

  /**
   * Returns a constrained view of the specified collection (or set) of entries,
   * using the specified constraint. The {@link Entry#setValue} operation will
   * be verified with the constraint, along with add operations on the returned
   * collection. The {@code add} and {@code addAll} operations simply forward to
   * the underlying collection, which throws an {@link
   * UnsupportedOperationException} per the map and multimap specification.
   *
   * @param entries the entries to constrain
   * @param constraint the constraint for the entries
   * @return a constrained view of the specified entries
   */
  private static <K, V> Collection> constrainedEntries(
      Collection<Entry entries, MapConstraint constraint) {
    if (entries instanceof Set) {
      return constrainedEntrySet((Set<Entry) entries, constraint);
    }
    return new ConstrainedEntries<K, V>(entries, constraint);
  }

  /**
   * Returns a constrained view of the specified set of entries, using the
   * specified constraint. The {@link Entry#setValue} operation will be verified
   * with the constraint, along with add operations on the returned set. The
   * {@code add} and {@code addAll} operations simply forward to the underlying
   * set, which throws an {@link UnsupportedOperationException} per the map and
   * multimap specification.
   *
   * <p>The returned multimap is not serializable.
   *
   * @param entries the entries to constrain
   * @param constraint the constraint for the entries
   * @return a constrained view of the specified entries
   */
  private static <K, V> Set> constrainedEntrySet(
      Set<Entry entries, MapConstraint constraint) {
    return new ConstrainedEntrySet<K, V>(entries, constraint);
  }

  /** @see MapConstraints#constrainedMap */
  static class ConstrainedMap<K, V> extends ForwardingMap {
    private final Map<K, V> delegate;
    final MapConstraint<? super K, ? super V> constraint;
    private transient Set<Entry entrySet;

    ConstrainedMap(Map<K, V> delegate, MapConstraint constraint) {
      this.delegate = checkNotNull(delegate);
      this.constraint = checkNotNull(constraint);
    }

    @Override
    protected Map<K, V> delegate() {
      return delegate;
    }

    @Override
    public Set<Entry entrySet() {
      Set<Entry result = entrySet;
      if (result == null) {
        entrySet = result = constrainedEntrySet(delegate.entrySet(), constraint);
      }
      return result;
    }

    @CanIgnoreReturnValue // TODO(kak): Remove this?
    @Override
    public V put(K key, V value) {
      constraint.checkKeyValue(key, value);
      return delegate.put(key, value);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
      delegate.putAll(checkMap(map, constraint));
    }
  }

  /**
   * Returns a constrained view of the specified bimap, using the specified
   * constraint. Any operations that modify the bimap will have the associated
   * keys and values verified with the constraint.
   *
   * <p>The returned bimap is not serializable.
   *
   * @param map the bimap to constrain
   * @param constraint the constraint that validates added entries
   * @return a constrained view of the specified bimap
   */
  public static <K, V> BiMap constrainedBiMap(
      BiMap<K, V> map, MapConstraint constraint) {
    return new ConstrainedBiMap<K, V>(map, null, constraint);
  }

  /** @see MapConstraints#constrainedBiMap */
  private static class ConstrainedBiMap<K, V> extends ConstrainedMap implements BiMap {
    /*
     * We could switch to racy single-check lazy init and remove volatile, but
     * there's a downside. That's because this field is also written in the
     * constructor. Without volatile, the constructor's write of the existing
     * inverse BiMap could occur after inverse()'s read of the field's initial
     * null value, leading inverse() to overwrite the existing inverse with a
     * doubly indirect version. This wouldn't be catastrophic, but it's
     * something to keep in mind if we make the change.
     *
     * Note that UnmodifiableBiMap *does* use racy single-check lazy init.
     * TODO(cpovirk): pick one and standardize
     */
    volatile BiMap<V, K> inverse;

    ConstrainedBiMap(
        BiMap<K, V> delegate,
        @Nullable BiMap<V, K> inverse,
        MapConstraint<? super K, ? super V> constraint) {
      super(delegate, constraint);
      this.inverse = inverse;
    }

    @Override
    protected BiMap<K, V> delegate() {
      return (BiMap<K, V>) super.delegate();
    }

    @Override
    public V forcePut(K key, V value) {
      constraint.checkKeyValue(key, value);
      return delegate().forcePut(key, value);
    }

    @Override
    public BiMap<V, K> inverse() {
      if (inverse == null) {
        inverse =
            new ConstrainedBiMap<V, K>(
                delegate().inverse(), this, new InverseConstraint<V, K>(constraint));
      }
      return inverse;
    }

    @Override
    public Set<V> values() {
      return delegate().values();
    }
  }

  /** @see MapConstraints#constrainedBiMap */
  private static class InverseConstraint<K, V> implements MapConstraint {
    final MapConstraint<? super V, ? super K> constraint;

    public InverseConstraint(MapConstraint<? super V, ? super K> constraint) {
      this.constraint = checkNotNull(constraint);
    }

    @Override
    public void checkKeyValue(K key, V value) {
      constraint.checkKeyValue(value, key);
    }
  }

  /** @see MapConstraints#constrainedMultimap */
  private static class ConstrainedMultimap<K, V> extends ForwardingMultimap
      implements Serializable {
    final MapConstraint<? super K, ? super V> constraint;
    final Multimap<K, V> delegate;

    transient Collection<Entry entries;

    transient Map<K, Collection asMap;

    public ConstrainedMultimap(
        Multimap<K, V> delegate, MapConstraint constraint) {
      this.delegate = checkNotNull(delegate);
      this.constraint = checkNotNull(constraint);
    }

    @Override
    protected Multimap<K, V> delegate() {
      return delegate;
    }

    @Override
    public Map<K, Collection asMap() {
      Map<K, Collection result = asMap;
      if (result == null) {
        final Map<K, Collection asMapDelegate = delegate.asMap();

        @WeakOuter
        class AsMap extends ForwardingMap<K, Collection {
          Set<Entry> entrySet;
          Collection<Collection values;

          @Override
          protected Map<K, Collection delegate() {
            return asMapDelegate;
          }

          @Override
          public Set<Entry> entrySet() {
            Set<Entry> result = entrySet;
            if (result == null) {
              entrySet = result = constrainedAsMapEntries(asMapDelegate.entrySet(), constraint);
            }
            return result;
          }

          @SuppressWarnings("unchecked")
          @Override
          public Collection<V> get(Object key) {
            try {
              Collection<V> collection = ConstrainedMultimap.this.get((K) key);
              return collection.isEmpty() ? null : collection;
            } catch (ClassCastException e) {
              return null; // key wasn't a K
            }
          }

          @Override
          public Collection<Collection values() {
            Collection<Collection result = values;
            if (result == null) {
              values = result = new ConstrainedAsMapValues<K, V>(delegate().values(), entrySet());
            }
            return result;
          }

          @Override
          public boolean containsValue(Object o) {
            return values().contains(o);
          }
        }
        asMap = result = new AsMap();
      }
      return result;
    }

    @Override
    public Collection<Entry entries() {
      Collection<Entry result = entries;
      if (result == null) {
        entries = result = constrainedEntries(delegate.entries(), constraint);
      }
      return result;
    }

    @Override
    public Collection<V> get(final K key) {
      return Constraints.constrainedTypePreservingCollection(
          delegate.get(key),
          new Constraint<V>() {
            @Override
            public V checkElement(V value) {
              constraint.checkKeyValue(key, value);
              return value;
            }
          });
    }

    @Override
    public boolean put(K key, V value) {
      constraint.checkKeyValue(key, value);
      return delegate.put(key, value);
    }

    @Override
    public boolean putAll(K key, Iterable<? extends V> values) {
      return delegate.putAll(key, checkValues(key, values, constraint));
    }

    @Override
    public boolean putAll(Multimap<? extends K, ? extends V> multimap) {
      boolean changed = false;
      for (Entry<? extends K, ? extends V> entry : multimap.entries()) {
        changed |= put(entry.getKey(), entry.getValue());
      }
      return changed;
    }

    @Override
    public Collection<V> replaceValues(K key, Iterable values) {
      return delegate.replaceValues(key, checkValues(key, values, constraint));
    }
  }

  /** @see ConstrainedMultimap#asMap */
  private static class ConstrainedAsMapValues<K, V> extends ForwardingCollection> {
    final Collection<Collection delegate;
    final Set<Entry> entrySet;

    /**
     * @param entrySet map entries, linking each key with its corresponding
     *     values, that already enforce the constraint
     */
    ConstrainedAsMapValues(
        Collection<Collection delegate, Set>> entrySet) {
      this.delegate = delegate;
      this.entrySet = entrySet;
    }

    @Override
    protected Collection<Collection delegate() {
      return delegate;
    }

    @Override
    public Iterator<Collection iterator() {
      final Iterator<Entry> iterator = entrySet.iterator();
      return new Iterator<Collection() {
        @Override
        public boolean hasNext() {
          return iterator.hasNext();
        }

        @Override
        public Collection<V> next() {
          return iterator.next().getValue();
        }

        @Override
        public void remove() {
          iterator.remove();
        }
      };
    }

    @Override
    public Object[] toArray() {
      return standardToArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      return standardToArray(array);
    }

    @Override
    public boolean contains(Object o) {
      return standardContains(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
      return standardContainsAll(c);
    }

    @Override
    public boolean remove(Object o) {
      return standardRemove(o);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
      return standardRemoveAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
      return standardRetainAll(c);
    }
  }

  /** @see MapConstraints#constrainedEntries */
  private static class ConstrainedEntries<K, V> extends ForwardingCollection> {
    final MapConstraint<? super K, ? super V> constraint;
    final Collection<Entry entries;

    ConstrainedEntries(
        Collection<Entry entries, MapConstraint constraint) {
      this.entries = entries;
      this.constraint = constraint;
    }

    @Override
    protected Collection<Entry delegate() {
      return entries;
    }

    @Override
    public Iterator<Entry iterator() {
      return new TransformedIterator<Entry>(entries.iterator()) {
        @Override
        Entry<K, V> transform(Entry from) {
          return constrainedEntry(from, constraint);
        }
      };
    }

    // See Collections.CheckedMap.CheckedEntrySet for details on attacks.

    @Override
    public Object[] toArray() {
      return standardToArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      return standardToArray(array);
    }

    @Override
    public boolean contains(Object o) {
      return Maps.containsEntryImpl(delegate(), o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
      return standardContainsAll(c);
    }

    @Override
    public boolean remove(Object o) {
      return Maps.removeEntryImpl(delegate(), o);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
      return standardRemoveAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
      return standardRetainAll(c);
    }
  }

  /** @see MapConstraints#constrainedEntrySet */
  static class ConstrainedEntrySet<K, V> extends ConstrainedEntries
      implements Set<Entry {
    ConstrainedEntrySet(Set<Entry entries, MapConstraint constraint) {
      super(entries, constraint);
    }

    // See Collections.CheckedMap.CheckedEntrySet for details on attacks.

    @Override
    public boolean equals(@Nullable Object object) {
      return Sets.equalsImpl(this, object);
    }

    @Override
    public int hashCode() {
      return Sets.hashCodeImpl(this);
    }
  }

  /** @see MapConstraints#constrainedAsMapEntries */
  static class ConstrainedAsMapEntries<K, V> extends ForwardingSet>> {
    private final MapConstraint<? super K, ? super V> constraint;
    private final Set<Entry> entries;

    ConstrainedAsMapEntries(
        Set<Entry> entries, MapConstraint constraint) {
      this.entries = entries;
      this.constraint = constraint;
    }

    @Override
    protected Set<Entry> delegate() {
      return entries;
    }

    @Override
    public Iterator<Entry> iterator() {
      return new TransformedIterator<Entry, Entry>>(
          entries.iterator()) {
        @Override
        Entry<K, Collection transform(Entry> from) {
          return constrainedAsMapEntry(from, constraint);
        }
      };
    }

    // See Collections.CheckedMap.CheckedEntrySet for details on attacks.

    @Override
    public Object[] toArray() {
      return standardToArray();
    }

    @Override
    public <T> T[] toArray(T[] array) {
      return standardToArray(array);
    }

    @Override
    public boolean contains(Object o) {
      return Maps.containsEntryImpl(delegate(), o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
      return standardContainsAll(c);
    }

    @Override
    public boolean equals(@Nullable Object object) {
      return standardEquals(object);
    }

    @Override
    public int hashCode() {
      return standardHashCode();
    }

    @Override
    public boolean remove(Object o) {
      return Maps.removeEntryImpl(delegate(), o);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
      return standardRemoveAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
      return standardRetainAll(c);
    }
  }

  private static class ConstrainedListMultimap<K, V> extends ConstrainedMultimap
      implements ListMultimap<K, V> {
    ConstrainedListMultimap(
        ListMultimap<K, V> delegate, MapConstraint constraint) {
      super(delegate, constraint);
    }

    @Override
    public List<V> get(K key) {
      return (List<V>) super.get(key);
    }

    @Override
    public List<V> removeAll(Object key) {
      return (List<V>) super.removeAll(key);
    }

    @Override
    public List<V> replaceValues(K key, Iterable values) {
      return (List<V>) super.replaceValues(key, values);
    }
  }

  private static class ConstrainedSetMultimap<K, V> extends ConstrainedMultimap
      implements SetMultimap<K, V> {
    ConstrainedSetMultimap(
        SetMultimap<K, V> delegate, MapConstraint constraint) {
      super(delegate, constraint);
    }

    @Override
    public Set<V> get(K key) {
      return (Set<V>) super.get(key);
    }

    @Override
    public Set<Map.Entry entries() {
      return (Set<Map.Entry) super.entries();
    }

    @Override
    public Set<V> removeAll(Object key) {
      return (Set<V>) super.removeAll(key);
    }

    @Override
    public Set<V> replaceValues(K key, Iterable values) {
      return (Set<V>) super.replaceValues(key, values);
    }
  }

  private static class ConstrainedSortedSetMultimap<K, V> extends ConstrainedSetMultimap
      implements SortedSetMultimap<K, V> {
    ConstrainedSortedSetMultimap(
        SortedSetMultimap<K, V> delegate, MapConstraint constraint) {
      super(delegate, constraint);
    }

    @Override
    public SortedSet<V> get(K key) {
      return (SortedSet<V>) super.get(key);
    }

    @Override
    public SortedSet<V> removeAll(Object key) {
      return (SortedSet<V>) super.removeAll(key);
    }

    @Override
    public SortedSet<V> replaceValues(K key, Iterable values) {
      return (SortedSet<V>) super.replaceValues(key, values);
    }

    @Override
    public Comparator<? super V> valueComparator() {
      return ((SortedSetMultimap<K, V>) delegate()).valueComparator();
    }
  }

  private static <K, V> Collection checkValues(
      K key, Iterable<? extends V> values, MapConstraint constraint) {
    Collection<V> copy = Lists.newArrayList(values);
    for (V value : copy) {
      constraint.checkKeyValue(key, value);
    }
    return copy;
  }

  private static <K, V> Map checkMap(
      Map<? extends K, ? extends V> map, MapConstraint constraint) {
    Map<K, V> copy = new LinkedHashMap(map);
    for (Entry<K, V> entry : copy.entrySet()) {
      constraint.checkKeyValue(entry.getKey(), entry.getValue());
    }
    return copy;
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java MapConstraints.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.