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