Skip to content

Instantly share code, notes, and snippets.

@smaldini
Created January 21, 2013 16:59
Show Gist options
  • Save smaldini/4587451 to your computer and use it in GitHub Desktop.
Save smaldini/4587451 to your computer and use it in GitHub Desktop.
/*
* Copyright 2003-2009 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 org.codehaus.groovy.syntax.Types
/**
* A ConfigObject at a simple level is a Map that creates configuration entries (other ConfigObjects) when referencing them.
* This means that navigating to foo.bar.stuff will not return null but nested ConfigObjects which are of course empty maps
* The Groovy truth can be used to check for the existence of "real" entries.
*
* @author Graeme Rocher
* @since 1.5
*/
class ConfigObject extends LinkedHashMap implements Writable {
static final KEYWORDS = Types.getKeywords()
static final TAB_CHARACTER = '\t'
/**
* The config file that was used when parsing this ConfigObject
*/
URL configFile
ConfigObject(URL file) {
this.configFile = file
}
ConfigObject() {}
/**
* Writes this config object into a String serialized representation which can later be parsed back using the parse()
* method
*
* @see groovy.lang.Writable#writeTo(java.io.Writer)
*/
Writer writeTo(Writer outArg) {
def out
try {
out = new BufferedWriter(outArg)
writeConfig("",this, out, 0, false)
} finally {
out.flush()
}
return outArg
}
/**
* Overrides the default getProperty implementation to create nested ConfigObject instances on demand
* for non-existent keys
*/
def getProperty(String name) {
if(name == 'configFile') return this.configFile
if(!containsKey (name)) {
ConfigObject prop = new ConfigObject(this.configFile)
put(name, prop)
return prop
}
return get(name)
}
/**
* A ConfigObject is a tree structure consisting of nested maps. This flattens the maps into
* a single level structure like a properties file
*/
Map flatten() {
return flatten(null)
}
/**
* Flattens this ConfigObject populating the results into the target Map
*
* @see ConfigObject#flatten()
*/
Map flatten(Map target) {
if(target == null)target = new ConfigObject()
populate("", target, this)
target
}
/**
* Merges the given map with this ConfigObject overriding any matching configuration entries in this ConfigObject
*
* @param other The ConfigObject to merge with
* @return The result of the merge
*/
Map merge(ConfigObject other) {
return doMerge(this,other)
}
/**
* Converts this ConfigObject into a the java.util.Properties format, flattening the tree structure beforehand
* @return A java.util.Properties instance
*/
Properties toProperties() {
def props = new Properties()
flatten(props)
props = convertValuesToString(props)
return props
}
/**
* Converts this ConfigObject ino the java.util.Properties format, flatten the tree and prefixing all entries with the given prefix
* @param prefix The prefix to append before property entries
* @return A java.util.Properties instance
*/
Properties toProperties(String prefix) {
def props = new Properties()
populate("${prefix}.", props, this)
props = convertValuesToString(props)
return props
}
private doMerge(Map config, Map other) {
for(entry in other) {
def configEntry = config[entry.key]
if(configEntry == null) {
config[entry.key] = entry.value
continue
}
else {
if(configEntry instanceof Map && configEntry.size() > 0 && entry.value instanceof Map) {
// recur
doMerge(configEntry, entry.value)
}
else {
config[entry.key] = entry.value
}
}
}
return config
}
private writeConfig(String prefix,ConfigObject map, out, Integer tab, boolean apply) {
def space = apply ? TAB_CHARACTER*tab : ''
for(key in map.keySet()) {
def value = map.get(key)
if(value instanceof ConfigObject) {
if(!value.isEmpty()) {
def dotsInKeys = value.find { entry -> entry.key.indexOf('.') > -1 }
def configSize = value.size()
def firstKey = value.keySet().iterator().next()
def firstValue = value.values().iterator().next()
def firstSize
if(firstValue instanceof ConfigObject){
firstSize = firstValue.size()
}
else { firstSize = 1 }
if(configSize == 1|| dotsInKeys ) {
if(firstSize == 1 && firstValue instanceof ConfigObject) {
key = KEYWORDS.contains(key) ? key.inspect() : key
def writePrefix = "${prefix}${key}.${firstKey}."
writeConfig(writePrefix, firstValue, out, tab, true)
}
else if(!dotsInKeys && firstValue instanceof ConfigObject) {
writeNode(key, space, tab,value, out)
} else {
for(j in value.keySet()) {
def v2 = value.get(j)
def k2 = j.indexOf('.') > -1 ? j.inspect() : j
if(v2 instanceof ConfigObject) {
key = KEYWORDS.contains(key) ? key.inspect() : key
writeConfig("${prefix}${key}", v2, out, tab, false)
}
else {
writeValue("${key}.${k2}", space, prefix, v2, out)
}
}
}
}
else {
writeNode(key, space,tab, value, out)
}
}
}
else {
writeValue(key, space, prefix, value, out)
}
}
}
private writeValue(key, space, prefix, value, out) {
key = key.indexOf('.') > -1 ? key.inspect() : key
boolean isKeyword = KEYWORDS.contains(key)
key = isKeyword ? key.inspect() : key
if(!prefix && isKeyword) prefix = "this."
out << "${space}${prefix}$key=${value.inspect()}"
out.newLine()
}
private writeNode(key, space, tab, value, out) {
key = KEYWORDS.contains(key) ? key.inspect() : key
out << "${space}$key {"
out.newLine()
writeConfig("",value, out, tab+1, true)
def last = "${space}}"
out << last
out.newLine()
}
private convertValuesToString(props) {
def newProps = [:]
for(e in props) {
newProps[e.key] = e.value?.toString()
}
return newProps
}
private populate(suffix, config, map) {
for(key in map.keySet()) {
def value = map.get(key)
if(value instanceof Map) {
populate(suffix+"${key}.", config, value)
}
else {
try {
config[suffix+key] = value
}
catch (java.lang.NullPointerException e) {
// it is idiotic story but if config map doesn't allow null values (like Hashtable)
// we can't do too much
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment