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