Index: pom.xml =================================================================== --- pom.xml (revision 960668) +++ pom.xml (working copy) @@ -120,6 +120,14 @@ + + + src/main/java + + **/*.properties + + + org.apache.maven.plugins @@ -137,7 +145,8 @@ org/apache/taglibs/standard/lang/jstl/test/StaticFunctionTests.java org/apache/taglibs/standard/TestVersion.java - org/apache/taglibs/standard/tag/**/Test*.java + org/apache/taglibs/standard/functions/TestFunctions.java + org/apache/taglibs/standard/tag/common/core/TestSetSupport.java Index: src/test/java/org/apache/taglibs/standard/functions/TestFunctions.java =================================================================== --- src/test/java/org/apache/taglibs/standard/functions/TestFunctions.java (revision 0) +++ src/test/java/org/apache/taglibs/standard/functions/TestFunctions.java (revision 0) @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.taglibs.standard.functions; + +import org.apache.taglibs.standard.resources.Resources; +import org.junit.Assert; +import org.junit.Test; + +import javax.servlet.jsp.JspTagException; +import java.util.Arrays; +import java.util.Collections; + +import static org.apache.taglibs.standard.functions.Functions.*; +/** + */ +public class TestFunctions { + + @Test + public void testSubstring() { + Assert.assertEquals("el", substring("Hello", 1, 3)); + Assert.assertEquals("", substring("Hello", 10, 0)); + Assert.assertEquals("He", substring("Hello", -1, 2)); + Assert.assertEquals("Hello", substring("Hello", -4, -1)); + Assert.assertEquals("ello", substring("Hello", 1, -1)); + Assert.assertEquals("ello", substring("Hello", 1, 10)); + Assert.assertEquals("", substring("Hello", 3, 1)); + Assert.assertEquals("", substring("Hello", 10, 6)); + Assert.assertEquals("Hello", substring("Hello", -1, -4)); + } + + @Test + public void testSubstringAfter() { + Assert.assertEquals("lo", substringAfter("Hello", "el")); + Assert.assertEquals("", substringAfter("", "el")); + Assert.assertEquals("Hello", substringAfter("Hello", "")); + Assert.assertEquals("", substringAfter("", "lx")); + Assert.assertEquals("lo All", substringAfter("Hello All", "l")); + } + + @Test + public void testSubstringBefore() { + Assert.assertEquals("H", substringBefore("Hello", "el")); + Assert.assertEquals("", substringBefore("", "el")); + Assert.assertEquals("", substringBefore("Hello", "")); + Assert.assertEquals("", substringBefore("", "lx")); + Assert.assertEquals("He", substringBefore("Hello All", "l")); + } + + @Test + public void testReplace() { + Assert.assertEquals("Hxxlo", replace("Hello", "el", "xx")); + Assert.assertEquals("Hexxxxo", replace("Hello", "l", "xx")); + Assert.assertEquals("", replace("", "l", "xx")); + Assert.assertEquals("Heo", replace("Hello", "l", "")); + Assert.assertEquals("Hello", replace("Hello", "", "xx")); + Assert.assertEquals("Hellllo", replace("Hello", "l", "ll")); + Assert.assertEquals("Hello", replace("Hello", "x", "ll")); + } + + @Test + public void testSplit() { + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, split("a:b:c", ":")); + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, split("a:b/c", ":/")); + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, split("a:b/c", "/:")); + Assert.assertArrayEquals(new String[]{"a", "b"}, split("a:b:", ":")); + Assert.assertArrayEquals(new String[]{"a:b:c"}, split("a:b:c", "x")); + Assert.assertArrayEquals(new String[]{""}, split("", "")); + Assert.assertArrayEquals(new String[]{""}, split("", ":")); + Assert.assertArrayEquals(new String[]{"Hello"}, split("Hello", "")); + } + + @Test + public void testJoin() { + Assert.assertEquals("a:b:c", join(new String[]{"a", "b", "c"}, ":")); + Assert.assertEquals("abc", join(new String[]{"a", "b", "c"}, "")); + Assert.assertEquals("axxbxxc", join(new String[]{"a", "b", "c"}, "xx")); + Assert.assertEquals("", join(null, "")); + Assert.assertEquals("", join(new String[]{}, ":")); + Assert.assertEquals("a:null:c", join(new String[]{"a", null, "c"}, ":")); + Assert.assertEquals("a", join(new String[]{"a"}, ":")); + Assert.assertEquals("null", join(new String[]{null}, ":")); + } + + @Test + public void testLength() throws Exception { + Assert.assertEquals(0, length(null)); + Assert.assertEquals(0, length("")); + Assert.assertEquals(3, length(new int[]{1,2,3})); + Assert.assertEquals(3, length(Arrays.asList(1,2,3))); + Assert.assertEquals(3, length(Arrays.asList(1,2,3).iterator())); + Assert.assertEquals(3, length(Collections.enumeration(Arrays.asList(1,2,3)))); + Assert.assertEquals(1, length(Collections.singletonMap("Hello", "World"))); + try { + length(3); + Assert.fail(); + } catch (JspTagException e) { + Assert.assertEquals(Resources.getMessage("PARAM_BAD_VALUE"), e.getMessage()); + } + } +} Index: src/main/java/org/apache/taglibs/standard/functions/Functions.java =================================================================== --- src/main/java/org/apache/taglibs/standard/functions/Functions.java (revision 960668) +++ src/main/java/org/apache/taglibs/standard/functions/Functions.java (working copy) @@ -30,7 +30,13 @@ import org.apache.taglibs.standard.tag.common.core.Util; /** - *

JSTL Functions

+ * Static functions that extend the Expression Language with standardized behaviour + * commonly used by page authors. + * + * Implementation Note: When passing a String parameter, section + * 1.18.2 of the EL specification requires the container to coerce a null value to an + * empty string. These implementation assume such behaviour and do not check for null + * parameters. Passing a null will generally trigger a NullPointerException. * * @author Pierre Delisle */ @@ -41,14 +47,22 @@ // String capitalization /** - * Converts all of the characters of the input string to upper case. + * Converts all of the characters of the input string to upper case according to the + * semantics of method String#toUpperCase(). + * + * @param input the input string on which the transformation to upper case is applied + * @return the input string transformed to upper case */ public static String toUpperCase(String input) { return input.toUpperCase(); } /** - * Converts all of the characters of the input string to lower case. + * Converts all of the characters of the input string to lower case according to the + * semantics of method String#toLowerCase(). + * + * @param input the input string on which the transformation to lower case is applied + * @return the input string transformed to lower case */ public static String toLowerCase(String input) { return input.toLowerCase(); @@ -56,52 +70,105 @@ //********************************************************************* // Substring processing - + + /** + * Returns the index (0-based) withing a string of the first occurrence of a specified + * substring according to the semantics of the method String#indexOf(). + * + * If substring is empty, this matches the beginning of the string and the + * value returned is 0. + * + * @param input the input string on which the function is applied + * @param substring the substring to search for in the input string + * @return the 0-based index of the first matching substring, or -1 if it does not occur + */ public static int indexOf(String input, String substring) { - if (input == null) input = ""; - if (substring == null) substring = ""; return input.indexOf(substring); - } + } + /** + * Tests if a string contains the specified substring. + * + * @param input the input string on which the function is applied + * @param substring the substring tested for + * @return true if the character sequence represented by the substring + * exists in the string + */ public static boolean contains(String input, String substring) { - return indexOf(input, substring) != -1; + return input.contains(substring); } + /** + * Tests if a string contains the specified substring in a case insensitive way. + * Equivalent to fn:contains(fn:toUpperCase(string), fn:toUpperCase(substring)). + * + * @param input the input string on which the function is applied + * @param substring the substring tested for + * @return true if the character sequence represented by the substring + * exists in the string + */ public static boolean containsIgnoreCase(String input, String substring) { - if (input == null) input = ""; - if (substring == null) substring = ""; - String inputUC = input.toUpperCase(); - String substringUC = substring.toUpperCase(); - return indexOf(inputUC, substringUC) != -1; - } + return contains(input.toUpperCase(), substring.toUpperCase()); + } - public static boolean startsWith(String input, String substring) { - if (input == null) input = ""; - if (substring == null) substring = ""; - return input.startsWith(substring); + /** + * Tests if a string starts with the specified prefix according to the semantics + * of String#startsWith(). + * + * @param input the input string on which the function is applied + * @param prefix the prefix to be matched + * @return true if the input string starts with the prefix + */ + public static boolean startsWith(String input, String prefix) { + return input.startsWith(prefix); } - public static boolean endsWith(String input, String substring) { - if (input == null) input = ""; - if (substring == null) substring = ""; - return input.endsWith(substring); - } - + /** + * Tests if a string ends with the specified suffix according to the semantics + * of String#endsWith(). + * + * @param input the input string on which the function is applied + * @param suffix the suffix to be matched + * @return true if the input string ends with the suffix + */ + public static boolean endsWith(String input, String suffix) { + return input.endsWith(suffix); + } + + /** + * Returns a subset of a string according to the semantics of String#substring() + * with additional semantics as follows: + *
    + *
  • if beginIndex < 0 its value is adjusted to 0
  • + *
  • if endIndex < 0 or greater than the string length, + * its value is adjusted to the length of the string
  • + *
  • if endIndex < beginIndex, an empty string is returned
  • + *
+ * + * @param input the input string on which the substring function is applied + * @param beginIndex the beginning index (0-based), inclusive + * @param endIndex the end index (0-based), exclusive + * @return a subset of string + */ public static String substring(String input, int beginIndex, int endIndex) { - if (input == null) input = ""; - if (beginIndex >= input.length()) return ""; if (beginIndex < 0) beginIndex = 0; if (endIndex < 0 || endIndex > input.length()) endIndex = input.length(); if (endIndex < beginIndex) return ""; return input.substring(beginIndex, endIndex); - } - + } + + /** + * Returns a subset of a string following the first occurrence of a specific substring. + * + * If the substring is empty, it matches the beginning of the input string and the + * entire input string is returned. If the substring does not occur, an empty string is returned. + * + * @param input the input string on which the substring function is applied + * @param substring the substring that delimits the beginning of the subset + * of the input string to be returned + * @return a substring of the input string that starts at the first character after the specified substring + */ public static String substringAfter(String input, String substring) { - if (input == null) input = ""; - if (input.length() == 0) return ""; - if (substring == null) substring = ""; - if (substring.length() == 0) return input; - int index = input.indexOf(substring); if (index == -1) { return ""; @@ -110,12 +177,18 @@ } } + /** + * Returns a subset of a string immediately before the first occurrence of a specific substring. + * + * If the substring is empty, it matches the beginning of the input string and an empty string is returned. + * If the substring does not occur, an empty string is returned. + * + * @param input the input string on which the substring function is applied + * @param substring the substring that delimits the beginning of the subset + * of the input string to be returned + * @return a substring of the input string that starts at the first character after the specified substring + */ public static String substringBefore(String input, String substring) { - if (input == null) input = ""; - if (input.length() == 0) return ""; - if (substring == null) substring = ""; - if (substring.length() == 0) return ""; - int index = input.indexOf(substring); if (index == -1) { return ""; @@ -126,75 +199,124 @@ //********************************************************************* // Character replacement - + + /** + * Escapes characters that could be interpreted as XML markup as defined by the <c:out> action. + * + * @param input the string to escape + * @return escaped string + */ public static String escapeXml(String input) { - if (input == null) return ""; return Util.escapeXml(input); } - + + /** + * removes whitespace from both ends of a string according to the semantics of String#trim(). + * + * @param input the input string to be trimmed + * @return the trimmed string + */ public static String trim(String input) { - if (input == null) return ""; return input.trim(); - } + } - public static String replace( - String input, - String substringBefore, - String substringAfter) + /** + * Returns a string resulting from replacing all occurrences of a "before" substring with an "after" substring. + * The string is processed once and not reprocessed for further replacements. + * + * @param input the string on which the replacement is to be applied + * @param before the substring to replace + * @param after the replacement substring + * @return a string with before replaced with after + */ + public static String replace(String input, String before, String after) { - if (input == null) input = ""; - if (input.length() == 0) return ""; - if (substringBefore == null) substringBefore = ""; - if (substringBefore.length() == 0) return input; - - StringBuffer buf = new StringBuffer(input.length()); - int startIndex = 0; - int index; - while ((index = input.indexOf(substringBefore, startIndex)) != -1) { - buf.append(input.substring(startIndex, index)).append(substringAfter); - startIndex = index + substringBefore.length(); + if (before.length() == 0) { + return input; } - return buf.append(input.substring(startIndex)).toString(); + return input.replace(before, after); } - - public static String[] split( - String input, - String delimiters) + + /** + * Splits a string into an array of substrings according to the semantics of StringTokenizer. + * If the input string is empty, a single element array containing an empty string is returned. + * If the delimiters are empty, a single element array containing the input string is returned. + * + * @param input the string to split + * @param delimiters characters used to split the string + * @return an array of strings + */ + public static String[] split(String input, String delimiters) { - String[] array; - if (input == null) input = ""; - if (input.length() == 0) { - array = new String[1]; - array[0] = ""; - return array; + if (input.length() == 0 || delimiters.length() == 0) { + return new String[] { input }; } - - if (delimiters == null) delimiters = ""; StringTokenizer tok = new StringTokenizer(input, delimiters); - int count = tok.countTokens(); - array = new String[count]; + String[] array = new String[tok.countTokens()]; int i = 0; while (tok.hasMoreTokens()) { array[i++] = tok.nextToken(); } return array; - } - + } + + /** + * Joins all elements of an array into a string. + * + * Implementation Note: The specification does not define what happens when + * elements in the array are null. For compatibility with previous implementations, the string + * "null" is used although EL conventions would suggest an empty string might be better. + * + * @param array an array of strings to be joined + * @param separator used to separate the joined strings + * @return all array elements joined into one string with the specified separator + */ + public static String join(String[] array, String separator) { + if (array == null || array.length == 0) { + return ""; + } + if (array.length == 1) { + return array[0] == null ? "null" : array[0]; + } + + StringBuilder buf = new StringBuilder(); + buf.append(array[0]); + for (int i = 1; i < array.length; i++) { + buf.append(separator).append(array[i]); + } + return buf.toString(); + } + //********************************************************************* // Collections processing - + + /** + * Returns the number of items in a collection or the number of characters in a string. + * The collection can be of any type supported for the items attribute of + * the <c:forEach> action. + * + * @param obj the collection or string whose length should be computed + * @return the length of the collection or string; 0 if obj is null + * @throws JspTagException if the type is not valid + */ public static int length(Object obj) throws JspTagException { - if (obj == null) return 0; + if (obj == null) { + return 0; + } - if (obj instanceof String) return ((String)obj).length(); - if (obj instanceof Collection) return ((Collection)obj).size(); - if (obj instanceof Map) return ((Map)obj).size(); - - int count = 0; + if (obj instanceof String) { + return ((String) obj).length(); + } + if (obj instanceof Collection) { + return ((Collection) obj).size(); + } + if (obj instanceof Map) { + return ((Map) obj).size(); + } if (obj instanceof Iterator) { + int count = 0; Iterator iter = (Iterator)obj; - count = 0; while (iter.hasNext()) { count++; iter.next(); @@ -203,30 +325,16 @@ } if (obj instanceof Enumeration) { Enumeration enum_ = (Enumeration)obj; - count = 0; + int count = 0; while (enum_.hasMoreElements()) { count++; enum_.nextElement(); } return count; } - try { - count = Array.getLength(obj); - return count; - } catch (IllegalArgumentException ex) {} - throw new JspTagException(Resources.getMessage("PARAM_BAD_VALUE")); + if (obj.getClass().isArray()) { + return Array.getLength(obj); + } + throw new JspTagException(Resources.getMessage("PARAM_BAD_VALUE")); } - - public static String join(String[] array, String separator) { - if (array == null) return ""; - if (separator == null) separator = ""; - - StringBuffer buf = new StringBuffer(); - for (int i=0; i