View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl;
18  
19  import java.io.BufferedReader;
20  import java.io.File;
21  import java.io.FileReader;
22  import java.io.IOException;
23  import java.io.InputStreamReader;
24  import java.io.StringReader;
25  import java.net.URL;
26  import java.net.URLConnection;
27  
28  import org.apache.commons.jexl.parser.ASTJexlScript;
29  import org.apache.commons.jexl.parser.ParseException;
30  import org.apache.commons.jexl.parser.Parser;
31  import org.apache.commons.jexl.parser.SimpleNode;
32  import org.apache.commons.jexl.parser.TokenMgrError;
33  import org.apache.commons.jexl.util.Introspector;
34  import org.apache.commons.jexl.util.introspection.Uberspect;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * <p>
40   * Creates {@link Script}s.  To create a JEXL Script, pass
41   * valid JEXL syntax to the static createScript() method:
42   * </p>
43   *
44   * <pre>
45   * String jexl = "y = x * 12 + 44; y = y * 4;";
46   * Script script = ScriptFactory.createScript( jexl );
47   * </pre>
48   *
49   * <p>
50   * When an {@link Script} is created, the JEXL syntax is
51   * parsed and verified.
52   * </p>
53   * @since 1.1
54   * @version $Id: ScriptFactory.java 548229 2007-06-18 06:11:32Z dion $
55   */
56  public class ScriptFactory {
57  
58      /** The Log to which all ScriptFactory messages will be logged.*/
59      protected static Log log =
60          LogFactory.getLog("org.apache.commons.jexl.ScriptFactory");
61  
62      /**
63       * The singleton ScriptFactory also holds a single instance of
64       * {@link Parser}. When parsing expressions, ScriptFactory
65       * synchronizes on Parser.
66       */
67      protected static Parser parser = new Parser(new StringReader(";"));
68  
69      /**
70       * ScriptFactory is a singleton and this is the private
71       * instance fufilling that pattern.
72       */
73      protected static ScriptFactory factory = new ScriptFactory();
74  
75      /**
76       * Private constructor, the single instance is always obtained
77       * with a call to getInstance().
78       */
79      private ScriptFactory() {
80          this(Introspector.getUberspect());
81      }
82      
83      public ScriptFactory(Uberspect uberspect) {
84          parser.setUberspect(uberspect);
85      }
86  
87      /**
88       * Returns the single instance of ScriptFactory.
89       * @return the instance of ScriptFactory.
90       */
91      protected static  ScriptFactory getInstance() {
92          return factory;
93      }
94  
95      /**
96       * Creates a Script from a String containing valid JEXL syntax.
97       * This method parses the script which validates the syntax.
98       *
99       * @param scriptText A String containing valid JEXL syntax
100      * @return A {@link Script} which can be executed with a
101      *      {@link JexlContext}.
102      * @throws Exception An exception can be thrown if there is a
103      *      problem parsing the script.
104      */
105     public static Script createScript(String scriptText) throws Exception {
106         return getInstance().createNewScript(scriptText);
107     }
108 
109     /**
110      * Creates a Script from a {@link File} containing valid JEXL syntax.
111      * This method parses the script and validates the syntax.
112      *
113      * @param scriptFile A {@link File} containing valid JEXL syntax.
114      *      Must not be null. Must be a readable file.
115      * @return A {@link Script} which can be executed with a
116      *      {@link JexlContext}.
117      * @throws Exception An exception can be thrown if there is a problem
118      *      parsing the script.
119      */
120     public static Script createScript(File scriptFile) throws Exception {
121         if (scriptFile == null) {
122             throw new NullPointerException("scriptFile is null");
123         }
124         if (!scriptFile.canRead()) {
125             throw new IOException("Can't read scriptFile ("
126                 + scriptFile.getCanonicalPath() + ")");
127         }
128         BufferedReader reader = new BufferedReader(new FileReader(scriptFile));
129         return createScript(readerToString(reader));
130 
131     }
132 
133     /**
134      * Creates a Script from a {@link URL} containing valid JEXL syntax.
135      * This method parses the script and validates the syntax.
136      *
137      * @param scriptUrl A {@link URL} containing valid JEXL syntax.
138      *      Must not be null. Must be a readable file.
139      * @return A {@link Script} which can be executed with a
140      *      {@link JexlContext}.
141      * @throws Exception An exception can be thrown if there is a problem
142      *      parsing the script.
143      */
144     public static Script createScript(URL scriptUrl) throws Exception {
145         if (scriptUrl == null) {
146             throw new NullPointerException("scriptUrl is null");
147         }
148         URLConnection connection = scriptUrl.openConnection();
149 
150         BufferedReader reader = new BufferedReader(
151             new InputStreamReader(connection.getInputStream()));
152         return createScript(readerToString(reader));
153     }
154 
155     /**
156      *  Creates a new Script based on the string.
157      *
158      *  @param scriptText valid Jexl script
159      *  @return Script a new script
160      *  @throws Exception for a variety of reasons - mostly malformed scripts
161      */
162     protected Script createNewScript(String scriptText) throws Exception {
163         String cleanText = cleanScript(scriptText);
164         SimpleNode script;
165         // Parse the Expression
166         synchronized (parser) {
167             log.debug("Parsing script: " + cleanText);
168             try {
169                 script = parser.parse(new StringReader(cleanText));
170             } catch (TokenMgrError tme) {
171                 throw new ParseException(tme.getMessage());
172             }
173         }
174         if (script instanceof ASTJexlScript) {
175             return new ScriptImpl(cleanText, (ASTJexlScript) script);
176         } else {
177             throw new IllegalStateException("Parsed script is not "
178                 + "an ASTJexlScript");
179         }
180     }
181 
182     /**
183      * @todo move to ParseUtils?
184      * Trims the expression and adds a semi-colon if missing.
185      * @param script to clean
186      * @return trimmed expression ending in a semi-colon
187      */
188     private String cleanScript(String script) {
189         String expr = script.trim();
190         if (!expr.endsWith(";")) {
191             expr += ";";
192         }
193         return expr;
194     }
195 
196     /**
197      * Read a buffered reader into a StringBuffer and return a String with
198      * the contents of the reader.
199      * @param reader to be read.
200      * @return the contents of the reader as a String.
201      * @throws IOException on any error reading the reader.
202      */
203     private static String readerToString(BufferedReader reader)
204         throws IOException {
205         StringBuffer buffer = new StringBuffer();
206         try {
207             String line;
208             while ((line = reader.readLine()) != null) {
209                 buffer.append(line).append('\n');
210             }
211             return buffer.toString();
212         } finally {
213             reader.close();
214         }
215 
216     }
217 
218 }