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

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
package org.netbeans.modules.xml.tree.nodes;

import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyChangeEvent;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeListener;

import org.xml.sax.*;  // debug

import org.openide.ErrorManager;
import org.openide.actions.*;
import org.openide.nodes.*;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataNode;
import org.openide.cookies.SaveCookie;
import org.openide.util.actions.SystemAction;
import org.openide.util.actions.NodeAction;
import org.openide.util.Utilities;
import org.openide.util.WeakListener;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.util.datatransfer.TransferListener;

import org.netbeans.tax.*;
import org.netbeans.tax.io.XMLStringResult;
import org.netbeans.modules.xml.tree.lib.OrderedBeanNode;
import org.netbeans.modules.xml.tree.lib.BeanUtil;
import org.netbeans.modules.xml.tree.lib.StringUtil;
import org.netbeans.modules.xml.tree.lib.GuiUtil;
import org.openide.util.HelpCtx;

/**
 * 
 * @author  Libor Kramolis
 * @version 0.1
 */
abstract class AbstractObjectNode extends OrderedBeanNode implements TreeObjectNode, DataNodeCookie {

    /** */
    public static final XMLDataFlavor XML_NODE_COPY_FLAVOR =
        new XMLDataFlavor (AbstractObjectNode.class, "XML_NODE_COPY_FLAVOR"); // NOI18N
    /** */
    public static final XMLDataFlavor XML_NODE_CUT_FLAVOR =
        new XMLDataFlavor (AbstractObjectNode.class, "XML_NODE_CUT_FLAVOR"); // NOI18N

    /** */
    protected static final String ICON_DIR_BASE = "org/netbeans/modules/xml/tree/resources/"; // NOI18N

    /** */
    private static final String NODE_TYPE_PREFIX_SEPARATOR = " : "; // NOI18N


    /** Data Node */
    private DataNode dataNode;

    /** */
    private final TreeListener treeListener;
    /** */
    private final DataObjectListener doListener;

    /** */
    private Image mergeIcon;

    /** */
    private short cutIconStack;

    /** Cache system actions. */
    private SystemAction[] systemActions;

    // debug
    protected static final ErrorManager clipboardEM = ErrorManager.getDefault().getInstance ("org.netbeans.modules.xml.tree.nodes:clipboard"); // NOI18N


    //
    // init
    //

    /**
     * Creates new AbstractObjectNode with particular children.
     */
    protected AbstractObjectNode (TreeObject treeObject, Children children, String iconName) throws IntrospectionException {
        super (treeObject, children);

	cutIconStack = 0;
        treeListener = new TreeListener();
        doListener = new DataObjectListener();
        
        init (treeObject);
        updateIconBase (iconName);
    }

    /**
     * Creates new AbstractObjectNode with particular children.
     */
    protected AbstractObjectNode (TreeObject treeObject, String iconName) throws IntrospectionException {
        this (treeObject, Children.LEAF, iconName);
    }

    /** 
     * Attach weak property change listener and init. 
     */
    private void init (TreeObject peer) {
        setSynchronizeName (false);

        peer.addPropertyChangeListener (WeakListener.propertyChange (treeListener, peer));

        updateNodeNames();

        if ( hasCustomizer() ) {
            setDefaultAction (SystemAction.get (CustomizeAction.class));
        }
    }


    //
    // from TreeObjectNode
    //

    
    /**
     * @return related tree object.
     */
    public final TreeObject getTreeObject () {
        return (TreeObject) getBean();
    }


    //
    // util
    //
    
    /** Is this node read only? This affect canCut, canDestroy, canRename actions.
     */
    protected boolean isReadOnly () {
        return getTreeObject().isReadOnly();
    }

    /**
     */
    protected boolean canPaste () {
        return !!! isReadOnly();
    }

    /**
     */
    protected boolean canChangeOrder () {
        return false;
    }

    /**
     */
    protected boolean canMoveUpDown () {
        return false;
    }


    //
    // Customizer
    //

    /**
     */
    public boolean hasCustomizer () {
        return !!! isReadOnly();
    }


    //
    // name support
    //

    /** Name of property used as node name. Used to listen on it.
     */
    abstract protected String getPresentableNamePropertyName ();

    /** Set new value to property which is used as node name.
     */
    abstract protected void setPresentableNameProperty (String name) throws TreeException;

    /**
     */
    private void updatePresentableNameProperty () {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::updatePresentableNameProperty: this.getName = " + getName());//, new RuntimeException()); // NOI18N

        try {
            setPresentableNameProperty (getName());
        } catch (TreeException exc) {
            updateNodeNames();
            GuiUtil.notifyTreeException (exc);
        }
    }

    
    /**
     */
    private void updateNodeNames () {
        setNameImpl (createName());
        setDisplayName (createDisplayName());
        updateShortDescription();
    }

    /**
     */
    private void updateShortDescription () {
        setShortDescription (createShortDescription());
    }
    
    /**
     * @return node name prefix or null.
     */
    protected String getNodeTypePrefix () {
        return null;
    }

    /**
     */
    protected boolean checkName (String name) {
        return true;
    }

    /**
     */
    private void setNameImpl (String name) {
        super.setName (name);

        updatePresentableNameProperty();
    }

    /**
     */
    public final void setName (String name) {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::setName: '" + name + "'"); // NOI18N

        if ( checkName (name) ) {
            setNameImpl (name);
        }
    }
    
    /**
     */
    abstract protected String createName ();

    /**
     */
    protected String createDisplayName () {
        return StringUtil.printableValue (createName());
    }

    /**
     */
    protected final String createShortDescription () {
        StringBuffer sb = new StringBuffer();
        if (getNodeTypePrefix() != null) {
            sb.append (getNodeTypePrefix()).append (NODE_TYPE_PREFIX_SEPARATOR);
        }
        sb.append (createNodePreview());
        return sb.toString();
    }

    /**
     */
    protected String createNodePreview () {
        return createDisplayName();
    }
    

    //
    // icon
    //

    /**
     */
    protected final void updateIconBase (String iconName) {
        setIconBase (ICON_DIR_BASE + iconName);
    }

    /**
     */
    protected final void setMergeIcon (Image img) {
        this.mergeIcon = img;
        fireIconChange ();
        fireOpenedIconChange ();
    }

    /**
     */
    public Image getIcon (int type) {
        Image img = super.getIcon (type);

        if ( isReadOnly() ) {
            Image roBadge = Utilities.loadImage ("org/netbeans/modules/xml/tree/resources/ro-badge.gif"); // NOI18N
            img = Utilities.mergeImages (img, roBadge, 16, 8);
        }

        if ( mergeIcon == null ) {
            return img;
        }

        return Utilities.mergeImages (img, mergeIcon, 0, 0);
    }

    /**
     */
    protected final void activateCutIcon () {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::activateCutIcon: cutIconStack = " + cutIconStack + " ++"); // NOI18N
	
	if ( cutIconStack == 0 ) {
	    setMergeIcon (GuiUtil.getCutBadgeIcon());
	}
	cutIconStack++;
    }

    /**
     */
    protected final void deactivateCutIcon () {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::DEactivateCutIcon: cutIconStack = " + cutIconStack + " --"); // NOI18N
	
	cutIconStack--;
	if ( cutIconStack == 0 ) {
	    setMergeIcon (null);
	}
    }

    
    public HelpCtx getHelpCtx() {
        return new HelpCtx (this.getClass());
    }

    //
    // actions
    //


    /** Node impl can add its specific actions.
     * @return node specific actions or null.
     */
    protected SystemAction[] createNodeSpecificActions () {
        return null;
    }

    /**
     */
    protected final SystemAction[] createActions() {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::createActions: this = " + this); // NOI18N

        if ( systemActions == null ) {

            List nodeActions = new LinkedList();
            boolean separator = false;

            // new actions
            if ( (getNewTypes() != null) && (getNewTypes().length != 0) ) {
                nodeActions.add (SystemAction.get (NewAction.class));
                nodeActions.add (null);
            }

            // node specific actions
            SystemAction[] nodeSpecificActions = createNodeSpecificActions();
            if (nodeSpecificActions != null) {
                nodeActions.addAll (Arrays.asList (nodeSpecificActions));
                nodeActions.add (null);
            }

            // change order actions
            if ( canChangeOrder() ) {
                nodeActions.add (SystemAction.get (ReorderAction.class));
                separator = true;
            }
            if ( canMoveUpDown() ) {
                nodeActions.add (SystemAction.get (MoveUpAction.class));
                nodeActions.add (SystemAction.get (MoveDownAction.class));
                separator = true;
            }
            if ( separator ) {
                nodeActions.add (null);
                separator = false;
            }

            // clipboard actions
            if ( canCut() ) {
                nodeActions.add (SystemAction.get (CutAction.class));
                separator = true;
            }
            if ( canCopy() ) {
                nodeActions.add (SystemAction.get (CopyAction.class));
                separator = true;
            }
            if ( ( !!! isLeaf()  ) && canPaste() ) {
                nodeActions.add (SystemAction.get (PasteAction.class));
                separator = true;
            }
            if ( separator ) {
                nodeActions.add (null);
                separator = false;
            }

            // delete, rename actions
            if ( canDestroy() ) {
                nodeActions.add (SystemAction.get (DeleteAction.class));
                separator = true;
            }
            if ( canRename() ) {
                nodeActions.add (SystemAction.get (RenameAction.class));
                separator = true;
            }
            if ( separator ) {
                nodeActions.add (null);
                separator = false;
            }

            // properties action
            nodeActions.add (SystemAction.get (PropertiesAction.class));

            // 
            if ( Util.THIS.isLoggable() ) {
                nodeActions.add (SystemAction.get (ToolsAction.class));

                nodeActions.add( new GotoAction() {
                        public void performAction(Node[] activatedNodes) {
                            Object peer = getBean();
                            
                            Util.THIS.debug("Dumping " + AbstractObjectNode.this.getDisplayName() + " /" + AbstractObjectNode.this.getName() + "/"); // NOI18N
                            Util.THIS.debug("\tTree model listener " + treeListener); // NOI18N
                            Util.THIS.debug("\tThis node listener " + doListener); // NOI18N
                            Util.THIS.debug("\tRaw peer " + peer.getClass() + System.identityHashCode(peer)); // NOI18N
                            Util.THIS.debug("\tString peer " + peer.toString()); // NOI18N
                            if (peer instanceof TreeNode) {
                                Object parent = ((TreeNode)peer).getOwnerDocument();
                                if (parent == null) {
                                    Util.THIS.debug("\tMISSING OWNER DOCUMENT!"); // NOI18N
                                    Util.THIS.debug("\tTraversing parents:"); // NOI18N
                                    for(TreeParentNode p = ((TreeChild)peer).getParentNode(); p != null; p = p.getParentNode() ) {
                                        Util.THIS.debug("\t\tRaw parent " + p.getClass() + System.identityHashCode(p)); // NOI18N
                                    }
                                } else {
                                    Util.THIS.debug("\tRaw document " + parent.getClass() + System.identityHashCode(parent)); // NOI18N
                                    Util.THIS.debug("\tDocument listeners: " + ((TreeObject)parent).listListeners()); // NOI18N
                                }
                        
                                parent = ((TreeChild)peer).getParentNode();
                                if (parent == null) {
                                    Util.THIS.debug("\tMISSING PARENT DOCUMENT!"); // NOI18N
                                } else {
                                    Util.THIS.debug("\tRaw parent " + parent.getClass() + System.identityHashCode(parent)); // NOI18N
                                    Util.THIS.debug("\tChildren: "); // NOI18N
                                    for (int max = ((TreeParentNode)parent).getChildrenNumber(), i = 0; i is 'addPropertyChangeListener' public: " + java.lang.reflect.Modifier.isPublic (method.getModifiers())); // NOI18N
                            } catch (Exception exc) {
                                Util.THIS.debug (exc);
                            }
                        }
                
                        public boolean enable(Node[] nodes) {
                            return true;
                        }
                    });
            
            }
            // 

            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("                  ::createActions: nodeActions = " + nodeActions); // NOI18N
        
            systemActions = (SystemAction[])nodeActions.toArray (new SystemAction[0]);
        }

        return systemActions;
    }


    //
    // DataNode caching
    //

    /** 
     * Determine associated DataNode/Object at runtime.
     * It would cache the DataNode. 
     * Cache results in dataNode field because of expensivity.
     * @return first adjacent DataNode or null if can not be found
     */
    public final DataNode getDataNode () {
        String thisClassName = this.getClass().getName();

        if (dataNode != null) {
            return dataNode; // cache hit
        }

        // try to populate cache
        Node parent = getParentNode();

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("                    [ " + thisClassName + " ] ::getDataNode: parent = " + parent); // NOI18N

        if (parent != null) {
            DataNodeCookie cake = (DataNodeCookie) parent.getCookie (DataNodeCookie.class);

            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("                    [ " + thisClassName + " ] ::getDataNode: parent's DataNodeCookie = " + cake); // NOI18N
            
            if (cake != null) {
                setDataNode (cake.getDataNode());
            }
        }

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("                    [ " + thisClassName + " ] ::getDataNode: dataNode = " + dataNode); // NOI18N

        return dataNode;
    }

    /** 
     */
    protected void setDataNode (DataNode dataNode) {
        this.dataNode = dataNode;
        addDataObjectListener();
    }


    //
    // cookie
    //


    /**
     */
    private void addDataObjectListener () {
        if (dataNode != null) {
            DataObject dataObject = dataNode.getDataObject();
            dataObject.addPropertyChangeListener (WeakListener.propertyChange (doListener, dataObject));
        }
    }


    protected void superFireCookieChange () {
        super.fireCookieChange();
    }

    /**      
     * Delegate DataObjectCookies to DataNode.
     * Add itself into cookie set as DataNodeCookie. 
     */
    public final Cookie getCookie (Class type) {
        if (type == SaveCookie.class || DataObject.class == type) {
            DataNode node = getDataNode();
            if (node != null)
                return node.getCookie (type);
        }

        // go over FilterNodes
        if (DataNodeCookie.class == type) {
            return this;
        }

        return super.getCookie (type);
    }


    //
    // node operation
    //

    /**
     */
    public boolean canDestroy () {
        boolean canDestroy = (!!! isReadOnly());

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::canDestroy : " + canDestroy); // NOI18N

        return canDestroy;
    }    

    /**
     */
    public void destroy () throws IOException {
	try {
            getTreeObject().removeFromContext();
//              super.destroy(); //!!! -- why yes / why not
	} catch (ReadOnlyException exc) {
	    GuiUtil.notifyTreeException (exc); //!!! -- what about declared IOException?
	}
    }


    /**
     */
    public boolean canRename () {
        boolean canRename = (!!! isReadOnly());

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::canRename : " + canRename); // NOI18N

        return canRename;
    }    


    //
    // BeanNode
    //
    
    /**
     */
    protected void createProperties (Object bean, BeanInfo info) {
        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::createProperties: bean = " + bean); // NOI18N

        if ( bean instanceof TreeObject ) {
            Node.Property[][] properties = BeanUtil.computeProperties ((TreeObject)bean, info);
            
            Sheet sets = getSheet ();
            Sheet.Set pset = Sheet.createPropertiesSet ();
            pset.put (properties[0]);
            
            sets.put (pset);

            if (properties[1].length != 0) {
                Sheet.Set eset = Sheet.createExpertSet ();
                eset.put (properties[1]);
                
                sets.put (eset);
            }
        } else {
            super.createProperties (bean, info);
        }
    }




    //
    // Clipboard operations
    //

    /**
     */
    public boolean canCopy () {
        boolean canCopy = true;

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::canCopy : " + canCopy); // NOI18N

        return canCopy;
    }

    /**
     */
    public boolean canCut () {
        boolean canCut = (!!! isReadOnly());

        if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::canCut : " + canCut); // NOI18N

        return canCut;
    }

    /**
     */
    public final Transferable clipboardCopy () throws IOException {
        // debug
        clipboardEM.log ("Clipboard copy: " + this.getDisplayName());

        TreeObject clone = (TreeObject)getTreeObject().clone();

        ExTransferable transferable = ExTransferable.create (new XMLTransferable (XML_NODE_COPY_FLAVOR, clone));

        // debug
        clipboardEM.log ("Copy transferable [ " + System.identityHashCode (transferable) + " ] = " + transferable);

        return transferable;
    }

    /**
     */
    public final Transferable clipboardCut () throws IOException {
        // debug
        clipboardEM.log ("Clipboard cut: " + this.getDisplayName());

        activateCutIcon();
        ExTransferable transferable = ExTransferable.create (new XMLTransferable (XML_NODE_CUT_FLAVOR, getTreeObject()));

        // listen on ownershipLost and deactivate cut icon
        transferable.addTransferListener (new TransferListener () {
                public void accepted (int action) {
                    // debug
                    clipboardEM.log ("Clipboard accepted [" + action + "]: " + AbstractObjectNode.this.getDisplayName());
                }
                public void rejected () {
                    // debug
                    clipboardEM.log ("Clipboard rejected: " + AbstractObjectNode.this.getDisplayName());
                }
                public void ownershipLost () {
                    // debug
                    clipboardEM.log ("Clipboard ownershipLost: " + AbstractObjectNode.this.getDisplayName());

		    AbstractObjectNode.this.deactivateCutIcon();
                }
            });


        // debug
        clipboardEM.log ("Cut transferable [ " + System.identityHashCode (transferable) + " ] = " + transferable);

        return transferable;
    }


    //
    // class XMLDataFlavor
    //

    /**
     *
     */
    public static class XMLDataFlavor extends DataFlavor {
        /** Serial Version UID */
        private static final long serialVersionUID = 2137534426987675449L;
        
        //
        // init
        //

        /** */
        XMLDataFlavor (Class representationClass, String name) {
            super (representationClass, name);
        }

        
        //
        // itself
        //

        /**
         */
        public boolean equals (Object obj) {
            if ( (obj instanceof XMLDataFlavor) == false )
                return false;

            XMLDataFlavor xmlDF = (XMLDataFlavor)obj;
            return ( this.getHumanPresentableName().equals (xmlDF.getHumanPresentableName())
                     && super.equals (obj) );
        }

    } // end: class XMLDataFlavor

    
    //
    // class XMLTransferable
    //

    /**
     *
     */
    static class XMLTransferable implements Transferable {
        /** */
        private TreeObject treeObject;
        /** */
        private XMLDataFlavor xmlFlavor;

        //
        // init
        //


        /** */
        public XMLTransferable (XMLDataFlavor flavor, TreeObject treeObject) {
            this.xmlFlavor  = flavor;
            this.treeObject = treeObject;
        }


        //
        // from Transferable
        //

        /**
         */
        public DataFlavor[] getTransferDataFlavors() {
            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::XMLTransferable::getTransferDataFlavors: xmlFlavor = " + xmlFlavor); // NOI18N

            return new DataFlavor[] { xmlFlavor, DataFlavor.stringFlavor }; // XMLDataFlavor + String flavor
        }

        /**
         */
        public boolean isDataFlavorSupported (DataFlavor flavor) {
            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::XMLTransferable::isDataFlavorSupported: flavor = " + flavor); // NOI18N

            if ( DataFlavor.stringFlavor.equals (flavor) ) { // String flavor
                if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::XMLTransferable::isDataFlavorSupported: is stringFlavor"); // NOI18N

                return true;
            }

            if (xmlFlavor.equals ((Object)flavor)) {
                if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::XMLTransferable::isDataFlavorSupported: is XMLDataFlavor"); // NOI18N

                return true;
            }

            return false;
        }

        /**
         */
        public Object getTransferData (DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if ( isDataFlavorSupported (flavor) ) {

                if (DataFlavor.stringFlavor == flavor) { // String flavor
                    try {
                        if ( treeObject instanceof TreeNode ) {
                            return XMLStringResult.toString ((TreeNode)treeObject);
                        } else {
                            return ""; //??? -- what to do? // NOI18N
                        }
                    } catch (Exception exc) {
                        GuiUtil.notifyException (exc);
                    }
                } else if (xmlFlavor == flavor) {//flavor instanceof XMLDataFlavor) {
                    return treeObject;
                }

            }

            throw new UnsupportedFlavorException (flavor);
        }

    } // end: class XMLTransferable



    //
    // class TreeListener
    //

    /**
     * class TreeListener listen on
     */
    private class TreeListener implements PropertyChangeListener {

        /** */
        public void propertyChange (PropertyChangeEvent pche) {
            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::TreeListener::propertyChange: propertyName = '" + pche.getPropertyName() + "'"); // NOI18N

            if (pche.getPropertyName().equals (AbstractObjectNode.this.getPresentableNamePropertyName())) {
                //[WARNING]: AbstractObjectNode.this.getPresentableNamePropertyName() can return null
                AbstractObjectNode.this.updateNodeNames();
            } else if (TreeNode.PROP_NODE.equals (pche.getPropertyName())) {
                AbstractObjectNode.this.updateShortDescription();
            }
        }

    } // end: class TreeListener



    //
    // class DataObjectListener
    //

    /**
     * class DataObjectListener listen on
     */
    private class DataObjectListener implements PropertyChangeListener {

        /** */
        public void propertyChange (PropertyChangeEvent pche) {
            if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("AbstractObjectNode::DataObjectListener::propertyChange: propertyName = '" + pche.getPropertyName() + "'"); // NOI18N

            if ( Node.PROP_COOKIE.equals (pche.getPropertyName()) ) {
                AbstractObjectNode.this.superFireCookieChange();
            }
        }

    } // end: class DataObjectListener

}
... 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.