| 
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(location) : new 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 == 1 && args[0] instanceof 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(name) instanceof 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 == 2 && args[1] instanceof 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 }
 |