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

Java example source code file (InvokableTest.java)

This example Java source code file (InvokableTest.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, class, constructor, exception, illegalargumentexception, invokable, iterable, local, object, override, prepender, reflection, runnable, string, suppresswarnings, typetoken, util

The InvokableTest.java Java example source code

/*
 * Copyright (C) 2012 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.reflect;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.testing.EqualsTester;
import com.google.common.testing.NullPointerTester;

import junit.framework.TestCase;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.Collections;

import javax.annotation.Nullable;

/**
 * Unit tests for {@link Invokable}.
 *
 * @author Ben Yu
 */
@AndroidIncompatible // lots of failures, possibly some related to bad equals() implementations?
public class InvokableTest extends TestCase {

  public void testConstructor_returnType() throws Exception {
    assertEquals(Prepender.class,
        Prepender.constructor().getReturnType().getType());
  }

  private static class WithConstructorAndTypeParameter<T> {
    @SuppressWarnings("unused") // by reflection
    <X> WithConstructorAndTypeParameter() {}
  }

  public void testConstructor_returnType_hasTypeParameter() throws Exception {
    @SuppressWarnings("rawtypes") // Foo.class for Foo<T> is always raw type
    Class<WithConstructorAndTypeParameter> type = WithConstructorAndTypeParameter.class;
    @SuppressWarnings("rawtypes") // Foo.class
    Constructor<WithConstructorAndTypeParameter> constructor = type.getDeclaredConstructor();
    Invokable<?, ?> factory = Invokable.from(constructor);
    assertThat(factory.getTypeParameters()).hasLength(2);
    assertEquals(type.getTypeParameters()[0], factory.getTypeParameters()[0]);
    assertEquals(constructor.getTypeParameters()[0], factory.getTypeParameters()[1]);
    ParameterizedType returnType = (ParameterizedType) factory.getReturnType().getType();
    assertEquals(type, returnType.getRawType());
    assertEquals(ImmutableList.copyOf(type.getTypeParameters()),
        ImmutableList.copyOf(returnType.getActualTypeArguments()));
  }

  public void testConstructor_exceptionTypes() throws Exception {
    assertEquals(ImmutableList.of(TypeToken.of(NullPointerException.class)),
        Prepender.constructor(String.class, int.class).getExceptionTypes());
  }

  public void testConstructor_typeParameters() throws Exception {
    TypeVariable<?>[] variables = Prepender.constructor().getTypeParameters();
    assertThat(variables).hasLength(1);
    assertEquals("A", variables[0].getName());
  }

  public void testConstructor_parameters() throws Exception {
    Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class);
    ImmutableList<Parameter> parameters = delegate.getParameters();
    assertEquals(2, parameters.size());
    assertEquals(String.class, parameters.get(0).getType().getType());
    assertTrue(parameters.get(0).isAnnotationPresent(NotBlank.class));
    assertEquals(int.class, parameters.get(1).getType().getType());
    assertFalse(parameters.get(1).isAnnotationPresent(NotBlank.class));
    new EqualsTester()
        .addEqualityGroup(parameters.get(0))
        .addEqualityGroup(parameters.get(1))
        .testEquals();
  }

  public void testConstructor_call() throws Exception {
    Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class);
    Prepender prepender = delegate.invoke(null, "a", 1);
    assertEquals("a", prepender.prefix);
    assertEquals(1, prepender.times);
  }

  public void testConstructor_returning() throws Exception {
    Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class)
        .returning(Prepender.class);
    Prepender prepender = delegate.invoke(null, "a", 1);
    assertEquals("a", prepender.prefix);
    assertEquals(1, prepender.times);
  }

  public void testConstructor_invalidReturning() throws Exception {
    Invokable<?, Prepender> delegate = Prepender.constructor(String.class, int.class);
    try {
      delegate.returning(SubPrepender.class);
      fail();
    } catch (IllegalArgumentException expected) {}
  }

  public void testStaticMethod_returnType() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class);
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
  }

  public void testStaticMethod_exceptionTypes() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class);
    assertEquals(ImmutableList.of(), delegate.getExceptionTypes());
  }

  public void testStaticMethod_typeParameters() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class);
    TypeVariable<?>[] variables = delegate.getTypeParameters();
    assertThat(variables).hasLength(1);
    assertEquals("T", variables[0].getName());
  }

  public void testStaticMethod_parameters() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class);
    ImmutableList<Parameter> parameters = delegate.getParameters();
    assertEquals(2, parameters.size());
    assertEquals(String.class, parameters.get(0).getType().getType());
    assertTrue(parameters.get(0).isAnnotationPresent(NotBlank.class));
    assertEquals(new TypeToken<Iterable() {}, parameters.get(1).getType());
    assertFalse(parameters.get(1).isAnnotationPresent(NotBlank.class));
    new EqualsTester()
        .addEqualityGroup(parameters.get(0))
        .addEqualityGroup(parameters.get(1))
        .testEquals();
  }

  public void testStaticMethod_call() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", String.class, Iterable.class);
    @SuppressWarnings("unchecked") // prepend() returns Iterable<String>
    Iterable<String> result = (Iterable)
        delegate.invoke(null, "a", ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testStaticMethod_returning() throws Exception {
    Invokable<?, Iterable delegate = Prepender.method(
            "prepend", String.class, Iterable.class)
        .returning(new TypeToken<Iterable() {});
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
    Iterable<String> result = delegate.invoke(null, "a", ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testStaticMethod_returningRawType() throws Exception {
    @SuppressWarnings("rawtypes") // the purpose is to test raw type
    Invokable<?, Iterable> delegate = Prepender.method(
            "prepend", String.class, Iterable.class)
        .returning(Iterable.class);
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
    @SuppressWarnings("unchecked") // prepend() returns Iterable<String>
    Iterable<String> result = delegate.invoke(null, "a", ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testStaticMethod_invalidReturning() throws Exception {
    Invokable<?, Object> delegate = Prepender.method("prepend", String.class, Iterable.class);
    try {
      delegate.returning(new TypeToken<Iterable() {});
      fail();
    } catch (IllegalArgumentException expected) {}
  }

  public void testInstanceMethod_returnType() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class);
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
  }

  public void testInstanceMethod_exceptionTypes() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class);
    assertEquals(
        ImmutableList.of(
            TypeToken.of(IllegalArgumentException.class),
            TypeToken.of(NullPointerException.class)),
        delegate.getExceptionTypes());
  }

  public void testInstanceMethod_typeParameters() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class);
    assertThat(delegate.getTypeParameters()).isEmpty();
  }

  public void testInstanceMethod_parameters() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("prepend", Iterable.class);
    ImmutableList<Parameter> parameters = delegate.getParameters();
    assertEquals(1, parameters.size());
    assertEquals(new TypeToken<Iterable() {}, parameters.get(0).getType());
    assertThat(parameters.get(0).getAnnotations()).isEmpty();
    new EqualsTester().addEqualityGroup(parameters.get(0)).testEquals();
  }

  public void testInstanceMethod_call() throws Exception {
    Invokable<Prepender, ?> delegate = Prepender.method("prepend", Iterable.class);
    @SuppressWarnings("unchecked") // prepend() returns Iterable<String>
    Iterable<String> result = (Iterable)
        delegate.invoke(new Prepender("a", 2), ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testInstanceMethod_returning() throws Exception {
    Invokable<Prepender, Iterable delegate = Prepender.method(
            "prepend", Iterable.class)
        .returning(new TypeToken<Iterable() {});
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
    Iterable<String> result = delegate.invoke(new Prepender("a", 2), ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testInstanceMethod_returningRawType() throws Exception {
    @SuppressWarnings("rawtypes") // the purpose is to test raw type
    Invokable<Prepender, Iterable> delegate = Prepender.method("prepend", Iterable.class)
        .returning(Iterable.class);
    assertEquals(new TypeToken<Iterable() {}, delegate.getReturnType());
    @SuppressWarnings("unchecked") // prepend() returns Iterable<String>
    Iterable<String> result = delegate.invoke(
        new Prepender("a", 2), ImmutableList.of("b", "c"));
    assertEquals(ImmutableList.of("a", "a", "b", "c"), ImmutableList.copyOf(result));
  }

  public void testInstanceMethod_invalidReturning() throws Exception {
    Invokable<?, Object> delegate = Prepender.method("prepend", Iterable.class);
    try {
      delegate.returning(new TypeToken<Iterable() {});
      fail();
    } catch (IllegalArgumentException expected) {}
  }

  public void testPrivateInstanceMethod_isOverridable() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("privateMethod");
    assertTrue(delegate.isPrivate());
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  public void testPrivateFinalInstanceMethod_isOverridable() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("privateFinalMethod");
    assertTrue(delegate.isPrivate());
    assertTrue(delegate.isFinal());
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  public void testStaticMethod_isOverridable() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("staticMethod");
    assertTrue(delegate.isStatic());
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  public void testStaticFinalMethod_isFinal() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("staticFinalMethod");
    assertTrue(delegate.isStatic());
    assertTrue(delegate.isFinal());
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  static class Foo {}

  public void testConstructor_isOverridablel() throws Exception {
    Invokable<?, ?> delegate = Invokable.from(Foo.class.getDeclaredConstructor());
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  public void testMethod_isVarArgs() throws Exception {
    Invokable<?, ?> delegate = Prepender.method("privateVarArgsMethod", String[].class);
    assertTrue(delegate.isVarArgs());
  }

  public void testConstructor_isVarArgs() throws Exception {
    Invokable<?, ?> delegate = Prepender.constructor(String[].class);
    assertTrue(delegate.isVarArgs());
  }

  public void testGetOwnerType_constructor() throws Exception {
    Invokable<String, String> invokable = Invokable.from(String.class.getConstructor());
    assertEquals(TypeToken.of(String.class), invokable.getOwnerType());
  }

  public void testGetOwnerType_method() throws Exception {
    Invokable<?, ?> invokable = Invokable.from(String.class.getMethod("length"));
    assertEquals(TypeToken.of(String.class), invokable.getOwnerType());
  }

  private static final class FinalClass {
    @SuppressWarnings("unused") // used by reflection
    void notFinalMethod() {}
  }

  public void testNonFinalMethodInFinalClass_isOverridable() throws Exception {
    Invokable<?, ?> delegate = Invokable.from(
        FinalClass.class.getDeclaredMethod("notFinalMethod"));
    assertFalse(delegate.isOverridable());
    assertFalse(delegate.isVarArgs());
  }

  private class InnerWithDefaultConstructor {
    class NestedInner {}
  }

  public void testInnerClassDefaultConstructor() {
    Constructor<?> constructor =
        InnerWithDefaultConstructor.class.getDeclaredConstructors() [0];
    assertEquals(0, Invokable.from(constructor).getParameters().size());
  }

  public void testNestedInnerClassDefaultConstructor() {
    Constructor<?> constructor =
        InnerWithDefaultConstructor.NestedInner.class.getDeclaredConstructors() [0];
    assertEquals(0, Invokable.from(constructor).getParameters().size());
  }

  private class InnerWithOneParameterConstructor {
    @SuppressWarnings("unused") // called by reflection
    public InnerWithOneParameterConstructor(String s) {}
  }

  public void testInnerClassWithOneParameterConstructor() {
    Constructor<?> constructor =
        InnerWithOneParameterConstructor.class.getDeclaredConstructors()[0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(1, invokable.getParameters().size());
    assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType());
  }

  private class InnerWithAnnotatedConstructorParameter {
    @SuppressWarnings("unused") // called by reflection
    InnerWithAnnotatedConstructorParameter(@Nullable String s) {}
  }

  public void testInnerClassWithAnnotatedConstructorParameter() {
    Constructor<?> constructor =
        InnerWithAnnotatedConstructorParameter.class.getDeclaredConstructors() [0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(1, invokable.getParameters().size());
    assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType());
  }

  private class InnerWithGenericConstructorParameter {
    @SuppressWarnings("unused") // called by reflection
    InnerWithGenericConstructorParameter(Iterable<String> it, String s) {}
  }

  public void testInnerClassWithGenericConstructorParameter() {
    Constructor<?> constructor =
        InnerWithGenericConstructorParameter.class.getDeclaredConstructors() [0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(2, invokable.getParameters().size());
    assertEquals(new TypeToken<Iterable() {},
        invokable.getParameters().get(0).getType());
    assertEquals(TypeToken.of(String.class),
        invokable.getParameters().get(1).getType());
  }

  public void testAnonymousClassDefaultConstructor() {
    final int i = 1;
    final String s = "hello world";
    Class<?> anonymous = new Runnable() {
      @Override public void run() {
        System.out.println(s + i);
      }
    }.getClass();
    Constructor<?> constructor = anonymous.getDeclaredConstructors() [0];
    assertEquals(0, Invokable.from(constructor).getParameters().size());
  }

  public void testAnonymousClassWithTwoParametersConstructor() {
    abstract class Base {
      @SuppressWarnings("unused") // called by reflection
      Base(String s, int i) {}
    }
    Class<?> anonymous = new Base("test", 0) {}.getClass();
    Constructor<?> constructor = anonymous.getDeclaredConstructors() [0];
    assertEquals(2, Invokable.from(constructor).getParameters().size());
  }

  public void testLocalClassDefaultConstructor() {
    final int i = 1;
    final String s = "hello world";
    class LocalWithDefaultConstructor implements Runnable {
      @Override public void run() {
        System.out.println(s + i);
      }
    }
    Constructor<?> constructor = LocalWithDefaultConstructor.class.getDeclaredConstructors() [0];
    assertEquals(0, Invokable.from(constructor).getParameters().size());
  }

  public void testStaticAnonymousClassDefaultConstructor() throws Exception {
    doTestStaticAnonymousClassDefaultConstructor();
  }

  private static void doTestStaticAnonymousClassDefaultConstructor() {
    final int i = 1;
    final String s = "hello world";
    Class<?> anonymous = new Runnable() {
      @Override public void run() {
        System.out.println(s + i);
      }
    }.getClass();
    Constructor<?> constructor = anonymous.getDeclaredConstructors() [0];
    assertEquals(0, Invokable.from(constructor).getParameters().size());
  }

  public void testAnonymousClassInConstructor() {
    new AnonymousClassInConstructor();
  }

  private static class AnonymousClassInConstructor {
    AnonymousClassInConstructor() {
      final int i = 1;
      final String s = "hello world";
      Class<?> anonymous = new Runnable() {
        @Override public void run() {
          System.out.println(s + i);
        }
      }.getClass();
      Constructor<?> constructor = anonymous.getDeclaredConstructors() [0];
      assertEquals(0, Invokable.from(constructor).getParameters().size());
    }
  }

  public void testLocalClassInInstanceInitializer() {
    new LocalClassInInstanceInitializer();
  }

  private static class LocalClassInInstanceInitializer {
    {
      class Local {}
      Constructor<?> constructor = Local.class.getDeclaredConstructors() [0];
      assertEquals(0, Invokable.from(constructor).getParameters().size());
    }
  }

  public void testLocalClassInStaticInitializer() {
    new LocalClassInStaticInitializer();
  }

  private static class LocalClassInStaticInitializer {
    static {
      class Local {}
      Constructor<?> constructor = Local.class.getDeclaredConstructors() [0];
      assertEquals(0, Invokable.from(constructor).getParameters().size());
    }
  }

  public void testLocalClassWithSeeminglyHiddenThisInStaticInitializer_BUG() {
    new LocalClassWithSeeminglyHiddenThisInStaticInitializer();
  }

  /**
   * This class demonstrates a bug in getParameters() when the local class is inside static
   * initializer.
   */
  private static class LocalClassWithSeeminglyHiddenThisInStaticInitializer {
    static {
      class Local {
        @SuppressWarnings("unused") // through reflection
        Local(LocalClassWithSeeminglyHiddenThisInStaticInitializer outer) {}
      }
      Constructor<?> constructor = Local.class.getDeclaredConstructors() [0];
      int miscalculated = 0;
      assertEquals(miscalculated, Invokable.from(constructor).getParameters().size());
    }
  }

  public void testLocalClassWithOneParameterConstructor() throws Exception {
    final int i = 1;
    final String s = "hello world";
    class LocalWithOneParameterConstructor {
      @SuppressWarnings("unused") // called by reflection
      public LocalWithOneParameterConstructor(String x) {
        System.out.println(s + i);
      }
    }
    Constructor<?> constructor =
        LocalWithOneParameterConstructor.class.getDeclaredConstructors()[0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(1, invokable.getParameters().size());
    assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType());
  }

  public void testLocalClassWithAnnotatedConstructorParameter() throws Exception {
    class LocalWithAnnotatedConstructorParameter {
      @SuppressWarnings("unused") // called by reflection
      LocalWithAnnotatedConstructorParameter(@Nullable String s) {}
    }
    Constructor<?> constructor =
        LocalWithAnnotatedConstructorParameter.class.getDeclaredConstructors() [0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(1, invokable.getParameters().size());
    assertEquals(TypeToken.of(String.class), invokable.getParameters().get(0).getType());
  }

  public void testLocalClassWithGenericConstructorParameter() throws Exception {
    class LocalWithGenericConstructorParameter {
      @SuppressWarnings("unused") // called by reflection
      LocalWithGenericConstructorParameter(Iterable<String> it, String s) {}
    }
    Constructor<?> constructor =
        LocalWithGenericConstructorParameter.class.getDeclaredConstructors() [0];
    Invokable<?, ?> invokable = Invokable.from(constructor);
    assertEquals(2, invokable.getParameters().size());
    assertEquals(new TypeToken<Iterable() {},
        invokable.getParameters().get(0).getType());
    assertEquals(TypeToken.of(String.class),
        invokable.getParameters().get(1).getType());
  }

  public void testEquals() throws Exception {
    new EqualsTester()
        .addEqualityGroup(Prepender.constructor(), Prepender.constructor())
        .addEqualityGroup(Prepender.constructor(String.class, int.class))
        .addEqualityGroup(Prepender.method("privateMethod"), Prepender.method("privateMethod"))
        .addEqualityGroup(Prepender.method("privateFinalMethod"))
        .testEquals();
  }

  public void testNulls() {
    new NullPointerTester().testAllPublicStaticMethods(Invokable.class);
    new NullPointerTester().testAllPublicInstanceMethods(Prepender.method("staticMethod"));
  }

  @Retention(RetentionPolicy.RUNTIME)
  private @interface NotBlank {}

  /** Class for testing constructor, static method and instance method. */
  @SuppressWarnings("unused") // most are called by reflection
  private static class Prepender {

    private final String prefix;
    private final int times;

    Prepender(@NotBlank String prefix, int times) throws NullPointerException {
      this.prefix = prefix;
      this.times = times;
    }

    Prepender(String... varargs) {
      this(null, 0);
    }

    // just for testing
    private <A> Prepender() {
      this(null, 0);
    }

    static <T> Iterable prepend(@NotBlank String first, Iterable tail) {
      return Iterables.concat(ImmutableList.of(first), tail);
    }

    Iterable<String> prepend(Iterable tail)
        throws IllegalArgumentException, NullPointerException {
      return Iterables.concat(Collections.nCopies(times, prefix), tail);
    }

    static Invokable<?, Prepender> constructor(Class... parameterTypes) throws Exception {
      Constructor<Prepender> constructor = Prepender.class.getDeclaredConstructor(parameterTypes);
      return Invokable.from(constructor);
    }

    static Invokable<Prepender, Object> method(String name, Class... parameterTypes) {
      try {
        Method method = Prepender.class.getDeclaredMethod(name, parameterTypes);
        @SuppressWarnings("unchecked") // The method is from Prepender.
        Invokable<Prepender, Object> invokable = (Invokable)
            Invokable.from(method);
        return invokable;
      } catch (NoSuchMethodException e) {
        throw new IllegalArgumentException(e);
      }
    }

    private void privateMethod() {}

    private final void privateFinalMethod() {}

    static void staticMethod() {}

    static final void staticFinalMethod() {}

    private void privateVarArgsMethod(String... varargs) {}
  }

  private static class SubPrepender extends Prepender {
    @SuppressWarnings("unused") // needed to satisfy compiler, never called
    public SubPrepender() throws NullPointerException {
      throw new AssertionError();
    }
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java InvokableTest.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.