blob: 7d48d65b6e8a5c9742061712b14f5ae766500c3d [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package groovy.util
import java.beans.Introspector
import java.beans.BeanInfo
/**
* <p>
* ConfigSlurper is a utility class for reading configuration files defined in the form of Groovy
* scripts. Configuration settings can be defined using dot notation or scoped using closures
*
* <pre><code>
* grails.webflow.stateless = true
* smtp {
* mail.host = 'smtp.myisp.com'
* mail.auth.user = 'server'
* }
* resources.URL = "http://localhost:80/resources"
* </pre></code>
*
* <p>Settings can either be bound into nested maps or onto a specified JavaBean instance. In the case
* of the latter an error will be thrown if a property cannot be bound.
*
* @author Graeme Rocher
* @since 1.1
*
* <p/>
* Created: Jun 19, 2007
* Time: 3:53:48 PM
*/
class ConfigSlurper {
private static final ENV_METHOD = "environments"
static final ENV_SETTINGS = '__env_settings__'
//private BeanInfo bean
//private instance
GroovyClassLoader classLoader = new GroovyClassLoader()
String environment
private envMode = false
private Map bindingVars
ConfigSlurper() { }
/**
* Constructs a new ConfigSlurper instance using the given environment
* @param env The Environment to use
*/
ConfigSlurper(String env) {
this.environment = env
}
/**
* Sets any additional variables that should be placed into the binding when evaluating Config scripts
*/
void setBinding(Map vars) {
this.bindingVars = vars
}
/**
* Parses a ConfigObject instances from an instance of java.util.Properties
* @param The java.util.Properties instance
*/
ConfigObject parse(Properties properties) {
ConfigObject config = new ConfigObject()
for(key in properties.keySet()) {
def tokens = key.split(/\./)
def current = config
def currentToken
def last
def lastToken
def foundBase = false
for(token in tokens) {
if (foundBase) {
// handle not properly nested tokens by ignoring
// hierarchy below this point
lastToken += "." + token
current = last
} else {
last = current
lastToken = token
current = current."${token}"
if(!(current instanceof ConfigObject)) foundBase = true
}
}
if(current instanceof ConfigObject) {
if(last[lastToken]) {
def flattened = last.flatten()
last.clear()
flattened.each { k2, v2 -> last[k2] = v2 }
last[lastToken] = properties.get(key)
}
else {
last[lastToken] = properties.get(key)
}
}
current = config
}
return config
}
/**
* Parse the given script as a string and return the configuration object
*
* @see ConfigSlurper#parse(groovy.lang.Script)
*/
ConfigObject parse(String script) {
return parse(classLoader.parseClass(script))
}
/**
* Create a new instance of the given script class and parse a configuration object from it
*
* @see ConfigSlurper#parse(groovy.lang.Script)
*/
ConfigObject parse(Class scriptClass) {
return parse(scriptClass.newInstance())
}
/**
* Parse the given script into a configuration object (a Map)
* @param script The script to parse
* @return A Map of maps that can be navigating with dot de-referencing syntax to obtain configuration entries
*/
ConfigObject parse(Script script) {
return parse(script, null)
}
/**
* Parses a Script represented by the given URL into a ConfigObject
*
* @param scriptLocation The location of the script to parse
* @return The ConfigObject instance
*/
ConfigObject parse(URL scriptLocation) {
return parse(classLoader.parseClass(scriptLocation.text).newInstance(), scriptLocation)
}
/**
* Parses the passed groovy.lang.Script instance using the second argument to allow the ConfigObject
* to retain an reference to the original location other Groovy script
*
* @param script The groovy.lang.Script instance
* @param location The original location of the Script as a URL
* @return The ConfigObject instance
*/
ConfigObject parse(Script script, URL location) {
def config = location ? new ConfigObject(location) : new ConfigObject()
GroovySystem.metaClassRegistry.removeMetaClass(script.class)
def mc = script.class.metaClass
def prefix = ""
LinkedList stack = new LinkedList()
stack << [config:config,scope:[:]]
def pushStack = { co ->
stack << [config:co,scope:stack.last.scope.clone()]
}
def assignName = { name, co ->
def current = stack.last
current.config[name] = co
current.scope[name] = co
}
mc.getProperty = { String name ->
def current = stack.last
def result
if(current.config.get(name)) {
result = current.config.get(name)
} else if(current.scope[name]) {
result = current.scope[name]
} else {
result = new ConfigObject()
assignName.call(name,result)
}
result
}
mc.invokeMethod = { String name, args ->
def result
if(args.length == 1 && args[0] instanceof Closure) {
if(name == ENV_METHOD) {
try {
envMode = true
args[0].call()
} finally {
envMode = false
}
} else if (envMode) {
if(name == environment) {
def co = new ConfigObject()
config[ENV_SETTINGS] = co
pushStack.call(co)
try {
envMode = false
args[0].call()
} finally {
envMode = true
}
stack.pop()
}
} else {
def co = new ConfigObject()
assignName.call(name, co)
pushStack.call(co)
args[0].call()
stack.pop()
}
} else if (args.length == 2 && args[1] instanceof Closure) {
try {
prefix = name +'.'
assignName.call(name, args[0])
args[1].call()
} finally { prefix = "" }
} else {
MetaMethod mm = mc.getMetaMethod(name, args)
if(mm) {
result = mm.invoke(delegate, args)
} else {
throw new MissingMethodException(name, getClass(), args)
}
}
result
}
script.metaClass = mc
def setProperty = { String name, value ->
assignName.call(prefix+name, value)
}
def binding = new ConfigBinding(setProperty)
if(this.bindingVars) {
binding.getVariables().putAll(this.bindingVars)
}
script.binding = binding
script.run()
def envSettings = config.remove(ENV_SETTINGS)
if(envSettings) {
config.merge(envSettings)
}
return config
}
}
/**
* Since Groovy Script don't support overriding setProperty, we have to using a trick with the Binding to provide this
* functionality
*/
class ConfigBinding extends Binding {
def callable
ConfigBinding(Closure c) {
this.callable = c
}
void setVariable(String name, Object value) {
callable(name, value)
}
}
-----
[Groovy script]
[Package definition]
[Modifiers]
[Import statement]
[Modifiers]
[Import statement]
[Modifiers]
[Class definition : ConfigSlurper]
[Modifiers]
[Extends clause]
[Implements clause]
[Type definition body]
[Variable definitions]
[Modifiers]
[Field : ENV_METHOD]
[Variable definitions]
[Modifiers]
[Field : ENV_SETTINGS]
[Variable definitions]
[Modifiers]
[Field : classLoader]
[Variable definitions]
[Modifiers]
[Field : environment]
[Variable definitions]
[Modifiers]
[Field : envMode]
[Variable definitions]
[Modifiers]
[Field : bindingVars]
[Constructor : ConfigSlurper]
[Modifiers]
[Parameter list]
[Constructor : ConfigSlurper]
[Modifiers]
[Parameter list]
[Parameter : env]
[Modifiers]
[Method : setBinding]
[Modifiers]
[Parameter list]
[Parameter : vars]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : properties]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter : key]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter : token]
[Modifiers]
[Parameter list]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : k2]
[Modifiers]
[Parameter : v2]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : script]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : scriptClass]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : script]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : scriptLocation]
[Modifiers]
[Method : parse]
[Modifiers]
[Parameter list]
[Parameter : script]
[Modifiers]
[Parameter : location]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : co]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : name]
[Modifiers]
[Parameter : co]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : name]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : name]
[Modifiers]
[Parameter : args]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Parameter list]
[Parameter : name]
[Modifiers]
[Parameter : value]
[Modifiers]
[Variable definitions]
[Modifiers]
[Variable definitions]
[Modifiers]
[Class definition : ConfigBinding]
[Modifiers]
[Extends clause]
[Implements clause]
[Type definition body]
[Variable definitions]
[Modifiers]
[Field : callable]
[Constructor : ConfigBinding]
[Modifiers]
[Parameter list]
[Parameter : c]
[Modifiers]
[Method : setVariable]
[Modifiers]
[Parameter list]
[Parameter : name]
[Modifiers]
[Parameter : value]
[Modifiers]