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.jasper.compiler;
18
19 import java.io.BufferedInputStream;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.UnsupportedEncodingException;
25 import java.util.ArrayList;
26
27 import org.apache.jasper.Constants;
28 import org.apache.jasper.JasperException;
29 import org.apache.jasper.JspCompilationContext;
30 import org.apache.tomcat.Jar;
31 import org.apache.tomcat.util.security.Escape;
32 import org.xml.sax.Attributes;
33 import org.xml.sax.InputSource;
34
35 /**
36  * This class has all the utility method(s). Ideally should move all the bean
37  * containers here.
38  *
39  * @author Mandar Raje.
40  * @author Rajiv Mordani.
41  * @author Danno Ferrin
42  * @author Pierre Delisle
43  * @author Shawn Bayern
44  * @author Mark Roth
45  */

46 public class JspUtil {
47
48     private static final String WEB_INF_TAGS = "/WEB-INF/tags/";
49     private static final String META_INF_TAGS = "/META-INF/tags/";
50
51     // Delimiters for request-time expressions (JSP and XML syntax)
52     private static final String OPEN_EXPR = "<%=";
53     private static final String CLOSE_EXPR = "%>";
54
55     private static final String javaKeywords[] = { "abstract""assert",
56             "boolean""break""byte""case""catch""char""class",
57             "const""continue""default""do""double""else""enum",
58             "extends""final""finally""float""for""goto""if",
59             "implements""import""instanceof""int""interface""long",
60             "native""new""package""private""protected""public",
61             "return""short""static""strictfp""super""switch",
62             "synchronized""this""throw""throws""transient""try",
63             "void""volatile""while" };
64
65     static final int JSP_INPUT_STREAM_BUFFER_SIZE = 1024;
66
67     public static final int CHUNKSIZE = 1024;
68
69     /**
70      * Takes a potential expression and converts it into XML form.
71      * @param expression The expression to convert
72      * @return XML view
73      */

74     public static String getExprInXml(String expression) {
75         String returnString;
76         int length = expression.length();
77
78         if (expression.startsWith(OPEN_EXPR) &&
79                 expression.endsWith(CLOSE_EXPR)) {
80             returnString = expression.substring(1, length - 1);
81         } else {
82             returnString = expression;
83         }
84
85         return Escape.xml(returnString);
86     }
87
88     /**
89      * Checks to see if the given scope is valid.
90      *
91      * @param scope
92      *            The scope to be checked
93      * @param n
94      *            The Node containing the 'scope' attribute whose value is to be
95      *            checked
96      * @param err
97      *            error dispatcher
98      *
99      * @throws JasperException
100      *             if scope is not null and different from &quot;page&quot;,
101      *             &quot;request&quot;, &quot;session&quot;, and
102      *             &quot;application&quot;
103      */

104     public static void checkScope(String scope, Node n, ErrorDispatcher err)
105             throws JasperException {
106         if (scope != null && !scope.equals("page") && !scope.equals("request")
107                 && !scope.equals("session") && !scope.equals("application")) {
108             err.jspError(n, "jsp.error.invalid.scope", scope);
109         }
110     }
111
112     /**
113      * Checks if all mandatory attributes are present and if all attributes
114      * present have valid names. Checks attributes specified as XML-style
115      * attributes as well as attributes specified using the jsp:attribute
116      * standard action.
117      * @param typeOfTag The tag type
118      * @param n The corresponding node
119      * @param validAttributes The array with the valid attributes
120      * @param err Dispatcher for errors
121      * @throws JasperException An error occurred
122      */

123     public static void checkAttributes(String typeOfTag, Node n,
124             ValidAttribute[] validAttributes, ErrorDispatcher err)
125             throws JasperException {
126         Attributes attrs = n.getAttributes();
127         Mark start = n.getStart();
128         boolean valid = true;
129
130         // AttributesImpl.removeAttribute is broken, so we do this...
131         int tempLength = (attrs == null) ? 0 : attrs.getLength();
132         ArrayList<String> temp = new ArrayList<>(tempLength);
133         for (int i = 0; i < tempLength; i++) {
134             @SuppressWarnings("null")  // If attrs==null, tempLength == 0
135             String qName = attrs.getQName(i);
136             if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:"))) {
137                 temp.add(qName);
138             }
139         }
140
141         // Add names of attributes specified using jsp:attribute
142         Node.Nodes tagBody = n.getBody();
143         if (tagBody != null) {
144             int numSubElements = tagBody.size();
145             for (int i = 0; i < numSubElements; i++) {
146                 Node node = tagBody.getNode(i);
147                 if (node instanceof Node.NamedAttribute) {
148                     String attrName = node.getAttributeValue("name");
149                     temp.add(attrName);
150                     // Check if this value appear in the attribute of the node
151                     if (n.getAttributeValue(attrName) != null) {
152                         err.jspError(n,
153                                 "jsp.error.duplicate.name.jspattribute",
154                                 attrName);
155                     }
156                 } else {
157                     // Nothing can come before jsp:attribute, and only
158                     // jsp:body can come after it.
159                     break;
160                 }
161             }
162         }
163
164         /*
165          * First check to see if all the mandatory attributes are present. If so
166          * only then proceed to see if the other attributes are valid for the
167          * particular tag.
168          */

169         String missingAttribute = null;
170
171         for (int i = 0; i < validAttributes.length; i++) {
172             int attrPos;
173             if (validAttributes[i].mandatory) {
174                 attrPos = temp.indexOf(validAttributes[i].name);
175                 if (attrPos != -1) {
176                     temp.remove(attrPos);
177                     valid = true;
178                 } else {
179                     valid = false;
180                     missingAttribute = validAttributes[i].name;
181                     break;
182                 }
183             }
184         }
185
186         // If mandatory attribute is missing then the exception is thrown
187         if (!valid) {
188             err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag,
189                     missingAttribute);
190         }
191
192         // Check to see if there are any more attributes for the specified tag.
193         int attrLeftLength = temp.size();
194         if (attrLeftLength == 0) {
195             return;
196         }
197
198         // Now check to see if the rest of the attributes are valid too.
199         for(String attribute : temp) {
200             valid = false;
201             for (int i = 0; i < validAttributes.length; i++) {
202                 if (attribute.equals(validAttributes[i].name)) {
203                     valid = true;
204                     break;
205                 }
206             }
207             if (!valid) {
208                 err.jspError(start, "jsp.error.invalid.attribute", typeOfTag,
209                         attribute);
210             }
211         }
212         // XXX *could* move EL-syntax validation here... (sb)
213     }
214
215     public static class ValidAttribute {
216
217         private final String name;
218         private final boolean mandatory;
219
220         public ValidAttribute(String name, boolean mandatory) {
221             this.name = name;
222             this.mandatory = mandatory;
223         }
224
225         public ValidAttribute(String name) {
226             this(name, false);
227         }
228     }
229
230     /**
231      * Convert a String value to 'boolean'. Besides the standard conversions
232      * done by Boolean.parseBoolean(s), the value "yes" (ignore case)
233      * is also converted to 'true'. If 's' is null, then 'false' is returned.
234      *
235      * @param s
236      *            the string to be converted
237      * @return the boolean value associated with the string s
238      */

239     public static boolean booleanValue(String s) {
240         boolean b = false;
241         if (s != null) {
242             if (s.equalsIgnoreCase("yes")) {
243                 b = true;
244             } else {
245                 b = Boolean.parseBoolean(s);
246             }
247         }
248         return b;
249     }
250
251     /**
252      * Returns the <code>Class</code> object associated with the class or
253      * interface with the given string name.
254      *
255      * <p>
256      * The <code>Class</code> object is determined by passing the given string
257      * name to the <code>Class.forName()</code> method, unless the given string
258      * name represents a primitive type, in which case it is converted to a
259      * <code>Class</code> object by appending ".class" to it (e.g.,
260      * "int.class").
261      * @param type The class name, array or primitive type
262      * @param loader The class loader
263      * @return the loaded class
264      * @throws ClassNotFoundException Loading class failed
265      */

266     public static Class<?> toClass(String type, ClassLoader loader)
267             throws ClassNotFoundException {
268
269         Class<?> c = null;
270         int i0 = type.indexOf('[');
271         int dims = 0;
272         if (i0 > 0) {
273             // This is an array. Count the dimensions
274             for (int i = 0; i < type.length(); i++) {
275                 if (type.charAt(i) == '[') {
276                     dims++;
277                 }
278             }
279             type = type.substring(0, i0);
280         }
281
282         if ("boolean".equals(type)) {
283             c = boolean.class;
284         } else if ("char".equals(type)) {
285             c = char.class;
286         } else if ("byte".equals(type)) {
287             c = byte.class;
288         } else if ("short".equals(type)) {
289             c = short.class;
290         } else if ("int".equals(type)) {
291             c = int.class;
292         } else if ("long".equals(type)) {
293             c = long.class;
294         } else if ("float".equals(type)) {
295             c = float.class;
296         } else if ("double".equals(type)) {
297             c = double.class;
298         } else if ("void".equals(type)) {
299             c = void.class;
300         } else {
301             c = loader.loadClass(type);
302         }
303
304         if (dims == 0) {
305             return c;
306         }
307
308         if (dims == 1) {
309             return java.lang.reflect.Array.newInstance(c, 1).getClass();
310         }
311
312         // Array of more than i dimension
313         return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass();
314     }
315
316     /**
317      * Produces a String representing a call to the EL interpreter.
318      *
319      * @param isTagFile <code>true</code> if the file is a tag file
320      *  rather than a JSP
321      * @param expression
322      *            a String containing zero or more "${}" expressions
323      * @param expectedType
324      *            the expected type of the interpreted result
325      * @param fnmapvar
326      *            Variable pointing to a function map.
327      * @return a String representing a call to the EL interpreter.
328      */

329     public static String interpreterCall(boolean isTagFile, String expression,
330             Class<?> expectedType, String fnmapvar) {
331         /*
332          * Determine which context object to use.
333          */

334         String jspCtxt = null;
335         if (isTagFile) {
336             jspCtxt = "this.getJspContext()";
337         } else {
338             jspCtxt = "_jspx_page_context";
339         }
340
341         /*
342          * Determine whether to use the expected type's textual name or, if it's
343          * a primitive, the name of its correspondent boxed type.
344          */

345         String returnType = expectedType.getCanonicalName();
346         String targetType = returnType;
347         String primitiveConverterMethod = null;
348         if (expectedType.isPrimitive()) {
349             if (expectedType.equals(Boolean.TYPE)) {
350                 returnType = Boolean.class.getName();
351                 primitiveConverterMethod = "booleanValue";
352             } else if (expectedType.equals(Byte.TYPE)) {
353                 returnType = Byte.class.getName();
354                 primitiveConverterMethod = "byteValue";
355             } else if (expectedType.equals(Character.TYPE)) {
356                 returnType = Character.class.getName();
357                 primitiveConverterMethod = "charValue";
358             } else if (expectedType.equals(Short.TYPE)) {
359                 returnType = Short.class.getName();
360                 primitiveConverterMethod = "shortValue";
361             } else if (expectedType.equals(Integer.TYPE)) {
362                 returnType = Integer.class.getName();
363                 primitiveConverterMethod = "intValue";
364             } else if (expectedType.equals(Long.TYPE)) {
365                 returnType = Long.class.getName();
366                 primitiveConverterMethod = "longValue";
367             } else if (expectedType.equals(Float.TYPE)) {
368                 returnType = Float.class.getName();
369                 primitiveConverterMethod = "floatValue";
370             } else if (expectedType.equals(Double.TYPE)) {
371                 returnType = Double.class.getName();
372                 primitiveConverterMethod = "doubleValue";
373             }
374         }
375
376         /*
377          * Build up the base call to the interpreter.
378          */

379         // XXX - We use a proprietary call to the interpreter for now
380         // as the current standard machinery is inefficient and requires
381         // lots of wrappers and adapters. This should all clear up once
382         // the EL interpreter moves out of JSTL and into its own project.
383         // In the future, this should be replaced by code that calls
384         // ExpressionEvaluator.parseExpression() and then cache the resulting
385         // expression objects. The interpreterCall would simply select
386         // one of the pre-cached expressions and evaluate it.
387         // Note that PageContextImpl implements VariableResolver and
388         // the generated Servlet/SimpleTag implements FunctionMapper, so
389         // that machinery is already in place (mroth).
390         targetType = toJavaSourceType(targetType);
391         StringBuilder call = new StringBuilder(
392                 "("
393                         + returnType
394                         + ") "
395                         + "org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate"
396                         + "(" + Generator.quote(expression) + ", " + targetType
397                         + ".class, " + "(javax.servlet.jsp.PageContext)" + jspCtxt + ", "
398                         + fnmapvar + ")");
399
400         /*
401          * Add the primitive converter method if we need to.
402          */

403         if (primitiveConverterMethod != null) {
404             call.insert(0, "(");
405             call.append(")." + primitiveConverterMethod + "()");
406         }
407
408         return call.toString();
409     }
410
411     public static String coerceToPrimitiveBoolean(String s,
412             boolean isNamedAttribute) {
413         if (isNamedAttribute) {
414             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToBoolean("
415                     + s + ")";
416         } else {
417             if (s == null || s.length() == 0) {
418                 return "false";
419             } else {
420                 return Boolean.valueOf(s).toString();
421             }
422         }
423     }
424
425     public static String coerceToBoolean(String s, boolean isNamedAttribute) {
426         if (isNamedAttribute) {
427             return "(java.lang.Boolean) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
428                     + s + ", java.lang.Boolean.class)";
429         } else {
430             if (s == null || s.length() == 0) {
431                 return "new java.lang.Boolean(false)";
432             } else {
433                 // Detect format error at translation time
434                 return "new java.lang.Boolean(" + Boolean.valueOf(s).toString() + ")";
435             }
436         }
437     }
438
439     public static String coerceToPrimitiveByte(String s,
440             boolean isNamedAttribute) {
441         if (isNamedAttribute) {
442             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToByte("
443                     + s + ")";
444         } else {
445             if (s == null || s.length() == 0) {
446                 return "(byte) 0";
447             } else {
448                 return "((byte)" + Byte.valueOf(s).toString() + ")";
449             }
450         }
451     }
452
453     public static String coerceToByte(String s, boolean isNamedAttribute) {
454         if (isNamedAttribute) {
455             return "(java.lang.Byte) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
456                     + s + ", java.lang.Byte.class)";
457         } else {
458             if (s == null || s.length() == 0) {
459                 return "new java.lang.Byte((byte) 0)";
460             } else {
461                 // Detect format error at translation time
462                 return "new java.lang.Byte((byte)" + Byte.valueOf(s).toString() + ")";
463             }
464         }
465     }
466
467     public static String coerceToChar(String s, boolean isNamedAttribute) {
468         if (isNamedAttribute) {
469             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToChar("
470                     + s + ")";
471         } else {
472             if (s == null || s.length() == 0) {
473                 return "(char) 0";
474             } else {
475                 char ch = s.charAt(0);
476                 // this trick avoids escaping issues
477                 return "((char) " + (int) ch + ")";
478             }
479         }
480     }
481
482     public static String coerceToCharacter(String s, boolean isNamedAttribute) {
483         if (isNamedAttribute) {
484             return "(java.lang.Character) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
485                     + s + ", java.lang.Character.class)";
486         } else {
487             if (s == null || s.length() == 0) {
488                 return "new java.lang.Character((char) 0)";
489             } else {
490                 char ch = s.charAt(0);
491                 // this trick avoids escaping issues
492                 return "new java.lang.Character((char) " + (int) ch + ")";
493             }
494         }
495     }
496
497     public static String coerceToPrimitiveDouble(String s,
498             boolean isNamedAttribute) {
499         if (isNamedAttribute) {
500             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToDouble("
501                     + s + ")";
502         } else {
503             if (s == null || s.length() == 0) {
504                 return "(double) 0";
505             } else {
506                 return Double.valueOf(s).toString();
507             }
508         }
509     }
510
511     public static String coerceToDouble(String s, boolean isNamedAttribute) {
512         if (isNamedAttribute) {
513             return "(java.lang.Double) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
514                     + s + ", Double.class)";
515         } else {
516             if (s == null || s.length() == 0) {
517                 return "new java.lang.Double(0)";
518             } else {
519                 // Detect format error at translation time
520                 return "new java.lang.Double(" + Double.valueOf(s).toString() + ")";
521             }
522         }
523     }
524
525     public static String coerceToPrimitiveFloat(String s,
526             boolean isNamedAttribute) {
527         if (isNamedAttribute) {
528             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToFloat("
529                     + s + ")";
530         } else {
531             if (s == null || s.length() == 0) {
532                 return "(float) 0";
533             } else {
534                 return Float.valueOf(s).toString() + "f";
535             }
536         }
537     }
538
539     public static String coerceToFloat(String s, boolean isNamedAttribute) {
540         if (isNamedAttribute) {
541             return "(java.lang.Float) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
542                     + s + ", java.lang.Float.class)";
543         } else {
544             if (s == null || s.length() == 0) {
545                 return "new java.lang.Float(0)";
546             } else {
547                 // Detect format error at translation time
548                 return "new java.lang.Float(" + Float.valueOf(s).toString() + "f)";
549             }
550         }
551     }
552
553     public static String coerceToInt(String s, boolean isNamedAttribute) {
554         if (isNamedAttribute) {
555             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToInt("
556                     + s + ")";
557         } else {
558             if (s == null || s.length() == 0) {
559                 return "0";
560             } else {
561                 return Integer.valueOf(s).toString();
562             }
563         }
564     }
565
566     public static String coerceToInteger(String s, boolean isNamedAttribute) {
567         if (isNamedAttribute) {
568             return "(java.lang.Integer) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
569                     + s + ", java.lang.Integer.class)";
570         } else {
571             if (s == null || s.length() == 0) {
572                 return "new java.lang.Integer(0)";
573             } else {
574                 // Detect format error at translation time
575                 return "new java.lang.Integer(" + Integer.valueOf(s).toString() + ")";
576             }
577         }
578     }
579
580     public static String coerceToPrimitiveShort(String s,
581             boolean isNamedAttribute) {
582         if (isNamedAttribute) {
583             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToShort("
584                     + s + ")";
585         } else {
586             if (s == null || s.length() == 0) {
587                 return "(short) 0";
588             } else {
589                 return "((short) " + Short.valueOf(s).toString() + ")";
590             }
591         }
592     }
593
594     public static String coerceToShort(String s, boolean isNamedAttribute) {
595         if (isNamedAttribute) {
596             return "(java.lang.Short) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
597                     + s + ", java.lang.Short.class)";
598         } else {
599             if (s == null || s.length() == 0) {
600                 return "new java.lang.Short((short) 0)";
601             } else {
602                 // Detect format error at translation time
603                 return "new java.lang.Short(\"" + Short.valueOf(s).toString() + "\")";
604             }
605         }
606     }
607
608     public static String coerceToPrimitiveLong(String s,
609             boolean isNamedAttribute) {
610         if (isNamedAttribute) {
611             return "org.apache.jasper.runtime.JspRuntimeLibrary.coerceToLong("
612                     + s + ")";
613         } else {
614             if (s == null || s.length() == 0) {
615                 return "(long) 0";
616             } else {
617                 return Long.valueOf(s).toString() + "l";
618             }
619         }
620     }
621
622     public static String coerceToLong(String s, boolean isNamedAttribute) {
623         if (isNamedAttribute) {
624             return "(java.lang.Long) org.apache.jasper.runtime.JspRuntimeLibrary.coerce("
625                     + s + ", java.lang.Long.class)";
626         } else {
627             if (s == null || s.length() == 0) {
628                 return "new java.lang.Long(0)";
629             } else {
630                 // Detect format error at translation time
631                 return "new java.lang.Long(" + Long.valueOf(s).toString() + "l)";
632             }
633         }
634     }
635
636     public static BufferedInputStream getInputStream(String fname, Jar jar,
637             JspCompilationContext ctxt) throws IOException {
638
639         InputStream in = null;
640
641         if (jar != null) {
642             String jarEntryName = fname.substring(1, fname.length());
643             in = jar.getInputStream(jarEntryName);
644         } else {
645             in = ctxt.getResourceAsStream(fname);
646         }
647
648         if (in == null) {
649             throw new FileNotFoundException(Localizer.getMessage(
650                     "jsp.error.file.not.found", fname));
651         }
652
653         return new BufferedInputStream(in, JspUtil.JSP_INPUT_STREAM_BUFFER_SIZE);
654     }
655
656     public static InputSource getInputSource(String fname, Jar jar, JspCompilationContext ctxt)
657         throws IOException {
658         InputSource source;
659         if (jar != null) {
660             String jarEntryName = fname.substring(1, fname.length());
661             source = new InputSource(jar.getInputStream(jarEntryName));
662             source.setSystemId(jar.getURL(jarEntryName));
663         } else {
664             source = new InputSource(ctxt.getResourceAsStream(fname));
665             source.setSystemId(ctxt.getResource(fname).toExternalForm());
666         }
667         return source;
668     }
669
670     /**
671      * Gets the fully-qualified class name of the tag handler corresponding to
672      * the given tag file path.
673      *
674      * @param path Tag file path
675      * @param urn The tag identifier
676      * @param err Error dispatcher
677      *
678      * @return Fully-qualified class name of the tag handler corresponding to
679      *         the given tag file path
680      * @throws JasperException Failed to generate a class name for the tag
681      */

682     public static String getTagHandlerClassName(String path, String urn,
683             ErrorDispatcher err) throws JasperException {
684
685
686         String className = null;
687         int begin = 0;
688         int index;
689
690         index = path.lastIndexOf(".tag");
691         if (index == -1) {
692             err.jspError("jsp.error.tagfile.badSuffix", path);
693         }
694
695         // It's tempting to remove the ".tag" suffix here, but we can't.
696         // If we remove it, the fully-qualified class name of this tag
697         // could conflict with the package name of other tags.
698         // For instance, the tag file
699         // /WEB-INF/tags/foo.tag
700         // would have fully-qualified class name
701         // org.apache.jsp.tag.web.foo
702         // which would conflict with the package name of the tag file
703         // /WEB-INF/tags/foo/bar.tag
704
705         index = path.indexOf(WEB_INF_TAGS);
706         if (index != -1) {
707             className = Constants.TAG_FILE_PACKAGE_NAME + ".web.";
708             begin = index + WEB_INF_TAGS.length();
709         } else {
710             index = path.indexOf(META_INF_TAGS);
711             if (index != -1) {
712                 className = getClassNameBase(urn);
713                 begin = index + META_INF_TAGS.length();
714             } else {
715                 err.jspError("jsp.error.tagfile.illegalPath", path);
716             }
717         }
718
719         className += makeJavaPackage(path.substring(begin));
720
721         return className;
722     }
723
724     private static String getClassNameBase(String urn) {
725         StringBuilder base =
726                 new StringBuilder(Constants.TAG_FILE_PACKAGE_NAME + ".meta.");
727         if (urn != null) {
728             base.append(makeJavaPackage(urn));
729             base.append('.');
730         }
731         return base.toString();
732     }
733
734     /**
735      * Converts the given path to a Java package or fully-qualified class name
736      *
737      * @param path
738      *            Path to convert
739      *
740      * @return Java package corresponding to the given path
741      */

742     public static final String makeJavaPackage(String path) {
743         String classNameComponents[] = path.split("/");
744         StringBuilder legalClassNames = new StringBuilder();
745         for (int i = 0; i < classNameComponents.length; i++) {
746             if (classNameComponents[i].length() > 0) {
747                 if (legalClassNames.length() > 0) {
748                     legalClassNames.append('.');
749                 }
750                 legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
751             }
752         }
753         return legalClassNames.toString();
754     }
755
756     /**
757      * Converts the given identifier to a legal Java identifier
758      *
759      * @param identifier
760      *            Identifier to convert
761      *
762      * @return Legal Java identifier corresponding to the given identifier
763      */

764     public static final String makeJavaIdentifier(String identifier) {
765         return makeJavaIdentifier(identifier, true);
766     }
767
768     /**
769      * Converts the given identifier to a legal Java identifier
770      * to be used for JSP Tag file attribute names.
771      *
772      * @param identifier
773      *            Identifier to convert
774      *
775      * @return Legal Java identifier corresponding to the given identifier
776      */

777     public static final String makeJavaIdentifierForAttribute(String identifier) {
778         return makeJavaIdentifier(identifier, false);
779     }
780
781     /**
782      * Converts the given identifier to a legal Java identifier.
783      *
784      * @param identifier
785      *            Identifier to convert
786      *
787      * @return Legal Java identifier corresponding to the given identifier
788      */

789     private static final String makeJavaIdentifier(String identifier,
790             boolean periodToUnderscore) {
791         StringBuilder modifiedIdentifier = new StringBuilder(identifier.length());
792         if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
793             modifiedIdentifier.append('_');
794         }
795         for (int i = 0; i < identifier.length(); i++) {
796             char ch = identifier.charAt(i);
797             if (Character.isJavaIdentifierPart(ch) &&
798                     (ch != '_' || !periodToUnderscore)) {
799                 modifiedIdentifier.append(ch);
800             } else if (ch == '.' && periodToUnderscore) {
801                 modifiedIdentifier.append('_');
802             } else {
803                 modifiedIdentifier.append(mangleChar(ch));
804             }
805         }
806         if (isJavaKeyword(modifiedIdentifier.toString())) {
807             modifiedIdentifier.append('_');
808         }
809         return modifiedIdentifier.toString();
810     }
811
812     /**
813      * Mangle the specified character to create a legal Java class name.
814      * @param ch The character
815      * @return the replacement character as a string
816      */

817     public static final String mangleChar(char ch) {
818         char[] result = new char[5];
819         result[0] = '_';
820         result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
821         result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
822         result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
823         result[4] = Character.forDigit(ch & 0xf, 16);
824         return new String(result);
825     }
826
827     /**
828      * Test whether the argument is a Java keyword.
829      * @param key The name
830      * @return <code>true</code> if the name is a java identifier
831      */

832     public static boolean isJavaKeyword(String key) {
833         int i = 0;
834         int j = javaKeywords.length;
835         while (i < j) {
836             int k = (i + j) >>> 1;
837             int result = javaKeywords[k].compareTo(key);
838             if (result == 0) {
839                 return true;
840             }
841             if (result < 0) {
842                 i = k + 1;
843             } else {
844                 j = k;
845             }
846         }
847         return false;
848     }
849
850     static InputStreamReader getReader(String fname, String encoding,
851             Jar jar, JspCompilationContext ctxt, ErrorDispatcher err)
852             throws JasperException, IOException {
853
854         return getReader(fname, encoding, jar, ctxt, err, 0);
855     }
856
857     static InputStreamReader getReader(String fname, String encoding,
858             Jar jar, JspCompilationContext ctxt, ErrorDispatcher err, int skip)
859             throws JasperException, IOException {
860
861         InputStreamReader reader = null;
862         InputStream in = getInputStream(fname, jar, ctxt);
863         try {
864             for (int i = 0; i < skip; i++) {
865                 in.read();
866             }
867         } catch (IOException ioe) {
868             try {
869                 in.close();
870             } catch (IOException e) {
871                 // Ignore
872             }
873             throw ioe;
874         }
875         try {
876             reader = new InputStreamReader(in, encoding);
877         } catch (UnsupportedEncodingException ex) {
878             err.jspError("jsp.error.unsupported.encoding", encoding);
879         }
880
881         return reader;
882     }
883
884     /**
885      * Handles taking input from TLDs 'java.lang.Object' -&gt;
886      * 'java.lang.Object.class' 'int' -&gt; 'int.class' 'void' -&gt; 'Void.TYPE'
887      * 'int[]' -&gt; 'int[].class'
888      *
889      * @param type The type from the TLD
890      * @return the Java type
891      */

892     public static String toJavaSourceTypeFromTld(String type) {
893         if (type == null || "void".equals(type)) {
894             return "java.lang.Void.TYPE";
895         }
896         return type + ".class";
897     }
898
899     /**
900      * Class.getName() return arrays in the form "[[[&lt;et&gt;", where et, the
901      * element type can be one of ZBCDFIJS or L&lt;classname&gt;;. It is
902      * converted into forms that can be understood by javac.
903      * @param type the type to convert
904      * @return the equivalent type in Java sources
905      */

906     public static String toJavaSourceType(String type) {
907
908         if (type.charAt(0) != '[') {
909             return type;
910         }
911
912         int dims = 1;
913         String t = null;
914         for (int i = 1; i < type.length(); i++) {
915             if (type.charAt(i) == '[') {
916                 dims++;
917             } else {
918                 switch (type.charAt(i)) {
919                 case 'Z': t = "boolean"break;
920                 case 'B': t = "byte"break;
921                 case 'C': t = "char"break;
922                 case 'D': t = "double"break;
923                 case 'F': t = "float"break;
924                 case 'I': t = "int"break;
925                 case 'J': t = "long"break;
926                 case 'S': t = "short"break;
927                 case 'L': t = type.substring(i+1, type.indexOf(';')); break;
928                 }
929                 break;
930             }
931         }
932
933         if (t == null) {
934             // Should never happen
935             throw new IllegalArgumentException(Localizer.getMessage("jsp.error.unable.getType", type));
936         }
937
938         StringBuilder resultType = new StringBuilder(t);
939         for (; dims > 0; dims--) {
940             resultType.append("[]");
941         }
942         return resultType.toString();
943     }
944 }
945