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