001 /*
002 * Copyright 2010-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 org.codehaus.griffon.runtime.core;
018
019 import griffon.core.GriffonView;
020 import griffon.core.GriffonViewClass;
021 import griffon.util.ApplicationClassLoader;
022 import griffon.util.Xml2Groovy;
023 import groovy.lang.GroovyShell;
024 import groovy.lang.Script;
025 import groovy.util.FactoryBuilderSupport;
026
027 import java.io.InputStream;
028 import java.util.Map;
029
030 import static griffon.util.GriffonNameUtils.isBlank;
031
032 /**
033 * Base implementation of the GriffonView interface.
034 *
035 * @author Andres Almiray
036 * @since 0.9.1
037 */
038 public abstract class AbstractGriffonView extends AbstractGriffonMvcArtifact implements GriffonView {
039 private FactoryBuilderSupport builder;
040
041 protected String getArtifactType() {
042 return GriffonViewClass.TYPE;
043 }
044
045 public FactoryBuilderSupport getBuilder() {
046 return builder;
047 }
048
049 public void setBuilder(FactoryBuilderSupport builder) {
050 this.builder = builder;
051 }
052
053 /**
054 * Transforms an XML file into a Groovy script and evaluates it using a builder.</p>
055 * <p>The file name matches the name of this class plus '.xml'. It must be found somewhere
056 * in the classpath.</p>
057 * <p>Every XML attribute that represents a string literal must be single quoted explicitly
058 * otherwise the build will not be able to parse it. The following XML contents</p>
059 * <pre><xmp>
060 * <application title="app.config.application.title"
061 * pack="true">
062 * <actions>
063 * <action id="'clickAction'"
064 * name="'Click'"
065 * closure="{controller.click(it)}"/>
066 * </actions>
067 * <gridLayout cols="1" rows="3"/>
068 * <textField id="'input'" columns="20"
069 * text="bind('value', target: model)"/>
070 * <textField id="'output'" columns="20"
071 * text="bind{model.value}" editable="false"/>
072 * <button action="clickAction"/>
073 * </application>
074 * </xmp></pre>
075 * <p/>
076 * <p>are translated to</p>
077 * <pre>
078 * application(title: app.config.application.title, pack: true) {
079 * actions {
080 * action(id: 'clickAction', name: 'Click', closure: {controller.click(it)})
081 * }
082 * gridLayout(cols: 1, rows: 3)
083 * textField(id: 'input', text: bind('value', target: model), columns: 20)
084 * textField(id: 'output', text: bind{target.model}, columns: 20, editable: false)
085 * button(action: clickAction)
086 * }
087 * </pre>
088 *
089 * @param args a Map containing all relevant values that the build might need to build the
090 * View; this typically includes 'app', 'controller' and 'model'.
091 * @since 0.9.2
092 */
093 public void buildViewFromXml(Map<String, Object> args) {
094 buildViewFromXml(args, getClass().getName().replace('.', '/') + ".xml");
095 }
096
097 /**
098 * Transforms an XML file into a Groovy script and evaluates it using a builder.</p>
099 * <p>Every XML attribute that represents a string literal must be single quoted explicitly
100 * otherwise the build will not be able to parse it. The following XML contents</p>
101 * <pre><xmp>
102 * <application title="app.config.application.title"
103 * pack="true">
104 * <actions>
105 * <action id="'clickAction'"
106 * name="'Click'"
107 * closure="{controller.click(it)}"/>
108 * </actions>
109 * <gridLayout cols="1" rows="3"/>
110 * <textField id="'input'" columns="20"
111 * text="bind('value', target: model)"/>
112 * <textField id="'output'" columns="20"
113 * text="bind{model.value}" editable="false"/>
114 * <button action="clickAction"/>
115 * </application>
116 * </xmp></pre>
117 * <p/>
118 * <p>are translated to</p>
119 * <pre>
120 * application(title: app.config.application.title, pack: true) {
121 * actions {
122 * action(id: 'clickAction', name: 'Click', closure: {controller.click(it)})
123 * }
124 * gridLayout(cols: 1, rows: 3)
125 * textField(id: 'input', text: bind('value', target: model), columns: 20)
126 * textField(id: 'output', text: bind{target.model}, columns: 20, editable: false)
127 * button(action: clickAction)
128 * }
129 * </pre>
130 *
131 * @param args a Map containing all relevant values that the build might need to build the
132 * View; this typically includes 'app', 'controller' and 'model'.
133 * @param fileName the name of an XML file
134 * @since 0.9.2
135 */
136 public void buildViewFromXml(Map<String, Object> args, String fileName) {
137 if (isBlank(fileName)) {
138 throw new IllegalArgumentException("Invalid file name for externalized view.");
139 }
140
141 InputStream is = ApplicationClassLoader.get().getResourceAsStream(fileName);
142 if (is == null) {
143 throw new IllegalArgumentException("Could not read file " + fileName);
144 }
145
146 String groovyScript = Xml2Groovy.getInstance().parse(is);
147 if (isBlank(groovyScript)) {
148 throw new IllegalArgumentException("File " + fileName + " is empty.");
149 } else if (getLog().isTraceEnabled()) {
150 getLog().trace("View script for " + fileName + "\n" + groovyScript);
151 }
152
153 final Script script = new GroovyShell().parse(groovyScript);
154 script.setBinding(getBuilder());
155 for (Map.Entry<String, ?> arg : args.entrySet()) {
156 script.getBinding().setVariable(arg.getKey(), arg.getValue());
157 }
158
159 getApp().execInsideUISync(new Runnable() {
160 public void run() {
161 getBuilder().build(script);
162 }
163 });
164 }
165 }
|