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