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  
18  package org.apache.struts2.jasper.compiler;
19  
20  import org.apache.commons.el.ExpressionEvaluatorImpl;
21  import org.apache.struts2.jasper.Constants;
22  import org.apache.struts2.jasper.JasperException;
23  import org.apache.struts2.jasper.JspCompilationContext;
24  import org.xml.sax.Attributes;
25  
26  import javax.servlet.jsp.el.ELException;
27  import javax.servlet.jsp.el.ELParseException;
28  import javax.servlet.jsp.el.FunctionMapper;
29  import java.io.*;
30  import java.util.Vector;
31  import java.util.jar.JarFile;
32  import java.util.zip.ZipEntry;
33  
34  /***
35   * This class has all the utility method(s).
36   * Ideally should move all the bean containers here.
37   *
38   * @author Mandar Raje.
39   * @author Rajiv Mordani.
40   * @author Danno Ferrin
41   * @author Pierre Delisle
42   * @author Shawn Bayern
43   * @author Mark Roth
44   */
45  public class JspUtil {
46  
47      private static final String WEB_INF_TAGS = "/WEB-INF/tags/";
48      private static final String META_INF_TAGS = "/META-INF/tags/";
49  
50      // Delimiters for request-time expressions (JSP and XML syntax)
51      private static final String OPEN_EXPR = "<%=";
52      private static final String CLOSE_EXPR = "%>";
53      private static final String OPEN_EXPR_XML = "%=";
54      private static final String CLOSE_EXPR_XML = "%";
55  
56      private static int tempSequenceNumber = 0;
57      private static ExpressionEvaluatorImpl expressionEvaluator
58              = new ExpressionEvaluatorImpl();
59  
60      private static final String javaKeywords[] = {
61              "abstract", "assert", "boolean", "break", "byte", "case",
62              "catch", "char", "class", "const", "continue",
63              "default", "do", "double", "else", "enum", "extends",
64              "final", "finally", "float", "for", "goto",
65              "if", "implements", "import", "instanceof", "int",
66              "interface", "long", "native", "new", "package",
67              "private", "protected", "public", "return", "short",
68              "static", "strictfp", "super", "switch", "synchronized",
69              "this", "throws", "transient", "try", "void",
70              "volatile", "while"};
71  
72      public static final int CHUNKSIZE = 1024;
73  
74      public static char[] removeQuotes(char[] chars) {
75          CharArrayWriter caw = new CharArrayWriter();
76          for (int i = 0; i < chars.length; i++) {
77              if (chars[i] == '%' && chars[i + 1] == '//' &&
78                      chars[i + 2] == '>') {
79                  caw.write('%');
80                  caw.write('>');
81                  i = i + 2;
82              } else {
83                  caw.write(chars[i]);
84              }
85          }
86          return caw.toCharArray();
87      }
88  
89      public static char[] escapeQuotes(char[] chars) {
90          // Prescan to convert %\> to %>
91          String s = new String(chars);
92          while (true) {
93              int n = s.indexOf("%//>");
94              if (n < 0)
95                  break;
96              StringBuffer sb = new StringBuffer(s.substring(0, n));
97              sb.append("%>");
98              sb.append(s.substring(n + 3));
99              s = sb.toString();
100         }
101         chars = s.toCharArray();
102         return (chars);
103 
104 
105         // Escape all backslashes not inside a Java string literal
106         /*
107         CharArrayWriter caw = new CharArrayWriter();
108         boolean inJavaString = false;
109         for (int i = 0; i < chars.length; i++) {
110             if (chars[i] == '"') inJavaString = !inJavaString;
111             // escape out the escape character
112             if (!inJavaString && (chars[i] == '//')) caw.write('//');
113             caw.write(chars[i]);
114         }
115         return caw.toCharArray();
116         */
117     }
118 
119     /***
120      * Checks if the token is a runtime expression.
121      * In standard JSP syntax, a runtime expression starts with '<%' and
122      * ends with '%>'. When the JSP document is in XML syntax, a runtime
123      * expression starts with '%=' and ends with '%'.
124      *
125      * @param token The token to be checked
126      *              return whether the token is a runtime expression or not.
127      */
128     public static boolean isExpression(String token, boolean isXml) {
129         String openExpr;
130         String closeExpr;
131         if (isXml) {
132             openExpr = OPEN_EXPR_XML;
133             closeExpr = CLOSE_EXPR_XML;
134         } else {
135             openExpr = OPEN_EXPR;
136             closeExpr = CLOSE_EXPR;
137         }
138         if (token.startsWith(openExpr) && token.endsWith(closeExpr)) {
139             return true;
140         } else {
141             return false;
142         }
143     }
144 
145     /***
146      * @return the "expression" part of a runtime expression,
147      *         taking the delimiters out.
148      */
149     public static String getExpr(String expression, boolean isXml) {
150         String returnString;
151         String openExpr;
152         String closeExpr;
153         if (isXml) {
154             openExpr = OPEN_EXPR_XML;
155             closeExpr = CLOSE_EXPR_XML;
156         } else {
157             openExpr = OPEN_EXPR;
158             closeExpr = CLOSE_EXPR;
159         }
160         int length = expression.length();
161         if (expression.startsWith(openExpr) &&
162                 expression.endsWith(closeExpr)) {
163             returnString = expression.substring(
164                     openExpr.length(), length - closeExpr.length());
165         } else {
166             returnString = "";
167         }
168         return returnString;
169     }
170 
171     /***
172      * Takes a potential expression and converts it into XML form
173      */
174     public static String getExprInXml(String expression) {
175         String returnString;
176         int length = expression.length();
177 
178         if (expression.startsWith(OPEN_EXPR)
179                 && expression.endsWith(CLOSE_EXPR)) {
180             returnString = expression.substring(1, length - 1);
181         } else {
182             returnString = expression;
183         }
184 
185         return escapeXml(returnString.replace(Constants.HACK_CHAR, '$'));
186     }
187 
188     /***
189      * Checks to see if the given scope is valid.
190      *
191      * @param scope The scope to be checked
192      * @param n     The Node containing the 'scope' attribute whose value is to be
193      *              checked
194      * @param err   error dispatcher
195      * @throws JasperException if scope is not null and different from
196      *                         &quot;page&quot;, &quot;request&quot;, &quot;session&quot;, and
197      *                         &quot;application&quot;
198      */
199     public static void checkScope(String scope, Node n, ErrorDispatcher err)
200             throws JasperException {
201         if (scope != null && !scope.equals("page") && !scope.equals("request")
202                 && !scope.equals("session") && !scope.equals("application")) {
203             err.jspError(n, "jsp.error.invalid.scope", scope);
204         }
205     }
206 
207     /***
208      * Checks if all mandatory attributes are present and if all attributes
209      * present have valid names.  Checks attributes specified as XML-style
210      * attributes as well as attributes specified using the jsp:attribute
211      * standard action.
212      */
213     public static void checkAttributes(String typeOfTag,
214                                        Node n,
215                                        ValidAttribute[] validAttributes,
216                                        ErrorDispatcher err)
217             throws JasperException {
218         Attributes attrs = n.getAttributes();
219         Mark start = n.getStart();
220         boolean valid = true;
221 
222         // AttributesImpl.removeAttribute is broken, so we do this...
223         int tempLength = (attrs == null) ? 0 : attrs.getLength();
224         Vector temp = new Vector(tempLength, 1);
225         for (int i = 0; i < tempLength; i++) {
226             String qName = attrs.getQName(i);
227             if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:")))
228                 temp.addElement(qName);
229         }
230 
231         // Add names of attributes specified using jsp:attribute
232         Node.Nodes tagBody = n.getBody();
233         if (tagBody != null) {
234             int numSubElements = tagBody.size();
235             for (int i = 0; i < numSubElements; i++) {
236                 Node node = tagBody.getNode(i);
237                 if (node instanceof Node.NamedAttribute) {
238                     String attrName = node.getAttributeValue("name");
239                     temp.addElement(attrName);
240                     // Check if this value appear in the attribute of the node
241                     if (n.getAttributeValue(attrName) != null) {
242                         err.jspError(n, "jsp.error.duplicate.name.jspattribute",
243                                 attrName);
244                     }
245                 } else {
246                     // Nothing can come before jsp:attribute, and only
247                     // jsp:body can come after it.
248                     break;
249                 }
250             }
251         }
252 
253         /*
254        * First check to see if all the mandatory attributes are present.
255        * If so only then proceed to see if the other attributes are valid
256        * for the particular tag.
257        */
258         String missingAttribute = null;
259 
260         for (int i = 0; i < validAttributes.length; i++) {
261             int attrPos;
262             if (validAttributes[i].mandatory) {
263                 attrPos = temp.indexOf(validAttributes[i].name);
264                 if (attrPos != -1) {
265                     temp.remove(attrPos);
266                     valid = true;
267                 } else {
268                     valid = false;
269                     missingAttribute = validAttributes[i].name;
270                     break;
271                 }
272             }
273         }
274 
275         // If mandatory attribute is missing then the exception is thrown
276         if (!valid)
277             err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag,
278                     missingAttribute);
279 
280         // Check to see if there are any more attributes for the specified tag.
281         int attrLeftLength = temp.size();
282         if (attrLeftLength == 0)
283             return;
284 
285         // Now check to see if the rest of the attributes are valid too.
286         String attribute = null;
287 
288         for (int j = 0; j < attrLeftLength; j++) {
289             valid = false;
290             attribute = (String) temp.elementAt(j);
291             for (int i = 0; i < validAttributes.length; i++) {
292                 if (attribute.equals(validAttributes[i].name)) {
293                     valid = true;
294                     break;
295                 }
296             }
297             if (!valid)
298                 err.jspError(start, "jsp.error.invalid.attribute", typeOfTag,
299                         attribute);
300         }
301         // XXX *could* move EL-syntax validation here... (sb)
302     }
303 
304     public static String escapeQueryString(String unescString) {
305         if (unescString == null)
306             return null;
307 
308         String escString = "";
309         String shellSpChars = "//\"";
310 
311         for (int index = 0; index < unescString.length(); index++) {
312             char nextChar = unescString.charAt(index);
313 
314             if (shellSpChars.indexOf(nextChar) != -1)
315                 escString += "//";
316 
317             escString += nextChar;
318         }
319         return escString;
320     }
321 
322     /***
323      * Escape the 5 entities defined by XML.
324      */
325     public static String escapeXml(String s) {
326         if (s == null) return null;
327         StringBuffer sb = new StringBuffer();
328         for (int i = 0; i < s.length(); i++) {
329             char c = s.charAt(i);
330             if (c == '<') {
331                 sb.append("&lt;");
332             } else if (c == '>') {
333                 sb.append("&gt;");
334             } else if (c == '\'') {
335                 sb.append("&apos;");
336             } else if (c == '&') {
337                 sb.append("&amp;");
338             } else if (c == '"') {
339                 sb.append("&quot;");
340             } else {
341                 sb.append(c);
342             }
343         }
344         return sb.toString();
345     }
346 
347     /***
348      * Replaces any occurrences of the character <tt>replace</tt> with the
349      * string <tt>with</tt>.
350      */
351     public static String replace(String name, char replace, String with) {
352         StringBuffer buf = new StringBuffer();
353         int begin = 0;
354         int end;
355         int last = name.length();
356 
357         while (true) {
358             end = name.indexOf(replace, begin);
359             if (end < 0) {
360                 end = last;
361             }
362             buf.append(name.substring(begin, end));
363             if (end == last) {
364                 break;
365             }
366             buf.append(with);
367             begin = end + 1;
368         }
369 
370         return buf.toString();
371     }
372 
373     public static class ValidAttribute {
374         String name;
375         boolean mandatory;
376         boolean rtexprvalue;    // not used now
377 
378         public ValidAttribute(String name, boolean mandatory,
379                               boolean rtexprvalue) {
380             this.name = name;
381             this.mandatory = mandatory;
382             this.rtexprvalue = rtexprvalue;
383         }
384 
385         public ValidAttribute(String name, boolean mandatory) {
386             this(name, mandatory, false);
387         }
388 
389         public ValidAttribute(String name) {
390             this(name, false);
391         }
392     }
393 
394     /***
395      * Convert a String value to 'boolean'.
396      * Besides the standard conversions done by
397      * Boolean.valueOf(s).booleanValue(), the value "yes"
398      * (ignore case) is also converted to 'true'.
399      * If 's' is null, then 'false' is returned.
400      *
401      * @param s the string to be converted
402      * @return the boolean value associated with the string s
403      */
404     public static boolean booleanValue(String s) {
405         boolean b = false;
406         if (s != null) {
407             if (s.equalsIgnoreCase("yes")) {
408                 b = true;
409             } else {
410                 b = Boolean.valueOf(s).booleanValue();
411             }
412         }
413         return b;
414     }
415 
416     /***
417      * Returns the <tt>Class</tt> object associated with the class or
418      * interface with the given string name.
419      * <p/>
420      * <p> The <tt>Class</tt> object is determined by passing the given string
421      * name to the <tt>Class.forName()</tt> method, unless the given string
422      * name represents a primitive type, in which case it is converted to a
423      * <tt>Class</tt> object by appending ".class" to it (e.g., "int.class").
424      */
425     public static Class toClass(String type, ClassLoader loader)
426             throws ClassNotFoundException {
427 
428         Class c = null;
429         int i0 = type.indexOf('[');
430         int dims = 0;
431         if (i0 > 0) {
432             // This is an array.  Count the dimensions
433             for (int i = 0; i < type.length(); i++) {
434                 if (type.charAt(i) == '[')
435                     dims++;
436             }
437             type = type.substring(0, i0);
438         }
439 
440         if ("boolean".equals(type))
441             c = boolean.class;
442         else if ("char".equals(type))
443             c = char.class;
444         else if ("byte".equals(type))
445             c = byte.class;
446         else if ("short".equals(type))
447             c = short.class;
448         else if ("int".equals(type))
449             c = int.class;
450         else if ("long".equals(type))
451             c = long.class;
452         else if ("float".equals(type))
453             c = float.class;
454         else if ("double".equals(type))
455             c = double.class;
456         else if (type.indexOf('[') < 0)
457             c = loader.loadClass(type);
458 
459         if (dims == 0)
460             return c;
461 
462         if (dims == 1)
463             return java.lang.reflect.Array.newInstance(c, 1).getClass();
464 
465         // Array of more than i dimension
466         return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass();
467     }
468 
469     /***
470      * Produces a String representing a call to the EL interpreter.
471      *
472      * @param expression   a String containing zero or more "${}" expressions
473      * @param expectedType the expected type of the interpreted result
474      * @param fnmapvar     Variable pointing to a function map.
475      * @param XmlEscape    True if the result should do XML escaping
476      * @return a String representing a call to the EL interpreter.
477      */
478     public static String interpreterCall(boolean isTagFile,
479                                          String expression,
480                                          Class expectedType,
481                                          String fnmapvar,
482                                          boolean XmlEscape) {
483         /*
484          * Determine which context object to use.
485          */
486         String jspCtxt = null;
487         if (isTagFile)
488             jspCtxt = "this.getJspContext()";
489         else
490             jspCtxt = "_jspx_page_context";
491 
492         /*
493         * Determine whether to use the expected type's textual name
494     * or, if it's a primitive, the name of its correspondent boxed
495     * type.
496         */
497         String targetType = expectedType.getName();
498         String primitiveConverterMethod = null;
499         if (expectedType.isPrimitive()) {
500             if (expectedType.equals(Boolean.TYPE)) {
501                 targetType = Boolean.class.getName();
502                 primitiveConverterMethod = "booleanValue";
503             } else if (expectedType.equals(Byte.TYPE)) {
504                 targetType = Byte.class.getName();
505                 primitiveConverterMethod = "byteValue";
506             } else if (expectedType.equals(Character.TYPE)) {
507                 targetType = Character.class.getName();
508                 primitiveConverterMethod = "charValue";
509             } else if (expectedType.equals(Short.TYPE)) {
510                 targetType = Short.class.getName();
511                 primitiveConverterMethod = "shortValue";
512             } else if (expectedType.equals(Integer.TYPE)) {
513                 targetType = Integer.class.getName();
514                 primitiveConverterMethod = "intValue";
515             } else if (expectedType.equals(Long.TYPE)) {
516                 targetType = Long.class.getName();
517                 primitiveConverterMethod = "longValue";
518             } else if (expectedType.equals(Float.TYPE)) {
519                 targetType = Float.class.getName();
520                 primitiveConverterMethod = "floatValue";
521             } else if (expectedType.equals(Double.TYPE)) {
522                 targetType = Double.class.getName();
523                 primitiveConverterMethod = "doubleValue";
524             }
525         }
526 
527         if (primitiveConverterMethod != null) {
528             XmlEscape = false;
529         }
530 
531         /*
532         * Build up the base call to the interpreter.
533         */
534         // XXX - We use a proprietary call to the interpreter for now
535         // as the current standard machinery is inefficient and requires
536         // lots of wrappers and adapters.  This should all clear up once
537         // the EL interpreter moves out of JSTL and into its own project.
538         // In the future, this should be replaced by code that calls
539         // ExpressionEvaluator.parseExpression() and then cache the resulting
540         // expression objects.  The interpreterCall would simply select
541         // one of the pre-cached expressions and evaluate it.
542         // Note that PageContextImpl implements VariableResolver and
543         // the generated Servlet/SimpleTag implements FunctionMapper, so
544         // that machinery is already in place (mroth).
545         targetType = toJavaSourceType(targetType);
546         StringBuffer call = new StringBuffer(
547                 "(" + targetType + ") "
548                         + "org.apache.struts2.jasper.runtime.PageContextImpl.proprietaryEvaluate"
549                         + "(" + Generator.quote(expression) + ", "
550                         + targetType + ".class, "
551                         + "(PageContext)" + jspCtxt
552                         + ", " + fnmapvar
553                         + ", " + XmlEscape
554                         + ")");
555 
556         /*
557         * Add the primitive converter method if we need to.
558         */
559         if (primitiveConverterMethod != null) {
560             call.insert(0, "(");
561             call.append(")." + primitiveConverterMethod + "()");
562         }
563 
564         return call.toString();
565     }
566 
567     /***
568      * Validates the syntax of all ${} expressions within the given string.
569      *
570      * @param where       the approximate location of the expressions in the JSP page
571      * @param expressions a string containing zero or more "${}" expressions
572      * @param err         an error dispatcher to use
573      */
574     public static void validateExpressions(Mark where,
575                                            String expressions,
576                                            Class expectedType,
577                                            FunctionMapper functionMapper,
578                                            ErrorDispatcher err)
579             throws JasperException {
580 
581         try {
582             JspUtil.expressionEvaluator.parseExpression(expressions,
583                     expectedType, null);
584         }
585         catch (ELParseException e) {
586             err.jspError(where, "jsp.error.invalid.expression", expressions,
587                     e.toString());
588         }
589         catch (ELException e) {
590             err.jspError(where, "jsp.error.invalid.expression", expressions,
591                     e.toString());
592         }
593     }
594 
595     /***
596      * Resets the temporary variable name.
597      * (not thread-safe)
598      */
599     public static void resetTemporaryVariableName() {
600         tempSequenceNumber = 0;
601     }
602 
603     /***
604      * Generates a new temporary variable name.
605      * (not thread-safe)
606      */
607     public static String nextTemporaryVariableName() {
608         return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
609     }
610 
611     public static String coerceToPrimitiveBoolean(String s,
612                                                   boolean isNamedAttribute) {
613         if (isNamedAttribute) {
614             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToBoolean(" + s + ")";
615         } else {
616             if (s == null || s.length() == 0)
617                 return "false";
618             else
619                 return Boolean.valueOf(s).toString();
620         }
621     }
622 
623     public static String coerceToBoolean(String s, boolean isNamedAttribute) {
624         if (isNamedAttribute) {
625             return "(Boolean) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Boolean.class)";
626         } else {
627             if (s == null || s.length() == 0) {
628                 return "new Boolean(false)";
629             } else {
630                 // Detect format error at translation time
631                 return "new Boolean(" + Boolean.valueOf(s).toString() + ")";
632             }
633         }
634     }
635 
636     public static String coerceToPrimitiveByte(String s,
637                                                boolean isNamedAttribute) {
638         if (isNamedAttribute) {
639             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToByte(" + s + ")";
640         } else {
641             if (s == null || s.length() == 0)
642                 return "(byte) 0";
643             else
644                 return "((byte)" + Byte.valueOf(s).toString() + ")";
645         }
646     }
647 
648     public static String coerceToByte(String s, boolean isNamedAttribute) {
649         if (isNamedAttribute) {
650             return "(Byte) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Byte.class)";
651         } else {
652             if (s == null || s.length() == 0) {
653                 return "new Byte((byte) 0)";
654             } else {
655                 // Detect format error at translation time
656                 return "new Byte((byte)" + Byte.valueOf(s).toString() + ")";
657             }
658         }
659     }
660 
661     public static String coerceToChar(String s, boolean isNamedAttribute) {
662         if (isNamedAttribute) {
663             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToChar(" + s + ")";
664         } else {
665             if (s == null || s.length() == 0) {
666                 return "(char) 0";
667             } else {
668                 char ch = s.charAt(0);
669                 // this trick avoids escaping issues
670                 return "((char) " + (int) ch + ")";
671             }
672         }
673     }
674 
675     public static String coerceToCharacter(String s, boolean isNamedAttribute) {
676         if (isNamedAttribute) {
677             return "(Character) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Character.class)";
678         } else {
679             if (s == null || s.length() == 0) {
680                 return "new Character((char) 0)";
681             } else {
682                 char ch = s.charAt(0);
683                 // this trick avoids escaping issues
684                 return "new Character((char) " + (int) ch + ")";
685             }
686         }
687     }
688 
689     public static String coerceToPrimitiveDouble(String s,
690                                                  boolean isNamedAttribute) {
691         if (isNamedAttribute) {
692             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToDouble(" + s + ")";
693         } else {
694             if (s == null || s.length() == 0)
695                 return "(double) 0";
696             else
697                 return Double.valueOf(s).toString();
698         }
699     }
700 
701     public static String coerceToDouble(String s, boolean isNamedAttribute) {
702         if (isNamedAttribute) {
703             return "(Double) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Double.class)";
704         } else {
705             if (s == null || s.length() == 0) {
706                 return "new Double(0)";
707             } else {
708                 // Detect format error at translation time
709                 return "new Double(" + Double.valueOf(s).toString() + ")";
710             }
711         }
712     }
713 
714     public static String coerceToPrimitiveFloat(String s,
715                                                 boolean isNamedAttribute) {
716         if (isNamedAttribute) {
717             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToFloat(" + s + ")";
718         } else {
719             if (s == null || s.length() == 0)
720                 return "(float) 0";
721             else
722                 return Float.valueOf(s).toString() + "f";
723         }
724     }
725 
726     public static String coerceToFloat(String s, boolean isNamedAttribute) {
727         if (isNamedAttribute) {
728             return "(Float) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Float.class)";
729         } else {
730             if (s == null || s.length() == 0) {
731                 return "new Float(0)";
732             } else {
733                 // Detect format error at translation time
734                 return "new Float(" + Float.valueOf(s).toString() + "f)";
735             }
736         }
737     }
738 
739     public static String coerceToInt(String s, boolean isNamedAttribute) {
740         if (isNamedAttribute) {
741             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToInt(" + s + ")";
742         } else {
743             if (s == null || s.length() == 0)
744                 return "0";
745             else
746                 return Integer.valueOf(s).toString();
747         }
748     }
749 
750     public static String coerceToInteger(String s, boolean isNamedAttribute) {
751         if (isNamedAttribute) {
752             return "(Integer) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Integer.class)";
753         } else {
754             if (s == null || s.length() == 0) {
755                 return "new Integer(0)";
756             } else {
757                 // Detect format error at translation time
758                 return "new Integer(" + Integer.valueOf(s).toString() + ")";
759             }
760         }
761     }
762 
763     public static String coerceToPrimitiveShort(String s,
764                                                 boolean isNamedAttribute) {
765         if (isNamedAttribute) {
766             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToShort(" + s + ")";
767         } else {
768             if (s == null || s.length() == 0)
769                 return "(short) 0";
770             else
771                 return "((short) " + Short.valueOf(s).toString() + ")";
772         }
773     }
774 
775     public static String coerceToShort(String s, boolean isNamedAttribute) {
776         if (isNamedAttribute) {
777             return "(Short) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Short.class)";
778         } else {
779             if (s == null || s.length() == 0) {
780                 return "new Short((short) 0)";
781             } else {
782                 // Detect format error at translation time
783                 return "new Short(\"" + Short.valueOf(s).toString() + "\")";
784             }
785         }
786     }
787 
788     public static String coerceToPrimitiveLong(String s,
789                                                boolean isNamedAttribute) {
790         if (isNamedAttribute) {
791             return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToLong(" + s + ")";
792         } else {
793             if (s == null || s.length() == 0)
794                 return "(long) 0";
795             else
796                 return Long.valueOf(s).toString() + "l";
797         }
798     }
799 
800     public static String coerceToLong(String s, boolean isNamedAttribute) {
801         if (isNamedAttribute) {
802             return "(Long) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Long.class)";
803         } else {
804             if (s == null || s.length() == 0) {
805                 return "new Long(0)";
806             } else {
807                 // Detect format error at translation time
808                 return "new Long(" + Long.valueOf(s).toString() + "l)";
809             }
810         }
811     }
812 
813     public static InputStream getInputStream(String fname, JarFile jarFile,
814                                              JspCompilationContext ctxt,
815                                              ErrorDispatcher err)
816             throws JasperException, IOException {
817 
818         InputStream in = null;
819 
820         if (jarFile != null) {
821             String jarEntryName = fname.substring(1, fname.length());
822             ZipEntry jarEntry = jarFile.getEntry(jarEntryName);
823             if (jarEntry == null) {
824                 err.jspError("jsp.error.file.not.found", fname);
825             }
826             in = jarFile.getInputStream(jarEntry);
827         } else {
828             in = ctxt.getResourceAsStream(fname);
829         }
830 
831         if (in == null) {
832             err.jspError("jsp.error.file.not.found", fname);
833         }
834 
835         return in;
836     }
837 
838     /***
839      * Gets the fully-qualified class name of the tag handler corresponding to
840      * the given tag file path.
841      *
842      * @param path Tag file path
843      * @param err  Error dispatcher
844      * @return Fully-qualified class name of the tag handler corresponding to
845      *         the given tag file path
846      */
847     public static String getTagHandlerClassName(String path,
848                                                 ErrorDispatcher err)
849             throws JasperException {
850 
851         String className = null;
852         int begin = 0;
853         int index;
854 
855         index = path.lastIndexOf(".tag");
856         if (index == -1) {
857             err.jspError("jsp.error.tagfile.badSuffix", path);
858         }
859 
860         //It's tempting to remove the ".tag" suffix here, but we can't.
861         //If we remove it, the fully-qualified class name of this tag
862         //could conflict with the package name of other tags.
863         //For instance, the tag file
864         //    /WEB-INF/tags/foo.tag
865         //would have fully-qualified class name
866         //    org.apache.jsp.tag.web.foo
867         //which would conflict with the package name of the tag file
868         //    /WEB-INF/tags/foo/bar.tag
869 
870         index = path.indexOf(WEB_INF_TAGS);
871         if (index != -1) {
872             className = "org.apache.jsp.tag.web.";
873             begin = index + WEB_INF_TAGS.length();
874         } else {
875             index = path.indexOf(META_INF_TAGS);
876             if (index != -1) {
877                 className = "org.apache.jsp.tag.meta.";
878                 begin = index + META_INF_TAGS.length();
879             } else {
880                 err.jspError("jsp.error.tagfile.illegalPath", path);
881             }
882         }
883 
884         className += makeJavaPackage(path.substring(begin));
885 
886         return className;
887     }
888 
889     /***
890      * Converts the given path to a Java package or fully-qualified class name
891      *
892      * @param path Path to convert
893      * @return Java package corresponding to the given path
894      */
895     public static final String makeJavaPackage(String path) {
896         String classNameComponents[] = split(path, "/");
897         StringBuffer legalClassNames = new StringBuffer();
898         for (int i = 0; i < classNameComponents.length; i++) {
899             legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
900             if (i < classNameComponents.length - 1) {
901                 legalClassNames.append('.');
902             }
903         }
904         return legalClassNames.toString();
905     }
906 
907     /***
908      * Splits a string into it's components.
909      *
910      * @param path String to split
911      * @param pat  Pattern to split at
912      * @return the components of the path
913      */
914     private static final String[] split(String path, String pat) {
915         Vector comps = new Vector();
916         int pos = path.indexOf(pat);
917         int start = 0;
918         while (pos >= 0) {
919             if (pos > start) {
920                 String comp = path.substring(start, pos);
921                 comps.add(comp);
922             }
923             start = pos + pat.length();
924             pos = path.indexOf(pat, start);
925         }
926         if (start < path.length()) {
927             comps.add(path.substring(start));
928         }
929         String[] result = new String[comps.size()];
930         for (int i = 0; i < comps.size(); i++) {
931             result[i] = (String) comps.elementAt(i);
932         }
933         return result;
934     }
935 
936     /***
937      * Converts the given identifier to a legal Java identifier
938      *
939      * @param identifier Identifier to convert
940      * @return Legal Java identifier corresponding to the given identifier
941      */
942     public static final String makeJavaIdentifier(String identifier) {
943         StringBuffer modifiedIdentifier =
944                 new StringBuffer(identifier.length());
945         if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
946             modifiedIdentifier.append('_');
947         }
948         for (int i = 0; i < identifier.length(); i++) {
949             char ch = identifier.charAt(i);
950             if (Character.isJavaIdentifierPart(ch) && ch != '_') {
951                 modifiedIdentifier.append(ch);
952             } else if (ch == '.') {
953                 modifiedIdentifier.append('_');
954             } else {
955                 modifiedIdentifier.append(mangleChar(ch));
956             }
957         }
958         if (isJavaKeyword(modifiedIdentifier.toString())) {
959             modifiedIdentifier.append('_');
960         }
961         return modifiedIdentifier.toString();
962     }
963 
964     /***
965      * Mangle the specified character to create a legal Java class name.
966      */
967     public static final String mangleChar(char ch) {
968         char[] result = new char[5];
969         result[0] = '_';
970         result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
971         result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
972         result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
973         result[4] = Character.forDigit(ch & 0xf, 16);
974         return new String(result);
975     }
976 
977     /***
978      * Test whether the argument is a Java keyword
979      */
980     public static boolean isJavaKeyword(String key) {
981         int i = 0;
982         int j = javaKeywords.length;
983         while (i < j) {
984             int k = (i + j) / 2;
985             int result = javaKeywords[k].compareTo(key);
986             if (result == 0) {
987                 return true;
988             }
989             if (result < 0) {
990                 i = k + 1;
991             } else {
992                 j = k;
993             }
994         }
995         return false;
996     }
997 
998     /***
999      * Converts the given Xml name to a legal Java identifier.  This is
1000      * slightly more efficient than makeJavaIdentifier in that we only need
1001      * to worry about '.', '-', and ':' in the string.  We also assume that
1002      * the resultant string is further concatenated with some prefix string
1003      * so that we don't have to worry about it being a Java key word.
1004      *
1005      * @param name Identifier to convert
1006      * @return Legal Java identifier corresponding to the given identifier
1007      */
1008     public static final String makeXmlJavaIdentifier(String name) {
1009         if (name.indexOf('-') >= 0)
1010             name = replace(name, '-', "$1");
1011         if (name.indexOf('.') >= 0)
1012             name = replace(name, '.', "$2");
1013         if (name.indexOf(':') >= 0)
1014             name = replace(name, ':', "$3");
1015         return name;
1016     }
1017 
1018     static InputStreamReader getReader(String fname, String encoding,
1019                                        JarFile jarFile,
1020                                        JspCompilationContext ctxt,
1021                                        ErrorDispatcher err)
1022             throws JasperException, IOException {
1023 
1024         InputStreamReader reader = null;
1025         InputStream in = getInputStream(fname, jarFile, ctxt, err);
1026 
1027         try {
1028             reader = new InputStreamReader(in, encoding);
1029         } catch (UnsupportedEncodingException ex) {
1030             err.jspError("jsp.error.unsupported.encoding", encoding);
1031         }
1032 
1033         return reader;
1034     }
1035 
1036     /***
1037      * Class.getName() return arrays in the form "[[[<et>", where et,
1038      * the element type can be one of ZBCDFIJS or L<classname>;
1039      * It is converted into forms that can be understood by javac.
1040      */
1041     public static String toJavaSourceType(String type) {
1042 
1043         if (type.charAt(0) != '[') {
1044             return type;
1045         }
1046 
1047         int dims = 1;
1048         String t = null;
1049         for (int i = 1; i < type.length(); i++) {
1050             if (type.charAt(i) == '[') {
1051                 dims++;
1052             } else {
1053                 switch (type.charAt(i)) {
1054                     case 'Z':
1055                         t = "boolean";
1056                         break;
1057                     case 'B':
1058                         t = "byte";
1059                         break;
1060                     case 'C':
1061                         t = "char";
1062                         break;
1063                     case 'D':
1064                         t = "double";
1065                         break;
1066                     case 'F':
1067                         t = "float";
1068                         break;
1069                     case 'I':
1070                         t = "int";
1071                         break;
1072                     case 'J':
1073                         t = "long";
1074                         break;
1075                     case 'S':
1076                         t = "short";
1077                         break;
1078                     case 'L':
1079                         t = type.substring(i + 1, type.indexOf(';'));
1080                         break;
1081                 }
1082                 break;
1083             }
1084         }
1085         StringBuffer resultType = new StringBuffer(t);
1086         for (; dims > 0; dims--) {
1087             resultType.append("[]");
1088         }
1089         return resultType.toString();
1090     }
1091 
1092     /***
1093      * Compute the canonical name from a Class instance.  Note that a
1094      * simple replacment of '$' with '.' of a binary name would not work,
1095      * as '$' is a legal Java Identifier character.
1096      *
1097      * @param c A instance of java.lang.Class
1098      * @return The canonical name of c.
1099      */
1100     public static String getCanonicalName(Class c) {
1101 
1102         String binaryName = c.getName();
1103         c = c.getDeclaringClass();
1104 
1105         if (c == null) {
1106             return binaryName;
1107         }
1108 
1109         StringBuffer buf = new StringBuffer(binaryName);
1110         do {
1111             buf.setCharAt(c.getName().length(), '.');
1112             c = c.getDeclaringClass();
1113         } while (c != null);
1114 
1115         return buf.toString();
1116     }
1117 }
1118