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 }