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 }
|