    001    /*****************************************************************************
    002     * Copyright (C) NanoContainer Organization. All rights reserved.            *
    003     * ------------------------------------------------------------------------- *
    004     * The software in this package is published under the terms of the BSD      *
    005     * style license a copy of which has been included with this distribution in *
    006     * the LICENSE.txt file.                                                     *
    007     *                                                                           *
    008     * Original code by James Strachan                                           *
    009     *****************************************************************************/
    010
    011    package org.nanocontainer.script.groovy.buildernodes;
    012
    013    import java.io.Serializable;
    014    import java.util.Collections;
    015    import java.util.HashSet;
    016    import java.util.Set;
    017
    018    import org.nanocontainer.script.groovy.BuilderNode;
    019    import java.util.Map;
    020    import org.nanocontainer.script.NanoContainerMarkupException;
    021    import java.util.Iterator;
    022
    023    /**
    024     * Abstract base class for custom nodes.  Also provides basic services and
    025     * construction capabilities.
    026     * @author James Strachan
    027     * @author Paul Hammant
    028     * @author Aslak Helles&oslash;y
    029     * @author Michael Rimov
    030     * @author Mauro Talevi
    031     * @version $Revision: 2443 $
    032     */
    033    abstract public class AbstractBuilderNode implements BuilderNode, Serializable {
    034
    035        /**
    036         * The name of the node we're working with.
    037         */
    038        private final String nodeName;
    039
    040
    041        /**
    042         * A set of all possible supported attribute names.
    043         */
    044        private Set supportedAttributes = new HashSet();
    045
    046
    047
    048        /**
    049         * Constructs a custom node builder.  In derived classes you would
    050         * typically create a default constructor and call addPossibleParent()/addAttribute()
    051         * to customize the validation capabilities of the Node.
    052         * @param nodeName the name of the node we're constructing.
    053         */
    054        public AbstractBuilderNode(final String nodeName) {
    055            this.nodeName = nodeName;
    056
    057        }
    058
    059
    060        /**
    061         * Add an attribute to the list of ones supported by this node.
    062         * @param name String the name of the attribute we support.
    063         * @return AbstractBuilderNode (this) to allow for method chaining.
    064         */
    065        protected AbstractBuilderNode addAttribute(final String name) {
    066            supportedAttributes.add(name);
    067            return this;
    068        }
    069
    070
    071        public String getNodeName() {
    072            return nodeName;
    073        }
    074
    075
    076        public Set getSupportedAttributes() {
    077            return Collections.unmodifiableSet(supportedAttributes);
    078        }
    079
    080        public String toString() {
    081            return "Nanocontainer Builder Node: " + this.getClass().getName() + " (\"" + getNodeName() + "\")";
    082        }
    083
    084        /**
    085         * Checks that an attribute actually exists in the attirbute map. (The key
    086         * exists and the value is non-null)
    087         * @param attributes Map the current node's attributes.
    088         * @param key String the attribute key we're looking for.
    089         * @return boolean true if the attribute exists for the current node.
    090         */
    091        protected boolean isAttribute(final Map attributes, final String key) {
    092            return attributes.containsKey(key) && attributes.get(key) != null;
    093        }
    094
    095        /**
    096         * {@inheritDoc}
    097         * <p>This particular implementation checks all specified attribute keynames
    098         * against the names supported in the node type.  It does not type checking
    099         * against the values passed in via the attributes.</p>
    100         * @param specifiedAttributes the attributes as passed in by the groovy
    101         * script.
    102         * @throws NanoContainerMarkupException if an attribute is specified that
    103         * is not recognized.
    104         */
    105        public void validateScriptedAttributes(final Map specifiedAttributes) throws NanoContainerMarkupException {
    106            Set specifiedAttributeNames = specifiedAttributes.keySet();
    107            if (this.getSupportedAttributes().containsAll(specifiedAttributeNames)) {
    108                return;
    109            }
    110
    111            Set unknownAttributes = new HashSet(specifiedAttributeNames);
    112            unknownAttributes.removeAll(this.getSupportedAttributes());
    113
    114            StringBuffer errorMessage = new StringBuffer();
    115            errorMessage.append("Found one or more unknown attributes for builder node '");
    116            errorMessage.append(this.getNodeName());
    117            errorMessage.append("': ");
    118            errorMessage.append(convertSetToCommaDelimitedString(unknownAttributes));
    119            errorMessage.append(".  Recognized Attributes For this node are [");
    120            errorMessage.append(convertSetToCommaDelimitedString(this.getSupportedAttributes()));
    121            errorMessage.append("].");
    122
    123            throw new NanoContainerMarkupException(errorMessage.toString());
    124        }
    125
    126        /**
    127         * Utility function that takes a set and converts it to a comma delimited
    128         * String with the format:  key1, key2,.....
    129         * @param specifiedSet Set the set to convert.  For each object in the set,
    130         * its toString() is called.
    131         *
    132         * @return String
    133         */
    134        private String convertSetToCommaDelimitedString(final Set specifiedSet) {
    135
    136            StringBuffer result = new StringBuffer();
    137
    138            boolean needComma = false;
    139            for (Iterator i = specifiedSet.iterator(); i.hasNext();) {
    140                if (needComma) {
    141                    result.append(",");
    142                } else {
    143                    needComma = true;
    144                }
    145
    146                result.append(i.next().toString());
    147            }
    148            return result.toString();
    149        }
    150
    151    }