1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springmodules.validation.valang.javascript;
18
19 import java.io.IOException;
20 import java.io.InputStreamReader;
21 import java.io.Reader;
22 import java.io.Writer;
23 import java.util.Calendar;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.Iterator;
27
28 import org.apache.commons.collections.Predicate;
29 import org.apache.commons.collections.functors.AndPredicate;
30 import org.apache.commons.collections.functors.NotPredicate;
31 import org.apache.commons.collections.functors.OrPredicate;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.springframework.context.support.MessageSourceAccessor;
35 import org.springframework.util.Assert;
36 import org.springframework.util.ClassUtils;
37 import org.springframework.util.StringUtils;
38 import org.springframework.web.util.JavaScriptUtils;
39 import org.springmodules.validation.valang.functions.AbstractFunction;
40 import org.springmodules.validation.valang.functions.AbstractMathFunction;
41 import org.springmodules.validation.valang.functions.AddFunction;
42 import org.springmodules.validation.valang.functions.BeanPropertyFunction;
43 import org.springmodules.validation.valang.functions.DateLiteralFunction;
44 import org.springmodules.validation.valang.functions.DivideFunction;
45 import org.springmodules.validation.valang.functions.Function;
46 import org.springmodules.validation.valang.functions.LengthOfFunction;
47 import org.springmodules.validation.valang.functions.LiteralFunction;
48 import org.springmodules.validation.valang.functions.LowerCaseFunction;
49 import org.springmodules.validation.valang.functions.MapEntryFunction;
50 import org.springmodules.validation.valang.functions.ModuloFunction;
51 import org.springmodules.validation.valang.functions.MultiplyFunction;
52 import org.springmodules.validation.valang.functions.NotFunction;
53 import org.springmodules.validation.valang.functions.SubtractFunction;
54 import org.springmodules.validation.valang.functions.TargetBeanFunction;
55 import org.springmodules.validation.valang.functions.UpperCaseFunction;
56 import org.springmodules.validation.valang.predicates.AbstractPropertyPredicate;
57 import org.springmodules.validation.valang.predicates.BasicValidationRule;
58 import org.springmodules.validation.valang.predicates.Operator;
59 import org.springmodules.validation.valang.predicates.ValidationRule;
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class ValangJavaScriptTranslator {
74
75
76
77
78
79 public static Reader getCodebaseReader() {
80 return new InputStreamReader(ValangJavaScriptTranslator.class.getResourceAsStream("valang_codebase.js"));
81 }
82
83 private static final Logger logger = LoggerFactory.getLogger(ValangJavaScriptTranslator.class);
84
85 private static final ReflectiveVisitorHelper reflectiveVisitorHelper = new ReflectiveVisitorHelper();
86
87 private Writer writer;
88
89 public ValangJavaScriptTranslator() {
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105 public void writeJavaScriptValangValidator(Writer writer, String name, boolean installSelfWithForm,
106 Collection<ValidationRule> rules, MessageSourceAccessor messageSource)
107 throws IOException {
108 try {
109 setWriter(writer);
110 append("new ValangValidator(");
111 appendJsString(name);
112 append(',');
113 append(Boolean.toString(installSelfWithForm));
114 append(',');
115 appendArrayValidators(rules, messageSource);
116 append(')');
117 }
118 finally {
119 clearWriter();
120 }
121 }
122
123 protected void setWriter(Writer writer) {
124 Assert.state(this.writer == null,
125 "Attempted to set writer when one already set - is this class being used is multiple threads?");
126 this.writer = writer;
127 }
128
129 protected void clearWriter() {
130 writer = null;
131 }
132
133 protected void append(String string) throws IOException {
134 writer.write(string);
135 }
136
137 protected void appendJsString(String string) throws IOException {
138 writer.write('\'');
139 if (string == null) {
140 writer.write("null");
141 } else {
142 writer.write(JavaScriptUtils.javaScriptEscape(string));
143 }
144 writer.write('\'');
145 }
146
147 protected void append(char c) throws IOException {
148 writer.write(c);
149 }
150
151 private void append(int i) throws IOException {
152 writer.write(Integer.toString(i));
153 }
154
155 protected void appendArrayValidators(Collection<ValidationRule> rules, MessageSourceAccessor messageSource) throws IOException {
156 append("new Array(");
157
158 for (Iterator i = rules.iterator(); i.hasNext();) {
159 appendValidatorRule((BasicValidationRule) i.next(), messageSource);
160 if (i.hasNext()) {
161 append(',');
162 }
163 }
164 append(')');
165 }
166
167 protected void appendValidatorRule(BasicValidationRule rule, MessageSourceAccessor messageSource)
168 throws IOException {
169 append("new ValangValidator.Rule('");
170 append(rule.getField());
171 append("','not implemented',");
172 appendJsString(getErrorMessage(rule, messageSource));
173 append(',');
174 appendValidationFunction(rule.getPredicate());
175 append(')');
176 }
177
178 protected String getErrorMessage(BasicValidationRule rule, MessageSourceAccessor messageSource) {
179 if (StringUtils.hasLength(rule.getErrorKey())) {
180 if (rule.getErrorArgs() != null && !rule.getErrorArgs().isEmpty()) {
181
182 logger.warn("Translating error message with arguments is not implemented; using default message");
183 return rule.getErrorMessage();
184 } else {
185 return messageSource.getMessage(rule.getErrorKey(), rule.getErrorMessage());
186 }
187 } else {
188 return rule.getErrorMessage();
189 }
190 }
191
192 protected void appendValidationFunction(Predicate p) throws IOException {
193 append("function() {return ");
194 doVisit(p);
195 append('}');
196 }
197
198 protected void doVisit(Object value) throws IOException {
199 reflectiveVisitorHelper.invokeVisit(this, value);
200 }
201
202 void visitNull() throws IOException {
203 append("null");
204 }
205
206 void visit(Function f) throws IOException {
207 if (logger.isWarnEnabled()) {
208 logger.warn("Encountered unsupported custom function '" + f.getClass().getName() + "'");
209 }
210 append("this._throwError('don\\'t know how to handle custom function \\'");
211 append(getNameForCustomFunction(f));
212 append("\\'')");
213 }
214
215 void visit(AbstractFunction f) throws IOException {
216 Function[] arguments = f.getArguments();
217 append(getNameForCustomFunction(f));
218 append('(');
219 for (int i = 0; i < arguments.length; i++) {
220 doVisit(arguments[i]);
221 if (i < arguments.length - 1) {
222 append(',');
223 }
224 }
225 append(')');
226 }
227
228 protected String getNameForCustomFunction(Function f) {
229 return "this." + ClassUtils.getShortName(f.getClass());
230 }
231
232 void visit(NotPredicate p) throws IOException {
233 Assert.isTrue(p.getPredicates().length == 1);
234 append("! (");
235 doVisit(p.getPredicates()[0]);
236 append(')');
237 }
238
239 void visit(AndPredicate p) throws IOException {
240 String op = ") && ";
241 for (int i = 0; i < p.getPredicates().length; i++) {
242 Predicate innerP = p.getPredicates()[i];
243 append('(');
244 doVisit(innerP);
245 if (i < p.getPredicates().length - 1) {
246 append(op);
247 } else {
248 append(')');
249 }
250 }
251 }
252
253 void visit(OrPredicate p) throws IOException {
254 String op = ") || ";
255 for (int i = 0; i < p.getPredicates().length; i++) {
256 Predicate innerP = p.getPredicates()[i];
257 append('(');
258 doVisit(innerP);
259 if (i < p.getPredicates().length - 1) {
260 append(op);
261 } else {
262 append(')');
263 }
264 }
265 }
266
267 void visit(AbstractPropertyPredicate p) throws IOException {
268 append(operatorToFunctionName(p.getOperator()));
269 append("((");
270 doVisit(p.getLeftFunction());
271 append("), (");
272 doVisit(p.getRightFunction());
273 append("))");
274 }
275
276 protected String operatorToFunctionName(Operator operator) {
277 switch(operator) {
278 case EQUAL:
279 return "this.equals";
280 case NOT_EQUAL:
281 return "! this.equals";
282 case LESS_THAN:
283 return "this.lessThan";
284 case LESS_THAN_OR_EQUAL:
285 return "this.lessThanOrEquals";
286 case GREATER_THAN:
287 return "this.moreThan";
288 case GREATER_THAN_OR_EQUAL:
289 return "this.moreThanOrEquals";
290 case IN:
291 return "this.inFunc";
292 case NOT_IN:
293 return "! this.inFunc";
294 case BETWEEN:
295 return "this.between";
296 case NOT_BETWEEN:
297 return "! this.between";
298 case NULL:
299 return "this.nullFunc";
300 case NOT_NULL:
301 return "! this.nullFunc";
302 case HAS_TEXT:
303 return "this.hasText";
304 case HAS_NO_TEXT:
305 return "! this.hasText";
306 case HAS_LENGTH:
307 return "this.hasLength";
308 case HAS_NO_LENGTH:
309 return "! this.hasLength";
310 case IS_BLANK:
311 return "this.isBlank";
312 case IS_NOT_BLANK:
313 return "! this.isBlank";
314 case IS_WORD:
315 return "this.isWord";
316 case IS_NOT_WORD:
317 return "! this.isWord";
318 case IS_UPPERCASE:
319 return "this.isUpper";
320 case IS_NOT_UPPERCASE:
321 return "! this.isUpper";
322 case IS_LOWERCASE:
323 return "this.isLower";
324 case IS_NOT_LOWERCASE:
325 return "! this.isLower";
326 default:
327 throw new UnsupportedOperationException("Unexpected operator type '" + operator.getClass().getName() + "'");
328 }
329 }
330
331 void visit(TargetBeanFunction f) throws IOException {
332 append("this.getTargetBean()");
333 }
334
335 void visit(BeanPropertyFunction f) throws IOException {
336 append("this.getPropertyValue(");
337 appendJsString(f.getField());
338 append(')');
339 }
340
341 void visit(MapEntryFunction f) throws IOException {
342 append("(");
343 doVisit(f.getMapFunction());
344 append("[");
345 doVisit(f.getKeyFunction());
346 append("])");
347 }
348
349 void visit(LiteralFunction f) throws IOException {
350 Object literal = f.getResult(null);
351 if (literal instanceof String) {
352 appendJsString((String) literal);
353 } else if (literal instanceof Number) {
354 append(literal.toString());
355 } else if (literal instanceof Boolean) {
356 append(literal.toString());
357 } else if (literal instanceof Function[]) {
358 Function[] functions = (Function[]) literal;
359 appeandLiteralArray(functions);
360 } else if (literal instanceof Collection) {
361 appeandLiteralArray((((Collection) literal).toArray()));
362 } else {
363 throw new UnsupportedOperationException("Unexpected literal type '" + literal.getClass() + "'");
364 }
365 }
366
367 void appeandLiteralArray(Object[] functions) throws IOException {
368 append("new Array(");
369 for (int i = 0; i < functions.length; i++) {
370 doVisit(functions[i]);
371 if (i < functions.length - 1) {
372 append(",");
373 }
374 }
375 append(')');
376 }
377
378 void visit(DateLiteralFunction f) throws IOException {
379 Calendar cal = Calendar.getInstance();
380 cal.setTime((Date) f.getResult(null));
381 append("new Date(");
382 append(cal.get(Calendar.YEAR));
383 append(", ");
384 append(cal.get(Calendar.MONTH));
385 append(", ");
386 append(cal.get(Calendar.DATE));
387 append(", ");
388 append(cal.get(Calendar.HOUR_OF_DAY));
389 append(", ");
390 append(cal.get(Calendar.MINUTE));
391 append(", ");
392 append(cal.get(Calendar.SECOND));
393 append(", ");
394 append(cal.get(Calendar.MILLISECOND));
395 append(')');
396 }
397
398 void visit(LengthOfFunction f) throws IOException {
399 append("this.lengthOf(");
400 doVisit(f.getArguments()[0]);
401 append(')');
402 }
403
404 void visit(NotFunction f) throws IOException {
405 append("! ");
406 doVisit(f.getArguments()[0]);
407 }
408
409 void visit(UpperCaseFunction f) throws IOException {
410 append("this.upperCase(");
411 doVisit(f.getArguments()[0]);
412 append(')');
413 }
414
415 void visit(LowerCaseFunction f) throws IOException {
416 append("this.lowerCase(");
417 doVisit(f.getArguments()[0]);
418 append(')');
419 }
420
421 void visit(AbstractMathFunction f) throws IOException {
422 append(mathToFunctionName(f));
423 append("((");
424 doVisit(f.getLeftFunction());
425 append("),(");
426 doVisit(f.getRightFunction());
427 append("))");
428 }
429
430 protected String mathToFunctionName(AbstractMathFunction f) {
431 if (f instanceof AddFunction) {
432 return "this.add";
433 } else if (f instanceof DivideFunction) {
434 return "this.divide";
435 } else if (f instanceof ModuloFunction) {
436 return "this.modulo";
437 } else if (f instanceof MultiplyFunction) {
438 return "this.multiply";
439 } else if (f instanceof SubtractFunction) {
440 return "this.subtract";
441 } else {
442 throw new UnsupportedOperationException("Unexpected math function type '" + f.getClass().getName() + "'");
443 }
444 }
445 }