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