AbstractGriffonView.java
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 }