ConfigReader.groovy
001 /*
002  * Copyright 2012-2013 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *      http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 
017 package griffon.util
018 
019 import org.codehaus.groovy.runtime.InvokerHelper
020 
021 import static griffon.util.GriffonNameUtils.isBlank
022 
023 /**
024  * Updated version of {@code groovy.util.ConfigSlurper}.<br/>
025  * New features include:
026  <ul>
027  *     <li>Ability to specify multiple conditional blocks, not just "environments".</li>
028  </ul>
029  *
030  @author Graeme Rocher (Groovy 1.5)
031  @author Andres Almiray
032  @since 1.1.0
033  */
034 class ConfigReader {
035     private static final ENVIRONMENTS_METHOD = 'environments'
036     GroovyClassLoader classLoader
037     private Map bindingVars = [:]
038 
039     private Stack<String> currentConditionalBlock = new Stack<String>()
040     private final Map<String, String> conditionValues = [:]
041     private final Stack<Map<String, ConfigObject>> conditionalBlocks = new Stack<Map<String,ConfigObject>>()
042 
043     ConfigReader() {
044         this('')
045     }
046 
047     /**
048      * Constructs a new ConfigReader instance using the given environment
049      @param env The Environment to use
050      */
051     ConfigReader(String env) {
052         conditionValues[ENVIRONMENTS_METHOD= env
053         classLoader = new GroovyClassLoader(ApplicationClassLoader.get())
054     }
055 
056     void registerConditionalBlock(String blockName, String blockValue) {
057         if (!isBlank(blockName)) {
058             if (isBlank(blockValue)) {
059                 conditionValues.remove(blockName)
060             else {
061                 conditionValues[blockName= blockValue
062             }
063         }
064     }
065 
066     Map<String, String> getConditionalBlockValues() {
067         Collections.unmodifiableMap(conditionValues)
068     }
069 
070     String getEnvironment() {
071         return conditionValues[ENVIRONMENTS_METHOD]
072     }
073 
074     void setEnvironment(String environment) {
075         conditionValues[ENVIRONMENTS_METHOD= environment
076     }
077 
078     void setBinding(Map vars) {
079         this.bindingVars = vars
080     }
081 
082     /**
083      * Parses a ConfigObject instances from an instance of java.util.Properties
084      @param The java.util.Properties instance
085      */
086     ConfigObject parse(Properties properties) {
087         ConfigObject config = new ConfigObject()
088         for (key in properties.keySet()) {
089             def tokens = key.split(/\./)
090 
091             def current = config
092             def last
093             def lastToken
094             def foundBase = false
095             for (token in tokens) {
096                 if (foundBase) {
097                     // handle not properly nested tokens by ignoring
098                     // hierarchy below this point
099                     lastToken += "." + token
100                     current = last
101                 else {
102                     last = current
103                     lastToken = token
104                     current = current."${token}"
105                     if (!(current instanceof ConfigObject)) foundBase = true
106                 }
107             }
108 
109             if (current instanceof ConfigObject) {
110                 if (last[lastToken]) {
111                     def flattened = last.flatten()
112                     last.clear()
113                     flattened.each k2, v2 -> last[k2= v2 }
114                     last[lastToken= properties.get(key)
115                 }
116                 else {
117                     last[lastToken= properties.get(key)
118                 }
119             }
120             current = config
121         }
122         return config
123     }
124     /**
125      * Parse the given script as a string and return the configuration object
126      *
127      @see ConfigReader#parse(groovy.lang.Script)
128      */
129     ConfigObject parse(String script) {
130         return parse(classLoader.parseClass(script))
131     }
132 
133     /**
134      * Create a new instance of the given script class and parse a configuration object from it
135      *
136      @see ConfigReader#parse(groovy.lang.Script)
137      */
138     ConfigObject parse(Class scriptClass) {
139         return parse(scriptClass.newInstance())
140     }
141 
142     /**
143      * Parse the given script into a configuration object (a Map)
144      @param script The script to parse
145      @return A Map of maps that can be navigating with dot de-referencing syntax to obtain configuration entries
146      */
147     ConfigObject parse(Script script) {
148         return parse(script, null)
149     }
150 
151     /**
152      * Parses a Script represented by the given URL into a ConfigObject
153      *
154      @param scriptLocation The location of the script to parse
155      @return The ConfigObject instance
156      */
157     ConfigObject parse(URL scriptLocation) {
158         return parse(classLoader.parseClass(scriptLocation.text).newInstance(), scriptLocation)
159     }
160 
161     /**
162      * Parses the passed groovy.lang.Script instance using the second argument to allow the ConfigObject
163      * to retain an reference to the original location other Groovy script
164      *
165      @param script The groovy.lang.Script instance
166      @param location The original location of the Script as a URL
167      @return The ConfigObject instance
168      */
169     ConfigObject parse(Script script, URL location) {
170         def config = location ? new ConfigObject(locationnew ConfigObject()
171         GroovySystem.metaClassRegistry.removeMetaClass(script.class)
172         def mc = script.class.metaClass
173         def prefix = ""
174         LinkedList stack = new LinkedList()
175         stack << [config: config, scope: [:]]
176         def pushStack = co ->
177             stack << [config: co, scope: stack.last.scope.clone()]
178         }
179         def assignName = name, co ->
180             def current = stack.last
181             /*
182             def cfg = current.config
183             if (cfg instanceof ConfigObject) {
184                 String[] keys = name.split(/\./)
185                 for (int i = 0; i < keys.length - 1; i++) {
186                     String key = keys[i]
187                     if (!cfg.containsKey(key)) {
188                         cfg[key] = new ConfigObject()
189                     }
190                     cfg = cfg.get(key)
191                 }
192                 name = keys[keys.length - 1]
193             }
194             cfg[name] = co
195             */
196             current.config[name= co
197             current.scope[name= co
198         }
199         mc.getProperty = String name ->
200             def current = stack.last
201             def result
202             if (current.config.get(name)) {
203                 result = current.config.get(name)
204             else if (current.scope[name]) {
205                 result = current.scope[name]
206             else {
207                 try {
208                     result = InvokerHelper.getProperty(this, name)
209                 catch (GroovyRuntimeException e) {
210                     result = new ConfigObject()
211                     assignName.call(name, result)
212                 }
213             }
214             result
215         }
216 
217         ConfigObject overrides = new ConfigObject()
218         mc.invokeMethod = String name, args ->
219             def result
220             if (args.length == && args[0instanceof Closure) {
221                 if (name in conditionValues.keySet()) {
222                     try {
223                         currentConditionalBlock.push(name)
224                         conditionalBlocks.push([:])
225                         args[0].call()
226                     finally {
227                         currentConditionalBlock.pop()
228                         for (entry in conditionalBlocks.pop().entrySet()) {
229                             def c = stack.last.config
230                             (c != config? c : overrides).merge(entry.value)
231                         }
232                     }
233                 else if (currentConditionalBlock.size() 0) {
234                     String conditionalBlockKey = currentConditionalBlock.peek()
235                     if (name == conditionValues[conditionalBlockKey]) {
236                         def co = new ConfigObject()
237                         conditionalBlocks.peek()[conditionalBlockKey= co
238 
239                         pushStack.call(co)
240                         try {
241                             currentConditionalBlock.pop()
242                             args[0].call()
243                         finally {
244                             currentConditionalBlock.push(conditionalBlockKey)
245                         }
246                         stack.pop()
247                     }
248                 else {
249                     def co
250                     if (stack.last.config.get(nameinstanceof ConfigObject) {
251                         co = stack.last.config.get(name)
252                     else {
253                         co = new ConfigObject()
254                     }
255 
256                     assignName.call(name, co)
257                     pushStack.call(co)
258                     args[0].call()
259                     stack.pop()
260                 }
261             else if (args.length == && args[1instanceof Closure) {
262                 try {
263                     prefix = name + '.'
264                     assignName.call(name, args[0])
265                     args[1].call()
266                 finally prefix = "" }
267             else {
268                 MetaMethod mm = mc.getMetaMethod(name, args)
269                 if (mm) {
270                     result = mm.invoke(delegate, args)
271                 else {
272                     throw new MissingMethodException(name, getClass(), args)
273                 }
274             }
275             result
276         }
277         script.metaClass = mc
278 
279         def setProperty = String name, value ->
280             assignName.call(prefix + name, value)
281         }
282         def binding = new ConfigBinding(setProperty)
283         if (this.bindingVars) {
284             binding.getVariables().putAll(this.bindingVars)
285         }
286         script.binding = binding
287 
288         script.run()
289 
290         config.merge(overrides)
291 
292         return config
293     }
294 }