Merge branch 'apache:2.3-gae' into make-build-reproducible
diff --git a/build.gradle.kts b/build.gradle.kts
index d6f5f67..bc8cad3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -54,8 +54,7 @@
 
 freemarkerRoot {
     configureSourceSet(SourceSet.MAIN_SOURCE_SET_NAME) { enableTests() }
-    configureSourceSet("jsp20")
-    configureSourceSet("jsp21") { enableTests() }
+    configureSourceSet("javaxServlet") { enableTests() }
     configureSourceSet("jython20")
     configureSourceSet("jython22")
     configureSourceSet("jython25") { enableTests() }
@@ -120,7 +119,7 @@
 configurations {
     register("combinedClasspath") {
         extendsFrom(named("jython25CompileClasspath").get())
-        extendsFrom(named("jsp21CompileClasspath").get())
+        extendsFrom(named("javaxServletCompileClasspath").get())
     }
 }
 
@@ -509,17 +508,11 @@
             configurations["combinedClasspath"],
             configurations["core16CompileClasspath"],
             configurations["testUtilsCompileClasspath"],
-            configurations["jsp21TestCompileClasspath"]
+            configurations["javaxServletTestCompileClasspath"]
         )
     }
 }
 
-// Choose the Jetty version very carefully, as it should implement the same Servlet API, JSP API, and EL API
-// what we declare below, because the same classes will come from Jetty as well. For example, Jetty depends
-// on org.mortbay.jasper:apache-el, which contains the javax.el classes, along with non-javax.el classes, so you
-// can't even exclude it. Similarly, org.eclipse.jetty:apache-jsp contains the JSP API javax.servlet.jsp classes,
-// yet again along with other classes. Anyway, this mess is temporary, as we will migrate to Jakarta, and only
-// support that.
 val jettyVersion = "9.4.53.v20231009"
 val slf4jVersion = "1.6.1"
 val springVersion = "2.5.6.SEC03"
@@ -530,10 +523,12 @@
         exclude(group = "xml-apis", module = "xml-apis")
     }
 
-    "jsp21TestImplementation" {
+    "javaxServletTestImplementation" {
         extendsFrom(compileClasspath.get())
+        // Exclude classes that are also coming from Jetty, which we use for testing:
         exclude(group = "javax.servlet.jsp")
         exclude(group = "javax.servlet", module = "servlet-api")
+        exclude(group = "javax.el", module = "el-api")
     }
 }
 
@@ -559,27 +554,24 @@
 
     testImplementation(xalan)
 
-    "jsp20CompileOnly"("javax.servlet.jsp:jsp-api:2.0")
-    "jsp20CompileOnly"("javax.servlet:servlet-api:2.4")
+    "javaxServletCompileOnly"("javax.servlet:javax.servlet-api:3.0.1")
+    "javaxServletCompileOnly"("javax.servlet.jsp:jsp-api:2.2")
+    "javaxServletCompileOnly"("javax.el:el-api:2.2")
 
-    "jsp21CompileOnly"(sourceSets["jsp20"].output)
-    "jsp21CompileOnly"("javax.servlet.jsp:jsp-api:2.1")
-    "jsp21CompileOnly"("javax.servlet:servlet-api:2.5")
-
-    "jsp21TestImplementation"("org.eclipse.jetty:jetty-server:${jettyVersion}")
-    "jsp21TestImplementation"("org.eclipse.jetty:jetty-webapp:${jettyVersion}")
-    "jsp21TestImplementation"("org.eclipse.jetty:jetty-util:${jettyVersion}")
-    "jsp21TestImplementation"("org.eclipse.jetty:apache-jsp:${jettyVersion}")
-    // Jetty also contains the servlet-api and jsp-api classes
+    "javaxServletTestImplementation"("org.eclipse.jetty:jetty-server:${jettyVersion}")
+    "javaxServletTestImplementation"("org.eclipse.jetty:jetty-webapp:${jettyVersion}")
+    "javaxServletTestImplementation"("org.eclipse.jetty:jetty-util:${jettyVersion}")
+    "javaxServletTestImplementation"("org.eclipse.jetty:apache-jsp:${jettyVersion}")
+    // Jetty also contains the servlet-api and jsp-api and el-api classes
 
     // JSP JSTL (not included in Jetty):
-    "jsp21TestImplementation"("org.apache.taglibs:taglibs-standard-impl:${tagLibsVersion}")
-    "jsp21TestImplementation"("org.apache.taglibs:taglibs-standard-spec:${tagLibsVersion}")
+    "javaxServletTestImplementation"("org.apache.taglibs:taglibs-standard-impl:${tagLibsVersion}")
+    "javaxServletTestImplementation"("org.apache.taglibs:taglibs-standard-spec:${tagLibsVersion}")
 
-    "jsp21TestImplementation"("org.springframework:spring-core:${springVersion}") {
+    "javaxServletTestImplementation"("org.springframework:spring-core:${springVersion}") {
         exclude(group = "commons-logging", module = "commons-logging")
     }
-    "jsp21TestImplementation"("org.springframework:spring-test:${springVersion}") {
+    "javaxServletTestImplementation"("org.springframework:spring-test:${springVersion}") {
         exclude(group = "commons-logging", module = "commons-logging")
     }
 
diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
index 6683f0a..dfdd9b3 100644
--- a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
+++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
@@ -19,7 +19,6 @@
 
 package freemarker.build
 
-import java.util.concurrent.atomic.AtomicBoolean
 import org.gradle.api.NamedDomainObjectProvider
 import org.gradle.api.Project
 import org.gradle.api.artifacts.VersionCatalogsExtension
@@ -42,6 +41,7 @@
 import org.gradle.language.base.plugins.LifecycleBasePlugin
 import org.gradle.language.jvm.tasks.ProcessResources
 import org.gradle.testing.base.TestingExtension
+import java.util.concurrent.atomic.AtomicBoolean
 
 private const val TEST_UTILS_SOURCE_SET_NAME = "test-utils"
 
@@ -94,7 +94,7 @@
     val compilerVersion: JavaLanguageVersion
 ) {
     val main = sourceSetName == SourceSet.MAIN_SOURCE_SET_NAME
-    val baseDirName = if (main) "core" else sourceSetName
+    val baseDirName = if (main) "core" else sourceSetName.camelCaseToDashed()
 
     val sourceSet = context.sourceSets.maybeCreate(sourceSetName)
 
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory2.java b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerStringExtensions.kt
similarity index 78%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory2.java
rename to buildSrc/src/main/kotlin/freemarker/build/FreemarkerStringExtensions.kt
index 2eed561..b290760 100644
--- a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory2.java
+++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerStringExtensions.kt
@@ -17,14 +17,9 @@
  * under the License.
  */
 
-package freemarker.ext.jsp;
+package freemarker.build
 
+private val CAMEL_HUMP = "(?<=[A-Za-z0-9])[A-Z]".toRegex()
 
-/**
- */
-class FreeMarkerJspFactory2 extends FreeMarkerJspFactory {
-    @Override
-    protected String getSpecificationVersion() {
-        return "2.0";
-    }
-}
\ No newline at end of file
+fun String.camelCaseToDashed(): String =
+    replace(CAMEL_HUMP) { "-" + it.value.replaceFirstChar(Char::lowercaseChar) }
diff --git a/freemarker-core/src/main/java/freemarker/core/Configurable.java b/freemarker-core/src/main/java/freemarker/core/Configurable.java
index 1882025..fc98db5 100644
--- a/freemarker-core/src/main/java/freemarker/core/Configurable.java
+++ b/freemarker-core/src/main/java/freemarker/core/Configurable.java
@@ -1703,10 +1703,10 @@
     }
 
     /**
-     * Specifies the algorithm used for {@code ?truncate}. Defaults to
+     * Specifies the algorithm used for {@code ?truncate}, {@code ?truncate_w}, and {@code ?truncate_c}. Defaults to
      * {@link DefaultTruncateBuiltinAlgorithm#ASCII_INSTANCE}. Most customization needs can be addressed by
-     * creating a new {@link DefaultTruncateBuiltinAlgorithm} with the proper constructor parameters. Otherwise users
-     * my use their own {@link TruncateBuiltinAlgorithm} implementation.
+     * creating a new {@link DefaultTruncateBuiltinAlgorithm} with the proper constructor parameters. Otherwise, users
+     * may use their own {@link TruncateBuiltinAlgorithm} implementation.
      *
      * <p>In case you need to set this with {@link Properties}, or a similar configuration approach that doesn't let you
      * create the value in Java, see examples at {@link #setSetting(String, String)}.
diff --git a/freemarker-core/src/main/java/freemarker/core/DefaultTruncateBuiltinAlgorithm.java b/freemarker-core/src/main/java/freemarker/core/DefaultTruncateBuiltinAlgorithm.java
index 99625b5..1a0eda7 100644
--- a/freemarker-core/src/main/java/freemarker/core/DefaultTruncateBuiltinAlgorithm.java
+++ b/freemarker-core/src/main/java/freemarker/core/DefaultTruncateBuiltinAlgorithm.java
@@ -143,9 +143,9 @@
      * @param defaultTerminator
      *            The terminator to use if the invocation (like {@code s?truncate(20)}) doesn't specify it. The
      *            terminator is the text appended after a truncated string, to indicate that it was truncated.
-     *            Typically it's {@code "[...]"} or {@code "..."}, or the same with UNICODE ellipsis character.
+     *            Typically, it's {@code "[...]"} or {@code "..."}, or the same with UNICODE ellipsis character.
      * @param defaultTerminatorLength
-     *            The assumed length of {@code defaultTerminator}, or {@code null} if it should be get via
+     *            The assumed length of {@code defaultTerminator}, or {@code null} if the assumed length is simply
      *            {@code defaultTerminator.length()}.
      * @param defaultTerminatorRemovesDots
      *            Whether dots and ellipsis characters that the {@code defaultTerminator} touches should be removed. If
@@ -157,8 +157,12 @@
      *            in which case {@code defaultTerminator} will be used even if {@code ?truncate_m} or similar built-in
      *            is called.
      * @param defaultMTerminatorLength
-     *            The assumed length of the terminator, or {@code null} if it should be get via
-     *            {@link #getMTerminatorLength}.
+     *            The assumed length of the terminator, or {@code null} if the assumed length will be
+     *            {@link #getMTerminatorLength(TemplateMarkupOutputModel)}. Note that if you have HTML tags, or entity
+     *            references in the {@code defaultMTerminator}, then the visual length differs from the string length,
+     *            and {@link #getMTerminatorLength(TemplateMarkupOutputModel)} accounts for these complications to an
+     *            extent, but it for example it won't know what CSS does, or if the nested content of some HTML elements
+     *            are not displayed.
      * @param defaultMTerminatorRemovesDots
      *            Similar to {@code defaultTerminatorRemovesDots}, but for {@code defaultMTerminator}. If {@code
      *            null}, and {@code defaultMTerminator} is HTML/XML/XHTML, then it will be examined of the
@@ -168,17 +172,17 @@
      * @param addSpaceAtWordBoundary,
      *            Whether to add a space before the terminator if the truncation happens directly after the end of a
      *            word. For example, when "too long sentence" is truncated, it will be a like "too long [...]"
-     *            instead of "too long[...]". When the truncation happens inside a word, this has on effect, i.e., it
+     *            instead of "too long[...]". When the truncation happens inside a word, this has no effect, i.e., it
      *            will be always like "too long se[...]" (no space before the terminator). Note that only whitespace is
      *            considered to be a word separator, not punctuation, so if this is {@code true}, you get results
      *            like "Some sentence. [...]".
      * @param wordBoundaryMinLength
      *            Used when {@link #truncate} or {@link #truncateM} has to decide between
-     *            word boundary truncation and character boundary truncation; it's the minimum length, given as
+     *            word boundary truncation, and character boundary truncation; it's the minimum length, given as
      *            proportion of {@code maxLength}, that word boundary truncation has to produce. If the resulting
      *            length is less, we do character boundary truncation instead. For example, if {@code maxLength} is
-     *            30, and this parameter is 0.85, then: 30*0.85 = 25.5, rounded up that's 26, so the resulting length
-     *            must be at least 26. The result of character boundary truncation will be always accepted, even if its
+     *            30, and this parameter is 0.85, then: 30 * 0.85 = 25.5, rounded up that's 26, so the resulting length
+     *            must be at least 26. The result of character boundary truncation will always be accepted, even if it's
      *            still too short. If this parameter is {@code null}, then {@link #DEFAULT_WORD_BOUNDARY_MIN_LENGTH}
      *            will be used. If this parameter is 0, then truncation always happens at word boundary. If this
      *            parameter is 1.0, then truncation doesn't prefer word boundaries over other places.
@@ -355,7 +359,7 @@
      *
      * <p>In the implementation in {@link DefaultTruncateBuiltinAlgorithm}, if the markup is HTML/XML/XHTML, then this
      * counts the characters outside tags and comments, and inside CDATA sections (ignoring the CDATA section
-     * delimiters). Furthermore then it counts character and entity references as having length of 1. If the markup
+     * delimiters). Furthermore, then it counts character and entity references as having length of 1. If the markup
      * is not HTML/XML/XHTML (or subclasses of those {@link MarkupOutputFormat}-s) then it doesn't know how to
      * measure it, and simply returns 3.
      */
@@ -394,9 +398,6 @@
                 : true;
     }
 
-    /**
-     * Deals with both CB and WB truncation, hence it's unified.
-     */
     private TemplateModel unifiedTruncate(
             String s, int maxLength,
             TemplateModel terminator, Integer terminatorLength,
@@ -437,7 +438,7 @@
                 terminator, terminatorLength, terminatorRemovesDots,
                 mode);
 
-        // The terminator is always shown, even if with that we exceed maxLength. Otherwise the user couldn't
+        // The terminator is always shown, even if with that we exceed maxLength. Otherwise, the user couldn't
         // see that the string was truncated.
         if (truncatedS == null || truncatedS.length() == 0) {
             return terminator;
@@ -447,7 +448,7 @@
             truncatedS.append(((TemplateScalarModel) terminator).getAsString());
             return new SimpleScalar(truncatedS.toString());
         } else if (terminator instanceof TemplateMarkupOutputModel) {
-            TemplateMarkupOutputModel markup = (TemplateMarkupOutputModel) terminator;
+            TemplateMarkupOutputModel markup = (TemplateMarkupOutputModel<?>) terminator;
             MarkupOutputFormat outputFormat = markup.getOutputFormat();
             return outputFormat.concat(outputFormat.fromPlainTextByEscaping(truncatedS.toString()), markup);
         } else {
@@ -470,6 +471,8 @@
             return null;
         }
 
+        boolean addSpaceAtWordBoundary = this.addSpaceAtWordBoundary && terminatorLength != 0;
+
         if (mode == TruncationMode.AUTO && wordBoundaryMinLength < 1.0 || mode == TruncationMode.WORD_BOUNDARY) {
             // Do word boundary truncation. Might not be possible due to minLength restriction (see below), in which
             // case truncedS stays null.
@@ -527,7 +530,7 @@
 
         // If the truncation point is a word boundary, and thus we add a space before the terminator, then we may run
         // out of the maxLength by 1. In that case we have to truncate one character earlier.
-        if (cbLastCIdx == cbInitialLastCIdx && addSpaceAtWordBoundary  && isWordEnd(s, cbLastCIdx)) {
+        if (cbLastCIdx == cbInitialLastCIdx && addSpaceAtWordBoundary && isWordEnd(s, cbLastCIdx)) {
             cbLastCIdx--;
             if (cbLastCIdx < 0) {
                 return null;
diff --git a/freemarker-core/src/test/java/freemarker/core/TruncateBuiltInTest.java b/freemarker-core/src/test/java/freemarker/core/TruncateBuiltInTest.java
index 2cfe037..9929151 100644
--- a/freemarker-core/src/test/java/freemarker/core/TruncateBuiltInTest.java
+++ b/freemarker-core/src/test/java/freemarker/core/TruncateBuiltInTest.java
@@ -71,7 +71,8 @@
 
     @Test
     public void testTruncateM() throws IOException, TemplateException {
-        assertOutput("${t?truncateM(15)}", "Some text <span class='truncateTerminator'>[&#8230;]</span>"); // String arg allowed...
+        assertOutput("${t?truncateM(15)}",
+                "Some text <span class='truncateTerminator'>[&#8230;]</span>"); // String arg allowed...
         assertOutput("${t?truncate_m(15, mTerm)}", "Some text for " + M_TERM_SRC);
         assertOutput("${t?truncateM(15, mTerm)}", "Some text for " + M_TERM_SRC);
         assertOutput("${t?truncateM(15, mTerm, 3)}", "Some text " + M_TERM_SRC);
@@ -150,4 +151,20 @@
         assertOutput("${t?truncateM(20)}", "Some text for " + M_TERM_SRC);
     }
 
-}
+    @Test
+    public void testJiraIssueFREEMARKER219() throws IOException, TemplateException {
+        assertOutput("${'1 3'?truncate_c(2, '|')}", "|");
+        assertOutput("${' 2 '?truncate_c(2, '|')}", "|");
+        assertOutput("${'1 '?truncate_c(1, '|')}", "|");
+        assertOutput("${' 2'?truncate_c(1, '|')}", "|");
+        assertOutput("${'1234 SOMESTREETSSS AVE NE 123'?truncate_c(25, '|')}", "1234 SOMESTREETSSS AVE N|");
+
+        assertOutput("${'1 3'?truncate_c(2, '')}", "1");
+        assertOutput("${' 2 '?truncate_c(2, '')}", " 2");
+        assertOutput("${'1 '?truncate_c(1, '')}", "1");
+        assertOutput("${' 2'?truncate_c(1, '')}", "");
+        assertOutput("${'1234 SOMESTREETSSS AVE NE 123'?truncate_c(25, '')}", "1234 SOMESTREETSSS AVE NE");
+        assertOutput("${'1234 SOMESTREETSSS AVE NE 123'?truncate_c(24, '')}", "1234 SOMESTREETSSS AVE N");
+        assertOutput("${'1234 SOMESTREETSSS AVE NE 123'?truncate_c(23, '')}", "1234 SOMESTREETSSS AVE");
+    }
+}
\ No newline at end of file
diff --git a/freemarker-jsp20/src/main/java/freemarker/cache/WebappTemplateLoader.java b/freemarker-javax-servlet/src/main/java/freemarker/cache/WebappTemplateLoader.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/cache/WebappTemplateLoader.java
rename to freemarker-javax-servlet/src/main/java/freemarker/cache/WebappTemplateLoader.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/CustomTagAndELFunctionCombiner.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/EventForwarding.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/EventForwarding.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/EventForwarding.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/EventForwarding.java
diff --git a/freemarker-jsp21/src/main/java/freemarker/ext/jsp/FreeMarkerJspApplicationContext.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerJspApplicationContext.java
similarity index 100%
rename from freemarker-jsp21/src/main/java/freemarker/ext/jsp/FreeMarkerJspApplicationContext.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerJspApplicationContext.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java
similarity index 68%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java
index 9f67335..9633e14 100644
--- a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java
+++ b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory.java
@@ -20,16 +20,23 @@
 package freemarker.ext.jsp;
 
 import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.jsp.JspApplicationContext;
 import javax.servlet.jsp.JspEngineInfo;
 import javax.servlet.jsp.JspFactory;
 import javax.servlet.jsp.PageContext;
 
-/**
- */
-abstract class FreeMarkerJspFactory extends JspFactory {
-    protected abstract String getSpecificationVersion();
+class FreeMarkerJspFactory extends JspFactory {
+    private static final String SPECIFICATION_VERSION = "2.2";
+
+    // This still ends with "21", just in case someone used that key for some workaround.
+    private static final String JSPCTX_KEY = "freemarker.ext.jsp.FreeMarkerJspFactory21#jspAppContext";
+
+    String getSpecificationVersion() {
+        return SPECIFICATION_VERSION;
+    }
     
     @Override
     public JspEngineInfo getEngineInfo() {
@@ -60,4 +67,21 @@
         // for this API.
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public JspApplicationContext getJspApplicationContext(ServletContext ctx) {
+        JspApplicationContext jspctx = (JspApplicationContext) ctx.getAttribute(
+                JSPCTX_KEY);
+        if (jspctx == null) {
+            synchronized (ctx) {
+                jspctx = (JspApplicationContext) ctx.getAttribute(JSPCTX_KEY);
+                if (jspctx == null) {
+                    jspctx = new FreeMarkerJspApplicationContext();
+                    ctx.setAttribute(JSPCTX_KEY, jspctx);
+                }
+            }
+        }
+        return jspctx;
+    }
+
 }
\ No newline at end of file
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
similarity index 76%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
index 041f892..6110b42 100644
--- a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
+++ b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreeMarkerPageContext.java
@@ -23,12 +23,15 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.Writer;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.ListIterator;
 
+import javax.el.ELContext;
 import javax.servlet.GenericServlet;
 import javax.servlet.Servlet;
 import javax.servlet.ServletConfig;
@@ -41,8 +44,14 @@
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
 import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspApplicationContext;
+import javax.servlet.jsp.JspContext;
+import javax.servlet.jsp.JspFactory;
 import javax.servlet.jsp.JspWriter;
 import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
 import javax.servlet.jsp.tagext.BodyContent;
 
 import freemarker.core.Environment;
@@ -50,6 +59,7 @@
 import freemarker.ext.servlet.HttpRequestHashModel;
 import freemarker.ext.servlet.ServletContextHashModel;
 import freemarker.ext.util.WrapperTemplateModel;
+import freemarker.log.Logger;
 import freemarker.template.AdapterTemplateModel;
 import freemarker.template.ObjectWrapper;
 import freemarker.template.ObjectWrapperAndUnwrapper;
@@ -62,13 +72,20 @@
 import freemarker.template.TemplateNumberModel;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template._VersionInts;
+import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.UndeclaredThrowableException;
 
-/**
- */
-abstract class FreeMarkerPageContext extends PageContext implements TemplateModel {
-    private static final Class OBJECT_CLASS = Object.class;
-        
+class FreeMarkerPageContext extends PageContext implements TemplateModel {
+    private static final Logger LOG = Logger.getLogger("freemarker.jsp");
+
+    static {
+        if (JspFactory.getDefaultFactory() == null) {
+            JspFactory.setDefaultFactory(new FreeMarkerJspFactory());
+        }
+        LOG.debug("Using JspFactory implementation class " +
+                JspFactory.getDefaultFactory().getClass().getName());
+    }
+
     private final Environment environment;
     private final int incompatibleImprovements;
     private List tags = new ArrayList();
@@ -80,7 +97,8 @@
     private final ObjectWrapper wrapper;
     private final ObjectWrapperAndUnwrapper unwrapper;
     private JspWriter jspOut;
-    
+    private ELContext elContext;
+
     protected FreeMarkerPageContext() throws TemplateModelException {
         environment = Environment.getCurrentEnvironment();
         incompatibleImprovements = environment.getConfiguration().getIncompatibleImprovements().intValue();
@@ -94,15 +112,15 @@
         if (appModel instanceof ServletContextHashModel) {
             this.servlet = ((ServletContextHashModel) appModel).getServlet();
         } else {
-            throw new  TemplateModelException("Could not find an instance of " + 
-                    ServletContextHashModel.class.getName() + 
-                    " in the data model under either the name " + 
-                    FreemarkerServlet.KEY_APPLICATION_PRIVATE + " or " + 
+            throw new TemplateModelException("Could not find an instance of " +
+                    ServletContextHashModel.class.getName() +
+                    " in the data model under either the name " +
+                    FreemarkerServlet.KEY_APPLICATION_PRIVATE + " or " +
                     FreemarkerServlet.KEY_APPLICATION);
         }
-        
-        TemplateModel requestModel = 
-            environment.getGlobalVariable(FreemarkerServlet.KEY_REQUEST_PRIVATE);
+
+        TemplateModel requestModel =
+                environment.getGlobalVariable(FreemarkerServlet.KEY_REQUEST_PRIVATE);
         if (!(requestModel instanceof HttpRequestHashModel)) {
             requestModel = environment.getGlobalVariable(
                     FreemarkerServlet.KEY_REQUEST);
@@ -116,10 +134,10 @@
             unwrapper = this.wrapper instanceof ObjectWrapperAndUnwrapper
                     ? (ObjectWrapperAndUnwrapper) this.wrapper : null;
         } else {
-            throw new  TemplateModelException("Could not find an instance of " + 
-                    HttpRequestHashModel.class.getName() + 
-                    " in the data model under either the name " + 
-                    FreemarkerServlet.KEY_REQUEST_PRIVATE + " or " + 
+            throw new TemplateModelException("Could not find an instance of " +
+                    HttpRequestHashModel.class.getName() +
+                    " in the data model under either the name " +
+                    FreemarkerServlet.KEY_REQUEST_PRIVATE + " or " +
                     FreemarkerServlet.KEY_REQUEST);
         }
 
@@ -132,17 +150,17 @@
         setAttribute(CONFIG, servlet.getServletConfig());
         setAttribute(PAGECONTEXT, this);
         setAttribute(APPLICATION, servlet.getServletContext());
-    }    
-            
+    }
+
     ObjectWrapper getObjectWrapper() {
         return wrapper;
     }
-    
+
     @Override
     public void initialize(
-        Servlet servlet, ServletRequest request, ServletResponse response,
-        String errorPageURL, boolean needsSession, int bufferSize, 
-        boolean autoFlush) {
+            Servlet servlet, ServletRequest request, ServletResponse response,
+            String errorPageURL, boolean needsSession, int bufferSize,
+            boolean autoFlush) {
         throw new UnsupportedOperationException();
     }
 
@@ -157,7 +175,7 @@
 
     @Override
     public void setAttribute(String name, Object value, int scope) {
-        switch(scope) {
+        switch (scope) {
             case PAGE_SCOPE: {
                 try {
                     environment.setGlobalVariable(name, wrapper.wrap(value));
@@ -199,7 +217,7 @@
                         return unwrapper.unwrap(tm);
                     } else { // Legacy behavior branch
                         if (tm instanceof AdapterTemplateModel) {
-                            return ((AdapterTemplateModel) tm).getAdaptedObject(OBJECT_CLASS);
+                            return ((AdapterTemplateModel) tm).getAdaptedObject(Object.class);
                         }
                         if (tm instanceof WrapperTemplateModel) {
                             return ((WrapperTemplateModel) tm).getWrappedObject();
@@ -263,7 +281,7 @@
 
     @Override
     public void removeAttribute(String name, int scope) {
-        switch(scope) {
+        switch (scope) {
             case PAGE_SCOPE: {
                 environment.getGlobalNamespace().remove(name);
                 break;
@@ -300,11 +318,11 @@
 
     @Override
     public Enumeration getAttributeNamesInScope(int scope) {
-        switch(scope) {
+        switch (scope) {
             case PAGE_SCOPE: {
                 try {
-                    return 
-                        new TemplateHashModelExEnumeration(environment.getGlobalNamespace());
+                    return
+                            new TemplateHashModelExEnumeration(environment.getGlobalNamespace());
                 } catch (TemplateModelException e) {
                     throw new UndeclaredThrowableException(e);
                 }
@@ -347,7 +365,7 @@
     public HttpSession getSession() {
         return getSession(false);
     }
-    
+
     @Override
     public Object getPage() {
         return servlet;
@@ -401,7 +419,7 @@
             public PrintWriter getWriter() {
                 return pw;
             }
-            
+
             @Override
             public ServletOutputStream getOutputStream() {
                 throw new UnsupportedOperationException("JSP-included resource must use getWriter()");
@@ -422,13 +440,13 @@
 
     @Override
     public BodyContent pushBody() {
-      return (BodyContent) pushWriter(new TagTransformModel.BodyContentImpl(getOut(), true));
-  }
+        return (BodyContent) pushWriter(new TagTransformModel.BodyContentImpl(getOut(), true));
+    }
 
-  @Override
-public JspWriter pushBody(Writer w) {
-      return pushWriter(new JspWriterAdapter(w));
-  }
+    @Override
+    public JspWriter pushBody(Writer w) {
+        return pushWriter(new JspWriterAdapter(w));
+    }
 
     @Override
     public JspWriter popBody() {
@@ -444,35 +462,95 @@
             }
         }
         return null;
-    }  
-    
+    }
+
     void popTopTag() {
         tags.remove(tags.size() - 1);
-    }  
+    }
 
     void popWriter() {
         jspOut = (JspWriter) outs.remove(outs.size() - 1);
         setAttribute(OUT, jspOut);
     }
-    
+
     void pushTopTag(Object tag) {
         tags.add(tag);
-    } 
-    
+    }
+
     JspWriter pushWriter(JspWriter out) {
         outs.add(jspOut);
         jspOut = out;
         setAttribute(OUT, jspOut);
         return out;
-    } 
-    
+    }
+
+    /**
+     * Attempts to locate and manufacture an expression evaulator instance. For this to work you <b>must</b> have the
+     * Apache Commons-EL package in the classpath. If Commons-EL is not available, this method will throw an
+     * UnsupportedOperationException.
+     */
+    @Override
+    public ExpressionEvaluator getExpressionEvaluator() {
+        try {
+            Class type = ((ClassLoader) AccessController.doPrivileged(
+                    new PrivilegedAction() {
+                        @Override
+                        public Object run() {
+                            return Thread.currentThread().getContextClassLoader();
+                        }
+                    })).loadClass
+                    ("org.apache.commons.el.ExpressionEvaluatorImpl");
+            return (ExpressionEvaluator) type.newInstance();
+        } catch (Exception e) {
+            throw new UnsupportedOperationException("In order for the getExpressionEvaluator() " +
+                    "method to work, you must have downloaded the apache commons-el jar and " +
+                    "made it available in the classpath.");
+        }
+    }
+
+    /**
+     * Returns a variable resolver that will resolve variables by searching through the page scope, request scope,
+     * session scope and application scope for an attribute with a matching name.
+     */
+    @Override
+    public VariableResolver getVariableResolver() {
+        final PageContext ctx = this;
+
+        return new VariableResolver() {
+            @Override
+            public Object resolveVariable(String name) throws ELException {
+                return ctx.findAttribute(name);
+            }
+        };
+    }
+
+    @Override
+    public ELContext getELContext() {
+        if (elContext == null) {
+            JspApplicationContext jspctx = JspFactory.getDefaultFactory().getJspApplicationContext(getServletContext());
+            if (jspctx instanceof FreeMarkerJspApplicationContext) {
+                elContext = ((FreeMarkerJspApplicationContext) jspctx).createNewELContext(this);
+                elContext.putContext(JspContext.class, this);
+            } else {
+                throw new UnsupportedOperationException(
+                        "Can not create an ELContext using a foreign JspApplicationContext (of class "
+                                + ClassUtil.getShortClassNameOfObject(jspctx) + ").\n" +
+                                "Hint: The cause of this is often that you are trying to use JSTL tags/functions in " +
+                                "FTL. "
+                                + "In that case, know that that's not really suppored, and you are supposed to use FTL "
+                                + "constrcuts instead, like #list instead of JSTL's forEach, etc.");
+            }
+        }
+        return elContext;
+    }
+
     private static class TemplateHashModelExEnumeration implements Enumeration {
         private final TemplateModelIterator it;
-            
+
         private TemplateHashModelExEnumeration(TemplateHashModelEx hashEx) throws TemplateModelException {
             it = hashEx.keys().iterator();
         }
-        
+
         @Override
         public boolean hasMoreElements() {
             try {
@@ -481,7 +559,7 @@
                 throw new UndeclaredThrowableException(tme);
             }
         }
-        
+
         @Override
         public Object nextElement() {
             try {
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreemarkerTag.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspContextModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspContextModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspContextModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspContextModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspTagModelBase.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspTagModelBase.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspWriterAdapter.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspWriterAdapter.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/JspWriterAdapter.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/JspWriterAdapter.java
diff --git a/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/PageContextFactory.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/PageContextFactory.java
new file mode 100644
index 0000000..b7fedb9
--- /dev/null
+++ b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/PageContextFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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 freemarker.ext.jsp;
+
+import javax.servlet.jsp.PageContext;
+
+import freemarker.core.Environment;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+class PageContextFactory {
+    static FreeMarkerPageContext getCurrentPageContext() throws TemplateModelException {
+        Environment env = Environment.getCurrentEnvironment();
+        TemplateModel pageContextModel = env.getGlobalVariable(PageContext.PAGECONTEXT);
+        if (pageContextModel instanceof FreeMarkerPageContext) {
+            return (FreeMarkerPageContext) pageContextModel;
+        }
+        FreeMarkerPageContext pageContext = new FreeMarkerPageContext();
+        env.setGlobalVariable(PageContext.PAGECONTEXT, pageContext);
+        return pageContext;
+    }
+}
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/SimpleTagDirectiveModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/SimpleTagDirectiveModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/SimpleTagDirectiveModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/SimpleTagDirectiveModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/TagTransformModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TagTransformModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/TagTransformModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TagTransformModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/TaglibFactory.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TaglibFactory.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/TaglibFactory.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TaglibFactory.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/TaglibMethodUtil.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TaglibMethodUtil.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/TaglibMethodUtil.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/TaglibMethodUtil.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/package.html b/freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/package.html
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/jsp/package.html
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/jsp/package.html
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/AllHttpScopesHashModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpRequestHashModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpRequestHashModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpRequestHashModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpRequestHashModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpRequestParametersHashModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpRequestParametersHashModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpRequestParametersHashModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpRequestParametersHashModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpSessionHashModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpSessionHashModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/HttpSessionHashModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/HttpSessionHashModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/IncludePage.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/IncludePage.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/IncludePage.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/IncludePage.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/InitParamParser.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/InitParamParser.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/InitParamParser.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/InitParamParser.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/ServletContextHashModel.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/ServletContextHashModel.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/ServletContextHashModel.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/ServletContextHashModel.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/SuppressFBWarnings.java b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/SuppressFBWarnings.java
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/SuppressFBWarnings.java
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/SuppressFBWarnings.java
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/servlet/package.html b/freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/package.html
similarity index 100%
rename from freemarker-jsp20/src/main/java/freemarker/ext/servlet/package.html
rename to freemarker-javax-servlet/src/main/java/freemarker/ext/servlet/package.html
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServlet.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServlet.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServlet.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServlet.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServletWithDefaultOverride.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServletWithDefaultOverride.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServletWithDefaultOverride.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/JspTestFreemarkerServletWithDefaultOverride.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/RealServletContainertTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/TLDParsingTest.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/TLDParsingTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/TLDParsingTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/TLDParsingTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/TaglibMethodUtilTest.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/TaglibMethodUtilTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/TaglibMethodUtilTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/TaglibMethodUtilTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeAccessorTag.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeAccessorTag.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeAccessorTag.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeAccessorTag.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeInfoTag.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeInfoTag.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeInfoTag.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/AttributeInfoTag.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/EnclosingClass.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/EnclosingClass.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/EnclosingClass.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/EnclosingClass.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/GetAndSetTag.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/GetAndSetTag.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/GetAndSetTag.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/GetAndSetTag.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestFunctions.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestFunctions.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestFunctions.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestFunctions.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag2.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag2.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag2.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag2.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag3.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag3.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag3.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestSimpleTag3.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag2.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag2.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag2.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag2.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag3.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag3.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag3.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/taglibmembers/TestTag3.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/jsp/webapps/config/WebappLocalFreemarkerServlet.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/ext/servlet/InitParamParserTest.java b/freemarker-javax-servlet/src/test/java/freemarker/ext/servlet/InitParamParserTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/ext/servlet/InitParamParserTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/ext/servlet/InitParamParserTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/template/MockServletContext.java b/freemarker-javax-servlet/src/test/java/freemarker/template/MockServletContext.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/template/MockServletContext.java
rename to freemarker-javax-servlet/src/test/java/freemarker/template/MockServletContext.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/template/TemplateNotFoundMessageTest.java b/freemarker-javax-servlet/src/test/java/freemarker/template/TemplateNotFoundMessageTest.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/template/TemplateNotFoundMessageTest.java
rename to freemarker-javax-servlet/src/test/java/freemarker/template/TemplateNotFoundMessageTest.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/test/servlet/DefaultModel2TesterAction.java b/freemarker-javax-servlet/src/test/java/freemarker/test/servlet/DefaultModel2TesterAction.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/test/servlet/DefaultModel2TesterAction.java
rename to freemarker-javax-servlet/src/test/java/freemarker/test/servlet/DefaultModel2TesterAction.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/test/servlet/Model2Action.java b/freemarker-javax-servlet/src/test/java/freemarker/test/servlet/Model2Action.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/test/servlet/Model2Action.java
rename to freemarker-javax-servlet/src/test/java/freemarker/test/servlet/Model2Action.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/test/servlet/Model2TesterServlet.java b/freemarker-javax-servlet/src/test/java/freemarker/test/servlet/Model2TesterServlet.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/test/servlet/Model2TesterServlet.java
rename to freemarker-javax-servlet/src/test/java/freemarker/test/servlet/Model2TesterServlet.java
diff --git a/freemarker-jsp21/src/test/java/freemarker/test/servlet/WebAppTestCase.java b/freemarker-javax-servlet/src/test/java/freemarker/test/servlet/WebAppTestCase.java
similarity index 100%
rename from freemarker-jsp21/src/test/java/freemarker/test/servlet/WebAppTestCase.java
rename to freemarker-javax-servlet/src/test/java/freemarker/test/servlet/WebAppTestCase.java
diff --git a/freemarker-jsp21/src/test/resources/META-INF/tldDiscovery MetaInfTldSources-1.tld b/freemarker-javax-servlet/src/test/resources/META-INF/tldDiscovery MetaInfTldSources-1.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/META-INF/tldDiscovery MetaInfTldSources-1.tld
rename to freemarker-javax-servlet/src/test/resources/META-INF/tldDiscovery MetaInfTldSources-1.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/TLDParsingTest.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/TLDParsingTest.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/TLDParsingTest.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/TLDParsingTest.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/templates/classpath-test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/templates/classpath-test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/templates/classpath-test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/templates/classpath-test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-1.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-1.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-1.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-1.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-2.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-2.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-2.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/tldDiscovery-ClassPathTlds-2.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/CONTENTS.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-function-tag-name-clash.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-functions.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-functions.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-functions.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/el-functions.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.0.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.0.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.0.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.0.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.22-future.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.22-future.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.22-future.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes-2.3.22-future.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/attributes.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/customTags1.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/customTags1.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/customTags1.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/expected/customTags1.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/test.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/test.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/test.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/test.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/WEB-INF/web.xml
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/attributes.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/attributes.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/attributes.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/attributes.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.jsp b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.jsp
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.jsp
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customELFunctions1.jsp
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customTags1.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customTags1.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/customTags1.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/customTags1.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/elFunctionsTagNameClash.jsp
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial-jstl-@Ignore.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial-jstl-@Ignore.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial-jstl-@Ignore.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial-jstl-@Ignore.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.jsp b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.jsp
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.jsp
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/basic/trivial.jsp
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/CONTENTS.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/sub/test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/sub/test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/sub/test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/sub/test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/classes/test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/lib/templates.jar/sub/test2.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/templates/test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/templates/test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/templates/test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/templates/test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/WEB-INF/web.xml
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/config/test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/config/test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/CONTENTS.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/WEB-INF/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/WEB-INF/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/WEB-INF/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/WEB-INF/web.xml
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.ftlnv b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.ftlnv
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.ftlnv
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.ftlnv
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.jsp b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.jsp
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.jsp
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-parsetime.jsp
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.jsp b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.jsp
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.jsp
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/failing-runtime.jsp
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/not-failing.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/not-failing.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/errors/not-failing.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/errors/not-failing.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/CONTENTS.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/templates/test.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/templates/test.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/templates/test.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/templates/test.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/multipleLoaders/WEB-INF/web.xml
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/CONTENTS.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/subdir/test-rel.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/subdir/test-rel.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/subdir/test-rel.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/subdir/test-rel.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test-noClasspath.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test-noClasspath.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test-noClasspath.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test-noClasspath.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test1.txt b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test1.txt
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test1.txt
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/expected/test1.txt
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag 2.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag 2.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag 2.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag 2.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag4.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag4.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag4.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/fmtesttag4.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/lib/taglib-foo.jar/META-INF/foo bar.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/subdir-with-tld/fmtesttag3.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/subdir-with-tld/fmtesttag3.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/subdir-with-tld/fmtesttag3.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/subdir-with-tld/fmtesttag3.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/taglib 2.jar/META-INF/taglib.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/WEB-INF/web.xml
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/not-auto-scanned/fmtesttag.tld b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/not-auto-scanned/fmtesttag.tld
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/not-auto-scanned/fmtesttag.tld
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/not-auto-scanned/fmtesttag.tld
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/subdir/test-rel.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/subdir/test-rel.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/subdir/test-rel.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/subdir/test-rel.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test-noClasspath.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test-noClasspath.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test-noClasspath.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test-noClasspath.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test1.ftl b/freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test1.ftl
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test1.ftl
rename to freemarker-javax-servlet/src/test/resources/freemarker/ext/jsp/webapps/tldDiscovery/test1.ftl
diff --git a/freemarker-jsp21/src/test/resources/freemarker/test/servlet/web.xml b/freemarker-javax-servlet/src/test/resources/freemarker/test/servlet/web.xml
similarity index 100%
rename from freemarker-jsp21/src/test/resources/freemarker/test/servlet/web.xml
rename to freemarker-javax-servlet/src/test/resources/freemarker/test/servlet/web.xml
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/PageContextFactory.java b/freemarker-jsp20/src/main/java/freemarker/ext/jsp/PageContextFactory.java
deleted file mode 100644
index 51aad6d..0000000
--- a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/PageContextFactory.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 freemarker.ext.jsp;
-
-import javax.servlet.jsp.PageContext;
-
-import freemarker.core.Environment;
-import freemarker.template.TemplateModel;
-import freemarker.template.TemplateModelException;
-import freemarker.template.utility.UndeclaredThrowableException;
-
-/**
- */
-class PageContextFactory {
-    private static final Class pageContextImpl = getPageContextImpl();
-    
-    private static Class getPageContextImpl() {
-        try {
-            try {
-                PageContext.class.getMethod("getELContext", (Class[]) null);
-                return Class.forName("freemarker.ext.jsp._FreeMarkerPageContext21");
-            } catch (NoSuchMethodException e1) {
-                try {
-                    PageContext.class.getMethod("getExpressionEvaluator", (Class[]) null);
-                    return Class.forName("freemarker.ext.jsp._FreeMarkerPageContext2");
-                } catch (NoSuchMethodException e2) {
-                    throw new IllegalStateException(
-                            "Since FreeMarker 2.3.24, JSP support requires at least JSP 2.0.");
-                }
-            }
-        } catch (ClassNotFoundException e) {
-            throw new NoClassDefFoundError(e.getMessage());
-        }
-    }
-
-    static FreeMarkerPageContext getCurrentPageContext() throws TemplateModelException {
-        Environment env = Environment.getCurrentEnvironment();
-        TemplateModel pageContextModel = env.getGlobalVariable(PageContext.PAGECONTEXT);
-        if (pageContextModel instanceof FreeMarkerPageContext) {
-            return (FreeMarkerPageContext) pageContextModel;
-        }
-        try {
-            FreeMarkerPageContext pageContext = 
-                (FreeMarkerPageContext) pageContextImpl.newInstance();
-            env.setGlobalVariable(PageContext.PAGECONTEXT, pageContext);
-            return pageContext;
-        } catch (IllegalAccessException e) {
-            throw new IllegalAccessError(e.getMessage());
-        } catch (InstantiationException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-}
diff --git a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext2.java b/freemarker-jsp20/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext2.java
deleted file mode 100644
index d2fa19d..0000000
--- a/freemarker-jsp20/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext2.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 freemarker.ext.jsp;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.jsp.JspFactory;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.el.ELException;
-import javax.servlet.jsp.el.ExpressionEvaluator;
-import javax.servlet.jsp.el.VariableResolver;
-
-import freemarker.log.Logger;
-import freemarker.template.TemplateModelException;
-
-/**
- * Don't use this class; it's only public to work around Google App Engine Java
- * compliance issues. FreeMarker developers only: treat this class as package-visible.
- * 
- * Implementation of PageContext that contains JSP 2.0 specific methods.
- */
-public class _FreeMarkerPageContext2 extends FreeMarkerPageContext {
-    private static final Logger LOG = Logger.getLogger("freemarker.jsp");
-
-    static {
-        if (JspFactory.getDefaultFactory() == null) {
-            JspFactory.setDefaultFactory(new FreeMarkerJspFactory2());
-        }
-        LOG.debug("Using JspFactory implementation class " + 
-                JspFactory.getDefaultFactory().getClass().getName());
-    }
-
-    public _FreeMarkerPageContext2() throws TemplateModelException {
-        super();
-    }
-
-    /**
-     * Attempts to locate and manufacture an expression evaulator instance. For this
-     * to work you <b>must</b> have the Apache Commons-EL package in the classpath. If
-     * Commons-EL is not available, this method will throw an UnsupportedOperationException. 
-     */
-    @Override
-    public ExpressionEvaluator getExpressionEvaluator() {
-        try {
-            Class type = Thread.currentThread().getContextClassLoader().loadClass
-                    ("org.apache.commons.el.ExpressionEvaluatorImpl");
-            return (ExpressionEvaluator) type.newInstance();
-        } catch (Exception e) {
-            throw new UnsupportedOperationException("In order for the getExpressionEvaluator() " +
-                "method to work, you must have downloaded the apache commons-el jar and " +
-                "made it available in the classpath.");
-        }
-    }
-
-    /**
-     * Returns a variable resolver that will resolve variables by searching through
-     * the page scope, request scope, session scope and application scope for an
-     * attribute with a matching name.
-     */
-    @Override
-    public VariableResolver getVariableResolver() {
-        final PageContext ctx = this;
-
-        return new VariableResolver() {
-            @Override
-            public Object resolveVariable(String name) throws ELException {
-                return ctx.findAttribute(name);
-            }
-        };
-    }
-
-    /**
-     * Includes the specified path. The flush argument is ignored!
-     */
-    @Override
-    public void include(String path, boolean flush) throws IOException, ServletException {
-        super.include(path);
-    }
-}
diff --git a/freemarker-jsp21/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory21.java b/freemarker-jsp21/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory21.java
deleted file mode 100644
index d7670c0..0000000
--- a/freemarker-jsp21/src/main/java/freemarker/ext/jsp/FreeMarkerJspFactory21.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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 freemarker.ext.jsp;
-
-import javax.servlet.ServletContext;
-import javax.servlet.jsp.JspApplicationContext;
-
-/**
- */
-class FreeMarkerJspFactory21 extends FreeMarkerJspFactory {
-    private static final String JSPCTX_KEY =  
-        FreeMarkerJspFactory21.class.getName() + "#jspAppContext";
-
-    @Override
-    protected String getSpecificationVersion() {
-        return "2.1";
-    }
-    
-    @Override
-    public JspApplicationContext getJspApplicationContext(ServletContext ctx) {
-        JspApplicationContext jspctx = (JspApplicationContext) ctx.getAttribute(
-                JSPCTX_KEY);
-        if (jspctx == null) {
-            synchronized (ctx) {
-                jspctx = (JspApplicationContext) ctx.getAttribute(JSPCTX_KEY);
-                if (jspctx == null) {
-                    jspctx = new FreeMarkerJspApplicationContext();
-                    ctx.setAttribute(JSPCTX_KEY, jspctx);
-                }
-            }
-        }
-        return jspctx;
-    }
-}
\ No newline at end of file
diff --git a/freemarker-jsp21/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext21.java b/freemarker-jsp21/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext21.java
deleted file mode 100644
index d99e22c..0000000
--- a/freemarker-jsp21/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext21.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 freemarker.ext.jsp;
-
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.el.ELContext;
-import javax.servlet.jsp.JspApplicationContext;
-import javax.servlet.jsp.JspContext;
-import javax.servlet.jsp.JspFactory;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.el.ELException;
-import javax.servlet.jsp.el.ExpressionEvaluator;
-import javax.servlet.jsp.el.VariableResolver;
-
-import freemarker.log.Logger;
-import freemarker.template.TemplateModelException;
-import freemarker.template.utility.ClassUtil;
-
-/**
- * Don't use this class; it's only public to work around Google App Engine Java
- * compliance issues. FreeMarker developers only: treat this class as package-visible.
- * 
- * Implementation of PageContext that contains JSP 2.0 and JSP 2.1 specific 
- * methods.
- */
-public class _FreeMarkerPageContext21 extends FreeMarkerPageContext {
-    private static final Logger LOG = Logger.getLogger("freemarker.jsp");
-
-    static {
-        if (JspFactory.getDefaultFactory() == null) {
-            JspFactory.setDefaultFactory(new FreeMarkerJspFactory21());
-        }
-        LOG.debug("Using JspFactory implementation class " + 
-                JspFactory.getDefaultFactory().getClass().getName());
-    }
-
-    public _FreeMarkerPageContext21() throws TemplateModelException {
-        super();
-    }
-
-    /**
-     * Attempts to locate and manufacture an expression evaulator instance. For this
-     * to work you <b>must</b> have the Apache Commons-EL package in the classpath. If
-     * Commons-EL is not available, this method will throw an UnsupportedOperationException. 
-     */
-    @Override
-    public ExpressionEvaluator getExpressionEvaluator() {
-        try {
-            Class type = ((ClassLoader) AccessController.doPrivileged(
-                    new PrivilegedAction() {
-                        @Override
-                        public Object run() {
-                            return Thread.currentThread().getContextClassLoader();
-                        }
-                    })).loadClass
-                    ("org.apache.commons.el.ExpressionEvaluatorImpl");
-            return (ExpressionEvaluator) type.newInstance();
-        } catch (Exception e) {
-            throw new UnsupportedOperationException("In order for the getExpressionEvaluator() " +
-                "method to work, you must have downloaded the apache commons-el jar and " +
-                "made it available in the classpath.");
-        }
-    }
-
-    /**
-     * Returns a variable resolver that will resolve variables by searching through
-     * the page scope, request scope, session scope and application scope for an
-     * attribute with a matching name.
-     */
-    @Override
-    public VariableResolver getVariableResolver() {
-        final PageContext ctx = this;
-
-        return new VariableResolver() {
-            @Override
-            public Object resolveVariable(String name) throws ELException {
-                return ctx.findAttribute(name);
-            }
-        };
-    }
-
-    private ELContext elContext;
-    
-    @Override
-    public ELContext getELContext() {
-        if (elContext == null) { 
-            JspApplicationContext jspctx = JspFactory.getDefaultFactory().getJspApplicationContext(getServletContext());
-            if (jspctx instanceof FreeMarkerJspApplicationContext) {
-                elContext = ((FreeMarkerJspApplicationContext) jspctx).createNewELContext(this);
-                elContext.putContext(JspContext.class, this);
-            } else {
-                throw new UnsupportedOperationException(
-                        "Can not create an ELContext using a foreign JspApplicationContext (of class "
-                        + ClassUtil.getShortClassNameOfObject(jspctx) + ").\n" +
-                        "Hint: The cause of this is often that you are trying to use JSTL tags/functions in FTL. "
-                        + "In that case, know that that's not really suppored, and you are supposed to use FTL "
-                        + "constrcuts instead, like #list instead of JSTL's forEach, etc.");
-            }
-        }
-        return elContext;
-    }
-}
diff --git a/freemarker-manual/src/main/docgen/en_US/book.xml b/freemarker-manual/src/main/docgen/en_US/book.xml
index fa06667..9d184e0 100644
--- a/freemarker-manual/src/main/docgen/en_US/book.xml
+++ b/freemarker-manual/src/main/docgen/en_US/book.xml
@@ -15115,11 +15115,27 @@
             <primary>truncate_w_m built-in</primary>
           </indexterm>
 
+          <note>
+            <para>If you just want to limit the length of string with
+            straightforward behavior, then do not use this built in, but the
+            <link linkend="dgui_template_exp_seqenceop_slice">sequence
+            slicing</link>, and <link
+            linkend="dgui_template_exp_direct_ranges">..* length limited
+            range</link> operators. For example, <literal>s[0 ..*
+            10]</literal> will give the first 10 characters of
+            <literal>s</literal>, if <literal>s</literal> is longer than that,
+            otherwise it just gives <literal>s</literal> as is. While
+            <literal>s?truncate(10, '')</literal> expresses similar intent, it
+            has complicated rules to give a result that looks nicer for
+            humans, like it trims the right side at the cut, and sometimes
+            cuts a bit early to avoid cutting into the last word.</para>
+          </note>
+
           <para>Cuts off the end of a string if that's necessary to keep it
-          under a the length given as parameter, and appends a terminator
-          string (<literal>[...]</literal> by default) to indicate that the
-          string was truncated. Example (assuming default FreeMarker
-          configuration settings):</para>
+          under the length given as parameter, and appends a terminator string
+          (<literal>[...]</literal> by default) to indicate that the string
+          was truncated. Example (assuming default FreeMarker configuration
+          settings):</para>
 
           <programlisting role="template">&lt;#assign shortName='This is short'&gt;
 &lt;#assign longName='This is a too long name'&gt;
@@ -15143,7 +15159,7 @@
 Truncated at "character boundary":
 This isonev[...]</programlisting>
 
-          <para>Things to note above:</para>
+          <para>Notes on some tricky aspects for truncation:</para>
 
           <itemizedlist>
             <listitem>
@@ -15159,9 +15175,9 @@
               better look (see later). Actually, the result length can also be
               longer than the parameter length, when the desired length is
               shorter than the terminator string alone, in which case the
-              terminator is still returned as is. Also, an algorithms other
+              terminator is still returned as is. Also, an algorithm other
               than the default might choses to return a longer string, as the
-              length parameter is in principle just hint for the desired
+              length parameter is in principle just a hint for the desired
               visual length.</para>
             </listitem>
 
@@ -15180,7 +15196,21 @@
               between the word end and the terminator string, otherwise
               there's no space between them. Only whitespace is treated as
               word separator, not punctuation, so this generally gives
-              intuitive results.</para>
+              intuitive results. (Except, if the terminator string is set to
+              be 0 length, no space is added before it, starting from
+              FreeMarker 2.3.33.)</para>
+            </listitem>
+
+            <listitem>
+              <para>Before adding the terminator string (possibly with a word
+              boundary space before it, as explained above) after the string
+              whose length was already cut, trailing whitespace is removed
+              from that. For example <literal>'1
+              67890A'?truncate(10)</literal>, where there are 4 spaces between
+              the <literal>1</literal> and <literal>6</literal>, will give
+              <quote><literal>1 [...]</literal></quote> (7 characters), not
+              <quote><literal>1 [...]</literal></quote> (10
+              characters).</para>
             </listitem>
           </itemizedlist>
 
@@ -15220,7 +15250,11 @@
                     to give a string length closer to the length specified,
                     but still not an exact length, as it removes white-space
                     before the terminator string, and re-adds a space if we
-                    are just after the end of a word, etc.</para>
+                    are just after the end of a word, etc. (Except, space is
+                    not re-added if the terminator string is set to be 0
+                    length, starting from FreeMarker 2.3.33.) If you need
+                    exact length, simply use <literal>longName[0 ..*
+                    16]</literal>.</para>
                   </listitem>
                 </itemizedlist>
               </listitem>
@@ -15229,11 +15263,11 @@
                 <para>Specifying the terminator string (instead of relying on
                 its default): <literal>truncate</literal> and all
                 <literal>truncate_<replaceable>...</replaceable></literal>
-                built-ins have an additional optional parameter for it. After
-                that, a further optional parameter can specify the assumed
-                length of the terminator string (otherwise its real length
-                will be used). If you find yourself specifying the terminator
-                string often, then certainly the defaults should be configured
+                built-ins have an optional 2nd parameter for that. After that,
+                a further optional parameter can specify the assumed length of
+                the terminator string (otherwise its real length will be
+                used). If you find yourself specifying the terminator string
+                often, then certainly the defaults should be configured
                 instead (via <literal>truncate_builtin_algorithm
                 configuration</literal> - see earlier). Example:</para>
 
@@ -30063,7 +30097,10 @@
         <para>Release date: [TODO]</para>
 
         <para>Please note that with this version the minimum required Java
-        version was increased from Java 7 to Java 8.</para>
+        version was increased from Java 7 to Java 8. Also for the few who
+        relly on Servlet and/or JSP support, the minimum is now increased to
+        Servlet 3.0, and JSP 2.2 (which are still very old versions from
+        2011).</para>
 
         <section>
           <title>Changes on the FTL side</title>
@@ -30143,6 +30180,20 @@
 
             <listitem>
               <para><link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-219">FREEMARKER-219</link>:
+              The <link linkend="ref_builtin_truncate"><quote>truncate</quote>
+              family of built-ins</link>, as in
+              <literal>maybeLong?truncate(10, '')</literal>, if the terminator
+              string is set to 0 length, now it will not add a space before
+              the terminator string when the cut happened exactly after the
+              end of a word. (Note that if you are using something like
+              <literal>maybeLong?truncate_c(10, '')</literal>, then certainly
+              what you really want is <literal>maybeLong[0 ..* 10]</literal>,
+              as that doesn't do trimming at the cut.)</para>
+            </listitem>
+
+            <listitem>
+              <para><link
               xlink:href="https://github.com/apache/freemarker/pull/89">GitHub
               PR 89</link>: Added <literal>TemplateProcessingTracer</literal>
               mechanism, that can be used to monitor coverage, and performance
@@ -30253,6 +30304,26 @@
                 </listitem>
               </itemizedlist>
             </listitem>
+
+            <listitem>
+              <para>Minimum requirements were increased:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Java 8 (or higher)</para>
+                </listitem>
+
+                <listitem>
+                  <para>If Servlet-related features are used, Servlet 3.0 (or
+                  higher)</para>
+                </listitem>
+
+                <listitem>
+                  <para>If JSP-related features are used, JSP 2.2 (or
+                  higher)</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
           </itemizedlist>
         </section>
       </section>