Created
January 21, 2013 16:59
-
-
Save smaldini/4587451 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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