| /* Copyright 2006-2007 Graeme Rocher |
| * |
| * 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 |
| |
| |
| /** |
| * 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 existance of "real" entries. |
| * |
| * @author Graeme Rocher |
| * @since 1.1 |
| */ |
| class ConfigObject extends LinkedHashMap implements Writable { |
| |
| // would be better to have these availabe as constants from Groovy, but couldn't find |
| static final KEYWORDS = ['class', 'extends', 'implements', 'package','return','def', |
| 'try','finally','this','new','catch','switch','case','default','while','if', |
| 'else','elseif','private','protected','final','for','in','byte','short','break', |
| 'instanceof','synchronized','const','float','null','throws','do','super','with', |
| 'threadsafe','transient','native','interface','any','double','volatile','as', |
| 'assert','goto','enum','int','boolean','char','false','true','static','abstract', |
| 'continue','import','void','long'] |
| |
| 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) |
| * @see #parse(URL) |
| */ |
| 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-existant 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 merge(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 merge(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) { |
| // recurse |
| merge(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) { |
| 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 |
| } |
| } |
| } |
| } |
| } |
| ----- |
| [Groovy script] |
| [Package definition] |
| [Modifiers] |
| [Class definition : ConfigObject] |
| [Modifiers] |
| [Extends clause] |
| [Implements clause] |
| [Type definition body] |
| [Variable definitions] |
| [Modifiers] |
| [Field : KEYWORDS] |
| [Variable definitions] |
| [Modifiers] |
| [Field : TAB_CHARACTER] |
| [Variable definitions] |
| [Modifiers] |
| [Field : configFile] |
| [Constructor : ConfigObject] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : file] |
| [Modifiers] |
| [Constructor : ConfigObject] |
| [Modifiers] |
| [Parameter list] |
| [Method : writeTo] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : outArg] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Method : getProperty] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : name] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Method : flatten] |
| [Modifiers] |
| [Parameter list] |
| [Method : flatten] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : target] |
| [Modifiers] |
| [Method : merge] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : other] |
| [Modifiers] |
| [Method : toProperties] |
| [Modifiers] |
| [Parameter list] |
| [Variable definitions] |
| [Modifiers] |
| [Method : toProperties] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : prefix] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Method : merge] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : config] |
| [Modifiers] |
| [Parameter : other] |
| [Modifiers] |
| [Parameter : entry] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Method : writeConfig] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : prefix] |
| [Modifiers] |
| [Parameter : map] |
| [Modifiers] |
| [Parameter : out] |
| [Modifiers] |
| [Parameter : tab] |
| [Modifiers] |
| [Parameter : apply] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter : key] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : entry] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Parameter list] |
| [Parameter list] |
| [Parameter : j] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Parameter list] |
| [Parameter list] |
| [Parameter list] |
| [Method : writeValue] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : key] |
| [Modifiers] |
| [Parameter : space] |
| [Modifiers] |
| [Parameter : prefix] |
| [Modifiers] |
| [Parameter : value] |
| [Modifiers] |
| [Parameter : out] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Parameter list] |
| [Parameter list] |
| [Method : writeNode] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : key] |
| [Modifiers] |
| [Parameter : space] |
| [Modifiers] |
| [Parameter : tab] |
| [Modifiers] |
| [Parameter : value] |
| [Modifiers] |
| [Parameter : out] |
| [Modifiers] |
| [Parameter list] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Method : convertValuesToString] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : props] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter : e] |
| [Modifiers] |
| [Method : populate] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : suffix] |
| [Modifiers] |
| [Parameter : config] |
| [Modifiers] |
| [Parameter : map] |
| [Modifiers] |
| [Parameter : key] |
| [Modifiers] |
| [Variable definitions] |
| [Modifiers] |
| [Parameter list] |
| [Parameter : e] |
| [Modifiers] |