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

Java example source code file (D3DPaints.cpp)

This example Java source code file (D3DPaints.cpp) 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

d3dpaints_resetpaint, d3dsamp_addressu, d3dtaddress_clamp, d3dtexf_linear, d3dtss_texcoordindex, d3dtss_texturetransformflags, dword, e_fail, hresult, idirect3ddevice9, j2dtraceln, max_fractions_small, return_status_if_failed, return_status_if_null

The D3DPaints.cpp Java example source code

/*
 * Copyright (c) 2007, 2008, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#include <jlong.h>
#include <string.h>

#include "sun_java2d_d3d_D3DPaints_MultiGradient.h"

#include "D3DPaints.h"
#include "D3DContext.h"
#include "D3DRenderQueue.h"
#include "D3DSurfaceData.h"

HRESULT
D3DPaints_ResetPaint(D3DContext *d3dc)
{
    jint pixel, paintState;
    jubyte ea;
    HRESULT res;

    J2dTraceLn(J2D_TRACE_INFO, "D3DPaints_ResetPaint");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);

    paintState = d3dc->GetPaintState();
    J2dTraceLn1(J2D_TRACE_VERBOSE, "  state=%d", paintState);

    res = d3dc->UpdateState(STATE_OTHEROP);

    // disable current complex paint state, if necessary
    if (paintState > PAINT_ALPHACOLOR) {
        IDirect3DDevice9 *pd3dDevice = d3dc->Get3DDevice();
        DWORD sampler = d3dc->useMask ? 1 : 0;

        d3dc->SetTexture(NULL, sampler);
        pd3dDevice->SetSamplerState(sampler,
                                    D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
        pd3dDevice->SetSamplerState(sampler,
                                    D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
        pd3dDevice->SetTextureStageState(sampler,
                                         D3DTSS_TEXCOORDINDEX, sampler);
        res = pd3dDevice->SetTextureStageState(sampler,
                                               D3DTSS_TEXTURETRANSFORMFLAGS,
                                               D3DTTFF_DISABLE);

        if (paintState == PAINT_GRADIENT     ||
            paintState == PAINT_LIN_GRADIENT ||
            paintState == PAINT_RAD_GRADIENT)
        {
            res = pd3dDevice->SetPixelShader(NULL);
        }
    }

    // set each component of the current color state to the extra alpha
    // value, which will effectively apply the extra alpha to each fragment
    // in paint/texturing operations
    ea = (jubyte)(d3dc->extraAlpha * 0xff + 0.5f);
    pixel = (ea << 24) | (ea << 16) | (ea << 8) | (ea << 0);
    d3dc->pVCacher->SetColor(pixel);
    d3dc->useMask = JNI_FALSE;
    d3dc->SetPaintState(-1);
    return res;
}

HRESULT
D3DPaints_SetColor(D3DContext *d3dc, jint pixel)
{
    HRESULT res = S_OK;

    J2dTraceLn1(J2D_TRACE_INFO, "D3DPaints_SetColor: pixel=%08x", pixel);

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);

    // no need to reset the current op state here unless the paint
    // state really needs to be changed
    if (d3dc->GetPaintState() > PAINT_ALPHACOLOR) {
        res = D3DPaints_ResetPaint(d3dc);
    }

    d3dc->pVCacher->SetColor(pixel);
    d3dc->useMask = JNI_FALSE;
    d3dc->SetPaintState(PAINT_ALPHACOLOR);
    return res;
}

/************************* GradientPaint support ****************************/

HRESULT
D3DPaints_SetGradientPaint(D3DContext *d3dc,
                           jboolean useMask, jboolean cyclic,
                           jdouble p0, jdouble p1, jdouble p3,
                           jint pixel1, jint pixel2)
{
    IDirect3DDevice9 *pd3dDevice;
    HRESULT res;

    J2dTraceLn(J2D_TRACE_INFO, "D3DPaints_SetGradientPaint");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);
    D3DPaints_ResetPaint(d3dc);

#if 0
    /*
     * REMIND: The following code represents the original fast gradient
     *         implementation.  The problem is that it relies on LINEAR
     *         texture filtering, which does not provide sufficient
     *         precision on certain hardware (from ATI, notably), which
     *         will cause visible banding (e.g. 64 shades of gray between
     *         black and white, instead of the expected 256 shades.  For
     *         correctness on such hardware, it is necessary to use a
     *         shader-based approach that does not suffer from these
     *         precision issues (see below).  This original implementation
     *         is about 16x faster than software, whereas the shader-based
     *         implementation is only about 4x faster than software (still
     *         impressive).  For simplicity, we will always use the
     *         shader-based version for now, but in the future we could
     *         consider using the fast path for certain hardware (that does
     *         not exhibit the problem) or provide a flag to allow developers
     *         to control which path we take (for those that are less
     *         concerned about quality).  Therefore, I'll leave this code
     *         here (currently disabled) for future use.
     */
    D3DResource *pGradientTexRes;
    IDirect3DTexture9 *pGradientTex;

    // this will initialize the gradient texture, if necessary
    res = d3dc->GetResourceManager()->GetGradientTexture(&pGradientTexRes);
    RETURN_STATUS_IF_FAILED(res);

    pGradientTex = pGradientTexRes->GetTexture();

    // update the texture containing the gradient colors
    {
        D3DLOCKED_RECT lockedRect;
        res = pGradientTex->LockRect(0, &lockedRect, NULL, D3DLOCK_NOSYSLOCK);
        RETURN_STATUS_IF_FAILED(res);
        jint *pPix = (jint*)lockedRect.pBits;
        pPix[0] = pixel1;
        pPix[1] = pixel2;
        pGradientTex->UnlockRect(0);
    }

    DWORD sampler = useMask ? 1 : 0;
    DWORD wrapMode = cyclic ? D3DTADDRESS_WRAP : D3DTADDRESS_CLAMP;
    d3dc->SetTexture(pGradientTex, sampler);
    d3dc->UpdateTextureColorState(D3DTA_TEXTURE, sampler);

    pd3dDevice = d3dc->Get3DDevice();
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSU, wrapMode);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSV, wrapMode);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

    D3DMATRIX mt;
    ZeroMemory(&mt, sizeof(mt));
    mt._11 = (float)p0;
    mt._21 = (float)p1;
    mt._31 = (float)0.0;
    mt._41 = (float)p3;
    mt._12 = 0.0f;
    mt._22 = 1.0f;
    mt._32 = 0.0f;
    mt._42 = 0.0f;
    pd3dDevice->SetTransform(useMask ? D3DTS_TEXTURE1 : D3DTS_TEXTURE0, &mt);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXCOORDINDEX,
                                     D3DTSS_TCI_CAMERASPACEPOSITION);
    res = pd3dDevice->SetTextureStageState(sampler,
                                     D3DTSS_TEXTURETRANSFORMFLAGS,
                                     D3DTTFF_COUNT2);
#else
    jfloat params[4];
    jfloat color[4];
    jint flags = 0;

    if (cyclic)  flags |= BASIC_GRAD_IS_CYCLIC;
    if (useMask) flags |= BASIC_GRAD_USE_MASK;

    // locate/enable the shader program for the given flags
    res = d3dc->EnableBasicGradientProgram(flags);
    RETURN_STATUS_IF_FAILED(res);

    // update the "uniform" values
    params[0] = (jfloat)p0;
    params[1] = (jfloat)p1;
    params[2] = (jfloat)p3;
    params[3] = 0.0f; // unused
    pd3dDevice = d3dc->Get3DDevice();
    res = pd3dDevice->SetPixelShaderConstantF(0, params, 1);

    color[0] = ((pixel1 >> 16) & 0xff) / 255.0f; // r
    color[1] = ((pixel1 >>  8) & 0xff) / 255.0f; // g
    color[2] = ((pixel1 >>  0) & 0xff) / 255.0f; // b
    color[3] = ((pixel1 >> 24) & 0xff) / 255.0f; // a
    res = pd3dDevice->SetPixelShaderConstantF(1, color, 1);

    color[0] = ((pixel2 >> 16) & 0xff) / 255.0f; // r
    color[1] = ((pixel2 >>  8) & 0xff) / 255.0f; // g
    color[2] = ((pixel2 >>  0) & 0xff) / 255.0f; // b
    color[3] = ((pixel2 >> 24) & 0xff) / 255.0f; // a
    res = pd3dDevice->SetPixelShaderConstantF(2, color, 1);

    // set up texture coordinate transform with identity matrix, which
    // will have the effect of passing the current window-space coordinates
    // through to the TEXCOORD0/1 register used by the basic gradient
    // pixel shader
    DWORD sampler = useMask ? 1 : 0;
    D3DMATRIX mt;
    ZeroMemory(&mt, sizeof(mt));
    mt._11 = 1.0f;
    mt._21 = 0.0f;
    mt._31 = 0.0f;
    mt._41 = 0.0f;
    mt._12 = 0.0f;
    mt._22 = 1.0f;
    mt._32 = 0.0f;
    mt._42 = 0.0f;
    pd3dDevice->SetTransform(useMask ? D3DTS_TEXTURE1 : D3DTS_TEXTURE0, &mt);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXCOORDINDEX,
                                     D3DTSS_TCI_CAMERASPACEPOSITION);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXTURETRANSFORMFLAGS,
                                     D3DTTFF_COUNT2);
#endif

    // pixel state has been set appropriately in D3DPaints_ResetPaint()
    d3dc->useMask = useMask;
    d3dc->SetPaintState(PAINT_GRADIENT);
    return res;
}

/************************** TexturePaint support ****************************/

HRESULT
D3DPaints_SetTexturePaint(D3DContext *d3dc,
                          jboolean useMask,
                          jlong pSrcOps, jboolean filter,
                          jdouble xp0, jdouble xp1, jdouble xp3,
                          jdouble yp0, jdouble yp1, jdouble yp3)
{
    D3DSDOps *srcOps = (D3DSDOps *)jlong_to_ptr(pSrcOps);
    IDirect3DDevice9 *pd3dDevice;
    HRESULT res;

    J2dTraceLn(J2D_TRACE_INFO, "D3DPaints_SetTexturePaint");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);
    RETURN_STATUS_IF_NULL(srcOps, E_FAIL);
    RETURN_STATUS_IF_NULL(srcOps->pResource, E_FAIL);
    D3DPaints_ResetPaint(d3dc);

    DWORD sampler = useMask ? 1 : 0;
    DWORD dwFilter = filter ? D3DTEXF_LINEAR : D3DTEXF_POINT;
    res = d3dc->SetTexture(srcOps->pResource->GetTexture(), sampler);
    d3dc->UpdateTextureColorState(D3DTA_TEXTURE, sampler);
    pd3dDevice = d3dc->Get3DDevice();
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MAGFILTER, dwFilter);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MINFILTER, dwFilter);

    D3DMATRIX mt;
    ZeroMemory(&mt, sizeof(mt));

    // offset by a half texel to correctly map texels to pixels
    //  m02 = tx * m00 + ty * m01 + m02;
    //  m12 = tx * m10 + ty * m11 + m12;
    jdouble tx = (1 / (2.0f * srcOps->pResource->GetDesc()->Width));
    jdouble ty = (1 / (2.0f * srcOps->pResource->GetDesc()->Height));
    xp3 = tx * xp0 + ty * xp1 + xp3;
    yp3 = tx * yp0 + ty * yp1 + yp3;

    mt._11 = (float)xp0;
    mt._21 = (float)xp1;
    mt._31 = (float)0.0;
    mt._41 = (float)xp3;
    mt._12 = (float)yp0;
    mt._22 = (float)yp1;
    mt._32 = (float)0.0;
    mt._42 = (float)yp3;
    pd3dDevice->SetTransform(useMask ? D3DTS_TEXTURE1 : D3DTS_TEXTURE0, &mt);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXCOORDINDEX,
                                     D3DTSS_TCI_CAMERASPACEPOSITION);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXTURETRANSFORMFLAGS,
                                     D3DTTFF_COUNT2);

    // pixel state has been set appropriately in D3DPaints_ResetPaint()
    d3dc->useMask = useMask;
    d3dc->SetPaintState(PAINT_TEXTURE);
    return res;
}

/****************** Shared MultipleGradientPaint support ********************/

/** Composes the given parameters as flags into the given flags variable.*/
#define COMPOSE_FLAGS(flags, cycleMethod, large, useMask, linear) \
    do {                                                        \
        flags |= ((cycleMethod) & MULTI_GRAD_CYCLE_METHOD);     \
        if (large)   flags |= MULTI_GRAD_LARGE;                 \
        if (useMask) flags |= MULTI_GRAD_USE_MASK;              \
        if (linear)  flags |= MULTI_GRAD_LINEAR_RGB;            \
    } while (0)

/**
 * The maximum number of gradient "stops" supported by the fragment shader
 * and related code.  When the MULTI_GRAD_LARGE flag is set, we will use
 * MAX_FRACTIONS_LARGE; otherwise, we use MAX_FRACTIONS_SMALL.  By having
 * two separate values, we can have one highly optimized shader (SMALL) that
 * supports only a few fractions/colors, and then another, less optimal
 * shader that supports more stops.
 */
#define MAX_FRACTIONS \
    sun_java2d_d3d_D3DPaints_MultiGradient_MULTI_MAX_FRACTIONS_D3D
#define MAX_FRACTIONS_LARGE MAX_FRACTIONS
#define MAX_FRACTIONS_SMALL 4

/**
 * Called from the D3DPaints_SetLinear/RadialGradientPaint() methods
 * in order to setup the fraction/color values that are common to both.
 */
static HRESULT
D3DPaints_SetMultiGradientPaint(D3DContext *d3dc,
                                jboolean useMask, jint numStops,
                                void *pFractions, void *pPixels)
{
    HRESULT res;
    IDirect3DDevice9 *pd3dDevice;
    IDirect3DTexture9 *pMultiGradientTex;
    D3DResource *pMultiGradientTexRes;
    jint maxFractions = (numStops > MAX_FRACTIONS_SMALL) ?
        MAX_FRACTIONS_LARGE : MAX_FRACTIONS_SMALL;
    jfloat stopVals[MAX_FRACTIONS * 4];
    jfloat *fractions = (jfloat *)pFractions;
    juint *pixels = (juint *)pPixels;
    int i;
    int fIndex = 0;

    pd3dDevice = d3dc->Get3DDevice();

    // update the "uniform" fractions and scale factors
    for (i = 0; i < maxFractions; i++) {
        stopVals[fIndex+0] = (i < numStops)   ?
            fractions[i] : 0.0f;
        stopVals[fIndex+1] = (i < numStops-1) ?
            1.0f / (fractions[i+1] - fractions[i]) : 0.0f;
        stopVals[fIndex+2] = 0.0f; // unused
        stopVals[fIndex+3] = 0.0f; // unused
        fIndex += 4;
    }
    pd3dDevice->SetPixelShaderConstantF(0, stopVals, maxFractions);

    // this will initialize the multi-gradient texture, if necessary
    res = d3dc->GetResourceManager()->
        GetMultiGradientTexture(&pMultiGradientTexRes);
    RETURN_STATUS_IF_FAILED(res);

    pMultiGradientTex = pMultiGradientTexRes->GetTexture();

    // update the texture containing the gradient colors
    D3DLOCKED_RECT lockedRect;
    res = pMultiGradientTex->LockRect(0, &lockedRect, NULL, D3DLOCK_NOSYSLOCK);
    RETURN_STATUS_IF_FAILED(res);

    juint *pPix = (juint*)lockedRect.pBits;
    memcpy(pPix, pixels, numStops*sizeof(juint));
    if (numStops < MAX_MULTI_GRADIENT_COLORS) {
        // when we don't have enough colors to fill the entire
        // color gradient, we have to replicate the last color
        // in the right-most texel for the NO_CYCLE case where the
        // texcoord is sometimes forced to 1.0
        pPix[MAX_MULTI_GRADIENT_COLORS-1] = pixels[numStops-1];
    }
    pMultiGradientTex->UnlockRect(0);

    // set the gradient texture and update relevant state
    DWORD sampler = useMask ? 1 : 0;
    res = d3dc->SetTexture(pMultiGradientTex, sampler);
    d3dc->UpdateTextureColorState(D3DTA_TEXTURE, sampler);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
    pd3dDevice->SetSamplerState(sampler, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

    // set up texture coordinate transform with identity matrix, which
    // will have the effect of passing the current window-space coordinates
    // through to the TEXCOORD0/1 register used by the multi-stop
    // gradient pixel shader
    D3DMATRIX mt;
    ZeroMemory(&mt, sizeof(mt));
    mt._11 = 1.0f;
    mt._21 = 0.0f;
    mt._31 = 0.0f;
    mt._41 = 0.0f;
    mt._12 = 0.0f;
    mt._22 = 1.0f;
    mt._32 = 0.0f;
    mt._42 = 0.0f;
    pd3dDevice->SetTransform(useMask ? D3DTS_TEXTURE1 : D3DTS_TEXTURE0, &mt);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXCOORDINDEX,
                                     D3DTSS_TCI_CAMERASPACEPOSITION);
    pd3dDevice->SetTextureStageState(sampler, D3DTSS_TEXTURETRANSFORMFLAGS,
                                     D3DTTFF_COUNT2);
    return res;
}

/********************** LinearGradientPaint support *************************/

HRESULT
D3DPaints_SetLinearGradientPaint(D3DContext *d3dc, D3DSDOps *dstOps,
                                 jboolean useMask, jboolean linear,
                                 jint cycleMethod, jint numStops,
                                 jfloat p0, jfloat p1, jfloat p3,
                                 void *fractions, void *pixels)
{
    HRESULT res;
    IDirect3DDevice9 *pd3dDevice;
    jfloat params[4];
    jboolean large = (numStops > MAX_FRACTIONS_SMALL);
    jint flags = 0;

    J2dTraceLn(J2D_TRACE_INFO, "D3DPaints_SetLinearGradientPaint");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);
    RETURN_STATUS_IF_NULL(dstOps, E_FAIL);
    D3DPaints_ResetPaint(d3dc);

    COMPOSE_FLAGS(flags, cycleMethod, large, useMask, linear);

    // locate/enable the shader program for the given flags
    res = d3dc->EnableLinearGradientProgram(flags);
    RETURN_STATUS_IF_FAILED(res);

    // update the common "uniform" values (fractions and colors)
    D3DPaints_SetMultiGradientPaint(d3dc, useMask,
                                    numStops, fractions, pixels);

    // update the other "uniform" values
    params[0] = p0;
    params[1] = p1;
    params[2] = p3;
    params[3] = 0.0f; // unused
    pd3dDevice = d3dc->Get3DDevice();
    res = pd3dDevice->SetPixelShaderConstantF(16, params, 1);

    // pixel state has been set appropriately in D3DPaints_ResetPaint()
    d3dc->useMask = useMask;
    d3dc->SetPaintState(PAINT_LIN_GRADIENT);
    return res;
}

/********************** RadialGradientPaint support *************************/

HRESULT
D3DPaints_SetRadialGradientPaint(D3DContext *d3dc, D3DSDOps *dstOps,
                                 jboolean useMask, jboolean linear,
                                 jint cycleMethod, jint numStops,
                                 jfloat m00, jfloat m01, jfloat m02,
                                 jfloat m10, jfloat m11, jfloat m12,
                                 jfloat focusX,
                                 void *fractions, void *pixels)
{
    HRESULT res;
    IDirect3DDevice9 *pd3dDevice;
    jfloat denom, inv_denom;
    jfloat params[4];
    jboolean large = (numStops > MAX_FRACTIONS_SMALL);
    jint flags = 0;

    J2dTraceLn(J2D_TRACE_INFO, "D3DPaints_SetRadialGradientPaint");

    RETURN_STATUS_IF_NULL(d3dc, E_FAIL);
    RETURN_STATUS_IF_NULL(dstOps, E_FAIL);
    D3DPaints_ResetPaint(d3dc);

    COMPOSE_FLAGS(flags, cycleMethod, large, useMask, linear);

    // locate/enable the shader program for the given flags
    res = d3dc->EnableRadialGradientProgram(flags);
    RETURN_STATUS_IF_FAILED(res);

    // update the common "uniform" values (fractions and colors)
    D3DPaints_SetMultiGradientPaint(d3dc, useMask,
                                    numStops, fractions, pixels);

    // update the other "uniform" values
    params[0] = m00;
    params[1] = m01;
    params[2] = m02;
    params[3] = 0.0f; // unused
    pd3dDevice = d3dc->Get3DDevice();
    pd3dDevice->SetPixelShaderConstantF(16, params, 1);

    params[0] = m10;
    params[1] = m11;
    params[2] = m12;
    params[3] = 0.0f; // unused
    pd3dDevice->SetPixelShaderConstantF(17, params, 1);

    // pack a few unrelated, precalculated values into a single float4
    denom = 1.0f - (focusX * focusX);
    inv_denom = 1.0f / denom;
    params[0] = focusX;
    params[1] = denom;
    params[2] = inv_denom;
    params[3] = 0.0f; // unused
    res = pd3dDevice->SetPixelShaderConstantF(18, params, 1);

    // pixel state has been set appropriately in D3DPaints_ResetPaint()
    d3dc->useMask = useMask;
    d3dc->SetPaintState(PAINT_RAD_GRADIENT);
    return res;
}

Other Java examples (source code examples)

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