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