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

Java example source code file (BIProperty.java)

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

biproperty, boolean, ccustomizations, celementpropertyinfo, collectiontypeattribute, creferencepropertyinfo, illegalstateexception, optionalpropertymode, qname, rawtypeset, sax, string, util, xmlattribute, xscomponent, xsparticle

The BIProperty.java Java example source code

/*
 * Copyright (c) 1997, 2011, 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.
 */

package com.sun.tools.internal.xjc.reader.xmlschema.bindinfo;

import java.util.Collection;
import java.util.Collections;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.namespace.QName;

import com.sun.codemodel.internal.JJavaName;
import com.sun.codemodel.internal.JType;
import com.sun.tools.internal.xjc.ErrorReceiver;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRenderer;
import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
import com.sun.tools.internal.xjc.generator.bean.field.IsSetFieldRenderer;
import com.sun.tools.internal.xjc.model.CAttributePropertyInfo;
import com.sun.tools.internal.xjc.model.CCustomizations;
import com.sun.tools.internal.xjc.model.CElementPropertyInfo;
import com.sun.tools.internal.xjc.model.CPropertyInfo;
import com.sun.tools.internal.xjc.model.CReferencePropertyInfo;
import com.sun.tools.internal.xjc.model.CValuePropertyInfo;
import com.sun.tools.internal.xjc.model.TypeUse;
import com.sun.tools.internal.xjc.reader.Const;
import com.sun.tools.internal.xjc.reader.RawTypeSet;
import com.sun.tools.internal.xjc.reader.Ring;
import com.sun.tools.internal.xjc.reader.TypeUtil;
import com.sun.tools.internal.xjc.reader.xmlschema.BGMBuilder;
import com.sun.xml.internal.bind.api.impl.NameConverter;
import com.sun.xml.internal.xsom.XSAnnotation;
import com.sun.xml.internal.xsom.XSAttGroupDecl;
import com.sun.xml.internal.xsom.XSAttributeDecl;
import com.sun.xml.internal.xsom.XSAttributeUse;
import com.sun.xml.internal.xsom.XSComplexType;
import com.sun.xml.internal.xsom.XSComponent;
import com.sun.xml.internal.xsom.XSContentType;
import com.sun.xml.internal.xsom.XSElementDecl;
import com.sun.xml.internal.xsom.XSFacet;
import com.sun.xml.internal.xsom.XSIdentityConstraint;
import com.sun.xml.internal.xsom.XSModelGroup;
import com.sun.xml.internal.xsom.XSModelGroupDecl;
import com.sun.xml.internal.xsom.XSNotation;
import com.sun.xml.internal.xsom.XSParticle;
import com.sun.xml.internal.xsom.XSSchema;
import com.sun.xml.internal.xsom.XSSimpleType;
import com.sun.xml.internal.xsom.XSWildcard;
import com.sun.xml.internal.xsom.XSXPath;
import com.sun.xml.internal.xsom.util.XSFinder;
import com.sun.xml.internal.xsom.visitor.XSFunction;

import org.xml.sax.Locator;

/**
 * Property customization.
 *
 * This customization turns an arbitrary schema component
 * into a Java property (some restrictions apply.)
 *
 * <p>
 * All the getter methods (such as <code>getBaseType or
 * <code>getBindStyle) honors the delegation chain of
 * property customization specified in the spec. Namely,
 * if two property customizations are attached to an attribute
 * use and an attribute decl, then anything unspecified in the
 * attribute use defaults to attribute decl.
 *
 * <p>
 * Property customizations are acknowledged
 * (1) when they are actually used, and
 * (2) when they are given at the component, which is mapped to a class.
 *     (so-called "point of declaration" customization)
 *
 * @author
 *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
 */
@XmlRootElement(name="property")
public final class BIProperty extends AbstractDeclarationImpl {

    // can be null
    @XmlAttribute
    private String name = null;

    // can be null
    @XmlElement
    private String javadoc = null;

    // can be null
    @XmlElement
    private BaseTypeBean baseType = null;

    // TODO: report 'unsupported' error if this is true
    @XmlAttribute
    private boolean generateFailFastSetterMethod = false;



    public BIProperty(Locator loc, String _propName, String _javadoc,
                      BaseTypeBean _baseType, CollectionTypeAttribute collectionType, Boolean isConst,
                      OptionalPropertyMode optionalProperty, Boolean genElemProp) {
        super(loc);

        this.name = _propName;
        this.javadoc = _javadoc;
        this.baseType = _baseType;
        this.collectionType = collectionType;
        this.isConstantProperty = isConst;
        this.optionalProperty = optionalProperty;
        this.generateElementProperty = genElemProp;
    }

    protected BIProperty() {}

    @Override
    public Collection<BIDeclaration> getChildren() {
        BIConversion conv = getConv();
        if(conv==null)
            return super.getChildren();
        else
            return Collections.<BIDeclaration>singleton(conv);
    }

    public void setParent( BindInfo parent ) {
        super.setParent(parent);
        if(baseType!=null && baseType.conv!=null)
            baseType.conv.setParent(parent);
    }



    /**
     * Returns the customized property name.
     *
     * This method honors the "enableJavaNamingConvention" customization
     * and formats the property name accordingly if necessary.
     *
     * Thus the caller should <em>NOT apply the XML-to-Java name
     * conversion algorithm to the value returned from this method.
     *
     * @param forConstant
     *      If the property name is intended for a constant property name,
     *      set to true. This will change the result
     *
     * @return
     *      This method can return null if the customization doesn't
     *      specify the name.
     */
    public String getPropertyName( boolean forConstant ) {
        if(name!=null) {
            BIGlobalBinding gb = getBuilder().getGlobalBinding();
            NameConverter nc = getBuilder().model.getNameConverter();

            if( gb.isJavaNamingConventionEnabled() && !forConstant )
                // apply XML->Java conversion
                return nc.toPropertyName(name);
            else
                return name;    // ... or don't change the value
        }
        BIProperty next = getDefault();
        if(next!=null)  return next.getPropertyName(forConstant);
        else            return null;
    }

    /**
     * Gets the associated javadoc.
     *
     * @return
     *      null if none is specfieid.
     */
    public String getJavadoc() {
        return javadoc;
    }

    // can be null
    public JType getBaseType() {
        if(baseType!=null && baseType.name!=null) {
            return TypeUtil.getType(getCodeModel(),
                    baseType.name,
                    Ring.get(ErrorReceiver.class),getLocation());
        }
        BIProperty next = getDefault();
        if(next!=null)  return next.getBaseType();
        else            return null;
    }


    // can be null
    @XmlAttribute
    private CollectionTypeAttribute collectionType = null;

    /**
     * Gets the realization of this field.
     * @return Always return non-null.
     */
    CollectionTypeAttribute getCollectionType() {
        if(collectionType!=null)   return collectionType;
        return getDefault().getCollectionType();
    }


    @XmlAttribute
    private OptionalPropertyMode optionalProperty = null;

    // virtual property for @generateIsSetMethod
    @XmlAttribute
    void setGenerateIsSetMethod(boolean b) {
        optionalProperty = b ? OptionalPropertyMode.ISSET : OptionalPropertyMode.WRAPPER;
    }

    public OptionalPropertyMode getOptionalPropertyMode() {
        if(optionalProperty!=null)   return optionalProperty;
        return getDefault().getOptionalPropertyMode();
    }

    // null if delegated
    @XmlAttribute
    private Boolean generateElementProperty = null;
    /**
     * If true, the property will automatically be a reference property.
     * (Talk about confusing names!)
     */
    private Boolean generateElementProperty() {
        if(generateElementProperty!=null)   return generateElementProperty;
        BIProperty next = getDefault();
        if(next!=null)      return next.generateElementProperty();

        return null;
    }


    // true, false, or null (which means the value should be inherited.)
    @XmlAttribute(name="fixedAttributeAsConstantProperty")
    private Boolean isConstantProperty;
    /**
     * Gets the inherited value of the "fixedAttrToConstantProperty" customization.
     *
     * <p>
     * Note that returning true from this method doesn't necessarily mean
     * that a property needs to be mapped to a constant property.
     * It just means that it's mapped to a constant property
     * <b>if an attribute use carries a fixed value.
     *
     * <p>
     * I don't like this semantics but that's what the spec implies.
     */
    public boolean isConstantProperty() {
        if(isConstantProperty!=null)    return isConstantProperty;

        BIProperty next = getDefault();
        if(next!=null)      return next.isConstantProperty();

        // globalBinding always has true or false in this property,
        // so this can't happen
        throw new AssertionError();
    }

    public CValuePropertyInfo createValueProperty(String defaultName,boolean forConstant,
        XSComponent source,TypeUse tu, QName typeName) {

        markAsAcknowledged();
        constantPropertyErrorCheck();

        String name = getPropertyName(forConstant);
        if(name==null) {
            name = defaultName;
            if(tu.isCollection() && getBuilder().getGlobalBinding().isSimpleMode())
                name = JJavaName.getPluralForm(name);
        }

        CValuePropertyInfo prop = wrapUp(new CValuePropertyInfo(name, source, getCustomizations(source), source.getLocator(), tu, typeName), source);
        BIInlineBinaryData.handle(source, prop);
        return prop;
    }

    public CAttributePropertyInfo createAttributeProperty( XSAttributeUse use, TypeUse tu ) {

        boolean forConstant =
            getCustomization(use).isConstantProperty() &&
            use.getFixedValue()!=null;

        String name = getPropertyName(forConstant);
        if(name==null) {
            NameConverter conv = getBuilder().getNameConverter();
            if(forConstant)
                name = conv.toConstantName(use.getDecl().getName());
            else
                name = conv.toPropertyName(use.getDecl().getName());
            if(tu.isCollection() && getBuilder().getGlobalBinding().isSimpleMode())
                name = JJavaName.getPluralForm(name);
        }

        markAsAcknowledged();
        constantPropertyErrorCheck();

        return wrapUp(new CAttributePropertyInfo(name,use,getCustomizations(use),use.getLocator(),
                BGMBuilder.getName(use.getDecl()), tu,
                BGMBuilder.getName(use.getDecl().getType()), use.isRequired() ),use);
    }

    /**
     *
     *
     * @param defaultName
     *      If the name is not customized, this name will be used
     *      as the default. Note that the name conversion <b>MUST
     *      be applied before this method is called if necessary.
     * @param source
     *      Source schema component from which a field is built.
     */
    public CElementPropertyInfo createElementProperty(String defaultName, boolean forConstant, XSParticle source,
                                                      RawTypeSet types) {

        if(!types.refs.isEmpty())
            // if this property is empty, don't acknowleedge the customization
            // this allows pointless property customization to be reported as an error
            markAsAcknowledged();
        constantPropertyErrorCheck();

        String name = getPropertyName(forConstant);
        if(name==null)
            name = defaultName;

        CElementPropertyInfo prop = wrapUp(
            new CElementPropertyInfo(
                name, types.getCollectionMode(),
                types.id(),
                types.getExpectedMimeType(),
                source, getCustomizations(source),
                source.getLocator(), types.isRequired()),
            source);

        types.addTo(prop);

        BIInlineBinaryData.handle(source.getTerm(), prop);
        return prop;
    }

    public CReferencePropertyInfo createDummyExtendedMixedReferenceProperty(
            String defaultName, XSComponent source, RawTypeSet types) {
            return createReferenceProperty(
                    defaultName,
                    false,
                    source,
                    types,
                    true,
                    true,
                    false,
                    true);
    }

    public CReferencePropertyInfo createContentExtendedMixedReferenceProperty(
            String defaultName, XSComponent source, RawTypeSet types) {
            return createReferenceProperty(
                    defaultName,
                    false,
                    source,
                    types,
                    true,
                    false,
                    true,
                    true);
    }

    public CReferencePropertyInfo createReferenceProperty(
            String defaultName, boolean forConstant, XSComponent source,
            RawTypeSet types, boolean isMixed, boolean dummy, boolean content, boolean isMixedExtended) {

        if (types == null) {    // this is a special case where we need to generate content because potential subtypes would need to be able to override what's store inside
            content = true;
        } else {
            if(!types.refs.isEmpty())
                // if this property is empty, don't acknowleedge the customization
                // this allows pointless property customization to be reported as an error
                markAsAcknowledged();
        }
        constantPropertyErrorCheck();

        String name = getPropertyName(forConstant);
        if(name==null)
            name = defaultName;

        CReferencePropertyInfo prop = wrapUp(
                                            new CReferencePropertyInfo(
                                                name,
                                                (types == null) ? true : types.getCollectionMode().isRepeated()||isMixed,
                                                (types == null) ? false : types.isRequired(),
                                                isMixed,
                                                source,
                                                getCustomizations(source), source.getLocator(), dummy, content, isMixedExtended),
                                        source);
        if (types != null) {
            types.addTo(prop);
        }

        BIInlineBinaryData.handle(source, prop);
        return prop;
    }

    public CPropertyInfo createElementOrReferenceProperty(
            String defaultName, boolean forConstant, XSParticle source,
            RawTypeSet types) {

        boolean generateRef;

        switch(types.canBeTypeRefs) {
        case CAN_BE_TYPEREF:
        case SHOULD_BE_TYPEREF:
            // it's up to the use
            Boolean b = generateElementProperty();
            if(b==null) // follow XJC recommendation
                generateRef = types.canBeTypeRefs== RawTypeSet.Mode.CAN_BE_TYPEREF;
            else // use the value user gave us
                generateRef = b;
            break;
        case MUST_BE_REFERENCE:
            generateRef = true;
            break;
        default:
            throw new AssertionError();
        }

        if(generateRef) {
            return createReferenceProperty(defaultName,forConstant,source,types, false, false, false, false);
        } else {
            return createElementProperty(defaultName,forConstant,source,types);
        }
    }

    /**
     * Common finalization of {@link CPropertyInfo} for the create***Property methods.
     */
    private <T extends CPropertyInfo> T wrapUp(T prop, XSComponent source) {
        prop.javadoc = concat(javadoc,
            getBuilder().getBindInfo(source).getDocumentation());
        if(prop.javadoc==null)
            prop.javadoc="";

        // decide the realization.
        FieldRenderer r;
        OptionalPropertyMode opm = getOptionalPropertyMode();
        if(prop.isCollection()) {
            CollectionTypeAttribute ct = getCollectionType();
            r = ct.get(getBuilder().model);
        } else {
            FieldRendererFactory frf = getBuilder().fieldRendererFactory;

            if(prop.isOptionalPrimitive()) {
                // the property type can be primitive type if we are to ignore absence
                switch(opm) {
                case PRIMITIVE:
                    r = frf.getRequiredUnboxed();
                    break;
                case WRAPPER:
                    // force the wrapper type
                    r = frf.getSingle();
                    break;
                case ISSET:
                    r = frf.getSinglePrimitiveAccess();
                    break;
                default:
                    throw new Error();
                }
            } else {
                r = frf.getDefault();
            }
        }
        if(opm==OptionalPropertyMode.ISSET) {
            // only isSet is allowed on a collection. these 3 modes aren't really symmetric.

            // if the property is a primitive type, we need an explicit unset because
            // we can't overload the meaning of set(null).
            // if it's a collection, we need to be able to unset it so that we can distinguish
            // null list and empty list.
            r = new IsSetFieldRenderer( r, prop.isOptionalPrimitive()||prop.isCollection(), true );
        }

        prop.realization = r;

        JType bt = getBaseType();
        if(bt!=null)
            prop.baseType = bt;

        return prop;
    }

    private CCustomizations getCustomizations( XSComponent src ) {
        return getBuilder().getBindInfo(src).toCustomizationList();
    }

    private CCustomizations getCustomizations( XSComponent... src ) {
        CCustomizations c = null;
        for (XSComponent s : src) {
            CCustomizations r = getCustomizations(s);
            if(c==null)     c = r;
            else            c = CCustomizations.merge(c,r);
        }
        return c;
    }

    private CCustomizations getCustomizations( XSAttributeUse src ) {
        // customizations for an attribute use should include those defined in the local attribute.
        // this is so that the schema like:
        //
        // <xs:attribute name="foo" type="xs:int">
        //   <xs:annotation>
        //     <hyperjaxb:... />
        //
        // would be picked up
        if(src.getDecl().isLocal())
            return getCustomizations(src,src.getDecl());
        else
            return getCustomizations((XSComponent)src);
    }

    private CCustomizations getCustomizations( XSParticle src ) {
        // customizations for a particle  should include those defined in the term unless it's global
        // this is so that the schema like:
        //
        // <xs:sequence>
        //   <xs:element name="foo" type="xs:int">
        //     <xs:annotation>
        //       <hyperjaxb:... />
        //
        // would be picked up
        if(src.getTerm().isElementDecl()) {
            XSElementDecl xed = src.getTerm().asElementDecl();
            if(xed.isGlobal())
                return getCustomizations((XSComponent)src);
        }

        return getCustomizations(src,src.getTerm());
    }



    public void markAsAcknowledged() {
        if( isAcknowledged() )  return;

        // mark the parent as well.
        super.markAsAcknowledged();

        BIProperty def = getDefault();
        if(def!=null)   def.markAsAcknowledged();
    }

    private void constantPropertyErrorCheck() {
        if( isConstantProperty!=null && getOwner()!=null ) {
            // run additional check on the isCOnstantProperty value.
            // this value is not allowed if the schema component doesn't have
            // a fixed value constraint.
            //
            // the setParent method associates a customization with the rest of
            // XSOM object graph, so this is the earliest possible moment where
            // we can test this.

            if( !hasFixedValue.find(getOwner()) ) {
                Ring.get(ErrorReceiver.class).error(
                    getLocation(),
                    Messages.ERR_ILLEGAL_FIXEDATTR.format()
                );
                // set this value to null to avoid the same error to be reported more than once.
                isConstantProperty = null;
            }
        }
    }

    /**
     * Function object that returns true if a component has
     * a fixed value constraint.
     */
    private final XSFinder hasFixedValue = new XSFinder() {
        public Boolean attributeDecl(XSAttributeDecl decl) {
            return decl.getFixedValue()!=null;
        }

        public Boolean attributeUse(XSAttributeUse use) {
            return use.getFixedValue()!=null;
        }

        public Boolean schema(XSSchema s) {
            // we allow globalBindings to have isConstantProperty==true,
            // so this method returns true to allow this.
            return true;
        }
    };

    /**
     * Finds a BIProperty which this object should delegate to.
     *
     * @return
     *      always return non-null for normal BIProperties.
     *      If this object is contained in the BIGlobalBinding, then
     *      this method returns null to indicate that there's no more default.
     */
    protected BIProperty getDefault() {
        if(getOwner()==null)    return null;
        BIProperty next = getDefault(getBuilder(),getOwner());
        if(next==this)  return null;    // global.
        else            return next;
    }

    private static BIProperty getDefault( BGMBuilder builder, XSComponent c ) {
        while(c!=null) {
            c = c.apply(defaultCustomizationFinder);
            if(c!=null) {
                BIProperty prop = builder.getBindInfo(c).get(BIProperty.class);
                if(prop!=null)  return prop;
            }
        }

        // default to the global one
        return builder.getGlobalBinding().getDefaultProperty();
    }


    /**
     * Finds a property customization that describes how the given
     * component should be mapped to a property (if it's mapped to
     * a property at all.)
     *
     * <p>
     * Consider an attribute use that does NOT carry a property
     * customization. This schema component is nonetheless considered
     * to carry a (sort of) implicit property customization, whose values
     * are defaulted.
     *
     * <p>
     * This method can be think of the method that returns this implied
     * property customization.
     *
     * <p>
     * Note that this doesn't mean the given component needs to be
     * mapped to a property. But if it does map to a property, it needs
     * to follow this customization.
     *
     * I think this semantics is next to non-sense but I couldn't think
     * of any other way to follow the spec.
     *
     * @param c
     *      A customization effective on this component will be returned.
     *      Can be null just to get the global customization.
     * @return
     *      Always return non-null valid object.
     */
    public static BIProperty getCustomization( XSComponent c ) {
        BGMBuilder builder = Ring.get(BGMBuilder.class);

        // look for a customization on this component
        if( c!=null ) {
            BIProperty prop = builder.getBindInfo(c).get(BIProperty.class);
            if(prop!=null)  return prop;
        }

        // if no such thing exists, defeault.
        return getDefault(builder,c);
    }

    private final static XSFunction<XSComponent> defaultCustomizationFinder = new XSFunction() {

        public XSComponent attributeUse(XSAttributeUse use) {
            return use.getDecl();   // inherit from the declaration
        }

        public XSComponent particle(XSParticle particle) {
            return particle.getTerm(); // inherit from the term
        }

        public XSComponent schema(XSSchema schema) {
            // no more delegation
            return null;
        }

        // delegates to the context schema object
        public XSComponent attributeDecl(XSAttributeDecl decl) { return decl.getOwnerSchema(); }
        public XSComponent wildcard(XSWildcard wc) { return wc.getOwnerSchema(); }
        public XSComponent modelGroupDecl(XSModelGroupDecl decl) { return decl.getOwnerSchema(); }
        public XSComponent modelGroup(XSModelGroup group) { return group.getOwnerSchema(); }
        public XSComponent elementDecl(XSElementDecl decl) { return decl.getOwnerSchema(); }
        public XSComponent complexType(XSComplexType type) { return type.getOwnerSchema(); }
        public XSComponent simpleType(XSSimpleType st) { return st.getOwnerSchema(); }

        // property customizations are not allowed on these components.
        public XSComponent attGroupDecl(XSAttGroupDecl decl) { throw new IllegalStateException(); }
        public XSComponent empty(XSContentType empty) { throw new IllegalStateException(); }
        public XSComponent annotation(XSAnnotation xsAnnotation) { throw new IllegalStateException(); }
        public XSComponent facet(XSFacet xsFacet) { throw new IllegalStateException(); }
        public XSComponent notation(XSNotation xsNotation) { throw new IllegalStateException(); }
        public XSComponent identityConstraint(XSIdentityConstraint x) { throw new IllegalStateException(); }
        public XSComponent xpath(XSXPath xsxPath) { throw new IllegalStateException(); }
    };


    private static String concat( String s1, String s2 ) {
        if(s1==null)    return s2;
        if(s2==null)    return s1;
        return s1+"\n\n"+s2;
    }

    public QName getName() { return NAME; }

    /** Name of this declaration. */
    public static final QName NAME = new QName(
        Const.JAXB_NSURI, "property" );

    public BIConversion getConv() {
        if(baseType!=null)
            return baseType.conv;
        else
            return null;
    }

    private static final class BaseTypeBean {
        /**
         * If there's a nested javaType customization, this field
         * will keep that customization. Otherwise null.
         *
         * This customization, if present, is used to customize
         * the simple type mapping at the point of reference.
         */
        @XmlElementRef
        BIConversion conv;

        /**
         * Java type name.
         */
        @XmlAttribute
        String name;
    }
}

Other Java examples (source code examples)

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