First pass conversion of ant build over to maven.
git-svn-id: https://svn.apache.org/repos/asf/tapestry/tapestry3/trunk@671206 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/build.xml b/build.xml
index 42eed76..2da4124 100644
--- a/build.xml
+++ b/build.xml
@@ -44,7 +44,7 @@
<path id="vlib.classpath">
<pathelement location="${vlibbeans.jar}"/>
- <pathelement location="examples/VlibBeans/jboss"/>
+ <pathelement location="tapestry-examples/VlibBeans/jboss"/>
<pathelement location="${jboss.client.dir}/jboss-j2ee.jar"/>
<pathelement location="${jboss.client.dir}/jboss-client.jar"/>
<pathelement location="${jboss.client.dir}/jnp-client.jar"/>
@@ -55,7 +55,7 @@
<ant dir="framework" target="clean"/>
<ant dir="contrib" target="clean"/>
<ant dir="junit" target="clean"/>
- <ant dir="examples" target="clean"/>
+ <ant dir="tapestry-examples" target="clean"/>
<ant dir="doc/src" target="clean"/>
<delete dir="${private.dir}" quiet="true"/>
</target>
@@ -80,19 +80,19 @@
doesn't override definitions in each project's buildfile. -->
<ant dir="framework" target="install" inheritAll="false"/>
<ant dir="contrib" target="install" inheritAll="false"/>
- <ant dir="examples" target="install" inheritAll="false"/>
+ <ant dir="tapestry-examples" target="install" inheritAll="false"/>
</target>
<target name="build-workbench" description="Builds and installs the Workbench demo."
depends="download-ext-framework">
- <ant dir="examples/Workbench" target="install" inheritAll="false"/>
+ <ant dir="tapestry-examples/tapestry-workbench" target="install" inheritAll="false"/>
</target>
<target name="build-vlib" description="Builds and installs the Virtual Library demo."
depends="download-ext-framework,check-for-jboss-dir">
- <ant dir="examples/VlibBeans" target="install" inheritAll="false"/>
- <ant dir="examples/Vlib" target="install" inheritAll="false"/>
- <ant dir="examples/VlibEAR" target="install" inheritAll="false"/>
+ <ant dir="tapestry-examples/VlibBeans" target="install" inheritAll="false"/>
+ <ant dir="tapestry-examples/Vlib" target="install" inheritAll="false"/>
+ <ant dir="tapestry-examples/VlibEAR" target="install" inheritAll="false"/>
</target>
<target name="documentation" depends="download-ext-doc"
diff --git a/doc/src/ComponentReference/ActionLink.html b/doc/src/ComponentReference/ActionLink.html
index b5198aa..2eddbaa 100644
--- a/doc/src/ComponentReference/ActionLink.html
+++ b/doc/src/ComponentReference/ActionLink.html
@@ -164,7 +164,7 @@
from the Customer List table.
<p>
-<table class="examples" cellspacing="6">
+<table class="tapestry-examples" cellspacing="6">
<tr align="left">
<th>ID</th>
<th> </th>
diff --git a/doc/src/ComponentReference/Any.html b/doc/src/ComponentReference/Any.html
index f3fe443..cae0c80 100644
--- a/doc/src/ComponentReference/Any.html
+++ b/doc/src/ComponentReference/Any.html
@@ -104,7 +104,7 @@
In this example the Any component is use to generate XML order list document.
<p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
<font color="blue"><?xml version="1.0" encoding="ISO-8859-1" ?></font><br>
diff --git a/doc/src/ComponentReference/Checkbox.html b/doc/src/ComponentReference/Checkbox.html
index fc34dcc..f468699 100644
--- a/doc/src/ComponentReference/Checkbox.html
+++ b/doc/src/ComponentReference/Checkbox.html
@@ -117,7 +117,7 @@
Provides a checkbox for the user contact a sales representative.
<p>
-<table class="examples" cellspacing="8">
+<table class="tapestry-examples" cellspacing="8">
<tr>
<td>
<input type="checkbox" checked> Contact Sales Rep
diff --git a/doc/src/ComponentReference/Conditional.html b/doc/src/ComponentReference/Conditional.html
index 12fd7f3..74728ed 100644
--- a/doc/src/ComponentReference/Conditional.html
+++ b/doc/src/ComponentReference/Conditional.html
@@ -129,7 +129,7 @@
person is a manager and if they are a manager whether they have any staff.
<p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
John Smith is a Manager with <font color="red"><b>no</b></font> staff.
diff --git a/doc/src/ComponentReference/DatePicker.html b/doc/src/ComponentReference/DatePicker.html
index bcbb8ee..5d1d54d 100644
--- a/doc/src/ComponentReference/DatePicker.html
+++ b/doc/src/ComponentReference/DatePicker.html
@@ -155,7 +155,7 @@
an end date.
</p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
<form jwcid="form">
diff --git a/doc/src/ComponentReference/DirectLink.html b/doc/src/ComponentReference/DirectLink.html
index 4a4d76a..70cafdc 100644
--- a/doc/src/ComponentReference/DirectLink.html
+++ b/doc/src/ComponentReference/DirectLink.html
@@ -195,7 +195,7 @@
Customers from a Customer List table.
<p>
-<table class="examples" cellspacing="6">
+<table class="tapestry-examples" cellspacing="6">
<tr align="left">
<th>ID</th>
<th> </th>
diff --git a/doc/src/ComponentReference/Foreach.html b/doc/src/ComponentReference/Foreach.html
index a641555..a679204 100644
--- a/doc/src/ComponentReference/Foreach.html
+++ b/doc/src/ComponentReference/Foreach.html
@@ -150,7 +150,7 @@
The Foreach component is used to generate a table from a Customer List.
<p>
-<table class="examples" cellspacing="6">
+<table class="tapestry-examples" cellspacing="6">
<tr align="left">
<th>ID</th>
<th> </th>
diff --git a/doc/src/ComponentReference/Form.html b/doc/src/ComponentReference/Form.html
index c363b35..fac2802 100644
--- a/doc/src/ComponentReference/Form.html
+++ b/doc/src/ComponentReference/Form.html
@@ -178,7 +178,7 @@
The Form component is used to provide a simple login page.
<p>
-<table class="examples" cellpadding="4">
+<table class="tapestry-examples" cellpadding="4">
<form>
<tr>
<td>Username:</td><td><input size="12"></td>
diff --git a/doc/src/ComponentReference/Image.html b/doc/src/ComponentReference/Image.html
index b3c3513..31dec24 100644
--- a/doc/src/ComponentReference/Image.html
+++ b/doc/src/ComponentReference/Image.html
@@ -111,7 +111,7 @@
<context-asset> to reference the image.
<p>
-<table class="examples" cellpadding="8" valign="middle">
+<table class="tapestry-examples" cellpadding="8" valign="middle">
<tr>
<td>
<a href="http://tapestry.sourceforge.net/">
@@ -150,7 +150,7 @@
<tt>ExternalAsset</tt></a> to reference the image's URL.
<p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
<h4>Elvis helitanker Saves 14 Lives</h4>
diff --git a/doc/src/ComponentReference/Insert.html b/doc/src/ComponentReference/Insert.html
index a287c28..2add08e 100644
--- a/doc/src/ComponentReference/Insert.html
+++ b/doc/src/ComponentReference/Insert.html
@@ -160,7 +160,7 @@
Inserts the pages dueDate and applies the specified DateFormat and HTML class.
<p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
The order was due on the <font color="red"><b>21 January 2002</b></font>.
diff --git a/doc/src/ComponentReference/PageLink.html b/doc/src/ComponentReference/PageLink.html
index 9c5f2c9..3169fcd 100644
--- a/doc/src/ComponentReference/PageLink.html
+++ b/doc/src/ComponentReference/PageLink.html
@@ -177,7 +177,7 @@
<td><font color="white"><b><a href="PageLink.html">Logout</a></b></font></td>
</tr>
</table>
-<table class="examples" cellpadding="6" width="100%">
+<table class="tapestry-examples" cellpadding="6" width="100%">
<tr>
<td>My mail page content.</td>
</tr>
diff --git a/doc/src/ComponentReference/PropertySelection.html b/doc/src/ComponentReference/PropertySelection.html
index b63ac03..6161220 100644
--- a/doc/src/ComponentReference/PropertySelection.html
+++ b/doc/src/ComponentReference/PropertySelection.html
@@ -173,7 +173,7 @@
<a href="../api/org/apache/tapestry/form/StringPropertySelectionModel.html"><tt>StringPropertySelectionModel<tt></tt></tt></a>
<tt><tt>
<p>
- <table class="examples" valign="middle" cellspacing="8">
+ <table class="tapestry-examples" valign="middle" cellspacing="8">
<tr>
<td>Gender:
<select>
@@ -218,7 +218,7 @@
and the new cloting item information is displayed by the description,
label and price <a href="Insert.html">Insert</a> components.
<p>
- <table class="examples" valign="middle" cellspacing="8">
+ <table class="tapestry-examples" valign="middle" cellspacing="8">
<tr>
<td>Item:
<select>
diff --git a/doc/src/ComponentReference/RadioGroup.html b/doc/src/ComponentReference/RadioGroup.html
index b51ebd4..3402e9d 100644
--- a/doc/src/ComponentReference/RadioGroup.html
+++ b/doc/src/ComponentReference/RadioGroup.html
@@ -133,7 +133,7 @@
<fieldset><legend>.. </legend>.. </fieldset> tags.
<p/>
-<table class="examples" cellpadding="4" cellspacing="4">
+<table class="tapestry-examples" cellpadding="4" cellspacing="4">
<tr>
<td>
<fieldset><legend>Order Size</legend>
diff --git a/doc/src/ComponentReference/RenderBlock.html b/doc/src/ComponentReference/RenderBlock.html
index 2ad72d7..4a45db9 100644
--- a/doc/src/ComponentReference/RenderBlock.html
+++ b/doc/src/ComponentReference/RenderBlock.html
@@ -113,7 +113,7 @@
<p> This example shows a page with a custom TabPanel component. When
a user selects a tab, TabPanel switches content. Each tab content
is defined by a Block.</p>
- <table class="examples" border="0" width="350" cellspacing="0" cellpadding="0">
+ <table class="tapestry-examples" border="0" width="350" cellspacing="0" cellpadding="0">
<tr>
<td width="10"> </td>
<td>
diff --git a/doc/src/ComponentReference/RenderBody.html b/doc/src/ComponentReference/RenderBody.html
index f93f95b..61d8ea0 100644
--- a/doc/src/ComponentReference/RenderBody.html
+++ b/doc/src/ComponentReference/RenderBody.html
@@ -107,7 +107,7 @@
a Border component to provide common layout to almost all of application
pages. </p>
- <table width="200" class="examples" cellspacing="0" border="0" align="center">
+ <table width="200" class="tapestry-examples" cellspacing="0" border="0" align="center">
<tr>
<td valign="top" style="text-align:justify;">
<H1 align=center><FONT color=#ff3333>Agnosis</FONT></H1>
@@ -123,7 +123,7 @@
<BR/>
<B>O</B>lvidados por el Hado
<BR/>
- <B>S</B>i Él existe
+ <B>S</B>i �l existe
<BR/>
<B>I</B>ncomprensible y eterno
<BR/>
diff --git a/doc/src/ComponentReference/Script.html b/doc/src/ComponentReference/Script.html
index 94aa8e6..b5b4a2a 100644
--- a/doc/src/ComponentReference/Script.html
+++ b/doc/src/ComponentReference/Script.html
@@ -146,7 +146,7 @@
<tt>${expression}</tt> syntax.
<p>
-<table class="examples" cellpadding="4">
+<table class="tapestry-examples" cellpadding="4">
<form>
<tr>
diff --git a/doc/src/ComponentReference/Select.html b/doc/src/ComponentReference/Select.html
index 8c2322c..b0ee45a 100644
--- a/doc/src/ComponentReference/Select.html
+++ b/doc/src/ComponentReference/Select.html
@@ -139,7 +139,7 @@
with only a single component (instead of three, as will be shown below).
<p>
-<table class="examples" cellpadding="4">
+<table class="tapestry-examples" cellpadding="4">
<tr>
<td>Select a color
<select name="selection" multiple>
diff --git a/doc/src/ComponentReference/Shell.html b/doc/src/ComponentReference/Shell.html
index 539e95e..5d4097d 100644
--- a/doc/src/ComponentReference/Shell.html
+++ b/doc/src/ComponentReference/Shell.html
@@ -220,7 +220,7 @@
path to resolve the stylesheet.
<p>
-<table class="examples" cellpadding="24">
+<table class="tapestry-examples" cellpadding="24">
<tr>
<td>
<h2><i><font color="navy">Customer Login</font></i></h2>
diff --git a/doc/src/ComponentReference/Submit.html b/doc/src/ComponentReference/Submit.html
index c0f558a..ec177c0 100644
--- a/doc/src/ComponentReference/Submit.html
+++ b/doc/src/ComponentReference/Submit.html
@@ -190,7 +190,7 @@
.
<p>
-<table class="examples" cellpadding="4">
+<table class="tapestry-examples" cellpadding="4">
<form>
<tr>
<td>Username:</td><td><input size="12"></td>
@@ -262,7 +262,7 @@
<A href="PropertySelection.html">PropertySelection</a>.
<p>
-<table class="examples" valign="middle" cellspacing="8">
+<table class="tapestry-examples" valign="middle" cellspacing="8">
<tr>
<td>
<form>
diff --git a/doc/src/ComponentReference/TextArea.html b/doc/src/ComponentReference/TextArea.html
index 3839e14..d6849bd 100644
--- a/doc/src/ComponentReference/TextArea.html
+++ b/doc/src/ComponentReference/TextArea.html
@@ -124,7 +124,7 @@
feedback <textarea>.
<p>
- <table class="examples" valign="middle" cellspacing="8">
+ <table class="tapestry-examples" valign="middle" cellspacing="8">
<tr>
<td>Your Comments</td>
<td><TEXTAREA name=textarea rows=5>Please enter your comments here</TEXTAREA>
diff --git a/doc/src/ComponentReference/TextField.html b/doc/src/ComponentReference/TextField.html
index 59699a8..532fb93 100644
--- a/doc/src/ComponentReference/TextField.html
+++ b/doc/src/ComponentReference/TextField.html
@@ -130,7 +130,7 @@
form input field.
<p>
-<table class="examples" cellspacing="8" valign="middle">
+<table class="tapestry-examples" cellspacing="8" valign="middle">
<tr>
<td>Id</td>
<td><input size="8" value="1245"></td>
diff --git a/doc/src/ComponentReference/Upload.html b/doc/src/ComponentReference/Upload.html
index 22dd12a..fa6eeb9 100644
--- a/doc/src/ComponentReference/Upload.html
+++ b/doc/src/ComponentReference/Upload.html
@@ -134,7 +134,7 @@
obtain the web application's directory path.
<form>
-<table class="examples" cellpadding="4">
+<table class="tapestry-examples" cellpadding="4">
<tr>
<td colspan="2">File: <input type="file"></td>
</tr>
diff --git a/doc/src/ComponentReference/ValidField.html b/doc/src/ComponentReference/ValidField.html
index 34553a9..88f53c9 100644
--- a/doc/src/ComponentReference/ValidField.html
+++ b/doc/src/ComponentReference/ValidField.html
@@ -226,7 +226,7 @@
<p>
<form>
-<table class="examples" cellpadding="2">
+<table class="tapestry-examples" cellpadding="2">
<tr>
<td colspan="2"><font color="navy" size="+2"><i><b>Regal Auctions Bid Page</b></i></font></td>
</tr>
diff --git a/doc/src/ComponentReference/contrib.InspectorButton.html b/doc/src/ComponentReference/contrib.InspectorButton.html
index 8b44773..d1a3d13 100644
--- a/doc/src/ComponentReference/contrib.InspectorButton.html
+++ b/doc/src/ComponentReference/contrib.InspectorButton.html
@@ -117,7 +117,7 @@
<p>
This example is a simple page with the InspectorButton.</p>
-<table class="examples" cellpadding="8">
+<table class="tapestry-examples" cellpadding="8">
<tr>
<td>
<h1>Hello world</h1>
diff --git a/doc/src/ComponentReference/contrib.PopupLink.html b/doc/src/ComponentReference/contrib.PopupLink.html
index 7ccb396..893f0f3 100644
--- a/doc/src/ComponentReference/contrib.PopupLink.html
+++ b/doc/src/ComponentReference/contrib.PopupLink.html
@@ -123,7 +123,7 @@
This example provides a context help popup link for an account number field.
<p>
-<table class="examples" cellspacing="8">
+<table class="tapestry-examples" cellspacing="8">
<tr>
<td>Account Number:</td>
<td><input type="text"/></td>
diff --git a/doc/src/ComponentReference/contrib.Table.html b/doc/src/ComponentReference/contrib.Table.html
index 51a1c08..2043adb 100644
--- a/doc/src/ComponentReference/contrib.Table.html
+++ b/doc/src/ComponentReference/contrib.Table.html
@@ -474,7 +474,7 @@
<b>Examples</b>
<p>
-<table class="examples" cellpadding="4" width="700">
+<table class="tapestry-examples" cellpadding="4" width="700">
<tr><td>
<SPAN><A
diff --git a/examples/Workbench/build.xml b/examples/Workbench/build.xml
deleted file mode 100644
index d213cd9..0000000
--- a/examples/Workbench/build.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0"?>
-<!-- $Id$ -->
-<project name="Tapestry Workbench Example" default="install">
- <property name="root.dir" value="../.."/>
- <property file="${root.dir}/config/Version.properties"/>
- <property file="${root.dir}/config/build.properties"/>
- <property file="${root.dir}/config/common.properties"/>
-
- <property name="config.dir" value="config"/>
- <property name="build.dir" value=".build"/>
-
- <path id="compile.classpath">
- <fileset dir="${root.lib.dir}">
- <include name="*.jar"/>
- <include name="${ext.dir}/*.jar"/>
- <include name="${j2ee.dir}/*.jar"/>
- </fileset>
- <fileset dir="${lib.dir}">
- <include name="*.jar"/>
- </fileset>
- </path>
- <target name="init">
- <mkdir dir="${classes.dir}"/>
- </target>
- <target name="clean">
- <delete dir="${classes.dir}" quiet="true"/>
- <delete dir="${build.dir}" quiet="true"/>
- </target>
- <target name="compile" depends="init"
- description="Compile all classes in the tutorial.">
- <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="on"
- target="1.1" source="1.3">
- <classpath refid="compile.classpath"/>
- </javac>
- </target>
- <target name="install" depends="compile"
- description="Compile all classes and build the installed WAR.">
-
- <mkdir dir="${build.dir}"/>
- <mkdir dir="${examples.dir}"/>
-
- <copy file="${config.dir}/web.xml" todir="${build.dir}">
- <filterset>
- <filter token="TAPESTRY_JAR" value="${framework.jar}"/>
- </filterset>
- </copy>
-
- <war warfile="${examples.dir}/${workbench.war}"
- webxml="${build.dir}/web.xml">
-
- <fileset dir="context"/>
-
- <classes dir="${classes.dir}"/>
- <classes dir="${src.dir}">
- <exclude name="**/*.java"/>
- <exclude name="**/package.html"/>
- </classes>
- <classes dir="${root.config.dir}">
- <include name="log4j.properties"/>
- </classes>
- <lib dir="${lib.dir}">
- <include name="*.jar"/>
- </lib>
- <lib dir="${root.lib.dir}">
- <include name="*.jar"/>
- </lib>
- <lib dir="${root.lib.dir}/${ext.dir}">
- <include name="*.jar"/>
- </lib>
- <lib dir="${root.lib.dir}/${runtime.dir}">
- <include name="*.jar"/>
- </lib>
- </war>
- </target>
-
-</project>
diff --git a/examples/Workbench/config/web.xml b/examples/Workbench/config/web.xml
deleted file mode 100644
index 48004f0..0000000
--- a/examples/Workbench/config/web.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0"?>
-<!--$Id$ -->
-<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd">
-<web-app>
- <display-name>Tapestry Workbench Example</display-name>
-
- <filter>
- <filter-name>redirect</filter-name>
- <filter-class>org.apache.tapestry.RedirectFilter</filter-class>
- </filter>
-
- <filter-mapping>
- <filter-name>redirect</filter-name>
- <url-pattern>/</url-pattern>
- </filter-mapping>
-
- <servlet>
- <servlet-name>workbench</servlet-name>
- <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
- <load-on-startup>0</load-on-startup>
- </servlet>
-
- <servlet-mapping>
- <servlet-name>workbench</servlet-name>
- <url-pattern>/app</url-pattern>
- </servlet-mapping>
-
- <session-config>
- <session-timeout>15</session-timeout>
- </session-config>
-
- <taglib>
- <taglib-uri>http://jakarta.apache.org/tapestry/tld/tapestry_1_0.tld</taglib-uri>
- <taglib-location>/WEB-INF/lib/@TAPESTRY_JAR@</taglib-location>
- </taglib>
-</web-app>
diff --git a/examples/build.xml b/examples/build.xml
deleted file mode 100644
index c024d54..0000000
--- a/examples/build.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<!-- Interface between the top-level build file and any of the examples. Simply re-executes
- its targets in each of its sub-projects. -->
-<project name="Tapestry Examples" default="install">
- <target name="clean">
- <ant dir="Workbench" target="clean"/>
- <ant dir="VlibBeans" target="clean"/>
- <ant dir="Vlib" target="clean"/>
- </target>
- <target name="install">
- <ant dir="Workbench" target="install" inheritAll="false"/>
- <ant dir="VlibBeans" target="install" inheritAll="false"/>
- <ant dir="Vlib" target="install" inheritAll="false"/>
- <ant dir="VlibEAR" target="install" inheritAll="false"/>
- </target>
-</project>
diff --git a/framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java b/framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java
deleted file mode 100644
index 0fa5093..0000000
--- a/framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2004 The Apache Software Foundation
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.apache.tapestry.enhance.javassist;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-
-import javassist.CannotCompileException;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtField;
-import javassist.CtMethod;
-import javassist.NotFoundException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.tapestry.enhance.CodeGenerationException;
-
-/**
- * @author Mindbridge
- * @version $Id$
- * @since 3.0
- */
-public class ClassFabricator
-{
- private static final Log LOG = LogFactory.getLog(ClassFabricator.class);
-
- /**
- * The code template for the standard property accessor method.
- * <p>
- * Legend: <br>
- * {0} = property field name <br>
- */
- private static final String PROPERTY_ACCESSOR_TEMPLATE = "" +
- "'{'" +
- " return {0}; " +
- "'}'";
-
- /**
- * The code template for the standard property mutator method.
- * <p>
- * Legend: <br>
- * {0} = property field name <br>
- */
- private static final String PROPERTY_MUTATOR_TEMPLATE = "" +
- "'{'" +
- " {0} = $1; " +
- "'}'";
-
- /**
- * The code template for the standard persistent property mutator method.
- * <p>
- * Legend: <br>
- * {0} = property field name <br>
- * {1} = property name <br>
- */
- private static final String PERSISTENT_PROPERTY_MUTATOR_TEMPLATE =
- "" +
- "'{'" +
- " {0} = $1;" +
- " fireObservedChange(\"{1}\", {0}); " +
- "'}'";
-
- private ClassPool _classPool;
- private CtClass _genClass;
-
- public ClassFabricator(String className, CtClass parentClass, ClassPool classPool)
- {
- _classPool = classPool;
- _genClass = _classPool.makeClass(className, parentClass);
- }
-
- public CtField getField(String fieldName)
- {
- try
- {
- return _genClass.getField(fieldName);
- }
- catch (NotFoundException e)
- {
- return null;
- }
- }
-
- public void createField(CtClass fieldType, String fieldName)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Creating field: " + fieldName);
-
- try
- {
- CtField field = new CtField(fieldType, fieldName, _genClass);
- _genClass.addField(field);
- }
- catch (CannotCompileException e)
- {
- throw new CodeGenerationException(e);
- }
- }
-
- public void createField(CtClass fieldType, String fieldName, String init)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Creating field: " + fieldName + " with initializer: " + init);
-
- try
- {
- CtField field = new CtField(fieldType, fieldName, _genClass);
- _genClass.addField(field, init);
- }
- catch (CannotCompileException e)
- {
- throw new CodeGenerationException(e);
- }
- }
-
- public CtMethod getMethod(String name, String signature)
- {
- try
- {
- return _genClass.getMethod(name, signature);
- }
- catch (NotFoundException e)
- {
- return null;
- }
- }
-
- public void addMethod(CtMethod method) throws CannotCompileException
- {
- _genClass.addMethod(method);
- }
-
- /**
- * Constructs an accessor method name.
- *
- **/
-
- public String buildMethodName(String prefix, String propertyName)
- {
- StringBuffer result = new StringBuffer(prefix);
-
- char ch = propertyName.charAt(0);
-
- result.append(Character.toUpperCase(ch));
-
- result.append(propertyName.substring(1));
-
- return result.toString();
- }
-
- public CtMethod createMethod(
- CtClass returnType,
- String methodName,
- CtClass[] arguments)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Creating method: " + methodName);
-
- CtMethod method = new CtMethod(returnType, methodName, arguments, _genClass);
-
- return method;
- }
-
- public CtMethod createAccessor(
- CtClass fieldType,
- String propertyName,
- String readMethodName)
- {
- String methodName =
- readMethodName == null ? buildMethodName("get", propertyName) : readMethodName;
-
- if (LOG.isDebugEnabled())
- LOG.debug("Creating accessor: " + methodName);
-
- CtMethod method = new CtMethod(fieldType, methodName, new CtClass[0], _genClass);
-
- return method;
- }
-
- /**
- * Creates an accessor (getter) method for the property.
- *
- * @param fieldType the return type for the method
- * @param fieldName the name of the field (not the name of the property)
- * @param propertyName the name of the property (used to build the name of the method)
- * @param readMethodName if not null, the name of the method to use
- *
- **/
-
- public void createPropertyAccessor(
- CtClass fieldType,
- String fieldName,
- String propertyName,
- String readMethodName)
- {
- try
- {
- String accessorBody =
- MessageFormat.format(PROPERTY_ACCESSOR_TEMPLATE, new Object[] { fieldName, propertyName });
-
- CtMethod method = createAccessor(fieldType, propertyName, readMethodName);
- method.setBody(accessorBody);
- _genClass.addMethod(method);
- }
- catch (CannotCompileException e)
- {
- throw new CodeGenerationException(e);
- }
- }
-
- public CtMethod createMutator(
- CtClass fieldType,
- String propertyName)
- {
- String methodName = buildMethodName("set", propertyName);
-
- if (LOG.isDebugEnabled())
- LOG.debug("Creating mutator: " + methodName);
-
- CtMethod method =
- new CtMethod(CtClass.voidType, methodName, new CtClass[] { fieldType }, _genClass);
-
- return method;
- }
-
- /**
- * Creates a mutator (aka "setter") method.
- *
- * @param fieldType type of field value (and type of parameter value)
- * @param fieldName name of field (not property!)
- * @param propertyName name of property (used to construct method name)
- * @param isPersistent if true, adds a call to fireObservedChange()
- *
- **/
-
- public void createPropertyMutator(
- CtClass fieldType,
- String fieldName,
- String propertyName,
- boolean isPersistent)
- {
- String bodyTemplate = isPersistent ? PERSISTENT_PROPERTY_MUTATOR_TEMPLATE : PROPERTY_MUTATOR_TEMPLATE;
- String body = MessageFormat.format(bodyTemplate, new Object[] { fieldName, propertyName });
-
- try
- {
- CtMethod method = createMutator(fieldType, propertyName);
- method.setBody(body);
- _genClass.addMethod(method);
- }
- catch (CannotCompileException e)
- {
- throw new CodeGenerationException(e);
- }
- }
-
-
- public void commit()
- {
- }
-
- public byte[] getByteCode()
- {
- try
- {
- return _genClass.toBytecode();
- }
- catch (NotFoundException e)
- {
- throw new CodeGenerationException(e);
- }
- catch (IOException e)
- {
- throw new CodeGenerationException(e);
- }
- catch (CannotCompileException e)
- {
- throw new CodeGenerationException(e);
- }
- }
-
-}
diff --git a/tapestry-contrib/.cvsignore b/tapestry-contrib/.cvsignore
new file mode 100644
index 0000000..602053d
--- /dev/null
+++ b/tapestry-contrib/.cvsignore
@@ -0,0 +1,3 @@
+classes
+*.log
+target
diff --git a/tapestry-contrib/build.xml b/tapestry-contrib/build.xml
new file mode 100644
index 0000000..35337d7
--- /dev/null
+++ b/tapestry-contrib/build.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<project name="Tapestry Contrib Framework" default="install">
+ <property name="root.dir" value=".."/>
+ <property file="${root.dir}/config/Version.properties"/>
+ <property file="${root.dir}/config/build.properties"/>
+ <property file="${root.dir}/config/common.properties"/>
+ <path id="jetty.classpath">
+ <fileset dir="${jetty.dir}">
+ <include name="**/javax.*.jar"/>
+ </fileset>
+ </path>
+ <path id="project.classpath">
+ <fileset dir="${root.lib.dir}">
+ <include name="${framework.jar}"/>
+ <include name="${ext.dir}/*.jar"/>
+ <include name="${j2ee.dir}/*.jar"/>
+ </fileset>
+ </path>
+ <target name="init">
+ <mkdir dir="${classes.dir}"/>
+ </target>
+ <target name="clean">
+ <delete dir="${classes.dir}"/>
+ </target>
+
+ <target name="compile" depends="init"
+ description="Compile all classes in the framework.">
+ <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="on"
+ target="1.1" source="1.3">
+ <classpath refid="project.classpath"/>
+ </javac>
+ </target>
+
+ <target name="install" depends="compile"
+ description="Compile all classes and build the installed JAR including all package resources."
+ >
+ <jar jarfile="${root.lib.dir}/${contrib.jar}">
+ <fileset dir="${classes.dir}"/>
+ <fileset dir="${src.dir}">
+ <exclude name="**/*.java"/>
+ <exclude name="**/package.html"/>
+ </fileset>
+ </jar>
+ </target>
+</project>
diff --git a/tapestry-contrib/pom.xml b/tapestry-contrib/pom.xml
new file mode 100644
index 0000000..dd5b3c3
--- /dev/null
+++ b/tapestry-contrib/pom.xml
@@ -0,0 +1,118 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-contrib</artifactId>
+ <packaging>jar</packaging>
+ <version>3.0.5-SNAPSHOT</version>
+
+ <parent>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-project</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </parent>
+ <name>Contrib</name>
+ <inceptionYear>2006</inceptionYear>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-framework</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>jboss</groupId>
+ <artifactId>jboss-j2ee</artifactId>
+ <version>4.0.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.9</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ <version>2.1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>ognl</groupId>
+ <artifactId>ognl</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+ <resources>
+ <resource>
+ <directory>src</directory>
+ <includes>
+ <include>**/*</include>
+ <include>**/*.library</include>
+ <include>**/*.page</include>
+ <include>**/*.jwc</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ <!--
+ <testSourceDirectory>src/test</testSourceDirectory>
+ <testResources>
+ <testResource>
+ <directory>src/test</directory>
+ <includes><include>**/*</include></includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </testResource>
+ </testResources>
+ -->
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <archive>
+ <compress>true</compress>
+ <index>true</index>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <archive>
+ <compress>true</compress>
+ <index>true</index>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <reporting>
+ <outputDirectory>../target/site/tapestry-contrib</outputDirectory>
+ <excludeDefaults>true</excludeDefaults>
+ </reporting>
+
+</project>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/Contrib.library b/tapestry-contrib/src/org/apache/tapestry/contrib/Contrib.library
new file mode 100644
index 0000000..fb5b813
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/Contrib.library
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE library-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<library-specification>
+
+ <component-type type="InspectorButton" specification-path="inspector/InspectorButton.jwc"/>
+ <page name="Inspector" specification-path="inspector/Inspector.page"/>
+
+ <library id="inspector" specification-path="inspector/Inspector.library"/>
+
+ <component-type type="Choose" specification-path="components/Choose.jwc"/>
+ <component-type type="When" specification-path="components/When.jwc"/>
+ <component-type type="Otherwise" specification-path="components/Otherwise.jwc"/>
+
+ <component-type type="Palette" specification-path="palette/Palette.jwc"/>
+ <component-type type="MultiplePropertySelection" specification-path="form/MultiplePropertySelection.jwc"/>
+ <component-type type="DateField" specification-path="valid/DateField.jwc"/>
+ <component-type type="MaskEdit" specification-path="form/MaskEdit.jwc"/>
+ <component-type type="NumericField" specification-path="valid/NumericField.jwc"/>
+ <component-type type="ValidatingTextField" specification-path="valid/ValidatingTextField.jwc"/>
+ <component-type type="FormConditional" specification-path="form/FormConditional.jwc"/>
+
+ <component-type type="InheritInformalAny" specification-path="informal/InheritInformalAny.jwc"/>
+
+ <component-type type="Table" specification-path="table/components/Table.jwc"/>
+ <component-type type="TableColumns" specification-path="table/components/TableColumns.jwc"/>
+ <component-type type="TablePages" specification-path="table/components/TablePages.jwc"/>
+ <component-type type="TableRows" specification-path="table/components/TableRows.jwc"/>
+ <component-type type="TableValues" specification-path="table/components/TableValues.jwc"/>
+ <component-type type="TableView" specification-path="table/components/TableView.jwc"/>
+ <component-type type="FormTable" specification-path="table/components/FormTable.jwc"/>
+ <component-type type="TableFormRows" specification-path="table/components/TableFormRows.jwc"/>
+ <component-type type="TableFormPages" specification-path="table/components/TableFormPages.jwc"/>
+
+ <page name="SimpleTableColumnPage" specification-path="table/components/inserted/SimpleTableColumnPage.page"/>
+ <component-type type="SimpleTableColumnComponent" specification-path="table/components/inserted/SimpleTableColumnComponent.jwc"/>
+ <component-type type="SimpleTableColumnFormComponent" specification-path="table/components/inserted/SimpleTableColumnFormComponent.jwc"/>
+
+ <component-type type="PopupLink" specification-path="popup/PopupLink.jwc"/>
+
+ <component-type type="Tree"
+ specification-path="/org/apache/tapestry/contrib/tree/components/Tree.jwc"/>
+ <component-type type="TreeDataView"
+ specification-path="/org/apache/tapestry/contrib/tree/components/TreeDataView.jwc"/>
+ <component-type type="TreeNodeView"
+ specification-path="/org/apache/tapestry/contrib/tree/components/TreeNodeView.jwc"/>
+ <component-type type="TreeView"
+ specification-path="/org/apache/tapestry/contrib/tree/components/TreeView.jwc"/>
+ <component-type type="TreeTableDataView"
+ specification-path="/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.jwc"/>
+ <component-type type="TreeTable"
+ specification-path="/org/apache/tapestry/contrib/tree/components/table/TreeTable.jwc"/>
+ <component-type type="TreeTableNodeViewDelegator"
+ specification-path="/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.jwc"/>
+ <page name="TreeNodeViewPage" specification-path="/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.page"/>
+
+ <page name="TreeTableNodeViewPage" specification-path="/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.page"/>
+</library-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.java b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.java
new file mode 100644
index 0000000..20c41f4
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.java
@@ -0,0 +1,62 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.components;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.components.Conditional;
+
+/**
+ * This component is a container for {@link When} or Otherwise components;
+ * it provides the context for mutually exclusive conditional evaluation.
+ *
+ * [<a href="../../../../../../ComponentReference/contrib.Choose.html">Component Reference</a>]
+ *
+ * @author David Solis
+ * @version $Id$
+ *
+ **/
+public abstract class Choose extends Conditional {
+
+
+ public void addBody(IRender element)
+ {
+ super.addBody(element);
+ if (element instanceof When)
+ ((When) element).setChoose(this);
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ setConditionMet(false);
+ super.cleanupAfterRender(cycle);
+ }
+
+ protected boolean evaluateCondition()
+ {
+ return getCondition();
+ }
+
+ public boolean getInvert()
+ {
+ // This component doesn't require invert parameter.
+ return false;
+ }
+
+ public abstract boolean getCondition();
+
+ public abstract boolean isConditionMet();
+ public abstract void setConditionMet(boolean value);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.jwc
new file mode 100644
index 0000000..e3a522c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Choose.jwc
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.components.Choose">
+
+ <description>
+ Provides the context for mutually exclusive conditional evaluation.
+ </description>
+
+ <parameter name="condition" type="boolean" direction="in" default-value="true">
+ <description>
+ The condition to evaluate.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" direction="in">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="invert"/>
+
+ <property-specification name="conditionMet" type="boolean" initial-value="false"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/Otherwise.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Otherwise.jwc
new file mode 100644
index 0000000..3beb4f9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/Otherwise.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.components.When">
+ <description>
+ Otherwise is just a When component that always tries to render its body and/or emulate an element
+ and its attributes (if element is specified) .
+ </description>
+
+ <parameter name="element" type="java.lang.String" direction="in">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="condition"/>
+
+ <reserved-parameter name="invert"/>
+
+ <property-specification name="condition" type="boolean" initial-value="true"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.java b/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.java
new file mode 100644
index 0000000..9bcc3fa
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.java
@@ -0,0 +1,91 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.components;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.components.Conditional;
+
+/**
+ * Represents an alternative whithin a {@link Choose} component.
+ * The default alternative is described by the Otherwise component.
+ *
+ * [<a href="../../../../../../ComponentReference/contrib.When.html">Component Reference</a>]
+ *
+ * @author David Solis
+ * @version $Id$
+ *
+ **/
+public abstract class When extends Conditional
+{
+ /** Parent of this component. */
+
+ private Choose _choose;
+
+ /**
+ * Renders its wrapped components only if the condition is true and its parent {@link Choose}
+ * allows it. In addition, if element is specified, can emulate that HTML element.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Choose choose = getChoose();
+
+ if (choose == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("When.must-be-contained-by-choose"),
+ this,
+ null,
+ null);
+
+ if (!choose.isConditionMet() && getCondition())
+ {
+ choose.setConditionMet(true);
+ super.renderComponent(writer, cycle);
+ }
+ }
+
+ protected boolean evaluateCondition()
+ {
+ return true;
+ }
+
+ public boolean getInvert()
+ {
+ // This component doesn't require invert parameter.
+ return false;
+ }
+
+ /**
+ * @return Choose
+ */
+ public Choose getChoose()
+ {
+ return _choose;
+ }
+
+ /**
+ * Sets the choose.
+ * @param value The choose to set
+ */
+ public void setChoose(Choose value)
+ {
+ _choose = value;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.jwc
new file mode 100644
index 0000000..be9b7a8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/When.jwc
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.components.When">
+ <description>
+ If this is the first When component to evaluate to true within Choose then emulates an element
+ and its attributes (if element is specified) and/or includes a block of content.
+ </description>
+
+ <parameter name="condition" type="boolean" direction="in">
+ <description>
+ The condition to evaluate.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" direction="in">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="invert"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/components/package.html b/tapestry-contrib/src/org/apache/tapestry/contrib/components/package.html
new file mode 100644
index 0000000..14e9648
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/components/package.html
@@ -0,0 +1,14 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Contribution of foundational components.
+
+@author David Solis <a href="mailto:dsolis@apache.org">dsolis@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XCreateException.java b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XCreateException.java
new file mode 100644
index 0000000..1e8bb86
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XCreateException.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.ejb;
+
+import javax.ejb.CreateException;
+
+/**
+ * Extended version of {@link CreateException} that includes a root cause.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class XCreateException extends CreateException
+{
+ private Throwable rootCause;
+
+ public XCreateException(String message)
+ {
+ super(message);
+ }
+
+ public XCreateException(String message, Throwable rootCause)
+ {
+ super(message);
+
+ this.rootCause = rootCause;
+ }
+
+ public XCreateException(Throwable rootCause)
+ {
+ super(rootCause.getMessage());
+
+ this.rootCause = rootCause;
+ }
+
+ public Throwable getRootCause()
+ {
+ return rootCause;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XEJBException.java b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XEJBException.java
new file mode 100644
index 0000000..bd92f4c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XEJBException.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.ejb;
+
+import javax.ejb.EJBException;
+
+/**
+ * Extended version of {@link EJBException} that includes a root cause.
+ * {@link EJBException} doesn't have quite the right constructor for this ...
+ * it has an equivalent to the rootCause property, (causedByException), but
+ * doesn't have a constructor that allows us to set a custom message.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class XEJBException extends EJBException
+{
+ private Throwable rootCause;
+
+ public XEJBException(String message)
+ {
+ super(message);
+ }
+
+ public XEJBException(String message, Throwable rootCause)
+ {
+ super(message);
+
+ this.rootCause = rootCause;
+ }
+
+ public XEJBException(Throwable rootCause)
+ {
+ super(rootCause.getMessage());
+
+ this.rootCause = rootCause;
+ }
+
+ public Throwable getRootCause()
+ {
+ return rootCause;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XFinderException.java b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XFinderException.java
new file mode 100644
index 0000000..a340ee8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XFinderException.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.ejb;
+
+import javax.ejb.FinderException;
+
+/**
+ * Extended version of {@link FinderException} that includes a root cause.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class XFinderException extends FinderException
+{
+ private Throwable rootCause;
+
+ public XFinderException(String message)
+ {
+ super(message);
+ }
+
+ public XFinderException(String message, Throwable rootCause)
+ {
+ super(message);
+
+ this.rootCause = rootCause;
+ }
+
+ public XFinderException(Throwable rootCause)
+ {
+ super(rootCause.getMessage());
+
+ this.rootCause = rootCause;
+ }
+
+ public Throwable getRootCause()
+ {
+ return rootCause;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XRemoveException.java b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XRemoveException.java
new file mode 100644
index 0000000..92b0132
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/XRemoveException.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.ejb;
+
+import javax.ejb.RemoveException;
+
+/**
+ * Extended version of {@link RemoveException} that includes a root cause.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class XRemoveException extends RemoveException
+{
+ private Throwable rootCause;
+
+ public XRemoveException(String message)
+ {
+ super(message);
+ }
+
+ public XRemoveException(String message, Throwable rootCause)
+ {
+ super(message);
+
+ this.rootCause = rootCause;
+ }
+
+ public XRemoveException(Throwable rootCause)
+ {
+ super(rootCause.getMessage());
+
+ this.rootCause = rootCause;
+ }
+
+ public Throwable getRootCause()
+ {
+ return rootCause;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/package.html b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/package.html
new file mode 100644
index 0000000..1e0fbfb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/ejb/package.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Subclasses of the EJB exceptions that take an optional, additional, root cause Throwable
+to identify the underlying reason for the exception. This is less necessary in JDK 1.4, which
+support root cause for all exceptions.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/CheckBoxMultiplePropertySelectionRenderer.java b/tapestry-contrib/src/org/apache/tapestry/contrib/form/CheckBoxMultiplePropertySelectionRenderer.java
new file mode 100644
index 0000000..3661094
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/CheckBoxMultiplePropertySelectionRenderer.java
@@ -0,0 +1,103 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.form;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IPropertySelectionModel;
+
+/**
+ * Implementation of {@link IMultiplePropertySelectionRenderer} that
+ * produces a table of checkbox (<input type=checkbox>) elements.
+ *
+ * @version $Id$
+ * @author Sanjay Munjal
+ *
+ **/
+
+public class CheckBoxMultiplePropertySelectionRenderer
+ implements IMultiplePropertySelectionRenderer
+{
+
+ /**
+ * Writes the <table> element.
+ *
+ **/
+
+ public void beginRender(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ writer.begin("table");
+ writer.attribute("border", 0);
+ writer.attribute("cellpadding", 0);
+ writer.attribute("cellspacing", 2);
+ }
+
+ /**
+ * Closes the <table> element.
+ *
+ **/
+
+ public void endRender(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ writer.end(); // <table>
+ }
+
+ /**
+ * Writes a row of the table. The table contains two cells; the first is the checkbox,
+ * the second is the label for the checkbox.
+ *
+ **/
+
+ public void renderOption(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionModel model,
+ Object option,
+ int index,
+ boolean selected)
+ {
+ writer.begin("tr");
+ writer.begin("td");
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "checkbox");
+ writer.attribute("name", component.getName());
+ writer.attribute("value", model.getValue(index));
+
+ if (component.isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ if (selected)
+ writer.attribute("checked", "checked");
+
+ writer.end(); // <td>
+
+ writer.println();
+
+ writer.begin("td");
+ writer.print(model.getLabel(index));
+ writer.end(); // <td>
+ writer.end(); // <tr>
+
+ writer.println();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.java b/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.java
new file mode 100644
index 0000000..c76fad0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.java
@@ -0,0 +1,168 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.form;
+
+import java.io.IOException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.AbstractFormComponent;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.util.io.DataSqueezer;
+
+/**
+ * A conditional element on a page which will render its wrapped elements
+ * zero or one times.
+ *
+ * This component is a variant of {@link org.apache.tapestry.components.Conditional},
+ * but is designed for operation in a form. The component parameters are stored in
+ * hidden fields during rendering and are taken from those fields during the rewind,
+ * thus no StaleLink exceptions occur.
+ *
+ * [<a href="../../../../../ComponentReference/contrib.FormConditional.html">Component Reference</a>]
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class FormConditional extends AbstractFormComponent
+{
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean cycleRewinding = cycle.isRewinding();
+
+ // If the cycle is rewinding, but not this particular form,
+ // then do nothing (don't even render the body).
+
+ if (cycleRewinding && !form.isRewinding())
+ return;
+
+ String name = form.getElementId(this);
+
+ boolean condition = getCondition(cycle, form, name);
+
+ // call listener
+ IActionListener listener = getListener();
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+
+ // render the component body only if the condition is true
+ if (condition) {
+ String element = getElement();
+
+ boolean render = !cycleRewinding && Tapestry.isNonBlank(element);
+
+ if (render)
+ {
+ writer.begin(element);
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (render)
+ writer.end(element);
+ }
+ }
+
+ private boolean getCondition(IRequestCycle cycle, IForm form, String name)
+ {
+ boolean condition;
+
+ if (!cycle.isRewinding())
+ {
+ condition = getCondition();
+ writeValue(form, name, condition);
+ }
+ else
+ {
+ RequestContext context = cycle.getRequestContext();
+ String submittedConditions[] = context.getParameters(name);
+ condition = convertValue(submittedConditions[0]);
+ }
+
+ IBinding conditionValueBinding = getConditionValueBinding();
+ if (conditionValueBinding != null)
+ conditionValueBinding.setBoolean(condition);
+
+ return condition;
+ }
+
+ private void writeValue(IForm form, String name, boolean value)
+ {
+ String externalValue;
+
+ Object booleanValue = new Boolean(value);
+ try
+ {
+ externalValue = getDataSqueezer().squeeze(booleanValue);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("FormConditional.unable-to-convert-value", booleanValue),
+ this,
+ null,
+ ex);
+ }
+
+ form.addHiddenValue(name, externalValue);
+ }
+
+ private boolean convertValue(String value)
+ {
+ try
+ {
+ Object booleanValue = getDataSqueezer().unsqueeze(value);
+ return Tapestry.evaluateBoolean(booleanValue);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("FormConditional.unable-to-convert-string", value),
+ this,
+ null,
+ ex);
+ }
+ }
+
+ private DataSqueezer getDataSqueezer()
+ {
+ return getPage().getEngine().getDataSqueezer();
+ }
+
+ public boolean isDisabled()
+ {
+ return false;
+ }
+
+ public abstract boolean getCondition();
+ public abstract String getElement();
+
+ public abstract IBinding getConditionValueBinding();
+
+ public abstract IActionListener getListener();
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.jwc
new file mode 100644
index 0000000..c2ba3d3
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/FormConditional.jwc
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.form.FormConditional">
+ <description>
+ Conditionally emulates an element and its attributes (if element is specified) and/or includes a block of content if a condition is met.
+ </description>
+
+ <parameter name="condition" type="boolean" direction="in" required="yes">
+ <description>
+ The condition to evaluate.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" direction="in" required="no">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+ <parameter name="listener" type="org.apache.tapestry.IActionListener" direction="in"/>
+
+ <parameter name="conditionValue" type="boolean">
+ <description>
+ The value of the condition. During render this is obtained from
+ the condition parameter. During rewind it is the submitted condition.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="invert"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/IMultiplePropertySelectionRenderer.java b/tapestry-contrib/src/org/apache/tapestry/contrib/form/IMultiplePropertySelectionRenderer.java
new file mode 100644
index 0000000..a70af59
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/IMultiplePropertySelectionRenderer.java
@@ -0,0 +1,65 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.form;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IPropertySelectionModel;
+
+/**
+ * Defines an object that works with a {@link MultiplePropertySelection} component
+ * to render the individual elements obtained from the {@link IPropertySelectionModel model}.
+ *
+ * @version $Id$
+ * @author Sanjay Munjal
+ *
+ **/
+
+public interface IMultiplePropertySelectionRenderer
+{
+ /**
+ * Begins the rendering of the {@link MultiplePropertySelection}.
+ *
+ **/
+
+ public void beginRender(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle);
+
+ /**
+ * Invoked for each element obtained from the {@link IPropertySelectionModel model}.
+ *
+ **/
+
+ public void renderOption(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionModel model,
+ Object option,
+ int index,
+ boolean selected);
+
+ /**
+ * Ends the rendering of the {@link MultiplePropertySelection}.
+ *
+ **/
+
+ public void endRender(
+ MultiplePropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.html b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.html
new file mode 100644
index 0000000..3e4b0a5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.html
@@ -0,0 +1,5 @@
+<input jwcid="maskEdit" type="text"/>
+<input jwcid="maskValue" type="hidden"/>
+<span jwcid="maskEditScript"/>
+
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.java b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.java
new file mode 100644
index 0000000..7d596b4
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.java
@@ -0,0 +1,113 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.form;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+
+/**
+ * Provides a mask edit HTML <input type="text"> form element.
+ * <p>
+ * Mask edit field validates the text the user enters against a
+ * mask that encodes the valid forms the text can take. The mask can
+ * also format text that is displayed to the user.
+ * <p>
+ * <table border="1" cellpadding="2">
+ * <tr>
+ * <th>Mask character</th><th>Meaning in mask</th>
+ * </tr>
+ * <tr>
+ * <td> l</td><td> Mixed case letter character [a..z, A..Z]</td>
+ * </tr>
+ * <tr>
+ * <td> L</td><td> Upper case letter character [A..Z]</td>
+ * </tr>
+ * <tr>
+ * <td> a</td><td> Mixed case alpha numeric character [a..z, A..Z, 0..1]</td>
+ * </tr>
+ * <tr>
+ * <td> A</td><td> Upper case alpha numeric character [A..Z, 0..9]</td>
+ * </tr>
+ * <tr>
+ * <td> #</td><td> Numeric character [0..9]</td>
+ * </tr>
+ * <tr>
+ * <td> _</td><td> Reserved character for display, do not use.</td>
+ * </tr>
+ * <tr>
+ * <td> others</td><td> Non editable character for display.</td>
+ * </tr>
+ * </table>
+ * <p>
+ * This component requires JavaScript to be enabled in the client browser.
+ * <p>
+ * [<a href="../../../../../ComponentReference/MaskEdit.html">Component Reference</a>]
+ *
+ * @author Malcolm Edgar
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class MaskEdit extends BaseComponent
+{
+ private String _mask;
+ private IBinding _valueBinding;
+ private boolean _disabled;
+
+ public String getMask()
+ {
+ return _mask;
+ }
+
+ public void setMask(String mask)
+ {
+ _mask = mask;
+ }
+
+ public String getValue()
+ {
+ if (_valueBinding != null) {
+ return _valueBinding.getString();
+ } else {
+ return null;
+ }
+ }
+
+ public void setValue(String value)
+ {
+ _valueBinding.setString(value);
+ }
+
+ public IBinding getValueBinding()
+ {
+ return _valueBinding;
+ }
+
+ public void setValueBinding(IBinding valueBinding)
+ {
+ _valueBinding = valueBinding;
+ }
+
+ public boolean isDisabled()
+ {
+ return _disabled;
+ }
+
+ public void setDisabled(boolean disabled)
+ {
+ _disabled = disabled;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.js b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.js
new file mode 100644
index 0000000..9db04c8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.js
@@ -0,0 +1,420 @@
+/**
+ * JavaScript Mask Edit control
+ * Paul Geerts
+ * Oct 2002
+ *
+ * Note: This probably only works for English
+ * Other languages have been deprecated and will be removed in the
+ * next version of Speech (TM)
+ **/
+
+var dontDoIt; // hack for Moz because it won't cancel events properly
+var isTab; // another Moz hack
+
+
+// Init the mask edit field by creating a lookalike DIV
+// and hiding the real one
+function initMask(field, maskField) {
+
+ if (field.disabled == true) {
+ return;
+ }
+
+ var mask = maskField.value;
+ var val = field.value;
+
+ if (!val) { // if there's no val, init it with empty mask
+ val = displayMask(mask);
+ field.value = displayMask(mask);
+ }
+ // create a div and add a bunch of spans
+ // and edits to it.
+ div = document.createElement("div");
+ div.style.backgroundColor = "white";
+ for (var i = 0 ; i < mask.length ; i++) {
+ var ds = document.createElement("SPAN");
+ var v = val.substr(i,1);
+ var m = mask.substr(i,1);
+ if (v==" ") {
+ v=" ";
+ }
+ ds.innerHTML = v;
+ ds.index = i;
+ ds.mask = m;
+ ds.div = div;
+ // if we can edit this char
+ // make a little tiny edit field
+ if (isEditChar(m)) {
+ var es = document.createElement("INPUT");
+ es.style.width = "1px";
+ es.style.border="0px";
+ es.index = i;
+ es.field = field;
+ es.mask = m;
+ es.display = ds;
+ ds.editField = es;
+ es.div = div;
+ div.appendChild(es); // set up some events
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ addEvent("keypress", es, changeBitIE);
+ } else {
+ addEvent("keypress", es, changeBitNS);
+ }
+ addEvent("keydown", es, specialKey); // keydown handles stuff like home, end etc
+ addEvent("click", ds, click);
+ }
+
+ div.appendChild(ds);
+
+ }
+
+ // the final edit field on the end
+ var es =document.createElement("INPUT");
+ es.style.width = "1px";
+ es.style.border="0px";
+ es.div = div;
+ div.appendChild(es);
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ addEvent("keypress", es, changeBitIE);
+ } else {
+ addEvent("keypress", es, changeBitNS);
+ }
+ addEvent("keydown", es, specialKey);
+
+ div.noWrap = true; // force single line display
+
+ formatDiv(div, field); // format the DIV to look like an edit box
+ field.style.display = 'none';
+ field.parentNode.insertBefore(div, field);
+ addEvent("click", div, divClick);
+}
+
+function formatDiv(div, field) {
+ // make it look like an IE edit
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ div.style.fontFamily="courier";
+ div.style.fontSize="10pt";
+ div.style.width = field.offsetWidth;
+ div.style.height = field.offsetHeight;
+ if (navigator.appVersion.match(/6.0/)) { // IE 6 is different
+ div.style.border = "1px solid #7F9DB9";
+ } else {
+ div.style.borderLeft = "2px solid #606060";
+ div.style.borderTop = "2px solid #606060";
+ div.style.borderRight = "1px solid #aaaaaa";
+ div.style.borderBottom = "1px solid #aaaaaa";
+ }
+
+ } else {
+ // Mozilla edit look-a-like
+ div.style.fontFamily="courier";
+ div.style.fontSize="10pt";
+ div.style.border="2px inset #cccccc";
+ if (field.size) {
+ div.style.widh = 13 * field.size;
+ } else {
+ div.style.width = "130px";
+ }
+ }
+}
+
+
+function isEditChar(c) { // is this char a meaningful mask char
+ switch (c) {
+ case "_":
+ case "#":
+ case "a":
+ case "A":
+ case "l":
+ case "L":
+ return true;
+ default:
+ return false;
+ }
+ return false;
+}
+
+function displayMaskChar(c) { // display mask chars as _
+ if (isEditChar(c)) { // otherwise just show normal char
+ return "_";
+ } else {
+ return c;
+ }
+}
+
+function displayMask(mask) { // display entire mask using about subroutine
+ var d = "";
+ for (var i = 0 ; i < mask.length ; i++) {
+ d+=displayMaskChar(mask.substr(i,1));
+ }
+ return d;
+}
+
+function divClick(e) { // when the main DIV is clicked, focus the end of the edit
+ var d = getEventObject(e);
+ if (d && d.lastChild) {
+ try {
+ d.lastChild.focus();
+ } catch (e) {
+ // nuffin
+ }
+ }
+}
+
+function specialKey(e) { // deal with special keys like backspace, delete etc
+ var s = getEventObject(e);
+ var code = e.keyCode;
+ dontDoIt = true; // Moz needs these, as I can't seem to cancel events properly
+ isTab = false; // Moz can't handle tabs well either
+ switch (code) {
+ case 8: // backspace
+ var b = getPrevEdit(s);
+ if (b) {
+ b.display.innerHTML = displayMaskChar(b.mask);
+ var i = b.index;
+ b.field.value = b.field.value.substr(0, i) +
+ displayMaskChar(b.mask) + b.field.value.substr(i+1, b.field.value.length - i);
+ b.focus();
+ }
+ cancelEvent(e);
+ return false;
+ case 46: // delete
+ if (s.display) {
+ s.display.innerHTML = displayMaskChar(s.mask);
+ var i = s.index;
+ s.field.value = s.field.value.substr(0, i) + displayMaskChar(s.mask) +
+ s.field.value.substr(i+1, s.field.value.length - i);
+ }
+ cancelEvent(e);
+ return false;
+ break;
+ case 37: // left
+ var p = getPrevEdit(s);
+ if (p) {
+ p.focus();
+ }
+ cancelEvent(e);
+ return false;
+ case 39: // right
+ var n = getNextEdit(s);
+ if (n) {
+ n.focus();
+ }
+ cancelEvent(e);
+ return false;
+ case 36: // home
+ s.div.firstChild.focus();
+ cancelEvent(e);
+ return false;
+ case 35: // end
+ s.div.lastChild.focus();
+ cancelEvent(e);
+ return false;
+ case 9: // tab
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ if (!e.shiftKey) {
+ s.div.lastChild.focus();
+ } else {
+ s.div.firstChild.focus();
+ }
+ return;
+ } else { // is mozilla/netscape
+ isTab = true; // best i can do really
+ }
+ break;
+ }
+
+ dontDoIt = false;
+}
+
+function moveForward(s) { // focus next edit
+ var b = getNextEdit(s);
+ if (b) {
+ b.focus();
+ }
+}
+
+function moveBackward(s) { // focus previous edit
+ var b = getPrevEdit(s);
+ if (b) {
+ b.focus();
+ }
+}
+
+function isInsertOK(code, s) { // check if you're good to insert a char
+ var mchar = s.mask;
+ switch (mchar) {
+ case "_":
+ return true;
+ break;
+ case "#":
+ return checkDigit(code);
+ break;
+ case "a":
+ return checkAlphaNumeric(code);
+ break;
+ case "A":
+ return checkUpCaseAlphaNumeric(code);
+ break;
+ case "l":
+ return checkAlpha(code);
+ break;
+ case "L":
+ return checkUpCaseAlpha(code);
+ break;
+ }
+ return false;
+}
+
+// functions to check the key code, good ol ASCII
+// fairly straightforward
+
+function checkDigit(code) {
+ if ((code>=48) && (code<=57)) {
+ return code;
+ } else {
+ return null;
+ }
+}
+
+function checkAlpha(code) {
+ if (((code>=65) && (code<=90)) || ((code>=97) && (code<=122))) {
+ return code;
+ } else {
+ return null;
+ }
+}
+
+function checkUpCaseAlpha(code) {
+ if ((code>=65) && (code<=90)) {
+ return code;
+ } else if ((code>=97) && (code<=122)) {
+ return code - 32;
+ } else {
+ return null;
+ }
+}
+
+function checkAlphaNumeric(code) {
+ if (((code>=65) && (code<=90)) || ((code>=97) && (code<=122)) || ((code>=48) && (code<=57))) {
+ return code;
+ } else {
+ return null;
+ }
+}
+
+function checkUpCaseAlphaNumeric(code) {
+ if ((code>=65) && (code<=90)) {
+ return code;
+ } else if ((code>=97) && (code<=122)) {
+ return code - 32;
+ } else if ((code>=48) && (code<=57)) {
+ return code;
+ } else {
+ return null;
+ }
+}
+
+
+function changeBitNS(e) { // handle key events in NS
+ var es = getEventObject(e);
+ if (!isTab) {
+ if (es.display) {
+ if (!dontDoIt) {
+ var code = e.charCode;
+ if (code = isInsertOK(code, es)) {
+ var c = String.fromCharCode(code);
+ es.display.innerHTML = c
+ var i = es.index;
+ es.field.value = es.field.value.substr(0, i) + c + es.field.value.substr(i+1, es.field.value.length - i);
+ moveForward(es);
+ }
+ }
+ es.value = "";
+ cancelEvent(e);
+ }
+ return false;
+ }
+}
+
+function changeBitIE(e) { // handle key events in IE
+ var es = getEventObject(e);
+ if (es.display) {
+ var code = e.keyCode;
+ if (code = isInsertOK(code, es)) {
+ var c = String.fromCharCode(code);
+ es.display.innerHTML = c;
+ var i = es.index;
+ es.field.value = es.field.value.substr(0, i) + c + es.field.value.substr(i+1, es.field.value.length - i);
+ moveForward(es);
+ es.value = "";
+ }
+ }
+ cancelEvent(e);
+ return false;
+}
+
+function click(e) { // clicking on a display span focuses the edit
+ var s = getEventObject(e);
+ s.editField.focus();
+ cancelEvent(e);
+ return false;
+}
+
+function getPrevEdit(s) { // get previous input field
+ var b = s.previousSibling;
+ while (b && (b.tagName!="INPUT")) {
+ b = b.previousSibling;
+ }
+ return b;
+}
+
+function getNextEdit(s) { // get previous next field
+ var b = s.nextSibling;
+ while (b && (b.tagName!="INPUT")) {
+ b = b.nextSibling;
+ }
+ return b;
+}
+
+function cancelEvent(e) { // kill event propagation
+ e.cancelBubble = true;
+ e.cancel = true;
+ if (navigator.appName != "Microsoft Internet Explorer") {
+ e.stopPropagation(); // doesn't seem to work for key events
+ e.preventDefault();
+ }
+}
+
+
+function getEventObject(e) { // utility function to retrieve object from event
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ return e.srcElement;
+ } else { // is mozilla/netscape
+ // need to crawl up the tree to get the first "real" element
+ // i.e. a tag, not raw text
+ var o = e.target;
+ while (!o.tagName) {
+ o = o.parentNode;
+ }
+ return o;
+ }
+}
+
+function addEvent(name, obj, funct) { // utility function to add event handlers
+
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ obj.attachEvent("on"+name, funct);
+ } else { // is mozilla/netscape
+ obj.addEventListener(name, funct, false);
+ }
+}
+
+function deleteEvent(name, obj, funct) { // utility function to delete event handlers
+
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ obj.detachEvent("on"+name, funct);
+ } else { // is mozilla/netscape
+ obj.removeEventListener(name, funct, false);
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.jwc
new file mode 100644
index 0000000..a796fcb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.form.MaskEdit" allow-informal-parameters="no">
+
+ <parameter name="mask" direction="in" type="java.lang.String" required="yes"/>
+ <parameter name="value" direction="custom" type="java.lang.String" required="yes"/>
+ <parameter name="disabled" direction="in" type="boolean" required="no"/>
+
+ <component id="maskEdit" type="TextField">
+ <binding name="value" expression="value"/>
+ <binding name="maxlength" expression="mask.length()"/>
+ <binding name="size" expression="mask.length()"/>
+ <binding name="disabled" expression="disabled"/>
+ </component>
+
+ <component id="maskValue" type="Hidden">
+ <binding name="value" expression="mask"/>
+ <binding name="encode" expression="false"/>
+ </component>
+
+ <component id="maskEditScript" type="Script">
+ <binding name="maskEdit" expression="components.maskEdit"/>
+ <binding name="maskValue" expression="components.maskValue"/>
+ <binding name="script" expression='"MaskEdit.script"'/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.script b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.script
new file mode 100644
index 0000000..e6c7756
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MaskEdit.script
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Howard Lewis Ship//Tapestry Script 1.2//EN"
+ "http://tapestry.sf.net/dtd/Script_1_2.dtd">
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/contrib/form/MaskEdit.js"/>
+
+<input-symbol key="maskEdit" class="org.apache.tapestry.form.TextField" required="yes"/>
+<input-symbol key="maskValue" class="org.apache.tapestry.form.Hidden" required="yes"/>
+
+<let key="formName">
+ ${maskEdit.form.name}
+</let>
+
+<let key="functionName">
+ ${maskEdit.name}_init
+</let>
+
+
+<body>
+function ${functionName}() {
+ initMask(document.${formName}.${maskEdit.name},
+ document.${formName}.${maskValue.name});
+}
+</body>
+
+<initialization>
+ ${functionName}();
+</initialization>
+
+</script>
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.java b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.java
new file mode 100644
index 0000000..3a412d3
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.java
@@ -0,0 +1,216 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.form;
+
+import java.util.List;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.AbstractFormComponent;
+import org.apache.tapestry.form.IPropertySelectionModel;
+
+/**
+ * A component which uses <input type=checkbox> to
+ * set a property of some object. Typically, the values for the object
+ * are defined using an {@link org.apache.commons.lang.enum.Enum}. A MultiplePropertySelection is dependent on
+ * an {link IPropertySelectionModel} to provide the list of possible values.
+ *
+ * <p>Often, this is used to select one or more {@link org.apache.commons.lang.enum.Enum} to assign to a property; the
+ * {@link org.apache.tapestry.form.EnumPropertySelectionModel} class simplifies this.
+ *
+ * <p>The {@link org.apache.tapestry.contrib.palette.Palette} component
+ * is more powerful, but requires client-side JavaScript and
+ * is not fully cross-browser compatible.
+ *
+ * <p>
+ *
+ * <table border=1>
+ * <tr>
+ * <td>Parameter</td>
+ * <td>Type</td>
+ * <td>Direction</td>
+ * <td>Required</td>
+ * <td>Default</td>
+ * <td>Description</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>selectedList</td>
+ * <td>java.util.List</td>
+ * <td>in-out</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The property to set. During rendering, this property is read, and sets
+ * the default value of the options in the select.
+ * When the form is submitted, list is cleared, then has each
+ * selected option added to it. </td> </tr>
+ *
+ * <tr>
+ * <td>renderer</td>
+ * <td>{@link IMultiplePropertySelectionRenderer}</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>shared instance of {@link CheckBoxMultiplePropertySelectionRenderer}</td>
+ * <td>Defines the object used to render this component. The default
+ * renders a table of checkboxes.</td></tr>
+ *
+ * <tr>
+ * <td>model</td>
+ * <td>{@link IPropertySelectionModel}</td>
+ * <td>in</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The model provides a list of possible labels, and matches those labels
+ * against possible values that can be assigned back to the property.</td> </tr>
+ *
+ * <tr>
+ * <td>disabled</td>
+ * <td>boolean</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>false</td>
+ * <td>Controls whether the <select> is active or not. A disabled PropertySelection
+ * does not update its value parameter.
+ *
+ * <p>Corresponds to the <code>disabled</code> HTML attribute.</td>
+ * </tr>
+ *
+ * </table>
+ *
+ * <p>Informal parameters are not allowed.
+ *
+ *
+ * @version $Id$
+ * @author Sanjay Munjal
+ *
+ **/
+
+public abstract class MultiplePropertySelection extends AbstractFormComponent
+{
+
+ /**
+ * A shared instance of {@link CheckBoxMultiplePropertySelectionRenderer}.
+ *
+ **/
+
+ public static final IMultiplePropertySelectionRenderer DEFAULT_CHECKBOX_RENDERER =
+ new CheckBoxMultiplePropertySelectionRenderer();
+
+ public abstract IBinding getSelectedListBinding();
+
+ protected void finishLoad()
+ {
+ setRenderer(DEFAULT_CHECKBOX_RENDERER);
+ }
+
+ /**
+ * Returns true if the component is disabled (this is relevant to the
+ * renderer).
+ *
+ **/
+
+ public abstract boolean isDisabled();
+
+ /**
+ * Renders the component, much of which is the responsiblity
+ * of the {@link IMultiplePropertySelectionRenderer renderer}. The possible options,
+ * their labels, and the values to be encoded in the form are provided
+ * by the {@link IPropertySelectionModel model}.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ List selectedList = (List) getSelectedListBinding().getObject("selectedList", List.class);
+
+ if (selectedList == null)
+ throw Tapestry.createRequiredParameterException(this, "selectedList");
+
+ IPropertySelectionModel model = getModel();
+
+ if (model == null)
+ throw Tapestry.createRequiredParameterException(this, "model");
+
+ // Handle the form processing first.
+ if (rewinding)
+ {
+ // If disabled, ignore anything that comes up from the client.
+
+ if (isDisabled())
+ return;
+
+ // get all the values
+ String[] optionValues = cycle.getRequestContext().getParameters(name);
+
+ // Clear the list
+
+ selectedList.clear();
+
+ // Nothing was selected
+ if (optionValues != null)
+ {
+
+ // Go through the array and translate and put back in the list
+ for (int i = 0; i < optionValues.length; i++)
+ {
+ // Translate the new value
+ Object selectedValue = model.translateValue(optionValues[i]);
+
+ // Add this element in the list back
+ selectedList.add(selectedValue);
+ }
+ }
+
+ return;
+ }
+
+ IMultiplePropertySelectionRenderer renderer = getRenderer();
+
+ // Start rendering
+ renderer.beginRender(this, writer, cycle);
+
+ int count = model.getOptionCount();
+
+ for (int i = 0; i < count; i++)
+ {
+ Object option = model.getOption(i);
+
+ // Try to find the option in the list and if yes, then it is checked.
+ boolean optionSelected = selectedList.contains(option);
+
+ renderer.renderOption(this, writer, cycle, model, option, i, optionSelected);
+ }
+
+ // A PropertySelection doesn't allow a body, so no need to worry about
+ // wrapped components.
+ renderer.endRender(this, writer, cycle);
+ }
+
+ public abstract IPropertySelectionModel getModel();
+
+ public abstract IMultiplePropertySelectionRenderer getRenderer();
+
+ public abstract void setRenderer(IMultiplePropertySelectionRenderer renderer);
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.jwc
new file mode 100644
index 0000000..ae91cb2
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/form/MultiplePropertySelection.jwc
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.form.MultiplePropertySelection"
+ allow-body="no" allow-informal-parameters="no">
+
+ <parameter name="model"
+ type="org.apache.tapestry.form.IPropertySelectionModel" required="yes" direction="in"/>
+
+ <parameter name="selectedList" type="java.util.List" required="yes"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.contrib.form.IMultiplePropertySelectionRenderer"
+ direction="in"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.java b/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.java
new file mode 100644
index 0000000..f50d4b9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.java
@@ -0,0 +1,117 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.informal;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ *
+ * A version of the Any component that inherits the informal attributes of its parent.
+ * This component has been deprecated in favour of the 'inherit-informal-parameters'
+ * tag that indicates that a particular component must inherit the informal parameters
+ * of its parent. This tag is available in the page or component specification file.
+ *
+ * @deprecated
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.2
+ *
+ **/
+
+public class InheritInformalAny extends AbstractComponent
+{
+ // Bindings
+ private IBinding m_objElementBinding;
+
+ public IBinding getElementBinding()
+ {
+ return m_objElementBinding;
+ }
+
+ public void setElementBinding(IBinding objElementBinding)
+ {
+ m_objElementBinding = objElementBinding;
+ }
+
+ protected void generateParentAttributes(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String attribute;
+
+ IComponent objParent = getContainer();
+ if (objParent == null)
+ return;
+
+ IComponentSpecification specification = objParent.getSpecification();
+ Map bindings = objParent.getBindings();
+ if (bindings == null)
+ return;
+
+ Iterator i = bindings.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ String name = (String) entry.getKey();
+
+ // Skip over formal parameters stored in the bindings
+ // Map. We're just interested in informal parameters.
+
+ if (specification.getParameter(name) != null)
+ continue;
+
+ IBinding binding = (IBinding) entry.getValue();
+
+ Object value = binding.getObject();
+ if (value == null)
+ continue;
+
+ if (value instanceof IAsset)
+ {
+ IAsset asset = (IAsset) value;
+
+ // Get the URL of the asset and insert that.
+ attribute = asset.buildURL(cycle);
+ }
+ else
+ attribute = value.toString();
+
+ writer.attribute(name, attribute);
+ }
+
+ }
+
+ public void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String strElement = m_objElementBinding.getObject().toString();
+
+ writer.begin(strElement);
+ generateParentAttributes(writer, cycle);
+ renderInformalParameters(writer, cycle);
+
+ renderBody(writer, cycle);
+
+ writer.end();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.jwc
new file mode 100644
index 0000000..e9d9eb1
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/informal/InheritInformalAny.jwc
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.informal.InheritInformalAny" allow-body="yes" allow-informal-parameters="yes">
+ <parameter name="element" type="java.lang.String" required="yes" direction="custom"/>
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_HRp4.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_HRp4.gif
new file mode 100644
index 0000000..42ff5ec
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_HRp4.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Hp3.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Hp3.gif
new file mode 100644
index 0000000..5f2860d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Hp3.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NBanner.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NBanner.gif
new file mode 100644
index 0000000..0105a1b
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NBanner.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NRp2.gif
new file mode 100644
index 0000000..8590996
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Np1.gif
new file mode 100644
index 0000000..78ee58c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Engine_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.css b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.css
new file mode 100644
index 0000000..c8852b0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.css
@@ -0,0 +1,201 @@
+H1 {
+ font-size: 12pt;
+ font-weight: bold;
+
+}
+
+H2 {}
+
+H3 {}
+
+A {
+ color:#ffffff;
+}
+
+A:Visited {}
+
+A:Active {}
+
+A:Hover {}
+
+SPAN.error
+{
+ color: Red;
+ font-weight: bold;
+ background-color : "#330066"
+}
+
+BODY {
+ font-family: "Trebuchet MS", sans-serif;
+ background-color: #839cd1;
+}
+
+
+TABLE.inspector-data TR.odd TD {
+ text-align : left;
+ color : Black;
+ background-color : Silver;
+}
+
+
+TABLE.inspector-data TR.even TH
+{
+ text-align : right;
+ font-weight: bold;
+}
+
+TABLE.inspector-data TR.odd TH
+{
+ text-align: right;
+ color : Black;
+ background-color : Silver;
+ font-weight: bold;
+}
+
+TABLE.inspector-data TR.even TD {
+ text-align : left;
+}
+
+TABLE.inspector-data,
+TABLE.selector
+{
+ font-size: 9pt;
+}
+
+TABLE.selector TD.page-link
+{
+ font-style: italic;
+}
+
+TABLE.selector TD
+{
+ verticle-align: center;
+}
+
+TABLE.inspector-data TR.heading TH,
+TABLE.template TH
+{
+ text-align: center;
+ color : White;
+ background-color : "#330066";
+ font-weight: bold;
+}
+
+TABLE.template TD
+{
+ background-color: Silver;
+ font-size: small;
+}
+
+SPAN.message
+{
+ color : Silver
+ font-size: large;
+}
+
+SPAN.jwc-tag
+{
+ font-weight: bold;
+}
+
+SPAN.jwc-id, SPAN.localized-string
+{
+ font-style: italic;
+}
+
+
+
+TABLE.request-context-border {
+ border-width : 1;
+ border-color : Black;
+ font-size: 9pt;
+}
+
+SPAN.request-context-object {
+ font-weight : bold;
+ text-align : left;
+ font-size: 12pt;
+}
+
+TR.request-context-section TH {
+ text-align : center;
+ color : White;
+ background-color : Blue;
+}
+
+TR.request-context-header TH {
+ text-align : center;
+ color : White;
+ background-color : Blue;
+}
+
+TABLE.request-context-object TR.odd TD {
+ text-align : left;
+ color : Black;
+ background-color : Silver;
+}
+
+TABLE.request-context-object TR.odd TH {
+ color : Black;
+ background-color : Silver;
+ text-align : right;
+}
+
+TABLE.request-context-object TR.even TD {
+ text-align : left;
+}
+
+TABLE.request-context-object TR.even TH {
+ text-align : right;
+}
+
+TABLE.request-context-object {
+ width : 100%;
+ font-size: 9pt;
+}
+
+TABLE.request-context-object TR {
+ vertical-align : text-top;
+}
+
+TABLE.exception-display TR.even {
+ top : auto;
+}
+
+TABLE.exception-displaY TD
+{
+ width: 100%;
+}
+
+TABLE.exception-display TR.even TH {
+ text-align : right;
+ font-weight : bold;
+}
+
+TABLE.exception-display TR.odd TD {
+ text-align : left;
+ background-color : Silver;
+}
+
+TABLE.exception-display TR.odd TH {
+ text-align : right;
+ font-weight : bold;
+ background-color : Silver;
+}
+
+TABLE.exception-display TR.even TD {
+ text-align : left;
+}
+
+TABLE.exception-display TR.stack-trace {
+ font-size : small;
+ font-family : sans-serif;
+ text-align : left;
+}
+
+UL
+{
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-left: 20px;
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.html
new file mode 100644
index 0000000..42d16d6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.html
@@ -0,0 +1,31 @@
+<!-- $Id$ -->
+<span jwcid="@Shell" title="ognl:inspectorTitle" stylesheet="ognl:assets.stylesheet">
+<body jwcid="@Body">
+
+<span jwcid="@inspector:Selector">
+
+<span jwcid="@inspector:ViewTabs">
+
+<span jwcid="@RenderBlock" block="ognl:blockForView"/>
+
+</span>
+</span>
+
+<span jwcid="specificationBlock@Block">
+<span jwcid="@inspector:ShowSpecification"/>
+</span>
+
+<span jwcid="templateBlock@Block">
+<span jwcid="@inspector:ShowTemplate"/>
+</span>
+
+<span jwcid="propertiesBlock@Block">
+<span jwcid="@inspector:ShowProperties"/>
+</span>
+
+<span jwcid="engineBlock@Block">
+<span jwcid="@inspector:ShowEngine"/>
+</span>
+
+</body>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.java
new file mode 100644
index 0000000..631e893
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.java
@@ -0,0 +1,145 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.components.Block;
+import org.apache.tapestry.html.BasePage;
+
+/**
+ * The Tapestry Inspector page.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public abstract class Inspector extends BasePage
+{
+ private Map _blocks = new HashMap();
+
+ protected void finishLoad()
+ {
+ _blocks.put(View.TEMPLATE, getComponent("templateBlock"));
+ _blocks.put(View.SPECIFICATION, getComponent("specificationBlock"));
+ _blocks.put(View.ENGINE, getComponent("engineBlock"));
+ _blocks.put(View.PROPERTIES, getComponent("propertiesBlock"));
+ }
+
+ public abstract View getView();
+
+ public abstract void setView(View value);
+
+ public abstract String getInspectedPageName();
+
+ public abstract void setInspectedPageName(String value);
+
+ public abstract String getInspectedIdPath();
+
+ public abstract void setInspectedIdPath(String value);
+
+ /**
+ * Invoked to change the component being inspected within the current
+ * page.
+ *
+ * @since 1.0.6
+ **/
+
+ public void selectComponent(String idPath)
+ {
+ setInspectedIdPath(idPath);
+ }
+
+ /**
+ * Method invoked by the {@link InspectorButton} component,
+ * to begin inspecting a page.
+ *
+ **/
+
+ public void inspect(String pageName, IRequestCycle cycle)
+ {
+ setInspectedPageName(pageName);
+ selectComponent((String) null);
+
+ cycle.activate(this);
+ }
+
+ /**
+ * Listener for the component selection, which allows a particular component.
+ *
+ * <p>The context is a single string,
+ * the id path of the component to be selected (or null to inspect
+ * the page itself). This invokes
+ * {@link #selectComponent(String)}.
+ *
+ **/
+
+ public void selectComponent(IRequestCycle cycle)
+ {
+ Object[] parameters = cycle.getServiceParameters();
+
+ String newIdPath;
+
+ // The up button may generate a null context.
+
+ if (parameters == null)
+ newIdPath = null;
+ else
+ newIdPath = (String) parameters[0];
+
+ selectComponent(newIdPath);
+ }
+
+ /**
+ * Returns the {@link IPage} currently inspected by the Inspector, as determined
+ * from the inspectedPageName property.
+ *
+ **/
+
+ public IPage getInspectedPage()
+ {
+ return getRequestCycle().getPage(getInspectedPageName());
+ }
+
+ /**
+ * Returns the {@link IComponent} current inspected; this is determined
+ * from the inspectedPageName and inspectedIdPath properties.
+ *
+ **/
+
+ public IComponent getInspectedComponent()
+ {
+ return getInspectedPage().getNestedComponent(getInspectedIdPath());
+ }
+
+ public String getInspectorTitle()
+ {
+ return "Tapestry Inspector: " + getEngine().getSpecification().getName();
+ }
+
+ /**
+ * Returns the {@link Block} for the currently selected view.
+ *
+ **/
+
+ public Block getBlockForView()
+ {
+ return (Block) _blocks.get(getView());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.library b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.library
new file mode 100644
index 0000000..17e172a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.library
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- Inspector.library,v 1.1 2002/08/24 16:07:50 hship Exp -->
+<!DOCTYPE library-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<library-specification/>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.page b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.page
new file mode 100644
index 0000000..03b88a4
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Inspector.page
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.contrib.inspector.Inspector">
+
+ <property-specification name="view" type="org.apache.tapestry.contrib.inspector.View"
+ persistent="yes"
+ initial-value="@org.apache.tapestry.contrib.inspector.View@SPECIFICATION"/>
+ <property-specification name="inspectedPageName" type="java.lang.String" persistent="yes"/>
+ <property-specification name="inspectedIdPath" type="java.lang.String" persistent="yes"/>
+
+ <private-asset name="stylesheet" resource-path="Inspector.css"/>
+
+</page-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.html
new file mode 100644
index 0000000..aa38e55
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.html
@@ -0,0 +1,10 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<div id="tapestryInspector" style="position:absolute; border-color:black; border-width:2px; border-style:solid; padding:3px; background-color:#839cd1;">
+<a jwcid="link"><img jwcid="rollover"/></a>
+</div>
+
+</span>
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.java
new file mode 100644
index 0000000..9fd512d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.java
@@ -0,0 +1,133 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.engine.IScriptSource;
+import org.apache.tapestry.html.Body;
+
+/**
+ * Component that can be placed into application pages that will launch
+ * the inspector in a new window.
+ *
+ * [<a href="../../../../../../ComponentReference/InspectorButton.html">Component Reference</a>]
+ *
+ * <p>Because the InspectorButton component is implemented using a {@link org.apache.tapestry.html.Rollover},
+ * the containing page must use a {@link Body} component instead of
+ * a <body> tag.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class InspectorButton extends BaseComponent implements IDirect
+{
+ private boolean _disabled = false;
+
+ /**
+ * Gets the listener for the link component.
+ *
+ * @since 1.0.5
+ **/
+
+ public void trigger(IRequestCycle cycle)
+ {
+ String name = getNamespace().constructQualifiedName("Inspector");
+
+ Inspector inspector = (Inspector) cycle.getPage(name);
+
+ inspector.inspect(getPage().getPageName(), cycle);
+ }
+
+ /**
+ * Renders the script, then invokes the normal implementation.
+ *
+ * @since 1.0.5
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (_disabled || cycle.isRewinding())
+ return;
+
+ IEngine engine = getPage().getEngine();
+ IScriptSource source = engine.getScriptSource();
+
+ IResourceLocation scriptLocation =
+ getSpecification().getSpecificationLocation().getRelativeLocation(
+ "InspectorButton.script");
+
+ IScript script = source.getScript(scriptLocation);
+
+ Map symbols = new HashMap();
+
+ IEngineService service = engine.getService(Tapestry.DIRECT_SERVICE);
+ ILink link = service.getLink(cycle, this, null);
+
+ symbols.put("URL", link.getURL());
+
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("InspectorButton.must-be-contained-by-body"),
+ this,
+ null,
+ null);
+
+ script.execute(cycle, body, symbols);
+
+ // Now, go render the rest from the template.
+
+ super.renderComponent(writer, cycle);
+ }
+
+ public boolean isDisabled()
+ {
+ return _disabled;
+ }
+
+ public void setDisabled(boolean disabled)
+ {
+ _disabled = disabled;
+ }
+
+ /**
+ * Always returns false.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public boolean isStateful()
+ {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.jwc
new file mode 100644
index 0000000..cccf00d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.jwc
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.InspectorButton"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+<![CDATA[
+Includes the Inspector button on the page (which dynamically positions itself in the
+lower right corner). Clicking the button raises the Tapestry Inspector in a pop-up
+window.
+]]>
+ </description>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <component id="link" type="GenericLink">
+ <static-binding name="href">javascript:ti_raiseInspector();</static-binding>
+ </component>
+
+ <component id="rollover" type="Rollover">
+ <binding name="image" expression="assets.logo"/>
+ <binding name="focus" expression="assets.inspector"/>
+ </component>
+
+ <private-asset name="logo" resource-path="tapestry-logo.gif"/>
+ <private-asset name="inspector" resource-path="inspector-rollover.gif"/>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.script b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.script
new file mode 100644
index 0000000..a0e1f31
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/InspectorButton.script
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Howard Lewis Ship//Tapestry Script 1.2//EN"
+ "http://tapestry.sf.net/dtd/Script_1_2.dtd">
+<!--
+
+Adds scripting support for the ShowInspector component.
+
+Prefixes all variables and functions with "ti_" (for Tapestry Inspector).
+
+Expects that the Inspector is inside a <div> named "tapestryInspector".
+
+Input symbols:
+ URL - The complete URL needed for to raise the Inspector
+
+-->
+<script>
+
+<include-script resource-path="/org/apache/tapestry/html/PracticalBrowserSniffer.js"/>
+
+<input-symbol key="URL" class="java.lang.String" required="yes"/>
+
+<body>
+var ti = new Object();
+
+ti.oldX = 0;
+ti.oldY = 0;
+
+function ti_positionInspector()
+{
+ var object;
+ var width;
+ var height;
+
+ if (navigator.family == "nn4")
+ {
+ object = document.tapestryInspector;
+ width = innerWidth + pageXOffset; <!-- Doesn't properly account for scrollbars! -->
+ height = innerHeight + pageYOffset;
+ }
+ else
+ {
+ object = document.getElementById("tapestryInspector");
+
+ if (navigator.OS == "mac")
+ {
+ width = document.body.offsetWidth;
+ height = document.body.offsetWidth;
+ }
+ else if (navigator.family == "gecko")
+ {
+ width = innerWidth + pageXOffset;
+ height = innerHeight + pageYOffset;
+ }
+ else
+ {
+ // IE 5, 6? on PC
+ width = document.body.clientWidth + document.body.scrollLeft;
+ height = document.body.clientHeight + document.body.scrollTop;
+ }
+ }
+
+ // The width/height of the animation, plus
+ // a couple of pixels of border.
+
+ var indent = 65;
+
+ var x = width - indent;
+ var y = height - indent;
+
+ if (navigator.family == "nn4")
+ {
+ if (x != ti.oldX || y != ti.oldY)
+ {
+ object.moveTo(x, y);
+ object.visibility = "visible";
+ }
+ }
+ else
+ {
+ if (x != ti.oldX)
+ {
+ object.style.left = x + "px";
+ ti.oldX = x;
+ }
+ if (y != ti.oldY)
+ {
+ object.style.top = y + "px";
+ ti.oldY = y;
+ }
+
+ object.style.visibility = "visible";
+ }
+
+
+
+ // Reposition it every quarter second.
+
+ window.setTimeout("ti_positionInspector()", 250);
+}
+
+function ti_raiseInspector()
+{
+ var newWindow = window.open(
+ "${URL}",
+ "TapestryInspector",
+ "titlebar,resizable,scrollbars,width=700,height=600");
+
+ newWindow.focus();
+}
+</body>
+
+<initialization>
+ti_positionInspector();
+</initialization>
+
+</script>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_HRp4.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_HRp4.gif
new file mode 100644
index 0000000..e2fe821
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_HRp4.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Hp3.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Hp3.gif
new file mode 100644
index 0000000..8aa25cb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Hp3.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NBanner.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NBanner.gif
new file mode 100644
index 0000000..5fa7f5b
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NBanner.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NRp2.gif
new file mode 100644
index 0000000..12b13a1
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Np1.gif
new file mode 100644
index 0000000..917436a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Properties_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_NRp2.gif
new file mode 100644
index 0000000..371cba0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1.gif
new file mode 100644
index 0000000..eb87bf4
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1_disabled.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1_disabled.gif
new file mode 100644
index 0000000..0cfdd51
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Reset_Np1_disabled.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_NRp2.gif
new file mode 100644
index 0000000..8fed715
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_Np1.gif
new file mode 100644
index 0000000..dda92b0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Restart_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.html
new file mode 100644
index 0000000..15ecbf7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.html
@@ -0,0 +1,23 @@
+<!-- $Id$ -->
+
+<table class="selector">
+ <tr valign=center>
+ <td>
+ <form jwcid="form">
+ <select jwcid="selectPage"/>
+ </form>
+ </td>
+ <td class="page-link">
+ <a jwcid="page">page</a>
+ </td>
+<span jwcid="e">
+ <td>>></td>
+ <td>
+ <a jwcid="component"><span jwcid="insertId"/></a>
+ </td>
+</span>
+ </tr>
+</table>
+
+<span jwcid="renderBody"/>
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.java
new file mode 100644
index 0000000..158025d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.java
@@ -0,0 +1,150 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ISpecificationSource;
+import org.apache.tapestry.form.IPropertySelectionModel;
+import org.apache.tapestry.form.StringPropertySelectionModel;
+
+/**
+ * Component of the {@link Inspector} page used to select the page and "crumb trail"
+ * of the inspected component.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class Selector extends BaseComponent
+{
+ /**
+ * When the form is submitted,
+ * the inspectedPageName of the {@link Inspector} page will be updated,
+ * but we need to reset the inspectedIdPath as well.
+ *
+ **/
+
+ public void formSubmit(IRequestCycle cycle)
+ {
+ Inspector inspector = (Inspector) getPage();
+
+ inspector.selectComponent((String) null);
+ }
+
+ /**
+ * Returns an {IPropertySelectionModel} used to select the name of the page
+ * to inspect. The page names are sorted.
+ *
+ **/
+
+ public IPropertySelectionModel getPageModel()
+ {
+ return new StringPropertySelectionModel(getPageNames());
+ }
+
+ /**
+ * The crumb trail is all the components from the inspected component up to
+ * (but not including) the page.
+ *
+ **/
+
+ public List getCrumbTrail()
+ {
+ List result = null;
+
+ Inspector inspector = (Inspector) getPage();
+ IComponent component = inspector.getInspectedComponent();
+ IComponent container = null;
+
+ while (true)
+ {
+ container = component.getContainer();
+ if (container == null)
+ break;
+
+ if (result == null)
+ result = new ArrayList();
+
+ result.add(component);
+
+ component = container;
+ }
+
+ if (result == null)
+ return null;
+
+ // Reverse the list, such that the inspected component is last, and the
+ // top-most container is first.
+
+ Collections.reverse(result);
+
+ return result;
+ }
+
+ private String[] getPageNames()
+ {
+ Set names = new HashSet();
+
+ ISpecificationSource source = getPage().getEngine().getSpecificationSource();
+
+ addPageNames(names, source.getFrameworkNamespace());
+ addPageNames(names, source.getApplicationNamespace());
+
+ List l = new ArrayList(names);
+ Collections.sort(l);
+
+ return (String[]) l.toArray(new String[l.size()]);
+ }
+
+ private void addPageNames(Set names, INamespace namespace)
+ {
+ String idPrefix = namespace.getExtendedId();
+
+ List pageNames = namespace.getPageNames();
+ int count = pageNames.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) pageNames.get(i);
+
+ if (idPrefix == null)
+ names.add(name);
+ else
+ names.add(idPrefix + ":" + name);
+ }
+
+ List ids = namespace.getChildIds();
+ count = ids.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String id = (String) ids.get(i);
+
+ addPageNames(names, namespace.getChildNamespace(id));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.jwc
new file mode 100644
index 0000000..dee4b40
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Selector.jwc
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.Selector">
+
+ <component id="form" type="Form">
+ <binding name="listener" expression="listeners.formSubmit"/>
+ </component>
+
+ <component id="selectPage" type="PropertySelection">
+ <binding name="value" expression="page.inspectedPageName"/>
+ <binding name="model" expression="pageModel"/>
+ <binding name="submitOnChange" expression="true"/>
+ </component>
+
+ <component id="page" type="DirectLink">
+ <binding name="listener" expression="page.listeners.selectComponent"/>
+ </component>
+
+ <component id="e" type="Foreach">
+ <binding name="source" expression="crumbTrail"/>
+ </component>
+
+ <component id="component" type="DirectLink">
+ <binding name="parameters" expression="components.e.value.idPath"/>
+ <binding name="listener" expression="page.listeners.selectComponent"/>
+ </component>
+
+ <component id="insertId" type="Insert">
+ <binding name="value" expression="components.e.value.id"/>
+ </component>
+
+ <component id="renderBody" type="RenderBody"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.html
new file mode 100644
index 0000000..917e59c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.html
@@ -0,0 +1,5 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+<span jwcid="ifDescription"><img jwcid="descriptionImage"/></span>
+</span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.jwc
new file mode 100644
index 0000000..28466ca
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowDescription.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.BaseComponent"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <parameter name="description" required="yes"/>
+
+ <component id="ifDescription" type="Conditional">
+ <inherited-binding name="condition" parameter-name="description"/>
+ </component>
+
+ <component id="descriptionImage" type="Image">
+ <binding name="image" expression="assets.info"/>
+ <inherited-binding name="alt" parameter-name="description"/>
+ </component>
+
+ <private-asset name="info" resource-path="info.gif"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.html
new file mode 100644
index 0000000..797c8ff
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.html
@@ -0,0 +1,90 @@
+<!-- $Id$ -->
+
+<table class="inspector-data">
+
+ <tr class="heading">
+ <th colspan=2>Engine/Application Properties</th>
+ </tr>
+
+ <tr class="heading">
+ <th>Name</th> <th>Property</th>
+ </tr>
+
+ <tr class="even">
+ <th>Tapestry Framework Version</th>
+ <td><span jwcid="insertFrameworkVersion"/></td>
+ </tr>
+
+ <tr class="odd">
+ <th>Application Name</th>
+ <td><span jwcid="insertApplicationName"/></td>
+ </tr>
+
+ <tr class="even">
+ <th>Context Path</th>
+ <td><span jwcid="insertContextPath"/></td>
+ </tr>
+
+ <tr class="odd">
+ <th>Servlet Path</th>
+ <td><span jwcid="insertServletPath"/></td>
+ </tr>
+
+ <tr class="even">
+ <th>Engine Class</th>
+ <td><span jwcid="insertEngineClass"/></td>
+ </tr>
+
+ <tr class="odd">
+ <th>Locale</th>
+ <td><span jwcid="insertLocale"/></td>
+ </tr>
+
+ <tr class="even">
+ <th>Visit</th>
+ <td>
+<span jwcid="ifNoVisit">
+<em>none</em>
+</span>
+
+<span jwcid="ifVisit">
+<span jwcid="insertVisit"/>
+</span>
+ </td>
+ </tr>
+
+</table>
+
+<h1>Operations</h1>
+
+<table class="inspector-data">
+
+ <tr class="even">
+ <td><a jwcid="restart" target="_new"><img jwcid="restartButton"/></a>
+ </td>
+ <td>Restart the application (in a new window).
+ </td>
+ </tr>
+
+ <tr class="even">
+ <td><a jwcid="reset"><img jwcid="resetButton"/></a>
+ </td>
+ <td>
+ Reset the application, discarding all cached specifications, assets
+ and templates.
+ </td>
+ </tr>
+</table>
+
+<h1>Serialized Engine</h1>
+
+<p>The serialized state of the application engine (the size of this is relevant
+for application servers which support clustering).
+
+<p><span jwcid="insertByteCount"/> bytes:
+<pre><span jwcid="insertSerializedEngine"/></pre>
+
+<h1>Request Context</h1>
+
+<span jwcid="insertRequest"/>
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.java
new file mode 100644
index 0000000..0b9dc71
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.java
@@ -0,0 +1,186 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.util.io.BinaryDumpOutputStream;
+
+/**
+ * Component of the {@link Inspector} page used to display
+ * the properties of the {@link org.apache.tapestry.IEngine} as well as a serialized view of it.
+ * Also, the {@link org.apache.tapestry.request.RequestContext} is dumped out.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ShowEngine extends BaseComponent implements PageDetachListener
+{
+ private byte[] serializedEngine;
+
+ public void pageDetached(PageEvent event)
+ {
+ serializedEngine = null;
+ }
+
+ /**
+ * Workaround for OGNL limitation --- OGNL can't dereference
+ * past class instances.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public String getEngineClassName()
+ {
+ return getPage().getEngine().getClass().getName();
+ }
+
+ private byte[] getSerializedEngine()
+ {
+ if (serializedEngine == null)
+ buildSerializedEngine();
+
+ return serializedEngine;
+ }
+
+ private void buildSerializedEngine()
+ {
+ ByteArrayOutputStream bos = null;
+ ObjectOutputStream oos = null;
+
+ try
+ {
+ bos = new ByteArrayOutputStream();
+ oos = new ObjectOutputStream(bos);
+
+ // Write the application object to the stream.
+
+ oos.writeObject(getPage().getEngine());
+
+ // Extract the application as an array of bytes.
+
+ serializedEngine = bos.toByteArray();
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("ShowEngine.could-not-serialize"),
+ ex);
+ }
+ finally
+ {
+ close(oos);
+ close(bos);
+ }
+
+ // It would be nice to deserialize the application object now, but in
+ // practice, that fails due to class loader problems.
+ }
+
+ private void close(OutputStream stream)
+ {
+ if (stream == null)
+ return;
+
+ try
+ {
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ }
+
+ public int getEngineByteCount()
+ {
+ return getSerializedEngine().length;
+ }
+
+ public IRender getEngineDumpDelegate()
+ {
+ return new IRender()
+ {
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ dumpSerializedEngine(writer);
+ }
+ };
+ }
+
+ private void dumpSerializedEngine(IMarkupWriter responseWriter)
+ {
+ CharArrayWriter writer = null;
+ BinaryDumpOutputStream bos = null;
+
+ try
+ {
+ // Because IReponseWriter doesn't implement the
+ // java.io.Writer interface, we have to buffer this
+ // stuff then pack it in all at once. Kind of a waste!
+
+ writer = new CharArrayWriter();
+
+ bos = new BinaryDumpOutputStream(writer);
+ bos.setBytesPerLine(32);
+
+ bos.write(getSerializedEngine());
+ bos.close();
+
+ responseWriter.print(writer.toString());
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ finally
+ {
+ if (bos != null)
+ {
+ try
+ {
+ bos.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ }
+
+ if (writer != null)
+ {
+ writer.reset();
+ writer.close();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.jwc
new file mode 100644
index 0000000..b181816
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowEngine.jwc
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.ShowEngine" allow-body="no" allow-informal-parameters="no">
+
+ <component id="insertFrameworkVersion" type="Insert">
+ <binding name="value" expression="@org.apache.tapestry.Tapestry@VERSION"/>
+ </component>
+
+ <component id="insertApplicationName" type="Insert">
+ <binding name="value" expression="page.engine.specification.name"/>
+ </component>
+
+ <component id="insertContextPath" type="Insert">
+ <binding name="value" expression="page.engine.contextPath"/>
+ </component>
+
+ <component id="insertServletPath" type="Insert">
+ <binding name="value" expression="page.engine.servletPath"/>
+ </component>
+
+ <component id="insertEngineClass" type="Insert">
+ <binding name="value" expression="engineClassName"/>
+ </component>
+
+ <component id="insertLocale" type="Insert">
+ <binding name="value" expression="page.engine.locale.displayName"/>
+ </component>
+
+ <component id="ifNoVisit" type="Conditional">
+ <binding name="condition" expression="! page.engine.hasVisit"/>
+ </component>
+
+ <component id="insertVisit" type="Insert">
+ <binding name="value" expression="page.engine.visit"/>
+ </component>
+
+ <component id="ifVisit" type="Conditional">
+ <binding name="condition" expression="page.engine.hasVisit"/>
+ </component>
+
+ <component id="restart" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
+ </component>
+
+ <component id="restartButton" type="Rollover">
+ <binding name="image" expression="assets.restart"/>
+ <binding name="focus" expression="assets.restartFocus"/>
+ </component>
+
+ <component id="reset" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESET_SERVICE"/>
+ <binding name="disabled" expression="! page.engine.resetServiceEnabled"/>
+ </component>
+
+ <component id="resetButton" type="Rollover">
+ <binding name="image" expression="assets.reset"/>
+ <binding name="focus" expression="assets.resetFocus"/>
+ <binding name="disabled" expression="assets.resetDisabled"/>
+ </component>
+
+ <component id="insertByteCount" type="Insert">
+ <binding name="value" expression="engineByteCount"/>
+ </component>
+
+ <component id="insertSerializedEngine" type="Delegator">
+ <binding name="delegate" expression="engineDumpDelegate"/>
+ </component>
+
+ <component id="insertRequest" type="Delegator">
+ <binding name="delegate" expression="page.requestCycle.requestContext"/>
+ </component>
+
+ <private-asset name="reset" resource-path="Reset_Np1.gif"/>
+ <private-asset name="resetFocus" resource-path="Reset_NRp2.gif"/>
+ <private-asset name="resetDisabled" resource-path="Reset_Np1_disabled.gif"/>
+ <private-asset name="restart" resource-path="Restart_Np1.gif"/>
+ <private-asset name="restartFocus" resource-path="Restart_NRp2.gif"/>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.html
new file mode 100644
index 0000000..1aa67d8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.html
@@ -0,0 +1,35 @@
+<!-- $Id$ -->
+
+<span jwcid="ifNoProperties">
+<span class="message">Page contains no persistent properties.</span>
+</span>
+
+<span jwcid="ifHasProperties">
+<table class="inspector-data">
+ <tr class="heading">
+ <th>Component</th> <th>Property Name</th> <th>Value Class</th> <th>Value</th>
+ </tr>
+
+ <tr jwcid="e">
+ <td>
+ <a jwcid="selectComponent"><span jwcid="insertPath"/></a>
+ </td>
+ <td>
+ <span jwcid="insertPersistPropertyName"/>
+ </td>
+ <td>
+ <span jwcid="insertPersistValueClass"/>
+ </td>
+ <td>
+ <span jwcid="insertPersistValue"/>
+ </td>
+ </tr>
+
+</table>
+
+
+</span>
+
+
+
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.java
new file mode 100644
index 0000000..6b96c2c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.java
@@ -0,0 +1,142 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.engine.IPageRecorder;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.record.IPageChange;
+
+/**
+ * Component of the {@link Inspector} page used to display
+ * the persisent properties of the page.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ShowProperties extends BaseComponent implements PageRenderListener
+{
+ private List _properties;
+ private IPageChange _change;
+ private IPage _inspectedPage;
+
+ /**
+ * Does nothing.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public void pageBeginRender(PageEvent event)
+ {
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ public void pageEndRender(PageEvent event)
+ {
+ _properties = null;
+ _change = null;
+ _inspectedPage = null;
+ }
+
+ private void buildProperties()
+ {
+ Inspector inspector = (Inspector) getPage();
+
+ _inspectedPage = inspector.getInspectedPage();
+
+ IEngine engine = getPage().getEngine();
+ IPageRecorder recorder =
+ engine.getPageRecorder(_inspectedPage.getPageName(), inspector.getRequestCycle());
+
+ // No page recorder? No properties.
+
+ if (recorder == null)
+ {
+ _properties = Collections.EMPTY_LIST;
+ return;
+ }
+
+ if (recorder.getHasChanges())
+ _properties = new ArrayList(recorder.getChanges());
+ }
+
+ /**
+ * Returns a {@link List} of {@link IPageChange} objects.
+ *
+ * <p>Sort order is not defined.
+ *
+ **/
+
+ public List getProperties()
+ {
+ if (_properties == null)
+ buildProperties();
+
+ return _properties;
+ }
+
+ public void setChange(IPageChange value)
+ {
+ _change = value;
+ }
+
+ public IPageChange getChange()
+ {
+ return _change;
+ }
+
+ /**
+ * Returns the name of the value's class, if the value is non-null.
+ *
+ **/
+
+ public String getValueClassName()
+ {
+ Object value;
+
+ value = _change.getNewValue();
+
+ if (value == null)
+ return "<null>";
+
+ return convertClassToName(value.getClass());
+ }
+
+ private String convertClassToName(Class cl)
+ {
+ // TODO: This only handles one-dimensional arrays
+ // property.
+
+ if (cl.isArray())
+ return "array of " + cl.getComponentType().getName();
+
+ return cl.getName();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.jwc
new file mode 100644
index 0000000..4bde6af
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowProperties.jwc
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.ShowProperties">
+
+ <bean name="persistPropertyClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="propertyClass" class="org.apache.tapestry.bean.EvenOdd"/>
+
+ <component id="ifNoProperties" type="Conditional">
+ <binding name="condition" expression="!properties"/>
+ </component>
+
+ <component id="ifHasProperties" type="Conditional">
+ <binding name="condition" expression="properties"/>
+ </component>
+
+ <component id="e" type="Foreach">
+ <binding name="source" expression="properties"/>
+ <binding name="value" expression="change"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.persistPropertyClass.next"/>
+ </component>
+
+ <component id="selectComponent" type="DirectLink">
+ <binding name="listener" expression="page.listeners.selectComponent"/>
+ <binding name="parameters" expression="change.componentPath"/>
+ <binding name="disabled" expression="change.componentPath == null"/>
+ </component>
+
+ <component id="insertPath" type="Insert">
+ <binding name="value" expression="change.componentPath"/>
+ </component>
+
+ <component id="insertPersistPropertyName" type="Insert">
+ <binding name="value" expression="change.propertyName"/>
+ </component>
+
+ <component id="insertPersistValueClass" type="Insert">
+ <binding name="value" expression="valueClassName"/>
+ </component>
+
+ <component id="insertPersistValue" type="Insert">
+ <binding name="value" expression="change.newValue"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.html
new file mode 100644
index 0000000..74bdccf
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.html
@@ -0,0 +1,161 @@
+<!-- $Id$ -->
+
+<table>
+<tr valign=top>
+<td>
+
+<table class="inspector-data" width="100%">
+ <tr class="even">
+ <th>Specification Resource Location</th>
+ <td><span jwcid="@Insert" value="ognl:inspectedSpecification.specificationLocation"/>
+ <span jwcid="@ShowDescription" description="ognl:inspectedSpecification.description"/>
+ </td>
+ </tr>
+
+ <tr class="odd">
+ <th>Java class</th>
+ <td><span jwcid="@Insert" value="ognl:inspectedComponent.getClass().getName()"/></td>
+ </tr>
+
+<span jwcid="@Conditional" condition="ognl:! inspectedSpecification.pageSpecification">
+
+ <tr class="even">
+ <th>Allow informal parameters</th>
+ <td><span jwcid="@Insert" value="ognl:inspectedSpecification.allowInformalParameters"/></td>
+ </tr>
+
+ <tr class="odd">
+ <th>Allow body</th>
+ <td><span jwcid="@Insert" value="ognl:inspectedSpecification.allowBody"/></td>
+ </tr>
+
+</span>
+
+</table>
+
+<span jwcid="@Conditional" condition="ognl:formalParameterNames">
+
+<table class="inspector-data" width="100%">
+ <tr class="heading">
+ <th colspan=4>Formal Parameters</th>
+ </tr>
+ <tr class="heading">
+ <th>Name</th> <th>Required</th> <th>Java type</th> <th>Binding</th>
+ </tr>
+
+ <tr jwcid="e_formal">
+ <td><span jwcid="@Insert" value="ognl:parameterName"/>
+ <span jwcid="@ShowDescription" description="ognl:parameterSpecification.description"/>
+ </td>
+ <td><span jwcid="@Insert" value="ognl:parameterSpecification.required"/></td>
+ <td><span jwcid="@Insert" value="ognl:parameterSpecification.type"/></td>
+ <td><span jwcid="@Insert" value="ognl:binding"/></td>
+ </tr>
+
+</table>
+</span>
+
+<span jwcid="@Conditional" condition="ognl:informalParameterNames">
+
+<table class="inspector-data" width="100%">
+ <tr class="heading">
+ <th colspan=2>Informal Parameters</th>
+ </tr>
+ <tr class="heading">
+ <th>Name</th> <th>Binding</th>
+ </tr>
+
+ <tr jwcid="e_informal">
+ <td><span jwcid="@Insert" value="ognl:parameterName"/></td>
+ <td><span jwcid="@Insert" value="ognl:binding"/></td>
+ </tr>
+
+</table>
+</span>
+
+<span jwcid="@Conditional" condition="ognl:assetNames">
+
+<table class="inspector-data" width="100%">
+ <tr class="heading">
+ <th colspan=2>Assets</th>
+ </tr>
+ <tr class="heading">
+ <th>Name</th> <th>Asset</th>
+ </tr>
+
+ <tr jwcid="e_asset">
+ <td><span jwcid="@Insert" value="ognl:assetName"/></td>
+ <td><span jwcid="@Insert" value="ognl:asset"/></td>
+ </tr>
+
+
+</table>
+</span>
+
+<span jwcid="@Conditional" condition="ognl:sortedPropertyNames">
+
+<table class="inspector-data" width="100%">
+ <tr class="heading">
+ <th colspan=2>Properties</th>
+ </tr>
+ <tr class="heading">
+ <th>Name</th> <th>Property</th>
+ </tr>
+
+ <tr jwcid="e_property">
+ <th><span jwcid="@Insert" value="ognl:propertyName"/></th>
+ <td><span jwcid="@Insert" value="ognl:propertyValue"/></td>
+ </tr>
+
+</table>
+</span>
+
+<span jwcid="@Conditional" condition="ognl:beanNames">
+
+<table class="inspector-data" width="100%">
+ <tr class="heading">
+ <th colspan=3>Helper Beans</th>
+ </tr>
+
+ <tr class="heading">
+ <th>Name</th> <th>Class</th> <th>Lifecycle</th>
+ </tr>
+
+ <tr jwcid="e_bean">
+ <td><span jwcid="@Insert" value="ognl:beanName"/></td>
+ <td><span jwcid="@Insert" value="ognl:beanSpecification.className"/></td>
+ <td><span jwcid="@Insert" value="ognl:beanSpecification.lifecycle.name"/></td>
+ </tr>
+
+</table>
+</span>
+
+</td>
+
+<td>
+<span jwcid="@Conditional" condition="ognl:sortedComponents">
+
+<table border="0" class="inspector-data">
+
+ <tr class="heading">
+ <th colspan=2>Embedded Components</th>
+ </tr>
+ <tr class="heading">
+ <th>Id</th> <th>Type</th>
+ </tr>
+
+ <tr jwcid="e_components">
+ <td>
+ <a jwcid="selectComponent"><span jwcid="@Insert" value="ognl:component.id"/></a>
+ </td>
+ <td>
+ <span jwcid="@Insert" value="ognl:componentType"/>
+ </td>
+ </tr>
+
+</table>
+</span>
+
+</td>
+</tr>
+</table>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.java
new file mode 100644
index 0000000..098a6fa
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.java
@@ -0,0 +1,366 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.spec.IBeanSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IContainedComponent;
+import org.apache.tapestry.spec.IParameterSpecification;
+
+/**
+ * Component of the {@link Inspector} page used to display
+ * the specification, parameters and bindings and assets of the inspected component.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ShowSpecification extends BaseComponent implements PageRenderListener
+{
+ private IComponent _inspectedComponent;
+ private IComponentSpecification _inspectedSpecification;
+ private String _parameterName;
+ private String _assetName;
+ private List _sortedComponents;
+ private IComponent _component;
+ private List _assetNames;
+ private List _formalParameterNames;
+ private List _informalParameterNames;
+ private List _sortedPropertyNames;
+ private String _propertyName;
+ private List _beanNames;
+ private String _beanName;
+ private IBeanSpecification _beanSpecification;
+
+ private static class ComponentComparitor implements Comparator
+ {
+ public int compare(Object left, Object right)
+ {
+ IComponent leftComponent;
+ String leftId;
+ IComponent rightComponent;
+ String rightId;
+
+ if (left == right)
+ return 0;
+
+ leftComponent = (IComponent) left;
+ rightComponent = (IComponent) right;
+
+ leftId = leftComponent.getId();
+ rightId = rightComponent.getId();
+
+ return leftId.compareTo(rightId);
+ }
+ }
+
+ /**
+ * Clears all cached information about the component and such after
+ * each render (including the rewind phase render used to process
+ * the tab view).
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public void pageEndRender(PageEvent event)
+ {
+ _inspectedComponent = null;
+ _inspectedSpecification = null;
+ _parameterName = null;
+ _assetName = null;
+ _sortedComponents = null;
+ _component = null;
+ _assetNames = null;
+ _formalParameterNames = null;
+ _informalParameterNames = null;
+ _sortedPropertyNames = null;
+ _propertyName = null;
+ _beanNames = null;
+ _beanName = null;
+ _beanSpecification = null;
+ }
+
+ /**
+ * Gets the inspected component and specification from the {@link Inspector} page.
+ *
+ * @since 1.0.5
+ **/
+
+ public void pageBeginRender(PageEvent event)
+ {
+ Inspector inspector = (Inspector) getPage();
+
+ _inspectedComponent = inspector.getInspectedComponent();
+ _inspectedSpecification = _inspectedComponent.getSpecification();
+ }
+
+ public IComponent getInspectedComponent()
+ {
+ return _inspectedComponent;
+ }
+
+ public IComponentSpecification getInspectedSpecification()
+ {
+ return _inspectedSpecification;
+ }
+
+ /**
+ * Returns a sorted list of formal parameter names.
+ *
+ **/
+
+ public List getFormalParameterNames()
+ {
+ if (_formalParameterNames == null)
+ _formalParameterNames = sort(_inspectedSpecification.getParameterNames());
+
+ return _formalParameterNames;
+ }
+
+ /**
+ * Returns a sorted list of informal parameter names. This is
+ * the list of all bindings, with the list of parameter names removed,
+ * sorted.
+ *
+ **/
+
+ public List getInformalParameterNames()
+ {
+ if (_informalParameterNames != null)
+ return _informalParameterNames;
+
+ Collection names = _inspectedComponent.getBindingNames();
+ if (names != null && names.size() > 0)
+ {
+ _informalParameterNames = new ArrayList(names);
+
+ // Remove the names of any formal parameters. This leaves
+ // just the names of informal parameters (informal parameters
+ // are any parameters/bindings that don't match a formal parameter
+ // name).
+
+ names = _inspectedSpecification.getParameterNames();
+ if (names != null)
+ _informalParameterNames.removeAll(names);
+
+ Collections.sort(_informalParameterNames);
+ }
+
+ return _informalParameterNames;
+ }
+
+ public String getParameterName()
+ {
+ return _parameterName;
+ }
+
+ public void setParameterName(String value)
+ {
+ _parameterName = value;
+ }
+
+ /**
+ * Returns the {@link org.apache.tapestry.spec.ParameterSpecification} corresponding to
+ * the value of the parameterName property.
+ *
+ **/
+
+ public IParameterSpecification getParameterSpecification()
+ {
+ return _inspectedSpecification.getParameter(_parameterName);
+ }
+
+ /**
+ * Returns the {@link IBinding} corresponding to the value of
+ * the parameterName property.
+ *
+ **/
+
+ public IBinding getBinding()
+ {
+ return _inspectedComponent.getBinding(_parameterName);
+ }
+
+ public void setAssetName(String value)
+ {
+ _assetName = value;
+ }
+
+ public String getAssetName()
+ {
+ return _assetName;
+ }
+
+ /**
+ * Returns the {@link IAsset} corresponding to the value
+ * of the assetName property.
+ *
+ **/
+
+ public IAsset getAsset()
+ {
+ return (IAsset) _inspectedComponent.getAssets().get(_assetName);
+ }
+
+ /**
+ * Returns a sorted list of asset names, or null if the
+ * component contains no assets.
+ *
+ **/
+
+ public List getAssetNames()
+ {
+ if (_assetNames == null)
+ _assetNames = sort(_inspectedComponent.getAssets().keySet());
+
+ return _assetNames;
+ }
+
+ public List getSortedComponents()
+ {
+ if (_sortedComponents != null)
+ return _sortedComponents;
+
+ Inspector inspector = (Inspector) getPage();
+ IComponent inspectedComponent = inspector.getInspectedComponent();
+
+ // Get a Map of the components and simply return null if there
+ // are none.
+
+ Map components = inspectedComponent.getComponents();
+
+ _sortedComponents = new ArrayList(components.values());
+
+ Collections.sort(_sortedComponents, new ComponentComparitor());
+
+ return _sortedComponents;
+ }
+
+ public void setComponent(IComponent value)
+ {
+ _component = value;
+ }
+
+ public IComponent getComponent()
+ {
+ return _component;
+ }
+
+ /**
+ * Returns the type of the component, as specified in the container's
+ * specification (i.e., the component alias if known).
+ *
+ **/
+
+ public String getComponentType()
+ {
+ IComponent container = _component.getContainer();
+
+ IComponentSpecification containerSpecification = container.getSpecification();
+
+ String id = _component.getId();
+ IContainedComponent contained = containerSpecification.getComponent(id);
+
+ // Temporary: An implicit component will not be in the containing
+ // component's specification as a ContainedComponent.
+
+ if (contained == null)
+ return null;
+
+ return contained.getType();
+ }
+
+ /**
+ * Returns a list of the properties for the component
+ * (from its specification), or null if the component
+ * has no properties.
+ *
+ **/
+
+ public List getSortedPropertyNames()
+ {
+ if (_sortedPropertyNames == null)
+ _sortedPropertyNames = sort(_inspectedSpecification.getPropertyNames());
+
+ return _sortedPropertyNames;
+ }
+
+ public void setPropertyName(String value)
+ {
+ _propertyName = value;
+ }
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+ public String getPropertyValue()
+ {
+ return _inspectedSpecification.getProperty(_propertyName);
+ }
+
+ public List getBeanNames()
+ {
+ if (_beanNames == null)
+ _beanNames = sort(_inspectedSpecification.getBeanNames());
+
+ return _beanNames;
+ }
+
+ public void setBeanName(String value)
+ {
+ _beanName = value;
+ _beanSpecification = _inspectedSpecification.getBeanSpecification(_beanName);
+ }
+
+ public String getBeanName()
+ {
+ return _beanName;
+ }
+
+ public IBeanSpecification getBeanSpecification()
+ {
+ return _beanSpecification;
+ }
+
+ private List sort(Collection c)
+ {
+ if (c == null || c.size() == 0)
+ return null;
+
+ List result = new ArrayList(c);
+
+ Collections.sort(result);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.jwc
new file mode 100644
index 0000000..fa9c420
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowSpecification.jwc
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd" >
+
+<component-specification class="org.apache.tapestry.contrib.inspector.ShowSpecification">
+
+ <bean name="formalClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="informalClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="assetClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="propertyClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="componentClass" class="org.apache.tapestry.bean.EvenOdd"/>
+ <bean name="beanClass" class="org.apache.tapestry.bean.EvenOdd"/>
+
+ <component id="e_formal" type="Foreach">
+ <binding name="source" expression="formalParameterNames"/>
+ <binding name="value" expression="parameterName"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.formalClass.next"/>
+ </component>
+
+
+ <component id="e_informal" type="Foreach">
+ <binding name="source" expression="informalParameterNames"/>
+ <binding name="value" expression="parameterName"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.informalClass.next"/>
+ </component>
+
+ <component id="e_asset" type="Foreach">
+ <binding name="source" expression="assetNames"/>
+ <binding name="value" expression="assetName"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.assetClass.next"/>
+ </component>
+
+ <component id="e_components" type="Foreach">
+ <binding name="source" expression="sortedComponents"/>
+ <binding name="value" expression="component"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.componentClass.next"/>
+ </component>
+
+ <component id="selectComponent" type="DirectLink">
+ <binding name="listener" expression="page.listeners.selectComponent"/>
+ <binding name="parameters" expression="component.idPath"/>
+ </component>
+
+ <component id="e_property" type="Foreach">
+ <binding name="source" expression="sortedPropertyNames"/>
+ <binding name="value" expression="propertyName"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.propertyClass.next"/>
+ </component>
+
+ <component id="e_bean" type="Foreach">
+ <binding name="source" expression="beanNames"/>
+ <binding name="value" expression="beanName"/>
+ <static-binding name="element">tr</static-binding>
+ <binding name="class" expression="beans.beanClass.next"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.html
new file mode 100644
index 0000000..2673368
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.html
@@ -0,0 +1,15 @@
+<!-- $Id$ -->
+
+<span jwcid="ifNoTemplate">
+<span class="message">Component does not have a template.
+</span>
+</span>
+
+<span jwcid="ifTemplate">
+<table class="template">
+ <tr>
+ <td><span jwcid="insertTemplate"/></td>
+ </tr>
+</table>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.java
new file mode 100644
index 0000000..65a8c13
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.java
@@ -0,0 +1,333 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.engine.ITemplateSource;
+import org.apache.tapestry.parse.CloseToken;
+import org.apache.tapestry.parse.ComponentTemplate;
+import org.apache.tapestry.parse.LocalizationToken;
+import org.apache.tapestry.parse.OpenToken;
+import org.apache.tapestry.parse.TemplateAttribute;
+import org.apache.tapestry.parse.TemplateToken;
+import org.apache.tapestry.parse.TextToken;
+import org.apache.tapestry.parse.TokenType;
+
+/**
+ * Component of the {@link Inspector} page used to display
+ * the ids and types of all embedded components.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ShowTemplate extends BaseComponent implements IDirect
+{
+
+ public boolean getHasTemplate()
+ {
+ Inspector inspector;
+
+ inspector = (Inspector) getPage();
+
+ // Components that inherit from BaseComponent have templates,
+ // others do not.
+
+ return inspector.getInspectedComponent() instanceof BaseComponent;
+ }
+
+ public IRender getTemplateDelegate()
+ {
+ return new IRender()
+ {
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writeTemplate(writer, cycle);
+ }
+ };
+ }
+
+ /**
+ * Writes the HTML template for the component. When <jwc> tags are
+ * written, the id is made a link (that selects the named component). We
+ * use some magic to accomplish this, creating links as if we were a
+ * {@link DirectLink} component, and attributing those links
+ * to the captive {@link DirectLink} component embedded here.
+ *
+ **/
+
+ private void writeTemplate(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IComponent inspectedComponent = getInspectedComponent();
+ ComponentTemplate template = null;
+ ITemplateSource source = getPage().getEngine().getTemplateSource();
+
+ try
+ {
+ template = source.getTemplate(cycle, inspectedComponent);
+ }
+ catch (Exception ex)
+ {
+ return;
+ }
+
+ writer.begin("pre");
+
+ int count = template.getTokenCount();
+
+ for (int i = 0; i < count; i++)
+ {
+ TemplateToken token = template.getToken(i);
+ TokenType type = token.getType();
+
+ if (type == TokenType.TEXT)
+ {
+ write(writer, (TextToken) token);
+ continue;
+ }
+
+ if (type == TokenType.CLOSE)
+ {
+ write(writer, (CloseToken) token);
+
+ continue;
+ }
+
+ if (token.getType() == TokenType.LOCALIZATION)
+ {
+
+ write(writer, (LocalizationToken) token);
+ continue;
+ }
+
+ if (token.getType() == TokenType.OPEN)
+ {
+ boolean nextIsClose =
+ (i + 1 < count) && (template.getToken(i + 1).getType() == TokenType.CLOSE);
+
+ write(writer, nextIsClose, (OpenToken) token);
+
+ if (nextIsClose)
+ i++;
+
+ continue;
+ }
+
+ // That's all the types known at this time.
+ }
+
+ writer.end(); // <pre>
+ }
+
+ /** @since 3.0 **/
+
+ private IComponent getInspectedComponent()
+ {
+ Inspector page = (Inspector) getPage();
+
+ return page.getInspectedComponent();
+ }
+
+ /** @since 3.0 **/
+
+ private void write(IMarkupWriter writer, TextToken token)
+ {
+ int start = token.getStartIndex();
+ int end = token.getEndIndex();
+
+ // Print the section of the template ... print() will
+ // escape and invalid characters as HTML entities. Also,
+ // we show the full stretch of text, not the trimmed version.
+
+ writer.print(token.getTemplateData(), start, end - start + 1);
+ }
+
+ /** @since 3.0 **/
+
+ private void write(IMarkupWriter writer, CloseToken token)
+ {
+ writer.begin("span");
+ writer.attribute("class", "jwc-tag");
+
+ writer.print("</");
+ writer.print(token.getTag());
+ writer.print(">");
+
+ writer.end(); // <span>
+ }
+
+ /** @since 3.0 **/
+
+ private void write(IMarkupWriter writer, LocalizationToken token)
+ {
+ IComponent component = getInspectedComponent();
+
+ writer.begin("span");
+ writer.attribute("class", "jwc-tag");
+
+ writer.print("<span key=\"");
+ writer.print(token.getKey());
+ writer.print('"');
+
+ Map attributes = token.getAttributes();
+ if (attributes != null && !attributes.isEmpty())
+ {
+ Iterator it = attributes.entrySet().iterator();
+ while (it.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) it.next();
+ String attributeName = (String) entry.getKey();
+ String attributeValue = (String) entry.getValue();
+
+ writer.print(' ');
+ writer.print(attributeName);
+ writer.print("=\"");
+ writer.print(attributeValue);
+ writer.print('"');
+
+ }
+ }
+
+ writer.print('>');
+ writer.begin("span");
+ writer.attribute("class", "localized-string");
+
+ writer.print(component.getMessages().getMessage(token.getKey()));
+ writer.end(); // <span>
+
+ writer.print("</span>");
+
+ writer.end(); // <span>
+ }
+
+ /** @since 3.0 **/
+
+ private void write(IMarkupWriter writer, boolean nextIsClose, OpenToken token)
+ {
+ IComponent component = getInspectedComponent();
+ IEngineService service = getPage().getEngine().getService(Tapestry.DIRECT_SERVICE);
+ String[] context = new String[1];
+
+ // Each id references a component embedded in the inspected component.
+ // Get that component.
+
+ String id = token.getId();
+ IComponent embedded = component.getComponent(id);
+ context[0] = embedded.getIdPath();
+
+ // Build a URL to select that component, as if by the captive
+ // component itself (it's a Direct).
+
+ ILink link = service.getLink(getPage().getRequestCycle(), this, context);
+
+ writer.begin("span");
+ writer.attribute("class", "jwc-tag");
+
+ writer.print("<");
+ writer.print(token.getTag());
+
+ writer.print(" jwcid=\"");
+
+ writer.begin("span");
+ writer.attribute("class", "jwc-id");
+
+ writer.begin("a");
+ writer.attribute("href", link.getURL());
+ writer.print(id);
+
+ writer.end(); // <a>
+ writer.end(); // <span>
+ writer.print('"');
+
+ Map attributes = token.getAttributesMap();
+
+ if (attributes != null)
+ {
+ Iterator ii = attributes.entrySet().iterator();
+
+ while (ii.hasNext())
+ {
+ Map.Entry e = (Map.Entry) ii.next();
+
+ TemplateAttribute attribute = (TemplateAttribute)e.getValue();
+
+ writer.print(' ');
+ writer.print(e.getKey().toString());
+ writer.print("=\"");
+
+ // TODO: Fix this to output something appropriate for each type
+ // of attribute (literal, expression, string).
+
+ writer.print(attribute.getValue());
+ writer.print('"');
+ }
+ }
+
+ // Collapse an open & close down to a single tag.
+
+ if (nextIsClose)
+ writer.print('/');
+
+ writer.print('>');
+ writer.end(); // <span>
+ }
+
+ /**
+ * Invoked when a component id is clicked.
+ *
+ **/
+
+ public void trigger(IRequestCycle cycle)
+ {
+ Inspector inspector = (Inspector) getPage();
+
+ Object[] parameters = cycle.getServiceParameters();
+
+ inspector.selectComponent((String) parameters[0]);
+
+ IComponent newComponent = inspector.getInspectedComponent();
+
+ // If the component is not a BaseComponent then it won't have
+ // a template, so switch to the specification view.
+
+ if (!(newComponent instanceof BaseComponent))
+ inspector.setView(View.SPECIFICATION);
+ }
+
+ /**
+ * Always returns true.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public boolean isStateful()
+ {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.jwc
new file mode 100644
index 0000000..266a467
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ShowTemplate.jwc
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.ShowTemplate">
+ <component id="ifNoTemplate" type="Conditional">
+ <binding name="condition" expression="! hasTemplate"/>
+ </component>
+
+ <component id="ifTemplate" type="Conditional">
+ <binding name="condition" expression="hasTemplate"/>
+ </component>
+
+ <component id="insertTemplate" type="Delegator">
+ <binding name="delegate" expression="templateDelegate"/>
+ </component>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_HRp4.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_HRp4.gif
new file mode 100644
index 0000000..7b695d0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_HRp4.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Hp3.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Hp3.gif
new file mode 100644
index 0000000..33062cb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Hp3.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NBanner.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NBanner.gif
new file mode 100644
index 0000000..896f81b
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NBanner.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NRp2.gif
new file mode 100644
index 0000000..6700d10
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Np1.gif
new file mode 100644
index 0000000..5f8b7db
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Specification_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_HRp4.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_HRp4.gif
new file mode 100644
index 0000000..16e1792
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_HRp4.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Hp3.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Hp3.gif
new file mode 100644
index 0000000..2bc30ee
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Hp3.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NBanner.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NBanner.gif
new file mode 100644
index 0000000..e250138
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NBanner.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NRp2.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NRp2.gif
new file mode 100644
index 0000000..b7e2175
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_NRp2.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Np1.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Np1.gif
new file mode 100644
index 0000000..a83fb37
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/Template_Np1.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/View.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/View.java
new file mode 100644
index 0000000..6aac890
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/View.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Identifies different views for the inspector.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class View extends Enum
+{
+ /**
+ * View that displays the basic specification information, plus
+ * formal and informal parameters (and related bindings), and
+ * assets.
+ *
+ **/
+
+ public static final View SPECIFICATION = new View("SPECIFICATION");
+
+ /**
+ * View that displays the HTML template for the component, if one
+ * exists.
+ *
+ **/
+
+ public static final View TEMPLATE = new View("TEMPLATE");
+
+ /**
+ * View that shows the persistent properties of the page containing
+ * the inspected component.
+ *
+ **/
+
+ public static final View PROPERTIES = new View("PROPERTIES");
+
+ /**
+ * View that shows information about the
+ * {@link org.apache.tapestry.IEngine}.
+ *
+ **/
+
+ public static final View ENGINE = new View("ENGINE");
+
+
+ private View(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.html
new file mode 100644
index 0000000..6d3a970
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.html
@@ -0,0 +1,20 @@
+<!-- $Id$ -->
+
+<table border=0 cellpadding=0 cellspacing=2>
+ <tr>
+ <td>
+<span jwcid="@Foreach" source="ognl:views" value="ognl:view">
+ <a jwcid="select@ActionLink" listener="ognl:listeners.selectTab"><img
+ jwcid="@Rollover" image="ognl:viewImage" focus="ognl:focusImage"
+ width="120" height="19"/></a>
+</span>
+ </td>
+ </tr>
+ <tr>
+ <td><img jwcid="@Image" image="ognl:bannerImage"/></td>
+ </tr>
+ <tr>
+ <td><span jwcid="@RenderBody"/></td>
+ </tr>
+</table>
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.java b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.java
new file mode 100644
index 0000000..3251d35
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.java
@@ -0,0 +1,92 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.inspector;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Component of the {@link Inspector} page used to select the view.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public abstract class ViewTabs extends BaseComponent
+{
+ private static View[] _views =
+ {
+ View.SPECIFICATION,
+ View.TEMPLATE,
+ View.PROPERTIES,
+ View.ENGINE };
+
+ public View[] getViews()
+ {
+ return _views;
+ }
+
+ public abstract void setView(View value);
+
+ public abstract View getView();
+
+ private IAsset getImageForView(boolean focus)
+ {
+ Inspector inspector = (Inspector) getPage();
+ View view = getView();
+
+ boolean selected = (view == inspector.getView());
+
+ StringBuffer buffer = new StringBuffer(view.getName());
+
+ if (selected)
+ buffer.append("_selected");
+
+ if (focus)
+ buffer.append("_focus");
+
+ String key = buffer.toString();
+
+ return (IAsset) getAssets().get(key);
+ }
+
+ public IAsset getViewImage()
+ {
+ return getImageForView(false);
+ }
+
+ public IAsset getFocusImage()
+ {
+ return getImageForView(true);
+ }
+
+ public IAsset getBannerImage()
+ {
+ Inspector inspector = (Inspector) getPage();
+ View selectedView = inspector.getView();
+ String key = selectedView.getName() + "_banner";
+
+ return (IAsset) getAssets().get(key);
+ }
+
+ public void selectTab(IRequestCycle cycle)
+ {
+ Inspector inspector = (Inspector) getPage();
+ inspector.setView(getView());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.jwc
new file mode 100644
index 0000000..1e03bad
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/ViewTabs.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.inspector.ViewTabs">
+
+ <property-specification name="view" type="org.apache.tapestry.contrib.inspector.View"/>
+
+ <private-asset name="SPECIFICATION" resource-path="Specification_Np1.gif"/>
+ <private-asset name="SPECIFICATION_selected" resource-path="Specification_Hp3.gif"/>
+ <private-asset name="SPECIFICATION_focus" resource-path="Specification_NRp2.gif"/>
+ <private-asset name="SPECIFICATION_selected_focus" resource-path="Specification_HRp4.gif"/>
+ <private-asset name="SPECIFICATION_banner" resource-path="Specification_NBanner.gif"/>
+ <private-asset name="TEMPLATE" resource-path="Template_Np1.gif"/>
+ <private-asset name="TEMPLATE_selected" resource-path="Template_Hp3.gif"/>
+ <private-asset name="TEMPLATE_focus" resource-path="Template_NRp2.gif"/>
+ <private-asset name="TEMPLATE_selected_focus" resource-path="Template_HRp4.gif"/>
+ <private-asset name="TEMPLATE_banner" resource-path="Template_NBanner.gif"/>
+ <private-asset name="PROPERTIES" resource-path="Properties_Np1.gif"/>
+ <private-asset name="PROPERTIES_selected" resource-path="Properties_Hp3.gif"/>
+ <private-asset name="PROPERTIES_focus" resource-path="Properties_NRp2.gif"/>
+ <private-asset name="PROPERTIES_selected_focus" resource-path="Properties_HRp4.gif"/>
+ <private-asset name="PROPERTIES_banner" resource-path="Properties_NBanner.gif"/>
+ <private-asset name="ENGINE" resource-path="Engine_Np1.gif"/>
+ <private-asset name="ENGINE_selected" resource-path="Engine_Hp3.gif"/>
+ <private-asset name="ENGINE_focus" resource-path="Engine_NRp2.gif"/>
+ <private-asset name="ENGINE_selected_focus" resource-path="Engine_HRp4.gif"/>
+ <private-asset name="ENGINE_banner" resource-path="Engine_NBanner.gif"/>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/info.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/info.gif
new file mode 100644
index 0000000..be3d1d1
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/info.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/inspector-rollover.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/inspector-rollover.gif
new file mode 100644
index 0000000..df0d3ef
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/inspector-rollover.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/package.html b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/package.html
new file mode 100644
index 0000000..7610fdb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/package.html
@@ -0,0 +1,17 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Implementation of the Tapestry <em>Inspector</em>, a specialized page
+used to dynamically introspect the construction of an application while
+it runs. The {@link org.apache.tapestry.contrib.inspector.InspectorButton} component
+creates an icon on the page that raises the Inspector in a seperate window.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/tapestry-logo.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/tapestry-logo.gif
new file mode 100644
index 0000000..82ad636
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/inspector/tapestry-logo.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/BooleanParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/BooleanParameter.java
new file mode 100644
index 0000000..80fc31d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/BooleanParameter.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Wrapper around a boolean parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class BooleanParameter implements IParameter
+{
+ private boolean _value;
+
+ public static final BooleanParameter TRUE = new BooleanParameter(true);
+
+ public static final BooleanParameter FALSE = new BooleanParameter(false);
+
+ private BooleanParameter(boolean value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setBoolean(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Boolean<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/DoubleParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/DoubleParameter.java
new file mode 100644
index 0000000..e9f6d65
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/DoubleParameter.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * A wrapper around a double parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class DoubleParameter implements IParameter
+{
+ private double _value;
+
+ public DoubleParameter(double value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setDouble(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Double<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/FloatParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/FloatParameter.java
new file mode 100644
index 0000000..b2eb348
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/FloatParameter.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * A wrapper around a float parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class FloatParameter implements IParameter
+{
+ private float _value;
+
+ public FloatParameter(float value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setFloat(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Float<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IParameter.java
new file mode 100644
index 0000000..2032a00
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IParameter.java
@@ -0,0 +1,41 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Represents a parameter within a dynamically generated SQL statement.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ * @see org.apache.tapestry.contrib.jdbc.ParameterizedStatement
+ *
+ **/
+
+public interface IParameter
+{
+ /**
+ * Invokes the appropriate setXXX() method on the
+ * {@link java.sql.PreparedStatement}.
+ *
+ **/
+
+ public void set(PreparedStatement statement, int index)
+ throws SQLException;
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IStatement.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IStatement.java
new file mode 100644
index 0000000..c9f2544
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IStatement.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * A wrapper around {@link java.sql.Statement} or
+ * {@link java.sql.PreparedStatement} which hides the differences
+ * between the two.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @see org.apache.tapestry.contrib.jdbc.StatementAssembly#createStatement(Connection)
+ *
+ **/
+
+public interface IStatement
+{
+ /**
+ * Returns the SQL associated with this statement.
+ *
+ **/
+
+ public String getSQL();
+
+ /**
+ * Returns the underlying {@link java.sql.Statement}
+ * (or {@link java.sql.PreparedStatement}).
+ *
+ **/
+
+ public Statement getStatement();
+
+ /**
+ * Closes the underlying statement, and nulls the reference to it.
+ *
+ **/
+
+ public void close() throws SQLException;
+
+ /**
+ * Executes the statement as a query, returning a {@link ResultSet}.
+ *
+ **/
+
+ public ResultSet executeQuery() throws SQLException;
+
+ /**
+ * Executes the statement as an update, returning the number of rows
+ * affected.
+ *
+ **/
+
+ public int executeUpdate() throws SQLException;
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IntegerParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IntegerParameter.java
new file mode 100644
index 0000000..9148201
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/IntegerParameter.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * A wrapper around an integer parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class IntegerParameter implements IParameter
+{
+ private int _value;
+
+ public IntegerParameter(int value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setInt(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Integer<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/LongParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/LongParameter.java
new file mode 100644
index 0000000..847e8a0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/LongParameter.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * Wrapper around long parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class LongParameter implements IParameter
+{
+ private long _value;
+
+ public LongParameter(long value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setLong(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Long<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ObjectParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ObjectParameter.java
new file mode 100644
index 0000000..25be80f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ObjectParameter.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * An arbitrary object parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class ObjectParameter implements IParameter
+{
+ private Object _value;
+
+ public ObjectParameter(Object value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setObject(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Object<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ParameterizedStatement.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ParameterizedStatement.java
new file mode 100644
index 0000000..3d37cca
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ParameterizedStatement.java
@@ -0,0 +1,152 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A wrapper around {@link PreparedStatement}.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ParameterizedStatement implements IStatement
+{
+ private static final Log LOG = LogFactory.getLog(ParameterizedStatement.class);
+
+ private String _SQL;
+ private PreparedStatement _statement;
+ private IParameter[] _parameters;
+
+ /**
+ * Create a new instance; the parameters list is copied.
+ *
+ * @param SQL the SQL to execute (see {@link Connection#prepareStatement(java.lang.String)})
+ * @param connection the JDBC connection to use
+ * @param parameters list of {@link IParameter}
+ *
+ **/
+
+ public ParameterizedStatement(String SQL, Connection connection, List parameters) throws SQLException
+ {
+ _SQL = SQL;
+
+ _statement = connection.prepareStatement(SQL);
+
+ _parameters = (IParameter[]) parameters.toArray(new IParameter[parameters.size()]);
+
+ for (int i = 0; i < _parameters.length; i++)
+ {
+ // JDBC numbers things from 1, not 0.
+
+ _parameters[i].set(_statement, i + 1);
+ }
+ }
+
+ /**
+ * Returns the SQL associated with this statement.
+ *
+ **/
+
+ public String getSQL()
+ {
+ return _SQL;
+ }
+
+ /**
+ * Returns the underlying or {@link PreparedStatement}.
+ *
+ **/
+
+ public Statement getStatement()
+ {
+ return _statement;
+ }
+
+ /**
+ * Closes the underlying statement, and nulls the reference to it.
+ *
+ **/
+
+ public void close() throws SQLException
+ {
+ _statement.close();
+
+ _statement = null;
+ _SQL = null;
+ }
+
+ /**
+ * Executes the statement as a query, returning a {@link ResultSet}.
+ *
+ **/
+
+ public ResultSet executeQuery() throws SQLException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Executing query: " + this);
+
+ return _statement.executeQuery();
+ }
+
+ /**
+ * Executes the statement as an update, returning the number of rows
+ * affected.
+ *
+ **/
+
+ public int executeUpdate() throws SQLException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Executing update: " + this);
+
+ return _statement.executeUpdate();
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("ParameterizedStatement@");
+ buffer.append(Integer.toHexString(hashCode()));
+ buffer.append("[SQL=\n<");
+ buffer.append(_SQL);
+ buffer.append("\n>");
+
+ for (int i = 0; i < _parameters.length; i++)
+ {
+ IParameter parameter = _parameters[i];
+
+ buffer.append(" ?");
+ buffer.append(i + 1);
+ buffer.append('=');
+
+ buffer.append(parameter);
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ShortParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ShortParameter.java
new file mode 100644
index 0000000..31cfea7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/ShortParameter.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+/**
+ * A wrapper around a short parameter.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class ShortParameter implements IParameter
+{
+ private short _value;
+
+ public ShortParameter(short value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ statement.setShort(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Short<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/SimpleStatement.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/SimpleStatement.java
new file mode 100644
index 0000000..16df20f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/SimpleStatement.java
@@ -0,0 +1,127 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A wrapper around {@link Statement}.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class SimpleStatement implements IStatement
+{
+ private static final Log LOG = LogFactory.getLog(SimpleStatement.class);
+
+ private String _sql;
+ private Statement _statement;
+
+ public SimpleStatement(String SQL, Connection connection) throws SQLException
+ {
+ _sql = SQL;
+ _statement = connection.createStatement();
+ }
+
+ public SimpleStatement(String SQL, Connection connection, int resultSetType, int resultSetConcurrency)
+ throws SQLException
+ {
+ _sql = SQL;
+ _statement = connection.createStatement(resultSetType, resultSetConcurrency);
+ }
+
+ /**
+ * Returns the SQL associated with this statement.
+ *
+ **/
+
+ public String getSQL()
+ {
+ return _sql;
+ }
+
+ /**
+ * Returns the underlying {@link Statement}.
+ *
+ **/
+
+ public Statement getStatement()
+ {
+ return _statement;
+ }
+
+ /**
+ * Closes the underlying statement, and nulls the reference to it.
+ *
+ **/
+
+ public void close() throws SQLException
+ {
+ _statement.close();
+
+ _statement = null;
+ _sql = null;
+ }
+
+ /**
+ * Executes the statement as a query, returning a {@link ResultSet}.
+ *
+ **/
+
+ public ResultSet executeQuery() throws SQLException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Executing query: " + this);
+
+ return _statement.executeQuery(_sql);
+ }
+
+ /**
+ * Executes the statement as an update, returning the number of rows
+ * affected.
+ *
+ **/
+
+ public int executeUpdate() throws SQLException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Executing update: " + this);
+
+ return _statement.executeUpdate(_sql);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer;
+
+ buffer = new StringBuffer("SimpleStatement@");
+ buffer.append(Integer.toHexString(hashCode()));
+
+ buffer.append("[SQL=<\n");
+ buffer.append(_sql);
+ buffer.append("\n>]");
+
+ return buffer.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StatementAssembly.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StatementAssembly.java
new file mode 100644
index 0000000..112e740
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StatementAssembly.java
@@ -0,0 +1,480 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * Class for creating and executing JDBC statements. Allows statements to be assembled
+ * incrementally (like a {@link StringBuffer}), but also tracks parameters, shielding
+ * the developer from the differences between constructing and
+ * using a JDBC
+ * {@link java.sql.Statement} and
+ * a JDBC {@link java.sql.PreparedStatement}. This class is somewhat skewed towards
+ * Oracle, which works more efficiently with prepared staments than
+ * simple SQL.
+ *
+ * <p>In addition, implements {@link #toString()} in a useful way (you can see the
+ * SQL and parameters), which is invaluable when debugging.
+ *
+ * <p>{@link #addParameter(int)} (and all overloaded versions of it for scalar types)
+ * adds a "?" to the statement and records the parameter value.
+ *
+ * <p>{@link #addParameter(Integer)} (and all overloaded version of it for wrapper
+ * types) does the same ... unless the value is null, in which case "NULL" is
+ * inserted into the statement.
+ *
+ * <p>{@link #addParameterList(int[], String)} (and all overloaded versions of it)
+ * simply invokes the appropriate {@link #addParameter(int)}, adding the
+ * separator in between parameters.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class StatementAssembly
+{
+ private StringBuffer _buffer = new StringBuffer();
+
+ private static final String NULL = "NULL";
+
+ public static final String SEP = ", ";
+
+ /**
+ * List of {@link IParameter}
+ *
+ **/
+
+ private List _parameters;
+
+ private int _lineLength;
+ private int _maxLineLength = 80;
+ private int _indent = 5;
+
+ /**
+ * Default constructor; uses a maximum line length of 80 and an indent of 5.
+ *
+ **/
+
+ public StatementAssembly()
+ {
+ }
+
+ public StatementAssembly(int maxLineLength, int indent)
+ {
+ _maxLineLength = maxLineLength;
+ _indent = indent;
+ }
+
+ /**
+ * Clears the assembly, preparing it for re-use.
+ *
+ * @since 1.0.7
+ *
+ **/
+
+ public void clear()
+ {
+ _buffer.setLength(0);
+ _lineLength = 0;
+
+ if (_parameters != null)
+ _parameters.clear();
+ }
+
+ /**
+ * Maximum length of a line.
+ *
+ **/
+
+ public int getMaxLineLength()
+ {
+ return _maxLineLength;
+ }
+
+ /**
+ * Number of spaces to indent continuation lines by.
+ *
+ **/
+
+ public int getIndent()
+ {
+ return _indent;
+ }
+
+ /**
+ * Adds text to the current line, unless that would make the line too long, in
+ * which case a new line is started (and indented) before adding the text.
+ *
+ * <p>Text is added as-is, with no concept of quoting. To add arbitrary strings
+ * (such as in a where clause), use {@link #addParameter(String)}.
+ *
+ *
+ **/
+
+ public void add(String text)
+ {
+ int textLength;
+
+ textLength = text.length();
+
+ if (_lineLength + textLength > _maxLineLength)
+ {
+ _buffer.append('\n');
+
+ for (int i = 0; i < _indent; i++)
+ _buffer.append(' ');
+
+ _lineLength = _indent;
+ }
+
+ _buffer.append(text);
+ _lineLength += textLength;
+ }
+
+ public void add(short value)
+ {
+ add(Short.toString(value));
+ }
+
+ public void add(int value)
+ {
+ add(Integer.toString(value));
+ }
+
+ public void add(long value)
+ {
+ add(Long.toString(value));
+ }
+
+ public void add(float value)
+ {
+ add(Float.toString(value));
+ }
+
+ public void add(double value)
+ {
+ add(Double.toString(value));
+ }
+
+ /**
+ * Adds a date value to a {@link StatementAssembly} converting
+ * it to a {@link java.sql.Timestamp} first.
+ *
+ **/
+
+ public void addParameter(Date date)
+ {
+ if (date == null)
+ {
+ add("NULL");
+ return;
+ }
+
+ Calendar calendar = GregorianCalendar.getInstance();
+
+ calendar.setTime(date);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ Date adjusted = calendar.getTime();
+
+ Timestamp timestamp = new Timestamp(adjusted.getTime());
+
+ addParameter(timestamp);
+ }
+
+ /**
+ * Adds a separator (usually a comma and a space) to the current line, regardless
+ * of line length. This is purely aesthetic ... it just looks odd if a separator
+ * gets wrapped to a new line by itself.
+ *
+ **/
+
+ public void addSep(String text)
+ {
+ _buffer.append(text);
+ _lineLength += text.length();
+ }
+
+ /**
+ * Starts a new line, without indenting.
+ *
+ **/
+
+ public void newLine()
+ {
+ if (_buffer.length() != 0)
+ _buffer.append('\n');
+
+ _lineLength = 0;
+ }
+
+ /**
+ * Starts a new line, then adds the given text.
+ *
+ **/
+
+ public void newLine(String text)
+ {
+ if (_buffer.length() != 0)
+ _buffer.append('\n');
+
+ _buffer.append(text);
+
+ _lineLength = text.length();
+ }
+
+ public void addList(String[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ add(items[i]);
+ }
+ }
+
+ public void addParameterList(int[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameterList(Integer[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameterList(long[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameterList(Long[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameterList(String[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameterList(double[] items, String separator)
+ {
+ for (int i = 0; i < items.length; i++)
+ {
+ if (i > 0)
+ addSep(separator);
+
+ addParameter(items[i]);
+ }
+ }
+
+ public void addParameter(Object value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(new ObjectParameter(value));
+ }
+
+ public void addParameter(Timestamp timestamp)
+ {
+ if (timestamp == null)
+ add(NULL);
+ else
+ addParameter(new TimestampParameter(timestamp));
+ }
+
+ public void addParameter(String value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(new StringParameter(value));
+ }
+
+ public void addParameter(int value)
+ {
+ addParameter(new IntegerParameter(value));
+ }
+
+ public void addParameter(Integer value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.intValue());
+ }
+
+ public void addParameter(long value)
+ {
+ addParameter(new LongParameter(value));
+ }
+
+ public void addParameter(Long value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.longValue());
+ }
+
+ public void addParameter(float value)
+ {
+ addParameter(new FloatParameter(value));
+ }
+
+ public void addParameter(Float value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.floatValue());
+ }
+
+ public void addParameter(double value)
+ {
+ addParameter(new DoubleParameter(value));
+ }
+
+ public void addParameter(Double value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.doubleValue());
+ }
+
+ public void addParameter(short value)
+ {
+ addParameter(new ShortParameter(value));
+ }
+
+ public void addParameter(Short value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.shortValue());
+ }
+
+ public void addParameter(boolean value)
+ {
+ addParameter(value ? BooleanParameter.TRUE : BooleanParameter.FALSE);
+ }
+
+ public void addParameter(Boolean value)
+ {
+ if (value == null)
+ add(NULL);
+ else
+ addParameter(value.booleanValue());
+ }
+
+ private void addParameter(IParameter parameter)
+ {
+ if (_parameters == null)
+ _parameters = new ArrayList();
+
+ _parameters.add(parameter);
+
+ add("?");
+ }
+
+ /**
+ * Creates and returns an {@link IStatement} based on the SQL and parameters
+ * acquired.
+ *
+ **/
+
+ public IStatement createStatement(Connection connection) throws SQLException
+ {
+ String sql = _buffer.toString();
+
+ if (_parameters == null || _parameters.isEmpty())
+ return new SimpleStatement(sql, connection);
+
+ return new ParameterizedStatement(sql, connection, _parameters);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("StatementAssembly@");
+
+ buffer.append(Integer.toHexString(hashCode()));
+ buffer.append("[SQL=\n<");
+ buffer.append(_buffer);
+ buffer.append("\n>");
+
+ if (_parameters != null)
+ {
+ int count = _parameters.size();
+ for (int i = 0; i < count; i++)
+ {
+ Object parameter = _parameters.get(i);
+
+ buffer.append(" ?");
+ buffer.append(i + 1);
+ buffer.append('=');
+
+ buffer.append(parameter);
+ }
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StringParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StringParameter.java
new file mode 100644
index 0000000..088a2d7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/StringParameter.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+
+/**
+ * Used with String parameters.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class StringParameter implements IParameter
+{
+ private String _value;
+
+ public StringParameter(String value)
+ {
+ _value = value;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ if (_value == null)
+ statement.setNull(index, Types.VARCHAR);
+ else
+ statement.setString(index, _value);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("String<");
+ buffer.append(_value);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/TimestampParameter.java b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/TimestampParameter.java
new file mode 100644
index 0000000..c5bd8fc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/TimestampParameter.java
@@ -0,0 +1,56 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.jdbc;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.sql.Types;
+
+/**
+ * Used with Timestamp parameters.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class TimestampParameter implements IParameter
+{
+ private Timestamp _timestamp;
+
+ public TimestampParameter(Timestamp timestamp)
+ {
+ _timestamp = timestamp;
+ }
+
+ public void set(PreparedStatement statement, int index) throws SQLException
+ {
+ if (_timestamp == null)
+ statement.setNull(index, Types.TIMESTAMP);
+ else
+ statement.setTimestamp(index, _timestamp);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Timestamp<");
+ buffer.append(_timestamp);
+ buffer.append('>');
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/package.html b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/package.html
new file mode 100644
index 0000000..13983ad
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/jdbc/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+
+<body>
+
+<p>A set of classes that assist with dynamically generating JDBC SQL queries. Importantly,
+they help hide the difference between a {@link java.sql.Statement} and
+{@link java.sql.PreparedStatement} ... in fact, using a
+{@link org.apache.tapestry.contrib.jdbc.StatementAssembly} you don't know in advance which
+you'll get, which is very handy when generating truly dynamic SQL.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/link/AreaLinkRenderer.java b/tapestry-contrib/src/org/apache/tapestry/contrib/link/AreaLinkRenderer.java
new file mode 100644
index 0000000..d27f12a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/link/AreaLinkRenderer.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.link;
+
+import org.apache.tapestry.link.DefaultLinkRenderer;
+import org.apache.tapestry.link.ILinkRenderer;
+
+/**
+ * A subclass of {@link org.apache.tapestry.link.DefaultLinkRenderer} for
+ * the HTML area element.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0
+ **/
+public class AreaLinkRenderer extends DefaultLinkRenderer
+{
+
+ /**
+ * A singleton for the area link.
+ **/
+
+ public static final ILinkRenderer SHARED_INSTANCE = new AreaLinkRenderer();
+
+ public String getElement()
+ {
+ return "area";
+ }
+
+ public boolean getHasBody()
+ {
+ return false;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/link/PopupLinkRenderer.java b/tapestry-contrib/src/org/apache/tapestry/contrib/link/PopupLinkRenderer.java
new file mode 100644
index 0000000..b079b6d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/link/PopupLinkRenderer.java
@@ -0,0 +1,88 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.link;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.link.DefaultLinkRenderer;
+
+/**
+ * This renderer emits javascript to launch the link in a window.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0.1
+ **/
+public class PopupLinkRenderer extends DefaultLinkRenderer
+{
+
+ private String _windowName;
+
+ private String _features;
+
+ public PopupLinkRenderer()
+ {
+ }
+
+ /**
+ * Initializes the name and features for javascript window.open function.
+ *
+ * @param windowName the window name
+ * @param features the window features
+ */
+ public PopupLinkRenderer(String windowName, String features)
+ {
+ _windowName = windowName;
+ _features = features;
+ }
+
+ /**
+ * @see DefaultLinkRenderer#constructURL(org.apache.tapestry.engine.ILink, String, org.apache.tapestry.IRequestCycle)
+ */
+ protected String constructURL(ILink link, String anchor, IRequestCycle cycle)
+ {
+ if (cycle.isRewinding()) {
+ return null;
+ }
+
+ String url = link.getURL(anchor, true);
+ return "javascript: w = window.open(" + normalizeString(url) + ", " + normalizeString(getWindowName()) + ", " + normalizeString(getFeatures()) + "); w.focus();";
+ }
+
+ private String normalizeString(String str)
+ {
+ return str == null ? "''" : "'" + str + "'";
+ }
+
+ public String getWindowName()
+ {
+ return _windowName;
+ }
+
+ public void setWindowName(String windowName)
+ {
+ _windowName = windowName;
+ }
+
+ public String getFeatures()
+ {
+ return _features;
+ }
+
+ public void setFeatures(String features)
+ {
+ _features = features;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.html b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.html
new file mode 100644
index 0000000..85e5095
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.html
@@ -0,0 +1,50 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+<table jwcid="@Any" element="table" class="ognl:tableClass">
+ <tr>
+ <th class="available-header">
+ <span jwcid="@RenderBlock" block="ognl:availableTitleBlock"/>
+ <span jwcid="defaultAvailableTitleBlock@Block"><span key="title.available"/></span>
+</th>
+ <td element="td" class="controls" rowspan="2">
+ <a jwcid="@Any"
+ element="a"
+ href="ognl:symbols.selectOnClickScript"><img jwcid="@Image"
+ image="ognl:selectImage"
+ name="ognl:symbols.selectImageName"
+ alt="message:tooltip.select"/></a>
+
+ <a jwcid="@Any" element="a"
+ href="ognl:symbols.deselectOnClickScript"><img jwcid="@Image"
+ image="ognl:deselectImage"
+ name="ognl:symbols.deselectImageName"
+ alt="message:tooltip.deselect"/></a>
+
+ <span jwcid="@Conditional" condition="ognl:sortUser">
+
+ <a jwcid="@Any" element="a"
+ href="ognl:symbols.upOnClickScript"><img jwcid="@Image"
+ image="ognl:upImage"
+ name="ognl:symbols.upImageName"
+ alt="message:tooltip.moveup"/></a>
+
+ <a jwcid="@Any" element="a"
+ href="ognl:symbols.downOnClickScript"><img jwcid="@Image"
+ image="ognl:downImage"
+ name="ognl:symbols.downImageName"
+ alt="message:tooltip.movedown"/></a>
+ </span>
+
+ </td>
+ <th class="selected-header">
+ <span jwcid="@RenderBlock" block="ognl:selectedTitleBlock"/>
+ <span jwcid="defaultSelectedTitleBlock@Block"><span key="title.selected"/></span>
+ </th>
+ </tr>
+ <tr>
+ <td class="available-cell"><select jwcid="@Delegator" delegate="ognl:availableColumn"/></td>
+ <td class="selected-cell"><select jwcid="@Delegator" delegate="ognl:selectedColumn"/></td>
+ </tr>
+</table>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.java b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.java
new file mode 100644
index 0000000..330c312
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.java
@@ -0,0 +1,583 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.palette;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.components.Block;
+import org.apache.tapestry.engine.IScriptSource;
+import org.apache.tapestry.form.Form;
+import org.apache.tapestry.form.FormEventType;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.form.IPropertySelectionModel;
+import org.apache.tapestry.html.Body;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.valid.IValidationDelegate;
+
+/**
+ * A component used to make a number of selections from a list. The general look
+ * is a pair of <select> elements. with a pair of buttons between them.
+ * The right element is a list of values that can be selected. The buttons move
+ * values from the right column ("available") to the left column ("selected").
+ *
+ * <p>This all takes a bit of JavaScript to accomplish (quite a bit), which means
+ * a {@link Body} component must wrap the Palette. If JavaScript is not enabled
+ * in the client browser, then the user will be unable to make (or change) any selections.
+ *
+ * <p>Cross-browser compatibility is not perfect. In some cases, the
+ * {@link org.apache.tapestry.contrib.form.MultiplePropertySelection} component
+ * may be a better choice.
+ *
+ * <p><table border=1>
+ * <tr>
+ * <td>Parameter</td>
+ * <td>Type</td>
+ * <td>Direction </td>
+ * <td>Required</td>
+ * <td>Default</td>
+ * <td>Description</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>selected</td>
+ * <td>{@link List}</td>
+ * <td>in</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>A List of selected values. Possible selections are defined by the model; this
+ * should be a subset of the possible values. This may be null when the
+ * component is renderred. When the containing form is submitted,
+ * this parameter is updated with a new List of selected objects.
+ *
+ * <p>The order may be set by the user, as well, depending on the
+ * sortMode parameter.</td> </tr>
+ *
+ * <tr>
+ * <td>model</td>
+ * <td>{@link IPropertySelectionModel}</td>
+ * <td>in</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>Works, as with a {@link org.apache.tapestry.form.PropertySelection} component, to define the
+ * possible values.
+ * </td> </tr>
+ *
+ * <tr>
+ * <td>sort</td>
+ * <td>{@link SortMode}</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>{@link SortMode#NONE}</td>
+ * <td>
+ * Controls automatic sorting of the options. </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>rows</td>
+ * <td>int</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>10</td>
+ * <td>The number of rows that should be visible in the Pallete's <select>
+ * elements.
+ * </td> </tr>
+ *
+ * <tr>
+ * <td>tableClass</td>
+ * <td>{@link String}</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>tapestry-palette</td>
+ * <td>The CSS class for the table which surrounds the other elements of
+ * the Palette.</td> </tr>
+ *
+ * <tr>
+ * <td>selectedTitleBlock</td>
+ * <td>{@link Block}</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>"Selected"</td>
+ * <td>If specified, allows a {@link Block} to be placed within
+ * the <th> reserved for the title above the selected items
+ * <select> (on the right). This allows for images or other components to
+ * be placed there. By default, the simple word <code>Selected</code>
+ * is used.</td> </tr>
+ *
+ * <tr>
+ * <td>availableTitleBlock</td>
+ * <td>{@link Block}</td>
+ * <td>in</td>
+ * <td>no</td>
+ * <td>"Available"</td>
+ * <td>As with selectedTitleBlock, but for the left column, of items
+ * which are available to be selected. The default is the word
+ * <code>Available</code>. </td> </tr>
+ *
+ * <tr>
+ * <td>selectImage
+ * <br>selectDisabledImage
+ * <br>deselectImage
+ * <br>deselectDisabledImage
+ * <br>upImage
+ * <br>upDisabledImage
+ * <br>downImage
+ * <br>downDisabledImage
+ * </td>
+ * <td>{@link IAsset}</td>
+ * <td>in</td>
+ * <td>no</td> <td> </td>
+ * <td>If any of these are specified then they override the default images provided
+ * with the component. This allows the look and feel to be customized relatively easily.
+ *
+ * <p>The most common reason to replace the images is to deal with backgrounds. The default
+ * images are anti-aliased against a white background. If a colored or patterned background
+ * is used, the default images will have an ugly white fringe. Until all browsers have full
+ * support for PNG (which has a true alpha channel), it is necessary to customize the images
+ * to match the background.
+ *
+ * </td> </tr>
+ *
+ * </table>
+ *
+ * <p>A Palette requires some CSS entries to render correctly ... especially
+ * the middle column, which contains the two or four buttons for moving selections
+ * between the two columns. The width and alignment of this column must be set
+ * using CSS. Additionally, CSS is commonly used to give the Palette columns
+ * a fixed width, and to dress up the titles. Here is an example of some CSS
+ * you can use to format the palette component:
+ *
+ * <pre>
+ * TABLE.tapestry-palette TH
+ * {
+ * font-size: 9pt;
+ * font-weight: bold;
+ * color: white;
+ * background-color: #330066;
+ * text-align: center;
+ * }
+ *
+ * TD.available-cell SELECT
+ * {
+ * font-weight: normal;
+ * background-color: #FFFFFF;
+ * width: 200px;
+ * }
+ *
+ * TD.selected-cell SELECT
+ * {
+ * font-weight: normal;
+ * background-color: #FFFFFF;
+ * width: 200px;
+ * }
+ *
+ * TABLE.tapestry-palette TD.controls
+ * {
+ * text-align: center;
+ * vertical-align: middle;
+ * width: 60px;
+ * }
+ * </pre>
+ *
+ * @author Howard Lewis Ship
+ */
+
+public abstract class Palette extends BaseComponent implements IFormComponent
+{
+ private static final int DEFAULT_ROWS = 10;
+ private static final int MAP_SIZE = 7;
+ private static final String DEFAULT_TABLE_CLASS = "tapestry-palette";
+
+ /**
+ * A set of symbols produced by the Palette script. This is used to
+ * provide proper names for some of the HTML elements (<select> and
+ * <button> elements, etc.).
+ *
+ */
+
+ private Map _symbols;
+
+ /**
+ * A cached copy of the script used with the component.
+ *
+ */
+
+ private IScript _script;
+
+ /** @since 3.0 **/
+ public abstract void setAvailableColumn(PaletteColumn column);
+
+ /** @since 3.0 **/
+ public abstract void setSelectedColumn(PaletteColumn column);
+
+ protected void finishLoad()
+ {
+ setSelectedTitleBlock((Block) getComponent("defaultSelectedTitleBlock"));
+ setAvailableTitleBlock((Block) getComponent("defaultAvailableTitleBlock"));
+
+ setSelectImage(getAsset("Select"));
+ setSelectDisabledImage(getAsset("SelectDisabled"));
+ setDeselectImage(getAsset("Deselect"));
+ setDeselectDisabledImage(getAsset("DeselectDisabled"));
+ setUpImage(getAsset("Up"));
+ setUpDisabledImage(getAsset("UpDisabled"));
+ setDownImage(getAsset("Down"));
+ setDownDisabledImage(getAsset("DownDisabled"));
+
+ setTableClass(DEFAULT_TABLE_CLASS);
+ setRows(DEFAULT_ROWS);
+ setSort(SortMode.NONE);
+ }
+
+ public abstract String getName();
+ public abstract void setName(String name);
+
+ public abstract IForm getForm();
+ public abstract void setForm(IForm form);
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = Form.get(getPage().getRequestCycle());
+
+ if (form == null)
+ throw new ApplicationRuntimeException(
+ "Palette component must be wrapped by a Form.",
+ this,
+ null,
+ null);
+
+ setForm(form);
+
+ IValidationDelegate delegate = form.getDelegate();
+
+ if (delegate != null)
+ delegate.setFormComponent(this);
+
+ setName(form.getElementId(this));
+
+ if (form.isRewinding())
+ handleSubmission(cycle);
+
+ // Don't do any additional work if rewinding
+ // (some other action or form on the page).
+
+ if (!cycle.isRewinding())
+ {
+ // Lots of work to produce JavaScript and HTML for this sucker.
+
+ _symbols = new HashMap(MAP_SIZE);
+
+ runScript(cycle);
+
+ // Output symbol 'formSubmitFunctionName' is the name
+ // of a JavaScript function to execute when the form
+ // is submitted. This is also key to the operation
+ // of the PropertySelection.
+
+ form.addEventHandler(
+ FormEventType.SUBMIT,
+ (String) _symbols.get("formSubmitFunctionName"));
+
+ constructColumns();
+ }
+
+ super.renderComponent(writer, cycle);
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ _symbols = null;
+
+ setAvailableColumn(null);
+ setSelectedColumn(null);
+
+ super.cleanupAfterRender(cycle);
+ }
+
+ /**
+ * Executes the associated script, which generates all the JavaScript to
+ * support this Palette.
+ *
+ */
+ private void runScript(IRequestCycle cycle)
+ {
+ // Get the script, if not already gotten. Scripts are re-entrant, so it is
+ // safe to share this between instances of Palette.
+
+ if (_script == null)
+ {
+ IEngine engine = getPage().getEngine();
+ IScriptSource source = engine.getScriptSource();
+
+ IResourceLocation scriptLocation =
+ getSpecification().getSpecificationLocation().getRelativeLocation("Palette.script");
+
+ _script = source.getScript(scriptLocation);
+ }
+
+ Body body = Body.get(cycle);
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ "Palette component must be wrapped by a Body.",
+ this,
+ null,
+ null);
+
+ setImage(body, cycle, "selectImage", getSelectImage());
+ setImage(body, cycle, "selectDisabledImage", getSelectDisabledImage());
+ setImage(body, cycle, "deselectImage", getDeselectImage());
+ setImage(body, cycle, "deselectDisabledImage", getDeselectDisabledImage());
+
+ if (isSortUser())
+ {
+ setImage(body, cycle, "upImage", getUpImage());
+ setImage(body, cycle, "upDisabledImage", getUpDisabledImage());
+ setImage(body, cycle, "downImage", getDownImage());
+ setImage(body, cycle, "downDisabledImage", getDownDisabledImage());
+ }
+
+ _symbols.put("palette", this);
+
+ _script.execute(cycle, body, _symbols);
+ }
+
+ /**
+ * Extracts its asset URL, sets it up for
+ * preloading, and assigns the preload reference as a script symbol.
+ *
+ */
+ private void setImage(Body body, IRequestCycle cycle, String symbolName, IAsset asset)
+ {
+ String URL = asset.buildURL(cycle);
+ String reference = body.getPreloadedImageReference(URL);
+
+ _symbols.put(symbolName, reference);
+ }
+
+ public Map getSymbols()
+ {
+ return _symbols;
+ }
+
+ /**
+ * Constructs a pair of {@link PaletteColumn}s: the available and selected options.
+ *
+ */
+ private void constructColumns()
+ {
+ // Build a Set around the list of selected items.
+
+ List selected = getSelected();
+
+ if (selected == null)
+ selected = Collections.EMPTY_LIST;
+
+ SortMode sortMode = getSort();
+
+ boolean sortUser = sortMode == SortMode.USER;
+
+ List selectedOptions = null;
+
+ if (sortUser)
+ {
+ int count = selected.size();
+ selectedOptions = new ArrayList(count);
+
+ for (int i = 0; i < count; i++)
+ selectedOptions.add(null);
+ }
+
+ PaletteColumn availableColumn =
+ new PaletteColumn((String) _symbols.get("availableName"), getRows());
+ PaletteColumn selectedColumn = new PaletteColumn(getName(), getRows());
+
+ // Each value specified in the model will go into either the selected or available
+ // lists.
+
+ IPropertySelectionModel model = getModel();
+
+ int count = model.getOptionCount();
+
+ for (int i = 0; i < count; i++)
+ {
+ Object optionValue = model.getOption(i);
+
+ PaletteOption o = new PaletteOption(model.getValue(i), model.getLabel(i));
+
+ int index = selected.indexOf(optionValue);
+ boolean isSelected = index >= 0;
+
+ if (sortUser && isSelected)
+ {
+ selectedOptions.set(index, o);
+ continue;
+ }
+
+ PaletteColumn c = isSelected ? selectedColumn : availableColumn;
+
+ c.addOption(o);
+ }
+
+ if (sortUser)
+ {
+ Iterator i = selectedOptions.iterator();
+ while (i.hasNext())
+ {
+ PaletteOption o = (PaletteOption) i.next();
+ selectedColumn.addOption(o);
+ }
+ }
+
+ if (sortMode == SortMode.VALUE)
+ {
+ availableColumn.sortByValue();
+ selectedColumn.sortByValue();
+ }
+ else
+ if (sortMode == SortMode.LABEL)
+ {
+ availableColumn.sortByLabel();
+ selectedColumn.sortByLabel();
+ }
+
+ setAvailableColumn(availableColumn);
+ setSelectedColumn(selectedColumn);
+ }
+
+ private void handleSubmission(IRequestCycle cycle)
+ {
+ RequestContext context = cycle.getRequestContext();
+ String[] values = context.getParameters(getName());
+
+ int count = Tapestry.size(values);
+
+ // Build a new ArrayList and fill it with the selected
+ // objects, if any.
+
+ List selected = new ArrayList(count);
+ IPropertySelectionModel model = getModel();
+
+ for (int i = 0; i < count; i++)
+ {
+ String value = values[i];
+ Object option = model.translateValue(value);
+
+ selected.add(option);
+ }
+
+ setSelected(selected);
+ }
+
+ public boolean isSortUser()
+ {
+ return getSort() == SortMode.USER;
+ }
+
+ /**
+ * Returns null, but may make sense to implement a displayName parameter.
+ *
+ */
+ public String getDisplayName()
+ {
+ return null;
+ }
+
+ public abstract Block getAvailableTitleBlock();
+
+ public abstract void setAvailableTitleBlock(Block availableTitleBlock);
+
+ public abstract IAsset getDeselectDisabledImage();
+
+ public abstract void setDeselectDisabledImage(IAsset deselectDisabledImage);
+
+ public abstract IAsset getDeselectImage();
+
+ public abstract void setDeselectImage(IAsset deselectImage);
+
+ public abstract IAsset getDownDisabledImage();
+
+ public abstract void setDownDisabledImage(IAsset downDisabledImage);
+
+ public abstract IAsset getDownImage();
+
+ public abstract void setDownImage(IAsset downImage);
+
+ public abstract IPropertySelectionModel getModel();
+
+ public abstract int getRows();
+
+ public abstract void setRows(int rows);
+
+ public abstract IAsset getSelectDisabledImage();
+
+ public abstract void setSelectDisabledImage(IAsset selectDisabledImage);
+
+ public abstract Block getSelectedTitleBlock();
+
+ public abstract void setSelectedTitleBlock(Block selectedTitleBlock);
+
+ public abstract IAsset getSelectImage();
+
+ public abstract void setSelectImage(IAsset selectImage);
+
+ public abstract SortMode getSort();
+
+ public abstract void setSort(SortMode sort);
+
+ public abstract void setTableClass(String tableClass);
+
+ public abstract IAsset getUpDisabledImage();
+
+ public abstract void setUpDisabledImage(IAsset upDisabledImage);
+
+ public abstract IAsset getUpImage();
+
+ public abstract void setUpImage(IAsset upImage);
+
+ /**
+ * Returns false. Palette components are never disabled.
+ *
+ * @since 2.2
+ *
+ */
+
+ public boolean isDisabled()
+ {
+ return false;
+ }
+
+ /** @since 2.2 **/
+
+ public abstract List getSelected();
+
+ /** @since 2.2 **/
+
+ public abstract void setSelected(List selected);
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.jwc
new file mode 100644
index 0000000..8b130c9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.jwc
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.palette.Palette"
+ allow-body="no" allow-informal-parameters="no">
+
+ <description>
+ A complex component used to manage multiple selection of items from a list.
+ </description>
+
+ <parameter name="selectedTitleBlock"
+ type="org.apache.tapestry.components.Block"
+ required="no"
+ direction="in"/>
+
+ <parameter name="availableTitleBlock"
+ type="org.apache.tapestry.components.Block"
+ required="no"
+ direction="in"/>
+
+ <parameter name="model"
+ type="org.apache.tapestry.form.IPropertySelectionModel"
+ required="yes"
+ direction="in"/>
+
+ <parameter name="selected" type="java.util.List"
+ required="yes" direction="form"/>
+
+ <parameter name="sort"
+ type="org.apache.tapestry.contrib.palette.SortMode"
+ required="no"
+ direction="in"/>
+
+ <parameter name="rows"
+ type="int"
+ required="no"
+ direction="in"/>
+
+ <parameter name="tableClass"
+ type="java.lang.String"
+ required="no"
+ direction="in"/>
+
+
+ <parameter name="selectImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="selectDisabledImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="deselectImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="deselectDisabledImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="upImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="upDisabledImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="downImage" type="org.apache.tapestry.IAsset" direction="in"/>
+ <parameter name="downDisabledImage" type="org.apache.tapestry.IAsset" direction="in"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+ <property-specification name="selectedColumn" type="org.apache.tapestry.contrib.palette.PaletteColumn"/>
+ <property-specification name="availableColumn" type="org.apache.tapestry.contrib.palette.PaletteColumn"/>
+
+
+ <private-asset name="Select" resource-path="select_right.gif"/>
+ <private-asset name="SelectDisabled" resource-path="select_right_off.gif"/>
+ <private-asset name="Deselect" resource-path="deselect_left.gif"/>
+ <private-asset name="DeselectDisabled" resource-path="deselect_left_off.gif"/>
+ <private-asset name="Up" resource-path="move_up.gif"/>
+ <private-asset name="UpDisabled" resource-path="move_up_off.gif"/>
+ <private-asset name="Down" resource-path="move_down.gif"/>
+ <private-asset name="DownDisabled" resource-path="move_down_off.gif"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.properties b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.properties
new file mode 100644
index 0000000..6056da7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.properties
@@ -0,0 +1,8 @@
+# $Id$
+title.available=Available
+title.selected=Selected
+
+tooltip.select=Select
+tooltip.deselect=Deselect
+tooltip.moveup=Move Up
+tooltip.movedown=Move Down
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.script b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.script
new file mode 100644
index 0000000..848346c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/Palette.script
@@ -0,0 +1,349 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+<script>
+<!--
+
+input symbols:
+ palette - the Palette instance
+ selectImage - reference to the select image
+ selectDisabledImage - referece to the disabled select image
+ deselectImage - reference to the deselect image
+ deselectDisabledImage - reference to the disbled deselect image
+ upImage - reference to the move up image
+ upDisabledImage - reference to the disabled move up image
+ downImage - reference to the move down image
+ downDisabledImage - reference to the disabled move down image
+
+Note: "reference" means the result of Body.getPreloadedImageReference(). The
+up and down images are only needed if user sorting is enabled.
+
+output symbols:
+ formSubmitFunctionName - name of a function to be executed when the form submits
+ availableName - the name of the available element
+ selectImageName - the name to use for the select image (inside the select link)
+ selectOnClickScript - the script to assign to the select link's onclick attribute
+ deselectOnClickScript - the script to assign to the deselect link's onclick attribute
+ deselectImageName - the name to use for the deselect image (inside the deselect link)
+ upImageName - the name of the up image (inside the up link)
+ downImageName the name of the move down image (inside the down link)
+ upOnClickScript - the script to assign to the up link's onclick attribute
+ downOnClickScript - the script to assign to the down link's onclick attribute
+-->
+
+<include-script resource-path="/org/apache/tapestry/html/PracticalBrowserSniffer.js"/>
+<include-script resource-path="/org/apache/tapestry/contrib/palette/PaletteFunctions.js"/>
+
+<input-symbol key="palette" class="org.apache.tapestry.contrib.palette.Palette" required="yes"/>
+<input-symbol key="selectImage" class="java.lang.String" required="yes"/>
+<input-symbol key="selectDisabledImage" class="java.lang.String" required="yes"/>
+<input-symbol key="deselectImage" class="java.lang.String" required="yes"/>
+<input-symbol key="deselectDisabledImage" class="java.lang.String" required="yes"/>
+<input-symbol key="upImage" class="java.lang.String"/>
+<input-symbol key="upDisabledImage" class="java.lang.String"/>
+<input-symbol key="downImage" class="java.lang.String"/>
+<input-symbol key="downDisabledImage" class="java.lang.String"/>
+
+<set key="formName" expression="palette.form.name"/>
+<set key="name" expression="palette.name"/>
+<set key="sortLabel" expression="palette.sort == @org.apache.tapestry.contrib.palette.SortMode@LABEL"/>
+<set key="sortValue" expression="palette.sort == @org.apache.tapestry.contrib.palette.SortMode@VALUE"/>
+<set key="sortUser" expression="palette.sort == @org.apache.tapestry.contrib.palette.SortMode@USER"/>
+
+
+<!-- baseName - base name from which other names are generated -->
+
+<let key="baseName" unique="yes">
+ ${name}
+</let>
+
+<let key="buttons">
+ ${baseName}$buttons
+</let>
+
+<let key="selectDisabled">
+ ${buttons}.selectDisabled
+</let>
+
+<let key="deselectDisabled">
+ ${buttons}.deselectDisabled
+</let>
+
+<let key="upDisabled">
+ ${buttons}.upDisabled
+</let>
+
+<let key="downDisabled">
+ ${buttons}.downDisabled
+</let>
+
+<let key="availableName">
+ ${name}$avail
+</let>
+
+<let key="updateFunctionName">
+ update_${baseName}
+</let>
+
+<let key="selectFunctionName">
+ select_${baseName}
+</let>
+
+<let key="selectOnClickScript">
+ javascript:${selectFunctionName}();
+</let>
+
+<let key="deselectFunctionName">
+ deselect_${baseName}
+</let>
+
+<let key="deselectOnClickScript">
+ javascript:${deselectFunctionName}();
+</let>
+
+<let key="formSubmitFunctionName">
+ onsubmit_${baseName}
+</let>
+
+<let key="selectImageName">
+ ${baseName}$selectImage
+</let>
+
+<let key="selectImagePath">
+ document.${selectImageName}
+</let>
+
+<let key="deselectImageName">
+ ${baseName}$deselectImage
+</let>
+
+<let key="deselectImagePath">
+ document.${deselectImageName}
+</let>
+
+<let key="formPath">
+ document.${formName}
+</let>
+
+<let key="selectedPath">
+ ${formPath}.${name}
+</let>
+
+<let key="selectedChangeFunctionName">
+ onChange_${baseName}_selected
+</let>
+
+<let key="availablePath">
+ ${formPath}.${availableName}
+</let>
+
+<let key="availableChangeFunctionName">
+ onChange_${baseName}_available
+</let>
+
+
+<let key="upImageName">
+ ${baseName}$upimage
+</let>
+
+<let key="upImagePath">
+ document.${upImageName}
+</let>
+
+<let key="downImageName">
+ ${baseName}$downimage
+</let>
+
+<let key="downImagePath">
+ document.${downImageName}
+</let>
+
+<let key="moveUpFunctionName">
+ moveup_${baseName}
+</let>
+
+<let key="upOnClickScript">
+ javascript:${moveUpFunctionName}();
+</let>
+
+<let key="moveDownFunctionName">
+ movedown_${baseName}
+</let>
+
+<let key="downOnClickScript">
+ javascript:${moveDownFunctionName}();
+</let>
+
+
+<body>
+
+<!-- A variable that is used to track which of the buttons are enabled
+ or disabled. All of the buttons are disabled until the page finishes
+ loading, at which point the update function will determine which
+ can be used. -->
+
+var ${buttons} = new Object();
+${selectDisabled} = true;
+${deselectDisabled} = true;
+<if expression="sortUser">
+${upDisabled} = true;
+${downDisabled} = true;
+</if>
+
+function ${updateFunctionName}()
+{
+ var disabled = ${availablePath}.selectedIndex < 0;
+
+ ${selectDisabled} = disabled;
+
+ if (document.images)
+ ${selectImagePath}.src =
+ disabled ? ${selectDisabledImage}
+ : ${selectImage};
+
+ var selected = ${selectedPath};
+ var index = selected.selectedIndex;
+
+ disabled = index < 0;
+ ${deselectDisabled} = disabled;
+
+ if (document.images)
+ ${deselectImagePath}.src =
+ disabled ? ${deselectDisabledImage}
+ : ${deselectImage};
+<if expression="sortUser">
+ var upImage = ${upImagePath};
+ var downImage = ${downImagePath};
+
+ ${upDisabled} = true;
+ ${downDisabled} = true;
+
+ if (document.images)
+ {
+ upImage.src = ${upDisabledImage};
+ downImage.src = ${downDisabledImage};
+ }
+
+ <!-- If there's no selection in the "selected" column, then leave
+ both buttons disabled. -->
+
+ if (disabled)
+ return;
+
+ <!-- Search for a second selected item -->
+
+ for (var i = index + 1; i < selected.options.length; i++)
+ {
+ <!-- Found a second selected option, so leave buttons disabled. -->
+ if (selected.options[i].selected)
+ return;
+ }
+
+ ${upDisabled} = (index == 0);
+ ${downDisabled} = (index == selected.options.length - 1);
+
+ if (document.images)
+ {
+ if (!${upDisabled})
+ upImage.src = ${upImage};
+
+ if (!${downDisabled})
+ downImage.src = ${downImage};
+ }
+</if>
+}
+
+function ${selectFunctionName}()
+{
+ if (${selectDisabled})
+ return;
+
+ var source = ${availablePath};
+ var target = ${selectedPath};
+
+ palette_transfer_selections(source, target);
+<if expression="sortLabel">
+ palette_sort_by_label(target);
+</if>
+<if expression="sortValue">
+ palette_sort_by_value(target);
+</if>
+ ${updateFunctionName}();
+}
+
+function ${deselectFunctionName}()
+{
+ if (${deselectDisabled})
+ return;
+
+ var source = ${selectedPath};
+ var target = ${availablePath};
+
+ palette_transfer_selections(source, target);
+<if expression="sortLabel">
+ palette_sort_by_label(target);
+</if>
+<if expression="sortValue">
+ palette_sort_by_value(target);
+</if>
+ ${updateFunctionName}();
+}
+
+function ${formSubmitFunctionName}()
+{
+ palette_clear_selections(${availablePath});
+ palette_select_all(${selectedPath});
+
+ return true;
+}
+<if expression="sortUser">
+function ${moveUpFunctionName}()
+{
+ if (${upDisabled})
+ return;
+
+ var element = ${selectedPath};
+ var options = element.options;
+
+ palette_swap_options(options, element.selectedIndex, element.selectedIndex - 1);
+
+ ${updateFunctionName}();
+}
+
+function ${moveDownFunctionName}()
+{
+ if (${downDisabled})
+ return;
+
+ var element = ${selectedPath};
+ var options = element.options;
+
+ palette_swap_options(options, element.selectedIndex, element.selectedIndex + 1);
+
+ ${updateFunctionName}();
+}
+</if>
+function ${selectedChangeFunctionName}()
+{
+ palette_clear_selections(${availablePath});
+ ${updateFunctionName}();
+}
+
+function ${availableChangeFunctionName}()
+{
+ palette_clear_selections(${selectedPath});
+ ${updateFunctionName}();
+}
+</body>
+
+<initialization>
+
+${selectedPath}.onchange = ${selectedChangeFunctionName};
+${selectedPath}.ondblclick = ${deselectFunctionName};
+${availablePath}.onchange = ${availableChangeFunctionName};
+${availablePath}.ondblclick = ${selectFunctionName};
+
+</initialization>
+</script>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteColumn.java
new file mode 100644
index 0000000..89aae9a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteColumn.java
@@ -0,0 +1,118 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.palette;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * One of the two columns in a Palette component: the left column lists
+ * available options, the right column lists the selected columns.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ */
+public class PaletteColumn implements IRender
+{
+ private String _name;
+ private int _rows;
+ private List _options = new ArrayList();
+
+ private static class ValueComparator implements Comparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ PaletteOption option1 = (PaletteOption) o1;
+ PaletteOption option2 = (PaletteOption) o2;
+
+ return option1.getValue().compareTo(option2.getValue());
+ }
+ }
+
+ private static class LabelComparator implements Comparator
+ {
+ public int compare(Object o1, Object o2)
+ {
+ PaletteOption option1 = (PaletteOption) o1;
+ PaletteOption option2 = (PaletteOption) o2;
+
+ return option1.getLabel().compareTo(option2.getLabel());
+ }
+ }
+
+ /**
+ * @param name the name of the column (the name attribute of the <select>)
+ * @param rows the number of visible rows (the size attribute of the <select>)
+ */
+ public PaletteColumn(String name, int rows)
+ {
+ _name = name;
+ _rows = rows;
+ }
+
+ public void addOption(PaletteOption option)
+ {
+ _options.add(option);
+ }
+
+ /**
+ * Sorts the options by value (the hidden value for the option
+ * that represents the object value). This should be invoked
+ * before rendering this PaletteColumn.
+ */
+ public void sortByValue()
+ {
+ Collections.sort(_options, new ValueComparator());
+ }
+
+ /**
+ * Sorts the options by the label visible to the user. This should be invoked
+ * before rendering this PaletteColumn.
+ */
+ public void sortByLabel()
+ {
+ Collections.sort(_options, new LabelComparator());
+ }
+
+ /**
+ * Renders the <select> and <option> tags for
+ * this column.
+ */
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writer.begin("select");
+ writer.attribute("multiple", "multiple");
+ writer.attribute("name", _name);
+ writer.attribute("size", _rows);
+ writer.println();
+
+ int count = _options.size();
+ for (int i = 0; i < count; i++)
+ {
+ PaletteOption o = (PaletteOption) _options.get(i);
+
+ o.render(writer, cycle);
+ }
+
+ writer.end();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteFunctions.js b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteFunctions.js
new file mode 100644
index 0000000..4973536
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteFunctions.js
@@ -0,0 +1,178 @@
+// $Id: PaletteFunctions.js,v 1.3 2002/05/03 20:03:06 hship Exp $
+// Requires: /org/apache/tapestry/html/PracticalBrowserSniffer.js
+
+function palette_clear_selections(element)
+{
+ var options = element.options;
+
+ for (var i = 0; i < options.length; i++)
+ options[i].selected = false;
+}
+
+function palette_select_all(element)
+{
+ var options = element.options;
+
+ for (var i = 0; i < options.length; i++)
+ options[i].selected = true;
+}
+
+function palette_sort(element, sorter)
+{
+ var options = element.options;
+ var list = new Array();
+ var index = 0;
+ var isNavigator = (navigator.family == "nn4" || navigator.family == "gecko");
+
+ while (options.length > 0)
+ {
+ var option = options[0];
+
+ if (isNavigator)
+ {
+ // Can't transfer option in nn4, nn6
+
+ if (navigator.family == 'gecko')
+ var copy = document.createElement("OPTION");
+ else
+ var copy = new Option(option.text, option.value);
+
+ copy.text = option.text;
+ copy.value = option.value;
+ copy.selected = options.selected;
+
+ list[index++] = copy;
+ }
+ else
+ list[index++] = option;
+
+
+ options[0] = null;
+ }
+
+ list.sort(sorter);
+
+ for (var i = 0; i < list.length; i++)
+ {
+ options[i] = list[i];
+ }
+
+
+}
+
+function palette_label_sorter(a, b)
+{
+ var a_text = a.text;
+ var b_text = b.text;
+
+ if (a_text == b_text)
+ return 0;
+
+ if (a_text < b.text)
+ return -1;
+
+ return 1;
+}
+
+function palette_sort_by_label(element)
+{
+ palette_sort(element, palette_label_sorter);
+}
+
+function palette_value_sorter(a, b)
+{
+ var a_value = a.value;
+ var b_value = b.value;
+
+ if (a_value == b_value)
+ return 0;
+
+ if (a_value < b_value)
+ return -1;
+
+ return 1;
+}
+
+function palette_sort_by_value(element)
+{
+ palette_sort(element, palette_value_sorter);
+}
+
+function palette_transfer_selections(source, target)
+{
+ var sourceOptions = source.options;
+ var targetOptions = target.options;
+
+ var targetIndex = target.selectedIndex;
+ var offset = 0;
+
+ palette_clear_selections(target);
+
+ for (var i = 0; i < sourceOptions.length; i++)
+ {
+ var option = sourceOptions[i];
+
+ if (option.selected)
+ {
+
+ if (navigator.family == 'nn4' || navigator.family == 'gecko')
+ {
+ // Can't share options between selects in NN4
+
+ var newOption = new Option(option.text, option.value, false, true);
+
+ sourceOptions[i] = null;
+
+ // Always added to end in NN4
+
+ targetOptions[targetOptions.length] = newOption;
+ }
+ else
+ {
+ sourceOptions.remove(i);
+
+ if (targetIndex < 0)
+ targetOptions.add(option);
+ else
+ targetOptions.add(option, targetIndex + offset++);
+ }
+
+ i--;
+ }
+ }
+
+}
+
+function palette_swap_options(options, selectedIndex, targetIndex)
+{
+ var option = options[selectedIndex];
+
+ // It's very hard to reorder options in NN4
+
+ if (navigator.family == 'nn4' || navigator.family == 'gecko')
+ {
+ var swap = options[targetIndex];
+
+ var hold = swap.text;
+ swap.text = option.text;
+ option.text = hold;
+
+ hold = swap.value;
+ swap.value = option.value;
+ option.value = hold;
+
+ hold = swap.selected;
+ swap.selected = option.selected;
+ option.selected = hold;
+
+ // defaultSelected isn't relevant to the Palette
+
+ return;
+ }
+
+ // Sensible browsers ...
+
+ options.remove(selectedIndex);
+ options.add(option, targetIndex);
+}
+
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteOption.java b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteOption.java
new file mode 100644
index 0000000..4c2d478
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/PaletteOption.java
@@ -0,0 +1,58 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.palette;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Used to hold options editable by a Palette component, so that they may
+ * be sorted into an appropriate order.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ */
+public class PaletteOption implements IRender
+{
+ private String _value;
+ private String _label;
+
+ public PaletteOption(String value, String label)
+ {
+ _value = value;
+ _label = label;
+ }
+
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writer.begin("option");
+ writer.attribute("value", _value);
+ writer.print(_label);
+ writer.end(); // <option>
+ writer.println();
+ }
+
+ public String getLabel()
+ {
+ return _label;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/SortMode.java b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/SortMode.java
new file mode 100644
index 0000000..ab8c082
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/SortMode.java
@@ -0,0 +1,64 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.palette;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Defines different sorting strategies for the {@link Palette} component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class SortMode extends Enum
+{
+ /**
+ * Sorting is not relevant and no sort controls should be visible.
+ *
+ **/
+
+ public static final SortMode NONE = new SortMode("NONE");
+
+ /**
+ * Options should be sorted by their label.
+ *
+ **/
+
+ public static final SortMode LABEL = new SortMode("LABEL");
+
+ /**
+ * Options should be sorted by thier value.
+ *
+ **/
+
+ public static final SortMode VALUE = new SortMode("VALUE");
+
+ /**
+ * The user controls sort order; additional controls are added
+ * to allow the user to control the order of options in the
+ * selected list.
+ *
+ **/
+
+ public static final SortMode USER = new SortMode("USER");
+
+ private SortMode(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left.gif
new file mode 100644
index 0000000..2940e01
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left_off.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left_off.gif
new file mode 100644
index 0000000..84dc945
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/deselect_left_off.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down.gif
new file mode 100644
index 0000000..8fb0088
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down_off.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down_off.gif
new file mode 100644
index 0000000..174baeb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_down_off.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up.gif
new file mode 100644
index 0000000..711c86a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up_off.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up_off.gif
new file mode 100644
index 0000000..e23a43f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/move_up_off.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right.gif
new file mode 100644
index 0000000..74e90c3
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right_off.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right_off.gif
new file mode 100644
index 0000000..452ce50
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/palette/select_right_off.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.html b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.html
new file mode 100644
index 0000000..63d5ed5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.html
@@ -0,0 +1,3 @@
+<!-- $Id$ -->
+<span jwcid="popoutScript"/>
+<span jwcid="link"><span jwcid="wrapped"/></span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.java b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.java
new file mode 100644
index 0000000..6810077
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.java
@@ -0,0 +1,128 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.popup;
+
+import java.io.UnsupportedEncodingException;
+
+import org.apache.commons.codec.net.URLCodec;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * This component provides a popup link to launch a new window using a given
+ * href, windowName and windowFeatures for the javascript function:
+ * <tt>window.open(URL, windowName, windowFeatures)</tt>.
+ *
+ * [<a href="../../../../../../ComponentReference/contrib.PopupLink.html">Component Reference</a>]
+ *
+ * @version $Id$
+ * @author Joe Panico
+ */
+public class PopupLink extends BaseComponent
+{
+ /** The default popup window name 'popuplink_window'. */
+ public static final String DEFAULT_WINDOW_NAME = "popuplink_window";
+ private static final URLCodec _urlCodec = new URLCodec();
+
+ // Instance variables
+ private IBinding _hrefBinding;
+ private IBinding _windowNameBinding;
+ private IBinding _featuresBinding;
+
+ public IBinding getHrefBinding()
+ {
+ return _hrefBinding;
+ }
+
+ public void setHrefBinding(IBinding hrefBinding)
+ {
+ _hrefBinding = hrefBinding;
+ }
+
+ public IBinding getWindowNameBinding()
+ {
+ return _windowNameBinding;
+ }
+
+ public void setWindowNameBinding(IBinding windowNameBinding)
+ {
+ _windowNameBinding = windowNameBinding;
+ }
+
+ public IBinding getFeaturesBinding()
+ {
+ return _featuresBinding;
+ }
+
+ public void setFeaturesBinding(IBinding featuresBinding)
+ {
+ _featuresBinding = featuresBinding;
+ }
+
+ public String getHref()
+ {
+ IBinding aHrefBinding = getHrefBinding();
+
+ if (aHrefBinding != null)
+ {
+ String encoding = getPage().getEngine().getOutputEncoding();
+ try
+ {
+ return _urlCodec.encode(aHrefBinding.getString(), encoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("illegal-encoding", encoding),
+ e);
+ }
+ }
+
+ return null;
+ }
+
+ public String getWindowName()
+ {
+ IBinding aWindowNameBinding = getWindowNameBinding();
+ if (aWindowNameBinding != null)
+ {
+ return aWindowNameBinding.getString();
+ }
+ else
+ {
+ return DEFAULT_WINDOW_NAME;
+ }
+ }
+
+ public String getFeatures()
+ {
+ IBinding aFeaturesBinding = getFeaturesBinding();
+ if (aFeaturesBinding != null)
+ {
+ return aFeaturesBinding.getString();
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+ public String getPopupFunctionName()
+ {
+ return getIdPath().replace('.', '_') + "_popup";
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.jwc
new file mode 100644
index 0000000..21e9fc4
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/PopupLink.jwc
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.popup.PopupLink" allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="href" type="String" direction="custom" required="yes"/>
+ <parameter name="windowName" type="String" direction="custom" required="no"/>
+ <parameter name="features" type="String" direction="custom" required="no"/>
+
+ <component id="popoutScript" type="Script">
+ <binding name="script" expression='"popup.script"'/>
+ <binding name="popupFunctionName" expression="popupFunctionName"/>
+ <binding name="url" expression="href"/>
+ <binding name="windowName" expression="windowName"/>
+ <binding name="features" expression="features"/>
+ </component>
+
+ <component id="link" type="Any" inherit-informal-parameters="yes">
+ <binding name="element" expression='"a"'/>
+ <binding name="href" expression='"javascript:" + popupFunctionName + "();"'/>
+ </component>
+
+ <component id="wrapped" type="RenderBody"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/popup/popup.script b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/popup.script
new file mode 100644
index 0000000..7035669
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/popup/popup.script
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Howard Ship//Tapestry Script 1.1//EN"
+ "http://tapestry.sf.net/dtd/Script_1_1.dtd">
+
+<script>
+
+<body>
+function ${popupFunctionName}()
+{
+ aWindow = window.open('${url}', '${windowName}', '${features}', false);
+ aWindow.focus();
+}
+</body>
+
+</script>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableRowComponent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableRowComponent.java
new file mode 100644
index 0000000..1501177
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableRowComponent.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableRowSource;
+
+/**
+ * The base implementation for a component that is wrapped by
+ * the TableRows component. Provides a utility method for getting
+ * a pointer to TableRows.
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public class AbstractTableRowComponent extends AbstractTableViewComponent
+{
+ public ITableRowSource getTableRowSource()
+ {
+ IRequestCycle objCycle = getPage().getRequestCycle();
+
+ Object objSourceObj = objCycle.getAttribute(ITableRowSource.TABLE_ROW_SOURCE_ATTRIBUTE);
+ ITableRowSource objSource = (ITableRowSource) objSourceObj;
+
+ if (objSource == null)
+ throw new ApplicationRuntimeException(
+ "The component "
+ + getId()
+ + " must be contained within an ITableRowSource component, such as TableRows",
+ this,
+ null,
+ null);
+
+ return objSource;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableViewComponent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableViewComponent.java
new file mode 100644
index 0000000..bd6456d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/AbstractTableViewComponent.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+
+/**
+ * The base implementation for a component that is wrapped by
+ * the TableView component. Provides a utility method for getting
+ * a pointer to TableView.
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public class AbstractTableViewComponent extends BaseComponent
+{
+ public ITableModelSource getTableModelSource()
+ {
+ IRequestCycle objCycle = getPage().getRequestCycle();
+
+ ITableModelSource objSource =
+ (ITableModelSource) objCycle.getAttribute(
+ ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE);
+
+ if (objSource == null)
+ throw new ApplicationRuntimeException(
+ "The component "
+ + getId()
+ + " must be contained within an ITableModelSource component, such as TableView",
+ this,
+ null,
+ null);
+
+ return objSource;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.html
new file mode 100644
index 0000000..a4bef19
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.html
@@ -0,0 +1,13 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="tableView">
+ <span jwcid="condPages"><span jwcid="tablePages"/></span>
+ <table jwcid="tableElement">
+ <tr><span jwcid="tableColumns"/></tr>
+ <tr jwcid="tableRows"><td jwcid="tableValues"/></tr>
+ </table>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.java
new file mode 100644
index 0000000..75dc316
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+
+/**
+ * A modified version of the facade component in the Table family.
+ * FormTable allows you to present a sortable and pagable table
+ * within a form by using only this one component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.FormTable.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class FormTable extends Table implements ITableModelSource
+{
+ // parameters
+ public abstract Object getColumns();
+
+ /**
+ * If the columns are defined via a String, make sure they use
+ * the form-specific column headers.
+ */
+ public Object getFormColumns()
+ {
+ Object objColumns = getColumns();
+ if (objColumns instanceof String)
+ objColumns = "*" + objColumns;
+ return objColumns;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.jwc
new file mode 100644
index 0000000..2003fb3
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/FormTable.jwc
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.FormTable"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ The main Table component that is implemented using the lower-level
+ Table components such as TableView and TableRows.
+ The component does not render its body, which makes it a good place
+ to declare Blocks defining the column appearances.
+ </description>
+
+ <parameter name="tableModel"
+ type="org.apache.tapestry.contrib.table.model.ITableModel"
+ required="no">
+ <description>
+ The model describing the data to be presented by the Table component.
+ This parameter is optional, but either the 'tableModel' or both
+ 'source' and 'columns' parameters must be provided.
+ </description>
+ </parameter>
+
+ <parameter name="source" type="java.lang.Object" required="no">
+ <description>
+ The data to be displayed by the component. This parameter is available as
+ an alternative to tableModel and must be used in combination with the
+ 'columns' parameter.
+ The parameter must be an array of values, a collection, an iterator,
+ or an object implementing the IBasicTableModel interface.
+ </description>
+ </parameter>
+
+ <parameter name="columns" type="java.lang.Object" required="no" direction="auto" default-value="null">
+ <description>
+ The table columns to be displayed.
+ The parameter must be an array, a list, or an Iterator of ITableColumn objects,
+ an ITableColumnModel, or a String describing the columns (see documentation).
+ </description>
+ </parameter>
+
+ <parameter name="pageSize"
+ type="int"
+ required="no">
+ <description>
+ The number of records displayed per page when source/columns are used.
+ The page size is 10 by default.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortColumn"
+ type="java.lang.String"
+ required="no">
+ <description>
+ The id of the column to initially sort the table by.
+ The column is set to null by default, i.e. there is no sorting.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortOrder"
+ type="boolean"
+ required="no">
+ <description>
+ The order of the initial sorting.
+ Set this parameter to 'false' to sort in an ascending order
+ and to 'true' to sort in a descending one.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStateManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStateManager"
+ required="no">
+ <description>
+ The manager that controls what part of the table model will be stored in
+ the session.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStoreManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStoreManager"
+ required="no">
+ <description>
+ The manager that controls where the session data will be stored.
+ </description>
+ </parameter>
+
+ <parameter name="columnSettingsContainer"
+ type="org.apache.tapestry.IComponent"
+ required="no"
+ default-value="container">
+ <description>
+ The component where Block and messages are pulled from when using source/columns.
+ </description>
+ </parameter>
+
+ <parameter name="convertor"
+ type="org.apache.tapestry.contrib.table.model.IPrimaryKeyConvertor"
+ required="no"
+ direction="auto"
+ default-value="null">
+ <description>
+ An interface defining how the items iterated upon by this component
+ will be stored in the form as Hidden values. This interface allows only
+ the primary key of the items to be stored, rather than the whole item.
+ </description>
+ </parameter>
+
+ <parameter name="pagesDisplayed"
+ type="int"
+ required="no">
+ <description>
+ The maximum number of pages that will be displayed in the list of table pages.
+ By default, only seven of the pages around the current one are shown.
+ </description>
+ </parameter>
+
+ <parameter name="column"
+ type="org.apache.tapestry.contrib.table.model.ITableColumn"
+ required="no">
+ <description>
+ The column that is being rendered. This value is updated when both
+ the column headers and column values are rendered.
+ </description>
+ </parameter>
+
+ <parameter name="row"
+ type="Object"
+ required="no"
+ direction="custom">
+ <description>
+ The row that is being rendered. This value is null when
+ the column headers are rendered.
+ </description>
+ </parameter>
+
+ <parameter name="arrowUpAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>
+ The image to use to describe a column sorted in an ascending order.
+ </description>
+ </parameter>
+
+ <parameter name="arrowDownAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>
+ The image to use to describe a column sorted in a descending order.
+ </description>
+ </parameter>
+
+ <parameter name="pagesClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table pages</description>
+ </parameter>
+
+ <parameter name="columnsClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table columns</description>
+ </parameter>
+
+ <parameter name="rowsClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table rows</description>
+ </parameter>
+
+ <parameter name="valuesClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table values</description>
+ </parameter>
+
+
+ <component id="tableElement" type="Any" inherit-informal-parameters="yes">
+ <static-binding name="element">table</static-binding>
+ </component>
+
+ <component id="condPages" type="FormConditional">
+ <binding name="condition" expression="tableModel.pageCount > 1"/>
+ </component>
+
+ <component id="tableView" type="TableView">
+ <inherited-binding name="tableModel" parameter-name="tableModel"/>
+ <inherited-binding name="source" parameter-name="source"/>
+ <binding name="columns" expression="formColumns"/>
+ <inherited-binding name="pageSize" parameter-name="pageSize"/>
+ <inherited-binding name="initialSortColumn" parameter-name="initialSortColumn"/>
+ <inherited-binding name="initialSortOrder" parameter-name="initialSortOrder"/>
+ <inherited-binding name="tableSessionStateManager" parameter-name="tableSessionStateManager"/>
+ <inherited-binding name="tableSessionStoreManager" parameter-name="tableSessionStoreManager"/>
+ <inherited-binding name="columnSettingsContainer" parameter-name="columnSettingsContainer"/>
+ <static-binding name="element">span</static-binding>
+ </component>
+
+ <component id="tablePages" type="TableFormPages">
+ <inherited-binding name="pagesDisplayed" parameter-name="pagesDisplayed"/>
+ <inherited-binding name="class" parameter-name="pagesClass"/>
+ </component>
+
+ <component id="tableColumns" type="TableColumns">
+ <inherited-binding name="column" parameter-name="column"/>
+ <inherited-binding name="class" parameter-name="columnsClass"/>
+ <inherited-binding name="arrowUpAsset" parameter-name="arrowUpAsset"/>
+ <inherited-binding name="arrowDownAsset" parameter-name="arrowDownAsset"/>
+ </component>
+
+ <component id="tableRows" type="TableFormRows">
+ <inherited-binding name="row" parameter-name="row"/>
+ <inherited-binding name="class" parameter-name="rowsClass"/>
+ <inherited-binding name="convertor" expression="convertor"/>
+ </component>
+
+ <component id="tableValues" type="TableValues">
+ <inherited-binding name="column" parameter-name="column"/>
+ <inherited-binding name="class" parameter-name="valuesClass"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.html
new file mode 100644
index 0000000..a4bef19
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.html
@@ -0,0 +1,13 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="tableView">
+ <span jwcid="condPages"><span jwcid="tablePages"/></span>
+ <table jwcid="tableElement">
+ <tr><span jwcid="tableColumns"/></tr>
+ <tr jwcid="tableRows"><td jwcid="tableValues"/></tr>
+ </table>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.java
new file mode 100644
index 0000000..c02b913
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.java
@@ -0,0 +1,111 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+
+/**
+ * The facade component in the Table family. Table allows you to present
+ * a sortable and pagable table simply and easily by using only this one component.
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.Table.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public class Table extends BaseComponent implements ITableModelSource
+{
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModelSource#getTableModel()
+ */
+ public ITableModel getTableModel()
+ {
+ return getTableViewComponent().getTableModel();
+ }
+
+ /**
+ * Indicates that the table model has changed and it may need to saved.
+ * This method has to be invoked if modifications are made to the model.
+ *
+ * @see org.apache.tapestry.contrib.table.model.ITableModelSource#fireObservedStateChange()
+ */
+ public void fireObservedStateChange()
+ {
+ getTableViewComponent().fireObservedStateChange();
+ }
+
+ /**
+ * Resets the state of the component and forces it to load a new
+ * TableModel from the tableModel binding the next time it renders.
+ */
+ public void reset()
+ {
+ getTableViewComponent().reset();
+ }
+
+ /**
+ * Returns the currently rendered table column.
+ * You can call this method to obtain the current column.
+ *
+ * @return ITableColumn the current table column
+ */
+ public ITableColumn getTableColumn()
+ {
+ Object objCurrentRow = getTableRow();
+
+ // if the current row is null, then we are most likely rendering TableColumns
+ if (objCurrentRow == null)
+ return getTableColumnsComponent().getTableColumn();
+ else
+ return getTableValuesComponent().getTableColumn();
+ }
+
+ /**
+ * Returns the currently rendered table row or null
+ * if the rows are not rendered at the moment.
+ * You can call this method to obtain the current row.
+ *
+ * @return Object the current table row
+ */
+ public Object getTableRow()
+ {
+ return getTableRowsComponent().getTableRow();
+ }
+
+ protected TableView getTableViewComponent()
+ {
+ return (TableView) getComponent("tableView");
+ }
+
+ protected TableColumns getTableColumnsComponent()
+ {
+ return (TableColumns) getComponent("tableColumns");
+ }
+
+ protected TableRows getTableRowsComponent()
+ {
+ return (TableRows) getComponent("tableRows");
+ }
+
+ protected TableValues getTableValuesComponent()
+ {
+ return (TableValues) getComponent("tableValues");
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.jwc
new file mode 100644
index 0000000..06b0360
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/Table.jwc
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.Table"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ The main Table component that is implemented using the lower-level
+ Table components such as TableView and TableRows.
+ The component does not render its body, which makes it a good place
+ to declare Blocks defining the column appearances.
+ </description>
+
+ <parameter name="tableModel"
+ type="org.apache.tapestry.contrib.table.model.ITableModel"
+ required="no">
+ <description>
+ The model describing the data to be presented by the Table component.
+ This parameter is optional, but either the 'tableModel' or both
+ 'source' and 'columns' parameters must be provided.
+ </description>
+ </parameter>
+
+ <parameter name="source" type="java.lang.Object" required="no">
+ <description>
+ The data to be displayed by the component. This parameter is available as
+ an alternative to tableModel and must be used in combination with the
+ 'columns' parameter.
+ The parameter must be an array of values, a collection, an iterator,
+ or an object implementing the IBasicTableModel interface.
+ </description>
+ </parameter>
+
+ <parameter name="columns" type="java.lang.Object" required="no">
+ <description>
+ The table columns to be displayed.
+ The parameter must be an array, a list, or an Iterator of ITableColumn objects,
+ an ITableColumnModel, or a String describing the columns (see documentation).
+ </description>
+ </parameter>
+
+ <parameter name="pageSize"
+ type="int"
+ required="no">
+ <description>
+ The number of records displayed per page when source/columns are used.
+ The page size is 10 by default.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortColumn"
+ type="java.lang.String"
+ required="no">
+ <description>
+ The id of the column to initially sort the table by.
+ The column is set to null by default, i.e. there is no sorting.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortOrder"
+ type="boolean"
+ required="no">
+ <description>
+ The order of the initial sorting.
+ Set this parameter to 'false' to sort in an ascending order
+ and to 'true' to sort in a descending one.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStateManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStateManager"
+ required="no">
+ <description>
+ The manager that controls what part of the table model will be stored in
+ the session.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStoreManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStoreManager"
+ required="no">
+ <description>
+ The manager that controls where the session data will be stored.
+ </description>
+ </parameter>
+
+ <parameter name="columnSettingsContainer"
+ type="org.apache.tapestry.IComponent"
+ required="no"
+ default-value="container">
+ <description>
+ The component where Block and messages are pulled from when using source/columns.
+ </description>
+ </parameter>
+
+ <parameter name="pagesDisplayed"
+ type="int"
+ required="no">
+ <description>
+ The maximum number of pages that will be displayed in the list of table pages.
+ By default, only seven of the pages around the current one are shown.
+ </description>
+ </parameter>
+
+ <parameter name="column"
+ type="org.apache.tapestry.contrib.table.model.ITableColumn"
+ required="no">
+ <description>
+ The column that is being rendered. This value is updated when both
+ the column headers and column values are rendered.
+ </description>
+ </parameter>
+
+ <parameter name="row"
+ type="Object"
+ required="no"
+ direction="custom">
+ <description>
+ The row that is being rendered. This value is null when
+ the column headers are rendered.
+ </description>
+ </parameter>
+
+ <parameter name="arrowUpAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>
+ The image to use to describe a column sorted in an ascending order.
+ </description>
+ </parameter>
+
+ <parameter name="arrowDownAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>
+ The image to use to describe a column sorted in a descending order.
+ </description>
+ </parameter>
+
+ <parameter name="pagesClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table pages</description>
+ </parameter>
+
+ <parameter name="columnsClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table columns</description>
+ </parameter>
+
+ <parameter name="rowsClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table rows</description>
+ </parameter>
+
+ <parameter name="valuesClass"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table values</description>
+ </parameter>
+
+
+ <component id="tableElement" type="Any" inherit-informal-parameters="yes">
+ <static-binding name="element">table</static-binding>
+ </component>
+
+ <component id="condPages" type="Conditional">
+ <binding name="condition" expression="tableModel.pageCount > 1"/>
+ </component>
+
+
+ <component id="tableView" type="TableView">
+ <inherited-binding name="tableModel" parameter-name="tableModel"/>
+ <inherited-binding name="source" parameter-name="source"/>
+ <inherited-binding name="columns" parameter-name="columns"/>
+ <inherited-binding name="pageSize" parameter-name="pageSize"/>
+ <inherited-binding name="initialSortColumn" parameter-name="initialSortColumn"/>
+ <inherited-binding name="initialSortOrder" parameter-name="initialSortOrder"/>
+ <inherited-binding name="tableSessionStateManager" parameter-name="tableSessionStateManager"/>
+ <inherited-binding name="tableSessionStoreManager" parameter-name="tableSessionStoreManager"/>
+ <inherited-binding name="columnSettingsContainer" parameter-name="columnSettingsContainer"/>
+ <static-binding name="element">span</static-binding>
+ </component>
+
+ <component id="tablePages" type="TablePages">
+ <inherited-binding name="pagesDisplayed" parameter-name="pagesDisplayed"/>
+ <inherited-binding name="class" parameter-name="pagesClass"/>
+ </component>
+
+ <component id="tableColumns" type="TableColumns">
+ <inherited-binding name="column" parameter-name="column"/>
+ <inherited-binding name="class" parameter-name="columnsClass"/>
+ <inherited-binding name="arrowUpAsset" parameter-name="arrowUpAsset"/>
+ <inherited-binding name="arrowDownAsset" parameter-name="arrowDownAsset"/>
+ </component>
+
+ <component id="tableRows" type="TableRows">
+ <inherited-binding name="row" parameter-name="row"/>
+ <inherited-binding name="class" parameter-name="rowsClass"/>
+ </component>
+
+ <component id="tableValues" type="TableValues">
+ <inherited-binding name="column" parameter-name="column"/>
+ <inherited-binding name="class" parameter-name="valuesClass"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.html
new file mode 100644
index 0000000..21360fd
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.html
@@ -0,0 +1,9 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="iterColumns">
+ <th jwcid="informal"><span jwcid="insertColumnRenderer"/></th>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.java
new file mode 100644
index 0000000..06c90e8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.java
@@ -0,0 +1,149 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+
+/**
+ * A low level Table component that renders the column headers in the table.
+ * This component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableView}.
+ * <p>
+ * The component iterates over all column objects in the
+ * {@link org.apache.tapestry.contrib.table.model.ITableColumnModel} and renders
+ * a header for each one of them using the renderer provided by the
+ * getColumnRender() method in {@link org.apache.tapestry.contrib.table.model.ITableColumn}.
+ * The headers are wrapped in 'th' tags by default.
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableColumns.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TableColumns extends AbstractTableViewComponent
+{
+ public static final String TABLE_COLUMN_ARROW_UP_ATTRIBUTE =
+ "org.apache.tapestry.contrib.table.components.TableColumns.arrowUp";
+
+ public static final String TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE =
+ "org.apache.tapestry.contrib.table.components.TableColumns.arrowDown";
+
+ public static final String TABLE_COLUMN_CSS_CLASS_SUFFIX = "ColumnHeader";
+
+ // Bindings
+ public abstract IBinding getColumnBinding();
+ public abstract IBinding getClassBinding();
+ public abstract IAsset getArrowDownAsset();
+ public abstract IAsset getArrowUpAsset();
+
+ // Transient
+ private ITableColumn m_objTableColumn = null;
+
+ /**
+ * Returns the currently rendered table column.
+ * You can call this method to obtain the current column.
+ *
+ * @return ITableColumn the current table column
+ */
+ public ITableColumn getTableColumn()
+ {
+ return m_objTableColumn;
+ }
+
+ /**
+ * Sets the currently rendered table column.
+ * This method is for internal use only.
+ *
+ * @param tableColumn The current table column
+ */
+ public void setTableColumn(ITableColumn tableColumn)
+ {
+ m_objTableColumn = tableColumn;
+
+ IBinding objColumnBinding = getColumnBinding();
+ if (objColumnBinding != null)
+ objColumnBinding.setObject(tableColumn);
+ }
+
+ /**
+ * Get the list of all table columns to be displayed.
+ *
+ * @return an iterator of all table columns
+ */
+ public Iterator getTableColumnIterator()
+ {
+ ITableColumnModel objColumnModel = getTableModelSource().getTableModel().getColumnModel();
+ return objColumnModel.getColumns();
+ }
+
+ /**
+ * Returns the renderer to be used to generate the header of the current column
+ *
+ * @return the header renderer of the current column
+ */
+ public IRender getTableColumnRenderer()
+ {
+ return getTableColumn().getColumnRenderer(
+ getPage().getRequestCycle(),
+ getTableModelSource());
+ }
+
+ /**
+ * Returns the CSS class of the generated table cell.
+ * It uses the class parameter if it has been bound, or
+ * the default value of "[column name]ColumnHeader" otherwise.
+ *
+ * @return the CSS class of the cell
+ */
+ public String getColumnClass()
+ {
+ IBinding objClassBinding = getClassBinding();
+ if (objClassBinding != null)
+ return objClassBinding.getString();
+ else
+ return getTableColumn().getColumnName() + TABLE_COLUMN_CSS_CLASS_SUFFIX;
+ }
+
+ /**
+ * @see org.apache.tapestry.BaseComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Object oldValueUp = cycle.getAttribute(TABLE_COLUMN_ARROW_UP_ATTRIBUTE);
+ Object oldValueDown = cycle.getAttribute(TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE);
+
+ cycle.setAttribute(TABLE_COLUMN_ARROW_UP_ATTRIBUTE, getArrowUpAsset());
+ cycle.setAttribute(TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE, getArrowDownAsset());
+
+ super.renderComponent(writer, cycle);
+
+ cycle.setAttribute(TABLE_COLUMN_ARROW_UP_ATTRIBUTE, oldValueUp);
+ cycle.setAttribute(TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE, oldValueDown);
+
+ // set the current column to null when the component is not active
+ m_objTableColumn = null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.jwc
new file mode 100644
index 0000000..37e5757
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableColumns.jwc
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableColumns"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ A low level Table component that renders the column headers in the table.
+ This component must be wrapped by TableView.
+ </description>
+
+ <parameter name="column"
+ type="org.apache.tapestry.contrib.table.model.ITableColumn"
+ required="no"
+ direction="custom">
+ <description>The column currently being rendered [out]</description>
+ </parameter>
+
+ <parameter name="element"
+ type="java.lang.String"
+ required="no"
+ direction="auto"
+ default-value="'th'">
+ <description>The tag to use to wrap the column headers.</description>
+ </parameter>
+
+ <parameter name="arrowUpAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>The image to use to describe a column sorted in an ascending order.</description>
+ </parameter>
+
+ <parameter name="arrowDownAsset"
+ type="org.apache.tapestry.IAsset"
+ required="no"
+ direction="in">
+ <description>The image to use to describe a column sorted in a descending order.</description>
+ </parameter>
+
+ <parameter name="class"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table columns</description>
+ </parameter>
+
+ <component id="iterColumns" type="Foreach">
+ <binding name="source" expression="tableColumnIterator"/>
+ <binding name="value" expression="tableColumn"/>
+ </component>
+
+ <component id="informal" type="Any" inherit-informal-parameters="yes">
+ <binding name="element" expression="element"/>
+ <binding name="class" expression="columnClass"/>
+ </component>
+
+ <component id="insertColumnRenderer" type="Delegator">
+ <binding name="delegate" expression="tableColumnRenderer"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.html
new file mode 100644
index 0000000..1d98cd8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.html
@@ -0,0 +1,32 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="informal">
+
+ <span jwcid="hiddenCurrentPage"/>
+ <span jwcid="hiddenPageCount"/>
+ <span jwcid="hiddenStartPage"/>
+ <span jwcid="hiddenStopPage"/>
+
+ <a jwcid="linkFirst"><<</a>
+ <a jwcid="linkBack"><</a>
+
+ <span jwcid="iterPage">
+
+ <span jwcid="condCurrent">
+ <b><span jwcid="insertCurrentPage"/></b>
+ </span>
+
+ <span jwcid="condOther">
+ <a jwcid="linkPage"><span jwcid="insertOtherPage"/></a>
+ </span>
+
+ </span>
+
+ <a jwcid="linkFwd">></a>
+ <a jwcid="linkLast">>></a>
+
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.java
new file mode 100644
index 0000000..2043799
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.java
@@ -0,0 +1,173 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+
+/**
+ * A low level Table component that renders the pages in the table.
+ *
+ * This component is a variant of {@link org.apache.tapestry.contrib.table.components.TablePages},
+ * but is designed for operation in a form. The necessary page data is stored
+ * in hidden fields, so that no StaleLink exceptions occur during a rewind.
+ * The links also submit the form, which ensures that the data in the other
+ * form fields is preserved even when the page chages.
+ *
+ * The component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableView}.
+ * <p>
+ * The component generates a list of pages in the Table centered around the
+ * current one and allows you to navigate to other pages.
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableFormPages.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TableFormPages extends TablePages
+ implements PageDetachListener, PageRenderListener
+{
+ private int m_nCurrentPage;
+ private int m_nPageCount;
+ private int m_nStartPage;
+ private int m_nStopPage;
+
+ public TableFormPages()
+ {
+ initialize();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageDetachListener#pageDetached(org.apache.tapestry.event.PageEvent)
+ */
+ public void pageDetached(PageEvent event)
+ {
+ initialize();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)
+ */
+ public void pageBeginRender(PageEvent event)
+ {
+ // values set during rewind are removed
+ initialize();
+ }
+
+ /**
+ * Initialize the values and return the object to operation identical
+ * to that of the super class.
+ */
+ private void initialize()
+ {
+ m_nCurrentPage = -1;
+ m_nPageCount = -1;
+ m_nStartPage = -1;
+ m_nStopPage = -1;
+ }
+
+ // This would ideally be a delayed invocation -- called after the form rewind
+ public void changePage(IRequestCycle objCycle)
+ {
+ ITableModelSource objSource = getTableModelSource();
+ setCurrentPage(objSource, getSelectedPage());
+
+ // ensure that the change is saved
+ objSource.fireObservedStateChange();
+ }
+
+ // defined in the JWC file
+ public abstract int getSelectedPage();
+
+
+ /**
+ * @return the current page
+ */
+ public int getCurrentPage()
+ {
+ if (m_nCurrentPage < 0)
+ m_nCurrentPage = super.getCurrentPage();
+ return m_nCurrentPage;
+ }
+
+ /**
+ * @return number of all pages to display
+ */
+ public int getPageCount()
+ {
+ if (m_nPageCount < 0)
+ m_nPageCount = super.getPageCount();
+ return m_nPageCount;
+ }
+
+ /**
+ * @return the first page to display
+ */
+ public int getStartPage()
+ {
+ if (m_nStartPage < 0)
+ m_nStartPage = super.getStartPage();
+ return m_nStartPage;
+ }
+
+ /**
+ * @return the last page to display
+ */
+ public int getStopPage()
+ {
+ if (m_nStopPage < 0)
+ m_nStopPage = super.getStopPage();
+ return m_nStopPage;
+ }
+
+ /**
+ * @param i the current page
+ */
+ public void setCurrentPage(int i)
+ {
+ m_nCurrentPage = i;
+ }
+
+ /**
+ * @param i number of all pages to display
+ */
+ public void setPageCount(int i)
+ {
+ m_nPageCount = i;
+ }
+
+ /**
+ * @param i the first page to display
+ */
+ public void setStartPage(int i)
+ {
+ m_nStartPage = i;
+ }
+
+ /**
+ * @param i the last page to display
+ */
+ public void setStopPage(int i)
+ {
+ m_nStopPage = i;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.jwc
new file mode 100644
index 0000000..a4bfe7d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormPages.jwc
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://tapestry.apache.org/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableFormPages"
+ allow-body="no" allow-informal-parameters="yes">
+
+ <description>
+ A version of TablePages that is designed for operation in a form.
+ It is a low level Table component that renders the pages in the table.
+ This component must be wrapped by TableView.
+ </description>
+
+ <parameter name="pagesDisplayed"
+ type="int"
+ required="no"
+ direction="auto"
+ default-value="7">
+ <description>
+ Determines the maximum number of pages to be displayed in the page list
+ when the table has more than one page.
+ </description>
+ </parameter>
+
+ <property-specification name="selectedPage" type="int"/>
+
+ <component id="informal" type="Any" inherit-informal-parameters="yes"/>
+
+ <component id="hiddenCurrentPage" type="Hidden">
+ <binding name="value" expression="currentPage"/>
+ </component>
+
+ <component id="hiddenPageCount" type="Hidden">
+ <binding name="value" expression="pageCount"/>
+ </component>
+
+ <component id="hiddenStartPage" type="Hidden">
+ <binding name="value" expression="startPage"/>
+ </component>
+
+ <component id="hiddenStopPage" type="Hidden">
+ <binding name="value" expression="stopPage"/>
+ </component>
+
+ <component id="condCurrent" type="Conditional">
+ <binding name="condition" expression="condCurrent"/>
+ </component>
+
+ <component id="condOther" type="Conditional">
+ <binding name="condition" expression="!condCurrent"/>
+ </component>
+
+ <component id="iterPage" type="Foreach">
+ <binding name="source" expression="pageList"/>
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="insertCurrentPage" type="Insert">
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="insertOtherPage" type="Insert">
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="linkPage" type="LinkSubmit">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="tag" expression="displayPage"/>
+ <binding name="selected" expression="selectedPage"/>
+ </component>
+
+ <component id="linkFirst" type="LinkSubmit">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="tag" expression="1"/>
+ <binding name="selected" expression="selectedPage"/>
+ <binding name="disabled" expression="!condBack"/>
+ </component>
+
+ <component id="linkBack" type="LinkSubmit">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="tag" expression="currentPage - 1"/>
+ <binding name="selected" expression="selectedPage"/>
+ <binding name="disabled" expression="!condBack"/>
+ </component>
+
+ <component id="linkFwd" type="LinkSubmit">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="tag" expression="currentPage + 1"/>
+ <binding name="selected" expression="selectedPage"/>
+ <binding name="disabled" expression="!condFwd"/>
+ </component>
+
+ <component id="linkLast" type="LinkSubmit">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="tag" expression="pageCount"/>
+ <binding name="selected" expression="selectedPage"/>
+ <binding name="disabled" expression="!condFwd"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.html
new file mode 100644
index 0000000..53b492a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.html
@@ -0,0 +1,9 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<tr jwcid="iterRows">
+ <span jwcid="@RenderBody"/>
+</tr>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.java
new file mode 100644
index 0000000..f33dae9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.java
@@ -0,0 +1,134 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.*;
+
+
+/**
+ * A low level Table component that generates the rows of the current page in the table.
+ *
+ * This component is a variant of {@link org.apache.tapestry.contrib.table.components.TablePages},
+ * but is designed for operation in a form. The displayed rows are stored in
+ * hidden form fields, which are then read during a rewind. This ensures that
+ * the form will rewind in exactly the same was as it was rendered even if the
+ * TableModel has changed and no StaleLink exceptions will occur.
+ *
+ * The component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableView}.
+ *
+ * <p>
+ * The component iterates over the rows of the current page in the table.
+ * The rows are wrapped in 'tr' tags by default.
+ * You can define columns manually within, or
+ * you can use {@link org.apache.tapestry.contrib.table.components.TableValues}
+ * to generate the columns automatically.
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableFormRows.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TableFormRows extends TableRows
+{
+ public abstract IPrimaryKeyConvertor getConvertor();
+ public abstract IPrimaryKeyConvertor getConvertorCache();
+ public abstract void setConvertorCache(IPrimaryKeyConvertor convertor);
+ public abstract Map getConvertedValues();
+
+ /**
+ * Returns the PK convertor cached within the realm of the current request cycle.
+ *
+ * @return the cached PK convertor
+ */
+ public IPrimaryKeyConvertor getCachedConvertor()
+ {
+ IPrimaryKeyConvertor objConvertor = getConvertorCache();
+
+ if (objConvertor == null) {
+ objConvertor = getConvertor();
+ setConvertorCache(objConvertor);
+ }
+
+ return objConvertor;
+ }
+
+ /**
+ * Get the list of all table rows to be displayed on this page, converted
+ * using the PK.convertor.
+ *
+ * @return an iterator of all converted table rows
+ */
+ public Iterator getConvertedTableRowsIterator()
+ {
+ final Iterator objTableRowsIterator = getTableRowsIterator();
+ final IPrimaryKeyConvertor objConvertor = getCachedConvertor();
+ if (objConvertor == null)
+ return objTableRowsIterator;
+
+ return new Iterator()
+ {
+ public boolean hasNext()
+ {
+ return objTableRowsIterator.hasNext();
+ }
+
+ public Object next()
+ {
+ Object objValue = objTableRowsIterator.next();
+ Object objPrimaryKey = objConvertor.getPrimaryKey(objValue);
+ Map mapConvertedValues = getConvertedValues();
+ mapConvertedValues.put(objPrimaryKey, objValue);
+ return objPrimaryKey;
+ }
+
+ public void remove()
+ {
+ objTableRowsIterator.remove();
+ }
+ };
+ }
+
+ /**
+ * Sets the current table row PK and invokes {@link #setTableRow(Object)} as a result.
+ * This method is for internal use only.
+ *
+ * @param objConvertedTableRow The current converted table row (PK)
+ */
+ public void setConvertedTableRow(Object objConvertedTableRow)
+ {
+ Object objValue = objConvertedTableRow;
+
+ IPrimaryKeyConvertor objConvertor = getCachedConvertor();
+ if (objConvertor != null) {
+ IRequestCycle objCycle = getPage().getRequestCycle();
+ if (objCycle.isRewinding()) {
+ objValue = objConvertor.getValue(objConvertedTableRow);
+ }
+ else {
+ Map mapConvertedValues = getConvertedValues();
+ objValue = mapConvertedValues.get(objConvertedTableRow);
+ }
+ }
+
+ setTableRow(objValue);
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.jwc
new file mode 100644
index 0000000..30d5992
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableFormRows.jwc
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableFormRows"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ A version of the TableRows designed for operation in a form.
+ This is a low level Table component that generates the rows of
+ the current page in the table. Each row is stored as a hidden value
+ in the form, which eliminates the chance of a stale link during rewinding.
+ This component must be wrapped by TableView.
+ </description>
+
+ <parameter name="row"
+ type="Object"
+ required="no"
+ direction="custom">
+ <description>The value object of the row currently being rendered.</description>
+ </parameter>
+
+ <parameter name="convertor"
+ type="org.apache.tapestry.contrib.table.model.IPrimaryKeyConvertor"
+ required="no"
+ direction="auto"
+ default-value="null">
+ <description>
+ An interface defining how the items iterated upon by this component
+ will be stored in the form as Hidden values. This interface allows only
+ the primary key of the items to be stored, rather than the whole item.
+ </description>
+ </parameter>
+
+ <parameter name="element"
+ type="java.lang.String"
+ required="no"
+ default-value='"tr"'>
+ <description>The tag to use to wrap the rows in, 'tr' by default.</description>
+ </parameter>
+
+ <component id="iterRows" type="ListEdit" inherit-informal-parameters="yes">
+ <binding name="source" expression="convertedTableRowsIterator"/>
+ <binding name="value" expression="convertedTableRow"/>
+ <inherited-binding name="element" parameter-name="element"/>
+ </component>
+
+ <property-specification name="convertedValues" type="java.util.Map" initial-value="new java.util.HashMap()"/>
+ <property-specification name="convertorCache" type="org.apache.tapestry.contrib.table.model.IPrimaryKeyConvertor" initial-value="null"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.html
new file mode 100644
index 0000000..ecf98ec
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.html
@@ -0,0 +1,27 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="informal">
+
+ <a jwcid="linkFirst"><<</a>
+ <a jwcid="linkBack"><</a>
+
+ <span jwcid="iterPage">
+
+ <span jwcid="condCurrent">
+ <b><span jwcid="insertCurrentPage"/></b>
+ </span>
+
+ <span jwcid="condOther">
+ <a jwcid="linkPage"><span jwcid="insertOtherPage"/></a>
+ </span>
+
+ </span>
+
+ <a jwcid="linkFwd">></a>
+ <a jwcid="linkLast">>></a>
+
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.java
new file mode 100644
index 0000000..6aee51f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.java
@@ -0,0 +1,195 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * A low level Table component that renders the pages in the table.
+ * This component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableView}.
+ * <p>
+ * The component generates a list of pages in the Table centered around the
+ * current one and allows you to navigate to other pages.
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TablePages.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TablePages extends AbstractTableViewComponent
+{
+ // Bindings
+ public abstract int getPagesDisplayed();
+
+ // Transient
+ private int m_nDisplayPage;
+
+ /**
+ * Returns the displayPage.
+ * @return int
+ */
+ public int getDisplayPage()
+ {
+ return m_nDisplayPage;
+ }
+
+ /**
+ * Sets the displayPage.
+ * @param displayPage The displayPage to set
+ */
+ public void setDisplayPage(int displayPage)
+ {
+ m_nDisplayPage = displayPage;
+ }
+
+ public int getCurrentPage()
+ {
+ return getTableModelSource().getTableModel().getPagingState().getCurrentPage() + 1;
+ }
+
+ public int getPageCount()
+ {
+ return getTableModelSource().getTableModel().getPageCount();
+ }
+
+ public boolean getCondBack()
+ {
+ return getCurrentPage() > 1;
+ }
+
+ public boolean getCondFwd()
+ {
+ return getCurrentPage() < getPageCount();
+ }
+
+ public boolean getCondCurrent()
+ {
+ return getDisplayPage() == getCurrentPage();
+ }
+
+ public int getStartPage()
+ {
+ int nCurrent = getCurrentPage();
+ int nPagesDisplayed = getPagesDisplayed();
+
+ int nRightMargin = nPagesDisplayed / 2;
+ int nStop = nCurrent + nRightMargin;
+ int nLastPage = getPageCount();
+
+ int nLeftAddon = 0;
+ if (nStop > nLastPage)
+ nLeftAddon = nStop - nLastPage;
+
+ int nLeftMargin = (nPagesDisplayed - 1) / 2 + nLeftAddon;
+ int nStart = nCurrent - nLeftMargin;
+ int nFirstPage = 1;
+ if (nStart < nFirstPage)
+ nStart = nFirstPage;
+ return nStart;
+ }
+
+ public int getStopPage()
+ {
+ int nCurrent = getCurrentPage();
+ int nPagesDisplayed = getPagesDisplayed();
+
+ int nLeftMargin = (nPagesDisplayed - 1) / 2;
+ int nStart = nCurrent - nLeftMargin;
+ int nFirstPage = 1;
+
+ int nRightAddon = 0;
+ if (nStart < nFirstPage)
+ nRightAddon = nFirstPage - nStart;
+
+ int nRightMargin = nPagesDisplayed / 2 + nRightAddon;
+ int nStop = nCurrent + nRightMargin;
+ int nLastPage = getPageCount();
+ if (nStop > nLastPage)
+ nStop = nLastPage;
+ return nStop;
+ }
+
+ public Integer[] getPageList()
+ {
+ int nStart = getStartPage();
+ int nStop = getStopPage();
+
+ Integer[] arrPages = new Integer[nStop - nStart + 1];
+ for (int i = nStart; i <= nStop; i++)
+ arrPages[i - nStart] = new Integer(i);
+
+ return arrPages;
+ }
+
+ public Object[] getFirstPageContext()
+ {
+ ComponentAddress objAddress = new ComponentAddress(getTableModelSource());
+ return new Object[] { objAddress, new Integer(1)};
+ }
+
+ public Object[] getLastPageContext()
+ {
+ ComponentAddress objAddress = new ComponentAddress(getTableModelSource());
+ return new Object[] { objAddress, new Integer(getPageCount())};
+ }
+
+ public Object[] getBackPageContext()
+ {
+ ComponentAddress objAddress = new ComponentAddress(getTableModelSource());
+ return new Object[] { objAddress, new Integer(getCurrentPage() - 1)};
+ }
+
+ public Object[] getFwdPageContext()
+ {
+ ComponentAddress objAddress = new ComponentAddress(getTableModelSource());
+ return new Object[] { objAddress, new Integer(getCurrentPage() + 1)};
+ }
+
+ public Object[] getDisplayPageContext()
+ {
+ ComponentAddress objAddress = new ComponentAddress(getTableModelSource());
+ return new Object[] { objAddress, new Integer(m_nDisplayPage)};
+ }
+
+ public void changePage(IRequestCycle objCycle)
+ {
+ Object[] arrParameters = objCycle.getServiceParameters();
+ if (arrParameters.length != 2
+ && !(arrParameters[0] instanceof ComponentAddress)
+ && !(arrParameters[1] instanceof Integer))
+ {
+ // error
+ return;
+ }
+
+ ComponentAddress objAddress = (ComponentAddress) arrParameters[0];
+ ITableModelSource objSource = (ITableModelSource) objAddress.findComponent(objCycle);
+ setCurrentPage(objSource, ((Integer) arrParameters[1]).intValue());
+
+ // ensure that the change is saved
+ objSource.fireObservedStateChange();
+ }
+
+ public void setCurrentPage(ITableModelSource objSource, int nPage)
+ {
+ objSource.getTableModel().getPagingState().setCurrentPage(nPage - 1);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.jwc
new file mode 100644
index 0000000..98640de
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TablePages.jwc
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TablePages"
+ allow-body="no" allow-informal-parameters="yes">
+
+ <description>
+ A low level Table component that renders the pages in the table.
+ This component must be wrapped by TableView.
+ </description>
+
+ <parameter name="pagesDisplayed"
+ type="int"
+ required="no"
+ direction="auto"
+ default-value="7">
+ <description>
+ Determines the maximum number of pages to be displayed in the page list
+ when the table has more than one page.
+ </description>
+ </parameter>
+
+ <component id="informal" type="Any" inherit-informal-parameters="yes"/>
+
+ <component id="condCurrent" type="Conditional">
+ <binding name="condition" expression="condCurrent"/>
+ </component>
+
+ <component id="condOther" type="Conditional">
+ <binding name="condition" expression="condCurrent"/>
+ <static-binding name="invert">true</static-binding>
+ </component>
+
+ <component id="iterPage" type="Foreach">
+ <binding name="source" expression="pageList"/>
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="insertCurrentPage" type="Insert">
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="insertOtherPage" type="Insert">
+ <binding name="value" expression="displayPage"/>
+ </component>
+
+ <component id="linkPage" type="DirectLink">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="parameters" expression="displayPageContext"/>
+ </component>
+
+ <component id="linkFirst" type="DirectLink">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="parameters" expression="firstPageContext"/>
+ <binding name="disabled" expression="!condBack"/>
+ </component>
+
+ <component id="linkBack" type="DirectLink">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="parameters" expression="backPageContext"/>
+ <binding name="disabled" expression="!condBack"/>
+ </component>
+
+ <component id="linkFwd" type="DirectLink">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="parameters" expression="fwdPageContext"/>
+ <binding name="disabled" expression="!condFwd"/>
+ </component>
+
+ <component id="linkLast" type="DirectLink">
+ <binding name="listener" expression="listeners.changePage"/>
+ <binding name="parameters" expression="lastPageContext"/>
+ <binding name="disabled" expression="!condFwd"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.html
new file mode 100644
index 0000000..8a73f34
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.html
@@ -0,0 +1,11 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="iterRows">
+ <tr jwcid="informal">
+ <span jwcid="wrapped"/>
+ </tr>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.java
new file mode 100644
index 0000000..b96b427
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.java
@@ -0,0 +1,106 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableRowSource;
+
+/**
+ * A low level Table component that generates the rows of the current page in the table.
+ * This component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableView}.
+ *
+ * <p>
+ * The component iterates over the rows of the current page in the table.
+ * The rows are wrapped in 'tr' tags by default.
+ * You can define columns manually within, or
+ * you can use {@link org.apache.tapestry.contrib.table.components.TableValues}
+ * to generate the columns automatically.
+ *
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableRows.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TableRows extends AbstractTableViewComponent implements ITableRowSource
+{
+ // Parameters
+ public abstract IBinding getRowBinding();
+
+ // Transient
+ private Object m_objTableRow = null;
+
+ /**
+ * Returns the currently rendered table row.
+ * You can call this method to obtain the current row.
+ *
+ * @return Object the current table row
+ */
+ public Object getTableRow()
+ {
+ return m_objTableRow;
+ }
+
+ /**
+ * Sets the currently rendered table row.
+ * This method is for internal use only.
+ *
+ * @param tableRow The current table row
+ */
+ public void setTableRow(Object tableRow)
+ {
+ m_objTableRow = tableRow;
+
+ IBinding objRowBinding = getRowBinding();
+ if (objRowBinding != null)
+ objRowBinding.setObject(tableRow);
+ }
+
+ /**
+ * Get the list of all table rows to be displayed on this page.
+ *
+ * @return an iterator of all table rows
+ */
+ public Iterator getTableRowsIterator()
+ {
+ ITableModel objTableModel = getTableModelSource().getTableModel();
+ return objTableModel.getCurrentPageRows();
+ }
+
+ /**
+ * @see org.apache.tapestry.BaseComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Object objOldValue = cycle.getAttribute(ITableRowSource.TABLE_ROW_SOURCE_ATTRIBUTE);
+ cycle.setAttribute(ITableRowSource.TABLE_ROW_SOURCE_ATTRIBUTE, this);
+
+ super.renderComponent(writer, cycle);
+
+ cycle.setAttribute(ITableRowSource.TABLE_ROW_SOURCE_ATTRIBUTE, objOldValue);
+
+ // set the current row to null when the component is not active
+ m_objTableRow = null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.jwc
new file mode 100644
index 0000000..0220ff7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableRows.jwc
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableRows"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ A low level Table component that generates the rows of the current page in the table.
+ This component must be wrapped by TableView.
+ </description>
+
+ <parameter name="row"
+ type="Object"
+ required="no"
+ direction="custom">
+ <description>The current row being rendered.</description>
+ </parameter>
+
+ <parameter name="element"
+ type="java.lang.String"
+ required="no"
+ direction="auto"
+ default-value="'tr'">
+ <description>The tag to use to wrap the rows in, 'tr' by default.</description>
+ </parameter>
+
+ <component id="iterRows" type="Foreach">
+ <binding name="source" expression="tableRowsIterator"/>
+ <binding name="value" expression="tableRow"/>
+ </component>
+
+ <component id="informal" type="Any" inherit-informal-parameters="yes">
+ <inherited-binding name="element" parameter-name="element"/>
+ </component>
+
+ <component id="wrapped" type="RenderBody"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableStrings.properties b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableStrings.properties
new file mode 100644
index 0000000..71b81e5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableStrings.properties
@@ -0,0 +1,7 @@
+# $Id$
+
+missing-table-model=Either the 'tableModel' parameter or both 'source' and 'columns' parameters must be specified by component {0}
+columns-only-please=The 'columns' parameter of component {0} must contain a list of ITableColumn objects only
+not-a-column=The expression '{1}' in the 'columns' parameter of component {0} does not evaluate to an ITableColumn
+invalid-table-source=The source parameter of component {0} is of type {1}, but must be of type Object[], Collection, Iterator, or IBasicTableModel
+invalid-table-columns=The columns parameter of component {0} is of type {1}, but must be of type String, ITableColumnModel, ITableColumn[], List, or Iterator
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableUtils.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableUtils.java
new file mode 100644
index 0000000..c7b5bb7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableUtils.java
@@ -0,0 +1,191 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.ognl.ExpressionTableColumn;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * A placeholder for a static methods related to the Table component
+ *
+ * @since 3.0
+ * @version $Id$
+ * @author Mindbridge
+ **/
+public class TableUtils
+{
+
+ /**
+ * Contains strings loaded from TableStrings.properties.
+ *
+ **/
+
+ private static ResourceBundle s_objStrings = null;
+
+ /**
+ * Gets a string from the TableStrings resource bundle.
+ *
+ **/
+
+ public static String format(String key, Object[] args)
+ {
+ synchronized (TableUtils.class) {
+ if (s_objStrings == null)
+ s_objStrings = ResourceBundle.getBundle("org.apache.tapestry.contrib.table.components.TableStrings");
+ }
+
+ String pattern = s_objStrings.getString(key);
+
+ if (args == null)
+ return pattern;
+
+ return MessageFormat.format(pattern, args);
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ **/
+
+ public static String getMessage(String key)
+ {
+ return format(key, null);
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ **/
+
+ public static String format(String key, Object arg)
+ {
+ return format(key, new Object[] { arg });
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ **/
+
+ public static String format(String key, Object arg1, Object arg2)
+ {
+ return format(key, new Object[] { arg1, arg2 });
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ **/
+
+ public static String format(String key, Object arg1, Object arg2, Object arg3)
+ {
+ return format(key, new Object[] { arg1, arg2, arg3 });
+ }
+
+ /**
+ * Generate a table column model out of the description string provided.
+ * Entries in the description string are separated by commas.
+ * Each column entry is of the format name, name:expression,
+ * or name:displayName:expression.
+ * An entry prefixed with ! represents a non-sortable column.
+ * If the whole description string is prefixed with *, it represents
+ * columns to be included in a Form.
+ *
+ * @param strDesc the description of the column model to be generated
+ * @param objComponent the component ordering the generation
+ * @param objColumnSettingsContainer the component containing the column settings
+ * @return a table column model based on the provided parameters
+ */
+ public static ITableColumnModel generateTableColumnModel(String strDesc, IComponent objComponent, IComponent objColumnSettingsContainer)
+ {
+ if (strDesc == null)
+ return null;
+
+ List arrColumns = new ArrayList();
+
+ boolean bFormColumns = false;
+ while (strDesc.startsWith("*"))
+ {
+ strDesc = strDesc.substring(1);
+ bFormColumns = true;
+ }
+
+ StringTokenizer objTokenizer = new StringTokenizer(strDesc, ",");
+ while (objTokenizer.hasMoreTokens())
+ {
+ String strToken = objTokenizer.nextToken().trim();
+
+ if (strToken.startsWith("="))
+ {
+ String strColumnExpression = strToken.substring(1);
+ IResourceResolver objResolver = objColumnSettingsContainer.getPage().getEngine().getResourceResolver();
+
+ Object objColumn =
+ OgnlUtils.get(strColumnExpression, objResolver, objColumnSettingsContainer);
+ if (!(objColumn instanceof ITableColumn))
+ throw new ApplicationRuntimeException(
+ format("not-a-column", objComponent.getExtendedId(), strColumnExpression));
+
+ arrColumns.add(objColumn);
+ continue;
+ }
+
+ boolean bSortable = true;
+ if (strToken.startsWith("!"))
+ {
+ strToken = strToken.substring(1);
+ bSortable = false;
+ }
+
+ StringTokenizer objColumnTokenizer = new StringTokenizer(strToken, ":");
+
+ String strName = "";
+ if (objColumnTokenizer.hasMoreTokens())
+ strName = objColumnTokenizer.nextToken();
+
+ String strExpression = strName;
+ if (objColumnTokenizer.hasMoreTokens())
+ strExpression = objColumnTokenizer.nextToken();
+
+ String strDisplayName = strName;
+ if (objColumnTokenizer.hasMoreTokens())
+ {
+ strDisplayName = strExpression;
+ strExpression = objColumnTokenizer.nextToken();
+ }
+
+ ExpressionTableColumn objColumn =
+ new ExpressionTableColumn(strName, strDisplayName, strExpression, bSortable);
+ if (bFormColumns)
+ objColumn.setColumnRendererSource(SimpleTableColumn.FORM_COLUMN_RENDERER_SOURCE);
+ if (objColumnSettingsContainer != null)
+ objColumn.loadSettings(objColumnSettingsContainer);
+
+ arrColumns.add(objColumn);
+ }
+
+ return new SimpleTableColumnModel(arrColumns);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.html
new file mode 100644
index 0000000..29e6927
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.html
@@ -0,0 +1,9 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="iterColumns">
+ <td jwcid="informal"><span jwcid="insertValueRenderer"/></td>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.java
new file mode 100644
index 0000000..a316d31
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.java
@@ -0,0 +1,136 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+
+/**
+ * A low level Table component that generates the columns in the current row in the table.
+ * This component must be wrapped by {@link org.apache.tapestry.contrib.table.components.TableRows}.
+ *
+ * <p>
+ * The component iterates over the columns in the table and
+ * automatically renders the column values for the current table row.
+ * The columns are wrapped in 'td' tags by default. <br>
+ * The column values are rendered using the renderer returned by the
+ * getValueRenderer() method in {@link org.apache.tapestry.contrib.table.model.ITableColumn}.
+ *
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableValues.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ *
+ */
+public abstract class TableValues extends AbstractTableRowComponent
+{
+ public static final String TABLE_VALUE_CSS_CLASS_SUFFIX = "ColumnValue";
+
+ // Bindings
+ public abstract IBinding getColumnBinding();
+ public abstract IBinding getClassBinding();
+
+ // Transient
+ private ITableColumn m_objTableColumn;
+
+ /**
+ * Get the list of all table columns to be displayed.
+ *
+ * @return an iterator of all table columns
+ */
+ public Iterator getTableColumnIterator()
+ {
+ ITableColumnModel objColumnModel =
+ getTableModelSource().getTableModel().getColumnModel();
+ return objColumnModel.getColumns();
+ }
+
+ /**
+ * Returns the currently rendered table column.
+ * You can call this method to obtain the current column.
+ *
+ * @return ITableColumn the current table column
+ */
+ public ITableColumn getTableColumn()
+ {
+ return m_objTableColumn;
+ }
+
+ /**
+ * Sets the currently rendered table column.
+ * This method is for internal use only.
+ *
+ * @param tableColumn The current table column
+ */
+ public void setTableColumn(ITableColumn tableColumn)
+ {
+ m_objTableColumn = tableColumn;
+
+ IBinding objColumnBinding = getColumnBinding();
+ if (objColumnBinding != null)
+ objColumnBinding.setObject(tableColumn);
+ }
+
+ /**
+ * Returns the renderer to be used to generate the appearance of the current column
+ *
+ * @return the value renderer of the current column
+ */
+ public IRender getTableValueRenderer()
+ {
+ Object objRow = getTableRowSource().getTableRow();
+ return getTableColumn().getValueRenderer(
+ getPage().getRequestCycle(),
+ getTableModelSource(),
+ objRow);
+ }
+
+ /**
+ * Returns the CSS class of the generated table cell.
+ * It uses the class parameter if it has been bound, or
+ * the default value of "[column name]ColumnValue" otherwise.
+ *
+ * @return the CSS class of the cell
+ */
+ public String getValueClass()
+ {
+ IBinding objClassBinding = getClassBinding();
+ if (objClassBinding != null)
+ return objClassBinding.getString();
+ else
+ return getTableColumn().getColumnName() + TABLE_VALUE_CSS_CLASS_SUFFIX;
+ }
+
+ /**
+ * @see org.apache.tapestry.BaseComponent#renderComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ super.renderComponent(writer, cycle);
+
+ // set the current column to null when the component is not active
+ m_objTableColumn = null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.jwc
new file mode 100644
index 0000000..66c4702
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableValues.jwc
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableValues"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ A low level Table component that generates the columns for the current row in the table.
+ This component must be wrapped by TableRows.
+ </description>
+
+ <parameter name="column"
+ type="org.apache.tapestry.contrib.table.model.ITableColumn"
+ required="no"
+ direction="custom">
+ <description>The current column being rendered</description>
+ </parameter>
+
+ <parameter name="element"
+ type="java.lang.String"
+ required="no"
+ direction="auto"
+ default-value="'td'">
+ <description>The tag to use to wrap the values in, 'td' by default.</description>
+ </parameter>
+
+ <parameter name="class"
+ type="java.lang.String"
+ required="no"
+ direction="custom">
+ <description>The CSS class of the table values</description>
+ </parameter>
+
+ <component id="iterColumns" type="Foreach">
+ <binding name="source" expression="tableColumnIterator"/>
+ <binding name="value" expression="tableColumn"/>
+ </component>
+
+ <component id="informal" type="Any" inherit-informal-parameters="yes">
+ <inherited-binding name="element" parameter-name="element"/>
+ <binding name="class" expression="valueClass"/>
+ </component>
+
+ <component id="insertValueRenderer" type="Delegator">
+ <binding name="delegate" expression="tableValueRenderer"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.html
new file mode 100644
index 0000000..331d131
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.html
@@ -0,0 +1,9 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<table jwcid="table">
+ <span jwcid="insertWrapped"/>
+</table>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.java
new file mode 100644
index 0000000..7b60608
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.java
@@ -0,0 +1,477 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.IBasicTableModel;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.ITableDataModel;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITablePagingState;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+import org.apache.tapestry.contrib.table.model.ITableSessionStoreManager;
+import org.apache.tapestry.contrib.table.model.common.BasicTableModelWrap;
+import org.apache.tapestry.contrib.table.model.simple.SimpleListTableDataModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+
+/**
+ * A low level Table component that wraps all other low level Table components.
+ * This component carries the {@link org.apache.tapestry.contrib.table.model.ITableModel}
+ * that is used by the other Table components. Please see the documentation of
+ * {@link org.apache.tapestry.contrib.table.model.ITableModel} if you need to know more
+ * about how a table is represented.
+ * <p>
+ * This component also handles the saving of the state of the model using an
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}
+ * to determine what part of the model is to be saved and an
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}
+ * to determine how to save it.
+ * <p>
+ * Upon the beginning of a new request cycle when the table model is first needed,
+ * the model is obtained using the following process:
+ * <ul>
+ * <li>The persistent state of the table is loaded.
+ * If the tableSessionStoreManager binding has not been bound, the state is loaded
+ * from a persistent property within the component (it is null at the beginning).
+ * Otherwise the supplied
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager} is used
+ * to load the persistent state.
+ * <li>The table model is recreated using the
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager} that
+ * could be supplied using the tableSessionStateManager binding
+ * (but has a default value and is therefore not required).
+ * <li>If the {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}
+ * returns null, then a table model is taken from the tableModel binding. Thus, if
+ * the {@link org.apache.tapestry.contrib.table.model.common.NullTableSessionStateManager}
+ * is used, the table model would be taken from the tableModel binding every time.
+ * </ul>
+ * Just before the rendering phase the persistent state of the model is saved in
+ * the session. This process occurs in reverse:
+ * <ul>
+ * <li>The persistent state of the model is taken via the
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}.
+ * <li>If the tableSessionStoreManager binding has not been bound, the persistent
+ * state is saved as a persistent page property. Otherwise the supplied
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager} is used
+ * to save the persistent state. Use of the
+ * {@link org.apache.tapestry.contrib.table.model.ITableSessionStoreManager}
+ * is usually necessary when tables with the same model have to be used across
+ * multiple pages, and hence the state has to be saved in the Visit, rather than
+ * in a persistent component property.
+ * </ul>
+ * <p>
+ *
+ * <p>
+ * Please see the Component Reference for details on how to use this component.
+ *
+ * [<a href="../../../../../../../ComponentReference/contrib.TableView.html">Component Reference</a>]
+ *
+ * @author mindbridge
+ * @version $Id$
+ */
+public abstract class TableView
+ extends BaseComponent
+ implements PageDetachListener, PageRenderListener, ITableModelSource
+{
+ // Component properties
+ private ITableSessionStateManager m_objDefaultSessionStateManager = null;
+ private ITableColumnModel m_objColumnModel = null;
+
+ // Transient objects
+ private ITableModel m_objTableModel;
+ private ITableModel m_objCachedTableModelValue;
+
+ // enhanced parameter methods
+ public abstract ITableModel getTableModelValue();
+ public abstract Object getSource();
+ public abstract Object getColumns();
+ public abstract IBinding getColumnsBinding();
+ public abstract IBinding getPageSizeBinding();
+ public abstract String getInitialSortColumn();
+ public abstract boolean getInitialSortOrder();
+ public abstract ITableSessionStateManager getTableSessionStateManager();
+ public abstract ITableSessionStoreManager getTableSessionStoreManager();
+ public abstract IComponent getColumnSettingsContainer();
+
+ // enhanced property methods
+ public abstract Serializable getSessionState();
+ public abstract void setSessionState(Serializable sessionState);
+
+ /**
+ * The component constructor. Invokes the component member initializations.
+ */
+ public TableView()
+ {
+ initialize();
+ }
+
+ /**
+ * Invokes the component member initializations.
+ *
+ * @see org.apache.tapestry.event.PageDetachListener#pageDetached(PageEvent)
+ */
+ public void pageDetached(PageEvent objEvent)
+ {
+ initialize();
+ }
+
+ /**
+ * Initialize the component member variables.
+ */
+ private void initialize()
+ {
+ m_objTableModel = null;
+ m_objCachedTableModelValue = null;
+ }
+
+ /**
+ * Resets the table by removing any stored table state.
+ * This means that the current column to sort on and the current page will be
+ * forgotten and all data will be reloaded.
+ */
+ public void reset()
+ {
+ initialize();
+ storeSessionState(null);
+ }
+
+ public ITableModel getCachedTableModelValue()
+ {
+ if (m_objCachedTableModelValue == null)
+ m_objCachedTableModelValue = getTableModelValue();
+ return m_objCachedTableModelValue;
+ }
+
+ /**
+ * Returns the tableModel.
+ *
+ * @return ITableModel the table model used by the table components
+ */
+ public ITableModel getTableModel()
+ {
+ // if null, first try to recreate the model from the session state
+ if (m_objTableModel == null)
+ {
+ Serializable objState = loadSessionState();
+ m_objTableModel = getTableSessionStateManager().recreateTableModel(objState);
+ }
+
+ // if the session state does not help, get the model from the binding
+ if (m_objTableModel == null)
+ m_objTableModel = getCachedTableModelValue();
+
+ // if the model from the binding is null, build a model from source and columns
+ if (m_objTableModel == null)
+ m_objTableModel = generateTableModel(null);
+
+ if (m_objTableModel == null)
+ throw new ApplicationRuntimeException(
+ TableUtils.format("missing-table-model", getExtendedId()));
+
+ return m_objTableModel;
+ }
+
+ /**
+ * Generate a table model using the 'source' and 'columns' parameters.
+ *
+ * @return the newly generated table model
+ */
+ protected ITableModel generateTableModel(SimpleTableState objState)
+ {
+ // create a new table state if none is passed
+ if (objState == null)
+ {
+ objState = new SimpleTableState();
+ objState.getSortingState().setSortColumn(getInitialSortColumn(), getInitialSortOrder());
+ }
+
+ // update the page size if set in the parameter
+ IBinding objPageSizeBinding = getPageSizeBinding();
+ if (objPageSizeBinding != null)
+ objState.getPagingState().setPageSize(objPageSizeBinding.getInt());
+
+ // get the column model. if not possible, return null.
+ ITableColumnModel objColumnModel = getTableColumnModel();
+ if (objColumnModel == null)
+ return null;
+
+ Object objSourceValue = getSource();
+ if (objSourceValue == null)
+ return null;
+
+ // if the source parameter is of type {@link IBasicTableModel},
+ // create and return an appropriate wrapper
+ if (objSourceValue instanceof IBasicTableModel)
+ return new BasicTableModelWrap(
+ (IBasicTableModel) objSourceValue,
+ objColumnModel,
+ objState);
+
+ // otherwise, the source parameter must contain the data to be displayed
+ ITableDataModel objDataModel = null;
+ if (objSourceValue instanceof Object[])
+ objDataModel = new SimpleListTableDataModel((Object[]) objSourceValue);
+ else if (objSourceValue instanceof List)
+ objDataModel = new SimpleListTableDataModel((List) objSourceValue);
+ else if (objSourceValue instanceof Collection)
+ objDataModel = new SimpleListTableDataModel((Collection) objSourceValue);
+ else if (objSourceValue instanceof Iterator)
+ objDataModel = new SimpleListTableDataModel((Iterator) objSourceValue);
+
+ if (objDataModel == null)
+ throw new ApplicationRuntimeException(
+ TableUtils.format(
+ "invalid-table-source",
+ getExtendedId(),
+ objSourceValue.getClass()));
+
+ return new SimpleTableModel(objDataModel, objColumnModel, objState);
+ }
+
+ /**
+ * Returns the table column model as specified by the 'columns' binding.
+ * If the value of the 'columns' binding is of a type different than
+ * ITableColumnModel, this method makes the appropriate conversion.
+ *
+ * @return The table column model as specified by the 'columns' binding
+ */
+ protected ITableColumnModel getTableColumnModel()
+ {
+ Object objColumns = getColumns();
+
+ if (objColumns == null)
+ return null;
+
+ if (objColumns instanceof ITableColumnModel)
+ {
+ return (ITableColumnModel) objColumns;
+ }
+
+ if (objColumns instanceof Iterator)
+ {
+ // convert to List
+ Iterator objColumnsIterator = (Iterator) objColumns;
+ List arrColumnsList = new ArrayList();
+ CollectionUtils.addAll(arrColumnsList, objColumnsIterator);
+ objColumns = arrColumnsList;
+ }
+
+ if (objColumns instanceof List)
+ {
+ // validate that the list contains only ITableColumn instances
+ List arrColumnsList = (List) objColumns;
+ int nColumnsNumber = arrColumnsList.size();
+ for (int i = 0; i < nColumnsNumber; i++)
+ {
+ if (!(arrColumnsList.get(i) instanceof ITableColumn))
+ throw new ApplicationRuntimeException(
+ TableUtils.format("columns-only-please", getExtendedId()));
+ }
+ //objColumns = arrColumnsList.toArray(new ITableColumn[nColumnsNumber]);
+ return new SimpleTableColumnModel(arrColumnsList);
+ }
+
+ if (objColumns instanceof ITableColumn[])
+ {
+ return new SimpleTableColumnModel((ITableColumn[]) objColumns);
+ }
+
+ if (objColumns instanceof String)
+ {
+ String strColumns = (String) objColumns;
+ if (getColumnsBinding().isInvariant())
+ {
+ // if the binding is invariant, create the columns only once
+ if (m_objColumnModel == null)
+ m_objColumnModel = generateTableColumnModel(strColumns);
+ return m_objColumnModel;
+ }
+
+ // if the binding is not invariant, create them every time
+ return generateTableColumnModel(strColumns);
+ }
+
+ throw new ApplicationRuntimeException(
+ TableUtils.format("invalid-table-columns", getExtendedId(), objColumns.getClass()));
+ }
+
+ /**
+ * Generate a table column model out of the description string provided.
+ * Entries in the description string are separated by commas.
+ * Each column entry is of the format name, name:expression,
+ * or name:displayName:expression.
+ * An entry prefixed with ! represents a non-sortable column.
+ * If the whole description string is prefixed with *, it represents
+ * columns to be included in a Form.
+ *
+ * @param strDesc the description of the column model to be generated
+ * @return a table column model based on the provided description
+ */
+ protected ITableColumnModel generateTableColumnModel(String strDesc)
+ {
+ IComponent objColumnSettingsContainer = getColumnSettingsContainer();
+ return TableUtils.generateTableColumnModel(strDesc, this, objColumnSettingsContainer);
+ }
+
+ /**
+ * The default session state manager to be used in case no such manager
+ * is provided by the corresponding parameter.
+ *
+ * @return the default session state manager
+ */
+ public ITableSessionStateManager getDefaultTableSessionStateManager()
+ {
+ if (m_objDefaultSessionStateManager == null)
+ m_objDefaultSessionStateManager = new TableViewSessionStateManager(this);
+ return m_objDefaultSessionStateManager;
+ }
+
+ /**
+ * Invoked when there is a modification of the table state and it needs to be saved
+ *
+ * @see org.apache.tapestry.contrib.table.model.ITableModelSource#fireObservedStateChange()
+ */
+ public void fireObservedStateChange()
+ {
+ saveSessionState();
+ }
+
+ /**
+ * Ensures that the table state is saved before the render phase begins
+ * in case there are modifications for which {@link #fireObservedStateChange()}
+ * has not been invoked.
+ *
+ * @see org.apache.tapestry.event.PageRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)
+ */
+ public void pageBeginRender(PageEvent event)
+ {
+ // 'suspenders': save the table model if it has been already loaded.
+ // this means that if a change has been made explicitly in a listener,
+ // it will be saved. this is the last place before committing the changes
+ // where a save can occur
+ if (m_objTableModel != null)
+ saveSessionState();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageRenderListener#pageEndRender(PageEvent)
+ */
+ public void pageEndRender(PageEvent objEvent)
+ {
+ }
+
+ /**
+ * Saves the table state using the SessionStateManager to determine
+ * what to save and the SessionStoreManager to determine where to save it.
+ *
+ */
+ protected void saveSessionState()
+ {
+ ITableModel objModel = getTableModel();
+ Serializable objState = getTableSessionStateManager().getSessionState(objModel);
+ storeSessionState(objState);
+ }
+
+ /**
+ * Loads the table state using the SessionStoreManager.
+ *
+ * @return the stored table state
+ */
+ protected Serializable loadSessionState()
+ {
+ ITableSessionStoreManager objManager = getTableSessionStoreManager();
+ if (objManager != null)
+ return objManager.loadState(getPage().getRequestCycle());
+ return getSessionState();
+ }
+
+ /**
+ * Stores the table state using the SessionStoreManager.
+ *
+ * @param objState the table state to store
+ */
+ protected void storeSessionState(Serializable objState)
+ {
+ ITableSessionStoreManager objManager = getTableSessionStoreManager();
+ if (objManager != null)
+ objManager.saveState(getPage().getRequestCycle(), objState);
+ else
+ setSessionState(objState);
+ }
+
+ /**
+ * Make sure that the values stored in the model are useable and correct.
+ * The changes made here are not saved.
+ */
+ protected void validateValues()
+ {
+ ITableModel objModel = getTableModel();
+
+ // make sure current page is within the allowed range
+ ITablePagingState objPagingState = objModel.getPagingState();
+ int nCurrentPage = objPagingState.getCurrentPage();
+ int nPageCount = objModel.getPageCount();
+ if (nCurrentPage >= nPageCount)
+ {
+ // the current page is greater than the page count. adjust.
+ nCurrentPage = nPageCount - 1;
+ objPagingState.setCurrentPage(nCurrentPage);
+ }
+ if (nCurrentPage < 0)
+ {
+ // the current page is before the first page. adjust.
+ nCurrentPage = 0;
+ objPagingState.setCurrentPage(nCurrentPage);
+ }
+ }
+
+ /**
+ * Stores a pointer to this component in the Request Cycle while rendering
+ * so that wrapped components have access to it.
+ *
+ * @see org.apache.tapestry.BaseComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Object objOldValue = cycle.getAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE);
+ cycle.setAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE, this);
+
+ initialize();
+ validateValues();
+ super.renderComponent(writer, cycle);
+
+ cycle.setAttribute(ITableModelSource.TABLE_MODEL_SOURCE_ATTRIBUTE, objOldValue);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.jwc
new file mode 100644
index 0000000..b2324fc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableView.jwc
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.TableView"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <description>
+ The main lower-level Table component.
+ This component should wrap all other lower-level Table components such as
+ TablePages and TableRows, as it provides the data they use.
+ </description>
+
+ <parameter name="tableModel"
+ type="org.apache.tapestry.contrib.table.model.ITableModel"
+ property-name="tableModelValue"
+ required="no"
+ direction="auto"
+ default-value="null">
+ <description>
+ The model describing the data to be presented by the table components.
+ This parameter is optional, but either the 'tableModel' or both
+ 'source' and 'columns' parameters must be provided.
+ </description>
+ </parameter>
+
+ <parameter name="source" type="java.lang.Object" required="no" direction="auto" default-value="null">
+ <description>
+ The data to be displayed by the component. This parameter is available as
+ an alternative to tableModel and must be used in combination with the
+ 'columns' parameter.
+ The parameter must be an array of values, a collection, an iterator,
+ or an object implementing the IBasicTableModel interface.
+ </description>
+ </parameter>
+
+ <parameter name="columns" type="java.lang.Object" required="no" direction="auto" default-value="null">
+ <description>
+ The table columns to be displayed.
+ The parameter must be an array, a list, or an Iterator of ITableColumn objects,
+ an ITableColumnModel, or a String describing the columns (see documentation).
+ </description>
+ </parameter>
+
+ <parameter name="pageSize"
+ type="int"
+ required="no">
+ <description>
+ The number of records displayed per page when source/columns are used.
+ The page size is 10 by default.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortColumn"
+ type="java.lang.String"
+ required="no"
+ direction="auto"
+ default-value="null">
+ <description>
+ The id of the column to initially sort the table by.
+ The column is set to null by default, i.e. there is no sorting.
+ </description>
+ </parameter>
+
+ <parameter name="initialSortOrder"
+ type="boolean"
+ required="no"
+ direction="auto"
+ default-value="false">
+ <description>
+ The order of the initial sorting.
+ Set this parameter to 'false' to sort in an ascending order
+ and to 'true' to sort in a descending one.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStateManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStateManager"
+ required="no"
+ direction="auto"
+ default-value="defaultTableSessionStateManager">
+ <description>
+ The manager defining what part of the table model will be stored in
+ the session.
+ </description>
+ </parameter>
+
+ <parameter name="tableSessionStoreManager"
+ type="org.apache.tapestry.contrib.table.model.ITableSessionStoreManager"
+ required="no"
+ direction="auto"
+ default-value="null">
+ <description>
+ The manager defining where the session data will be stored.
+ </description>
+ </parameter>
+
+ <parameter name="columnSettingsContainer"
+ type="org.apache.tapestry.IComponent"
+ required="no"
+ direction="auto"
+ default-value="container">
+ <description>
+ The component where Block and messages are pulled from when using source/columns.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" required="no" default-value="'table'">
+ <description>
+ The tag with which the component will be inserted in the generated content.
+ </description>
+ </parameter>
+
+ <property-specification name="sessionState" type="java.io.Serializable" persistent="yes"/>
+
+ <component id="table" type="Any" inherit-informal-parameters="yes">
+ <inherited-binding name="element" parameter-name="element"/>
+ </component>
+
+ <component id="insertWrapped" type="RenderBody"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableViewSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableViewSessionStateManager.java
new file mode 100644
index 0000000..b54ec2c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/TableViewSessionStateManager.java
@@ -0,0 +1,69 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ * Acts like {@link org.apache.tapestry.contrib.table.model.common.FullTableSessionStateManager}
+ * if the model is provided via the tableModel parameter;
+ * saves only the model state otherwise.
+ *
+ * @author mindbridge
+ * @version $Id$
+ */
+public class TableViewSessionStateManager implements ITableSessionStateManager
+{
+ private TableView m_objView;
+
+ public TableViewSessionStateManager(TableView objView)
+ {
+ m_objView = objView;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#getSessionState(org.apache.tapestry.contrib.table.model.ITableModel)
+ */
+ public Serializable getSessionState(ITableModel objModel)
+ {
+ // if the model is provided using the 'tableModel' parameter,
+ // emulate FullTableSessionStateManager and save everything
+ // (backward compatibility)
+ if (m_objView.getCachedTableModelValue() != null)
+ return (Serializable) objModel;
+
+ // otherwise save only the state
+ return new SimpleTableState(objModel.getPagingState(), objModel.getSortingState());
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#recreateTableModel(java.io.Serializable)
+ */
+ public ITableModel recreateTableModel(Serializable objState)
+ {
+ // if the state implements ITableModel, return itself
+ // (backward compatibility)
+ if (objState instanceof ITableModel)
+ return (ITableModel) objState;
+
+ // otherwise have the component re-generate the model using the provided state
+ return m_objView.generateTableModel((SimpleTableState) objState);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.html
new file mode 100644
index 0000000..6dcb718
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.html
@@ -0,0 +1,18 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="condSorted">
+ <table border=0 cellspacing=0 cellpadding=0 align="center">
+ <tr>
+ <td><a jwcid="linkColumn"><span jwcid="insertSortedColumn"/></a></td>
+ <span jwcid="condSort"><td> <span jwcid="imageSort" align="center"/></td></span>
+ </tr>
+ </table>
+</span>
+
+<span jwcid="condNotSorted">
+ <span jwcid="insertNotSortedColumn"/>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.java
new file mode 100644
index 0000000..9b83796
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.java
@@ -0,0 +1,164 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components.inserted;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.components.TableColumns;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererListener;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * A component that renders the default column header.
+ *
+ * If the current column is sortable, it renders the header as a link.
+ * Clicking on the link causes the table to be sorted on that column.
+ * Clicking on the link again causes the sorting order to be reversed.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableColumnComponent
+ extends BaseComponent
+ implements ITableRendererListener, PageDetachListener
+{
+ // transient
+ private ITableColumn m_objColumn;
+ private ITableModelSource m_objModelSource;
+
+ public SimpleTableColumnComponent()
+ {
+ init();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageDetachListener#pageDetached(PageEvent)
+ */
+ public void pageDetached(PageEvent arg0)
+ {
+ init();
+ }
+
+ private void init()
+ {
+ m_objColumn = null;
+ m_objModelSource = null;
+ }
+
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererListener#initializeRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public void initializeRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ m_objModelSource = objSource;
+ m_objColumn = objColumn;
+ }
+
+ public ITableModel getTableModel()
+ {
+ return m_objModelSource.getTableModel();
+ }
+
+ public boolean getColumnSorted()
+ {
+ return m_objColumn.getSortable();
+ }
+
+ public String getDisplayName()
+ {
+ if (m_objColumn instanceof SimpleTableColumn) {
+ SimpleTableColumn objSimpleColumn = (SimpleTableColumn) m_objColumn;
+ return objSimpleColumn.getDisplayName();
+ }
+ return m_objColumn.getColumnName();
+ }
+
+ public boolean getIsSorted()
+ {
+ ITableSortingState objSortingState = getTableModel().getSortingState();
+ String strSortColumn = objSortingState.getSortColumn();
+ return m_objColumn.getColumnName().equals(strSortColumn);
+ }
+
+ public IAsset getSortImage()
+ {
+ IAsset objImageAsset;
+
+ IRequestCycle objCycle = getPage().getRequestCycle();
+ ITableSortingState objSortingState = getTableModel().getSortingState();
+ if (objSortingState.getSortOrder()
+ == ITableSortingState.SORT_ASCENDING)
+ {
+ objImageAsset =
+ (IAsset) objCycle.getAttribute(
+ TableColumns.TABLE_COLUMN_ARROW_UP_ATTRIBUTE);
+ if (objImageAsset == null)
+ objImageAsset = getAsset("sortUp");
+ }
+ else
+ {
+ objImageAsset =
+ (IAsset) objCycle.getAttribute(
+ TableColumns.TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE);
+ if (objImageAsset == null)
+ objImageAsset = getAsset("sortDown");
+ }
+
+ return objImageAsset;
+ }
+
+ public Object[] getColumnSelectedParameters()
+ {
+ return new Object[] {
+ new ComponentAddress(m_objModelSource),
+ m_objColumn.getColumnName()};
+ }
+
+ public void columnSelected(IRequestCycle objCycle)
+ {
+ Object[] arrArgs = objCycle.getServiceParameters();
+ ComponentAddress objAddr = (ComponentAddress) arrArgs[0];
+ String strColumnName = (String) arrArgs[1];
+
+ ITableModelSource objSource =
+ (ITableModelSource) objAddr.findComponent(objCycle);
+ ITableModel objModel = objSource.getTableModel();
+
+ ITableSortingState objState = objModel.getSortingState();
+ if (strColumnName.equals(objState.getSortColumn()))
+ objState.setSortColumn(strColumnName, !objState.getSortOrder());
+ else
+ objState.setSortColumn(
+ strColumnName,
+ ITableSortingState.SORT_ASCENDING);
+
+ // ensure that the change is saved
+ objSource.fireObservedStateChange();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.jwc
new file mode 100644
index 0000000..93065bc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnComponent.jwc
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.inserted.SimpleTableColumnComponent"
+ allow-informal-parameters="yes">
+
+ <description>
+ </description>
+
+ <component id="condSorted" type="Conditional">
+ <binding name="condition" expression="columnSorted"/>
+ </component>
+
+ <component id="condNotSorted" type="Conditional">
+ <binding name="condition" expression="columnSorted"/>
+ <static-binding name="invert">true</static-binding>
+ </component>
+
+ <component id="insertSortedColumn" type="Insert">
+ <binding name="value" expression="displayName"/>
+ </component>
+
+ <component id="insertNotSortedColumn" type="Insert">
+ <binding name="value" expression="displayName"/>
+ </component>
+
+ <component id="linkColumn" type="DirectLink">
+ <binding name="listener" expression="listeners.columnSelected"/>
+ <binding name="parameters" expression="columnSelectedParameters"/>
+ </component>
+
+ <component id="imageSort" type="Image">
+ <binding name="image" expression="sortImage"/>
+ </component>
+
+ <component id="condSort" type="Conditional">
+ <binding name="condition" expression="isSorted"/>
+ </component>
+
+ <private-asset name="sortDown" resource-path="arrow-down.gif"/>
+ <private-asset name="sortUp" resource-path="arrow-up.gif"/>
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.html
new file mode 100644
index 0000000..6dcb718
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.html
@@ -0,0 +1,18 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+<span jwcid="condSorted">
+ <table border=0 cellspacing=0 cellpadding=0 align="center">
+ <tr>
+ <td><a jwcid="linkColumn"><span jwcid="insertSortedColumn"/></a></td>
+ <span jwcid="condSort"><td> <span jwcid="imageSort" align="center"/></td></span>
+ </tr>
+ </table>
+</span>
+
+<span jwcid="condNotSorted">
+ <span jwcid="insertNotSortedColumn"/>
+</span>
+
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.java
new file mode 100644
index 0000000..bfd9795
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.java
@@ -0,0 +1,137 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.components.inserted;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.components.TableColumns;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererListener;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+
+/**
+ * A component that renders the default column header in a form.
+ *
+ * If the current column is sortable, it renders the header as a link.
+ * Clicking on the link causes the table to be sorted on that column.
+ * Clicking on the link again causes the sorting order to be reversed.
+ *
+ * This component renders links that cause the form to be submitted.
+ * This ensures that the updated data in the other form fields is preserved.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public abstract class SimpleTableColumnFormComponent
+ extends BaseComponent
+ implements ITableRendererListener
+{
+
+ public abstract ITableColumn getTableColumn();
+ public abstract void setTableColumn(ITableColumn objColumn);
+
+ public abstract ITableModelSource getTableModelSource();
+ public abstract void setTableModelSource(ITableModelSource objSource);
+
+ public abstract String getSelectedColumnName();
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererListener#initializeRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public void initializeRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ setTableModelSource(objSource);
+ setTableColumn(objColumn);
+ }
+
+ public ITableModel getTableModel()
+ {
+ return getTableModelSource().getTableModel();
+ }
+
+ public boolean getColumnSorted()
+ {
+ return getTableColumn().getSortable();
+ }
+
+ public String getDisplayName()
+ {
+ ITableColumn objColumn = getTableColumn();
+
+ if (objColumn instanceof SimpleTableColumn) {
+ SimpleTableColumn objSimpleColumn = (SimpleTableColumn) objColumn;
+ return objSimpleColumn.getDisplayName();
+ }
+ return objColumn.getColumnName();
+ }
+
+ public boolean getIsSorted()
+ {
+ ITableSortingState objSortingState = getTableModel().getSortingState();
+ String strSortColumn = objSortingState.getSortColumn();
+ return getTableColumn().getColumnName().equals(strSortColumn);
+ }
+
+ public IAsset getSortImage()
+ {
+ IAsset objImageAsset;
+
+ IRequestCycle objCycle = getPage().getRequestCycle();
+ ITableSortingState objSortingState = getTableModel().getSortingState();
+ if (objSortingState.getSortOrder()
+ == ITableSortingState.SORT_ASCENDING)
+ {
+ objImageAsset =
+ (IAsset) objCycle.getAttribute(
+ TableColumns.TABLE_COLUMN_ARROW_UP_ATTRIBUTE);
+ if (objImageAsset == null)
+ objImageAsset = getAsset("sortUp");
+ }
+ else
+ {
+ objImageAsset =
+ (IAsset) objCycle.getAttribute(
+ TableColumns.TABLE_COLUMN_ARROW_DOWN_ATTRIBUTE);
+ if (objImageAsset == null)
+ objImageAsset = getAsset("sortDown");
+ }
+
+ return objImageAsset;
+ }
+
+ public void columnSelected(IRequestCycle objCycle)
+ {
+ String strColumnName = getSelectedColumnName();
+ ITableSortingState objState = getTableModel().getSortingState();
+ if (strColumnName.equals(objState.getSortColumn()))
+ objState.setSortColumn(strColumnName, !objState.getSortOrder());
+ else
+ objState.setSortColumn(
+ strColumnName,
+ ITableSortingState.SORT_ASCENDING);
+
+ // ensure that the change is saved
+ getTableModelSource().fireObservedStateChange();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.jwc
new file mode 100644
index 0000000..7f36541
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnFormComponent.jwc
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.table.components.inserted.SimpleTableColumnFormComponent"
+ allow-informal-parameters="yes">
+
+ <description>
+ </description>
+
+ <property-specification name="tableModelSource"
+ type="org.apache.tapestry.contrib.table.model.ITableModelSource"
+ initial-value="null"/>
+
+ <property-specification name="tableColumn"
+ type="org.apache.tapestry.contrib.table.model.ITableColumn"
+ initial-value="null"/>
+
+ <property-specification name="selectedColumnName" type="java.lang.String"/>
+
+ <component id="condSorted" type="FormConditional">
+ <binding name="condition" expression="columnSorted"/>
+ </component>
+
+ <component id="condNotSorted" type="FormConditional">
+ <binding name="condition" expression="!columnSorted"/>
+ </component>
+
+ <component id="insertSortedColumn" type="Insert">
+ <binding name="value" expression="displayName"/>
+ </component>
+
+ <component id="insertNotSortedColumn" type="Insert">
+ <binding name="value" expression="displayName"/>
+ </component>
+
+ <component id="linkColumn" type="LinkSubmit">
+ <binding name="listener" expression="listeners.columnSelected"/>
+ <binding name="tag" expression="tableColumn.columnName"/>
+ <binding name="selected" expression="selectedColumnName"/>
+ </component>
+
+ <component id="imageSort" type="Image">
+ <binding name="image" expression="sortImage"/>
+ </component>
+
+ <component id="condSort" type="FormConditional">
+ <binding name="condition" expression="isSorted"/>
+ </component>
+
+ <private-asset name="sortDown" resource-path="arrow-down.gif"/>
+ <private-asset name="sortUp" resource-path="arrow-up.gif"/>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.html b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.html
new file mode 100644
index 0000000..a1d70de
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.html
@@ -0,0 +1,8 @@
+<!-- $Id$ -->
+
+<span jwcid="$content$">
+
+ <span jwcid="tableColumnComponent"/>
+ <span jwcid="tableColumnFormComponent"/>
+
+</span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.page b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.page
new file mode 100644
index 0000000..b84772d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/SimpleTableColumnPage.page
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification>
+
+ <component id="tableColumnComponent" type="SimpleTableColumnComponent"/>
+ <component id="tableColumnFormComponent" type="SimpleTableColumnFormComponent"/>
+
+</page-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-down.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-down.gif
new file mode 100644
index 0000000..d9339a6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-down.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-up.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-up.gif
new file mode 100644
index 0000000..b70a479
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/components/inserted/arrow-up.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/CTableDataModelEvent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/CTableDataModelEvent.java
new file mode 100644
index 0000000..c402687
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/CTableDataModelEvent.java
@@ -0,0 +1,25 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * @author mindbridge
+ *
+ */
+public class CTableDataModelEvent
+{
+ public CTableDataModelEvent() {
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IBasicTableModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IBasicTableModel.java
new file mode 100644
index 0000000..aa1ac30
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IBasicTableModel.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.util.Iterator;
+
+/**
+ * A simplified version of the table model that concerns itself only with
+ * providing the data on the current page.
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 3.0
+ */
+public interface IBasicTableModel
+{
+ /**
+ * Returns the number of all records
+ * @return the number of all rows
+ **/
+ int getRowCount();
+
+ /**
+ * Returns the rows on the current page.
+ * @param nFirst the index of the first item to be dispayed
+ * @param nPageSize the number of items to be displayed
+ * @param objSortColumn the column to sort by or null if there is no sorting
+ * @param bSortOrder determines the sorting order (ascending or descending)
+ **/
+ Iterator getCurrentPageRows(int nFirst, int nPageSize, ITableColumn objSortColumn, boolean bSortOrder);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IPrimaryKeyConvertor.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IPrimaryKeyConvertor.java
new file mode 100644
index 0000000..ec66178
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/IPrimaryKeyConvertor.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * An interface for converting an object to its primary key and back.
+ * Typically used to determine how to store a given object as a hidden
+ * value when rendering a form.
+ *
+ * @version $Id$
+ * @author mb
+ * @since 3.0
+ */
+public interface IPrimaryKeyConvertor
+{
+ /**
+ * Gets the serializable primary key of the given value
+ *
+ * @param objValue the value for which a primary key needs to be extracted
+ * @return the serializable primary key of the value
+ */
+ Object getPrimaryKey(Object objValue);
+
+ /**
+ * Gets the value corresponding the given primary key
+ *
+ * @param objPrimaryKey the primary key for which a value needs to be generated
+ * @return the generated value corresponding to the given primary key
+ */
+ Object getValue(Object objPrimaryKey);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumn.java
new file mode 100644
index 0000000..5ce6c13
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumn.java
@@ -0,0 +1,89 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.util.Comparator;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * The interface defining a table column.
+ *
+ * A column is responsible for presenting a particular part of the data
+ * from the objects in the table. This is done via the getValueRender() method.
+ *
+ * A column may be sortable, in which case it defines the way in which the
+ * objects in the table must be sorted by providing a Comparator.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableColumn
+{
+ /**
+ * Method getColumnName provides the name of the column.
+ *
+ * The column name must be unique and is generally used for the identification
+ * of the column. It does not have to be the same as the display name
+ * via which the column is identified to the user (see the getColumnRender() method).
+ * @return String the name of the column
+ */
+ String getColumnName();
+
+ /**
+ * Method getSortable declares whether the column allows sorting.
+ * If the column allows sorting, it must also return a valid Comparator
+ * via the getComparator() method.
+ * @return boolean whether the column is sortable or not
+ */
+ boolean getSortable();
+
+ /**
+ * Method getComparator returns the Comparator to be used to sort
+ * the data in the table according to this column. The Comparator must
+ * accept two different rows, compare them according to this column,
+ * and return the appropriate value.
+ * @return Comparator the Comparator used to sort the table data
+ */
+ Comparator getComparator();
+
+ /**
+ * Method getColumnRenderer provides a renderer that takes care of rendering
+ * the column in the table header. If the column is sortable, the renderer
+ * may provide a mechanism to sort the table in an ascending or descending
+ * manner.
+ * @param objCycle the current request cycle
+ * @param objSource a component that can provide the table model (typically TableView)
+ * @return IRender the renderer to present the column header
+ */
+ IRender getColumnRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource);
+
+ /**
+ * Method getValueRenderer provides a renderer for presenting the value of a
+ * particular row in the current column.
+ *
+ * @param objCycle the current request cycle
+ * @param objSource a component that can provide the table model (typically TableView)
+ * @param objRow the row data
+ * @return IRender the renderer to present the value of the row in this column
+ */
+ IRender getValueRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ Object objRow);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumnModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumnModel.java
new file mode 100644
index 0000000..26de305
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableColumnModel.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.util.Iterator;
+
+/**
+ * Defines a list model of ITableColumn objects
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableColumnModel
+{
+ /**
+ * Method getColumnCount.
+ * @return int the number of columns in the model
+ */
+ int getColumnCount();
+
+ /**
+ * Method getColumn.
+ * @param strName the name of the requested column
+ * @return ITableColumn the column with the given name. null if no such column exists.
+ */
+ ITableColumn getColumn(String strName);
+
+ /**
+ * Method getColumns.
+ * @return Iterator an iterator of all columns in the model
+ */
+ Iterator getColumns();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModel.java
new file mode 100644
index 0000000..9cfb723
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModel.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.util.Iterator;
+
+/**
+ * A model of the table's data
+ * This model need not be used. Implementations may choose to
+ * access data via an abstraction.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableDataModel
+{
+ /**
+ * Method getRowCount.
+ * @return int the number of rows in the model
+ */
+ int getRowCount();
+
+ /**
+ * Iterates over all of the rows in the model
+ * @return Iterator the iterator for access to the data
+ */
+ Iterator getRows();
+
+ /**
+ * Method addTableDataModelListener
+ * Adds a listener that is notified when the data in the model is changed
+ * @param objListener the listener to add
+ */
+ void addTableDataModelListener(ITableDataModelListener objListener);
+
+ /**
+ * Method removeTableDataModelListener.
+ * Removes a listener that is notified when the data in the model is changed
+ * @param objListener the listener to remove
+ */
+ void removeTableDataModelListener(ITableDataModelListener objListener);
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModelListener.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModelListener.java
new file mode 100644
index 0000000..55026d0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableDataModelListener.java
@@ -0,0 +1,24 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * @author mindbridge
+ *
+ */
+public interface ITableDataModelListener
+{
+ void tableDataChanged(CTableDataModelEvent objEvent);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModel.java
new file mode 100644
index 0000000..a244ca7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModel.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.util.Iterator;
+
+/**
+ * The main interface defining the abstraction containing the table data and state
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableModel
+{
+ /**
+ * Method getColumnModel.
+ * @return ITableColumnModel the column model of the table
+ */
+ ITableColumnModel getColumnModel();
+
+ /**
+ * Method getSortingState.
+ * @return ITableSortingState the sorting state of the table
+ */
+ ITableSortingState getSortingState();
+ /**
+ * Method getPagingState.
+ * @return ITablePagingState the paging state of the table
+ */
+ ITablePagingState getPagingState();
+
+ /**
+ * Method getPageCount.
+ * @return int the number of pages this table would have given the current data and paging state
+ */
+ int getPageCount();
+ /**
+ * Method getCurrentPageRows.
+ * @return Iterator the rows in the current table page given the current data, sorting, and paging state
+ */
+ Iterator getCurrentPageRows();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModelSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModelSource.java
new file mode 100644
index 0000000..4b8ce2f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableModelSource.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import org.apache.tapestry.IComponent;
+
+/**
+ * A Tapestry component that provides the current table model.
+ * This interface is used for obtaining the table model source by
+ * components wrapped by it, as well as by external renderers,
+ * such as those provided by the column implementations
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableModelSource extends IComponent
+{
+ final static String TABLE_MODEL_SOURCE_ATTRIBUTE = "org.apache.tapestry.contrib.table.model.ITableModelSource";
+
+ /**
+ * Returns the table model currently used
+ * @return ITableModel the current table model
+ */
+ ITableModel getTableModel();
+
+ /**
+ * Notifies the model source that the model state has changed, and
+ * that it should consider saving it.<p>
+ * This method was added to allow using the table within a Block when
+ * the pageBeginRender() listener of the implementation will not be called
+ * and automatic state storage will therefore be hard to implement.
+ */
+ void fireObservedStateChange();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITablePagingState.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITablePagingState.java
new file mode 100644
index 0000000..ecbd8af
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITablePagingState.java
@@ -0,0 +1,50 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * An interface defining the management of the table's paging state.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITablePagingState
+{
+ /**
+ * Method getPageSize provides the size of a page in a number of records.
+ * This value may be meaningless if the model uses a different method for pagination.
+ * @return int the current page size
+ */
+ int getPageSize();
+
+ /**
+ * Method setPageSize updates the size of a page in a number of records.
+ * This value may be meaningless if the model uses a different method for pagination.
+ * @param nPageSize the new page size
+ */
+ void setPageSize(int nPageSize);
+
+ /**
+ * Gets the currently selected page. The page number is counted from 0.
+ * @return int the current active page
+ */
+ int getCurrentPage();
+
+ /**
+ * Sets the newly selected page. The page number is counted from 0.
+ * @param nPage the new active page
+ */
+ void setCurrentPage(int nPage);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererListener.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererListener.java
new file mode 100644
index 0000000..a24c1a1a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererListener.java
@@ -0,0 +1,34 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ *
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public interface ITableRendererListener extends IComponent
+{
+ void initializeRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererSource.java
new file mode 100644
index 0000000..c6348f8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRendererSource.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * This interface provides a renderer to present the data in a table column.
+ * It is usually used by the {@link org.apache.tapestry.contrib.table.model.ITableColumn}
+ * implementations via aggregation.
+ *
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public interface ITableRendererSource extends Serializable
+{
+ /**
+ * Returns a renderer to present the data of the row in the given column. <p>
+ * This method can also be used to return a renderer to present the
+ * heading of the column. In such a case the row passed would be null.
+ *
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getValueRenderer(IRequestCycle, ITableModelSource, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow);
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRowSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRowSource.java
new file mode 100644
index 0000000..e358297
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableRowSource.java
@@ -0,0 +1,34 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * A Tapestry component that provides the current row value.
+ * This interface is used for obtaining the row source by components
+ * wrapped by the row source
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableRowSource
+{
+ final static String TABLE_ROW_SOURCE_ATTRIBUTE = "org.apache.tapestry.contrib.table.model.ITableRowSource";
+
+ /**
+ * Method getTableRow
+ * @return Object the current table row object
+ */
+ Object getTableRow();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStateManager.java
new file mode 100644
index 0000000..2358beb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStateManager.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.io.Serializable;
+
+/**
+ * An interface responsible for determining <b>what</b> data would be stored
+ * in the session between requests.
+ * It could be only the table state, it could be entire table including the data,
+ * or it could be nothing at all.
+ * It is all determined by the implemention of this interface.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableSessionStateManager
+{
+
+ /**
+ * Method getSessionState extracts the "persistent" portion of the table model
+ * @param objModel the table model to extract the session state from
+ * @return Object the session state to be saved between the requests
+ */
+ Serializable getSessionState(ITableModel objModel);
+
+ /**
+ * Method recreateTableModel recreates a table model from the saved session state
+ * @param objState the saved session state
+ * @return ITableModel the recreated table model
+ */
+ ITableModel recreateTableModel(Serializable objState);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStoreManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStoreManager.java
new file mode 100644
index 0000000..6fb7261
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSessionStoreManager.java
@@ -0,0 +1,42 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * An interface responsible for determining <b>where</b> the session state
+ * will be saved between requests.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableSessionStoreManager
+{
+ /**
+ * Method saveState saves the session sate
+ * @param objCycle the current request cycle
+ * @param objState the session state to be saved
+ */
+ void saveState(IRequestCycle objCycle, Serializable objState);
+ /**
+ * Method loadState loads the session state
+ * @param objCycle the current request cycle
+ * @return Object the loaded sessions state
+ */
+ Serializable loadState(IRequestCycle objCycle);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSortingState.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSortingState.java
new file mode 100644
index 0000000..2fb8002
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ITableSortingState.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model;
+
+/**
+ * An interface defining the management of the table's sorting state.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ITableSortingState
+{
+ static final boolean SORT_ASCENDING = false;
+ static final boolean SORT_DESCENDING = true;
+
+ /**
+ * Method getSortColumn defines the column that the table should be sorted upon
+ * @return String the name of the sorting column or null if the table is not sorted
+ */
+ String getSortColumn();
+
+ /**
+ * Method getSortOrder defines the direction of the table sorting
+ * @return boolean the sorting order (see constants)
+ */
+ boolean getSortOrder();
+
+ /**
+ * Method setSortColumn updates the table sorting column and order
+ * @param strName the name of the column to sort by
+ * @param bOrder the sorting order (see constants)
+ */
+ void setSortColumn(String strName, boolean bOrder);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableColumn.java
new file mode 100644
index 0000000..5d69c1d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableColumn.java
@@ -0,0 +1,232 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.components.Block;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.valid.RenderString;
+
+/**
+ * A base implementation of {@link org.apache.tapestry.contrib.table.model.ITableColumn}
+ * that allows renderers to be set via aggregation.
+ *
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class AbstractTableColumn implements ITableColumn, Serializable
+{
+ /**
+ * The suffix of the name of the Block that will be used as the column renderer
+ * for this column
+ */
+ public final static String COLUMN_RENDERER_BLOCK_SUFFIX = "ColumnHeader";
+
+ /**
+ * The suffix of the name of the Block that will be used as the value renderer
+ * for this column
+ */
+ public final static String VALUE_RENDERER_BLOCK_SUFFIX = "ColumnValue";
+
+ private String m_strColumnName;
+ private boolean m_bSortable;
+ private Comparator m_objComparator;
+
+ private ITableRendererSource m_objColumnRendererSource;
+ private ITableRendererSource m_objValueRendererSource;
+
+ public AbstractTableColumn()
+ {
+ this("", false, null);
+ }
+
+ public AbstractTableColumn(
+ String strColumnName,
+ boolean bSortable,
+ Comparator objComparator)
+ {
+ this(strColumnName, bSortable, objComparator, null, null);
+ }
+
+ public AbstractTableColumn(
+ String strColumnName,
+ boolean bSortable,
+ Comparator objComparator,
+ ITableRendererSource objColumnRendererSource,
+ ITableRendererSource objValueRendererSource)
+ {
+ setColumnName(strColumnName);
+ setSortable(bSortable);
+ setComparator(objComparator);
+ setColumnRendererSource(objColumnRendererSource);
+ setValueRendererSource(objValueRendererSource);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getColumnName()
+ */
+ public String getColumnName()
+ {
+ return m_strColumnName;
+ }
+
+ /**
+ * Sets the columnName.
+ * @param columnName The columnName to set
+ */
+ public void setColumnName(String columnName)
+ {
+ m_strColumnName = columnName;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getSortable()
+ */
+ public boolean getSortable()
+ {
+ return m_bSortable;
+ }
+
+ /**
+ * Sets whether the column is sortable.
+ * @param sortable The sortable flag to set
+ */
+ public void setSortable(boolean sortable)
+ {
+ m_bSortable = sortable;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getComparator()
+ */
+ public Comparator getComparator()
+ {
+ return m_objComparator;
+ }
+
+ /**
+ * Sets the comparator.
+ * @param comparator The comparator to set
+ */
+ public void setComparator(Comparator comparator)
+ {
+ m_objComparator = comparator;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getColumnRenderer(IRequestCycle, ITableModelSource)
+ */
+ public IRender getColumnRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource)
+ {
+ ITableRendererSource objRendererSource =
+ getColumnRendererSource();
+ if (objRendererSource == null)
+ {
+ // log error
+ return new RenderString("");
+ }
+
+ return objRendererSource.getRenderer(objCycle, objSource, this, null);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableColumn#getValueRenderer(IRequestCycle, ITableModelSource, Object)
+ */
+ public IRender getValueRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ Object objRow)
+ {
+ ITableRendererSource objRendererSource = getValueRendererSource();
+ if (objRendererSource == null)
+ {
+ // log error
+ return new RenderString("");
+ }
+
+ return objRendererSource.getRenderer(
+ objCycle,
+ objSource,
+ this,
+ objRow);
+ }
+
+ /**
+ * Returns the columnRendererSource.
+ * @return ITableColumnRendererSource
+ */
+ public ITableRendererSource getColumnRendererSource()
+ {
+ return m_objColumnRendererSource;
+ }
+
+ /**
+ * Sets the columnRendererSource.
+ * @param columnRendererSource The columnRendererSource to set
+ */
+ public void setColumnRendererSource(ITableRendererSource columnRendererSource)
+ {
+ m_objColumnRendererSource = columnRendererSource;
+ }
+
+ /**
+ * Returns the valueRendererSource.
+ *
+ * @return the valueRendererSource of this column
+ */
+ public ITableRendererSource getValueRendererSource()
+ {
+ return m_objValueRendererSource;
+ }
+
+ /**
+ * Sets the valueRendererSource.
+ *
+ * @param valueRendererSource The valueRendererSource to set
+ */
+ public void setValueRendererSource(ITableRendererSource valueRendererSource)
+ {
+ m_objValueRendererSource = valueRendererSource;
+ }
+
+ /**
+ * Use the column name to get the column and value renderer sources
+ * from the provided component.
+ *
+ * @param objSettingsContainer the component from which to get the settings
+ */
+ public void loadSettings(IComponent objSettingsContainer)
+ {
+ IComponent objColumnRendererSource = (IComponent) objSettingsContainer.getComponents().get(getColumnName() + COLUMN_RENDERER_BLOCK_SUFFIX);
+ if (objColumnRendererSource != null && objColumnRendererSource instanceof Block)
+ setColumnRendererSource(new BlockTableRendererSource((Block) objColumnRendererSource));
+
+ IComponent objValueRendererSource = (IComponent) objSettingsContainer.getComponents().get(getColumnName() + VALUE_RENDERER_BLOCK_SUFFIX);
+ if (objValueRendererSource != null && objValueRendererSource instanceof Block)
+ setValueRendererSource(new BlockTableRendererSource((Block) objValueRendererSource));
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableDataModel.java
new file mode 100644
index 0000000..23e5477
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableDataModel.java
@@ -0,0 +1,105 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tapestry.contrib.table.model.CTableDataModelEvent;
+import org.apache.tapestry.contrib.table.model.ITableDataModel;
+import org.apache.tapestry.contrib.table.model.ITableDataModelListener;
+
+/**
+ * An implementation of the listener support in the ITableDataModel interface
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public abstract class AbstractTableDataModel implements ITableDataModel
+{
+ private List m_arrListeners;
+
+ public AbstractTableDataModel()
+ {
+ m_arrListeners = new ArrayList();
+ }
+
+ /**
+ * Method fireTableDataModelEvent.
+ * Fires a change event to all listeners
+ * @param objEvent the event to pass to the listeners
+ */
+ protected void fireTableDataModelEvent(CTableDataModelEvent objEvent)
+ {
+ synchronized (m_arrListeners) {
+ List arrEmptyReferences = null;
+
+ for (Iterator it = m_arrListeners.iterator(); it.hasNext();)
+ {
+ WeakReference objRef = (WeakReference) it.next();
+ ITableDataModelListener objListener =
+ (ITableDataModelListener) objRef.get();
+ if (objListener != null)
+ objListener.tableDataChanged(objEvent);
+ else {
+ if (arrEmptyReferences == null)
+ arrEmptyReferences = new ArrayList();
+ arrEmptyReferences.add(objRef);
+ }
+ }
+
+ if (arrEmptyReferences != null)
+ m_arrListeners.removeAll(arrEmptyReferences);
+ }
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#addTableDataModelListener(ITableDataModelListener)
+ */
+ public void addTableDataModelListener(ITableDataModelListener objListener)
+ {
+ synchronized (m_arrListeners) {
+ m_arrListeners.add(new WeakReference(objListener));
+ }
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#removeTableDataModelListener(ITableDataModelListener)
+ */
+ public void removeTableDataModelListener(ITableDataModelListener objListener)
+ {
+ synchronized (m_arrListeners) {
+ List arrEmptyReferences = null;
+
+ for (Iterator it = m_arrListeners.iterator(); it.hasNext();)
+ {
+ WeakReference objRef = (WeakReference) it.next();
+ ITableDataModelListener objStoredListener =
+ (ITableDataModelListener) objRef.get();
+ if (objListener == objStoredListener || objStoredListener == null) {
+ if (arrEmptyReferences == null)
+ arrEmptyReferences = new ArrayList();
+ arrEmptyReferences.add(objRef);
+ }
+ }
+
+ if (arrEmptyReferences != null)
+ m_arrListeners.removeAll(arrEmptyReferences);
+ }
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableModel.java
new file mode 100644
index 0000000..a8d964f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/AbstractTableModel.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITablePagingState;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ * A base table model that implements the handling of the model state.
+ * Used by most standard ITableModel implementations.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public abstract class AbstractTableModel implements ITableModel, Serializable
+{
+ private SimpleTableState m_objTableState;
+
+ protected AbstractTableModel()
+ {
+ this(new SimpleTableState());
+ }
+
+ protected AbstractTableModel(SimpleTableState objTableState)
+ {
+ m_objTableState = objTableState;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getPagingState()
+ */
+ public ITablePagingState getPagingState()
+ {
+ return getState().getPagingState();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getSortingState()
+ */
+ public ITableSortingState getSortingState()
+ {
+ return getState().getSortingState();
+ }
+
+ /**
+ * Returns the tableState.
+ * @return SimpleTableState
+ */
+ public SimpleTableState getState()
+ {
+ return m_objTableState;
+ }
+
+ protected abstract int getRowCount();
+
+ public int getPageCount()
+ {
+ int nRowCount = getRowCount();
+ if (nRowCount == 0)
+ return 1;
+
+ int nPageSize = getPagingState().getPageSize();
+ if (nPageSize <= 0)
+ return 1;
+
+ return (nRowCount - 1) / nPageSize + 1;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ArrayIterator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ArrayIterator.java
new file mode 100644
index 0000000..950f0dc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ArrayIterator.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * @version $Id$
+ * @author mindbridge
+ */
+public class ArrayIterator implements Iterator
+{
+ private Object[] m_arrValues;
+ private int m_nFrom;
+ private int m_nTo;
+ private int m_nCurrent;
+
+ public ArrayIterator(Object[] arrValues)
+ {
+ this(arrValues, 0, arrValues.length);
+ }
+
+ public ArrayIterator(Object[] arrValues, int nFrom, int nTo)
+ {
+ m_arrValues = arrValues;
+ m_nFrom = nFrom;
+ m_nTo = nTo;
+
+ if (m_nFrom < 0)
+ m_nFrom = 0;
+ if (m_nTo < m_nFrom)
+ m_nTo = m_nFrom;
+ if (m_nTo > m_arrValues.length)
+ m_nTo = m_arrValues.length;
+
+ m_nCurrent = m_nFrom;
+ }
+
+ /**
+ * @see java.util.Iterator#hasNext()
+ */
+ public boolean hasNext()
+ {
+ return m_nCurrent < m_nTo;
+ }
+
+ /**
+ * @see java.util.Iterator#next()
+ */
+ public Object next()
+ {
+ //System.out.println("index: " + m_nCurrent + " size: " + m_arrValues.length + " to: " + m_nTo);
+ if (!hasNext())
+ throw new NoSuchElementException();
+ return m_arrValues[m_nCurrent++];
+ }
+
+ /**
+ * @see java.util.Iterator#remove()
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BasicTableModelWrap.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BasicTableModelWrap.java
new file mode 100644
index 0000000..30e0118
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BasicTableModelWrap.java
@@ -0,0 +1,80 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.contrib.table.model.IBasicTableModel;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ * @version $Id$
+ * @author mindbridge
+ */
+public class BasicTableModelWrap extends AbstractTableModel
+{
+ private IBasicTableModel m_objBasicTableModel;
+ private ITableColumnModel m_objTableColumnModel;
+
+ public BasicTableModelWrap(IBasicTableModel objBasicTableModel, ITableColumnModel objColumnModel)
+ {
+ this(objBasicTableModel, objColumnModel, new SimpleTableState());
+ }
+
+ public BasicTableModelWrap(IBasicTableModel objBasicTableModel, ITableColumnModel objColumnModel, SimpleTableState objState)
+ {
+ super(objState);
+ m_objBasicTableModel = objBasicTableModel;
+ m_objTableColumnModel = objColumnModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getColumnModel()
+ */
+ public ITableColumnModel getColumnModel()
+ {
+ return m_objTableColumnModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableModel#getRowCount()
+ */
+ protected int getRowCount()
+ {
+ return m_objBasicTableModel.getRowCount();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getCurrentPageRows()
+ */
+ public Iterator getCurrentPageRows()
+ {
+ int nPageSize = getPagingState().getPageSize();
+ if (nPageSize <= 0)
+ nPageSize = getRowCount();
+
+ int nCurrentPage = getPagingState().getCurrentPage();
+ int nFrom = nCurrentPage * nPageSize;
+
+ String strSortColumn = getSortingState().getSortColumn();
+ ITableColumn objSortColumn = getColumnModel().getColumn(strSortColumn);
+ boolean bSortOrder = getSortingState().getSortOrder();
+
+ return m_objBasicTableModel.getCurrentPageRows(nFrom, nPageSize, objSortColumn, bSortOrder);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BlockTableRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BlockTableRendererSource.java
new file mode 100644
index 0000000..5b2a99a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/BlockTableRendererSource.java
@@ -0,0 +1,125 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.components.Block;
+import org.apache.tapestry.components.BlockRenderer;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererListener;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class BlockTableRendererSource implements ITableRendererSource
+{
+ private ComponentAddress m_objBlockAddress;
+ private ComponentAddress m_objListenerAddress;
+
+ public BlockTableRendererSource(Block objBlock)
+ {
+ this(new ComponentAddress(objBlock));
+ }
+
+ public BlockTableRendererSource(
+ Block objBlock,
+ ITableRendererListener objListener)
+ {
+ this(new ComponentAddress(objBlock), new ComponentAddress(objListener));
+ }
+
+ public BlockTableRendererSource(ComponentAddress objBlockAddress)
+ {
+ this(objBlockAddress, null);
+ }
+
+ public BlockTableRendererSource(
+ ComponentAddress objBlockAddress,
+ ComponentAddress objListenerAddress)
+ {
+ setBlockAddress(objBlockAddress);
+ setListenerAddress(objListenerAddress);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ ComponentAddress objListenerAddress = getListenerAddress();
+ if (objListenerAddress != null)
+ {
+ ITableRendererListener objListener =
+ (ITableRendererListener) objListenerAddress.findComponent(
+ objCycle);
+ objListener.initializeRenderer(
+ objCycle,
+ objSource,
+ objColumn,
+ objRow);
+ }
+
+ Block objBlock = (Block) getBlockAddress().findComponent(objCycle);
+ return new BlockRenderer(objBlock);
+ }
+
+ /**
+ * Returns the blockAddress.
+ * @return ComponentAddress
+ */
+ public ComponentAddress getBlockAddress()
+ {
+ return m_objBlockAddress;
+ }
+
+ /**
+ * Sets the blockAddress.
+ * @param blockAddress The blockAddress to set
+ */
+ public void setBlockAddress(ComponentAddress blockAddress)
+ {
+ m_objBlockAddress = blockAddress;
+ }
+
+ /**
+ * Returns the listenerAddress.
+ * @return ComponentAddress
+ */
+ public ComponentAddress getListenerAddress()
+ {
+ return m_objListenerAddress;
+ }
+
+ /**
+ * Sets the listenerAddress.
+ * @param listenerAddress The listenerAddress to set
+ */
+ public void setListenerAddress(ComponentAddress listenerAddress)
+ {
+ m_objListenerAddress = listenerAddress;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ComponentTableRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ComponentTableRendererSource.java
new file mode 100644
index 0000000..4a9c2f1
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ComponentTableRendererSource.java
@@ -0,0 +1,81 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererListener;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class ComponentTableRendererSource implements ITableRendererSource
+{
+ private ComponentAddress m_objComponentAddress;
+
+ public ComponentTableRendererSource(ITableRendererListener objComponent)
+ {
+ this(new ComponentAddress(objComponent));
+ }
+
+ public ComponentTableRendererSource(ComponentAddress objComponentAddress)
+ {
+ setComponentAddress(objComponentAddress);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ ITableRendererListener objComponent =
+ (ITableRendererListener) getComponentAddress().findComponent(
+ objCycle);
+
+ objComponent.initializeRenderer(objCycle, objSource, objColumn, objRow);
+
+ return objComponent;
+ }
+
+ /**
+ * Returns the listenerAddress.
+ * @return ComponentAddress
+ */
+ public ComponentAddress getComponentAddress()
+ {
+ return m_objComponentAddress;
+ }
+
+ /**
+ * Sets the listenerAddress.
+ * @param listenerAddress The listenerAddress to set
+ */
+ public void setComponentAddress(ComponentAddress listenerAddress)
+ {
+ m_objComponentAddress = listenerAddress;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/FullTableSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/FullTableSessionStateManager.java
new file mode 100644
index 0000000..7211bbd
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/FullTableSessionStateManager.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+
+/**
+ * A simple ITableSessionStateManager implementation
+ * that saves the entire table model into the session.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class FullTableSessionStateManager implements ITableSessionStateManager
+{
+
+ public final static FullTableSessionStateManager FULL_STATE_MANAGER =
+ new FullTableSessionStateManager();
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#getSessionState(ITableModel)
+ */
+ public Serializable getSessionState(ITableModel objModel)
+ {
+ return (Serializable) objModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#recreateTableModel(Serializable)
+ */
+ public ITableModel recreateTableModel(Serializable objState)
+ {
+ return (ITableModel) objState;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/NullTableSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/NullTableSessionStateManager.java
new file mode 100644
index 0000000..d2921f1
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/NullTableSessionStateManager.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+
+/**
+ * A simple ITableSessionStateManager implementation
+ * that saves nothing at all into the session.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class NullTableSessionStateManager implements ITableSessionStateManager
+{
+
+ public final static NullTableSessionStateManager NULL_STATE_MANAGER =
+ new NullTableSessionStateManager();
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#getSessionState(ITableModel)
+ */
+ public Serializable getSessionState(ITableModel objModel)
+ {
+ return null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#recreateTableModel(Serializable)
+ */
+ public ITableModel recreateTableModel(Serializable objState)
+ {
+ return null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ReverseComparator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ReverseComparator.java
new file mode 100644
index 0000000..4622156
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/common/ReverseComparator.java
@@ -0,0 +1,41 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.common;
+
+import java.util.Comparator;
+
+/**
+ * @version $Id$
+ * @author mindbridge
+ *
+ */
+public class ReverseComparator implements Comparator
+{
+ private Comparator m_objComparator;
+
+ public ReverseComparator(Comparator objComparator)
+ {
+ m_objComparator = objComparator;
+ }
+
+ /**
+ * @see java.util.Comparator#compare(Object, Object)
+ */
+ public int compare(Object objValue1, Object objValue2)
+ {
+ return -m_objComparator.compare(objValue1, objValue2);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumn.java
new file mode 100644
index 0000000..c0beb91
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumn.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.ognl;
+
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+
+/**
+ * @author mindbridge
+ *
+ */
+public class ExpressionTableColumn extends SimpleTableColumn
+{
+ public ExpressionTableColumn(String strColumnName, String strExpression)
+ {
+ this(strColumnName, strExpression, false);
+ }
+
+ public ExpressionTableColumn(String strColumnName, String strExpression, boolean bSortable)
+ {
+ this(strColumnName, strColumnName, strExpression, bSortable);
+ }
+
+ public ExpressionTableColumn(String strColumnName, String strDisplayName, String strExpression)
+ {
+ this(strColumnName, strDisplayName, strExpression, false);
+ }
+
+ public ExpressionTableColumn(
+ String strColumnName,
+ String strDisplayName,
+ String strExpression,
+ boolean bSortable)
+ {
+ super(strColumnName, strDisplayName, bSortable);
+ setEvaluator(new OgnlTableColumnEvaluator(strExpression));
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumnModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumnModel.java
new file mode 100644
index 0000000..04e26a9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/ExpressionTableColumnModel.java
@@ -0,0 +1,137 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.ognl;
+
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
+
+/**
+ * @author mindbridge
+ *
+ */
+public class ExpressionTableColumnModel extends SimpleTableColumnModel
+{
+ /**
+ * Constructs a table column model containting OGNL expression columns. <br>
+ * The data for the columns is provided in the form of a string array,
+ * where the info of each column is stored in two consecutive fields in
+ * the array, hence its size must be even. The expected info is the following:
+ * <ul>
+ * <li> Column Name
+ * <li> OGNL expression
+ * </ul>
+ * @param arrColumnInfo The information to construct the columns from
+ * @param bSorted Whether all columns are sorted or not
+ */
+ public ExpressionTableColumnModel(String[] arrColumnInfo, boolean bSorted)
+ {
+ this(convertToDetailedArray(arrColumnInfo, bSorted));
+ }
+
+ /**
+ * Constructs a table column model containting OGNL expression columns. <br>
+ * The data for the columns is provided in the form of a string array,
+ * where the info of each column is stored in four consecutive fields in
+ * the array, hence its size must be divisible by 4. <br>
+ * The expected info is the following:
+ * <ul>
+ * <li> Column Name
+ * <li> Display Name
+ * <li> OGNL expression
+ * <li> Sorting of the column. This is either a Boolean,
+ * or a String representation of a boolean.
+ * </ul>
+ * @param arrColumnInfo
+ */
+ public ExpressionTableColumnModel(Object[] arrColumnInfo)
+ {
+ super(convertToColumns(arrColumnInfo));
+ }
+
+ /**
+ * Method convertToDetailedArray.
+ * @param arrColumnInfo
+ * @param bSorted
+ * @return Object[]
+ */
+ protected static Object[] convertToDetailedArray(String[] arrColumnInfo, boolean bSorted)
+ {
+ int nColumns = arrColumnInfo.length / 2;
+ int nSize = nColumns * 4;
+ Object[] arrDetailedInfo = new Object[nSize];
+
+ for (int i = 0; i < nColumns; i++)
+ {
+ int nInputBaseIndex = 2 * i;
+ String strColumnName = arrColumnInfo[nInputBaseIndex];
+ String strExpression = arrColumnInfo[nInputBaseIndex + 1];
+
+ int nOutputBaseIndex = 4 * i;
+ arrDetailedInfo[nOutputBaseIndex] = strColumnName;
+ arrDetailedInfo[nOutputBaseIndex + 1] = strColumnName;
+ arrDetailedInfo[nOutputBaseIndex + 2] = strExpression;
+ arrDetailedInfo[nOutputBaseIndex + 3] = bSorted ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ return arrDetailedInfo;
+ }
+
+ /**
+ * Method convertToColumns.
+ * @param arrDetailedInfo
+ * @return ITableColumn[]
+ */
+ protected static ITableColumn[] convertToColumns(Object[] arrDetailedInfo)
+ {
+ int nColumns = arrDetailedInfo.length / 4;
+ ITableColumn[] arrColumns = new ITableColumn[nColumns];
+
+ for (int i = 0; i < nColumns; i++)
+ {
+ Object objTempValue;
+ int nBaseIndex = 4 * i;
+
+ String strColumnName = "";
+ objTempValue = arrDetailedInfo[nBaseIndex];
+ if (objTempValue != null)
+ strColumnName = objTempValue.toString();
+
+ String strDisplayName = "";
+ objTempValue = arrDetailedInfo[nBaseIndex + 1];
+ if (objTempValue != null)
+ strDisplayName = objTempValue.toString();
+
+ String strExpression = "";
+ objTempValue = arrDetailedInfo[nBaseIndex + 2];
+ if (objTempValue != null)
+ strExpression = objTempValue.toString();
+
+ boolean bSorted = false;
+ objTempValue = arrDetailedInfo[nBaseIndex + 3];
+ if (objTempValue != null)
+ {
+ if (objTempValue instanceof Boolean)
+ bSorted = ((Boolean) objTempValue).booleanValue();
+ else
+ bSorted = Boolean.getBoolean(objTempValue.toString());
+ }
+
+ arrColumns[i] =
+ new ExpressionTableColumn(strColumnName, strDisplayName, strExpression, bSorted);
+ }
+
+ return arrColumns;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/OgnlTableColumnEvaluator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/OgnlTableColumnEvaluator.java
new file mode 100644
index 0000000..f3f6b3a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/ognl/OgnlTableColumnEvaluator.java
@@ -0,0 +1,72 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.ognl;
+
+import ognl.Ognl;
+import ognl.OgnlException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.simple.ITableColumnEvaluator;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * @author mindbridge
+ *
+ */
+public class OgnlTableColumnEvaluator implements ITableColumnEvaluator
+{
+ private static final Log LOG =
+ LogFactory.getLog(ExpressionTableColumn.class);
+
+ private String m_strExpression;
+ transient private Object m_objParsedExpression = null;
+
+ public OgnlTableColumnEvaluator(String strExpression)
+ {
+ m_strExpression = strExpression;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.simple.ITableColumnEvaluator#getColumnValue(ITableColumn, Object)
+ */
+ public Object getColumnValue(ITableColumn objColumn, Object objRow)
+ {
+ // If no expression is given, then this is dummy column. Return something.
+ if (m_strExpression == null || m_strExpression.equals(""))
+ return "";
+
+ synchronized (this)
+ {
+ if (m_objParsedExpression == null)
+ m_objParsedExpression =
+ OgnlUtils.getParsedExpression(m_strExpression);
+ }
+
+ try
+ {
+ Object objValue = Ognl.getValue(m_objParsedExpression, objRow);
+ return objValue;
+ }
+ catch (OgnlException e)
+ {
+ LOG.error(
+ "Cannot use column expression '" + m_strExpression + "' in row",
+ e);
+ return "";
+ }
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ColumnComparator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ColumnComparator.java
new file mode 100644
index 0000000..2002f6e
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ColumnComparator.java
@@ -0,0 +1,65 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.util.Comparator;
+
+/**
+ * In order to provide more generic behaviour, ITableColumn
+ * has no "column value" concept. The comparator it returns
+ * compares two table rows, rather than values specific to the column.
+ * <p>
+ * SimpleTableColumn introduces the concept of "column value" and
+ * allows one to extract that "column value" from the row using
+ * the getColumnValue() method. In practice comparisons are also typically
+ * done between these values rather than the full row objects.
+ * <p>
+ * This comparator extracts the column values from the rows passed
+ * and uses the provided comparator to compare the values.
+ * It therefore allows a comparator designed for comparing column values to be
+ * quickly wrapped and used as a comparator comparing rows, which is what
+ * ITableColumn is expected to return.
+ * <p>
+ * Example:
+ * <p>
+ * objColumn.setComparator(new ColumnComparator(objColumn, objBeanComparator));
+ *
+ * @version $Id$
+ * @author mindbridge
+ *
+ */
+public class ColumnComparator implements Comparator
+{
+ private SimpleTableColumn m_objColumn;
+ private Comparator m_objComparator;
+
+ public ColumnComparator(SimpleTableColumn objColumn, Comparator objComparator)
+ {
+ m_objColumn = objColumn;
+ m_objComparator = objComparator;
+ }
+
+ /**
+ * @see java.util.Comparator#compare(Object, Object)
+ */
+ public int compare(Object objRow1, Object objRow2)
+ {
+ Object objValue1 = m_objColumn.getColumnValue(objRow1);
+ Object objValue2 = m_objColumn.getColumnValue(objRow2);
+
+ return m_objComparator.compare(objValue1, objValue2);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ITableColumnEvaluator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ITableColumnEvaluator.java
new file mode 100644
index 0000000..9eccdb8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/ITableColumnEvaluator.java
@@ -0,0 +1,28 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+
+/**
+ * @author mindbridge
+ *
+ */
+public interface ITableColumnEvaluator extends Serializable
+{
+ Object getColumnValue(ITableColumn objColumn, Object objRow);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleListTableDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleListTableDataModel.java
new file mode 100644
index 0000000..f9c0983
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleListTableDataModel.java
@@ -0,0 +1,143 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.tapestry.contrib.table.model.CTableDataModelEvent;
+import org.apache.tapestry.contrib.table.model.common.AbstractTableDataModel;
+import org.apache.tapestry.contrib.table.model.common.ArrayIterator;
+
+/**
+ * A minimal list implementation of the
+ * {@link org.apache.tapestry.contrib.table.model.ITableDataModel} interface
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleListTableDataModel extends AbstractTableDataModel implements Serializable
+{
+ private List m_arrRows;
+
+ public SimpleListTableDataModel(Object[] arrRows)
+ {
+ this(Arrays.asList(arrRows));
+ }
+
+ public SimpleListTableDataModel(List arrRows)
+ {
+ m_arrRows = arrRows;
+ }
+
+ public SimpleListTableDataModel(Collection arrRows)
+ {
+ m_arrRows = new ArrayList(arrRows);
+ }
+
+ public SimpleListTableDataModel(Iterator objRows)
+ {
+ m_arrRows = new ArrayList();
+ CollectionUtils.addAll(m_arrRows, objRows);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#getRowCount()
+ */
+ public int getRowCount()
+ {
+ return m_arrRows.size();
+ }
+
+ /**
+ * Returns the row element at the given position
+ * @param nRow the index of the element to return
+ */
+ public Object getRow(int nRow)
+ {
+ if (nRow < 0 || nRow >= m_arrRows.size())
+ {
+ // error message
+ return null;
+ }
+ return m_arrRows.get(nRow);
+ }
+
+ /**
+ * Returns an Iterator with the elements from the given range
+ * @param nFrom the start of the range (inclusive)
+ * @param nTo the stop of the range (exclusive)
+ */
+ public Iterator getRows(int nFrom, int nTo)
+ {
+ return new ArrayIterator(m_arrRows.toArray(), nFrom, nTo);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#getRows()
+ */
+ public Iterator getRows()
+ {
+ return m_arrRows.iterator();
+ }
+
+ /**
+ * Method addRow.
+ * Adds a row object to the model at its end
+ * @param objRow the row object to add
+ */
+ public void addRow(Object objRow)
+ {
+ m_arrRows.add(objRow);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ public void addRows(Collection arrRows)
+ {
+ m_arrRows.addAll(arrRows);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ /**
+ * Method removeRow.
+ * Removes a row object from the model
+ * @param objRow the row object to remove
+ */
+ public void removeRow(Object objRow)
+ {
+ m_arrRows.remove(objRow);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ public void removeRows(Collection arrRows)
+ {
+ m_arrRows.removeAll(arrRows);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleSetTableDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleSetTableDataModel.java
new file mode 100644
index 0000000..fbe6fe6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleSetTableDataModel.java
@@ -0,0 +1,101 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.tapestry.contrib.table.model.CTableDataModelEvent;
+import org.apache.tapestry.contrib.table.model.common.AbstractTableDataModel;
+
+/**
+ * A minimal set implementation of the
+ * {@link org.apache.tapestry.contrib.table.model.ITableDataModel} interface
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleSetTableDataModel extends AbstractTableDataModel implements Serializable
+{
+ private Set m_setRows;
+
+ public SimpleSetTableDataModel(Set setRows)
+ {
+ m_setRows = setRows;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#getRowCount()
+ */
+ public int getRowCount()
+ {
+ return m_setRows.size();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableDataModel#getRows()
+ */
+ public Iterator getRows()
+ {
+ return m_setRows.iterator();
+ }
+
+ /**
+ * Method addRow.
+ * Adds a row object to the model at its end
+ * @param objRow the row object to add
+ */
+ public void addRow(Object objRow)
+ {
+ if (m_setRows.contains(objRow)) return;
+ m_setRows.add(objRow);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ public void addRows(Collection arrRows)
+ {
+ m_setRows.addAll(arrRows);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ /**
+ * Method removeRow.
+ * Removes a row object from the model
+ * @param objRow the row object to remove
+ */
+ public void removeRow(Object objRow)
+ {
+ if (!m_setRows.contains(objRow)) return;
+ m_setRows.remove(objRow);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+ public void removeRows(Collection arrRows)
+ {
+ m_setRows.removeAll(arrRows);
+
+ CTableDataModelEvent objEvent = new CTableDataModelEvent();
+ fireTableDataModelEvent(objEvent);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumn.java
new file mode 100644
index 0000000..aeb8513
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumn.java
@@ -0,0 +1,235 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.contrib.table.model.common.AbstractTableColumn;
+
+/**
+ * A simple minimal implementation of the
+ * {@link org.apache.tapestry.contrib.table.model.ITableColumn} interface that
+ * provides all the basic services for displaying a column.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableColumn extends AbstractTableColumn
+{
+ public static final ITableRendererSource DEFAULT_COLUMN_RENDERER_SOURCE =
+ new SimpleTableColumnRendererSource();
+
+ public static final ITableRendererSource FORM_COLUMN_RENDERER_SOURCE =
+ new SimpleTableColumnFormRendererSource();
+
+ public static final ITableRendererSource DEFAULT_VALUE_RENDERER_SOURCE =
+ new SimpleTableValueRendererSource();
+
+ private String m_strDisplayName;
+ private ITableColumnEvaluator m_objEvaluator;
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name and display name of the column
+ */
+ public SimpleTableColumn(String strColumnName)
+ {
+ this(strColumnName, strColumnName);
+ }
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name and display name of the column
+ * @param bSortable whether the column is sortable
+ */
+ public SimpleTableColumn(String strColumnName, boolean bSortable)
+ {
+ this(strColumnName, strColumnName, bSortable);
+ }
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name and display name of the column
+ * @param bSortable whether the column is sortable
+ * @param objEvaluator the evaluator to extract the column value from the row
+ */
+ public SimpleTableColumn(
+ String strColumnName,
+ ITableColumnEvaluator objEvaluator,
+ boolean bSortable)
+ {
+ this(strColumnName, strColumnName, objEvaluator, bSortable);
+ }
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name of the column
+ * @param strDisplayName the display name of the column
+ */
+ public SimpleTableColumn(String strColumnName, String strDisplayName)
+ {
+ this(strColumnName, strDisplayName, false);
+ }
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name of the column
+ * @param strDisplayName the display name of the column
+ * @param bSortable whether the column is sortable
+ */
+ public SimpleTableColumn(
+ String strColumnName,
+ String strDisplayName,
+ boolean bSortable)
+ {
+ this(strColumnName, strDisplayName, null, bSortable);
+ }
+
+ /**
+ * Creates a SimpleTableColumn
+ * @param strColumnName the identifying name of the column
+ * @param strDisplayName the display name of the column
+ * @param bSortable whether the column is sortable
+ * @param objEvaluator the evaluator to extract the column value from the row
+ */
+ public SimpleTableColumn(
+ String strColumnName,
+ String strDisplayName,
+ ITableColumnEvaluator objEvaluator,
+ boolean bSortable)
+ {
+ super(strColumnName, bSortable, null);
+ setComparator(new DefaultTableComparator());
+ setDisplayName(strDisplayName);
+ setColumnRendererSource(DEFAULT_COLUMN_RENDERER_SOURCE);
+ setValueRendererSource(DEFAULT_VALUE_RENDERER_SOURCE);
+ setEvaluator(objEvaluator);
+ }
+
+ /**
+ * Returns the display name of the column that will be used
+ * in the table header.
+ * Override for internationalization.
+ * @return String the display name of the column
+ */
+ public String getDisplayName()
+ {
+ return m_strDisplayName;
+ }
+
+ /**
+ * Sets the displayName.
+ * @param displayName The displayName to set
+ */
+ public void setDisplayName(String displayName)
+ {
+ m_strDisplayName = displayName;
+ }
+
+ /**
+ * Returns the evaluator.
+ * @return ITableColumnEvaluator
+ */
+ public ITableColumnEvaluator getEvaluator()
+ {
+ return m_objEvaluator;
+ }
+
+ /**
+ * Sets the evaluator.
+ * @param evaluator The evaluator to set
+ */
+ public void setEvaluator(ITableColumnEvaluator evaluator)
+ {
+ m_objEvaluator = evaluator;
+ }
+
+ /**
+ * Sets a comparator that compares the values of this column rather than
+ * the objects representing the full rows. <br>
+ * This method allows easier use of standard comparators for sorting
+ * the column. It simply wraps the provided comparator with a row-to-column
+ * convertor and invokes the setComparator() method.
+ * @param comparator The column value comparator
+ */
+ public void setColumnComparator(Comparator comparator)
+ {
+ setComparator(new ColumnComparator(this, comparator));
+ }
+
+ /**
+ * Extracts the value of the column from the row object
+ * @param objRow the row object
+ * @return Object the column value
+ */
+ public Object getColumnValue(Object objRow)
+ {
+ ITableColumnEvaluator objEvaluator = getEvaluator();
+ if (objEvaluator != null)
+ return objEvaluator.getColumnValue(this, objRow);
+
+ // default fallback
+ return objRow.toString();
+ }
+
+ /**
+ * Use the column name to get the display name, as well as
+ * the column and value renderer sources from the provided component.
+ *
+ * @param objSettingsContainer the component from which to get the settings
+ */
+ public void loadSettings(IComponent objSettingsContainer)
+ {
+ String strDisplayName = objSettingsContainer.getMessages().getMessage(getColumnName(), null);
+ if (strDisplayName != null)
+ setDisplayName(strDisplayName);
+
+ super.loadSettings(objSettingsContainer);
+ }
+
+
+ public class DefaultTableComparator implements Comparator, Serializable
+ {
+ public int compare(Object objRow1, Object objRow2)
+ {
+ Object objValue1 = getColumnValue(objRow1);
+ Object objValue2 = getColumnValue(objRow2);
+
+ if (objValue1 == objValue2)
+ return 0;
+
+ boolean bComparable1 = objValue1 instanceof Comparable;
+ boolean bComparable2 = objValue2 instanceof Comparable;
+
+ // non-comparable values are considered equal
+ if (!bComparable1 && !bComparable2)
+ return 0;
+
+ // non-comparable values (null included) are considered smaller
+ // than the comparable ones
+ if (!bComparable1)
+ return -1;
+
+ if (!bComparable2)
+ return 1;
+
+ return ((Comparable) objValue1).compareTo(objValue2);
+ }
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnFormRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnFormRendererSource.java
new file mode 100644
index 0000000..8c43b9a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnFormRendererSource.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.contrib.table.model.common.ComponentTableRendererSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * This is a simple implementation of
+ * {@link org.apache.tapestry.contrib.table.model.ITableRendererSource}
+ * that returns a standard renderer of a column header. <p>
+ *
+ * This implementation requires that the column passed is of type SimpleTableColumn
+ *
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class SimpleTableColumnFormRendererSource implements ITableRendererSource
+{
+ private ComponentTableRendererSource m_objComponentRenderer;
+
+ public SimpleTableColumnFormRendererSource()
+ {
+ m_objComponentRenderer = null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ synchronized (this)
+ {
+ if (m_objComponentRenderer == null)
+ {
+ ComponentAddress objAddress =
+ new ComponentAddress(
+ objSource.getNamespace(),
+ "SimpleTableColumnPage",
+ "tableColumnFormComponent");
+ m_objComponentRenderer =
+ new ComponentTableRendererSource(objAddress);
+ }
+ }
+
+ return m_objComponentRenderer.getRenderer(
+ objCycle,
+ objSource,
+ objColumn,
+ objRow);
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnModel.java
new file mode 100644
index 0000000..7c519db
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnModel.java
@@ -0,0 +1,80 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.common.ArrayIterator;
+
+/**
+ * A minimal implementation of the
+ * {@link org.apache.tapestry.contrib.table.model.ITableColumnModel} interface
+ * that stores columns as an array.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableColumnModel implements ITableColumnModel, Serializable
+{
+
+ private ITableColumn[] m_arrColumns;
+ private Map m_mapColumns;
+
+ public SimpleTableColumnModel(ITableColumn[] arrColumns)
+ {
+ m_arrColumns = arrColumns;
+
+ m_mapColumns = new HashMap();
+ for (int i = 0; i < m_arrColumns.length; i++)
+ m_mapColumns.put(m_arrColumns[i].getColumnName(), m_arrColumns[i]);
+ }
+
+ public SimpleTableColumnModel(List arrColumns)
+ {
+ this((ITableColumn[]) arrColumns.toArray(new ITableColumn[arrColumns.size()]));
+ }
+
+ public int getColumnCount()
+ {
+ return m_arrColumns.length;
+ }
+
+ public ITableColumn getColumn(int nColumn)
+ {
+ if (nColumn < 0 || nColumn >= m_arrColumns.length)
+ {
+ // error message
+ return null;
+ }
+ return m_arrColumns[nColumn];
+ }
+
+ public ITableColumn getColumn(String strColumn)
+ {
+ return (ITableColumn) m_mapColumns.get(strColumn);
+ }
+
+ public Iterator getColumns()
+ {
+ return new ArrayIterator(m_arrColumns);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnRendererSource.java
new file mode 100644
index 0000000..ee0658e
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableColumnRendererSource.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.contrib.table.model.common.ComponentTableRendererSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * This is a simple implementation of
+ * {@link org.apache.tapestry.contrib.table.model.ITableRendererSource}
+ * that returns a standard renderer of a column header. <p>
+ *
+ * This implementation requires that the column passed is of type SimpleTableColumn
+ *
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class SimpleTableColumnRendererSource implements ITableRendererSource
+{
+ private ComponentTableRendererSource m_objComponentRenderer;
+
+ public SimpleTableColumnRendererSource()
+ {
+ m_objComponentRenderer = null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ synchronized (this)
+ {
+ if (m_objComponentRenderer == null)
+ {
+ ComponentAddress objAddress =
+ new ComponentAddress(
+ objSource.getNamespace(),
+ "SimpleTableColumnPage",
+ "tableColumnComponent");
+ m_objComponentRenderer =
+ new ComponentTableRendererSource(objAddress);
+ }
+ }
+
+ return m_objComponentRenderer.getRenderer(
+ objCycle,
+ objSource,
+ objColumn,
+ objRow);
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableModel.java
new file mode 100644
index 0000000..f1cd13d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableModel.java
@@ -0,0 +1,180 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+
+import org.apache.tapestry.contrib.table.model.CTableDataModelEvent;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.ITableDataModel;
+import org.apache.tapestry.contrib.table.model.ITableDataModelListener;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+import org.apache.tapestry.contrib.table.model.common.AbstractTableModel;
+import org.apache.tapestry.contrib.table.model.common.ArrayIterator;
+import org.apache.tapestry.contrib.table.model.common.ReverseComparator;
+
+/**
+ * A simple generic table model implementation.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableModel extends AbstractTableModel implements ITableDataModelListener
+{
+ private ITableDataModel m_objDataModel = null;
+ private Object[] m_arrRows = null;
+ private ITableColumnModel m_objColumnModel = null;
+
+ private SimpleTableSortingState m_objLastSortingState;
+
+ public SimpleTableModel(Object[] arrData, ITableColumn[] arrColumns)
+ {
+ this(new SimpleListTableDataModel(arrData), new SimpleTableColumnModel(arrColumns));
+ }
+
+ public SimpleTableModel(Object[] arrData, ITableColumnModel objColumnModel)
+ {
+ this(new SimpleListTableDataModel(arrData), objColumnModel);
+ }
+
+ public SimpleTableModel(ITableDataModel objDataModel, ITableColumnModel objColumnModel)
+ {
+ this(objDataModel, objColumnModel, new SimpleTableState());
+ }
+
+ public SimpleTableModel(ITableDataModel objDataModel, ITableColumnModel objColumnModel, SimpleTableState objState)
+ {
+ super(objState);
+
+ m_arrRows = null;
+ m_objColumnModel = objColumnModel;
+ m_objLastSortingState = new SimpleTableSortingState();
+
+ setDataModel(objDataModel);
+ }
+
+ public ITableColumnModel getColumnModel()
+ {
+ return m_objColumnModel;
+ }
+
+ public Iterator getCurrentPageRows()
+ {
+ sortRows();
+
+ int nPageSize = getPagingState().getPageSize();
+ if (nPageSize <= 0)
+ return new ArrayIterator(m_arrRows);
+
+ int nCurrentPage = getPagingState().getCurrentPage();
+ int nFrom = nCurrentPage * nPageSize;
+ int nTo = (nCurrentPage + 1) * nPageSize;
+
+ return new ArrayIterator(m_arrRows, nFrom, nTo);
+ }
+
+ public int getRowCount()
+ {
+ updateRows();
+ return m_arrRows.length;
+ }
+
+ private void updateRows()
+ {
+ // If it is not null, then there is no need to extract the data
+ if (m_arrRows != null)
+ return;
+
+ // Extract the data from the model
+ m_objLastSortingState = new SimpleTableSortingState();
+
+ int nRowCount = m_objDataModel.getRowCount();
+ Object[] arrRows = new Object[nRowCount];
+
+ int i = 0;
+ for (Iterator it = m_objDataModel.getRows(); it.hasNext();)
+ arrRows[i++] = it.next();
+
+ m_arrRows = arrRows;
+ }
+
+ protected void sortRows()
+ {
+ updateRows();
+
+ ITableSortingState objSortingState = getSortingState();
+
+ // see if there is sorting required
+ String strSortColumn = objSortingState.getSortColumn();
+ if (strSortColumn == null)
+ return;
+
+ boolean bSortOrder = objSortingState.getSortOrder();
+
+ // See if the table is already sorted this way. If so, return.
+ if (strSortColumn.equals(m_objLastSortingState.getSortColumn())
+ && m_objLastSortingState.getSortOrder() == bSortOrder)
+ return;
+
+ ITableColumn objColumn = getColumnModel().getColumn(strSortColumn);
+ if (objColumn == null || !objColumn.getSortable())
+ return;
+
+ Comparator objCmp = objColumn.getComparator();
+ if (objCmp == null)
+ return;
+
+ // Okay, we have everything in place. Sort the rows.
+ if (bSortOrder == ITableSortingState.SORT_DESCENDING)
+ objCmp = new ReverseComparator(objCmp);
+
+ Arrays.sort(m_arrRows, objCmp);
+
+ m_objLastSortingState.setSortColumn(strSortColumn, bSortOrder);
+ }
+
+ public void tableDataChanged(CTableDataModelEvent objEvent)
+ {
+ m_arrRows = null;
+ }
+
+ /**
+ * Returns the dataModel.
+ * @return ITableDataModel
+ */
+ public ITableDataModel getDataModel()
+ {
+ return m_objDataModel;
+ }
+
+ /**
+ * Sets the dataModel.
+ * @param dataModel The dataModel to set
+ */
+ public void setDataModel(ITableDataModel dataModel)
+ {
+ if (m_objDataModel != null)
+ m_objDataModel.removeTableDataModelListener(this);
+
+ m_objDataModel = dataModel;
+ m_objDataModel.addTableDataModelListener(this);
+
+ m_arrRows = null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTablePagingState.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTablePagingState.java
new file mode 100644
index 0000000..5968e6c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTablePagingState.java
@@ -0,0 +1,77 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITablePagingState;
+
+/**
+ * A minimal implementation of
+ * {@link org.apache.tapestry.contrib.table.model.ITablePagingState}.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTablePagingState implements ITablePagingState, Serializable
+{
+ private final static int DEFAULT_PAGE_SIZE = 10;
+
+ private int m_nPageSize;
+ private int m_nCurrentPage;
+
+ public SimpleTablePagingState()
+ {
+ m_nPageSize = DEFAULT_PAGE_SIZE;
+ m_nCurrentPage = 0;
+ }
+
+ /**
+ * Returns the pageSize.
+ * @return int
+ */
+ public int getPageSize()
+ {
+ return m_nPageSize;
+ }
+
+ /**
+ * Sets the pageSize.
+ * @param pageSize The pageSize to set
+ */
+ public void setPageSize(int pageSize)
+ {
+ m_nPageSize = pageSize;
+ }
+
+ /**
+ * Returns the currentPage.
+ * @return int
+ */
+ public int getCurrentPage()
+ {
+ return m_nCurrentPage;
+ }
+
+ /**
+ * Sets the currentPage.
+ * @param currentPage The currentPage to set
+ */
+ public void setCurrentPage(int currentPage)
+ {
+ m_nCurrentPage = currentPage;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSessionStateManager.java
new file mode 100644
index 0000000..65e0b9f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSessionStateManager.java
@@ -0,0 +1,69 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.ITableDataModel;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+
+/**
+ * A {@link org.apache.tapestry.contrib.table.model.ITableSessionStateManager}
+ * implementation that saves only the paging and sorting state of the table model
+ * into the session.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableSessionStateManager
+ implements ITableSessionStateManager
+{
+ private ITableDataModel m_objDataModel;
+ private ITableColumnModel m_objColumnModel;
+
+ public SimpleTableSessionStateManager(
+ ITableDataModel objDataModel,
+ ITableColumnModel objColumnModel)
+ {
+ m_objDataModel = objDataModel;
+ m_objColumnModel = objColumnModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#getSessionState(ITableModel)
+ */
+ public Serializable getSessionState(ITableModel objModel)
+ {
+ SimpleTableModel objSimpleModel = (SimpleTableModel) objModel;
+ return objSimpleModel.getState();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableSessionStateManager#recreateTableModel(Serializable)
+ */
+ public ITableModel recreateTableModel(Serializable objState)
+ {
+ if (objState == null)
+ return null;
+ SimpleTableState objSimpleState = (SimpleTableState) objState;
+ return new SimpleTableModel(
+ m_objDataModel,
+ m_objColumnModel,
+ objSimpleState);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSortingState.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSortingState.java
new file mode 100644
index 0000000..89d5307
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableSortingState.java
@@ -0,0 +1,69 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+
+/**
+ * A minimal implementation of
+ * {@link org.apache.tapestry.contrib.table.model.ITableSortingState}
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableSortingState
+ implements ITableSortingState, Serializable
+{
+ private String m_strSortColumn;
+ private boolean m_bSortOrder;
+
+ public SimpleTableSortingState()
+ {
+ m_strSortColumn = null; // no sorting
+ m_bSortOrder = ITableSortingState.SORT_ASCENDING;
+ // irrelevant, but anyway
+ }
+
+ /**
+ * Returns the SortOrder.
+ * @return boolean
+ */
+ public boolean getSortOrder()
+ {
+ return m_bSortOrder;
+ }
+
+ /**
+ * Returns the SortColumn.
+ * @return int
+ */
+ public String getSortColumn()
+ {
+ return m_strSortColumn;
+ }
+
+ /**
+ * Sets the SortColumn.
+ * @param strSortColumn The SortColumn to set
+ */
+ public void setSortColumn(String strSortColumn, boolean bSortOrder)
+ {
+ m_strSortColumn = strSortColumn;
+ m_bSortOrder = bSortOrder;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableState.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableState.java
new file mode 100644
index 0000000..628df07
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableState.java
@@ -0,0 +1,64 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.table.model.ITablePagingState;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+
+/**
+ * A container holding all of the table model states.
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleTableState implements Serializable
+{
+ private ITablePagingState m_objPagingState;
+ private ITableSortingState m_objSortingState;
+
+ public SimpleTableState()
+ {
+ this(new SimpleTablePagingState(), new SimpleTableSortingState());
+ }
+
+ public SimpleTableState(
+ ITablePagingState objPagingState,
+ ITableSortingState objSortingState)
+ {
+ m_objPagingState = objPagingState;
+ m_objSortingState = objSortingState;
+ }
+
+ /**
+ * Returns the pagingState.
+ * @return ITablePagingState
+ */
+ public ITablePagingState getPagingState()
+ {
+ return m_objPagingState;
+ }
+
+ /**
+ * Returns the sortingState.
+ * @return ITableSortingState
+ */
+ public ITableSortingState getSortingState()
+ {
+ return m_objSortingState;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableValueRendererSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableValueRendererSource.java
new file mode 100644
index 0000000..5214c43
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/simple/SimpleTableValueRendererSource.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.simple;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.valid.RenderString;
+
+/**
+ * This is a simple implementation of
+ * {@link org.apache.tapestry.contrib.table.model.ITableRendererSource}
+ * that returns a standard renderer of a column value.
+ *
+ * This implementation requires that the column passed is of type SimpleTableColumn
+ *
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.3
+ */
+public class SimpleTableValueRendererSource implements ITableRendererSource
+{
+ /**
+ * The representation of null values. This is geared towards HTML, but will
+ * work for some other *ML languages as well. In any case, changing the
+ * column's value renderer allows selecting fully custom rendering behaviour.
+ **/
+ private static final String EMPTY_REPRESENTATION = " ";
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ SimpleTableColumn objSimpleColumn = (SimpleTableColumn) objColumn;
+
+ Object objValue = objSimpleColumn.getColumnValue(objRow);
+ if (objValue == null)
+ return new RenderString(EMPTY_REPRESENTATION, true);
+
+ return new RenderString(objValue.toString());
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlConnectionSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlConnectionSource.java
new file mode 100644
index 0000000..fe2ab44
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlConnectionSource.java
@@ -0,0 +1,29 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ISqlConnectionSource
+{
+ Connection obtainConnection() throws SQLException;
+ void returnConnection(Connection objConnection);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlTableDataSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlTableDataSource.java
new file mode 100644
index 0000000..3ea4abd
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ISqlTableDataSource.java
@@ -0,0 +1,35 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public interface ISqlTableDataSource
+{
+ int getRowCount() throws SQLException;
+ ResultSet getCurrentRows(
+ SqlTableColumnModel objColumnModel,
+ SimpleTableState objState)
+ throws SQLException;
+ void closeResultSet(ResultSet objResultSet);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ResultSetIterator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ResultSetIterator.java
new file mode 100644
index 0000000..87174d5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/ResultSetIterator.java
@@ -0,0 +1,123 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class ResultSetIterator implements Iterator
+{
+ private static final Log LOG = LogFactory.getLog(ResultSetIterator.class);
+
+ private ResultSet m_objResultSet;
+ private boolean m_bFetched;
+ private boolean m_bAvailable;
+
+ public ResultSetIterator(ResultSet objResultSet)
+ {
+ m_objResultSet = objResultSet;
+ m_bFetched = false;
+ }
+
+ /**
+ * @see java.util.Iterator#hasNext()
+ */
+ public synchronized boolean hasNext()
+ {
+ if (getResultSet() == null) return false;
+
+ if (!m_bFetched)
+ {
+ m_bFetched = true;
+
+ try
+ {
+ m_bAvailable = !getResultSet().isLast();
+ }
+ catch (SQLException e)
+ {
+ LOG.warn(
+ "SQLException while testing for end of the ResultSet",
+ e);
+ m_bAvailable = false;
+ }
+
+ if (!m_bAvailable)
+ notifyEnd();
+ }
+
+ return m_bAvailable;
+ }
+
+ /**
+ * @see java.util.Iterator#next()
+ */
+ public synchronized Object next()
+ {
+ ResultSet objResultSet = getResultSet();
+
+ try
+ {
+ if (!objResultSet.next())
+ return null;
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("SQLException while iterating over the ResultSet", e);
+ return null;
+ }
+
+ m_bFetched = false;
+ return objResultSet;
+ }
+
+ /**
+ * @see java.util.Iterator#remove()
+ */
+ public void remove()
+ {
+ try
+ {
+ getResultSet().deleteRow();
+ }
+ catch (SQLException e)
+ {
+ LOG.error("Cannot delete record", e);
+ }
+ }
+
+ /**
+ * Returns the resultSet.
+ * @return ResultSet
+ */
+ public ResultSet getResultSet()
+ {
+ return m_objResultSet;
+ }
+
+ protected void notifyEnd()
+ {
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlConnectionSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlConnectionSource.java
new file mode 100644
index 0000000..9b64253
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlConnectionSource.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @version $Id : $
+ * @author mindbridge
+ */
+public class SimpleSqlConnectionSource implements ISqlConnectionSource
+{
+ private static final Log LOG =
+ LogFactory.getLog(SimpleSqlConnectionSource.class);
+
+ private String m_strUrl;
+ private String m_strUser;
+ private String m_strPwd;
+
+ public SimpleSqlConnectionSource(String strUrl)
+ {
+ this(strUrl, null, null);
+ }
+
+ public SimpleSqlConnectionSource(
+ String strUrl,
+ String strUser,
+ String strPwd)
+ {
+ m_strUrl = strUrl;
+ m_strUser = strUser;
+ m_strPwd = strPwd;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.sql.ISqlConnectionSource#obtainConnection()
+ */
+ public Connection obtainConnection() throws SQLException
+ {
+ if (m_strUser == null)
+ return DriverManager.getConnection(m_strUrl);
+ else
+ return DriverManager.getConnection(m_strUrl, m_strUser, m_strPwd);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.sql.ISqlConnectionSource#returnConnection(Connection)
+ */
+ public void returnConnection(Connection objConnection)
+ {
+ try
+ {
+ objConnection.close();
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("Could not close connection", e);
+ }
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlTableDataSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlTableDataSource.java
new file mode 100644
index 0000000..224a6b2
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SimpleSqlTableDataSource.java
@@ -0,0 +1,266 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.contrib.table.model.ITablePagingState;
+import org.apache.tapestry.contrib.table.model.ITableSortingState;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SimpleSqlTableDataSource implements ISqlTableDataSource
+{
+ private static final Log LOG =
+ LogFactory.getLog(SimpleSqlTableDataSource.class);
+
+ private ISqlConnectionSource m_objConnSource;
+ private String m_strTableName;
+ private String m_strWhereClause;
+
+ public SimpleSqlTableDataSource(
+ ISqlConnectionSource objConnSource,
+ String strTableName)
+ {
+ this(objConnSource, strTableName, null);
+ }
+
+ public SimpleSqlTableDataSource(
+ ISqlConnectionSource objConnSource,
+ String strTableName,
+ String strWhereClause)
+ {
+ setConnSource(objConnSource);
+ setTableName(strTableName);
+ setWhereClause(strWhereClause);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.sql.ISqlTableDataSource#getRowCount()
+ */
+ public int getRowCount() throws SQLException
+ {
+ String strQuery = generateCountQuery();
+ LOG.trace("Invoking query to count rows: " + strQuery);
+
+ Connection objConn = getConnSource().obtainConnection();
+ try
+ {
+ Statement objStmt = objConn.createStatement();
+ try
+ {
+ ResultSet objRS = objStmt.executeQuery(strQuery);
+ objRS.next();
+ return objRS.getInt(1);
+ }
+ finally
+ {
+ objStmt.close();
+ }
+ }
+ finally
+ {
+ getConnSource().returnConnection(objConn);
+ }
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.sql.ISqlTableDataSource#getCurrentRows(SqlTableColumnModel, SimpleTableState)
+ */
+ public ResultSet getCurrentRows(
+ SqlTableColumnModel objColumnModel,
+ SimpleTableState objState)
+ throws SQLException
+ {
+ String strQuery = generateDataQuery(objColumnModel, objState);
+ LOG.trace("Invoking query to load current rows: " + strQuery);
+
+ Connection objConn = getConnSource().obtainConnection();
+ Statement objStmt = objConn.createStatement();
+ return objStmt.executeQuery(strQuery);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.sql.ISqlTableDataSource#closeResultSet(ResultSet)
+ */
+ public void closeResultSet(ResultSet objResultSet)
+ {
+ try
+ {
+ Statement objStmt = objResultSet.getStatement();
+ Connection objConn = objStmt.getConnection();
+ try
+ {
+ objResultSet.close();
+ objStmt.close();
+ }
+ catch (SQLException e)
+ {
+ // ignore
+ }
+ getConnSource().returnConnection(objConn);
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("Error while closing the result set", e);
+ }
+ }
+
+ protected String quoteObjectName(String strObject)
+ {
+ return strObject;
+ }
+
+ /**
+ * Returns the tableName.
+ * @return String
+ */
+ public String getTableName()
+ {
+ return m_strTableName;
+ }
+
+ /**
+ * Sets the tableName.
+ * @param tableName The tableName to set
+ */
+ public void setTableName(String tableName)
+ {
+ m_strTableName = tableName;
+ }
+
+ /**
+ * Returns the connSource.
+ * @return ISqlConnectionSource
+ */
+ public ISqlConnectionSource getConnSource()
+ {
+ return m_objConnSource;
+ }
+
+ /**
+ * Sets the connSource.
+ * @param connSource The connSource to set
+ */
+ public void setConnSource(ISqlConnectionSource connSource)
+ {
+ m_objConnSource = connSource;
+ }
+
+ /**
+ * Returns the whereClause.
+ * @return String
+ */
+ public String getWhereClause()
+ {
+ return m_strWhereClause;
+ }
+
+ /**
+ * Sets the whereClause.
+ * @param whereClause The whereClause to set
+ */
+ public void setWhereClause(String whereClause)
+ {
+ m_strWhereClause = whereClause;
+ }
+
+ protected String generateColumnList(SqlTableColumnModel objColumnModel)
+ {
+ // build the column selection
+ StringBuffer objColumnBuf = new StringBuffer();
+ for (int i = 0; i < objColumnModel.getColumnCount(); i++)
+ {
+ SqlTableColumn objColumn = objColumnModel.getSqlColumn(i);
+ if (i > 0)
+ objColumnBuf.append(", ");
+ objColumnBuf.append(quoteObjectName(objColumn.getColumnName()));
+ }
+
+ return objColumnBuf.toString();
+ }
+
+ protected String generateWhereClause()
+ {
+ String strWhereClause = getWhereClause();
+ if (strWhereClause == null || strWhereClause.equals(""))
+ return "";
+ return "WHERE " + strWhereClause + " ";
+ }
+
+ protected String generateOrderByClause(ITableSortingState objSortingState)
+ {
+ // build the sorting clause
+ StringBuffer objSortingBuf = new StringBuffer();
+ if (objSortingState.getSortColumn() != null)
+ {
+ objSortingBuf.append("ORDER BY ");
+ objSortingBuf.append(objSortingState.getSortColumn());
+ if (objSortingState.getSortOrder()
+ == ITableSortingState.SORT_ASCENDING)
+ objSortingBuf.append(" ASC ");
+ else
+ objSortingBuf.append(" DESC ");
+ }
+
+ return objSortingBuf.toString();
+ }
+
+ protected String generateLimitClause(ITablePagingState objPagingState)
+ {
+ int nPageSize = objPagingState.getPageSize();
+ int nStart = objPagingState.getCurrentPage() * nPageSize;
+ String strPagingBuf = "LIMIT " + nPageSize + " OFFSET " + nStart + " ";
+ return strPagingBuf;
+ }
+
+ protected String generateDataQuery(
+ SqlTableColumnModel objColumnModel,
+ SimpleTableState objState)
+ {
+ String strQuery =
+ "SELECT "
+ + generateColumnList(objColumnModel)
+ + " FROM "
+ + getTableName()
+ + " "
+ + generateWhereClause()
+ + generateOrderByClause(objState.getSortingState())
+ + generateLimitClause(objState.getPagingState());
+
+ return strQuery;
+ }
+
+ protected String generateCountQuery()
+ {
+ String strQuery =
+ "SELECT COUNT(*) FROM "
+ + getTableName()
+ + " "
+ + generateWhereClause();
+
+ return strQuery;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumn.java
new file mode 100644
index 0000000..9698bcb
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumn.java
@@ -0,0 +1,78 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SqlTableColumn extends SimpleTableColumn
+{
+ private static final Log LOG = LogFactory.getLog(SqlTableColumn.class);
+
+ /**
+ * Creates an SqlTableColumn
+ * @param strSqlField the identifying name of the column and the SQL field it refers to
+ * @param strDisplayName the display name of the column
+ */
+ public SqlTableColumn(String strSqlField, String strDisplayName)
+ {
+ super(strSqlField, strDisplayName);
+ }
+
+ /**
+ * Creates an SqlTableColumn
+ * @param strSqlField the identifying name of the column and the SQL field it refers to
+ * @param strDisplayName the display name of the column
+ * @param bSortable whether the column is sortable
+ */
+ public SqlTableColumn(
+ String strSqlField,
+ String strDisplayName,
+ boolean bSortable)
+ {
+ super(strSqlField, strDisplayName, bSortable);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn#getColumnValue(Object)
+ */
+ public Object getColumnValue(Object objRow)
+ {
+ try
+ {
+ ResultSet objRS = (ResultSet) objRow;
+ String strColumnName = getColumnName();
+ Object objValue = objRS.getObject(strColumnName);
+ if (objValue == null)
+ objValue = "";
+ return objValue;
+ }
+ catch (SQLException e)
+ {
+ LOG.error("Cannot get the value for column: " + getColumnName(), e);
+ return "";
+ }
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumnModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumnModel.java
new file mode 100644
index 0000000..8fe47e7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableColumnModel.java
@@ -0,0 +1,40 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
+
+/**
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SqlTableColumnModel extends SimpleTableColumnModel
+{
+ public SqlTableColumnModel(SqlTableColumn[] arrColumns)
+ {
+ super(arrColumns);
+ }
+
+ public SqlTableColumn getSqlColumn(int nColumn)
+ {
+ return (SqlTableColumn) getColumn(nColumn);
+ }
+
+ public SqlTableColumn getSqlColumn(String strColumn)
+ {
+ return (SqlTableColumn) getColumn(strColumn);
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableModel.java
new file mode 100644
index 0000000..61c3aec
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/table/model/sql/SqlTableModel.java
@@ -0,0 +1,156 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.table.model.sql;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.contrib.table.model.ITableColumnModel;
+import org.apache.tapestry.contrib.table.model.common.AbstractTableModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableState;
+
+/**
+ * An implementation of ITableModel that obtains its data through SQL queries.
+ * This is a very efficient model, since it uses SQL to perform
+ * the data sorting (through ORDER BY) and obtains only the data
+ * on the current page (through LIMIT/OFFSET).
+ * <p>
+ * This object is typically created in the following manner:
+ * <pre>
+ * ISqlConnectionSource objConnSrc =
+ * new SimpleSqlConnectionSource("jdbc:postgresql://localhost/testdb", "testdb", "testdb");
+ *
+ * ISqlTableDataSource objDataSrc =
+ * new SimpleSqlTableDataSource(objConnSrc, "test_table");
+ *
+ * SqlTableColumnModel objColumnModel =
+ * new SqlTableColumnModel(new SqlTableColumn[] {
+ * new SqlTableColumn("language", "Language", true),
+ * new SqlTableColumn("country", "Country", true),
+ * new SqlTableColumn("variant", "Variant", true),
+ * new SqlTableColumn("intvalue", "Integer", true),
+ * new SqlTableColumn("floatvalue", "Float", true)
+ * });
+ *
+ * ITableModel objTableModel = new SqlTableModel(objDataSrc, objColumnModel);
+ *
+ * return objTableModel;
+ * </pre>
+ *
+ * @version $Id$
+ * @author mindbridge
+ */
+public class SqlTableModel extends AbstractTableModel
+{
+ private static final Log LOG = LogFactory.getLog(SqlTableModel.class);
+
+ private ISqlTableDataSource m_objDataSource;
+ private SqlTableColumnModel m_objColumnModel;
+
+ {
+ try {
+ Class.forName ( "org.hsqldb.jdbcDriver" );
+ } catch (Exception e) {
+ System.out.println("ERROR: failed to load HSQLDB JDBC driver.");
+ e.printStackTrace();
+ }
+ }
+
+ public SqlTableModel(
+ ISqlTableDataSource objDataSource,
+ SqlTableColumnModel objColumnModel)
+ {
+ this(objDataSource, objColumnModel, new SimpleTableState());
+ }
+
+ public SqlTableModel(
+ ISqlTableDataSource objDataSource,
+ SqlTableColumnModel objColumnModel,
+ SimpleTableState objState)
+ {
+ super(objState);
+ m_objDataSource = objDataSource;
+ m_objColumnModel = objColumnModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getColumnModel()
+ */
+ public ITableColumnModel getColumnModel()
+ {
+ return m_objColumnModel;
+ }
+
+ public SqlTableColumnModel getSqlColumnModel()
+ {
+ return m_objColumnModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableModel#getCurrentPageRows()
+ */
+ public Iterator getCurrentPageRows()
+ {
+ try
+ {
+ ResultSet objResultSet =
+ getSqlDataSource().getCurrentRows(
+ getSqlColumnModel(),
+ getState());
+
+ return new ResultSetIterator(objResultSet)
+ {
+ protected void notifyEnd()
+ {
+ getSqlDataSource().closeResultSet(getResultSet());
+ }
+ };
+ }
+ catch (SQLException e)
+ {
+ LOG.error("Cannot get current page rows", e);
+ return new ResultSetIterator(null);
+ }
+ }
+
+ /**
+ * Returns the dataSource.
+ * @return ISqlTableDataSource
+ */
+ public ISqlTableDataSource getSqlDataSource()
+ {
+ return m_objDataSource;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableModel#getRowCount()
+ */
+ protected int getRowCount()
+ {
+ try
+ {
+ return m_objDataSource.getRowCount();
+ }
+ catch (SQLException e)
+ {
+ LOG.error("Cannot get row count", e);
+ return 1;
+ }
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/INodeRenderFactory.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/INodeRenderFactory.java
new file mode 100644
index 0000000..13f3ea3
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/INodeRenderFactory.java
@@ -0,0 +1,29 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface INodeRenderFactory
+{
+ IRender getRenderByID(Object objUniqueKey, ITreeModelSource objTreeModelSource, IRequestCycle objCycle);
+ IRender getRender(Object objValue, ITreeModelSource objTreeModelSource, IRequestCycle objCycle);
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/ITreeComponent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/ITreeComponent.java
new file mode 100644
index 0000000..b83fd5a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/ITreeComponent.java
@@ -0,0 +1,30 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeComponent {
+ ComponentAddress getComponentPath();
+ ITreeModelSource getTreeModelSource();
+ ITreeRowSource getTreeRowSource();
+ void resetState();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.html
new file mode 100644
index 0000000..863017b
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.html
@@ -0,0 +1,7 @@
+<span jwcid="$content$">
+ <span class="tree" jwcid="treeView">
+ <span jwcid="treeData">
+ <span jwcid="treeNodeValue"/>
+ </span>
+ </span>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.java
new file mode 100644
index 0000000..c0e54d0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class Tree extends BaseComponent implements ITreeComponent{
+
+ public Tree() {
+ super();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#getComponentPath()
+ */
+ public ComponentAddress getComponentPath() {
+ return new ComponentAddress(this);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#getTreeModelSource()
+ */
+ public ITreeModelSource getTreeModelSource() {
+ TreeView objTreeView = (TreeView)getComponent("treeView");
+ return objTreeView;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#resetState()
+ */
+ public void resetState() {
+ TreeView objTreeView = (TreeView)getComponent("treeView");
+ objTreeView.resetState();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#getTreeRowSource()
+ */
+ public ITreeRowSource getTreeRowSource() {
+ TreeDataView objTreeDataView = (TreeDataView)getComponent("treeData");
+ return objTreeDataView;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.jwc
new file mode 100644
index 0000000..d69529a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/Tree.jwc
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.Tree"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="sessionStateManager"
+ type="org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager"
+ direction="custom" required="no"/>
+
+ <parameter name="sessionStoreManager"
+ type="org.apache.tapestry.contrib.tree.model.ISessionStoreManager"
+ direction="custom" required="no"/>
+
+ <parameter name="treeModel"
+ type="org.apache.tapestry.contrib.tree.model.ITreeModel"
+ direction="custom" required="yes">
+ </parameter>
+
+ <parameter name="treeStateListener"
+ type="org.apache.tapestry.contrib.tree.model.ITreeStateListener"
+ direction="custom" required="no">
+ </parameter>
+
+ <parameter name="closeNodeImage" type="org.apache.tapestry.IAsset"
+ required="no" direction="in"/>
+ <parameter name="openNodeImage" type="org.apache.tapestry.IAsset"
+ required="no" direction="in"/>
+
+ <parameter name="showNodeImages" type="boolean" required="no"
+ direction="custom"/>
+ <parameter name="makeNodeDirect" type="boolean" required="no"
+ direction="custom"/>
+ <parameter name="nodeRenderFactory"
+ type="org.apache.tapestry.contrib.tree.components.INodeRenderFactory"
+ required="no" direction="custom"/>
+
+ <component id="treeView" type="TreeView">
+ <inherited-binding name="sessionStateManager" parameter-name="sessionStateManager"/>
+ <inherited-binding name="sessionStoreManager" parameter-name="sessionStoreManager"/>
+ <inherited-binding name="treeModel" parameter-name="treeModel"/>
+ <inherited-binding name="treeStateListener" parameter-name="treeStateListener"/>
+ </component>
+
+ <component id="treeData" type="TreeDataView">
+ <binding name="treeView" expression='components.treeView'/>
+ <!--inherited-binding name="value" parameter-name="value"/-->
+ </component>
+
+ <component id="treeNodeValue" type="TreeNodeView">
+ <inherited-binding name="closeNodeImage" parameter-name="closeNodeImage"/>
+ <inherited-binding name="openNodeImage" parameter-name="openNodeImage"/>
+ <inherited-binding name="showNodeImages" parameter-name="showNodeImages"/>
+ <inherited-binding name="makeNodeDirect" parameter-name="makeNodeDirect"/>
+ <inherited-binding name="nodeRenderFactory" parameter-name="nodeRenderFactory"/>
+
+ <binding name="treeDataView" expression='components.treeData'/>
+ </component>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.html
new file mode 100644
index 0000000..39a93c5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.html
@@ -0,0 +1,3 @@
+<!-- generated by Spindle, http://spindle.sourceforge.net -->
+
+<span jwcid="$content$"><span jwcid="wrapped"/></span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.java
new file mode 100644
index 0000000..6514d92
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.java
@@ -0,0 +1,123 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.tree.model.ITreeDataModel;
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.contrib.tree.model.TreeRowObject;
+
+/**
+ * @version $Id$
+ */
+public class TreeDataView extends BaseComponent implements ITreeRowSource{
+ private IBinding m_objTreeViewBinding;
+
+ private TreeRowObject m_objTreeRowObject = null;
+ private int m_nTreeDeep = -1;
+
+ public TreeDataView(){
+ super();
+ initialize();
+ }
+
+ public void initialize(){
+ m_objTreeRowObject = null;
+ m_nTreeDeep = -1;
+ }
+
+ /**
+ * Returns the treeViewBinding.
+ * @return IBinding
+ */
+ public IBinding getTreeViewBinding() {
+ return m_objTreeViewBinding;
+ }
+
+ /**
+ * Sets the treeViewBinding.
+ * @param treeViewBinding The treeViewBinding to set
+ */
+ public void setTreeViewBinding(IBinding treeViewBinding) {
+ m_objTreeViewBinding = treeViewBinding;
+ }
+
+ public TreeView getTreeView() {
+ return (TreeView) m_objTreeViewBinding.getObject();
+ }
+
+ public void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
+ // render data
+ Object objExistedTreeModelSource = cycle.getAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE);
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, this);
+
+ TreeView objView = getTreeView();
+ ITreeModel objTreeModel = objView.getTreeModel();
+ ITreeDataModel objTreeDataModel = objTreeModel.getTreeDataModel();
+ Object objValue = objTreeDataModel.getRoot();
+ Object objValueUID = objTreeDataModel.getUniqueKey(objValue, null);
+
+ // Object objSelectedNode = objTreeModel.getTreeStateModel().getSelectedNode();
+ //if(objSelectedNode == null)
+ // objTreeModel.getTreeStateModel().expand(objValueUID);
+
+ walkTree(objValue, objValueUID, 0, objTreeModel, writer, cycle);
+
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, objExistedTreeModelSource);
+ }
+
+ public void walkTree(Object objParent, Object objParentUID, int nDepth,
+ ITreeModel objTreeModel, IMarkupWriter writer,
+ IRequestCycle cycle) {
+ m_objTreeRowObject = new TreeRowObject(objParent, objParentUID, nDepth);
+ m_nTreeDeep = nDepth;
+
+ super.renderComponent(writer, cycle);
+
+ boolean bContain = objTreeModel.getTreeStateModel().isUniqueKeyExpanded(objParentUID);
+ if (bContain) {
+ for (Iterator iter = objTreeModel.getTreeDataModel().getChildren(objParent); iter.hasNext();) {
+ Object objChild = iter.next();
+ Object objChildUID = objTreeModel.getTreeDataModel().getUniqueKey(objChild, objParentUID);
+ walkTree(objChild, objChildUID, nDepth+1, objTreeModel, writer, cycle);
+ }
+ }
+ }
+
+ public int getTreeDeep() {
+ return m_nTreeDeep;
+ }
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeRowSource#getTreeRow()
+ */
+ public TreeRowObject getTreeRow() {
+ return getTreeRowObject();
+ }
+
+ public TreeRowObject getTreeRowObject() {
+ return m_objTreeRowObject;
+ }
+
+ public void setTreeRowObject(TreeRowObject object) {
+ m_objTreeRowObject = object;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.jwc
new file mode 100644
index 0000000..c513d45
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeDataView.jwc
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.TreeDataView"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="treeView"
+ type="org.apache.tapestry.contrib.tree.components.TreeView"
+ required="yes" direction="custom"/>
+
+ <parameter name="value" type="java.lang.Object" required="no" direction="custom"/>
+
+ <component id="wrapped" type="RenderBody"/>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.html
new file mode 100644
index 0000000..ab24cde
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.html
@@ -0,0 +1,19 @@
+<!-- generated by Spindle, http://spindle.sourceforge.net -->
+
+<span jwcid="$content$">
+ <span jwcid="offset">
+ <span jwcid="makeNodeDirect">
+ <a jwcid="direct">
+ <span jwcid="showImages">
+ <img jwcid="imageNode"/>
+ </span>
+ <span jwcid="insertValue"/></a><br>
+ </span>
+ <span jwcid="makeNodeNoDirect">
+ <span jwcid="showImages2">
+ <img jwcid="imageNode2"/>
+ </span>
+ <span jwcid="insertValue2"/><br>
+ </span>
+ </span>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.java
new file mode 100644
index 0000000..2db1d7f
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.java
@@ -0,0 +1,399 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.contrib.tree.model.ITreeStateListener;
+import org.apache.tapestry.contrib.tree.model.ITreeStateModel;
+import org.apache.tapestry.contrib.tree.model.TreeRowObject;
+import org.apache.tapestry.contrib.tree.model.TreeStateEvent;
+import org.apache.tapestry.contrib.tree.simple.SimpleNodeRenderFactory;
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.spec.ComponentSpecification;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @version $Id$
+ **/
+public class TreeNodeView extends BaseComponent implements PageDetachListener{
+ private static final Log LOG = LogFactory.getLog(TreeNodeView.class);
+
+ private IBinding m_objNodeRenderFactoryBinding;
+ private IBinding m_objShowNodeImagesBinding;
+ private IBinding m_objMakeNodeDirectBinding;
+ private Boolean m_objNodeState;
+ private Boolean m_objShowNodeImages;
+ private Boolean m_objMakeNodeDirect;
+ private INodeRenderFactory m_objNodeRenderFactory;
+
+ private IAsset m_objOpenNodeImage;
+ private IAsset m_objCloseNodeImage;
+
+ public TreeNodeView(){
+ super();
+ initialize();
+ }
+
+ private void initialize(){
+ m_objNodeState = null;
+ m_objShowNodeImages = null;
+ m_objNodeRenderFactory = null;
+ m_objMakeNodeDirect = null;
+ }
+
+ public IRender getCurrentRenderer(){
+ INodeRenderFactory objRenderFactory = getNodeRenderFactory();
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ return objRenderFactory.getRender(objTreeRowSource.getTreeRow().getTreeNode(),
+ getTreeModelSource(),
+ getPage().getRequestCycle());
+ }
+
+ public Object[] getNodeContext(){
+ ITreeModelSource objModelSource = getTreeModelSource();
+ ComponentAddress objModelSourceAddress = new ComponentAddress(objModelSource);
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ TreeRowObject objTreeRowObject = objTreeRowSource.getTreeRow();
+ Object objValueUID = objTreeRowObject.getTreeNodeUID();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("getNodeContext objValueUID = " + objValueUID);
+ }
+
+ return new Object[] { objValueUID, new Boolean(isNodeOpen()), objModelSourceAddress };
+ }
+
+ /**
+ * Called when a node in the tree is clicked by the user.
+ * If the node is expanded, it will be collapsed, and vice-versa,
+ * that is, the tree state model is retrieved, and it is told
+ * to collapse or expand the node.
+ *
+ * @param cycle The Tapestry request cycle object.
+ */
+ public void nodeSelect(IRequestCycle cycle) {
+ Object context[] = cycle.getServiceParameters();
+ Object objValueUID = null;
+ if (context != null && context.length > 0) {
+ objValueUID = context[0];
+ }
+ ComponentAddress objModelSourceAddress = (ComponentAddress)context[2];
+ ITreeModelSource objTreeModelSource = (ITreeModelSource) objModelSourceAddress.findComponent(cycle);
+ //ITreeModelSource objTreeModelSource = getTreeModelSource();
+ ITreeStateModel objStateModel = objTreeModelSource.getTreeModel().getTreeStateModel();
+ boolean bState = objStateModel.isUniqueKeyExpanded(objValueUID);
+
+ if (bState) {
+ objStateModel.collapse(objValueUID);
+ fireNodeCollapsed(objValueUID, objTreeModelSource);
+ } else {
+ objStateModel.expandPath(objValueUID);
+ fireNodeExpanded(objValueUID, objTreeModelSource);
+ }
+ }
+
+ private void fireNodeCollapsed(Object objValueUID, ITreeModelSource objTreeModelSource){
+ deliverEvent(TreeStateEvent.NODE_COLLAPSED, objValueUID, objTreeModelSource);
+
+ }
+
+ private void fireNodeExpanded(Object objValueUID, ITreeModelSource objTreeModelSource){
+ deliverEvent(TreeStateEvent.NODE_EXPANDED, objValueUID, objTreeModelSource);
+ }
+
+ private void deliverEvent(int nEventUID, Object objValueUID, ITreeModelSource objTreeModelSource){
+ ITreeStateListener objListener = objTreeModelSource.getTreeStateListener();
+ if(objListener != null){
+ TreeStateEvent objEvent = new TreeStateEvent(nEventUID, objValueUID, objTreeModelSource.getTreeModel().getTreeStateModel());
+ objListener.treeStateChanged(objEvent);
+ }
+
+ }
+
+ private void deliverEventOld(int nEventUID, Object objValueUID, ITreeStateModel objStateModel){
+ IBinding objBinding = getBinding("treeStateListener");
+ if(objBinding != null){
+ ITreeStateListener objListener = (ITreeStateListener)objBinding.getObject();
+ TreeStateEvent objEvent = new TreeStateEvent(nEventUID, objValueUID, objStateModel);
+ objListener.treeStateChanged(objEvent);
+ }
+
+ }
+
+ public void pageDetached(PageEvent arg0) {
+ initialize();
+ }
+
+ public void finishLoad(IRequestCycle objCycle, IPageLoader arg0, ComponentSpecification arg1)
+ {
+ super.finishLoad(objCycle, arg0, arg1);
+ getPage().addPageDetachListener(this);
+
+ m_objOpenNodeImage = getAsset("_openNodeImage");
+ m_objCloseNodeImage = getAsset("_closeNodeImage");
+ }
+
+ public boolean isNodeOpen() {
+ if(m_objNodeState == null){
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ TreeRowObject objTreeRowObject = objTreeRowSource.getTreeRow();
+ Object objValueUID = objTreeRowObject.getTreeNodeUID();
+ ITreeModelSource objTreeModelSource = getTreeModelSource();
+ ITreeStateModel objStateModel = objTreeModelSource.getTreeModel().getTreeStateModel();
+ boolean bState = objStateModel.isUniqueKeyExpanded(objValueUID);
+ m_objNodeState = new Boolean(bState);
+ }
+ return m_objNodeState.booleanValue();
+ }
+
+ /**
+ * Returns the openNodeImage.
+ * @return IAsset
+ */
+ public IAsset getNodeImage() {
+ if(isNodeOpen()) {
+ if (m_objOpenNodeImage == null) {
+ m_objOpenNodeImage = getAsset("_openNodeImage");
+ }
+ return m_objOpenNodeImage;
+ } else {
+ if (m_objCloseNodeImage == null) {
+ m_objCloseNodeImage = getAsset("_closeNodeImage");
+ }
+ return m_objCloseNodeImage;
+ }
+ }
+
+ /**
+ * Returns the closeNodeImage.
+ * @return IAsset
+ */
+ public IAsset getCloseNodeImage() {
+ return m_objCloseNodeImage;
+ }
+
+ /**
+ * Returns the openNodeImage.
+ * @return IAsset
+ */
+ public IAsset getOpenNodeImage() {
+ return m_objOpenNodeImage;
+ }
+
+ /**
+ * Sets the closeNodeImage.
+ * @param closeNodeImage The closeNodeImage to set
+ */
+ public void setCloseNodeImage(IAsset closeNodeImage) {
+ m_objCloseNodeImage = closeNodeImage;
+ }
+
+ /**
+ * Sets the openNodeImage.
+ * @param openNodeImage The openNodeImage to set
+ */
+ public void setOpenNodeImage(IAsset openNodeImage) {
+ m_objOpenNodeImage = openNodeImage;
+ }
+
+ /**
+ * @see
+ * org.apache.tapestry.AbstractComponent#renderComponent(IMarkupWriter,
+ * IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter arg0, IRequestCycle arg1) {
+ super.renderComponent(arg0, arg1);
+ m_objNodeState = null;
+ }
+
+ /**
+ * Returns the ShowNodeImagesBinding.
+ * @return IBinding
+ */
+ public IBinding getShowNodeImagesBinding() {
+ return m_objShowNodeImagesBinding;
+ }
+
+ /**
+ * Sets the ShowNodeImagesBinding.
+ * @param ShowNodeImagesBinding The ShowNodeImagesBinding to set
+ */
+ public void setShowNodeImagesBinding(IBinding ShowNodeImagesBinding) {
+ m_objShowNodeImagesBinding = ShowNodeImagesBinding;
+ }
+
+ /**
+ * Returns the ShowNodeImages.
+ * @return Boolean
+ */
+ public Boolean isShowNodeImages() {
+ if(m_objShowNodeImages == null){
+ if(getNodeRenderFactoryBinding() == null){
+ m_objShowNodeImages = Boolean.TRUE;
+ } else {
+ if(m_objShowNodeImagesBinding != null) {
+ m_objShowNodeImages = (Boolean)
+ m_objShowNodeImagesBinding.getObject();
+ } else {
+ m_objShowNodeImages = Boolean.TRUE;
+ }
+ }
+ }
+ return m_objShowNodeImages;
+ }
+
+ public boolean getShowImages(){
+ boolean bResult = isShowNodeImages().booleanValue();
+ return bResult;
+ }
+
+ public boolean getShowWithoutImages(){
+ boolean bResult = !isShowNodeImages().booleanValue();
+ return bResult;
+ }
+
+ public String getOffsetStyle() {
+ //return "width: " + getTreeDataView().getTreeDeep() * 15;
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ TreeRowObject objTreeRowObject = objTreeRowSource.getTreeRow();
+ int nTreeRowDepth = 0;
+ if(objTreeRowObject != null){
+ nTreeRowDepth = objTreeRowObject.getTreeRowDepth();
+ }
+ return "padding-left: " + nTreeRowDepth * 15+"px";
+ }
+
+ /**
+ * Returns the nodeRenderFactoryBinding.
+ * @return IBinding
+ */
+ public IBinding getNodeRenderFactoryBinding() {
+ return m_objNodeRenderFactoryBinding;
+ }
+
+ /**
+ * Sets the nodeRenderFactoryBinding.
+ * @param nodeRenderFactoryBinding The nodeRenderFactoryBinding to set
+ */
+ public void setNodeRenderFactoryBinding(IBinding nodeRenderFactoryBinding) {
+ m_objNodeRenderFactoryBinding = nodeRenderFactoryBinding;
+ }
+
+ public INodeRenderFactory getNodeRenderFactory() {
+ if(m_objNodeRenderFactory == null){
+ IBinding objBinding = getNodeRenderFactoryBinding();
+ if( objBinding != null){
+ m_objNodeRenderFactory = (INodeRenderFactory)objBinding.getObject();
+ }else{
+ m_objNodeRenderFactory = new SimpleNodeRenderFactory();
+ }
+ }
+ return m_objNodeRenderFactory;
+ }
+
+ /**
+ * Returns the makeNodeDirectBinding.
+ * @return IBinding
+ */
+ public IBinding getMakeNodeDirectBinding() {
+ return m_objMakeNodeDirectBinding;
+ }
+
+ /**
+ * Sets the makeNodeDirectBinding.
+ * @param makeNodeDirectBinding The makeNodeDirectBinding to set
+ */
+ public void setMakeNodeDirectBinding(IBinding makeNodeDirectBinding) {
+ m_objMakeNodeDirectBinding = makeNodeDirectBinding;
+ }
+
+ /**
+ * Returns the makeNodeDirect.
+ * @return Boolean
+ */
+ public boolean getMakeNodeDirect() {
+ if(m_objMakeNodeDirect == null){
+ IBinding objBinding = getMakeNodeDirectBinding();
+ if(objBinding != null){
+ m_objMakeNodeDirect = (Boolean)objBinding.getObject();
+ }else{
+ m_objMakeNodeDirect = Boolean.TRUE;
+ }
+ }
+ return m_objMakeNodeDirect.booleanValue();
+ }
+ public boolean getMakeNodeNoDirect() {
+ return !getMakeNodeDirect();
+ }
+
+
+ public String getCleanSelectedID(){
+ return getSelectedNodeID();
+ }
+
+ public String getSelectedID(){
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ ITreeModelSource objTreeModelSource = getTreeModelSource();
+ TreeRowObject objTreeRowObject = objTreeRowSource.getTreeRow();
+ Object objNodeValueUID = objTreeRowObject.getTreeNodeUID();
+ Object objSelectedNode = objTreeModelSource.getTreeModel().getTreeStateModel().getSelectedNode();
+ if(objNodeValueUID.equals(objSelectedNode)) {
+ return getSelectedNodeID();
+ }
+ return "";
+ }
+
+ private String getSelectedNodeID(){
+ //return getTreeDataView().getTreeView().getSelectedNodeID();
+ return "tree";
+ }
+
+ public String getNodeStyleClass() {
+ ITreeRowSource objTreeRowSource = getTreeRowSource();
+ ITreeModelSource objTreeModelSource = getTreeModelSource();
+ TreeRowObject objTreeRowObject = objTreeRowSource.getTreeRow();
+ boolean bResult = false;
+ if(objTreeRowObject != null){
+ Object objNodeValueUID = objTreeRowObject.getTreeNodeUID();
+ Object objSelectedNode = objTreeModelSource.getTreeModel().getTreeStateModel().getSelectedNode();
+ bResult = objNodeValueUID.equals(objSelectedNode);
+ }
+ if (bResult) {
+ return "selectedNodeViewClass";
+ }
+
+ return "notSelectedNodeViewClass";
+ }
+
+ public ITreeRowSource getTreeRowSource(){
+ ITreeRowSource objSource = (ITreeRowSource)getPage().getRequestCycle().getAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE);
+ return objSource;
+ }
+
+ public ITreeModelSource getTreeModelSource(){
+ ITreeModelSource objSource = (ITreeModelSource)getPage().getRequestCycle().getAttribute(ITreeModelSource.TREE_MODEL_SOURCE_ATTRIBUTE);
+ return objSource;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.jwc
new file mode 100644
index 0000000..9adadce
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeView.jwc
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification
+ class="org.apache.tapestry.contrib.tree.components.TreeNodeView"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="closeNodeImage" type="org.apache.tapestry.IAsset"
+ required="no" direction="in"/>
+ <parameter name="openNodeImage" type="org.apache.tapestry.IAsset"
+ required="no" direction="in"/>
+
+ <parameter name="showNodeImages" type="boolean" required="no"
+ direction="custom"/>
+ <parameter name="makeNodeDirect" type="boolean" required="no"
+ direction="custom"/>
+
+ <parameter name="nodeRenderFactory"
+ type="org.apache.tapestry.contrib.tree.components.INodeRenderFactory"
+ required="no" direction="custom"/>
+
+ <!--parameter name="treeStateListener"
+ type="org.apache.tapestry.contrib.tree.model.ITreeStateListener"
+ direction="custom" required="no">
+ </parameter-->
+
+ <reserved-parameter name="opennodeimage"/>
+ <reserved-parameter name="treedataview"/>
+ <reserved-parameter name="closenodeimage"/>
+ <reserved-parameter name="nodeviewdirect"/>
+
+ <component id="direct" type="DirectLink">
+ <binding name="parameters" expression="nodeContext"/>
+ <binding name="listener" expression="listeners.nodeSelect"/>
+ <binding name="stateful" expression="false"/>
+ <binding name="name" expression="selectedID"/>
+ <binding name="anchor" expression="cleanSelectedID"/>
+ </component>
+
+ <component id="showImages" type="Conditional">
+ <binding name="condition" expression="showImages"/>
+ </component>
+
+ <component id="showImages2" copy-of="showImages"/>
+
+ <!--component id="showWithoutImages" type="Conditional">
+ <binding name="condition" expression="showWithoutImages"/>
+ </component-->
+
+ <component id="makeNodeDirect" type="Conditional">
+ <binding name="condition" expression="makeNodeDirect"/>
+ </component>
+
+ <component id="makeNodeNoDirect" type="Conditional">
+ <binding name="condition" expression="makeNodeNoDirect"/>
+ </component>
+
+ <component id="imageNode" type="Image">
+ <binding name="image" expression="nodeImage"/>
+ </component>
+ <component id="imageNode2" copy-of="imageNode"/>
+
+ <component id="insertValue" type="Delegator">
+ <binding name="delegate" expression="currentRenderer"/>
+ </component>
+ <component id="insertValue2" copy-of="insertValue"/>
+
+ <component id="offset" type="Any">
+ <static-binding name="element">span</static-binding>
+ <binding name="style" expression="offsetStyle"/>
+ <binding name="class" expression="nodeStyleClass"/>
+ </component>
+
+ <private-asset name="_closeNodeImage" resource-path="plus.gif"/>
+ <private-asset name="_openNodeImage" resource-path="minus.gif"/>
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.html
new file mode 100644
index 0000000..6b11e7a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.html
@@ -0,0 +1,5 @@
+<!-- generated by Spindle, http://spindle.sourceforge.net -->
+
+<span jwcid="$content$">
+ <span jwcid="treeNodeView"/>
+</span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.page b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.page
new file mode 100644
index 0000000..eaf27f7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeNodeViewPage.page
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- Forms.page,v 1.1 2002/08/23 22:18:31 hship Exp -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification>
+
+ <component id="treeNodeView" type="TreeNodeView">
+ </component>
+
+</page-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.html
new file mode 100644
index 0000000..5facb58
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.html
@@ -0,0 +1,5 @@
+<span jwcid="$content$">
+ <span jwcid="inheritInformalAny">
+ <span jwcid="insertWrapped"/>
+ </span>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.java
new file mode 100644
index 0000000..b926226
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.java
@@ -0,0 +1,324 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.tree.model.ISessionStoreManager;
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager;
+import org.apache.tapestry.contrib.tree.model.ITreeStateListener;
+import org.apache.tapestry.contrib.tree.simple.FullTreeSessionStateManager;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @version $Id$
+ */
+public class TreeView extends BaseComponent
+ implements PageDetachListener, PageRenderListener, ITreeModelSource {
+
+ private static final Log LOG = LogFactory.getLog(TreeView.class);
+
+ private IBinding m_objSessionStoreManagerBinding;
+ private IBinding m_objTreeModelBinding;
+ private IBinding m_objSessionStateManagerBinding;
+
+ private ITreeModel m_objTreeModel;
+ private ITreeSessionStateManager m_objTreeSessionStateManager;
+ private ISessionStoreManager m_objSessionStoreManager;
+ private Object m_objTreeSessionState;
+ private ComponentAddress m_objComponentAddress;
+
+ public TreeView(){
+ super();
+ initialize();
+ }
+
+ private void initialize(){
+ m_objTreeModel = null;
+ m_objTreeSessionStateManager = null;
+ m_objSessionStoreManager = null;
+ m_objTreeSessionState = null;
+ m_objComponentAddress = null;
+ }
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#finishLoad()
+ */
+ protected void finishLoad() {
+ super.finishLoad();
+ getPage().addPageRenderListener(this);
+ getPage().addPageDetachListener(this);
+ }
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ */
+ /* protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ throws RequestCycleException
+ {
+ renderWrapped(writer, cycle);
+ }
+ */
+
+ /**
+ * @see org.apache.tapestry.event.PageDetachListener#pageDetached(PageEvent)
+ */
+ public void pageDetached(PageEvent arg0) {
+ initialize();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageRenderListener#pageBeginRender(PageEvent)
+ */
+ public void pageBeginRender(PageEvent arg0) {
+ if(arg0.getRequestCycle().isRewinding()) {
+ return;
+ }
+ storeSesion();
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageRenderListener#pageEndRender(PageEvent)
+ */
+ public void pageEndRender(PageEvent arg0) {
+ }
+
+ /**
+ * Returns the treeModelBinding.
+ * @return IBinding
+ */
+ public IBinding getTreeModelBinding() {
+ return m_objTreeModelBinding;
+ }
+
+ /**
+ * Sets the treeModelBinding.
+ * @param treeModelBinding The treeModelBinding to set
+ */
+ public void setTreeModelBinding(IBinding treeModelBinding) {
+ m_objTreeModelBinding = treeModelBinding;
+ }
+
+ /**
+ * Returns the SessionStoreManagerBinding.
+ * @return IBinding
+ */
+ public IBinding getSessionStoreManagerBinding() {
+ return m_objSessionStoreManagerBinding;
+ }
+
+ /**
+ * Returns the sessionStateManagerBinding.
+ * @return IBinding
+ */
+ public IBinding getSessionStateManagerBinding() {
+ return m_objSessionStateManagerBinding;
+ }
+
+ /**
+ * Sets the SessionStoreManagerBinding.
+ * @param sessionStoreManagerBinding The SessionStoreManagerBinding to set
+ */
+ public void setSessionStoreManagerBinding(IBinding
+ sessionStoreManagerBinding) {
+ m_objSessionStoreManagerBinding = sessionStoreManagerBinding;
+ }
+
+ /**
+ * Sets the sessionStateManagerBinding.
+ * @param sessionStateManagerBinding The sessionStateManagerBinding to set
+ */
+ public void setSessionStateManagerBinding(IBinding
+ sessionStateManagerBinding) {
+ m_objSessionStateManagerBinding = sessionStateManagerBinding;
+ }
+
+ private void extractTreeModel(){
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("TreeView.extractTreeModel()");
+ }
+
+ ISessionStoreManager objHolder = getSessionStoreManager();
+ ITreeSessionStateManager objSessionManager = getTreeSessionStateMgr();
+ Object objSessionState;
+ if (objHolder == null) {
+ objSessionState = getTreeSessionState();
+ } else {
+ objSessionState = objHolder.getSessionState(this.getPage(),
+ "treeSessionState");
+ }
+
+ if (objSessionState != null) {
+ m_objTreeModel = objSessionManager.getModel(objSessionState);
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("TreeView.extractTreeModel() from BINDING");
+ }
+
+ m_objTreeModel = (ITreeModel)getTreeModelBinding().getObject();
+ }
+
+ }
+
+ private void storeSesion(){
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("TreeView.storeSesion()");
+ }
+
+ ITreeSessionStateManager objSessionManager = getTreeSessionStateMgr();
+ Object objSessionState =
+ objSessionManager.getSessionState(getTreeModel());
+
+ store(objSessionState);
+ }
+
+ private void store(Object objSessionState){
+ ISessionStoreManager objHolder = getSessionStoreManager();
+
+ if (objHolder == null) {
+ fireObservedChange("treeSessionState", objSessionState);
+ } else {
+ //String strPath = "treeSessionState";
+ String strPath = getExtendedId();
+ if (LOG.isDebugEnabled())
+ LOG.debug("store(): setting state with: " + strPath);
+ objHolder.setSessionState(this.getPage(), strPath,
+ objSessionState);
+ }
+ }
+
+ /**
+ * @see ITreeComponent#resetState()
+ */
+ public void resetState() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("TreeView.resetState()");
+ }
+ initialize();
+ store(null);
+ }
+
+ /**
+ * Returns the SessionStoreManager.
+ * @return ISessionStoreManager
+ */
+ public ISessionStoreManager getSessionStoreManager() {
+ if (m_objSessionStoreManager == null
+ && getSessionStoreManagerBinding() != null) {
+ m_objSessionStoreManager =
+ (ISessionStoreManager)getSessionStoreManagerBinding().getObject();
+ }
+
+ return m_objSessionStoreManager;
+ }
+
+ /**
+ * Returns the wizardSessionStateMgr.
+ * @return IWizardSessionStateManager
+ */
+ public ITreeSessionStateManager getTreeSessionStateMgr() {
+ if (m_objTreeSessionStateManager == null) {
+ IBinding objBinding = getSessionStateManagerBinding();
+ if(objBinding != null){
+ Object objManager = objBinding.getObject();
+ m_objTreeSessionStateManager =
+ (ITreeSessionStateManager) objManager;
+ } else {
+ m_objTreeSessionStateManager =
+ new FullTreeSessionStateManager();
+ }
+ }
+ return m_objTreeSessionStateManager;
+ }
+
+ public ComponentAddress getComponentPath() {
+ if (m_objComponentAddress == null) {
+ m_objComponentAddress = new ComponentAddress(this);
+ }
+ return m_objComponentAddress;
+ }
+
+ /**
+ * Returns the treeModel.
+ * @return ITreeModel
+ */
+ public ITreeModel getTreeModel() {
+ if (m_objTreeModel == null) {
+ extractTreeModel();
+ }
+ return m_objTreeModel;
+ }
+
+ /**
+ * Sets the treeModel.
+ * @param treeModel The treeModel to set
+ */
+ public void setTreeModel(ITreeModel treeModel) {
+ m_objTreeModel = treeModel;
+ }
+
+ /**
+ * Returns the treeSessionState.
+ * @return Object
+ */
+ public Object getTreeSessionState() {
+ return m_objTreeSessionState;
+ }
+
+ /**
+ * Sets the treeSessionState.
+ * @param treeSessionState The treeSessionState to set
+ */
+ public void setTreeSessionState(Object treeSessionState) {
+ m_objTreeSessionState = treeSessionState;
+ }
+
+ public String getSelectedNodeStyleID(){
+ return getId() + ":selected";
+ }
+
+ /**
+ * @see org.apache.tapestry.BaseComponent#renderComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter arg0, IRequestCycle arg1) {
+ Object objExistedTreeModelSource = arg1.getAttribute(ITreeModelSource.TREE_MODEL_SOURCE_ATTRIBUTE);
+ arg1.setAttribute(ITreeModelSource.TREE_MODEL_SOURCE_ATTRIBUTE, this);
+
+ super.renderComponent(arg0, arg1);
+ arg1.setAttribute(ITreeModelSource.TREE_MODEL_SOURCE_ATTRIBUTE, objExistedTreeModelSource);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeModelSource#getTreeStateListener()
+ */
+ public ITreeStateListener getTreeStateListener() {
+ ITreeStateListener objListener = null;
+ IBinding objBinding = getBinding("treeStateListener");
+ if(objBinding != null){
+ objListener = (ITreeStateListener) objBinding.getObject();
+ }
+ return objListener;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.jwc
new file mode 100644
index 0000000..e7f8b1e
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.jwc
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.TreeView"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="sessionStateManager"
+ type="org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager"
+ direction="custom" required="no"/>
+
+ <parameter name="sessionStoreManager"
+ type="org.apache.tapestry.contrib.tree.model.ISessionStoreManager"
+ direction="custom" required="no"/>
+
+ <parameter name="treeModel"
+ type="org.apache.tapestry.contrib.tree.model.ITreeModel"
+ direction="custom" required="yes">
+ </parameter>
+
+ <parameter name="treeStateListener"
+ type="org.apache.tapestry.contrib.tree.model.ITreeStateListener"
+ direction="custom" required="no">
+ </parameter>
+
+ <component id="inheritInformalAny" type="InheritInformalAny">
+ <static-binding name="element">span</static-binding>
+ </component>
+
+ <component id="insertWrapped" type="RenderBody"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.script b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.script
new file mode 100644
index 0000000..2162e74
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/TreeView.script
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE script PUBLIC "-//Howard Ship//Tapestry Script 1.1//EN"
+ "http://tapestry.sf.net/dtd/Script_1_1.dtd">
+
+<script>
+ <body>
+ function select(){
+ var objNode = document.getElementById("<insert property-path="selectedNodeID"/>");
+ window.scrollTo(0, objNode.y);
+ }
+
+ </body>
+ <initialization>
+ select();
+ </initialization>
+</script>
diff --git a/examples/Workbench/context/images/minus.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/minus.gif
similarity index 100%
copy from examples/Workbench/context/images/minus.gif
copy to tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/minus.gif
Binary files differ
diff --git a/examples/Workbench/context/images/plus.gif b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/plus.gif
similarity index 100%
copy from examples/Workbench/context/images/plus.gif
copy to tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/plus.gif
Binary files differ
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.html
new file mode 100644
index 0000000..60f27d6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.html
@@ -0,0 +1,5 @@
+<span jwcid="$content$">
+ <span class="tree" jwcid="treeView">
+ <span jwcid="treeTableDataView"/>
+ </span>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.java
new file mode 100644
index 0000000..c6176d6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components.table;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.contrib.tree.components.ITreeComponent;
+import org.apache.tapestry.contrib.tree.components.TreeView;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeTable extends BaseComponent implements ITreeComponent{
+
+ /**
+ *
+ */
+ public TreeTable() {
+ super();
+ }
+
+ public ITreeModelSource getTreeModelSource(){
+ return (ITreeModelSource) getComponent("treeView");
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#resetState()
+ */
+ public void resetState() {
+ TreeView objTreeView = (TreeView)getComponent("treeView");
+ objTreeView.resetState();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#getComponentPath()
+ */
+ public ComponentAddress getComponentPath() {
+ return new ComponentAddress(this);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.components.ITreeComponent#getTreeRowSource()
+ */
+ public ITreeRowSource getTreeRowSource() {
+ TreeTableDataView objTreeDataView = (TreeTableDataView)getComponent("treeTableDataView");
+ return objTreeDataView;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.jwc
new file mode 100644
index 0000000..08b4285
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTable.jwc
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.table.TreeTable"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="sessionStateManager"
+ type="org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager"
+ direction="custom" required="no"/>
+
+ <parameter name="sessionStoreManager"
+ type="org.apache.tapestry.contrib.tree.model.ISessionStoreManager"
+ direction="custom" required="no"/>
+
+ <parameter name="treeModel"
+ type="org.apache.tapestry.contrib.tree.model.ITreeModel"
+ direction="custom" required="yes">
+ </parameter>
+
+ <parameter name="treeStateListener"
+ type="org.apache.tapestry.contrib.tree.model.ITreeStateListener"
+ direction="custom" required="no">
+ </parameter>
+
+ <parameter name="entriesPerTablePage"
+ type="int"
+ required="no"
+ direction="custom"/>
+
+ <parameter name="nodeViewComponentAddress"
+ type="org.apache.tapestry.util.ComponentAddress"
+ required="no" direction="custom"/>
+
+ <parameter name="tableColunms"
+ type="java.util.ArrayList"
+ required="no" direction="custom"/>
+
+ <component id="treeView" type="TreeView">
+ <inherited-binding name="sessionStateManager" parameter-name="sessionStateManager"/>
+ <inherited-binding name="sessionStoreManager" parameter-name="sessionStoreManager"/>
+ <inherited-binding name="treeModel" parameter-name="treeModel"/>
+ <inherited-binding name="treeStateListener" parameter-name="treeStateListener"/>
+ </component>
+
+ <component id="treeTableDataView" type="TreeTableDataView">
+ <binding name="treeView" expression='components.treeView'/>
+ <inherited-binding name="nodeViewComponentAddress" parameter-name="nodeViewComponentAddress"/>
+ <inherited-binding name="entriesPerTablePage" parameter-name="entriesPerTablePage"/>
+ <inherited-binding name="tableColunms" parameter-name="tableColunms"/>
+ </component>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableColumn.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableColumn.java
new file mode 100644
index 0000000..bc90eec
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableColumn.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components.table;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumn;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeTableColumn extends SimpleTableColumn {
+
+ /**
+ * @param arg0
+ * @param arg1
+ */
+ public TreeTableColumn(String arg0, boolean arg1, ComponentAddress objComponentAddress) {
+ super(arg0, arg1);
+ setValueRendererSource(new TreeTableValueRenderSource(objComponentAddress));
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.common.AbstractTableColumn#getValueRenderer(org.apache.tapestry.IRequestCycle, org.apache.tapestry.contrib.table.model.ITableModelSource, java.lang.Object)
+ */
+ public IRender getValueRenderer(IRequestCycle arg0, ITableModelSource arg1, Object arg2) {
+ return super.getValueRenderer(arg0, arg1, arg2);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.html
new file mode 100644
index 0000000..5efd7af
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.html
@@ -0,0 +1,5 @@
+<!-- generated by Spindle, http://spindle.sourceforge.net -->
+
+<span jwcid="$content$">
+ <span jwcid="table"/>
+</span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.java
new file mode 100644
index 0000000..2c93ffd
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.java
@@ -0,0 +1,230 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components.table;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModel;
+import org.apache.tapestry.contrib.table.model.ITableSessionStateManager;
+import org.apache.tapestry.contrib.table.model.simple.SimpleListTableDataModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableColumnModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableModel;
+import org.apache.tapestry.contrib.table.model.simple.SimpleTableSessionStateManager;
+import org.apache.tapestry.contrib.tree.model.ITreeDataModel;
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.contrib.tree.model.ITreeRowSource;
+import org.apache.tapestry.contrib.tree.model.TreeRowObject;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+
+/**
+ * @version $Id$
+ */
+public class TreeTableDataView extends BaseComponent implements ITreeRowSource, PageDetachListener{
+ private int m_nTreeDeep = -1;
+ private TreeRowObject m_objTreeRowObject = null;
+ private ArrayList m_arrAllExpandedNodes = null;
+
+ public TreeTableDataView(){
+ super();
+ initialize();
+ }
+
+ public void initialize(){
+ m_nTreeDeep = -1;
+// m_objTableModel = null;
+ m_objTreeRowObject = null;
+ m_arrAllExpandedNodes = null;
+ }
+
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#finishLoad()
+ */
+ protected void finishLoad() {
+ super.finishLoad();
+ getPage().addPageDetachListener(this);
+ }
+
+ /**
+ * @see org.apache.tapestry.event.PageDetachListener#pageDetached(org.apache.tapestry.event.PageEvent)
+ */
+ public void pageDetached(PageEvent arg0) {
+ initialize();
+ }
+
+
+ public ITreeModelSource getTreeModelSource() {
+ ITreeModelSource objSource = (ITreeModelSource) getPage().getRequestCycle().getAttribute(ITreeModelSource.TREE_MODEL_SOURCE_ATTRIBUTE);
+ if(objSource == null){
+ objSource = (ITreeModelSource) getBinding("treeView").getObject();
+ }
+ return objSource;
+ }
+
+ public ArrayList generateNodeList() {
+ if(m_arrAllExpandedNodes == null){
+ // render data
+ ITreeModelSource objTreeModelSource = getTreeModelSource();
+ ITreeModel objTreeModel = objTreeModelSource.getTreeModel();
+ ITreeDataModel objTreeDataModel = objTreeModel.getTreeDataModel();
+ Object objValue = objTreeDataModel.getRoot();
+ Object objValueUID = objTreeDataModel.getUniqueKey(objValue, null);
+
+ // Object objSelectedNode = objTreeModel.getTreeStateModel().getSelectedNode();
+ //if(objSelectedNode == null)
+ // objTreeModel.getTreeStateModel().expand(objValueUID);
+ ArrayList arrAllExpandedNodes = new ArrayList();
+ walkTree(arrAllExpandedNodes, objValue, objValueUID, 0, objTreeModel);
+ m_arrAllExpandedNodes = arrAllExpandedNodes;
+ }
+
+
+ return m_arrAllExpandedNodes;
+ }
+
+ public void walkTree(ArrayList arrAllExpandedNodes, Object objParent, Object objParentUID, int nDepth,
+ ITreeModel objTreeModel) {
+ m_nTreeDeep = nDepth;
+
+ TreeRowObject objTreeRowObject = new TreeRowObject(objParent, objParentUID, nDepth);
+ arrAllExpandedNodes.add(objTreeRowObject);
+
+ boolean bContain = objTreeModel.getTreeStateModel().isUniqueKeyExpanded(objParentUID);
+ if (bContain) {
+ Iterator colChildren = objTreeModel.getTreeDataModel().getChildren(objParent);
+ for (Iterator iter = colChildren; iter.hasNext();) {
+ Object objChild = iter.next();
+ Object objChildUID = objTreeModel.getTreeDataModel().getUniqueKey(objChild, objParentUID);
+ walkTree(arrAllExpandedNodes, objChild, objChildUID, nDepth+1, objTreeModel);
+ }
+ }
+ }
+
+ /**
+ * Returns the treeDeep.
+ * @return int
+ */
+ public int getTreeDeep() {
+ return m_nTreeDeep;
+ }
+
+/* public ITableModel getTableModel() {
+ if(m_objTableModel == null){
+ m_objTableModel = createTableModel();
+ }
+ return m_objTableModel;
+ }
+*/
+ public ITableModel getTableModel() {
+ return createTableModel();
+ }
+
+ private ITableModel createTableModel(){
+ ArrayList arrAllNodes = generateNodeList();
+ Object[] arrAllExpandedNodes = new Object[arrAllNodes.size()];
+ arrAllNodes.toArray(arrAllExpandedNodes);
+
+
+ SimpleTableModel objTableModel = new SimpleTableModel(arrAllExpandedNodes, getTableColunms());
+ objTableModel.getPagingState().setPageSize(getEntriesPerTablePage());
+
+ return objTableModel;
+ }
+
+ public ITableColumn[] getTableColunms(){
+ ArrayList arrColumnsList = new ArrayList();
+ arrColumnsList.add(new TreeTableColumn ("Name", false, null));
+
+ ArrayList arrTableColunms = getTableColunmsFromBinding();
+ if(arrTableColunms != null)
+ arrColumnsList.addAll(arrTableColunms);
+
+ ITableColumn[] arrColumns = new ITableColumn[arrColumnsList.size()];
+ arrColumnsList.toArray(arrColumns);
+
+ return arrColumns;
+ }
+
+ public ArrayList getTableColunmsFromBinding(){
+ IBinding objBinding = getBinding("tableColunms");
+ if(objBinding != null)
+ return (ArrayList)objBinding.getObject();
+ return null;
+ }
+
+ public int getEntriesPerTablePage(){
+ IBinding objBinding = getBinding("entriesPerTablePage");
+ if(objBinding != null)
+ return objBinding.getInt();
+ return 50;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeRowSource#getTreeRow()
+ */
+ public TreeRowObject getTreeRow() {
+ return getTreeRowObject();
+ }
+
+ public ITableSessionStateManager getTableSessionStateManager(){
+ SimpleListTableDataModel objDataModel = new SimpleListTableDataModel(generateNodeList());
+ SimpleTableColumnModel objColumnModel = new SimpleTableColumnModel(getTableColunms());
+ SimpleTableSessionStateManager objStateManager = new SimpleTableSessionStateManager(objDataModel, objColumnModel);
+ return objStateManager;
+ //return NullTableSessionStateManager.NULL_STATE_MANAGER;
+ }
+
+ /**
+ * @see org.apache.tapestry.BaseComponent#renderComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ */
+ protected void renderComponent(IMarkupWriter arg0, IRequestCycle cycle) {
+ Object objExistedTreeModelSource = cycle.getAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE);
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, this);
+
+ super.renderComponent(arg0, cycle);
+
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, objExistedTreeModelSource);
+ }
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderBody(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ */
+ public void renderBody(IMarkupWriter arg0, IRequestCycle cycle) {
+ Object objExistedTreeModelSource = cycle.getAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE);
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, this);
+
+ super.renderBody(arg0, cycle);
+
+ cycle.setAttribute(ITreeRowSource.TREE_ROW_SOURCE_ATTRIBUTE, objExistedTreeModelSource);
+ }
+
+
+ public TreeRowObject getTreeRowObject() {
+ return m_objTreeRowObject;
+ }
+
+ public void setTreeRowObject(TreeRowObject object) {
+ m_objTreeRowObject = object;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.jwc
new file mode 100644
index 0000000..41d2e20
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableDataView.jwc
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.table.TreeTableDataView"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <parameter name="treeView"
+ type="org.apache.tapestry.contrib.tree.components.TreeView"
+ required="no" direction="custom"/>
+
+ <parameter name="nodeViewComponentAddress"
+ type="org.apache.tapestry.util.ComponentAddress"
+ required="no" direction="custom"/>
+
+ <parameter name="entriesPerTablePage"
+ type="int"
+ required="no"
+ direction="custom"/>
+
+ <parameter name="tableColunms"
+ type="java.util.ArrayList"
+ required="no" direction="custom"/>
+
+ <bean name="tableClass" class="org.apache.tapestry.bean.EvenOdd" lifecycle="request"/>
+
+ <component id="table" type="Table">
+ <binding name="tableModel" expression="tableModel"/>
+ <binding name="tableSessionStateManager" expression="tableSessionStateManager"/>
+ <binding name="row" expression="treeRowObject"/>
+ <binding name="rowsClass" expression='beans.tableClass.next'/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.html
new file mode 100644
index 0000000..f868b5a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.html
@@ -0,0 +1,3 @@
+<span jwcid="$content$">
+ <span jwcid="treeNodeView"/>
+</span>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.java
new file mode 100644
index 0000000..e38e50c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components.table;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererListener;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeTableNodeViewDelegator extends BaseComponent implements ITableRendererListener{
+
+ /**
+ *
+ */
+ public TreeTableNodeViewDelegator() {
+ super();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererListener#initializeRenderer(org.apache.tapestry.IRequestCycle, org.apache.tapestry.contrib.table.model.ITableModelSource, org.apache.tapestry.contrib.table.model.ITableColumn, java.lang.Object)
+ */
+ public void initializeRenderer(
+ IRequestCycle arg0,
+ ITableModelSource arg1,
+ ITableColumn arg2,
+ Object arg3) {
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.jwc
new file mode 100644
index 0000000..eb55db7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewDelegator.jwc
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.tree.components.table.TreeTableNodeViewDelegator"
+ allow-body="yes" allow-informal-parameters="yes">
+
+ <component id="treeNodeView" type="TreeNodeView">
+ </component>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.html b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.html
new file mode 100644
index 0000000..e554dea
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.html
@@ -0,0 +1,5 @@
+<!-- generated by Spindle, http://spindle.sourceforge.net -->
+
+<span jwcid="$content$">
+ <span jwcid="treeTableNodeViewDelegator"/>
+</span>
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.page b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.page
new file mode 100644
index 0000000..855e862
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableNodeViewPage.page
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- Forms.page,v 1.1 2002/08/23 22:18:31 hship Exp -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification>
+
+ <component id="treeTableNodeViewDelegator" type="TreeTableNodeViewDelegator">
+ </component>
+
+</page-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableValueRenderSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableValueRenderSource.java
new file mode 100644
index 0000000..2057138
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/components/table/TreeTableValueRenderSource.java
@@ -0,0 +1,76 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.components.table;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.table.model.ITableColumn;
+import org.apache.tapestry.contrib.table.model.ITableModelSource;
+import org.apache.tapestry.contrib.table.model.ITableRendererSource;
+import org.apache.tapestry.contrib.table.model.common.ComponentTableRendererSource;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeTableValueRenderSource implements ITableRendererSource
+{
+
+ private ComponentTableRendererSource m_objComponentRenderer;
+ private ComponentAddress m_objComponentAddress = null;
+
+ public TreeTableValueRenderSource()
+ {
+ m_objComponentRenderer = null;
+ }
+
+ public TreeTableValueRenderSource(ComponentAddress objComponentAddress)
+ {
+ m_objComponentAddress = objComponentAddress;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.table.model.ITableRendererSource#getRenderer(IRequestCycle, ITableModelSource, ITableColumn, Object)
+ */
+ public IRender getRenderer(
+ IRequestCycle objCycle,
+ ITableModelSource objSource,
+ ITableColumn objColumn,
+ Object objRow)
+ {
+ synchronized (this)
+ {
+ if (m_objComponentRenderer == null)
+ {
+
+ ComponentAddress objAddress = m_objComponentAddress;
+ if(m_objComponentAddress == null)
+ objAddress = new ComponentAddress(
+ "contrib:TreeTableNodeViewPage",
+ "treeTableNodeViewDelegator");
+ m_objComponentRenderer =
+ new ComponentTableRendererSource(objAddress);
+ }
+ }
+
+ return m_objComponentRenderer.getRenderer(
+ objCycle,
+ objSource,
+ objColumn,
+ objRow);
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/IMutableTreeNode.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/IMutableTreeNode.java
new file mode 100644
index 0000000..d27eef6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/IMutableTreeNode.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import java.util.Collection;
+
+/**
+ * Defines the requirements for a tree node object that can change --
+ * by adding or removing child nodes, or by changing the contents
+ * of a user object stored in the node.
+ *
+ * @see javax.swing.tree.DefaultMutableTreeNode
+ * @see javax.swing.JTree
+ *
+ * @author ceco
+ * @version $Id$
+ */
+
+public interface IMutableTreeNode extends ITreeNode
+{
+ /**
+ * Adds collection of<code>children</code> to the receiver.
+ * <code>Child</code> will be messaged with <code>setParent</code>.
+ */
+ void insert(Collection colChildren);
+
+ /**
+ * Removes <code>node</code> from the receiver. <code>setParent</code>
+ * will be messaged on <code>node</code>.
+ */
+ void remove(IMutableTreeNode node);
+
+ /**
+ * Removes the receiver from its parent.
+ */
+ void removeFromParent();
+
+ /**
+ * Sets the parent of the receiver to <code>newParent</code>.
+ */
+ void setParent(IMutableTreeNode newParent);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ISessionStoreManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ISessionStoreManager.java
new file mode 100644
index 0000000..fb5644d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ISessionStoreManager.java
@@ -0,0 +1,27 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import org.apache.tapestry.IPage;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+
+public interface ISessionStoreManager {
+ Object getSessionState(IPage objPage, String strSessionStateID);
+ Object setSessionState(IPage objPage, String strSessionStateID, Object objSessionState);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeDataModel.java
new file mode 100644
index 0000000..9522eb0
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeDataModel.java
@@ -0,0 +1,73 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import java.util.Iterator;
+
+/**
+ * The interface that defines a suitable data model for a <code>TreeView component</code>.
+ *
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeDataModel
+{
+ /**
+ * Returns the root node of the tree
+ */
+ Object getRoot();
+
+ /**
+ * Returns the number of children of parent node.
+ * @param objParent is the parent object whose nr of children are sought
+ */
+ int getChildCount(Object objParent);
+
+ /**
+ * Get an iterator to the Collection of children belonging to the parent node object
+ * @param objParent is the parent object whose children are requested
+ */
+ Iterator getChildren(Object objParent);
+
+ /**
+ * Get the actual node object based on some identifier (for example an UUID)
+ * @param objUniqueKey is the unique identifier of the node object being retrieved
+ * @return the instance of the node object identified by the key
+ */
+ Object getObject(Object objUniqueKey);
+
+ /**
+ * Get the unique identifier (UUID) of the node object with a certain parent node
+ * @param objTarget is the Object whose identifier is required
+ * @param objParentUniqueKey is the unique id of the parent of objTarget
+ * @return the unique identifier of objTarget
+ */
+ Object getUniqueKey(Object objTarget, Object objParentUniqueKey);
+
+ /**
+ * Get the unique identifier of the parent of an object
+ * @param objChildUniqueKey is the identifier of the Object for which the parent identifier is sought
+ * @return the identifier (possibly UUID) of the parent of objChildUniqueKey
+ */
+ Object getParentUniqueKey(Object objChildUniqueKey);
+
+ /**
+ * Check to see (on the basis of some node object identifier) whether the parent node is indeed the parent of the object
+ * @param objChildUniqueKey is the identifier of the object whose parent is being checked
+ * @param objParentUniqueKey is the identifier of the parent which is to be checked against
+ */
+ boolean isAncestorOf(Object objChildUniqueKey, Object objParentUniqueKey);
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModel.java
new file mode 100644
index 0000000..04902ce
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModel.java
@@ -0,0 +1,24 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeModel {
+ ITreeDataModel getTreeDataModel();
+ ITreeStateModel getTreeStateModel();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModelSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModelSource.java
new file mode 100644
index 0000000..75c751d
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeModelSource.java
@@ -0,0 +1,30 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import org.apache.tapestry.IComponent;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeModelSource extends IComponent
+{
+ final static String TREE_MODEL_SOURCE_ATTRIBUTE = "org.apache.tapestry.contrib.tree.model.ITreeModelSource";
+
+ ITreeModel getTreeModel();
+ ITreeStateListener getTreeStateListener();
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNode.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNode.java
new file mode 100644
index 0000000..7e08fda
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNode.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+
+public interface ITreeNode extends Serializable
+{
+
+ /**
+ * Returns the <code>Collection</code> of children.
+ */
+ Collection getChildren();
+
+ /**
+ * Returns the number of children <code>ITreeNode</code>s the receiver
+ * contains.
+ */
+ int getChildCount();
+
+ /**
+ * Returns the parent <code>ITreeNode</code> of the receiver.
+ */
+ ITreeNode getParent();
+
+ /**
+ * Returns the true if current node contains received children, otherwise return false;
+ */
+ boolean containsChild(ITreeNode node);
+
+ /**
+ * Returns true if the receiver allows children.
+ */
+ boolean getAllowsChildren();
+
+ /**
+ * Returns true if the receiver is a leaf.
+ */
+ boolean isLeaf();
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNodeManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNodeManager.java
new file mode 100644
index 0000000..dbd03d8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeNodeManager.java
@@ -0,0 +1,26 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.contrib.tree.components.ITreeComponent;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeNodeManager {
+ IRender getRenderer(Object objUniqueKey, ITreeComponent objTreeComponent);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeRowSource.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeRowSource.java
new file mode 100644
index 0000000..5f2ff40
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeRowSource.java
@@ -0,0 +1,36 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+/**
+ * A Tapestry component that provides the current row value.
+ * This interface is used for obtaining the row source by components
+ * wrapped by the row source
+ *
+ * @version $Id$
+ * @author tsvetelin
+ */
+public interface ITreeRowSource
+{
+ final static String TREE_ROW_SOURCE_ATTRIBUTE = "org.apache.tapestry.contrib.tree.model.ITreeRowSource";
+
+ /**
+ * Method getTreeRow
+ * @return Object the current tree row object.
+ */
+ TreeRowObject getTreeRow();
+ //Object getTreeRowNodeUID();
+ //int getTreeNodeDeep();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeSessionStateManager.java
new file mode 100644
index 0000000..00dbfa2
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeSessionStateManager.java
@@ -0,0 +1,26 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeSessionStateManager {
+ Object getSessionState(ITreeModel objModel);
+ ITreeModel getModel(Object objSessionState);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateListener.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateListener.java
new file mode 100644
index 0000000..cddf084
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateListener.java
@@ -0,0 +1,23 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public interface ITreeStateListener {
+ void treeStateChanged(TreeStateEvent objEvent);
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateModel.java
new file mode 100644
index 0000000..9286946
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/ITreeStateModel.java
@@ -0,0 +1,39 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+import java.util.Set;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+
+public interface ITreeStateModel {
+ Set getExpandSelection();
+ /*
+ * Return the selected node unique key
+ */
+ Object getSelectedNode();
+
+ void expand(Object objUniqueKey);
+ void expandPath(Object objUniqueKey);
+ void collapse(Object objUniqueKey);
+ void collapsePath(Object objUniqueKey);
+
+ boolean isUniqueKeyExpanded(Object objUniqueKey);
+
+ void resetState();
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeRowObject.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeRowObject.java
new file mode 100644
index 0000000..697c3f8
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeRowObject.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeRowObject {
+ private Object m_objTreeNode = null;
+ private Object m_objTreeNodeUID = null;
+ private int m_nTreeRowDepth;
+
+ public TreeRowObject(Object objTreeNode, Object objTreeNodeUID, int nTreeRowDepth) {
+ super();
+ m_objTreeNode = objTreeNode;
+ m_objTreeNodeUID = objTreeNodeUID;
+ m_nTreeRowDepth = nTreeRowDepth;
+ }
+
+ public Object getTreeNode() {
+ return m_objTreeNode;
+ }
+
+ public Object getTreeNodeUID() {
+ return m_objTreeNodeUID;
+ }
+
+ public int getTreeRowDepth() {
+ return m_nTreeRowDepth;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeStateEvent.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeStateEvent.java
new file mode 100644
index 0000000..86af620
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/model/TreeStateEvent.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.model;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeStateEvent {
+ public static final int SELECTED_NODE_CHANGED = 1;
+ public static final int NODE_EXPANDED = 2;
+ public static final int NODE_COLLAPSED = 4;
+
+ private int m_nEventType;
+ private transient ITreeStateModel m_objTreeStateModel = null;
+ private transient Object m_objNodeUID = null;
+
+ /**
+ * Constructor for TreeStateEvent.
+ */
+ public TreeStateEvent(int nEventType, Object objNodeUID, ITreeStateModel objTreeStateModel) {
+ super();
+ m_nEventType = nEventType;
+ m_objNodeUID = objNodeUID;
+ m_objTreeStateModel = objTreeStateModel;
+ }
+
+ /**
+ * Returns the EventType.
+ * @return int
+ */
+ public int getEventType() {
+ return m_nEventType;
+ }
+
+ public boolean isEvent(int nEventType){
+ return (getEventType() & nEventType) > 0;
+ }
+
+ public Object getNodeUID() {
+ return m_objNodeUID;
+ }
+
+ public ITreeStateModel getTreeStateModel() {
+ return m_objTreeStateModel;
+ }
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/FullTreeSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/FullTreeSessionStateManager.java
new file mode 100644
index 0000000..14771df
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/FullTreeSessionStateManager.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class FullTreeSessionStateManager implements ITreeSessionStateManager {
+
+ /**
+ * Constructor for FullTreeSessionStateManager.
+ */
+ public FullTreeSessionStateManager() {
+ super();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager#getSessionState(ITreeModel)
+ */
+ public Object getSessionState(ITreeModel objModel) {
+ return objModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager#getModel(Object)
+ */
+ public ITreeModel getModel(Object objSessionState) {
+ return (ITreeModel)objSessionState;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/NullSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/NullSessionStateManager.java
new file mode 100644
index 0000000..2294228
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/NullSessionStateManager.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class NullSessionStateManager implements ITreeSessionStateManager {
+
+ /**
+ * Constructor for NullSessionStateManager.
+ */
+ public NullSessionStateManager() {
+ super();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager#getSessionState(ITreeModel)
+ */
+ public Object getSessionState(ITreeModel objModel) {
+ return null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager#getModel(Object)
+ */
+ public ITreeModel getModel(Object objSessionState) {
+ return null;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleNodeRenderFactory.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleNodeRenderFactory.java
new file mode 100644
index 0000000..4696bda
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleNodeRenderFactory.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.contrib.tree.components.INodeRenderFactory;
+import org.apache.tapestry.contrib.tree.model.ITreeModelSource;
+import org.apache.tapestry.valid.RenderString;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class SimpleNodeRenderFactory implements INodeRenderFactory {
+
+ /**
+ * Constructor for SimpleNodeRenderFactory.
+ */
+ public SimpleNodeRenderFactory() {
+ super();
+ }
+
+ /**
+ * @see INodeRenderFactory#getRender
+ */
+ public IRender getRenderByID(
+ Object objUniqueKey,
+ ITreeModelSource objTreeModelSource,
+ IRequestCycle cycle)
+ {
+ Object objValue = objTreeModelSource.getTreeModel().getTreeDataModel().getObject(objUniqueKey);
+ return getRender(objValue, objTreeModelSource, cycle);
+ }
+
+ /**
+ * @see INodeRenderFactory#getRender
+ */
+ public IRender getRender(Object objValue, ITreeModelSource objTreeModelSource, IRequestCycle objCycle) {
+ return new RenderString(objValue.toString());
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleSessionStateManager.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleSessionStateManager.java
new file mode 100644
index 0000000..c7f8ee5
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleSessionStateManager.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeSessionStateManager;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class SimpleSessionStateManager implements ITreeSessionStateManager {
+
+ /**
+ * Constructor for SimpleSessionStateManager.
+ */
+ public SimpleSessionStateManager() {
+ super();
+ }
+
+ /**
+ * @see ITreeSessionStateManager#getSessionState(ITreeModel)
+ */
+ public Object getSessionState(ITreeModel objModel) {
+ return objModel;
+ }
+
+ /**
+ * @see ITreeSessionStateManager#getModel(Object)
+ */
+ public ITreeModel getModel(Object objSessionState) {
+ return (ITreeModel)objSessionState;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeDataModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeDataModel.java
new file mode 100644
index 0000000..71678c7
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeDataModel.java
@@ -0,0 +1,110 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+import javax.swing.tree.TreePath;
+
+import org.apache.tapestry.contrib.tree.model.ITreeDataModel;
+import org.apache.tapestry.contrib.tree.model.ITreeNode;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class SimpleTreeDataModel implements ITreeDataModel, Serializable {
+
+ protected ITreeNode m_objRootNode;
+ /**
+ * Constructor for SimpleTreeDataModel.
+ */
+ public SimpleTreeDataModel(ITreeNode objRootNode) {
+ super();
+ m_objRootNode = objRootNode;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getRoot()
+ */
+ public Object getRoot() {
+ return m_objRootNode;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getChildCount(Object)
+ */
+ public int getChildCount(Object objParent) {
+ ITreeNode objParentNode = (ITreeNode)objParent;
+
+ return objParentNode.getChildCount();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getChildren(Object)
+ */
+ public Iterator getChildren(Object objParent) {
+ ITreeNode objParentNode = (ITreeNode)objParent;
+ return objParentNode.getChildren().iterator();
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getObject(Object)
+ */
+ public Object getObject(Object objUniqueKey) {
+ if(objUniqueKey != null) {
+ TreePath objPath = (TreePath)objUniqueKey;
+ return objPath.getLastPathComponent();
+ }
+ return null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getUniqueKey(Object, Object)
+ */
+ public Object getUniqueKey(Object objTarget, Object objParentUniqueKey) {
+ TreePath objPath = (TreePath)objParentUniqueKey;
+ Object objTargetUID = null;
+ if(objPath != null){
+ objTargetUID = objPath.pathByAddingChild(objTarget);
+ }else{
+ objTargetUID = new TreePath(objTarget);
+ }
+ return objTargetUID;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#isAncestorOf(Object, Object)
+ */
+ public boolean isAncestorOf(Object objTargetUniqueKey, Object objParentUniqueKey) {
+ TreePath objParentPath = (TreePath)objParentUniqueKey;
+ TreePath objTargetPath = (TreePath)objTargetUniqueKey;
+ boolean bResult = objParentPath.isDescendant(objTargetPath);
+ return bResult;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeDataModel#getParentUniqueKey
+ */
+ public Object getParentUniqueKey(Object objChildUniqueKey) {
+ TreePath objChildPath = (TreePath)objChildUniqueKey;
+ TreePath objParentPath = objChildPath.getParentPath();
+ if(objParentPath == null)
+ return null;
+ return objParentPath.getLastPathComponent();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeModel.java
new file mode 100644
index 0000000..35c51bc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeModel.java
@@ -0,0 +1,58 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.contrib.tree.model.ITreeDataModel;
+import org.apache.tapestry.contrib.tree.model.ITreeModel;
+import org.apache.tapestry.contrib.tree.model.ITreeStateModel;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class SimpleTreeModel implements ITreeModel, Serializable{
+
+ private ITreeDataModel m_objDataModel;
+ private ITreeStateModel m_objTreeStateModel;
+
+ /**
+ * Constructor for SimpleTreeModel.
+ */
+ public SimpleTreeModel(ITreeDataModel objDataModel) {
+ this(objDataModel, new SimpleTreeStateModel());
+ }
+
+ public SimpleTreeModel(ITreeDataModel objDataModel, ITreeStateModel objTreeStateModel) {
+ super();
+ m_objDataModel = objDataModel;
+ m_objTreeStateModel = objTreeStateModel;
+ }
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeModel#getTreeDataModel()
+ */
+ public ITreeDataModel getTreeDataModel() {
+ return m_objDataModel;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeModel#getTreeStateModel()
+ */
+ public ITreeStateModel getTreeStateModel() {
+ return m_objTreeStateModel;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeStateModel.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeStateModel.java
new file mode 100644
index 0000000..88d0e8c
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/SimpleTreeStateModel.java
@@ -0,0 +1,106 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tapestry.contrib.tree.model.ITreeStateModel;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class SimpleTreeStateModel implements ITreeStateModel, Serializable{
+
+ private Set m_setExpanded;
+ private Object m_objSelectedNodeUID = null;
+
+ /**
+ * Constructor for SimpleTreeStateModel.
+ */
+ public SimpleTreeStateModel() {
+ super();
+ initialize();
+ }
+ private void initialize(){
+ m_setExpanded = new HashSet();
+ m_objSelectedNodeUID = null;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#getExpandSelection()
+ */
+ public Set getExpandSelection() {
+ return m_setExpanded;
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#expand(Object)
+ */
+ public void expand(Object objUniqueKey) {
+ m_setExpanded.add(objUniqueKey);
+ setSelectedNode(objUniqueKey);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#expandPath(Object)
+ */
+ public void expandPath(Object objUniqueKey) {
+ m_setExpanded.add(objUniqueKey);
+ setSelectedNode(objUniqueKey);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#collapse(Object)
+ */
+ public void collapse(Object objUniqueKey) {
+ m_setExpanded.remove(objUniqueKey);
+ setSelectedNode(objUniqueKey);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#collapsePath(Object)
+ */
+ public void collapsePath(Object objUniqueKey) {
+ m_setExpanded.remove(objUniqueKey);
+ setSelectedNode(objUniqueKey);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#isUniqueKeyExpanded(Object)
+ */
+ public boolean isUniqueKeyExpanded(Object objUniqueKey) {
+ return m_setExpanded.contains(objUniqueKey);
+ }
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#getSelectedNode()
+ */
+ public Object getSelectedNode() {
+ return m_objSelectedNodeUID;
+ }
+ private void setSelectedNode(Object objUniqueKey){
+ if(m_objSelectedNodeUID == null || !m_objSelectedNodeUID.equals(objUniqueKey))
+ m_objSelectedNodeUID = objUniqueKey;
+ }
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeStateModel#resetState()
+ */
+ public void resetState() {
+ initialize();
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/TreeNode.java b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/TreeNode.java
new file mode 100644
index 0000000..ba15fe6
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/tree/simple/TreeNode.java
@@ -0,0 +1,108 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.tree.simple;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.tapestry.contrib.tree.model.IMutableTreeNode;
+import org.apache.tapestry.contrib.tree.model.ITreeNode;
+
+/**
+ * @author ceco
+ * @version $Id$
+ */
+public class TreeNode implements IMutableTreeNode {
+
+ protected Set m_setChildren;
+ protected IMutableTreeNode m_objParentNode;
+
+ /**
+ * Constructor for TreeNode.
+ */
+ public TreeNode() {
+ this(null);
+ }
+ public TreeNode(IMutableTreeNode parentNode) {
+ super();
+ m_objParentNode = parentNode;
+ m_setChildren = new HashSet();
+ }
+
+
+ public int getChildCount() {
+ return m_setChildren.size();
+ }
+
+ public ITreeNode getParent() {
+ return m_objParentNode;
+ }
+
+ public boolean getAllowsChildren() {
+ return true;
+ }
+
+ public boolean isLeaf() {
+ return m_setChildren.size() == 0 ? true:false;
+ }
+
+ public Collection children() {
+ return m_setChildren;
+ }
+
+
+ public void insert(IMutableTreeNode child) {
+ child.setParent(this);
+ m_setChildren.add(child);
+ }
+
+ public void remove(IMutableTreeNode node) {
+ m_setChildren.remove(node);
+ }
+
+ public void removeFromParent() {
+ m_objParentNode.remove(this);
+ m_objParentNode = null;
+ }
+
+ public void setParent(IMutableTreeNode newParent) {
+ m_objParentNode = newParent;
+ }
+
+ public void insert(Collection colChildren){
+ for (Iterator iter = colChildren.iterator(); iter.hasNext();) {
+ IMutableTreeNode element = (IMutableTreeNode) iter.next();
+ element.setParent(this);
+ m_setChildren.add(element);
+ }
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeNode#containsChild(ITreeNode)
+ */
+ public boolean containsChild(ITreeNode node) {
+ return m_setChildren.contains(node);
+ }
+
+ /**
+ * @see org.apache.tapestry.contrib.tree.model.ITreeNode#getChildren()
+ */
+ public Collection getChildren() {
+ return m_setChildren;
+ }
+
+}
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.java b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.java
new file mode 100644
index 0000000..fca6a21
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.java
@@ -0,0 +1,240 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.valid;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.valid.DateValidator;
+import org.apache.tapestry.valid.IValidator;
+import org.apache.tapestry.valid.ValidField;
+
+/**
+ *
+ * Backwards compatible version of the 1.0.7 DateField component.
+ *
+ * <table border=1>
+ * <tr>
+ * <td>Parameter</td>
+ * <td>Type</td>
+ * <td>Read / Write </td>
+ * <td>Required</td>
+ * <td>Default</td>
+ * <td>Description</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>date</td>
+ * <td>java.util.Date</td>
+ * <td>R / W</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The date property to edit.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>required</td>
+ * <td>boolean</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td>no</td>
+ * <td>If true, then a value must be entered.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>minimum</td>
+ * <td>java.util.Date</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td> </td>
+ * <td>If provided, the date entered must be equal to or later than the
+ * provided minimum date.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>maximum</td>
+ * <td>java.util.Date</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td> </td>
+ * <td>If provided, the date entered must be less than or equal to the
+ * provided maximum date.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>displayName</td>
+ * <td>String</td>
+ * <td>R</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>A textual name for the field that is used when formulating error messages.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>format</td>
+ * <td>{@link DateFormat}</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td>Default format <code>MM/dd/yyyy</code></td>
+ * <td>The format used to display and parse dates.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>displayFormat</td>
+ * <td>{@link String}</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td><code>MM/DD/YYYY</code></td>
+ * <td>The format string presented to the user if the date entered is in an
+ * incorrect format. e.g. the format object throws a ParseException.</td>
+ * </tr>
+ *
+ * </table>
+ *
+ * <p>Informal parameters are allowed. A body is not allowed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ * @see ValidField
+ *
+ **/
+
+public abstract class DateField extends ValidField
+{
+ public abstract Date getDate();
+ public abstract void setDate(Date date);
+
+ private IBinding minimumBinding;
+ private IBinding maximumBinding;
+ private IBinding requiredBinding;
+ private IBinding formatBinding;
+ private IBinding displayFormatBinding;
+
+
+ /**
+ * Overrides {@link ValidField#getValidator()} to construct a validator
+ * on-the-fly.
+ *
+ **/
+
+ public IValidator getValidator()
+ {
+ DateValidator validator = new DateValidator();
+
+ if (minimumBinding != null)
+ {
+ Date minimum = (Date) minimumBinding.getObject("minimum", Date.class);
+ validator.setMinimum(minimum);
+ }
+
+ if (maximumBinding != null)
+ {
+ Date maximum = (Date) maximumBinding.getObject("maximum", Date.class);
+ validator.setMaximum(maximum);
+ }
+
+ if (requiredBinding != null)
+ {
+ boolean required = requiredBinding.getBoolean();
+ validator.setRequired(required);
+ }
+
+ if (formatBinding != null)
+ {
+ DateFormat format =
+ (DateFormat) formatBinding.getObject("format", DateFormat.class);
+ validator.setFormat(format);
+ }
+
+ if (displayFormatBinding != null)
+ {
+ String displayFormat =
+ (String) displayFormatBinding.getObject("displayFormat", String.class);
+ validator.setDisplayFormat(displayFormat);
+ }
+
+ return validator;
+ }
+
+ public IBinding getRequiredBinding()
+ {
+ return requiredBinding;
+ }
+
+ public void setRequiredBinding(IBinding requiredBinding)
+ {
+ this.requiredBinding = requiredBinding;
+ }
+
+ public IBinding getFormatBinding()
+ {
+ return formatBinding;
+ }
+
+ public void setFormatBinding(IBinding formatBinding)
+ {
+ this.formatBinding = formatBinding;
+ }
+
+ public IBinding getDisplayFormatBinding()
+ {
+ return displayFormatBinding;
+ }
+
+ public void setDisplayFormatBinding(IBinding displayFormatBinding)
+ {
+ this.displayFormatBinding = displayFormatBinding;
+ }
+
+ public IBinding getMinimumBinding() {
+ return minimumBinding;
+ }
+
+ public void setMinimumBinding(IBinding value)
+ {
+ this.minimumBinding = value;
+ }
+
+ public IBinding getMaximumBinding()
+ {
+ return maximumBinding;
+ }
+
+ public void setMaximumBinding(IBinding value)
+ {
+ this.maximumBinding = value;
+ }
+
+ /**
+ * @see org.apache.tapestry.valid.ValidField#getValue()
+ */
+ public Object getValue()
+ {
+ return getDate();
+ }
+
+ /**
+ * @see org.apache.tapestry.valid.ValidField#setValue(java.lang.Object)
+ */
+ public void setValue(Object value)
+ {
+ setDate((Date) value);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.jwc
new file mode 100644
index 0000000..d3fe179
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/DateField.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.valid.DateField">
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+ <parameter name="hidden" type="boolean" direction="in"/>
+ <parameter name="displayWidth" type="int" direction="in"/>
+ <parameter name="maximumLength" type="int" direction="in"/>
+
+ <parameter name="date" type="java.util.Date" direction="auto" required="yes" />
+ <parameter name="displayName" type="java.lang.String" direction="auto" required="yes" />
+ <parameter name="maximum" type="java.util.Date" />
+ <parameter name="minimum" type="java.util.Date" />
+ <parameter name="required" />
+ <parameter name="format" type="java.text.DateFormat" />
+ <parameter name="displayFormat" type="java.lang.String" />
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="value"/>
+ <reserved-parameter name="size"/>
+ <reserved-parameter name="maxlength"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.java b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.java
new file mode 100644
index 0000000..2903ed9
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.java
@@ -0,0 +1,198 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.valid;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.valid.IValidator;
+import org.apache.tapestry.valid.NumberValidator;
+import org.apache.tapestry.valid.ValidField;
+
+/**
+ *
+ * Backwards compatible version of the 1.0.7 NumericField component.
+ *
+ * <table border=1>
+ * <tr>
+ * <td>Parameter</td>
+ * <td>Type</td>
+ * <td>Read / Write </td>
+ * <td>Required</td>
+ * <td>Default</td>
+ * <td>Description</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>value</td>
+ * <td>{@link Number}</td>
+ * <td>R / W</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The value to be updated.
+ *
+ * <p>When the form is submitted, this parameter is only updated if the value
+ * is valid.
+ *
+ * <p>When rendering, a null value will render as the empty string. A value
+ * of zero will render normally.
+ *
+ * <p>When the form is submitted, the type of the binding
+ * is used to determine what kind of object to convert the string to.
+ *
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>minimum</td>
+ * <td>{@link Number}</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td> </td>
+ * <td>The minimum value accepted for the field.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>maximum</td>
+ * <td>{@link Number}</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td> </td>
+ * <td>The maximum value accepted for the field.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>required</td>
+ * <td>boolean</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td>false</td>
+ * <td>If true, then a non-null value must be provided. If the field is not
+ * required, and a null (all whitespace) value is supplied in the field, then the
+ * value parameter is <em>not</em> updated.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>displayName</td>
+ * <td>String</td>
+ * <td>R</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>A textual name for the field that is used when formulating error messages.
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>type</td>
+ * <td>String</td>
+ * <td>R</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The class name used to convert the value entered. See {@link NumberValidator#setValueType(String)}
+ * </td>
+ * </tr>
+ *
+ * </table>
+ *
+ * <p>May not contain a body. May have informal parameters.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ * @see ValidField
+ *
+ **/
+
+public abstract class NumericField extends ValidField
+{
+ private IBinding minimumBinding;
+ private IBinding maximumBinding;
+ private IBinding requiredBinding;
+ private IBinding typeBinding;
+
+ public IBinding getMinimumBinding()
+ {
+ return minimumBinding;
+ }
+
+ public void setMinimumBinding(IBinding value)
+ {
+ minimumBinding = value;
+ }
+
+ public IBinding getMaximumBinding()
+ {
+ return maximumBinding;
+ }
+
+ public void setMaximumBinding(IBinding value)
+ {
+ maximumBinding = value;
+ }
+
+ public IBinding getRequiredBinding()
+ {
+ return requiredBinding;
+ }
+
+ public void setRequiredBinding(IBinding requiredBinding)
+ {
+ this.requiredBinding = requiredBinding;
+ }
+
+ public IBinding getTypeBinding()
+ {
+ return typeBinding;
+ }
+
+ public void setTypeBinding(IBinding typeNameBinding)
+ {
+ this.typeBinding = typeNameBinding;
+ }
+
+ /**
+ * Overrides {@link ValidField#getValidator()} to construct
+ * a validator on the fly.
+ **/
+
+ public IValidator getValidator()
+ {
+ NumberValidator validator = new NumberValidator();
+
+ if (minimumBinding != null)
+ {
+ Number minimum = (Number) minimumBinding.getObject("minimum", Number.class);
+ validator.setMinimum(minimum);
+ }
+
+ if (maximumBinding != null)
+ {
+ Number maximum = (Number) maximumBinding.getObject("maximum", Number.class);
+ validator.setMaximum(maximum);
+ }
+
+ if (requiredBinding != null)
+ {
+ boolean required = requiredBinding.getBoolean();
+ validator.setRequired(required);
+ }
+
+ if (typeBinding != null)
+ {
+ validator.setValueType(typeBinding.getString());
+ }
+
+ return validator;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.jwc
new file mode 100644
index 0000000..f37d3fc
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/NumericField.jwc
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.valid.NumericField">
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+ <parameter name="hidden" type="boolean" direction="in"/>
+ <parameter name="displayWidth" type="int" direction="in" />
+ <parameter name="maximumLength" type="int" direction="in" />
+
+ <parameter name="value" type="java.lang.Object" direction="auto" required="yes"/>
+ <parameter name="displayName" type="java.lang.String" direction="auto" required="yes"/>
+ <parameter name="maximum" type="java.lang.Number"/>
+ <parameter name="minimum" type="java.lang.Number"/>
+ <parameter name="required"/>
+ <parameter name="type" type="java.lang.String" required="yes" direction="in" />
+
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.java b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.java
new file mode 100644
index 0000000..bb7bf86
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.java
@@ -0,0 +1,188 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.contrib.valid;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.valid.IValidator;
+import org.apache.tapestry.valid.StringValidator;
+import org.apache.tapestry.valid.ValidField;
+
+/**
+ *
+ * Backwards compatible version of the 1.0.7 ValidatingTextField component.
+ *
+ * <table border=1>
+ * <tr>
+ * <td>Parameter</td>
+ * <td>Type</td>
+ * <td>Read / Write </td>
+ * <td>Required</td>
+ * <td>Default</td>
+ * <td>Description</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>text</td>
+ * <td>java.lang.String</td>
+ * <td>R / W</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>The text inside the text field.
+ *
+ * <p>When the form is submitted, the binding is only updated if the value
+ * is valid.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>minimumLength</td>
+ * <td>int</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td>0</td>
+ * <td>The minimum length (number of characters read) for the field. The
+ * value provided in the request is trimmed of leading and trailing whitespace.
+ *
+ * <p>If a field is not required and no value is given, then minimumLength is ignored.
+ * Minimum length only applies if <em>some</em> non-null value is given.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>required</td>
+ * <td>boolean</td>
+ * <td>R</td>
+ * <td>no</td>
+ * <td>false</td>
+ * <td>If true, then a non-null value must be provided. A value consisting
+ * only of whitespace is considered null. </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>displayName</td>
+ * <td>String</td>
+ * <td>R</td>
+ * <td>yes</td>
+ * <td> </td>
+ * <td>A textual name for the field that is used when formulating error messages.
+ * </td>
+ * </tr>
+ *
+ * </table>
+ *
+ * <p>May not have a body. May have informal parameters.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ * @see ValidField
+ *
+ **/
+
+public abstract class ValidatingTextField extends ValidField
+{
+ private IBinding minimumLengthBinding;
+ private IBinding requiredBinding;
+ private IBinding valueBinding;
+
+ /* (non-Javadoc)
+ * @see org.apache.tapestry.valid.ValidField#getValue()
+ */
+ public Object getValue()
+ {
+ if (getTextBinding() != null)
+ {
+ return getTextBinding().getObject();
+ }
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.tapestry.valid.ValidField#setValue(java.lang.Object)
+ */
+ public void setValue(Object value)
+ {
+ if(getTextBinding() != null) {
+ getTextBinding().setObject(value);
+ }
+ // otherwise do nothing, we have nowhere to bind the value to
+ }
+
+ public IBinding getValueBinding()
+ {
+ return valueBinding;
+ }
+
+ public void setValueBinding(IBinding binding)
+ {
+ valueBinding = binding;
+ }
+
+ /** Returns the valueBinding. **/
+ public IBinding getTextBinding()
+ {
+ return getValueBinding();
+ }
+
+ /** Updates valueBinding. **/
+ public void setTextBinding(IBinding value)
+ {
+ setValueBinding(value);
+ }
+
+ public IBinding getMinimumLengthBinding()
+ {
+ return minimumLengthBinding;
+ }
+
+ public void setMinimumLengthBinding(IBinding value)
+ {
+ minimumLengthBinding = value;
+ }
+
+ public IBinding getRequiredBinding()
+ {
+ return requiredBinding;
+ }
+
+ public void setRequiredBinding(IBinding requiredBinding)
+ {
+ this.requiredBinding = requiredBinding;
+ }
+
+ /**
+ * Overrides {@link ValidField#getValidator()} to construct
+ * a validator on the fly.
+ *
+ **/
+ public IValidator getValidator()
+ {
+ StringValidator validator = new StringValidator();
+
+ if (requiredBinding != null)
+ {
+ boolean required = requiredBinding.getBoolean();
+
+ validator.setRequired(required);
+ }
+
+ if (minimumLengthBinding != null)
+ {
+ int minimumLength = minimumLengthBinding.getInt();
+
+ validator.setMinimumLength(minimumLength);
+ }
+
+ return validator;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.jwc b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.jwc
new file mode 100644
index 0000000..b6dce0a
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/ValidatingTextField.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.contrib.valid.ValidatingTextField" allow-body="no">
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+ <parameter name="hidden" type="boolean" direction="in"/>
+ <parameter name="displayWidth" type="int" direction="in"/>
+ <parameter name="maximumLength" type="int" direction="in"/>
+
+ <parameter name="text" type="java.lang.String" required="yes" direction="custom"/>
+ <parameter name="displayName" type="java.lang.String" required="yes" direction="auto" />
+ <parameter name="minimumLength" type="java.lang.Integer" direction="custom" />
+ <parameter name="required" direction="custom" />
+
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="value"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-contrib/src/org/apache/tapestry/contrib/valid/package.html b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/package.html
new file mode 100644
index 0000000..c01ca74
--- /dev/null
+++ b/tapestry-contrib/src/org/apache/tapestry/contrib/valid/package.html
@@ -0,0 +1,20 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+</head>
+<body>
+
+Backwards compatible versions of Tapestry 1.0.7's validating text fields,
+built as wrappers
+around the 1.0.8 versions.
+
+<p>All that is necessary to use these without changing existing Pages
+(built against 1.0.7 or earlier)
+is to add aliases for these components to the
+application specification.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/examples/DevelopmentEnvironment/build.xml b/tapestry-examples/DevelopmentEnvironment/build.xml
similarity index 100%
rename from examples/DevelopmentEnvironment/build.xml
rename to tapestry-examples/DevelopmentEnvironment/build.xml
diff --git a/examples/DevelopmentEnvironment/config/jetty-dev-env.xml b/tapestry-examples/DevelopmentEnvironment/config/jetty-dev-env.xml
similarity index 100%
rename from examples/DevelopmentEnvironment/config/jetty-dev-env.xml
rename to tapestry-examples/DevelopmentEnvironment/config/jetty-dev-env.xml
diff --git a/examples/DevelopmentEnvironment/config/webdefault.xml b/tapestry-examples/DevelopmentEnvironment/config/webdefault.xml
similarity index 100%
rename from examples/DevelopmentEnvironment/config/webdefault.xml
rename to tapestry-examples/DevelopmentEnvironment/config/webdefault.xml
diff --git a/examples/DevelopmentEnvironment/web/WEB-INF/web.xml b/tapestry-examples/DevelopmentEnvironment/web/WEB-INF/web.xml
similarity index 100%
rename from examples/DevelopmentEnvironment/web/WEB-INF/web.xml
rename to tapestry-examples/DevelopmentEnvironment/web/WEB-INF/web.xml
diff --git a/examples/DevelopmentEnvironment/web/index.html b/tapestry-examples/DevelopmentEnvironment/web/index.html
similarity index 100%
rename from examples/DevelopmentEnvironment/web/index.html
rename to tapestry-examples/DevelopmentEnvironment/web/index.html
diff --git a/examples/Vlib/.cvsignore b/tapestry-examples/Vlib/.cvsignore
similarity index 100%
rename from examples/Vlib/.cvsignore
rename to tapestry-examples/Vlib/.cvsignore
diff --git a/examples/Vlib/build.xml b/tapestry-examples/Vlib/build.xml
similarity index 100%
rename from examples/Vlib/build.xml
rename to tapestry-examples/Vlib/build.xml
diff --git a/examples/Vlib/context/ApplicationUnavailable.html b/tapestry-examples/Vlib/context/ApplicationUnavailable.html
similarity index 100%
rename from examples/Vlib/context/ApplicationUnavailable.html
rename to tapestry-examples/Vlib/context/ApplicationUnavailable.html
diff --git a/examples/Vlib/context/BookMatches.html b/tapestry-examples/Vlib/context/BookMatches.html
similarity index 100%
rename from examples/Vlib/context/BookMatches.html
rename to tapestry-examples/Vlib/context/BookMatches.html
diff --git a/examples/Vlib/context/BorrowedBooks.html b/tapestry-examples/Vlib/context/BorrowedBooks.html
similarity index 100%
rename from examples/Vlib/context/BorrowedBooks.html
rename to tapestry-examples/Vlib/context/BorrowedBooks.html
diff --git a/examples/Vlib/context/ConfirmBookDelete.html b/tapestry-examples/Vlib/context/ConfirmBookDelete.html
similarity index 100%
rename from examples/Vlib/context/ConfirmBookDelete.html
rename to tapestry-examples/Vlib/context/ConfirmBookDelete.html
diff --git a/examples/Vlib/context/EditBook.html b/tapestry-examples/Vlib/context/EditBook.html
similarity index 100%
rename from examples/Vlib/context/EditBook.html
rename to tapestry-examples/Vlib/context/EditBook.html
diff --git a/examples/Vlib/context/EditProfile.html b/tapestry-examples/Vlib/context/EditProfile.html
similarity index 100%
rename from examples/Vlib/context/EditProfile.html
rename to tapestry-examples/Vlib/context/EditProfile.html
diff --git a/examples/Vlib/context/EditPublishers.html b/tapestry-examples/Vlib/context/EditPublishers.html
similarity index 100%
rename from examples/Vlib/context/EditPublishers.html
rename to tapestry-examples/Vlib/context/EditPublishers.html
diff --git a/examples/Vlib/context/EditUsers.html b/tapestry-examples/Vlib/context/EditUsers.html
similarity index 100%
rename from examples/Vlib/context/EditUsers.html
rename to tapestry-examples/Vlib/context/EditUsers.html
diff --git a/examples/Vlib/context/GiveAwayBooks.html b/tapestry-examples/Vlib/context/GiveAwayBooks.html
similarity index 100%
rename from examples/Vlib/context/GiveAwayBooks.html
rename to tapestry-examples/Vlib/context/GiveAwayBooks.html
diff --git a/examples/Vlib/context/Home.html b/tapestry-examples/Vlib/context/Home.html
similarity index 100%
rename from examples/Vlib/context/Home.html
rename to tapestry-examples/Vlib/context/Home.html
diff --git a/examples/Vlib/context/Login.html b/tapestry-examples/Vlib/context/Login.html
similarity index 100%
rename from examples/Vlib/context/Login.html
rename to tapestry-examples/Vlib/context/Login.html
diff --git a/examples/Vlib/context/MyLibrary.html b/tapestry-examples/Vlib/context/MyLibrary.html
similarity index 100%
rename from examples/Vlib/context/MyLibrary.html
rename to tapestry-examples/Vlib/context/MyLibrary.html
diff --git a/examples/Vlib/context/NewBook.html b/tapestry-examples/Vlib/context/NewBook.html
similarity index 100%
rename from examples/Vlib/context/NewBook.html
rename to tapestry-examples/Vlib/context/NewBook.html
diff --git a/examples/Vlib/context/Register.html b/tapestry-examples/Vlib/context/Register.html
similarity index 100%
rename from examples/Vlib/context/Register.html
rename to tapestry-examples/Vlib/context/Register.html
diff --git a/examples/Vlib/context/TransferBooksSelect.html b/tapestry-examples/Vlib/context/TransferBooksSelect.html
similarity index 100%
rename from examples/Vlib/context/TransferBooksSelect.html
rename to tapestry-examples/Vlib/context/TransferBooksSelect.html
diff --git a/examples/Vlib/context/TransferBooksTransfer.html b/tapestry-examples/Vlib/context/TransferBooksTransfer.html
similarity index 100%
rename from examples/Vlib/context/TransferBooksTransfer.html
rename to tapestry-examples/Vlib/context/TransferBooksTransfer.html
diff --git a/examples/Vlib/context/ViewBook.html b/tapestry-examples/Vlib/context/ViewBook.html
similarity index 100%
rename from examples/Vlib/context/ViewBook.html
rename to tapestry-examples/Vlib/context/ViewBook.html
diff --git a/examples/Vlib/context/ViewPerson.html b/tapestry-examples/Vlib/context/ViewPerson.html
similarity index 100%
rename from examples/Vlib/context/ViewPerson.html
rename to tapestry-examples/Vlib/context/ViewPerson.html
diff --git a/examples/Vlib/context/WEB-INF/ApplicationUnavailable.page b/tapestry-examples/Vlib/context/WEB-INF/ApplicationUnavailable.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ApplicationUnavailable.page
rename to tapestry-examples/Vlib/context/WEB-INF/ApplicationUnavailable.page
diff --git a/examples/Vlib/context/WEB-INF/BookLink.html b/tapestry-examples/Vlib/context/WEB-INF/BookLink.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BookLink.html
rename to tapestry-examples/Vlib/context/WEB-INF/BookLink.html
diff --git a/examples/Vlib/context/WEB-INF/BookLink.jwc b/tapestry-examples/Vlib/context/WEB-INF/BookLink.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BookLink.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/BookLink.jwc
diff --git a/examples/Vlib/context/WEB-INF/BookMatches.page b/tapestry-examples/Vlib/context/WEB-INF/BookMatches.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BookMatches.page
rename to tapestry-examples/Vlib/context/WEB-INF/BookMatches.page
diff --git a/examples/Vlib/context/WEB-INF/BookMatches.properties b/tapestry-examples/Vlib/context/WEB-INF/BookMatches.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BookMatches.properties
rename to tapestry-examples/Vlib/context/WEB-INF/BookMatches.properties
diff --git a/examples/Vlib/context/WEB-INF/Border.html b/tapestry-examples/Vlib/context/WEB-INF/Border.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Border.html
rename to tapestry-examples/Vlib/context/WEB-INF/Border.html
diff --git a/examples/Vlib/context/WEB-INF/Border.jwc b/tapestry-examples/Vlib/context/WEB-INF/Border.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Border.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/Border.jwc
diff --git a/examples/Vlib/context/WEB-INF/Border.properties b/tapestry-examples/Vlib/context/WEB-INF/Border.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Border.properties
rename to tapestry-examples/Vlib/context/WEB-INF/Border.properties
diff --git a/examples/Vlib/context/WEB-INF/Borrow.html b/tapestry-examples/Vlib/context/WEB-INF/Borrow.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Borrow.html
rename to tapestry-examples/Vlib/context/WEB-INF/Borrow.html
diff --git a/examples/Vlib/context/WEB-INF/Borrow.jwc b/tapestry-examples/Vlib/context/WEB-INF/Borrow.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Borrow.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/Borrow.jwc
diff --git a/examples/Vlib/context/WEB-INF/BorrowedBooks.page b/tapestry-examples/Vlib/context/WEB-INF/BorrowedBooks.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BorrowedBooks.page
rename to tapestry-examples/Vlib/context/WEB-INF/BorrowedBooks.page
diff --git a/examples/Vlib/context/WEB-INF/BorrowedBooks.properties b/tapestry-examples/Vlib/context/WEB-INF/BorrowedBooks.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/BorrowedBooks.properties
rename to tapestry-examples/Vlib/context/WEB-INF/BorrowedBooks.properties
diff --git a/examples/Vlib/context/WEB-INF/Browser.jwc b/tapestry-examples/Vlib/context/WEB-INF/Browser.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Browser.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/Browser.jwc
diff --git a/examples/Vlib/context/WEB-INF/ColumnSorter.html b/tapestry-examples/Vlib/context/WEB-INF/ColumnSorter.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ColumnSorter.html
rename to tapestry-examples/Vlib/context/WEB-INF/ColumnSorter.html
diff --git a/examples/Vlib/context/WEB-INF/ColumnSorter.jwc b/tapestry-examples/Vlib/context/WEB-INF/ColumnSorter.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ColumnSorter.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/ColumnSorter.jwc
diff --git a/examples/Vlib/context/WEB-INF/ConfirmBookDelete.page b/tapestry-examples/Vlib/context/WEB-INF/ConfirmBookDelete.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ConfirmBookDelete.page
rename to tapestry-examples/Vlib/context/WEB-INF/ConfirmBookDelete.page
diff --git a/examples/Vlib/context/WEB-INF/ConfirmBookDelete.properties b/tapestry-examples/Vlib/context/WEB-INF/ConfirmBookDelete.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ConfirmBookDelete.properties
rename to tapestry-examples/Vlib/context/WEB-INF/ConfirmBookDelete.properties
diff --git a/examples/Vlib/context/WEB-INF/EditBook.page b/tapestry-examples/Vlib/context/WEB-INF/EditBook.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditBook.page
rename to tapestry-examples/Vlib/context/WEB-INF/EditBook.page
diff --git a/examples/Vlib/context/WEB-INF/EditBook.properties b/tapestry-examples/Vlib/context/WEB-INF/EditBook.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditBook.properties
rename to tapestry-examples/Vlib/context/WEB-INF/EditBook.properties
diff --git a/examples/Vlib/context/WEB-INF/EditProfile.page b/tapestry-examples/Vlib/context/WEB-INF/EditProfile.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditProfile.page
rename to tapestry-examples/Vlib/context/WEB-INF/EditProfile.page
diff --git a/examples/Vlib/context/WEB-INF/EditProfile.properties b/tapestry-examples/Vlib/context/WEB-INF/EditProfile.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditProfile.properties
rename to tapestry-examples/Vlib/context/WEB-INF/EditProfile.properties
diff --git a/examples/Vlib/context/WEB-INF/EditPublishers.page b/tapestry-examples/Vlib/context/WEB-INF/EditPublishers.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditPublishers.page
rename to tapestry-examples/Vlib/context/WEB-INF/EditPublishers.page
diff --git a/examples/Vlib/context/WEB-INF/EditPublishers.properties b/tapestry-examples/Vlib/context/WEB-INF/EditPublishers.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditPublishers.properties
rename to tapestry-examples/Vlib/context/WEB-INF/EditPublishers.properties
diff --git a/examples/Vlib/context/WEB-INF/EditUsers.page b/tapestry-examples/Vlib/context/WEB-INF/EditUsers.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditUsers.page
rename to tapestry-examples/Vlib/context/WEB-INF/EditUsers.page
diff --git a/examples/Vlib/context/WEB-INF/EditUsers.properties b/tapestry-examples/Vlib/context/WEB-INF/EditUsers.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/EditUsers.properties
rename to tapestry-examples/Vlib/context/WEB-INF/EditUsers.properties
diff --git a/examples/Vlib/context/WEB-INF/GiveAwayBooks.page b/tapestry-examples/Vlib/context/WEB-INF/GiveAwayBooks.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/GiveAwayBooks.page
rename to tapestry-examples/Vlib/context/WEB-INF/GiveAwayBooks.page
diff --git a/examples/Vlib/context/WEB-INF/GiveAwayBooks.properties b/tapestry-examples/Vlib/context/WEB-INF/GiveAwayBooks.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/GiveAwayBooks.properties
rename to tapestry-examples/Vlib/context/WEB-INF/GiveAwayBooks.properties
diff --git a/examples/Vlib/context/WEB-INF/Home.page b/tapestry-examples/Vlib/context/WEB-INF/Home.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Home.page
rename to tapestry-examples/Vlib/context/WEB-INF/Home.page
diff --git a/examples/Vlib/context/WEB-INF/Information.html b/tapestry-examples/Vlib/context/WEB-INF/Information.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Information.html
rename to tapestry-examples/Vlib/context/WEB-INF/Information.html
diff --git a/examples/Vlib/context/WEB-INF/Information.jwc b/tapestry-examples/Vlib/context/WEB-INF/Information.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Information.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/Information.jwc
diff --git a/examples/Vlib/context/WEB-INF/Login.page b/tapestry-examples/Vlib/context/WEB-INF/Login.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Login.page
rename to tapestry-examples/Vlib/context/WEB-INF/Login.page
diff --git a/examples/Vlib/context/WEB-INF/MyLibrary.page b/tapestry-examples/Vlib/context/WEB-INF/MyLibrary.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/MyLibrary.page
rename to tapestry-examples/Vlib/context/WEB-INF/MyLibrary.page
diff --git a/examples/Vlib/context/WEB-INF/MyLibrary.properties b/tapestry-examples/Vlib/context/WEB-INF/MyLibrary.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/MyLibrary.properties
rename to tapestry-examples/Vlib/context/WEB-INF/MyLibrary.properties
diff --git a/examples/Vlib/context/WEB-INF/NewBook.page b/tapestry-examples/Vlib/context/WEB-INF/NewBook.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/NewBook.page
rename to tapestry-examples/Vlib/context/WEB-INF/NewBook.page
diff --git a/examples/Vlib/context/WEB-INF/NewBook.properties b/tapestry-examples/Vlib/context/WEB-INF/NewBook.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/NewBook.properties
rename to tapestry-examples/Vlib/context/WEB-INF/NewBook.properties
diff --git a/examples/Vlib/context/WEB-INF/PersonLink.html b/tapestry-examples/Vlib/context/WEB-INF/PersonLink.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/PersonLink.html
rename to tapestry-examples/Vlib/context/WEB-INF/PersonLink.html
diff --git a/examples/Vlib/context/WEB-INF/PersonLink.jwc b/tapestry-examples/Vlib/context/WEB-INF/PersonLink.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/PersonLink.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/PersonLink.jwc
diff --git a/examples/Vlib/context/WEB-INF/Publisher.script b/tapestry-examples/Vlib/context/WEB-INF/Publisher.script
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Publisher.script
rename to tapestry-examples/Vlib/context/WEB-INF/Publisher.script
diff --git a/examples/Vlib/context/WEB-INF/Question.html b/tapestry-examples/Vlib/context/WEB-INF/Question.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Question.html
rename to tapestry-examples/Vlib/context/WEB-INF/Question.html
diff --git a/examples/Vlib/context/WEB-INF/Question.jwc b/tapestry-examples/Vlib/context/WEB-INF/Question.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Question.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/Question.jwc
diff --git a/examples/Vlib/context/WEB-INF/Register.page b/tapestry-examples/Vlib/context/WEB-INF/Register.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Register.page
rename to tapestry-examples/Vlib/context/WEB-INF/Register.page
diff --git a/examples/Vlib/context/WEB-INF/Register.properties b/tapestry-examples/Vlib/context/WEB-INF/Register.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/Register.properties
rename to tapestry-examples/Vlib/context/WEB-INF/Register.properties
diff --git a/examples/Vlib/context/WEB-INF/ShowError.html b/tapestry-examples/Vlib/context/WEB-INF/ShowError.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowError.html
rename to tapestry-examples/Vlib/context/WEB-INF/ShowError.html
diff --git a/examples/Vlib/context/WEB-INF/ShowError.jwc b/tapestry-examples/Vlib/context/WEB-INF/ShowError.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowError.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/ShowError.jwc
diff --git a/examples/Vlib/context/WEB-INF/ShowMessage.html b/tapestry-examples/Vlib/context/WEB-INF/ShowMessage.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowMessage.html
rename to tapestry-examples/Vlib/context/WEB-INF/ShowMessage.html
diff --git a/examples/Vlib/context/WEB-INF/ShowMessage.jwc b/tapestry-examples/Vlib/context/WEB-INF/ShowMessage.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowMessage.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/ShowMessage.jwc
diff --git a/examples/Vlib/context/WEB-INF/ShowValidationError.html b/tapestry-examples/Vlib/context/WEB-INF/ShowValidationError.html
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowValidationError.html
rename to tapestry-examples/Vlib/context/WEB-INF/ShowValidationError.html
diff --git a/examples/Vlib/context/WEB-INF/ShowValidationError.jwc b/tapestry-examples/Vlib/context/WEB-INF/ShowValidationError.jwc
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ShowValidationError.jwc
rename to tapestry-examples/Vlib/context/WEB-INF/ShowValidationError.jwc
diff --git a/examples/Vlib/context/WEB-INF/TransferBooksSelect.page b/tapestry-examples/Vlib/context/WEB-INF/TransferBooksSelect.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/TransferBooksSelect.page
rename to tapestry-examples/Vlib/context/WEB-INF/TransferBooksSelect.page
diff --git a/examples/Vlib/context/WEB-INF/TransferBooksSelect.properties b/tapestry-examples/Vlib/context/WEB-INF/TransferBooksSelect.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/TransferBooksSelect.properties
rename to tapestry-examples/Vlib/context/WEB-INF/TransferBooksSelect.properties
diff --git a/examples/Vlib/context/WEB-INF/TransferBooksTransfer.page b/tapestry-examples/Vlib/context/WEB-INF/TransferBooksTransfer.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/TransferBooksTransfer.page
rename to tapestry-examples/Vlib/context/WEB-INF/TransferBooksTransfer.page
diff --git a/examples/Vlib/context/WEB-INF/TransferBooksTransfer.properties b/tapestry-examples/Vlib/context/WEB-INF/TransferBooksTransfer.properties
similarity index 100%
rename from examples/Vlib/context/WEB-INF/TransferBooksTransfer.properties
rename to tapestry-examples/Vlib/context/WEB-INF/TransferBooksTransfer.properties
diff --git a/examples/Vlib/context/WEB-INF/ViewBook.page b/tapestry-examples/Vlib/context/WEB-INF/ViewBook.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ViewBook.page
rename to tapestry-examples/Vlib/context/WEB-INF/ViewBook.page
diff --git a/examples/Vlib/context/WEB-INF/ViewPerson.page b/tapestry-examples/Vlib/context/WEB-INF/ViewPerson.page
similarity index 100%
rename from examples/Vlib/context/WEB-INF/ViewPerson.page
rename to tapestry-examples/Vlib/context/WEB-INF/ViewPerson.page
diff --git a/examples/Vlib/context/WEB-INF/vlib.application b/tapestry-examples/Vlib/context/WEB-INF/vlib.application
similarity index 100%
rename from examples/Vlib/context/WEB-INF/vlib.application
rename to tapestry-examples/Vlib/context/WEB-INF/vlib.application
diff --git a/examples/Vlib/context/WEB-INF/web.xml b/tapestry-examples/Vlib/context/WEB-INF/web.xml
similarity index 100%
rename from examples/Vlib/context/WEB-INF/web.xml
rename to tapestry-examples/Vlib/context/WEB-INF/web.xml
diff --git a/examples/Vlib/context/css/vlib.css b/tapestry-examples/Vlib/context/css/vlib.css
similarity index 100%
rename from examples/Vlib/context/css/vlib.css
rename to tapestry-examples/Vlib/context/css/vlib.css
diff --git a/examples/Vlib/context/images/.cvsignore b/tapestry-examples/Vlib/context/images/.cvsignore
similarity index 100%
rename from examples/Vlib/context/images/.cvsignore
rename to tapestry-examples/Vlib/context/images/.cvsignore
diff --git a/examples/Vlib/context/images/add.png b/tapestry-examples/Vlib/context/images/add.png
similarity index 100%
rename from examples/Vlib/context/images/add.png
rename to tapestry-examples/Vlib/context/images/add.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_1x1.png b/tapestry-examples/Vlib/context/images/browser/browser_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_1x1.png
rename to tapestry-examples/Vlib/context/images/browser/browser_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_2x1.png b/tapestry-examples/Vlib/context/images/browser/browser_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_2x1.png
rename to tapestry-examples/Vlib/context/images/browser/browser_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_2x3.png b/tapestry-examples/Vlib/context/images/browser/browser_2x3.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_2x3.png
rename to tapestry-examples/Vlib/context/images/browser/browser_2x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_3x1.png b/tapestry-examples/Vlib/context/images/browser/browser_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_3x1.png
rename to tapestry-examples/Vlib/context/images/browser/browser_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x1.png b/tapestry-examples/Vlib/context/images/browser/browser_4x1.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x1.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x2.png b/tapestry-examples/Vlib/context/images/browser/browser_4x2.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x2.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x2.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x3.png b/tapestry-examples/Vlib/context/images/browser/browser_4x3.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x3.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x4.png b/tapestry-examples/Vlib/context/images/browser/browser_4x4.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x4.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x4.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x5.png b/tapestry-examples/Vlib/context/images/browser/browser_4x5.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x5.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x5.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x6.png b/tapestry-examples/Vlib/context/images/browser/browser_4x6.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x6.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x6.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_4x7.png b/tapestry-examples/Vlib/context/images/browser/browser_4x7.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_4x7.png
rename to tapestry-examples/Vlib/context/images/browser/browser_4x7.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser/browser_5x1.png b/tapestry-examples/Vlib/context/images/browser/browser_5x1.png
similarity index 100%
rename from examples/Vlib/context/images/browser/browser_5x1.png
rename to tapestry-examples/Vlib/context/images/browser/browser_5x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_d/browser_4x2.png b/tapestry-examples/Vlib/context/images/browser_d/browser_4x2.png
similarity index 100%
rename from examples/Vlib/context/images/browser_d/browser_4x2.png
rename to tapestry-examples/Vlib/context/images/browser_d/browser_4x2.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_d/browser_4x3.png b/tapestry-examples/Vlib/context/images/browser_d/browser_4x3.png
similarity index 100%
rename from examples/Vlib/context/images/browser_d/browser_4x3.png
rename to tapestry-examples/Vlib/context/images/browser_d/browser_4x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_d/browser_4x5.png b/tapestry-examples/Vlib/context/images/browser_d/browser_4x5.png
similarity index 100%
rename from examples/Vlib/context/images/browser_d/browser_4x5.png
rename to tapestry-examples/Vlib/context/images/browser_d/browser_4x5.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_d/browser_4x6.png b/tapestry-examples/Vlib/context/images/browser_d/browser_4x6.png
similarity index 100%
rename from examples/Vlib/context/images/browser_d/browser_4x6.png
rename to tapestry-examples/Vlib/context/images/browser_d/browser_4x6.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_e/browser_4x2.png b/tapestry-examples/Vlib/context/images/browser_e/browser_4x2.png
similarity index 100%
rename from examples/Vlib/context/images/browser_e/browser_4x2.png
rename to tapestry-examples/Vlib/context/images/browser_e/browser_4x2.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_e/browser_4x3.png b/tapestry-examples/Vlib/context/images/browser_e/browser_4x3.png
similarity index 100%
rename from examples/Vlib/context/images/browser_e/browser_4x3.png
rename to tapestry-examples/Vlib/context/images/browser_e/browser_4x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_e/browser_4x5.png b/tapestry-examples/Vlib/context/images/browser_e/browser_4x5.png
similarity index 100%
rename from examples/Vlib/context/images/browser_e/browser_4x5.png
rename to tapestry-examples/Vlib/context/images/browser_e/browser_4x5.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_e/browser_4x6.png b/tapestry-examples/Vlib/context/images/browser_e/browser_4x6.png
similarity index 100%
rename from examples/Vlib/context/images/browser_e/browser_4x6.png
rename to tapestry-examples/Vlib/context/images/browser_e/browser_4x6.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_h/browser_4x2.png b/tapestry-examples/Vlib/context/images/browser_h/browser_4x2.png
similarity index 100%
rename from examples/Vlib/context/images/browser_h/browser_4x2.png
rename to tapestry-examples/Vlib/context/images/browser_h/browser_4x2.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_h/browser_4x3.png b/tapestry-examples/Vlib/context/images/browser_h/browser_4x3.png
similarity index 100%
rename from examples/Vlib/context/images/browser_h/browser_4x3.png
rename to tapestry-examples/Vlib/context/images/browser_h/browser_4x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_h/browser_4x5.png b/tapestry-examples/Vlib/context/images/browser_h/browser_4x5.png
similarity index 100%
rename from examples/Vlib/context/images/browser_h/browser_4x5.png
rename to tapestry-examples/Vlib/context/images/browser_h/browser_4x5.png
Binary files differ
diff --git a/examples/Vlib/context/images/browser_h/browser_4x6.png b/tapestry-examples/Vlib/context/images/browser_h/browser_4x6.png
similarity index 100%
rename from examples/Vlib/context/images/browser_h/browser_4x6.png
rename to tapestry-examples/Vlib/context/images/browser_h/browser_4x6.png
Binary files differ
diff --git a/examples/Vlib/context/images/cancel.png b/tapestry-examples/Vlib/context/images/cancel.png
similarity index 100%
rename from examples/Vlib/context/images/cancel.png
rename to tapestry-examples/Vlib/context/images/cancel.png
Binary files differ
diff --git a/examples/Vlib/context/images/checkout.png b/tapestry-examples/Vlib/context/images/checkout.png
similarity index 100%
rename from examples/Vlib/context/images/checkout.png
rename to tapestry-examples/Vlib/context/images/checkout.png
Binary files differ
diff --git a/examples/Vlib/context/images/checkout_h.png b/tapestry-examples/Vlib/context/images/checkout_h.png
similarity index 100%
rename from examples/Vlib/context/images/checkout_h.png
rename to tapestry-examples/Vlib/context/images/checkout_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/continue.png b/tapestry-examples/Vlib/context/images/continue.png
similarity index 100%
rename from examples/Vlib/context/images/continue.png
rename to tapestry-examples/Vlib/context/images/continue.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete-cancel.png b/tapestry-examples/Vlib/context/images/delete-cancel.png
similarity index 100%
rename from examples/Vlib/context/images/delete-cancel.png
rename to tapestry-examples/Vlib/context/images/delete-cancel.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete-cancel_h.png b/tapestry-examples/Vlib/context/images/delete-cancel_h.png
similarity index 100%
rename from examples/Vlib/context/images/delete-cancel_h.png
rename to tapestry-examples/Vlib/context/images/delete-cancel_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete-confirm.png b/tapestry-examples/Vlib/context/images/delete-confirm.png
similarity index 100%
rename from examples/Vlib/context/images/delete-confirm.png
rename to tapestry-examples/Vlib/context/images/delete-confirm.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete-confirm_h.png b/tapestry-examples/Vlib/context/images/delete-confirm_h.png
similarity index 100%
rename from examples/Vlib/context/images/delete-confirm_h.png
rename to tapestry-examples/Vlib/context/images/delete-confirm_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete.png b/tapestry-examples/Vlib/context/images/delete.png
similarity index 100%
rename from examples/Vlib/context/images/delete.png
rename to tapestry-examples/Vlib/context/images/delete.png
Binary files differ
diff --git a/examples/Vlib/context/images/delete_h.png b/tapestry-examples/Vlib/context/images/delete_h.png
similarity index 100%
rename from examples/Vlib/context/images/delete_h.png
rename to tapestry-examples/Vlib/context/images/delete_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/edit.png b/tapestry-examples/Vlib/context/images/edit.png
similarity index 100%
rename from examples/Vlib/context/images/edit.png
rename to tapestry-examples/Vlib/context/images/edit.png
Binary files differ
diff --git a/examples/Vlib/context/images/edit_h.png b/tapestry-examples/Vlib/context/images/edit_h.png
similarity index 100%
rename from examples/Vlib/context/images/edit_h.png
rename to tapestry-examples/Vlib/context/images/edit_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/error-icon.png b/tapestry-examples/Vlib/context/images/error-icon.png
similarity index 100%
rename from examples/Vlib/context/images/error-icon.png
rename to tapestry-examples/Vlib/context/images/error-icon.png
Binary files differ
diff --git a/examples/Vlib/context/images/info-icon.png b/tapestry-examples/Vlib/context/images/info-icon.png
similarity index 100%
rename from examples/Vlib/context/images/info-icon.png
rename to tapestry-examples/Vlib/context/images/info-icon.png
Binary files differ
diff --git a/examples/Vlib/context/images/login.png b/tapestry-examples/Vlib/context/images/login.png
similarity index 100%
rename from examples/Vlib/context/images/login.png
rename to tapestry-examples/Vlib/context/images/login.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_10x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_10x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_10x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_10x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_10x1_login.png b/tapestry-examples/Vlib/context/images/nav-h/nav_10x1_login.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_10x1_login.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_10x1_login.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_1x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_1x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_2x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_2x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_3x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_3x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_4x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_4x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_4x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_4x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_5x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_5x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_5x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_5x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_5x1_editprofile.png b/tapestry-examples/Vlib/context/images/nav-h/nav_5x1_editprofile.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_5x1_editprofile.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_5x1_editprofile.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_7x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_7x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_7x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_7x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_8x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_8x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_8x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_8x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-h/nav_9x1.png b/tapestry-examples/Vlib/context/images/nav-h/nav_9x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-h/nav_9x1.png
rename to tapestry-examples/Vlib/context/images/nav-h/nav_9x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_10x1_login.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_10x1_login.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_10x1_login.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_10x1_login.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_1x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_1x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_2x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_2x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_3x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_3x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_4x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_4x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_4x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_4x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_5x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_5x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_5x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_5x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_5x1_editprofile.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_5x1_editprofile.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_5x1_editprofile.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_5x1_editprofile.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_7x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_7x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_7x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_7x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_8x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_8x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_8x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_8x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected-h/nav_9x1.png b/tapestry-examples/Vlib/context/images/nav-selected-h/nav_9x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected-h/nav_9x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected-h/nav_9x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_10x1_login.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_10x1_login.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_10x1_login.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_10x1_login.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_1x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_1x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_2x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_2x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_3x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_3x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_4x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_4x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_4x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_4x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_5x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_5x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_5x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_5x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_5x1_editprofile.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_5x1_editprofile.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_5x1_editprofile.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_5x1_editprofile.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_7x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_7x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_7x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_7x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_8x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_8x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_8x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_8x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav-selected/nav_9x1.png b/tapestry-examples/Vlib/context/images/nav-selected/nav_9x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav-selected/nav_9x1.png
rename to tapestry-examples/Vlib/context/images/nav-selected/nav_9x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_10x1.png b/tapestry-examples/Vlib/context/images/nav/nav_10x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_10x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_10x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_10x1_login.png b/tapestry-examples/Vlib/context/images/nav/nav_10x1_login.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_10x1_login.png
rename to tapestry-examples/Vlib/context/images/nav/nav_10x1_login.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_1x1.png b/tapestry-examples/Vlib/context/images/nav/nav_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_1x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_2x1.png b/tapestry-examples/Vlib/context/images/nav/nav_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_2x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_3x1.png b/tapestry-examples/Vlib/context/images/nav/nav_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_3x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_4x1.png b/tapestry-examples/Vlib/context/images/nav/nav_4x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_4x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_4x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_5x1.png b/tapestry-examples/Vlib/context/images/nav/nav_5x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_5x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_5x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_5x1_editprofile.png b/tapestry-examples/Vlib/context/images/nav/nav_5x1_editprofile.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_5x1_editprofile.png
rename to tapestry-examples/Vlib/context/images/nav/nav_5x1_editprofile.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_6x1.png b/tapestry-examples/Vlib/context/images/nav/nav_6x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_6x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_6x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_7x1.png b/tapestry-examples/Vlib/context/images/nav/nav_7x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_7x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_7x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_8x1.png b/tapestry-examples/Vlib/context/images/nav/nav_8x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_8x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_8x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/nav/nav_9x1.png b/tapestry-examples/Vlib/context/images/nav/nav_9x1.png
similarity index 100%
rename from examples/Vlib/context/images/nav/nav_9x1.png
rename to tapestry-examples/Vlib/context/images/nav/nav_9x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/new.png b/tapestry-examples/Vlib/context/images/new.png
similarity index 100%
rename from examples/Vlib/context/images/new.png
rename to tapestry-examples/Vlib/context/images/new.png
Binary files differ
diff --git a/examples/Vlib/context/images/question-icon.png b/tapestry-examples/Vlib/context/images/question-icon.png
similarity index 100%
rename from examples/Vlib/context/images/question-icon.png
rename to tapestry-examples/Vlib/context/images/question-icon.png
Binary files differ
diff --git a/examples/Vlib/context/images/register.png b/tapestry-examples/Vlib/context/images/register.png
similarity index 100%
rename from examples/Vlib/context/images/register.png
rename to tapestry-examples/Vlib/context/images/register.png
Binary files differ
diff --git a/examples/Vlib/context/images/return.png b/tapestry-examples/Vlib/context/images/return.png
similarity index 100%
rename from examples/Vlib/context/images/return.png
rename to tapestry-examples/Vlib/context/images/return.png
Binary files differ
diff --git a/examples/Vlib/context/images/return_h.png b/tapestry-examples/Vlib/context/images/return_h.png
similarity index 100%
rename from examples/Vlib/context/images/return_h.png
rename to tapestry-examples/Vlib/context/images/return_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/search.png b/tapestry-examples/Vlib/context/images/search.png
similarity index 100%
rename from examples/Vlib/context/images/search.png
rename to tapestry-examples/Vlib/context/images/search.png
Binary files differ
diff --git a/examples/Vlib/context/images/sort-down.png b/tapestry-examples/Vlib/context/images/sort-down.png
similarity index 100%
rename from examples/Vlib/context/images/sort-down.png
rename to tapestry-examples/Vlib/context/images/sort-down.png
Binary files differ
diff --git a/examples/Vlib/context/images/sort-down_h.png b/tapestry-examples/Vlib/context/images/sort-down_h.png
similarity index 100%
rename from examples/Vlib/context/images/sort-down_h.png
rename to tapestry-examples/Vlib/context/images/sort-down_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/sort-up.png b/tapestry-examples/Vlib/context/images/sort-up.png
similarity index 100%
rename from examples/Vlib/context/images/sort-up.png
rename to tapestry-examples/Vlib/context/images/sort-up.png
Binary files differ
diff --git a/examples/Vlib/context/images/sort-up_h.png b/tapestry-examples/Vlib/context/images/sort-up_h.png
similarity index 100%
rename from examples/Vlib/context/images/sort-up_h.png
rename to tapestry-examples/Vlib/context/images/sort-up_h.png
Binary files differ
diff --git a/examples/Vlib/context/images/spacer.png b/tapestry-examples/Vlib/context/images/spacer.png
similarity index 100%
rename from examples/Vlib/context/images/spacer.png
rename to tapestry-examples/Vlib/context/images/spacer.png
Binary files differ
diff --git a/examples/Vlib/context/images/step1.png b/tapestry-examples/Vlib/context/images/step1.png
similarity index 100%
rename from examples/Vlib/context/images/step1.png
rename to tapestry-examples/Vlib/context/images/step1.png
Binary files differ
diff --git a/examples/Vlib/context/images/step2.png b/tapestry-examples/Vlib/context/images/step2.png
similarity index 100%
rename from examples/Vlib/context/images/step2.png
rename to tapestry-examples/Vlib/context/images/step2.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/AddNewBook.png b/tapestry-examples/Vlib/context/images/title/AddNewBook.png
similarity index 100%
rename from examples/Vlib/context/images/title/AddNewBook.png
rename to tapestry-examples/Vlib/context/images/title/AddNewBook.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/BookMatches.png b/tapestry-examples/Vlib/context/images/title/BookMatches.png
similarity index 100%
rename from examples/Vlib/context/images/title/BookMatches.png
rename to tapestry-examples/Vlib/context/images/title/BookMatches.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/BorrowedBooks.png b/tapestry-examples/Vlib/context/images/title/BorrowedBooks.png
similarity index 100%
rename from examples/Vlib/context/images/title/BorrowedBooks.png
rename to tapestry-examples/Vlib/context/images/title/BorrowedBooks.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/DeleteBook.png b/tapestry-examples/Vlib/context/images/title/DeleteBook.png
similarity index 100%
rename from examples/Vlib/context/images/title/DeleteBook.png
rename to tapestry-examples/Vlib/context/images/title/DeleteBook.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/EditBook.png b/tapestry-examples/Vlib/context/images/title/EditBook.png
similarity index 100%
rename from examples/Vlib/context/images/title/EditBook.png
rename to tapestry-examples/Vlib/context/images/title/EditBook.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/EditProfile.png b/tapestry-examples/Vlib/context/images/title/EditProfile.png
similarity index 100%
rename from examples/Vlib/context/images/title/EditProfile.png
rename to tapestry-examples/Vlib/context/images/title/EditProfile.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/EditPublishers.png b/tapestry-examples/Vlib/context/images/title/EditPublishers.png
similarity index 100%
rename from examples/Vlib/context/images/title/EditPublishers.png
rename to tapestry-examples/Vlib/context/images/title/EditPublishers.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/EditUsers.png b/tapestry-examples/Vlib/context/images/title/EditUsers.png
similarity index 100%
rename from examples/Vlib/context/images/title/EditUsers.png
rename to tapestry-examples/Vlib/context/images/title/EditUsers.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/GiveAwayBooks.png b/tapestry-examples/Vlib/context/images/title/GiveAwayBooks.png
similarity index 100%
rename from examples/Vlib/context/images/title/GiveAwayBooks.png
rename to tapestry-examples/Vlib/context/images/title/GiveAwayBooks.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/Login.png b/tapestry-examples/Vlib/context/images/title/Login.png
similarity index 100%
rename from examples/Vlib/context/images/title/Login.png
rename to tapestry-examples/Vlib/context/images/title/Login.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/MyLibrary.png b/tapestry-examples/Vlib/context/images/title/MyLibrary.png
similarity index 100%
rename from examples/Vlib/context/images/title/MyLibrary.png
rename to tapestry-examples/Vlib/context/images/title/MyLibrary.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/Register.png b/tapestry-examples/Vlib/context/images/title/Register.png
similarity index 100%
rename from examples/Vlib/context/images/title/Register.png
rename to tapestry-examples/Vlib/context/images/title/Register.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/Search.png b/tapestry-examples/Vlib/context/images/title/Search.png
similarity index 100%
rename from examples/Vlib/context/images/title/Search.png
rename to tapestry-examples/Vlib/context/images/title/Search.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/TransferBooks.png b/tapestry-examples/Vlib/context/images/title/TransferBooks.png
similarity index 100%
rename from examples/Vlib/context/images/title/TransferBooks.png
rename to tapestry-examples/Vlib/context/images/title/TransferBooks.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/ViewBook.png b/tapestry-examples/Vlib/context/images/title/ViewBook.png
similarity index 100%
rename from examples/Vlib/context/images/title/ViewBook.png
rename to tapestry-examples/Vlib/context/images/title/ViewBook.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/ViewPerson.png b/tapestry-examples/Vlib/context/images/title/ViewPerson.png
similarity index 100%
rename from examples/Vlib/context/images/title/ViewPerson.png
rename to tapestry-examples/Vlib/context/images/title/ViewPerson.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/blank.png b/tapestry-examples/Vlib/context/images/title/blank.png
similarity index 100%
rename from examples/Vlib/context/images/title/blank.png
rename to tapestry-examples/Vlib/context/images/title/blank.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/title_1x1.png b/tapestry-examples/Vlib/context/images/title/title_1x1.png
similarity index 100%
rename from examples/Vlib/context/images/title/title_1x1.png
rename to tapestry-examples/Vlib/context/images/title/title_1x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/title_2x1.png b/tapestry-examples/Vlib/context/images/title/title_2x1.png
similarity index 100%
rename from examples/Vlib/context/images/title/title_2x1.png
rename to tapestry-examples/Vlib/context/images/title/title_2x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/title_2x3.png b/tapestry-examples/Vlib/context/images/title/title_2x3.png
similarity index 100%
rename from examples/Vlib/context/images/title/title_2x3.png
rename to tapestry-examples/Vlib/context/images/title/title_2x3.png
Binary files differ
diff --git a/examples/Vlib/context/images/title/title_3x1.png b/tapestry-examples/Vlib/context/images/title/title_3x1.png
similarity index 100%
rename from examples/Vlib/context/images/title/title_3x1.png
rename to tapestry-examples/Vlib/context/images/title/title_3x1.png
Binary files differ
diff --git a/examples/Vlib/context/images/transfer.png b/tapestry-examples/Vlib/context/images/transfer.png
similarity index 100%
rename from examples/Vlib/context/images/transfer.png
rename to tapestry-examples/Vlib/context/images/transfer.png
Binary files differ
diff --git a/examples/Vlib/context/images/update.png b/tapestry-examples/Vlib/context/images/update.png
similarity index 100%
rename from examples/Vlib/context/images/update.png
rename to tapestry-examples/Vlib/context/images/update.png
Binary files differ
diff --git a/examples/Vlib/jetty.xml b/tapestry-examples/Vlib/jetty.xml
similarity index 100%
rename from examples/Vlib/jetty.xml
rename to tapestry-examples/Vlib/jetty.xml
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/ActivateCallback.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/ActivateCallback.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/ActivateCallback.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/ActivateCallback.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/ActivatePage.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/ActivatePage.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/ActivatePage.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/ActivatePage.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/AdminPage.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/AdminPage.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/AdminPage.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/AdminPage.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/EntitySelectionModel.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/EntitySelectionModel.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/EntitySelectionModel.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/EntitySelectionModel.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/Global.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Global.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/Global.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Global.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/IActivate.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IActivate.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/IActivate.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IActivate.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/IErrorProperty.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IErrorProperty.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/IErrorProperty.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IErrorProperty.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/IMessageProperty.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IMessageProperty.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/IMessageProperty.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/IMessageProperty.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/Protected.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Protected.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/Protected.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Protected.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryDelegate.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryDelegate.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryDelegate.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryDelegate.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryEngine.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryEngine.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryEngine.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/VirtualLibraryEngine.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/Visit.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Visit.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/Visit.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/Visit.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/components/BookLink.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/BookLink.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/components/BookLink.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/BookLink.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/components/Border.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Border.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/components/Border.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Border.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/components/Borrow.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Borrow.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/components/Borrow.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Borrow.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/components/Browser.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Browser.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/components/Browser.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/Browser.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/components/ColumnSorter.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/ColumnSorter.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/components/ColumnSorter.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/components/ColumnSorter.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/ApplicationUnavailable.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ApplicationUnavailable.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/ApplicationUnavailable.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ApplicationUnavailable.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/BookMatches.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/BookMatches.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/BookMatches.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/BookMatches.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/BorrowedBooks.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/BorrowedBooks.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/BorrowedBooks.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/BorrowedBooks.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/ConfirmBookDelete.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ConfirmBookDelete.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/ConfirmBookDelete.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ConfirmBookDelete.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/EditBook.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/EditBook.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/EditBook.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/EditBook.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/EditProfile.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/EditProfile.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/EditProfile.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/EditProfile.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/GiveAwayBooks.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/GiveAwayBooks.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/GiveAwayBooks.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/GiveAwayBooks.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/Home.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Home.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/Home.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Home.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/Login.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Login.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/Login.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Login.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/MyLibrary.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/MyLibrary.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/MyLibrary.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/MyLibrary.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/NewBook.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/NewBook.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/NewBook.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/NewBook.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/Register.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Register.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/Register.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/Register.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewBook.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewBook.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewBook.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewBook.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewPerson.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewPerson.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewPerson.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/ViewPerson.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditPublishers.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditPublishers.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditPublishers.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditPublishers.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditUsers.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditUsers.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditUsers.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/EditUsers.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksSelect.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksSelect.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksSelect.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksSelect.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksTransfer.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksTransfer.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksTransfer.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/TransferBooksTransfer.java
diff --git a/examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/UserListEditMap.java b/tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/UserListEditMap.java
similarity index 100%
rename from examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/UserListEditMap.java
rename to tapestry-examples/Vlib/src/org/apache/tapestry/vlib/pages/admin/UserListEditMap.java
diff --git a/examples/VlibBeans/.cvsignore b/tapestry-examples/VlibBeans/.cvsignore
similarity index 100%
rename from examples/VlibBeans/.cvsignore
rename to tapestry-examples/VlibBeans/.cvsignore
diff --git a/examples/VlibBeans/build.xml b/tapestry-examples/VlibBeans/build.xml
similarity index 100%
rename from examples/VlibBeans/build.xml
rename to tapestry-examples/VlibBeans/build.xml
diff --git a/examples/VlibBeans/createDb.sql b/tapestry-examples/VlibBeans/createDb.sql
similarity index 100%
rename from examples/VlibBeans/createDb.sql
rename to tapestry-examples/VlibBeans/createDb.sql
diff --git a/examples/VlibBeans/ejb-jar.xml b/tapestry-examples/VlibBeans/ejb-jar.xml
similarity index 100%
rename from examples/VlibBeans/ejb-jar.xml
rename to tapestry-examples/VlibBeans/ejb-jar.xml
diff --git a/examples/VlibBeans/jboss.xml b/tapestry-examples/VlibBeans/jboss.xml
similarity index 100%
rename from examples/VlibBeans/jboss.xml
rename to tapestry-examples/VlibBeans/jboss.xml
diff --git a/examples/VlibBeans/jbosscmp-jdbc.xml b/tapestry-examples/VlibBeans/jbosscmp-jdbc.xml
similarity index 100%
rename from examples/VlibBeans/jbosscmp-jdbc.xml
rename to tapestry-examples/VlibBeans/jbosscmp-jdbc.xml
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Book.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Book.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Book.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Book.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/BorrowException.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/BorrowException.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/BorrowException.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/BorrowException.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBook.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBook.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBook.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBook.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQuery.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQuery.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQuery.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQuery.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQueryHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQueryHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQueryHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IBookQueryHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IEntityBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IEntityBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IEntityBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IEntityBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocator.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocator.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocator.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocator.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocatorHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocatorHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocatorHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IKeyAllocatorHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperations.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperations.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperations.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperations.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperationsHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperationsHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperationsHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IOperationsHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPerson.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPerson.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPerson.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPerson.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPersonHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPersonHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPersonHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPersonHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisher.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisher.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisher.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisher.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisherHome.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisherHome.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisherHome.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/IPublisherHome.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/LoginException.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/LoginException.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/LoginException.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/LoginException.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/MasterQueryParameters.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/MasterQueryParameters.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/MasterQueryParameters.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/MasterQueryParameters.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Person.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Person.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Person.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Person.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Publisher.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Publisher.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Publisher.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/Publisher.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/RegistrationException.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/RegistrationException.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/RegistrationException.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/RegistrationException.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortColumn.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortColumn.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortColumn.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortColumn.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortOrdering.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortOrdering.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortOrdering.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/SortOrdering.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/AbstractEntityBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/AbstractEntityBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/AbstractEntityBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/AbstractEntityBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookQueryBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookQueryBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookQueryBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/BookQueryBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/KeyAllocatorBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/KeyAllocatorBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/KeyAllocatorBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/KeyAllocatorBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/OperationsBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/OperationsBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/OperationsBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/OperationsBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PersonBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PersonBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PersonBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PersonBean.java
diff --git a/examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PublisherBean.java b/tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PublisherBean.java
similarity index 100%
rename from examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PublisherBean.java
rename to tapestry-examples/VlibBeans/src/org/apache/tapestry/vlib/ejb/impl/PublisherBean.java
diff --git a/examples/VlibEAR/META-INF/application.xml b/tapestry-examples/VlibEAR/META-INF/application.xml
similarity index 100%
rename from examples/VlibEAR/META-INF/application.xml
rename to tapestry-examples/VlibEAR/META-INF/application.xml
diff --git a/examples/VlibEAR/build.xml b/tapestry-examples/VlibEAR/build.xml
similarity index 100%
rename from examples/VlibEAR/build.xml
rename to tapestry-examples/VlibEAR/build.xml
diff --git a/tapestry-examples/build.xml b/tapestry-examples/build.xml
new file mode 100644
index 0000000..f812cf2
--- /dev/null
+++ b/tapestry-examples/build.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!-- Interface between the top-level build file and any of the examples. Simply re-executes
+ its targets in each of its sub-projects. -->
+<project name="Tapestry Examples" default="install">
+ <target name="clean">
+ <ant dir="tapestry-workbench" target="clean"/>
+ <ant dir="VlibBeans" target="clean"/>
+ <ant dir="Vlib" target="clean"/>
+ </target>
+ <target name="install">
+ <ant dir="tapestry-workbench" target="install" inheritAll="false"/>
+ <ant dir="VlibBeans" target="install" inheritAll="false"/>
+ <ant dir="Vlib" target="install" inheritAll="false"/>
+ <ant dir="VlibEAR" target="install" inheritAll="false"/>
+ </target>
+</project>
diff --git a/tapestry-examples/pom.xml b/tapestry-examples/pom.xml
new file mode 100644
index 0000000..596ddcb
--- /dev/null
+++ b/tapestry-examples/pom.xml
@@ -0,0 +1,57 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-examples</artifactId>
+ <packaging>pom</packaging>
+ <version>3.0.5-SNAPSHOT</version>
+ <!-- This should change to tapestry-project -->
+ <parent>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-project</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </parent>
+ <name>Examples</name>
+ <description>Tapestry example applications</description>
+ <inceptionYear>2006</inceptionYear>
+
+ <modules>
+ <module>tapestry-workbench</module>
+ </modules>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-framework</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-contrib</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <inherited>true</inherited>
+ <configuration>
+ <source>1.4</source>
+ <target>1.4</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <reporting>
+ <outputDirectory>../target/site/tapestry-examples</outputDirectory>
+ </reporting>
+</project>
diff --git a/examples/Workbench/.cvsignore b/tapestry-examples/tapestry-workbench/.cvsignore
similarity index 100%
rename from examples/Workbench/.cvsignore
rename to tapestry-examples/tapestry-workbench/.cvsignore
diff --git a/tapestry-examples/tapestry-workbench/build.xml b/tapestry-examples/tapestry-workbench/build.xml
new file mode 100644
index 0000000..00c86a1
--- /dev/null
+++ b/tapestry-examples/tapestry-workbench/build.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<project name="Tapestry Workbench Example" default="install">
+ <property name="root.dir" value="../.."/>
+ <property file="${root.dir}/config/Version.properties"/>
+ <property file="${root.dir}/config/build.properties"/>
+ <property file="${root.dir}/config/common.properties"/>
+
+ <property name="config.dir" value="config"/>
+ <property name="build.dir" value=".build"/>
+
+ <path id="compile.classpath">
+ <fileset dir="${root.lib.dir}">
+ <include name="*.jar"/>
+ <include name="${ext.dir}/*.jar"/>
+ <include name="${j2ee.dir}/*.jar"/>
+ </fileset>
+ <fileset dir="${lib.dir}">
+ <include name="*.jar"/>
+ </fileset>
+ </path>
+ <target name="init">
+ <mkdir dir="${classes.dir}"/>
+ </target>
+ <target name="clean">
+ <delete dir="${classes.dir}" quiet="true"/>
+ <delete dir="${build.dir}" quiet="true"/>
+ </target>
+ <target name="compile" depends="init"
+ description="Compile all classes in the tutorial.">
+ <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="on"
+ target="1.1" source="1.3">
+ <classpath refid="compile.classpath"/>
+ </javac>
+ </target>
+ <target name="install" depends="compile"
+ description="Compile all classes and build the installed WAR.">
+
+ <mkdir dir="${build.dir}"/>
+ <mkdir dir="${examples.dir}"/>
+
+ <copy file="context/WEB-INF/web.xml" todir="${build.dir}">
+ <filterset>
+ <filter token="TAPESTRY_JAR" value="${framework.jar}"/>
+ </filterset>
+ </copy>
+
+ <war warfile="${examples.dir}/${workbench.war}"
+ webxml="${build.dir}/web.xml">
+
+ <fileset dir="context"/>
+
+ <classes dir="${classes.dir}"/>
+ <classes dir="${src.dir}">
+ <exclude name="**/*.java"/>
+ <exclude name="**/package.html"/>
+ </classes>
+ <classes dir="${root.config.dir}">
+ <include name="log4j.properties"/>
+ </classes>
+ <lib dir="${lib.dir}">
+ <include name="*.jar"/>
+ </lib>
+ <lib dir="${root.lib.dir}">
+ <include name="*.jar"/>
+ </lib>
+ <lib dir="${root.lib.dir}/${ext.dir}">
+ <include name="*.jar"/>
+ </lib>
+ <lib dir="${root.lib.dir}/${runtime.dir}">
+ <include name="*.jar"/>
+ </lib>
+ </war>
+ </target>
+
+</project>
diff --git a/examples/Workbench/context/Chart.html b/tapestry-examples/tapestry-workbench/context/Chart.html
similarity index 100%
rename from examples/Workbench/context/Chart.html
rename to tapestry-examples/tapestry-workbench/context/Chart.html
diff --git a/examples/Workbench/context/Dates.html b/tapestry-examples/tapestry-workbench/context/Dates.html
similarity index 100%
rename from examples/Workbench/context/Dates.html
rename to tapestry-examples/tapestry-workbench/context/Dates.html
diff --git a/examples/Workbench/context/ExceptionTab.html b/tapestry-examples/tapestry-workbench/context/ExceptionTab.html
similarity index 100%
rename from examples/Workbench/context/ExceptionTab.html
rename to tapestry-examples/tapestry-workbench/context/ExceptionTab.html
diff --git a/examples/Workbench/context/Fields.html b/tapestry-examples/tapestry-workbench/context/Fields.html
similarity index 100%
rename from examples/Workbench/context/Fields.html
rename to tapestry-examples/tapestry-workbench/context/Fields.html
diff --git a/examples/Workbench/context/FieldsResults.html b/tapestry-examples/tapestry-workbench/context/FieldsResults.html
similarity index 100%
rename from examples/Workbench/context/FieldsResults.html
rename to tapestry-examples/tapestry-workbench/context/FieldsResults.html
diff --git a/examples/Workbench/context/FileSystemTableTree.html b/tapestry-examples/tapestry-workbench/context/FileSystemTableTree.html
similarity index 100%
rename from examples/Workbench/context/FileSystemTableTree.html
rename to tapestry-examples/tapestry-workbench/context/FileSystemTableTree.html
diff --git a/examples/Workbench/context/FileSystemTree.html b/tapestry-examples/tapestry-workbench/context/FileSystemTree.html
similarity index 100%
rename from examples/Workbench/context/FileSystemTree.html
rename to tapestry-examples/tapestry-workbench/context/FileSystemTree.html
diff --git a/examples/Workbench/context/Home.html b/tapestry-examples/tapestry-workbench/context/Home.html
similarity index 100%
rename from examples/Workbench/context/Home.html
rename to tapestry-examples/tapestry-workbench/context/Home.html
diff --git a/examples/Workbench/context/JSP.html b/tapestry-examples/tapestry-workbench/context/JSP.html
similarity index 100%
rename from examples/Workbench/context/JSP.html
rename to tapestry-examples/tapestry-workbench/context/JSP.html
diff --git a/examples/Workbench/context/JSPResults.html b/tapestry-examples/tapestry-workbench/context/JSPResults.html
similarity index 100%
rename from examples/Workbench/context/JSPResults.html
rename to tapestry-examples/tapestry-workbench/context/JSPResults.html
diff --git a/examples/Workbench/context/Localization.html b/tapestry-examples/tapestry-workbench/context/Localization.html
similarity index 100%
rename from examples/Workbench/context/Localization.html
rename to tapestry-examples/tapestry-workbench/context/Localization.html
diff --git a/examples/Workbench/context/LocalizationChange.html b/tapestry-examples/tapestry-workbench/context/LocalizationChange.html
similarity index 100%
rename from examples/Workbench/context/LocalizationChange.html
rename to tapestry-examples/tapestry-workbench/context/LocalizationChange.html
diff --git a/examples/Workbench/context/LocalizationChange_de.html b/tapestry-examples/tapestry-workbench/context/LocalizationChange_de.html
similarity index 100%
rename from examples/Workbench/context/LocalizationChange_de.html
rename to tapestry-examples/tapestry-workbench/context/LocalizationChange_de.html
diff --git a/examples/Workbench/context/LocalizationChange_fr.html b/tapestry-examples/tapestry-workbench/context/LocalizationChange_fr.html
similarity index 100%
rename from examples/Workbench/context/LocalizationChange_fr.html
rename to tapestry-examples/tapestry-workbench/context/LocalizationChange_fr.html
diff --git a/examples/Workbench/context/LocalizationChange_it.html b/tapestry-examples/tapestry-workbench/context/LocalizationChange_it.html
similarity index 100%
rename from examples/Workbench/context/LocalizationChange_it.html
rename to tapestry-examples/tapestry-workbench/context/LocalizationChange_it.html
diff --git a/examples/Workbench/context/Localization_de.html b/tapestry-examples/tapestry-workbench/context/Localization_de.html
similarity index 100%
rename from examples/Workbench/context/Localization_de.html
rename to tapestry-examples/tapestry-workbench/context/Localization_de.html
diff --git a/examples/Workbench/context/Localization_fr.html b/tapestry-examples/tapestry-workbench/context/Localization_fr.html
similarity index 100%
rename from examples/Workbench/context/Localization_fr.html
rename to tapestry-examples/tapestry-workbench/context/Localization_fr.html
diff --git a/examples/Workbench/context/Localization_it.html b/tapestry-examples/tapestry-workbench/context/Localization_it.html
similarity index 100%
rename from examples/Workbench/context/Localization_it.html
rename to tapestry-examples/tapestry-workbench/context/Localization_it.html
diff --git a/examples/Workbench/context/Palette.html b/tapestry-examples/tapestry-workbench/context/Palette.html
similarity index 100%
rename from examples/Workbench/context/Palette.html
rename to tapestry-examples/tapestry-workbench/context/Palette.html
diff --git a/examples/Workbench/context/PaletteResults.html b/tapestry-examples/tapestry-workbench/context/PaletteResults.html
similarity index 100%
rename from examples/Workbench/context/PaletteResults.html
rename to tapestry-examples/tapestry-workbench/context/PaletteResults.html
diff --git a/examples/Workbench/context/Redirect.html b/tapestry-examples/tapestry-workbench/context/Redirect.html
similarity index 100%
rename from examples/Workbench/context/Redirect.html
rename to tapestry-examples/tapestry-workbench/context/Redirect.html
diff --git a/examples/Workbench/context/Table.html b/tapestry-examples/tapestry-workbench/context/Table.html
similarity index 100%
rename from examples/Workbench/context/Table.html
rename to tapestry-examples/tapestry-workbench/context/Table.html
diff --git a/examples/Workbench/context/TapestryTags.jsp b/tapestry-examples/tapestry-workbench/context/TapestryTags.jsp
similarity index 100%
rename from examples/Workbench/context/TapestryTags.jsp
rename to tapestry-examples/tapestry-workbench/context/TapestryTags.jsp
diff --git a/examples/Workbench/context/TreeHome.html b/tapestry-examples/tapestry-workbench/context/TreeHome.html
similarity index 100%
rename from examples/Workbench/context/TreeHome.html
rename to tapestry-examples/tapestry-workbench/context/TreeHome.html
diff --git a/examples/Workbench/context/Upload.html b/tapestry-examples/tapestry-workbench/context/Upload.html
similarity index 100%
rename from examples/Workbench/context/Upload.html
rename to tapestry-examples/tapestry-workbench/context/Upload.html
diff --git a/examples/Workbench/context/UploadResults.html b/tapestry-examples/tapestry-workbench/context/UploadResults.html
similarity index 100%
rename from examples/Workbench/context/UploadResults.html
rename to tapestry-examples/tapestry-workbench/context/UploadResults.html
diff --git a/examples/Workbench/context/WEB-INF/Border.html b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border.html
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border.html
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border.html
diff --git a/examples/Workbench/context/WEB-INF/Border.jwc b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border.jwc
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border.jwc
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border.jwc
diff --git a/examples/Workbench/context/WEB-INF/Border.properties b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border.properties
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border.properties
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border.properties
diff --git a/examples/Workbench/context/WEB-INF/Border_de.properties b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border_de.properties
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border_de.properties
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border_de.properties
diff --git a/examples/Workbench/context/WEB-INF/Border_fr.properties b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border_fr.properties
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border_fr.properties
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border_fr.properties
diff --git a/examples/Workbench/context/WEB-INF/Border_it.properties b/tapestry-examples/tapestry-workbench/context/WEB-INF/Border_it.properties
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Border_it.properties
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Border_it.properties
diff --git a/examples/Workbench/context/WEB-INF/Chart.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Chart.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Chart.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Chart.page
diff --git a/examples/Workbench/context/WEB-INF/Dates.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Dates.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Dates.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Dates.page
diff --git a/examples/Workbench/context/WEB-INF/DirectoryTableView.html b/tapestry-examples/tapestry-workbench/context/WEB-INF/DirectoryTableView.html
similarity index 100%
rename from examples/Workbench/context/WEB-INF/DirectoryTableView.html
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/DirectoryTableView.html
diff --git a/examples/Workbench/context/WEB-INF/DirectoryTableView.jwc b/tapestry-examples/tapestry-workbench/context/WEB-INF/DirectoryTableView.jwc
similarity index 100%
rename from examples/Workbench/context/WEB-INF/DirectoryTableView.jwc
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/DirectoryTableView.jwc
diff --git a/examples/Workbench/context/WEB-INF/ErrorFest.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/ErrorFest.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/ErrorFest.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/ErrorFest.page
diff --git a/examples/Workbench/context/WEB-INF/Fields.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Fields.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Fields.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Fields.page
diff --git a/examples/Workbench/context/WEB-INF/FieldsResults.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/FieldsResults.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/FieldsResults.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/FieldsResults.page
diff --git a/examples/Workbench/context/WEB-INF/FileSystemTableTree.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/FileSystemTableTree.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/FileSystemTableTree.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/FileSystemTableTree.page
diff --git a/examples/Workbench/context/WEB-INF/FileSystemTree.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/FileSystemTree.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/FileSystemTree.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/FileSystemTree.page
diff --git a/examples/Workbench/context/WEB-INF/JSP.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/JSP.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/JSP.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/JSP.page
diff --git a/examples/Workbench/context/WEB-INF/JSPResults.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/JSPResults.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/JSPResults.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/JSPResults.page
diff --git a/examples/Workbench/context/WEB-INF/LocaleList.html b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleList.html
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocaleList.html
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleList.html
diff --git a/examples/Workbench/context/WEB-INF/LocaleList.jwc b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleList.jwc
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocaleList.jwc
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleList.jwc
diff --git a/examples/Workbench/context/WEB-INF/LocaleSelection.html b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.html
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocaleSelection.html
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.html
diff --git a/examples/Workbench/context/WEB-INF/LocaleSelection.jwc b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.jwc
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocaleSelection.jwc
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.jwc
diff --git a/examples/Workbench/context/WEB-INF/LocaleSelection.properties b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.properties
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocaleSelection.properties
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocaleSelection.properties
diff --git a/examples/Workbench/context/WEB-INF/Localization.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Localization.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Localization.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Localization.page
diff --git a/examples/Workbench/context/WEB-INF/LocalizationChange.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/LocalizationChange.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/LocalizationChange.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/LocalizationChange.page
diff --git a/examples/Workbench/context/WEB-INF/Palette.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Palette.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Palette.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Palette.page
diff --git a/examples/Workbench/context/WEB-INF/PaletteResults.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/PaletteResults.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/PaletteResults.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/PaletteResults.page
diff --git a/examples/Workbench/context/WEB-INF/Redirect.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Redirect.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Redirect.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Redirect.page
diff --git a/examples/Workbench/context/WEB-INF/ShowError.html b/tapestry-examples/tapestry-workbench/context/WEB-INF/ShowError.html
similarity index 100%
rename from examples/Workbench/context/WEB-INF/ShowError.html
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/ShowError.html
diff --git a/examples/Workbench/context/WEB-INF/ShowError.jwc b/tapestry-examples/tapestry-workbench/context/WEB-INF/ShowError.jwc
similarity index 100%
rename from examples/Workbench/context/WEB-INF/ShowError.jwc
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/ShowError.jwc
diff --git a/examples/Workbench/context/WEB-INF/Table.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Table.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Table.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Table.page
diff --git a/examples/Workbench/context/WEB-INF/Upload.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/Upload.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/Upload.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/Upload.page
diff --git a/examples/Workbench/context/WEB-INF/UploadResults.page b/tapestry-examples/tapestry-workbench/context/WEB-INF/UploadResults.page
similarity index 100%
rename from examples/Workbench/context/WEB-INF/UploadResults.page
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/UploadResults.page
diff --git a/tapestry-examples/tapestry-workbench/context/WEB-INF/web.xml b/tapestry-examples/tapestry-workbench/context/WEB-INF/web.xml
new file mode 100644
index 0000000..3fa3b04
--- /dev/null
+++ b/tapestry-examples/tapestry-workbench/context/WEB-INF/web.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!--$Id$ -->
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd">
+<web-app>
+ <display-name>Tapestry Workbench Example</display-name>
+
+ <filter>
+ <filter-name>redirect</filter-name>
+ <filter-class>org.apache.tapestry.RedirectFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>redirect</filter-name>
+ <url-pattern>/</url-pattern>
+ </filter-mapping>
+
+ <servlet>
+ <servlet-name>workbench</servlet-name>
+ <servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
+ <load-on-startup>0</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>workbench</servlet-name>
+ <url-pattern>/app</url-pattern>
+ </servlet-mapping>
+
+ <session-config>
+ <session-timeout>15</session-timeout>
+ </session-config>
+
+ <taglib>
+ <taglib-uri>http://jakarta.apache.org/tapestry/tld/tapestry_1_0.tld</taglib-uri>
+ <taglib-location>/WEB-INF/lib/tapestry-framework-3.0.5-SNAPSHOT.jar</taglib-location>
+ </taglib>
+</web-app>
diff --git a/examples/Workbench/context/WEB-INF/workbench.application b/tapestry-examples/tapestry-workbench/context/WEB-INF/workbench.application
similarity index 100%
rename from examples/Workbench/context/WEB-INF/workbench.application
rename to tapestry-examples/tapestry-workbench/context/WEB-INF/workbench.application
diff --git a/examples/Workbench/context/css/workbench.css b/tapestry-examples/tapestry-workbench/context/css/workbench.css
similarity index 100%
rename from examples/Workbench/context/css/workbench.css
rename to tapestry-examples/tapestry-workbench/context/css/workbench.css
diff --git a/examples/Workbench/context/images/.cvsignore b/tapestry-examples/tapestry-workbench/context/images/.cvsignore
similarity index 100%
rename from examples/Workbench/context/images/.cvsignore
rename to tapestry-examples/tapestry-workbench/context/images/.cvsignore
diff --git a/examples/Workbench/context/images/Back-focus.gif b/tapestry-examples/tapestry-workbench/context/images/Back-focus.gif
similarity index 100%
rename from examples/Workbench/context/images/Back-focus.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back-focus.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back-focus_de.gif b/tapestry-examples/tapestry-workbench/context/images/Back-focus_de.gif
similarity index 100%
rename from examples/Workbench/context/images/Back-focus_de.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back-focus_de.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back-focus_fr.gif b/tapestry-examples/tapestry-workbench/context/images/Back-focus_fr.gif
similarity index 100%
rename from examples/Workbench/context/images/Back-focus_fr.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back-focus_fr.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back-focus_it.gif b/tapestry-examples/tapestry-workbench/context/images/Back-focus_it.gif
similarity index 100%
rename from examples/Workbench/context/images/Back-focus_it.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back-focus_it.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back.gif b/tapestry-examples/tapestry-workbench/context/images/Back.gif
similarity index 100%
rename from examples/Workbench/context/images/Back.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back_de.gif b/tapestry-examples/tapestry-workbench/context/images/Back_de.gif
similarity index 100%
rename from examples/Workbench/context/images/Back_de.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back_de.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back_fr.gif b/tapestry-examples/tapestry-workbench/context/images/Back_fr.gif
similarity index 100%
rename from examples/Workbench/context/images/Back_fr.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back_fr.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Back_it.gif b/tapestry-examples/tapestry-workbench/context/images/Back_it.gif
similarity index 100%
rename from examples/Workbench/context/images/Back_it.gif
rename to tapestry-examples/tapestry-workbench/context/images/Back_it.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Change.gif b/tapestry-examples/tapestry-workbench/context/images/Change.gif
similarity index 100%
rename from examples/Workbench/context/images/Change.gif
rename to tapestry-examples/tapestry-workbench/context/images/Change.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Change_de.gif b/tapestry-examples/tapestry-workbench/context/images/Change_de.gif
similarity index 100%
rename from examples/Workbench/context/images/Change_de.gif
rename to tapestry-examples/tapestry-workbench/context/images/Change_de.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Change_fr.gif b/tapestry-examples/tapestry-workbench/context/images/Change_fr.gif
similarity index 100%
rename from examples/Workbench/context/images/Change_fr.gif
rename to tapestry-examples/tapestry-workbench/context/images/Change_fr.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Change_it.gif b/tapestry-examples/tapestry-workbench/context/images/Change_it.gif
similarity index 100%
rename from examples/Workbench/context/images/Change_it.gif
rename to tapestry-examples/tapestry-workbench/context/images/Change_it.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Continue.gif b/tapestry-examples/tapestry-workbench/context/images/Continue.gif
similarity index 100%
rename from examples/Workbench/context/images/Continue.gif
rename to tapestry-examples/tapestry-workbench/context/images/Continue.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Update.gif b/tapestry-examples/tapestry-workbench/context/images/Update.gif
similarity index 100%
rename from examples/Workbench/context/images/Update.gif
rename to tapestry-examples/tapestry-workbench/context/images/Update.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Warning-small.gif b/tapestry-examples/tapestry-workbench/context/images/Warning-small.gif
similarity index 100%
rename from examples/Workbench/context/images/Warning-small.gif
rename to tapestry-examples/tapestry-workbench/context/images/Warning-small.gif
Binary files differ
diff --git a/examples/Workbench/context/images/Warning.gif b/tapestry-examples/tapestry-workbench/context/images/Warning.gif
similarity index 100%
rename from examples/Workbench/context/images/Warning.gif
rename to tapestry-examples/tapestry-workbench/context/images/Warning.gif
Binary files differ
diff --git a/examples/Workbench/context/images/minus.gif b/tapestry-examples/tapestry-workbench/context/images/minus.gif
similarity index 100%
rename from examples/Workbench/context/images/minus.gif
rename to tapestry-examples/tapestry-workbench/context/images/minus.gif
Binary files differ
diff --git a/examples/Workbench/context/images/nodeimage.gif b/tapestry-examples/tapestry-workbench/context/images/nodeimage.gif
similarity index 100%
rename from examples/Workbench/context/images/nodeimage.gif
rename to tapestry-examples/tapestry-workbench/context/images/nodeimage.gif
Binary files differ
diff --git a/examples/Workbench/context/images/plus.gif b/tapestry-examples/tapestry-workbench/context/images/plus.gif
similarity index 100%
rename from examples/Workbench/context/images/plus.gif
rename to tapestry-examples/tapestry-workbench/context/images/plus.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-active-left.gif b/tapestry-examples/tapestry-workbench/context/images/tab-active-left.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-active-left.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-active-left.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-active-mid.gif b/tapestry-examples/tapestry-workbench/context/images/tab-active-mid.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-active-mid.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-active-mid.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-active-right.gif b/tapestry-examples/tapestry-workbench/context/images/tab-active-right.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-active-right.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-active-right.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-inactive-left.gif b/tapestry-examples/tapestry-workbench/context/images/tab-inactive-left.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-inactive-left.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-inactive-left.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-inactive-mid.gif b/tapestry-examples/tapestry-workbench/context/images/tab-inactive-mid.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-inactive-mid.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-inactive-mid.gif
Binary files differ
diff --git a/examples/Workbench/context/images/tab-inactive-right.gif b/tapestry-examples/tapestry-workbench/context/images/tab-inactive-right.gif
similarity index 100%
rename from examples/Workbench/context/images/tab-inactive-right.gif
rename to tapestry-examples/tapestry-workbench/context/images/tab-inactive-right.gif
Binary files differ
diff --git a/examples/Workbench/context/popuplink-help.html b/tapestry-examples/tapestry-workbench/context/popuplink-help.html
similarity index 100%
rename from examples/Workbench/context/popuplink-help.html
rename to tapestry-examples/tapestry-workbench/context/popuplink-help.html
diff --git a/examples/Workbench/context/redirect-target.html b/tapestry-examples/tapestry-workbench/context/redirect-target.html
similarity index 100%
rename from examples/Workbench/context/redirect-target.html
rename to tapestry-examples/tapestry-workbench/context/redirect-target.html
diff --git a/examples/Workbench/image-src/.cvsignore b/tapestry-examples/tapestry-workbench/image-src/.cvsignore
similarity index 100%
rename from examples/Workbench/image-src/.cvsignore
rename to tapestry-examples/tapestry-workbench/image-src/.cvsignore
diff --git a/examples/Workbench/image-src/ActiveBlank.psp b/tapestry-examples/tapestry-workbench/image-src/ActiveBlank.psp
similarity index 100%
rename from examples/Workbench/image-src/ActiveBlank.psp
rename to tapestry-examples/tapestry-workbench/image-src/ActiveBlank.psp
Binary files differ
diff --git a/examples/Workbench/image-src/Back.psp b/tapestry-examples/tapestry-workbench/image-src/Back.psp
similarity index 100%
rename from examples/Workbench/image-src/Back.psp
rename to tapestry-examples/tapestry-workbench/image-src/Back.psp
Binary files differ
diff --git a/examples/Workbench/image-src/BlankFlat.psp b/tapestry-examples/tapestry-workbench/image-src/BlankFlat.psp
similarity index 100%
rename from examples/Workbench/image-src/BlankFlat.psp
rename to tapestry-examples/tapestry-workbench/image-src/BlankFlat.psp
Binary files differ
diff --git a/examples/Workbench/image-src/Change.psp b/tapestry-examples/tapestry-workbench/image-src/Change.psp
similarity index 100%
rename from examples/Workbench/image-src/Change.psp
rename to tapestry-examples/tapestry-workbench/image-src/Change.psp
Binary files differ
diff --git a/examples/Workbench/image-src/InactiveBlank.psp b/tapestry-examples/tapestry-workbench/image-src/InactiveBlank.psp
similarity index 100%
rename from examples/Workbench/image-src/InactiveBlank.psp
rename to tapestry-examples/tapestry-workbench/image-src/InactiveBlank.psp
Binary files differ
diff --git a/examples/Workbench/jetty.xml b/tapestry-examples/tapestry-workbench/jetty.xml
similarity index 100%
rename from examples/Workbench/jetty.xml
rename to tapestry-examples/tapestry-workbench/jetty.xml
diff --git a/examples/Workbench/lib/LICENSE.jCharts.txt b/tapestry-examples/tapestry-workbench/lib/LICENSE.jCharts.txt
similarity index 100%
rename from examples/Workbench/lib/LICENSE.jCharts.txt
rename to tapestry-examples/tapestry-workbench/lib/LICENSE.jCharts.txt
diff --git a/examples/Workbench/lib/jCharts-0.6.0.jar b/tapestry-examples/tapestry-workbench/lib/jCharts-0.6.0.jar
similarity index 100%
rename from examples/Workbench/lib/jCharts-0.6.0.jar
rename to tapestry-examples/tapestry-workbench/lib/jCharts-0.6.0.jar
Binary files differ
diff --git a/tapestry-examples/tapestry-workbench/pom.xml b/tapestry-examples/tapestry-workbench/pom.xml
new file mode 100644
index 0000000..ccee15f
--- /dev/null
+++ b/tapestry-examples/tapestry-workbench/pom.xml
@@ -0,0 +1,110 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-Workbench</artifactId>
+ <packaging>war</packaging>
+ <version>3.0.5-SNAPSHOT</version>
+
+ <!-- This should change to tapestry-project -->
+ <parent>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-examples</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </parent>
+
+ <name>Tapestry Workbench</name>
+ <inceptionYear>2006</inceptionYear>
+
+ <dependencies>
+ <dependency>
+ <groupId>jcharts</groupId>
+ <artifactId>jcharts</artifactId>
+ <version>0.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-contrib</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-framework</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.13</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+
+ <resources>
+ <resource>
+ <directory>src</directory>
+ <includes>
+ <include>**/*.gif</include>
+ <include>**/*.png</include>
+ <include>**/*.jwc</include>
+ <include>**/*.page</include>
+ <include>**/*.html</include>
+ <include>**/*.properties</include>
+ </includes>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.4</source>
+ <target>1.4</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.mortbay.jetty</groupId>
+ <artifactId>maven-jetty-plugin</artifactId>
+ <version>6.1.3</version>
+ <configuration>
+ <webAppSourceDirectory>${basedir}/context</webAppSourceDirectory>
+ <contextPath>/</contextPath>
+ <systemProperties>
+ <systemProperty>
+ <name>org.apache.tapestry.disable-caching</name>
+ <value>true</value>
+ </systemProperty>
+ </systemProperties>
+ </configuration>
+ <dependencies>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.0.4</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.13</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <warSourceDirectory>${basedir}/context</warSourceDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/Redirect.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/Redirect.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/Redirect.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/Redirect.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/RequestDecoder.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/RequestDecoder.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/RequestDecoder.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/RequestDecoder.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/Visit.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/Visit.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/Visit.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/Visit.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/WorkbenchHomeService.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/WorkbenchHomeService.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/WorkbenchHomeService.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/WorkbenchHomeService.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/WorkbenchValidationDelegate.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/WorkbenchValidationDelegate.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/WorkbenchValidationDelegate.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/WorkbenchValidationDelegate.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartAsset.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartAsset.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartAsset.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartAsset.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartPage.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartPage.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartPage.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartPage.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartService.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartService.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/chart/ChartService.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/ChartService.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/chart/IChartProvider.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/IChartProvider.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/chart/IChartProvider.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/IChartProvider.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/chart/PlotValue.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/PlotValue.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/chart/PlotValue.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/chart/PlotValue.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/components/.cvsignore b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/components/.cvsignore
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/components/.cvsignore
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/components/.cvsignore
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/components/Border.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/components/Border.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/components/Border.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/components/Border.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/fields/Dates.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/fields/Dates.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/fields/Dates.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/fields/Dates.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/fields/Fields.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/fields/Fields.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/fields/Fields.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/fields/Fields.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/jsp/JSP.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/jsp/JSP.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/jsp/JSP.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/jsp/JSP.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/jsp/JSPResults.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/jsp/JSPResults.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/jsp/JSPResults.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/jsp/JSPResults.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/localization/LocaleModel.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/LocaleModel.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/localization/LocaleModel.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/LocaleModel.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/localization/Localization.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/Localization.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/localization/Localization.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/Localization.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/localization/LocalizationChange.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/LocalizationChange.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/localization/LocalizationChange.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/localization/LocalizationChange.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/palette/Palette.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/Palette.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/palette/Palette.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/Palette.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/palette/PaletteResults.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/PaletteResults.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/palette/PaletteResults.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/PaletteResults.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/palette/SortModeStrings.properties b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/SortModeStrings.properties
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/palette/SortModeStrings.properties
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/palette/SortModeStrings.properties
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/table/ILocaleSelectionListener.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/ILocaleSelectionListener.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/table/ILocaleSelectionListener.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/ILocaleSelectionListener.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/table/LocaleList.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/LocaleList.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/table/LocaleList.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/LocaleList.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/table/LocaleSelection.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/LocaleSelection.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/table/LocaleSelection.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/LocaleSelection.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/table/VerbosityRating.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/VerbosityRating.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/table/VerbosityRating.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/table/VerbosityRating.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/DirectoryTableView.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/DirectoryTableView.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/DirectoryTableView.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/DirectoryTableView.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTree.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTree.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTree.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTree.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTreeTable.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTreeTable.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTreeTable.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/FileSystemTreeTable.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/ISelectedFolderSource.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/ISelectedFolderSource.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/ISelectedFolderSource.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/ISelectedFolderSource.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/SessionVisit.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/SessionVisit.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/SessionVisit.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/SessionVisit.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/SimpleTree.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/SimpleTree.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/SimpleTree.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/SimpleTree.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/TestTreeNode.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/TestTreeNode.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/TestTreeNode.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/TestTreeNode.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/AssetsHolder.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/AssetsHolder.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/AssetsHolder.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/AssetsHolder.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/Drive.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/Drive.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/Drive.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/Drive.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileObject.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileObject.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileObject.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileObject.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystem.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystem.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystem.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystem.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemDataModel.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemDataModel.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemDataModel.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemDataModel.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemStateManager.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemStateManager.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemStateManager.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FileSystemStateManager.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FolderObject.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FolderObject.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FolderObject.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/FolderObject.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/IFileSystemTreeNode.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/IFileSystemTreeNode.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/IFileSystemTreeNode.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/IFileSystemTreeNode.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/NodeRenderFactory.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/NodeRenderFactory.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/NodeRenderFactory.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/NodeRenderFactory.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/SFObject.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/SFObject.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/SFObject.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/SFObject.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeClosed.gif b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeClosed.gif
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeClosed.gif
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeClosed.gif
Binary files differ
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeOpen.gif b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeOpen.gif
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeOpen.gif
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/TreeOpen.gif
Binary files differ
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/computer.gif b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/computer.gif
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/computer.gif
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/computer.gif
Binary files differ
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/file.gif b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/file.gif
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/file.gif
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/file.gif
Binary files differ
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/harddrive.gif b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/harddrive.gif
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/harddrive.gif
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/tree/examples/fsmodel/harddrive.gif
Binary files differ
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/upload/Upload.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/upload/Upload.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/upload/Upload.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/upload/Upload.java
diff --git a/examples/Workbench/src/org/apache/tapestry/workbench/upload/UploadResults.java b/tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/upload/UploadResults.java
similarity index 100%
rename from examples/Workbench/src/org/apache/tapestry/workbench/upload/UploadResults.java
rename to tapestry-examples/tapestry-workbench/src/org/apache/tapestry/workbench/upload/UploadResults.java
diff --git a/examples/wap/.cvsignore b/tapestry-examples/wap/.cvsignore
similarity index 100%
rename from examples/wap/.cvsignore
rename to tapestry-examples/wap/.cvsignore
diff --git a/examples/wap/build.xml b/tapestry-examples/wap/build.xml
similarity index 100%
rename from examples/wap/build.xml
rename to tapestry-examples/wap/build.xml
diff --git a/examples/wap/context/WEB-INF/animate/Home.page b/tapestry-examples/wap/context/WEB-INF/animate/Home.page
similarity index 100%
rename from examples/wap/context/WEB-INF/animate/Home.page
rename to tapestry-examples/wap/context/WEB-INF/animate/Home.page
diff --git a/examples/wap/context/WEB-INF/animate/animate.application b/tapestry-examples/wap/context/WEB-INF/animate/animate.application
similarity index 100%
rename from examples/wap/context/WEB-INF/animate/animate.application
rename to tapestry-examples/wap/context/WEB-INF/animate/animate.application
diff --git a/examples/wap/context/WEB-INF/hello/Hello.page b/tapestry-examples/wap/context/WEB-INF/hello/Hello.page
similarity index 100%
rename from examples/wap/context/WEB-INF/hello/Hello.page
rename to tapestry-examples/wap/context/WEB-INF/hello/Hello.page
diff --git a/examples/wap/context/WEB-INF/hello/Home.page b/tapestry-examples/wap/context/WEB-INF/hello/Home.page
similarity index 100%
rename from examples/wap/context/WEB-INF/hello/Home.page
rename to tapestry-examples/wap/context/WEB-INF/hello/Home.page
diff --git a/examples/wap/context/WEB-INF/hello/hello.application b/tapestry-examples/wap/context/WEB-INF/hello/hello.application
similarity index 100%
rename from examples/wap/context/WEB-INF/hello/hello.application
rename to tapestry-examples/wap/context/WEB-INF/hello/hello.application
diff --git a/examples/wap/context/WEB-INF/quiz/Home.page b/tapestry-examples/wap/context/WEB-INF/quiz/Home.page
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/Home.page
rename to tapestry-examples/wap/context/WEB-INF/quiz/Home.page
diff --git a/examples/wap/context/WEB-INF/quiz/Quiz.page b/tapestry-examples/wap/context/WEB-INF/quiz/Quiz.page
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/Quiz.page
rename to tapestry-examples/wap/context/WEB-INF/quiz/Quiz.page
diff --git a/examples/wap/context/WEB-INF/quiz/Scores.page b/tapestry-examples/wap/context/WEB-INF/quiz/Scores.page
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/Scores.page
rename to tapestry-examples/wap/context/WEB-INF/quiz/Scores.page
diff --git a/examples/wap/context/WEB-INF/quiz/easyquestions.txt b/tapestry-examples/wap/context/WEB-INF/quiz/easyquestions.txt
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/easyquestions.txt
rename to tapestry-examples/wap/context/WEB-INF/quiz/easyquestions.txt
diff --git a/examples/wap/context/WEB-INF/quiz/hardquestions.txt b/tapestry-examples/wap/context/WEB-INF/quiz/hardquestions.txt
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/hardquestions.txt
rename to tapestry-examples/wap/context/WEB-INF/quiz/hardquestions.txt
diff --git a/examples/wap/context/WEB-INF/quiz/mediumquestions.txt b/tapestry-examples/wap/context/WEB-INF/quiz/mediumquestions.txt
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/mediumquestions.txt
rename to tapestry-examples/wap/context/WEB-INF/quiz/mediumquestions.txt
diff --git a/examples/wap/context/WEB-INF/quiz/quiz.application b/tapestry-examples/wap/context/WEB-INF/quiz/quiz.application
similarity index 100%
rename from examples/wap/context/WEB-INF/quiz/quiz.application
rename to tapestry-examples/wap/context/WEB-INF/quiz/quiz.application
diff --git a/examples/wap/context/WEB-INF/web.xml b/tapestry-examples/wap/context/WEB-INF/web.xml
similarity index 100%
rename from examples/wap/context/WEB-INF/web.xml
rename to tapestry-examples/wap/context/WEB-INF/web.xml
diff --git a/examples/wap/context/animate/Home.wml b/tapestry-examples/wap/context/animate/Home.wml
similarity index 100%
rename from examples/wap/context/animate/Home.wml
rename to tapestry-examples/wap/context/animate/Home.wml
diff --git a/examples/wap/context/animate/images/img1.wbmp b/tapestry-examples/wap/context/animate/images/img1.wbmp
similarity index 100%
rename from examples/wap/context/animate/images/img1.wbmp
rename to tapestry-examples/wap/context/animate/images/img1.wbmp
Binary files differ
diff --git a/examples/wap/context/animate/images/img2.wbmp b/tapestry-examples/wap/context/animate/images/img2.wbmp
similarity index 100%
rename from examples/wap/context/animate/images/img2.wbmp
rename to tapestry-examples/wap/context/animate/images/img2.wbmp
Binary files differ
diff --git a/examples/wap/context/animate/images/img3.wbmp b/tapestry-examples/wap/context/animate/images/img3.wbmp
similarity index 100%
rename from examples/wap/context/animate/images/img3.wbmp
rename to tapestry-examples/wap/context/animate/images/img3.wbmp
Binary files differ
diff --git a/examples/wap/context/animate/images/img4.wbmp b/tapestry-examples/wap/context/animate/images/img4.wbmp
similarity index 100%
rename from examples/wap/context/animate/images/img4.wbmp
rename to tapestry-examples/wap/context/animate/images/img4.wbmp
Binary files differ
diff --git a/examples/wap/context/hello/Hello.wml b/tapestry-examples/wap/context/hello/Hello.wml
similarity index 100%
rename from examples/wap/context/hello/Hello.wml
rename to tapestry-examples/wap/context/hello/Hello.wml
diff --git a/examples/wap/context/hello/Home.wml b/tapestry-examples/wap/context/hello/Home.wml
similarity index 100%
rename from examples/wap/context/hello/Home.wml
rename to tapestry-examples/wap/context/hello/Home.wml
diff --git a/examples/wap/context/index.wml b/tapestry-examples/wap/context/index.wml
similarity index 100%
rename from examples/wap/context/index.wml
rename to tapestry-examples/wap/context/index.wml
diff --git a/examples/wap/context/quiz/Home.wml b/tapestry-examples/wap/context/quiz/Home.wml
similarity index 100%
rename from examples/wap/context/quiz/Home.wml
rename to tapestry-examples/wap/context/quiz/Home.wml
diff --git a/examples/wap/context/quiz/Quiz.wml b/tapestry-examples/wap/context/quiz/Quiz.wml
similarity index 100%
rename from examples/wap/context/quiz/Quiz.wml
rename to tapestry-examples/wap/context/quiz/Quiz.wml
diff --git a/examples/wap/context/quiz/Scores.wml b/tapestry-examples/wap/context/quiz/Scores.wml
similarity index 100%
rename from examples/wap/context/quiz/Scores.wml
rename to tapestry-examples/wap/context/quiz/Scores.wml
diff --git a/examples/wap/context/quiz/images/logo.wbmp b/tapestry-examples/wap/context/quiz/images/logo.wbmp
similarity index 100%
rename from examples/wap/context/quiz/images/logo.wbmp
rename to tapestry-examples/wap/context/quiz/images/logo.wbmp
Binary files differ
diff --git a/examples/wap/jetty.xml b/tapestry-examples/wap/jetty.xml
similarity index 100%
rename from examples/wap/jetty.xml
rename to tapestry-examples/wap/jetty.xml
diff --git a/examples/wap/src/org/apache/tapestry/wap/hello/Hello.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/hello/Hello.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/hello/Hello.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/hello/Hello.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/hello/Home.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/hello/Home.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/hello/Home.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/hello/Home.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/quiz/Global.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Global.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/quiz/Global.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Global.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/quiz/Home.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Home.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/quiz/Home.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Home.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/quiz/Quiz.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Quiz.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/quiz/Quiz.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Quiz.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/quiz/Scores.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Scores.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/quiz/Scores.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Scores.java
diff --git a/examples/wap/src/org/apache/tapestry/wap/quiz/Visit.java b/tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Visit.java
similarity index 100%
rename from examples/wap/src/org/apache/tapestry/wap/quiz/Visit.java
rename to tapestry-examples/wap/src/org/apache/tapestry/wap/quiz/Visit.java
diff --git a/tapestry-framework/.cvsignore b/tapestry-framework/.cvsignore
new file mode 100644
index 0000000..4b3be5f
--- /dev/null
+++ b/tapestry-framework/.cvsignore
@@ -0,0 +1,2 @@
+target
+classes
diff --git a/tapestry-framework/META-INF/taglib.tld b/tapestry-framework/META-INF/taglib.tld
new file mode 100644
index 0000000..77c1ae5
--- /dev/null
+++ b/tapestry-framework/META-INF/taglib.tld
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+ "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_2.dtd">
+<taglib>
+ <tlib-version>1.0</tlib-version>
+ <jsp-version>1.2</jsp-version>
+ <short-name>tapestry</short-name>
+ <uri>http://jakarta.apache.org/tapestry/tld/tapestry_1_0.tld</uri>
+ <display-name>Tapestry</display-name>
+ <description>Tapestry JSP taglibrary for providing rudimentary access to a Tapestry application.</description>
+ <tag>
+ <name>page</name>
+ <tag-class>org.apache.tapestry.jsp.PageTag</tag-class>
+ <body-content>JSP</body-content>
+ <display-name>Page</display-name>
+ <description>Creates a link to a named page within a Tapestry
+ application.</description>
+ <attribute>
+ <name>servlet</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The relative path to the servlet for the application. The default value is "/app".</description>
+ </attribute>
+ <attribute>
+ <name>page</name>
+ <required>yes</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The name of the page within the application.</description>
+ </attribute>
+ <attribute>
+ <name>styleClass</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The CSS style class for the rendered tag.</description>
+ </attribute>
+ </tag>
+ <tag>
+ <name>page-url</name>
+ <tag-class>org.apache.tapestry.jsp.PageURLTag</tag-class>
+ <body-content>empty</body-content>
+ <display-name>Page URL</display-name>
+ <description>Inserts a URL to a named page within a Tapestry
+ application.</description>
+ <attribute>
+ <name>servlet</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The relative path to the servlet for the application. The default value is "/app".</description>
+ </attribute>
+ <attribute>
+ <name>page</name>
+ <required>yes</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The name of the page within the application.</description>
+ </attribute>
+ </tag>
+ <tag>
+ <name>external</name>
+ <tag-class>org.apache.tapestry.jsp.ExternalTag</tag-class>
+ <body-content>JSP</body-content>
+ <display-name>External</display-name>
+ <description>Creates a link, with parameters, to an external page within a Tapestry application.</description>
+ <attribute>
+ <name>servlet</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The relative path to the servlet for the application. The default value is "/app".</description>
+ </attribute>
+ <attribute>
+ <name>page</name>
+ <required>yes</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The name of the page within the application, which must be an external page.</description>
+ </attribute>
+ <attribute>
+ <name>styleClass</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The CSS style class for the rendered tag.</description>
+ </attribute>
+ <attribute>
+ <name>parameters</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>Either a single string to pass as a parameter, or (if prefixed with "ognl:") an OGNL expression evaluated against the pageContext.</description>
+ </attribute>
+ </tag>
+ <tag>
+ <name>external-url</name>
+ <tag-class>org.apache.tapestry.jsp.ExternalURLTag</tag-class>
+ <body-content>empty</body-content>
+ <display-name>External URL</display-name>
+ <description>Inserts a URL to an external page within a Tapestry application, including service parameters.</description>
+ <attribute>
+ <name>servlet</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The relative path to the servlet for the application. The default value is "/app".</description>
+ </attribute>
+ <attribute>
+ <name>page</name>
+ <required>yes</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>The name of the page within the application, which must be an external page.</description>
+ </attribute>
+ <attribute>
+ <name>parameters</name>
+ <required>no</required>
+ <rtexprvalue>yes</rtexprvalue>
+ <type>java.lang.String</type>
+ <description>Either a single string to pass as a parameter, or (if prefixed with "ognl:") an OGNL expression evaluated against the pageContext.</description>
+ </attribute>
+ </tag>
+</taglib>
diff --git a/tapestry-framework/build.xml b/tapestry-framework/build.xml
new file mode 100644
index 0000000..052edc1
--- /dev/null
+++ b/tapestry-framework/build.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<project name="Tapestry Framework" default="install">
+ <property name="root.dir" value=".."/>
+ <property file="${root.dir}/config/Version.properties"/>
+ <property file="${root.dir}/config/common.properties"/>
+ <property file="${root.dir}/config/build.properties"/>
+
+ <path id="project.class.path">
+ <fileset dir="${root.lib.dir}">
+ <include name="${ext.dir}/*.jar"/>
+ </fileset>
+ <fileset dir="${root.lib.dir}">
+ <include name="${j2ee.dir}/*.jar"/>
+ </fileset>
+ </path>
+ <target name="init">
+ <mkdir dir="${classes.dir}"/>
+ </target>
+ <target name="clean">
+ <delete dir="${classes.dir}"/>
+ </target>
+
+ <target name="compile" depends="init"
+ description="Compile all classes in the framework.">
+ <javac srcdir="${src.dir}" destdir="${classes.dir}" debug="on"
+ target="1.1" source="1.3">
+ <classpath refid="project.class.path"/>
+ </javac>
+ </target>
+ <target name="install" depends="compile"
+ description="Compile all classes and build the installed JAR including all package resources."
+ >
+ <copy file="${root.dir}/config/Version.properties"
+ todir="${classes.dir}/org/apache/tapestry"/>
+ <jar jarfile="${root.lib.dir}/${framework.jar}">
+ <fileset dir="${classes.dir}">
+ </fileset>
+
+ <fileset dir="${src.dir}">
+ <exclude name="**/*.java"/>
+ <exclude name="**/package.html"/>
+ </fileset>
+
+ <metainf dir="META-INF">
+ <include name="*"/>
+ </metainf>
+ </jar>
+
+ </target>
+</project>
diff --git a/tapestry-framework/pom.xml b/tapestry-framework/pom.xml
new file mode 100644
index 0000000..af23cc3
--- /dev/null
+++ b/tapestry-framework/pom.xml
@@ -0,0 +1,162 @@
+<!--suppress ALL -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-framework</artifactId>
+ <packaging>jar</packaging>
+ <version>3.0.5-SNAPSHOT</version>
+ <!-- This should change to tapestry-project -->
+ <parent>
+ <groupId>org.apache.tapestry</groupId>
+ <artifactId>tapestry-project</artifactId>
+ <version>3.0.5-SNAPSHOT</version>
+ </parent>
+ <name>Tapestry Core Library - ${version}</name>
+ <inceptionYear>2006</inceptionYear>
+
+ <dependencies>
+ <dependency>
+ <groupId>jdom</groupId>
+ <artifactId>jdom</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>jboss</groupId>
+ <artifactId>javassist</artifactId>
+ <!-- Override parent pom -->
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>ognl</groupId>
+ <artifactId>ognl</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jdom</groupId>
+ <artifactId>jdom</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.13</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-beanutils</groupId>
+ <artifactId>commons-beanutils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-digester</groupId>
+ <artifactId>commons-digester</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>bsf</groupId>
+ <artifactId>bsf</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>oro</groupId>
+ <artifactId>oro</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+ <resources>
+ <resource>
+ <directory>META-INF</directory>
+ <includes>
+ <include>**</include>
+ </includes>
+ <targetPath>META-INF</targetPath>
+ </resource>
+ <resource>
+ <directory>src</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ <exclude>**/build.xml</exclude>
+ <exclude>**/.cvsignore</exclude>
+ </excludes>
+ </resource>
+ </resources>
+ <!--
+ <testSourceDirectory>src/test</testSourceDirectory>
+ <testResources>
+ <testResource>
+ <directory>src/test</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </testResource>
+ <testResource>
+ <directory>src/conf</directory>
+ <includes>
+ <include>log4j.properties</include>
+ </includes>
+ </testResource>
+ </testResources>
+ -->
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.1</version>
+ <configuration>
+ <archive>
+ <compress>true</compress>
+ <index>true</index>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <archive>
+ <compress>true</compress>
+ <index>true</index>
+ </archive>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <reporting>
+ <outputDirectory>../target/site/tapestry-framework</outputDirectory>
+ </reporting>
+
+</project>
diff --git a/tapestry-framework/src/org/apache/tapestry/AbstractComponent.java b/tapestry-framework/src/org/apache/tapestry/AbstractComponent.java
new file mode 100644
index 0000000..4e406ec
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/AbstractComponent.java
@@ -0,0 +1,1146 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import ognl.OgnlRuntime;
+
+import org.apache.tapestry.bean.BeanProvider;
+import org.apache.tapestry.bean.BeanProviderPropertyAccessor;
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.event.ChangeObserver;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.event.PageValidateListener;
+import org.apache.tapestry.listener.ListenerMap;
+import org.apache.tapestry.param.ParameterManager;
+import org.apache.tapestry.spec.BaseLocatable;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.util.prop.OgnlUtils;
+import org.apache.tapestry.util.prop.PropertyFinder;
+import org.apache.tapestry.util.prop.PropertyInfo;
+
+/**
+ * Abstract base class implementing the {@link IComponent} interface.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class AbstractComponent extends BaseLocatable implements IComponent
+{
+ /**
+ * Used to check that subclasses invoke this implementation of prepareForRender().
+ * @see Tapestry#checkMethodInvocation(Object, String, Object)
+ * @since 3.0
+ */
+ private final static String PREPAREFORRENDER_METHOD_ID = "AbstractComponent.prepareForRender()";
+
+ /**
+ * Used to check that subclasses invoke this implementation of cleanupAfterRender().
+ * @see Tapestry#checkMethodInvocation(Object, String, Object)
+ * @since 3.0
+ */
+
+ private final static String CLEANUPAFTERRENDER_METHOD_ID =
+ "AbstractComponent.cleanupAfterRender()";
+
+ static {
+ // Register the BeanProviderHelper to provide access to the
+ // beans of a bean provider as named properties.
+
+ OgnlRuntime.setPropertyAccessor(IBeanProvider.class, new BeanProviderPropertyAccessor());
+ }
+
+ /**
+ * The specification used to originally build the component.
+ *
+ *
+ **/
+
+ private IComponentSpecification _specification;
+
+ /**
+ * The page that contains the component, possibly itself (if the component is
+ * in fact, a page).
+ *
+ *
+ **/
+
+ private IPage _page;
+
+ /**
+ * The component which contains the component. This will only be
+ * null if the component is actually a page.
+ *
+ **/
+
+ private IComponent _container;
+
+ /**
+ * The simple id of this component.
+ *
+ *
+ **/
+
+ private String _id;
+
+ /**
+ * The fully qualified id of this component. This is calculated the first time
+ * it is needed, then cached for later.
+ *
+ **/
+
+ private String _idPath;
+
+ private static final int MAP_SIZE = 5;
+
+ /**
+ * A {@link Map} of all bindings (for which there isn't a corresponding
+ * JavaBeans property); the keys are the names of formal and informal
+ * parameters.
+ *
+ **/
+
+ private Map _bindings;
+
+ private Map _components;
+ private static final int BODY_INIT_SIZE = 5;
+
+ private INamespace _namespace;
+
+ /**
+ * Used in place of JDK 1.3's Collections.EMPTY_MAP (which is not
+ * available in JDK 1.2).
+ *
+ **/
+
+ private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(1));
+
+ /**
+ * The number of {@link IRender} objects in the body of
+ * this component.
+ *
+ *
+ **/
+
+ private int _bodyCount = 0;
+
+ /**
+ * An aray of elements in the body of this component.
+ *
+ *
+ **/
+
+ private IRender[] _body;
+
+ /**
+ * The components' asset map.
+ *
+ **/
+
+ private Map _assets;
+
+ /**
+ * A mapping that allows public instance methods to be dressed up
+ * as {@link IActionListener} listener
+ * objects.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ private ListenerMap _listeners;
+
+ /**
+ * A bean provider; these are lazily created as needed.
+ *
+ * @since 1.0.4
+ *
+ **/
+
+ private IBeanProvider _beans;
+
+ /**
+ * Manages setting and clearing parameter properties for the component.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ private ParameterManager _parameterManager;
+
+ /**
+ * Provides access to localized Strings for this component.
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ private IMessages _strings;
+
+ public void addAsset(String name, IAsset asset)
+ {
+ if (_assets == null)
+ _assets = new HashMap(MAP_SIZE);
+
+ _assets.put(name, asset);
+ }
+
+ public void addComponent(IComponent component)
+ {
+ if (_components == null)
+ _components = new HashMap(MAP_SIZE);
+
+ _components.put(component.getId(), component);
+ }
+
+ /**
+ * Adds an element (which may be static text or a component) as a body
+ * element of this component. Such elements are rendered
+ * by {@link #renderBody(IMarkupWriter, IRequestCycle)}.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void addBody(IRender element)
+ {
+ // Should check the specification to see if this component
+ // allows body. Curently, this is checked by the component
+ // in render(), which is silly.
+
+ if (_body == null)
+ {
+ _body = new IRender[BODY_INIT_SIZE];
+ _body[0] = element;
+
+ _bodyCount = 1;
+ return;
+ }
+
+ // No more room? Make the array bigger.
+
+ if (_bodyCount == _body.length)
+ {
+ IRender[] newWrapped;
+
+ newWrapped = new IRender[_body.length * 2];
+
+ System.arraycopy(_body, 0, newWrapped, 0, _bodyCount);
+
+ _body = newWrapped;
+ }
+
+ _body[_bodyCount++] = element;
+ }
+
+ /**
+ * Registers this component as a listener of the page if it
+ * implements {@link org.apache.tapestry.event.PageDetachListener} or
+ * {@link org.apache.tapestry.event.PageRenderListener}.
+ *
+ * <p>
+ * Invokes {@link #finishLoad()}. Subclasses may overide as needed, but
+ * must invoke this implementation.
+ * {@link BaseComponent}
+ * loads its HTML template.
+ *
+ **/
+
+ public void finishLoad(
+ IRequestCycle cycle,
+ IPageLoader loader,
+ IComponentSpecification specification)
+ {
+ if (this instanceof PageDetachListener)
+ _page.addPageDetachListener((PageDetachListener) this);
+
+ if (this instanceof PageRenderListener)
+ _page.addPageRenderListener((PageRenderListener) this);
+
+ if (this instanceof PageValidateListener)
+ _page.addPageValidateListener((PageValidateListener) this);
+
+ finishLoad();
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, int)} instead.
+ */
+ protected void fireObservedChange(String propertyName, int newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, Object)} instead.
+ */
+ protected void fireObservedChange(String propertyName, Object newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, boolean)} instead.
+ */
+ protected void fireObservedChange(String propertyName, boolean newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, double)} instead.
+ */
+ protected void fireObservedChange(String propertyName, double newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, float)} instead.
+ */
+ protected void fireObservedChange(String propertyName, float newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, long)} instead.
+ */
+ protected void fireObservedChange(String propertyName, long newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, char)} instead.
+ */
+ protected void fireObservedChange(String propertyName, char newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, byte)} instead.
+ */
+ protected void fireObservedChange(String propertyName, byte newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1.
+ * Use {@link Tapestry#fireObservedChange(IComponent, String, short)} instead.
+ */
+ protected void fireObservedChange(String propertyName, short newValue)
+ {
+ Tapestry.fireObservedChange(this, propertyName, newValue);
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use
+ * {@link #renderInformalParameters(IMarkupWriter, IRequestCycle)}
+ * instead.
+ *
+ **/
+
+ protected void generateAttributes(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ renderInformalParameters(writer, cycle);
+ }
+
+ /**
+ * Converts informal parameters into additional attributes on the
+ * curently open tag.
+ *
+ * <p>Invoked from subclasses to allow additional attributes to
+ * be specified within a tag (this works best when there is a
+ * one-to-one corespondence between an {@link IComponent} and a
+ * HTML element.
+ *
+ * <p>Iterates through the bindings for this component. Filters
+ * out bindings when the name matches a formal parameter (as of 1.0.5,
+ * informal bindings are weeded out at page load / template load time,
+ * if they match a formal parameter, or a specificied reserved name).
+ * For the most part, all the bindings here are either informal parameter,
+ * or formal parameter without a corresponding JavaBeans property.
+ *
+ * <p>For each acceptible key, the value is extracted using {@link IBinding#getObject()}.
+ * If the value is null, no attribute is written.
+ *
+ * <p>If the value is an instance of {@link IAsset}, then
+ * {@link IAsset#buildURL(IRequestCycle)} is invoked to convert the asset
+ * to a URL.
+ *
+ * <p>Finally, {@link IMarkupWriter#attribute(String,String)} is
+ * invoked with the value (or the URL).
+ *
+ * <p>The most common use for informal parameters is to support
+ * the HTML class attribute (for use with cascading style sheets)
+ * and to specify JavaScript event handlers.
+ *
+ * <p>Components are only required to generate attributes on the
+ * result phase; this can be skipped during the rewind phase.
+ **/
+
+ protected void renderInformalParameters(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String attribute;
+
+ if (_bindings == null)
+ return;
+
+ Iterator i = _bindings.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ String name = (String) entry.getKey();
+
+ IBinding binding = (IBinding) entry.getValue();
+
+ Object value = binding.getObject();
+ if (value == null)
+ continue;
+
+ if (value instanceof IAsset)
+ {
+ IAsset asset = (IAsset) value;
+
+ // Get the URL of the asset and insert that.
+
+ attribute = asset.buildURL(cycle);
+ }
+ else
+ attribute = value.toString();
+
+ writer.attribute(name, attribute);
+ }
+
+ }
+
+ /**
+ * Returns an object used to resolve classes.
+ * @since 3.0
+ *
+ **/
+ private IResourceResolver getResourceResolver()
+ {
+ return getPage().getEngine().getResourceResolver();
+ }
+
+ /**
+ * Returns the named binding, or null if it doesn't exist.
+ *
+ * <p>This method looks for a JavaBeans property with an
+ * appropriate name, of type {@link IBinding}. The property
+ * should be named <code><i>name</i>Binding</code>. If it exists
+ * and is both readable and writable, then it is accessor method
+ * is invoked. Components which implement such methods can
+ * access their own binding through their instance variables
+ * instead of invoking this method, a performance optimization.
+ *
+ * @see #setBinding(String,IBinding)
+ *
+ **/
+
+ public IBinding getBinding(String name)
+ {
+ String bindingPropertyName = name + Tapestry.PARAMETER_PROPERTY_NAME_SUFFIX;
+ PropertyInfo info = PropertyFinder.getPropertyInfo(getClass(), bindingPropertyName);
+
+ if (info != null && info.isReadWrite() && info.getType().equals(IBinding.class))
+ {
+ IResourceResolver resolver = getPage().getEngine().getResourceResolver();
+
+ return (IBinding) OgnlUtils.get(bindingPropertyName, resolver, this);
+ }
+
+ if (_bindings == null)
+ return null;
+
+ return (IBinding) _bindings.get(name);
+ }
+
+
+ /**
+ * Return's the page's change observer. In practical terms, this
+ * will be an {@link org.apache.tapestry.engine.IPageRecorder}.
+ *
+ * @see IPage#getChangeObserver()
+ * @deprecated To be removed in 3.1; use {@link IPage#getChangeObserver()}.
+ **/
+
+ public ChangeObserver getChangeObserver()
+ {
+ return _page.getChangeObserver();
+ }
+
+ public IComponent getComponent(String id)
+ {
+ IComponent result = null;
+
+ if (_components != null)
+ result = (IComponent) _components.get(id);
+
+ if (result == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("no-such-component", this, id),
+ this,
+ null,
+ null);
+
+ return result;
+ }
+
+ public IComponent getContainer()
+ {
+ return _container;
+ }
+
+ public void setContainer(IComponent value)
+ {
+ if (_container != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractComponent.attempt-to-change-container"));
+
+ _container = value;
+ }
+
+ /**
+ * Returns the name of the page, a slash, and this component's id path.
+ * Pages are different, they simply return their name.
+ *
+ * @see #getIdPath()
+ *
+ **/
+
+ public String getExtendedId()
+ {
+ if (_page == null)
+ return null;
+
+ return _page.getPageName() + "/" + getIdPath();
+ }
+
+ public String getId()
+ {
+ return _id;
+ }
+
+ public void setId(String value)
+ {
+ if (_id != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractComponent.attempt-to-change-component-id"));
+
+ _id = value;
+ }
+
+ public String getIdPath()
+ {
+ String containerIdPath;
+
+ if (_container == null)
+ throw new NullPointerException(
+ Tapestry.format("AbstractComponent.null-container", this));
+
+ containerIdPath = _container.getIdPath();
+
+ if (containerIdPath == null)
+ _idPath = _id;
+ else
+ _idPath = containerIdPath + "." + _id;
+
+ return _idPath;
+ }
+
+ public IPage getPage()
+ {
+ return _page;
+ }
+
+ public void setPage(IPage value)
+ {
+ if (_page != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractComponent.attempt-to-change-page"));
+
+ _page = value;
+ }
+
+ public IComponentSpecification getSpecification()
+ {
+ return _specification;
+ }
+
+ public void setSpecification(IComponentSpecification value)
+ {
+ if (_specification != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractComponent.attempt-to-change-spec"));
+
+ _specification = value;
+ }
+
+ /**
+ * Renders all elements wrapped by the receiver.
+ *
+ **/
+
+ public void renderBody(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ for (int i = 0; i < _bodyCount; i++)
+ _body[i].render(writer, cycle);
+ }
+
+ /**
+ * Adds the binding with the given name, replacing any existing binding
+ * with that name.
+ *
+ * <p>This method checks to see if a matching JavaBeans property
+ * (with a name of <code><i>name</i>Binding</code> and a type of
+ * {@link IBinding}) exists. If so, that property is updated.
+ * An optimized component can simply implement accessor and
+ * mutator methods and then access its bindings via its own
+ * instance variables, rather than going through {@link
+ * #getBinding(String)}.
+ *
+ * <p>Informal parameters should <em>not</em> be stored in
+ * instance variables if {@link
+ * #renderInformalParameters(IMarkupWriter, IRequestCycle)} is to be used.
+ * It relies on using the collection of bindings (to store informal parameters).
+ **/
+
+ public void setBinding(String name, IBinding binding)
+ {
+ String bindingPropertyName = name + Tapestry.PARAMETER_PROPERTY_NAME_SUFFIX;
+
+ PropertyInfo info = PropertyFinder.getPropertyInfo(getClass(), bindingPropertyName);
+
+ if (info != null && info.isReadWrite() && info.getType().equals(IBinding.class))
+ {
+ IResourceResolver resolver = getPage().getEngine().getResourceResolver();
+ OgnlUtils.set(bindingPropertyName, resolver, this, binding);
+ return;
+ }
+
+ if (_bindings == null)
+ _bindings = new HashMap(MAP_SIZE);
+
+ _bindings.put(name, binding);
+ }
+
+
+ public String toString()
+ {
+ StringBuffer buffer;
+
+ buffer = new StringBuffer(super.toString());
+
+ buffer.append('[');
+
+ buffer.append(getExtendedId());
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns an unmodifiable {@link Map} of components, keyed on component id.
+ * Never returns null, but may return an empty map. The returned map is
+ * immutable.
+ *
+ **/
+
+ public Map getComponents()
+ {
+ if (_components == null)
+ return EMPTY_MAP;
+
+ return Collections.unmodifiableMap(_components);
+
+ }
+
+ public Map getAssets()
+ {
+ if (_assets == null)
+ return EMPTY_MAP;
+
+ return Collections.unmodifiableMap(_assets);
+ }
+
+ public IAsset getAsset(String name)
+ {
+ if (_assets == null)
+ return null;
+
+ return (IAsset) _assets.get(name);
+ }
+
+ public Collection getBindingNames()
+ {
+ // If no conainer, i.e. a page, then no bindings.
+
+ if (_container == null)
+ return null;
+
+ HashSet result = new HashSet();
+
+ // All the informal bindings go into the bindings Map.
+
+ if (_bindings != null)
+ result.addAll(_bindings.keySet());
+
+ // Now, iterate over the formal parameters and add the formal parameters
+ // that have a binding.
+
+ List names = _specification.getParameterNames();
+
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) names.get(i);
+
+ if (result.contains(name))
+ continue;
+
+ if (getBinding(name) != null)
+ result.add(name);
+ }
+
+ return result;
+ }
+
+ /**
+ *
+ * Returns a {@link Map} of all bindings for this component. This implementation
+ * is expensive, since it has to merge the disassociated bindings (informal parameters,
+ * and parameters without a JavaBeans property) with the associated bindings (formal
+ * parameters with a JavaBeans property).
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public Map getBindings()
+ {
+ Map result = new HashMap();
+
+ // Add any informal parameters.
+
+ if (_bindings != null)
+ result.putAll(_bindings);
+
+ // Now work on the formal parameters
+
+ Iterator i = _specification.getParameterNames().iterator();
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+
+ if (result.containsKey(name))
+ continue;
+
+ IBinding binding = getBinding(name);
+
+ if (binding != null)
+ result.put(name, binding);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a {@link ListenerMap} for the component. A {@link ListenerMap} contains a number of
+ * synthetic read-only properties that implement the {@link IActionListener}
+ * interface, but in fact, cause public instance methods to be invoked.
+ *
+ * @since 1.0.2
+ **/
+
+ public ListenerMap getListeners()
+ {
+ if (_listeners == null)
+ _listeners = new ListenerMap(this);
+
+ return _listeners;
+ }
+
+ /**
+ * Returns the {@link IBeanProvider} for this component. This is lazily created the
+ * first time it is needed.
+ *
+ * @since 1.0.4
+ *
+ **/
+
+ public IBeanProvider getBeans()
+ {
+ if (_beans == null)
+ _beans = new BeanProvider(this);
+
+ return _beans;
+ }
+
+ /**
+ *
+ * Invoked, as a convienience,
+ * from {@link #finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}.
+ * This implemenation does nothing. Subclasses may override without invoking
+ * this implementation.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ protected void finishLoad()
+ {
+ }
+
+ /**
+ * The main method used to render the component.
+ * Invokes {@link #prepareForRender(IRequestCycle)}, then
+ * {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
+ * {@link #cleanupAfterRender(IRequestCycle)} is invoked in a
+ * <code>finally</code> block.
+ *
+ * <p>Subclasses should not override this method; instead they
+ * will implement {@link #renderComponent(IMarkupWriter, IRequestCycle)}.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ public final void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ try
+ {
+ Tapestry.clearMethodInvocations();
+
+ prepareForRender(cycle);
+
+ Tapestry.checkMethodInvocation(PREPAREFORRENDER_METHOD_ID, "prepareForRender()", this);
+
+ renderComponent(writer, cycle);
+ }
+ finally
+ {
+ Tapestry.clearMethodInvocations();
+
+ cleanupAfterRender(cycle);
+
+ Tapestry.checkMethodInvocation(
+ CLEANUPAFTERRENDER_METHOD_ID,
+ "cleanupAfterRender()",
+ this);
+ }
+ }
+
+ /**
+ * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}
+ * to prepare the component to render. This implementation
+ * sets JavaBeans properties from matching bound parameters.
+ * Subclasses that override this method must invoke this
+ * implementation as well.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ protected void prepareForRender(IRequestCycle cycle)
+ {
+ Tapestry.addMethodInvocation(PREPAREFORRENDER_METHOD_ID);
+
+ if (_parameterManager == null)
+ {
+ // Pages inherit from this class too, but pages (by definition)
+ // never have parameters.
+
+ if (getSpecification().isPageSpecification())
+ return;
+
+ _parameterManager = new ParameterManager(this);
+ }
+
+ _parameterManager.setParameters(cycle);
+ }
+
+ /**
+ * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}
+ * to actually render the component (with any parameter values
+ * already set). This is the method that subclasses must implement.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ protected abstract void renderComponent(IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Invoked by {@link #render(IMarkupWriter, IRequestCycle)}
+ * after the component renders, to clear any parameters back to
+ * null (or 0, or false, or whatever the correct default is).
+ * Primarily, this is used to ensure
+ * that the component doesn't hold onto any objects that could
+ * otherwise be garbage collected.
+ *
+ * <p>Subclasses may override this implementation, but must
+ * also invoke it.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ Tapestry.addMethodInvocation(CLEANUPAFTERRENDER_METHOD_ID);
+
+ if (_parameterManager != null)
+ _parameterManager.resetParameters(cycle);
+ }
+
+ /** @since 3.0 **/
+
+ public IMessages getMessages()
+ {
+ if (_strings == null)
+ _strings = getPage().getEngine().getComponentMessagesSource().getMessages(this);
+
+ return _strings;
+ }
+
+ /**
+ * Obtains the {@link IMessages} for this component
+ * (if necessary), and gets the string from it.
+ *
+ **/
+
+ public String getString(String key)
+ {
+ return getMessages().getMessage(key);
+ }
+
+ public String getMessage(String key)
+ {
+ // Invoke the deprecated implementation (for code coverage reasons).
+ // In 3.1, remove getString() and move its implementation
+ // here.
+
+ return getString(key);
+ }
+
+ /**
+ * Formats a message string, using
+ * {@link IMessages#format(java.lang.String, java.lang.Object[])}.
+ *
+ * @param key the key used to obtain a localized pattern using
+ * {@link #getString(String)}
+ * @param arguments passed to the formatter
+ *
+ * @since 2.2
+ * @deprecated To be removed in 3.1. Use {@link #format(String, Object[])} instead.
+ **/
+
+ public String formatString(String key, Object[] arguments)
+ {
+ return getMessages().format(key, arguments);
+ }
+
+ /**
+ * Formats a localized message string, using
+ * {@link IMessages#format(java.lang.String, java.lang.Object[])}.
+ *
+ * @param key the key used to obtain a localized pattern using
+ * {@link #getString(String)}
+ * @param arguments passed to the formatter
+ *
+ * @since 3.0
+ */
+
+ public String format(String key, Object[] arguments)
+ {
+ // SOP: New name invokes deprecated method (consistency and
+ // code coverage); in 3.1 we move the implementation here.
+
+ return formatString(key, arguments);
+ }
+
+ /**
+ * Convienience method for invoking {@link IMessages#format(String, Object[])}
+ *
+ * @since 2.2
+ * @deprecated To be removed in 3.1. Use {@link #format(String, Object)} instead.
+ *
+ **/
+
+ public String formatString(String key, Object argument)
+ {
+ return getMessages().format(key, argument);
+ }
+
+ /**
+ * Convienience method for invoking {@link IMessages#format(String, Object)}
+ *
+ * @since 3.0
+ *
+ */
+
+ public String format(String key, Object argument)
+ {
+ return formatString(key, argument);
+ }
+
+ /**
+ * Convienience method for invoking {@link IMessages#format(String, Object, Object)}.
+ *
+ * @since 2.2
+ * @deprecated To be removed in 3.1. Use {@link #format(String, Object, Object)} instead.
+ **/
+
+ public String formatString(String key, Object argument1, Object argument2)
+ {
+ return getMessages().format(key, argument1, argument2);
+ }
+
+ /**
+ * Convienience method for invoking {@link IMessages#format(String, Object, Object)}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public String format(String key, Object argument1, Object argument2)
+ {
+ return formatString(key, argument1, argument2);
+ }
+
+ /**
+ * Convienience method for {@link IMessages#format(String, Object, Object, Object)}.
+ *
+ * @since 2.2
+ * @deprecated To be removed in 3.1. Use {@link #format(String, Object, Object, Object)} instead.
+ */
+
+ public String formatString(String key, Object argument1, Object argument2, Object argument3)
+ {
+ return getMessages().format(key, argument1, argument2, argument3);
+ }
+
+ /**
+ * Convienience method for {@link IMessages#format(String, Object, Object, Object)}.
+ *
+ * @since 3.0
+ */
+
+ public String format(String key, Object argument1, Object argument2, Object argument3)
+ {
+ return formatString(key, argument1, argument2, argument3);
+ }
+
+ public INamespace getNamespace()
+ {
+ return _namespace;
+ }
+
+ public void setNamespace(INamespace namespace)
+ {
+ _namespace = namespace;
+ }
+
+ /**
+ * Returns the body of the component, the element (which may be static HTML or components)
+ * that the component immediately wraps. May return null. Do not modify the returned
+ * array. The array may be padded with nulls.
+ *
+ * @since 2.3
+ * @see #getBodyCount()
+ *
+ **/
+
+ public IRender[] getBody()
+ {
+ return _body;
+ }
+
+ /**
+ * Returns the active number of elements in the the body, which may be zero.
+ *
+ * @since 2.3
+ * @see #getBody()
+ *
+ **/
+
+ public int getBodyCount()
+ {
+ return _bodyCount;
+ }
+
+ /**
+ * Empty implementation of
+ * {@link org.apache.tapestry.event.PageRenderListener#pageEndRender(PageEvent)}.
+ * This allows classes to implement
+ * {@link org.apache.tapestry.event.PageRenderListener} and only
+ * implement the
+ * {@link org.apache.tapestry.event.PageRenderListener#pageBeginRender(PageEvent)}
+ * method.
+ * @since 3.0
+ */
+
+ public void pageEndRender(PageEvent event)
+ {
+ }
+
+ /**
+ * Sets a property of a component.
+ * @see IComponent
+ * @since 3.0
+ */
+ public void setProperty(String propertyName, Object value)
+ {
+ IResourceResolver resolver = getResourceResolver();
+ OgnlUtils.set(propertyName, resolver, this, value);
+ }
+ /**
+ * Gets a property of a component.
+ * @see IComponent
+ * @since 3.0
+ */
+ public Object getProperty(String propertyName)
+ {
+ IResourceResolver resolver = getResourceResolver();
+ return OgnlUtils.get(propertyName, resolver, this);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/AbstractMarkupWriter.java b/tapestry-framework/src/org/apache/tapestry/AbstractMarkupWriter.java
new file mode 100644
index 0000000..b4514b3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/AbstractMarkupWriter.java
@@ -0,0 +1,843 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Stack;
+
+import org.apache.tapestry.util.ContentType;
+
+/**
+ * Abstract base class implementing the {@link IMarkupWriter} interface.
+ * This class is used to create a Generic Tag Markup Language (GTML) output.
+ * It is more sophisticated than <code>PrintWriter</code> in that it maintains
+ * a concept hierarchy of open GTML tags. It also supplies a number of other
+ * of the features that are useful when creating GTML.
+ *
+ * Elements are started with the {@link #begin(String)}
+ * or {@link #beginEmpty(String)}
+ * methods. Once they are started, attributes for the elements may be set with
+ * the various <code>attribute()</code> methods. The element is closed off
+ * (i.e., the closing '>' character is written) when any other method
+ * is invoked (exception: methods which do not produce output, such as
+ * {@link #flush()}). The <code>end()</code> methods end an element,
+ * writing an GTML close tag to the output.
+ *
+ * <p>TBD:
+ * <ul>
+ * <li>Support XML and XHTML
+ * <li>What to do with Unicode characters with a value greater than 255?
+ * </ul>
+ *
+ * <p>This class is derived from the original class
+ * <code>com.primix.servlet.HTMLWriter</code>,
+ * part of the <b>ServletUtils</b> framework available from
+ * <a href="http://www.gjt.org/servlets/JCVSlet/list/gjt/com/primix/servlet">The Giant
+ * Java Tree</a>.
+ *
+ * @version $Id$
+ * @author Howard Ship, David Solis
+ * @since 0.2.9
+ *
+ **/
+
+public abstract class AbstractMarkupWriter implements IMarkupWriter
+{
+ /**
+ * The encoding to be used should it be omitted in the constructors.
+ * This is only used for backward compatibility. New code always provides the encoding.
+ */
+
+ private static final String DEFAULT_ENCODING = "utf-8";
+
+ /**
+ * The underlying {@link PrintWriter} that output is sent to.
+ *
+ **/
+
+ private PrintWriter _writer;
+
+ /**
+ * Indicates whether a tag is open or not. A tag is opened by
+ * {@link #begin(String)} or {@link #beginEmpty(String)}.
+ * It stays open while calls to the <code>attribute()</code>
+ * methods are made. It is closed
+ * (the '>' is written) when any other method is invoked.
+ *
+ **/
+
+ private boolean _openTag = false;
+
+ /**
+ * Indicates that the tag was opened with
+ * {@link #beginEmpty(String)}, which affects
+ * how the tag is closed (a slash is added to indicate the
+ * lack of a body). This is compatible with HTML, but reflects
+ * an XML/XHTML leaning.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private boolean _emptyTag = false;
+
+ /**
+ * A Stack of Strings used to track the active tag elements. Elements are active
+ * until the corresponding close tag is written. The {@link #push(String)} method
+ * adds elements to the stack, {@link #pop()} removes them.
+ *
+ **/
+
+ private Stack _activeElementStack;
+
+ /**
+ * The depth of the open tag stack.
+ * @see #_activeElementStack
+ *
+ **/
+
+ private int _depth = 0;
+
+ private char[] _buffer;
+
+ private String[] _entities;
+ private boolean[] _safe;
+
+ /**
+ * Implemented in concrete subclasses to provide an indication of which
+ * characters are 'safe' to insert directly into the response. The index
+ * into the array is the character, if the value at the index is false (or the
+ * index out of range), then the character is escaped.
+ *
+ **/
+
+ private String _contentType;
+
+ /**
+ * Indicates whether {@link #close()} should close the
+ * underlying {@link PrintWriter}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private boolean _propagateClose = true;
+
+ public String getContentType()
+ {
+ return _contentType;
+ }
+
+ abstract public IMarkupWriter getNestedWriter();
+
+ /**
+ * General constructor used by subclasses.
+ *
+ * @param safe an array of flags indicating which characters
+ * can be passed directly through with out filtering. Characters marked
+ * unsafe, or outside the range defined by safe, are converted to entities.
+ * @param entities a set of prefered entities, unsafe characters with
+ * a defined entity use the entity, other characters are converted
+ * to numeric entities.
+ * @param contentType the MIME type of the content produced by the writer.
+ * @param encoding the encoding of content produced by the writer.
+ * @param stream stream to which content will be written.
+ *
+ **/
+
+ protected AbstractMarkupWriter(
+ boolean safe[],
+ String[] entities,
+ String contentType,
+ String encoding,
+ OutputStream stream)
+ {
+ if (entities == null || safe == null || contentType == null || encoding == null)
+ throw new IllegalArgumentException(
+ Tapestry.getMessage("AbstractMarkupWriter.missing-constructor-parameters"));
+
+ _entities = entities;
+ _safe = safe;
+
+ _contentType = generateFullContentType(contentType, encoding);
+
+ setOutputStream(stream, encoding);
+ }
+
+ /**
+ * General constructor used by subclasses.
+ * This constructor is left for backward compatibility. It is preferred that
+ * it is not used since it does not specify an encoding for conversion.
+ *
+ * @param safe an array of flags indicating which characters
+ * can be passed directly through with out filtering. Characters marked
+ * unsafe, or outside the range defined by safe, are converted to entities.
+ * @param entities a set of prefered entities, unsafe characters with
+ * a defined entity use the entity, other characters are converted
+ * to numeric entities.
+ * @param contentType the type of content produced by the
+ * writer.
+ * @param stream stream to which content will be written.
+ **/
+
+ protected AbstractMarkupWriter(
+ boolean safe[],
+ String[] entities,
+ String contentType,
+ OutputStream stream)
+ {
+ this(safe, entities, contentType);
+
+ ContentType contentTypeObject = new ContentType(contentType);
+ String encoding = contentTypeObject.getParameter("charset");
+
+ setOutputStream(stream, encoding);
+ }
+
+ /**
+ * Creates new markup writer around the underlying {@link PrintWriter}.
+ *
+ * <p>This is primarily used by {@link org.apache.tapestry.engine.TagSupportService},
+ * which is inlcuding content, and therefore this method will not
+ * close the writer when the markup writer is closed.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected AbstractMarkupWriter(
+ boolean safe[],
+ String[] entities,
+ String contentType,
+ PrintWriter writer)
+ {
+ this(safe, entities, contentType);
+
+ // When the markup writer is closed, the underlying writer
+ // is NOT closed.
+
+ _propagateClose = false;
+ _writer = writer;
+ }
+
+ /**
+ * Special constructor used for nested response writers.
+ * The subclass is responsible for creating the writer.
+ *
+ **/
+
+ protected AbstractMarkupWriter(boolean safe[], String[] entities, String contentType)
+ {
+ if (entities == null || safe == null || contentType == null)
+ throw new IllegalArgumentException(
+ Tapestry.getMessage("AbstractMarkupWriter.missing-constructor-parameters"));
+
+ _entities = entities;
+ _safe = safe;
+ _contentType = generateFullContentType(contentType, DEFAULT_ENCODING);
+ }
+
+ /**
+ * Ensures that the content type has a charset (encoding) parameter.
+ *
+ * @param contentType The content type, e.g. text/html. It may contain a charset parameter.
+ * @param encoding The value of the charset parameter of the content type if it is not already present.
+ * @return The content type containing a charset parameter, e.g. text/html;charset=utf-8
+ */
+ private String generateFullContentType(String contentType, String encoding)
+ {
+ ContentType contentTypeObject = new ContentType(contentType);
+ if (contentTypeObject.getParameter("charset") == null)
+ contentTypeObject.setParameter("charset", encoding);
+ return contentTypeObject.unparse();
+ }
+
+ protected void setWriter(PrintWriter writer)
+ {
+ _writer = writer;
+ }
+
+ protected void setOutputStream(OutputStream stream, String encoding)
+ {
+ try
+ {
+ OutputStreamWriter owriter;
+ if (encoding != null)
+ owriter = new OutputStreamWriter(stream, encoding);
+ else
+ owriter = new OutputStreamWriter(stream);
+ Writer bwriter = new BufferedWriter(owriter);
+
+ _writer = new PrintWriter(bwriter);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new IllegalArgumentException(
+ Tapestry.format("illegal-encoding", encoding));
+ }
+ }
+
+ /**
+ * Writes an integer attribute into the currently open tag.
+ *
+ * <p>TBD: Validate that name is legal.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ **/
+
+ public void attribute(String name, int value)
+ {
+ checkTagOpen();
+
+ _writer.print(' ');
+ _writer.print(name);
+ _writer.print("=\"");
+ _writer.print(value);
+ _writer.print('"');
+ }
+
+ /**
+ * Writes a boolean attribute into the currently open tag.
+ *
+ * <p>TBD: Validate that name is legal.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void attribute(String name, boolean value)
+ {
+ checkTagOpen();
+
+ _writer.print(' ');
+ _writer.print(name);
+ _writer.print("=\"");
+ _writer.print(value);
+ _writer.print('"');
+ }
+
+ /**
+ * Writes an attribute into the most recently opened tag. This must be called after
+ * {@link #begin(String)}
+ * and before any other kind of writing (which closes the tag).
+ *
+ * <p>The value may be null. A null value will be rendered as an empty string.
+ *
+ * <p>Troublesome characters in the value are converted to thier GTML entities, much
+ * like a <code>print()</code> method, with the following exceptions:
+ * <ul>
+ * <li>The double quote (") is converted to &quot;
+ * <li>The ampersand (&) is passed through unchanged
+ * </ul>
+ *
+ * @throws IllegalStateException if there is no open tag.
+ * @param name The name of the attribute to write (no validation
+ * is done on the name).
+ * @param value The value to write. If null, the attribute
+ * name is written as the value. Otherwise, the
+ * value is written,
+ **/
+
+ public void attribute(String name, String value)
+ {
+ checkTagOpen();
+
+ _writer.print(' ');
+
+ // Could use a check here that name contains only valid characters
+
+ _writer.print(name);
+ _writer.print("=\"");
+
+ if (value != null)
+ {
+ int length = value.length();
+
+ if (_buffer == null || _buffer.length < length)
+ _buffer = new char[length];
+
+ value.getChars(0, length, _buffer, 0);
+
+ safePrint(_buffer, 0, length, true);
+ }
+
+ _writer.print('"');
+
+ }
+
+ /**
+ * Similar to {@link #attribute(String, String)} but no escaping of invalid elements
+ * is done for the value.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ * @since 3.0
+ *
+ **/
+ public void attributeRaw(String name, String value)
+ {
+ if (value == null)
+ {
+ attribute(name, value);
+ return;
+ }
+
+ checkTagOpen();
+
+ _writer.print(' ');
+
+ _writer.print(name);
+
+ _writer.print("=\"");
+ _writer.print(value);
+ _writer.print('"');
+ }
+
+ /**
+ * Closes any existing tag then starts a new element. The new element is pushed
+ * onto the active element stack.
+ **/
+
+ public void begin(String name)
+ {
+ if (_openTag)
+ closeTag();
+
+ push(name);
+
+ _writer.print('<');
+ _writer.print(name);
+
+ _openTag = true;
+ _emptyTag = false;
+ }
+
+ /**
+ * Starts an element that will not later be matched with an <code>end()</code>
+ * call. This is useful for elements such as <hr;> or <br> that
+ * do not need closing tags.
+ *
+ **/
+
+ public void beginEmpty(String name)
+ {
+ if (_openTag)
+ closeTag();
+
+ _writer.print('<');
+ _writer.print(name);
+
+ _openTag = true;
+ _emptyTag = true;
+ }
+
+ /**
+ * Invokes <code>checkError()</code> on the
+ * <code>PrintWriter</code> used to format output.
+ **/
+
+ public boolean checkError()
+ {
+ return _writer.checkError();
+ }
+
+ private void checkTagOpen()
+ {
+ if (!_openTag)
+ throw new IllegalStateException(
+ Tapestry.getMessage("AbstractMarkupWriter.tag-not-open"));
+ }
+
+ /**
+ * Closes this <code>IMarkupWriter</code>. Any active elements are closed. The
+ * {@link PrintWriter} is then sent {@link PrintWriter#close()}.
+ *
+ **/
+
+ public void close()
+ {
+ if (_openTag)
+ closeTag();
+
+ // Close any active elements.
+
+ while (_depth > 0)
+ {
+ _writer.print("</");
+ _writer.print(pop());
+ _writer.print('>');
+ }
+
+ if (_propagateClose)
+ _writer.close();
+
+ _writer = null;
+ _activeElementStack = null;
+ _buffer = null;
+ }
+
+ /**
+ * Closes the most recently opened element by writing the '>' that ends
+ * it. May write a slash before the '>' if the tag
+ * was opened by {@link #beginEmpty(String)}.
+ *
+ * <p>Once this is invoked, the <code>attribute()</code> methods
+ * may not be used until a new element is opened with {@link #begin(String)} or
+ * or {@link #beginEmpty(String)}.
+ **/
+
+ public void closeTag()
+ {
+ if (_emptyTag)
+ _writer.print('/');
+
+ _writer.print('>');
+
+ _openTag = false;
+ _emptyTag = false;
+ }
+
+ /**
+ * Writes an GTML comment. Any open tag is first closed.
+ * The method takes care of
+ * providing the <code><!--</code> and <code>--></code>,
+ * including a blank line after the close of the comment.
+ *
+ * <p>Most characters are valid inside an GTML comment, so no check
+ * of the contents is made (much like {@link #printRaw(String)}.
+ *
+ **/
+
+ public void comment(String value)
+ {
+ if (_openTag)
+ closeTag();
+
+ _writer.print("<!-- ");
+ _writer.print(value);
+ _writer.println(" -->");
+ }
+
+ /**
+ * Ends the element most recently started by {@link #begin(String)}.
+ * The name of the tag
+ * is popped off of the active element stack and used to form an GTML close tag.
+ *
+ * <p>TBD: Error checking for the open element stack empty.
+ **/
+
+ public void end()
+ {
+ if (_openTag)
+ closeTag();
+
+ _writer.print("</");
+ _writer.print(pop());
+ _writer.print('>');
+ }
+
+ /**
+ * Ends the most recently started element with the given name. This will
+ * also end any other intermediate elements. This is very useful for easily
+ * ending a table or even an entire page.
+ *
+ * <p>TBD: Error check if the name matches nothing on the open tag stack.
+ **/
+
+ public void end(String name)
+ {
+ if (_openTag)
+ closeTag();
+
+ while (true)
+ {
+ String tagName = pop();
+
+ _writer.print("</");
+ _writer.print(tagName);
+ _writer.print('>');
+
+ if (tagName.equals(name))
+ break;
+ }
+ }
+
+ /**
+ * Forwards <code>flush()</code> to this <code>AbstractMarkupWriter</code>'s
+ * <code>PrintWriter</code>.
+ *
+ **/
+
+ public void flush()
+ {
+ _writer.flush();
+ }
+
+ /**
+ * Removes the top element from the active element stack and returns it.
+ *
+ **/
+
+ protected final String pop()
+ {
+ String result = (String) _activeElementStack.pop();
+ _depth--;
+
+ return result;
+ }
+
+ /**
+ *
+ * The primary <code>print()</code> method, used by most other methods.
+ *
+ * <p>Prints the character array, first closing any open tag. Problematic characters
+ * ('<', '>' and '&') are converted to their
+ * GTML entities.
+ *
+ * <p>All 'unsafe' characters are properly converted to either a named
+ * or numeric GTML entity. This can be somewhat expensive, so use
+ * {@link #printRaw(char[], int, int)} if the data to print is guarenteed
+ * to be safe.
+ *
+ * <p>Does <em>nothing</em> if <code>data</code> is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(char[] data, int offset, int length)
+ {
+ if (data == null)
+ return;
+
+ if (_openTag)
+ closeTag();
+
+ safePrint(data, offset, length, false);
+ }
+
+ /**
+ * Prints a single character. If the character is not a 'safe' character,
+ * such as '<', then it's GTML entity (named or numeric) is printed instead.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(char value)
+ {
+ if (_openTag)
+ closeTag();
+
+ if (value < _safe.length && _safe[value])
+ {
+ _writer.print(value);
+ return;
+ }
+
+ String entity = null;
+
+ if (value < _entities.length)
+ entity = _entities[value];
+
+ if (entity != null)
+ {
+ _writer.print(entity);
+ return;
+ }
+
+ // Not a well-known entity. Print it's numeric equivalent. Note: this omits
+ // the leading '0', but most browsers (IE 5.0) don't seem to mind. Is this a bug?
+
+ _writer.print("&#" + (int) value + ";");
+ }
+
+ /**
+ * Prints an integer.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(int value)
+ {
+ if (_openTag)
+ closeTag();
+
+ _writer.print(value);
+ }
+
+ /**
+ * Invokes {@link #print(char[], int, int)} to print the string. Use
+ * {@link #printRaw(String)} if the character data is known to be safe.
+ *
+ * <p>Does <em>nothing</em> if <code>value</code> is null.
+ *
+ * <p>Closes any open tag.
+ *
+ * @see #print(char[], int, int)
+ *
+ **/
+
+ public void print(String value)
+ {
+ if (value == null)
+ return;
+
+ int length = value.length();
+
+ if (_buffer == null || _buffer.length < length)
+ _buffer = new char[length];
+
+ value.getChars(0, length, _buffer, 0);
+
+ print(_buffer, 0, length);
+ }
+
+ /**
+ * Closes the open tag (if any), then prints a line seperator to the output stream.
+ *
+ **/
+
+ public void println()
+ {
+ if (_openTag)
+ closeTag();
+
+ _writer.println();
+ }
+
+ /**
+ * Prints and portion of an output buffer to the stream.
+ * No escaping of invalid GTML elements is done, which
+ * makes this more effecient than <code>print()</code>.
+ * Does <em>nothing</em> if <code>buffer</code>
+ * is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void printRaw(char[] buffer, int offset, int length)
+ {
+ if (buffer == null)
+ return;
+
+ if (_openTag)
+ closeTag();
+
+ _writer.write(buffer, offset, length);
+ }
+
+ /**
+ * Prints output to the stream. No escaping of invalid GTML elements is done, which
+ * makes this more effecient than <code>print()</code>. Does <em>nothing</em>
+ * if <code>value</code>
+ * is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void printRaw(String value)
+ {
+ if (value == null)
+ return;
+
+ if (_openTag)
+ closeTag();
+
+ _writer.print(value);
+ }
+
+ /**
+ * Adds an element to the active element stack.
+ *
+ **/
+
+ protected final void push(String name)
+ {
+ if (_activeElementStack == null)
+ _activeElementStack = new Stack();
+
+ _activeElementStack.push(name);
+
+ _depth++;
+ }
+
+ /**
+ * Internal support for safe printing. Ensures that all characters emitted
+ * are safe: either valid GTML characters or GTML entities (named or numeric).
+ **/
+
+ private void safePrint(char[] data, int offset, int length, boolean isAttribute)
+ {
+ int safelength = 0;
+ int start = offset;
+
+ for (int i = 0; i < length; i++)
+ {
+ char ch = data[offset + i];
+
+ // Ignore safe characters. In an attribute, quotes
+ // are not ok and are escaped.
+
+ boolean isSafe = (ch < _safe.length && _safe[ch]);
+
+ if (isAttribute && ch == '"')
+ isSafe = false;
+
+ if (isSafe)
+ {
+ safelength++;
+ continue;
+ }
+
+ // Write the safe stuff.
+
+ if (safelength > 0)
+ _writer.write(data, start, safelength);
+
+ String entity = null;
+
+ // Look for a known entity.
+
+ if (ch < _entities.length)
+ entity = _entities[ch];
+
+ // Failing that, emit a numeric entity.
+
+ if (entity == null)
+ entity = "&#" + (int) ch + ";";
+
+ _writer.print(entity);
+
+ start = offset + i + 1;
+ safelength = 0;
+ }
+
+ if (safelength > 0)
+ _writer.write(data, start, safelength);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/AbstractPage.java b/tapestry-framework/src/org/apache/tapestry/AbstractPage.java
new file mode 100644
index 0000000..67ad269
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/AbstractPage.java
@@ -0,0 +1,576 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.OutputStream;
+import java.util.EventListener;
+import java.util.Locale;
+
+import javax.swing.event.EventListenerList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.event.ChangeObserver;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.event.PageValidateListener;
+import org.apache.tapestry.util.StringSplitter;
+
+/**
+ * Abstract base class implementing the {@link IPage} interface.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship, David Solis
+ * @since 0.2.9
+ *
+ **/
+
+public abstract class AbstractPage extends BaseComponent implements IPage
+{
+ private static final Log LOG = LogFactory.getLog(AbstractPage.class);
+
+ /**
+ * Object to be notified when a observered property changes. Observered
+ * properties are the ones that will be persisted between request cycles.
+ * Unobserved properties are reconstructed.
+ *
+ **/
+
+ private ChangeObserver _changeObserver;
+
+ /**
+ * The {@link IEngine} the page is currently attached to.
+ *
+ **/
+
+ private IEngine _engine;
+
+ /**
+ * The visit object, if any, for the application. Set inside
+ * {@link #attach(IEngine)} and cleared
+ * by {@link #detach()}.
+ *
+ **/
+
+ private Object _visit;
+
+ /**
+ * The qualified name of the page, which may be prefixed by the
+ * namespace.
+ *
+ * @since 2.3
+ *
+ **/
+
+ private String _pageName;
+
+ /**
+ * Set when the page is attached to the engine.
+ *
+ **/
+
+ private IRequestCycle _requestCycle;
+
+ /**
+ * The locale of the page, initially determined from the {@link IEngine engine}.
+ *
+ **/
+
+ private Locale _locale;
+
+ /**
+ * A list of listeners for the page.
+ * @see PageRenderListener
+ * @see PageDetachListener
+ *
+ * @since 1.0.5
+ **/
+
+ private EventListenerList _listenerList;
+
+
+ /**
+ * The output encoding to be used when rendering this page.
+ * This value is cached from the engine.
+ *
+ * @since 3.0
+ **/
+ private String _outputEncoding;
+
+ /**
+ * Standard constructor; invokes {@link #initialize()}
+ * to configure initial values for properties
+ * of the page.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public AbstractPage()
+ {
+ initialize();
+ }
+
+ /**
+ * Implemented in subclasses to provide a particular kind of
+ * response writer (and therefore, a particular kind of
+ * content).
+ *
+ **/
+
+ abstract public IMarkupWriter getResponseWriter(OutputStream out);
+
+ /**
+ * Prepares the page to be returned to the pool.
+ * <ul>
+ * <li>Clears the changeObserved property
+ * <li>Invokes {@link PageDetachListener#pageDetached(PageEvent)} on all listeners
+ * <li>Invokes {@link #initialize()} to clear/reset any properties
+ * <li>Clears the engine, visit and requestCycle properties
+ * </ul>
+ *
+ * <p>Subclasses may override this method, but must invoke this
+ * implementation (usually, last).
+ *
+ **/
+
+ public void detach()
+ {
+ Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID);
+
+ // Do this first,so that any changes to persistent properties do not
+ // cause errors.
+
+ _changeObserver = null;
+
+ firePageDetached();
+
+ initialize();
+
+ _engine = null;
+ _visit = null;
+ _requestCycle = null;
+ }
+
+ /**
+ * Method invoked from the constructor, and from
+ * {@link #detach()} to (re-)initialize properties
+ * of the page. This is most useful when
+ * properties have non-null initial values.
+ *
+ * <p>Subclasses may override this implementation
+ * (which is empty).
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected void initialize()
+ {
+ // Does nothing.
+ }
+
+ public IEngine getEngine()
+ {
+ return _engine;
+ }
+
+ public ChangeObserver getChangeObserver()
+ {
+ return _changeObserver;
+ }
+
+ /**
+ * Returns the name of the page.
+ *
+ **/
+
+ public String getExtendedId()
+ {
+ return _pageName;
+ }
+
+ /**
+ * Pages always return null for idPath.
+ *
+ **/
+
+ public String getIdPath()
+ {
+ return null;
+ }
+
+ /**
+ * Returns the locale for the page, which may be null if the
+ * locale is not known (null corresponds to the "default locale").
+ *
+ **/
+
+ public Locale getLocale()
+ {
+ return _locale;
+ }
+
+ public void setLocale(Locale value)
+ {
+ if (_locale != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractPage.attempt-to-change-locale"));
+
+ _locale = value;
+ }
+
+ public IComponent getNestedComponent(String path)
+ {
+ StringSplitter splitter;
+ IComponent current;
+ String[] elements;
+ int i;
+
+ if (path == null)
+ return this;
+
+ splitter = new StringSplitter('.');
+ current = this;
+
+ elements = splitter.splitToArray(path);
+ for (i = 0; i < elements.length; i++)
+ {
+ current = current.getComponent(elements[i]);
+ }
+
+ return current;
+
+ }
+
+ /**
+ * Called by the {@link IEngine engine} to attach the page
+ * to itself. Does
+ * <em>not</em> change the locale, but since a page is selected
+ * from the {@link org.apache.tapestry.engine.IPageSource} pool based on its
+ * locale matching the engine's locale, they should match
+ * anyway.
+ *
+ **/
+
+ public void attach(IEngine value)
+ {
+ if (_engine != null)
+ LOG.error(this +" attach(" + value + "), but engine = " + _engine);
+
+ _engine = value;
+ }
+
+ /**
+ *
+ * <ul>
+ * <li>Invokes {@link PageRenderListener#pageBeginRender(PageEvent)}
+ * <li>Invokes {@link #beginResponse(IMarkupWriter, IRequestCycle)}
+ * <li>Invokes {@link IRequestCycle#commitPageChanges()} (if not rewinding)
+ * <li>Invokes {@link #render(IMarkupWriter, IRequestCycle)}
+ * <li>Invokes {@link PageRenderListener#pageEndRender(PageEvent)} (this occurs
+ * even if a previous step throws an exception)
+ *
+ **/
+
+ public void renderPage(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ try
+ {
+ firePageBeginRender();
+
+ beginResponse(writer, cycle);
+
+ if (!cycle.isRewinding())
+ cycle.commitPageChanges();
+
+ render(writer, cycle);
+ }
+ finally
+ {
+ firePageEndRender();
+ }
+ }
+
+ public void setChangeObserver(ChangeObserver value)
+ {
+ _changeObserver = value;
+ }
+
+ /** @since 3.0 **/
+
+ public void setPageName(String pageName)
+ {
+ if (_pageName != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractPage.attempt-to-change-name"));
+
+ _pageName = pageName;
+ }
+
+ /**
+ * By default, pages are not protected and this method does nothing.
+ *
+ **/
+
+ public void validate(IRequestCycle cycle)
+ {
+ Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID);
+
+ firePageValidate();
+ }
+
+ /**
+ * Does nothing, subclasses may override as needed.
+ *
+ * @deprecated To be removed in 3.1. Implement
+ * {@link PageRenderListener} instead.
+ *
+ **/
+
+ public void beginResponse(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ }
+
+ public IRequestCycle getRequestCycle()
+ {
+ return _requestCycle;
+ }
+
+ public void setRequestCycle(IRequestCycle value)
+ {
+ _requestCycle = value;
+ }
+
+ /**
+ * Returns the visit object obtained from the engine via
+ * {@link IEngine#getVisit(IRequestCycle)}.
+ *
+ **/
+
+ public Object getVisit()
+ {
+ if (_visit == null)
+ _visit = _engine.getVisit(_requestCycle);
+
+ return _visit;
+ }
+
+ /**
+ * Convienience methods, simply invokes
+ * {@link IEngine#getGlobal()}.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public Object getGlobal()
+ {
+ return _engine.getGlobal();
+ }
+
+ public void addPageDetachListener(PageDetachListener listener)
+ {
+ addListener(PageDetachListener.class, listener);
+ }
+
+ private void addListener(Class listenerClass, EventListener listener)
+ {
+ if (_listenerList == null)
+ _listenerList = new EventListenerList();
+
+ _listenerList.add(listenerClass, listener);
+ }
+
+ /**
+ * @since 2.1-beta-2
+ *
+ **/
+
+ private void removeListener(Class listenerClass, EventListener listener)
+ {
+ if (_listenerList != null)
+ _listenerList.remove(listenerClass, listener);
+ }
+
+ public void addPageRenderListener(PageRenderListener listener)
+ {
+ addListener(PageRenderListener.class, listener);
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ protected void firePageDetached()
+ {
+ if (_listenerList == null)
+ return;
+
+ PageEvent event = null;
+ Object[] listeners = _listenerList.getListenerList();
+
+ for (int i = 0; i < listeners.length; i += 2)
+ {
+ if (listeners[i] == PageDetachListener.class)
+ {
+ PageDetachListener l = (PageDetachListener) listeners[i + 1];
+
+ if (event == null)
+ event = new PageEvent(this, _requestCycle);
+
+ l.pageDetached(event);
+ }
+ }
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ protected void firePageBeginRender()
+ {
+ if (_listenerList == null)
+ return;
+
+ PageEvent event = null;
+ Object[] listeners = _listenerList.getListenerList();
+
+ for (int i = 0; i < listeners.length; i += 2)
+ {
+ if (listeners[i] == PageRenderListener.class)
+ {
+ PageRenderListener l = (PageRenderListener) listeners[i + 1];
+
+ if (event == null)
+ event = new PageEvent(this, _requestCycle);
+
+ l.pageBeginRender(event);
+ }
+ }
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ protected void firePageEndRender()
+ {
+ if (_listenerList == null)
+ return;
+
+ PageEvent event = null;
+ Object[] listeners = _listenerList.getListenerList();
+
+ for (int i = 0; i < listeners.length; i += 2)
+ {
+ if (listeners[i] == PageRenderListener.class)
+ {
+ PageRenderListener l = (PageRenderListener) listeners[i + 1];
+
+ if (event == null)
+ event = new PageEvent(this, _requestCycle);
+
+ l.pageEndRender(event);
+ }
+ }
+ }
+
+ /**
+ * @since 2.1-beta-2
+ *
+ **/
+
+ public void removePageDetachListener(PageDetachListener listener)
+ {
+ removeListener(PageDetachListener.class, listener);
+ }
+
+ public void removePageRenderListener(PageRenderListener listener)
+ {
+ removeListener(PageRenderListener.class, listener);
+ }
+
+ /** @since 2.2 **/
+
+ public void beginPageRender()
+ {
+ firePageBeginRender();
+ }
+
+ /** @since 2.2 **/
+
+ public void endPageRender()
+ {
+ firePageEndRender();
+ }
+
+ /** @since 3.0 **/
+
+ public String getPageName()
+ {
+ return _pageName;
+ }
+
+ public void addPageValidateListener(PageValidateListener listener)
+ {
+ addListener(PageValidateListener.class, listener);
+ }
+
+ public void removePageValidateListener(PageValidateListener listener)
+ {
+ removeListener(PageValidateListener.class, listener);
+ }
+
+ protected void firePageValidate()
+ {
+ if (_listenerList == null)
+ return;
+
+ PageEvent event = null;
+ Object[] listeners = _listenerList.getListenerList();
+
+ for (int i = 0; i < listeners.length; i += 2)
+ {
+ if (listeners[i] == PageValidateListener.class)
+ {
+ PageValidateListener l = (PageValidateListener) listeners[i + 1];
+
+ if (event == null)
+ event = new PageEvent(this, _requestCycle);
+
+ l.pageValidate(event);
+ }
+ }
+ }
+
+ /**
+ * Returns the output encoding to be used when rendering this page.
+ * This value is usually cached from the Engine.
+ *
+ * @since 3.0
+ **/
+ protected String getOutputEncoding()
+ {
+ if (_outputEncoding == null)
+ _outputEncoding = getEngine().getOutputEncoding();
+
+ return _outputEncoding;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/ApplicationRuntimeException.java b/tapestry-framework/src/org/apache/tapestry/ApplicationRuntimeException.java
new file mode 100644
index 0000000..dfcf792
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ApplicationRuntimeException.java
@@ -0,0 +1,83 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * General wrapper for any exception (normal or runtime) that may occur during
+ * runtime processing for the application. This exception is used
+ * when the intent is to communicate a low-level failure to the user or
+ * developer; it is not expected to be caught. The {@link #getCause() rootCause}
+ * property is a <em>nested</em> exception (Tapestry supported this concept
+ * long before the JDK did).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public class ApplicationRuntimeException extends RuntimeException implements ILocatable
+{
+ private Throwable _rootCause;
+ private transient ILocation _location;
+ private transient Object _component;
+
+ public ApplicationRuntimeException(Throwable rootCause)
+ {
+ this(rootCause.getMessage(), rootCause);
+ }
+
+ public ApplicationRuntimeException(String message)
+ {
+ this(message, null, null, null);
+ }
+
+ public ApplicationRuntimeException(String message, Throwable rootCause)
+ {
+ this(message, null, null, rootCause);
+ }
+
+ public ApplicationRuntimeException(
+ String message,
+ Object component,
+ ILocation location,
+ Throwable rootCause)
+ {
+ super(message);
+
+ _rootCause = rootCause;
+ _component = component;
+
+ _location = Tapestry.findLocation(new Object[] { location, rootCause, component });
+ }
+
+ public ApplicationRuntimeException(String message, ILocation location, Throwable rootCause)
+ {
+ this(message, null, location, rootCause);
+ }
+
+ public Throwable getCause()
+ {
+ return _rootCause;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public Object getComponent()
+ {
+ return _component;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/ApplicationServlet.java b/tapestry-framework/src/org/apache/tapestry/ApplicationServlet.java
new file mode 100644
index 0000000..80ce4a0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ApplicationServlet.java
@@ -0,0 +1,807 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.engine.BaseEngine;
+import org.apache.tapestry.engine.IPropertySource;
+import org.apache.tapestry.parse.SpecificationParser;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+import org.apache.tapestry.resource.ContextResourceLocation;
+import org.apache.tapestry.spec.ApplicationSpecification;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.util.DefaultResourceResolver;
+import org.apache.tapestry.util.DelegatingPropertySource;
+import org.apache.tapestry.util.JanitorThread;
+import org.apache.tapestry.util.ServletContextPropertySource;
+import org.apache.tapestry.util.ServletPropertySource;
+import org.apache.tapestry.util.SystemPropertiesPropertySource;
+import org.apache.tapestry.util.exception.ExceptionAnalyzer;
+import org.apache.tapestry.util.pool.Pool;
+import org.apache.tapestry.util.xml.DocumentParseException;
+
+/**
+ * Links a servlet container with a Tapestry application. The servlet has some
+ * responsibilities related to bootstrapping the application (in terms of
+ * logging, reading the {@link ApplicationSpecification specification}, etc.).
+ * It is also responsible for creating or locating the {@link IEngine} and delegating
+ * incoming requests to it.
+ *
+ * <p>The servlet init parameter
+ * <code>org.apache.tapestry.specification-path</code>
+ * should be set to the complete resource path (within the classpath)
+ * to the application specification, i.e.,
+ * <code>/com/foo/bar/MyApp.application</code>.
+ *
+ * <p>In some servlet containers (notably
+ * <a href="www.bea.com"/>WebLogic</a>)
+ * it is necessary to invoke {@link HttpSession#setAttribute(String,Object)}
+ * in order to force a persistent value to be replicated to the other
+ * servers in the cluster. Tapestry applications usually only have a single
+ * persistent value, the {@link IEngine engine}. For persistence to
+ * work in such an environment, the
+ * JVM system property <code>org.apache.tapestry.store-engine</code>
+ * must be set to <code>true</code>. This will force the application
+ * servlet to restore the engine into the {@link HttpSession} at the
+ * end of each request cycle.
+ *
+ * <p>As of release 1.0.1, it is no longer necessary for a {@link HttpSession}
+ * to be created on the first request cycle. Instead, the HttpSession is created
+ * as needed by the {@link IEngine} ... that is, when a visit object is created,
+ * or when persistent page state is required. Otherwise, for sessionless requests,
+ * an {@link IEngine} from a {@link Pool} is used. Additional work must be done
+ * so that the {@link IEngine} can change locale <em>without</em> forcing
+ * the creation of a session; this involves the servlet and the engine storing
+ * locale information in a {@link Cookie}.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class ApplicationServlet extends HttpServlet
+{
+ private static final Log LOG = LogFactory.getLog(ApplicationServlet.class);
+
+ /** @since 2.3 **/
+
+ private static final String APP_SPEC_PATH_PARAM =
+ "org.apache.tapestry.application-specification";
+
+ /**
+ * Name of the cookie written to the client web browser to
+ * identify the locale.
+ *
+ **/
+
+ private static final String LOCALE_COOKIE_NAME = "org.apache.tapestry.locale";
+
+ /**
+ * A {@link Pool} used to store {@link IEngine engine}s that are not currently
+ * in use. The key is on {@link Locale}.
+ *
+ **/
+
+ private Pool _enginePool = new Pool();
+
+ /**
+ * The application specification, which is read once and kept in memory
+ * thereafter.
+ *
+ **/
+
+ private IApplicationSpecification _specification;
+
+ /**
+ * The name under which the {@link IEngine engine} is stored within the
+ * {@link HttpSession}.
+ *
+ **/
+
+ private String _attributeName;
+
+ /**
+ * The resolved class name used to instantiate the engine.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private String _engineClassName;
+
+ /**
+ * Used to search for configuration properties.
+ *
+ *
+ * @since 3.0
+ *
+ **/
+
+ private IPropertySource _propertySource;
+
+ /**
+ * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}.
+ *
+ * @since 1.0.6
+ *
+ **/
+
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ doService(request, response);
+ }
+
+ /**
+ * @since 2.3
+ *
+ **/
+
+ private IResourceResolver _resolver;
+
+ /**
+ * Handles the GET and POST requests. Performs the following:
+ * <ul>
+ * <li>Construct a {@link RequestContext}
+ * <li>Invoke {@link #getEngine(RequestContext)} to get or create the {@link IEngine}
+ * <li>Invoke {@link IEngine#service(RequestContext)} on the application
+ * </ul>
+ **/
+
+ protected void doService(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ RequestContext context = null;
+
+ try
+ {
+
+ // Create a context from the various bits and pieces.
+
+ context = createRequestContext(request, response);
+
+ // The subclass provides the engine.
+
+ IEngine engine = getEngine(context);
+
+ if (engine == null)
+ throw new ServletException(
+ Tapestry.getMessage("ApplicationServlet.could-not-locate-engine"));
+
+ boolean dirty = engine.service(context);
+
+ HttpSession session = context.getSession();
+
+ // When there's an active session, we *may* store it into
+ // the HttpSession and we *will not* store the engine
+ // back into the engine pool.
+
+ if (session != null)
+ {
+ // If the service may have changed the engine and the
+ // special storeEngine flag is on, then re-save the engine
+ // into the session. Otherwise, we only save the engine
+ // into the session when the session is first created (is new).
+
+ try
+ {
+
+ boolean forceStore =
+ engine.isStateful() && (session.getAttribute(_attributeName) == null);
+
+ if (forceStore || dirty)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Storing " + engine + " into session as " + _attributeName);
+
+ session.setAttribute(_attributeName, engine);
+ }
+ }
+ catch (IllegalStateException ex)
+ {
+ // Ignore because the session been's invalidated.
+ // Allow the engine (which has state particular to the client)
+ // to be reclaimed by the garbage collector.
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Session invalidated.");
+ }
+
+ // The engine is stateful and stored in a session. Even if it started
+ // the request cycle in the pool, it doesn't go back.
+
+ return;
+ }
+
+ if (engine.isStateful())
+ {
+ LOG.error(
+ Tapestry.format(
+ "ApplicationServlet.engine-stateful-without-session",
+ engine));
+ return;
+ }
+
+ // No session; the engine contains no state particular to
+ // the client (except for locale). Don't throw it away,
+ // instead save it in a pool for later reuse (by this, or another
+ // client in the same locale).
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Returning " + engine + " to pool.");
+
+ _enginePool.store(engine.getLocale(), engine);
+
+ }
+ catch (ServletException ex)
+ {
+ log("ServletException", ex);
+
+ show(ex);
+
+ // Rethrow it.
+
+ throw ex;
+ }
+ catch (IOException ex)
+ {
+ log("IOException", ex);
+
+ show(ex);
+
+ // Rethrow it.
+
+ throw ex;
+ }
+ finally
+ {
+ if (context != null)
+ context.cleanup();
+ }
+
+ }
+
+ /**
+ * Invoked by {@link #doService(HttpServletRequest, HttpServletResponse)} to create
+ * the {@link RequestContext} for this request cycle. Some applications may need to
+ * replace the default RequestContext with a subclass for particular behavior.
+ *
+ * @since 2.3
+ *
+ **/
+
+ protected RequestContext createRequestContext(
+ HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException
+ {
+ return new RequestContext(this, request, response);
+ }
+
+ protected void show(Exception ex)
+ {
+ System.err.println("\n\n**********************************************************\n\n");
+
+ new ExceptionAnalyzer().reportException(ex, System.err);
+
+ System.err.println("\n**********************************************************\n");
+
+ }
+
+ /**
+ * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}.
+ *
+ *
+ **/
+
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws IOException, ServletException
+ {
+ doService(request, response);
+ }
+
+ /**
+ * Returns the application specification, which is read
+ * by the {@link #init(ServletConfig)} method.
+ *
+ **/
+
+ public IApplicationSpecification getApplicationSpecification()
+ {
+ return _specification;
+ }
+
+ /**
+ * Retrieves the {@link IEngine engine} that will process this
+ * request. This comes from one of the following places:
+ * <ul>
+ * <li>The {@link HttpSession}, if the there is one.
+ * <li>From the pool of available engines
+ * <li>Freshly created
+ * </ul>
+ *
+ **/
+
+ protected IEngine getEngine(RequestContext context) throws ServletException
+ {
+ IEngine engine = null;
+ HttpSession session = context.getSession();
+
+ // If there's a session, then find the engine within it.
+
+ if (session != null)
+ {
+ engine = (IEngine) session.getAttribute(_attributeName);
+ if (engine != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Retrieved " + engine + " from session " + session.getId() + ".");
+
+ return engine;
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Session exists, but doesn't contain an engine.");
+ }
+
+ Locale locale = getLocaleFromRequest(context);
+
+ engine = (IEngine) _enginePool.retrieve(locale);
+
+ if (engine == null)
+ {
+ engine = createEngine(context);
+ engine.setLocale(locale);
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Using pooled engine " + engine + " (from locale " + locale + ").");
+ }
+
+ return engine;
+ }
+
+ /**
+ * Determines the {@link Locale} for the incoming request.
+ * This is determined from the locale cookie or, if not set,
+ * from the request itself. This may return null
+ * if no locale is determined.
+ *
+ **/
+
+ protected Locale getLocaleFromRequest(RequestContext context) throws ServletException
+ {
+ Cookie cookie = context.getCookie(LOCALE_COOKIE_NAME);
+
+ if (cookie != null)
+ return Tapestry.getLocale(cookie.getValue());
+
+ return context.getRequest().getLocale();
+ }
+
+ /**
+ * Reads the application specification when the servlet is
+ * first initialized. All {@link IEngine engine instances}
+ * will have access to the specification via the servlet.
+ *
+ * @see #getApplicationSpecification()
+ * @see #constructApplicationSpecification()
+ * @see #createResourceResolver()
+ *
+ **/
+
+ public void init(ServletConfig config) throws ServletException
+ {
+ super.init(config);
+
+ _resolver = createResourceResolver();
+
+ _specification = constructApplicationSpecification();
+
+ _attributeName = "org.apache.tapestry.engine:" + config.getServletName();
+ }
+
+ /**
+ * Invoked from {@link #init(ServletConfig)} to create a resource resolver
+ * for the servlet (which will utlimately be shared and used through the
+ * application).
+ *
+ * <p>This implementation constructs a {@link DefaultResourceResolver}, subclasses
+ * may provide a different implementation.
+ *
+ * @see #getResourceResolver()
+ * @since 2.3
+ *
+ **/
+
+ protected IResourceResolver createResourceResolver() throws ServletException
+ {
+ return new DefaultResourceResolver();
+ }
+
+ /**
+ * Invoked from {@link #init(ServletConfig)} to read and construct
+ * the {@link ApplicationSpecification} for this servlet.
+ * Invokes {@link #getApplicationSpecificationPath()}, opens
+ * the resource as a stream, then invokes
+ * {@link #parseApplicationSpecification(IResourceLocation)}.
+ *
+ * <p>
+ * This method exists to be overriden in
+ * applications where the application specification cannot be
+ * loaded from the classpath. Alternately, a subclass
+ * could override this method, invoke this implementation,
+ * and then add additional data to it (for example, an application
+ * where some of the pages are defined in an external source
+ * such as a database).
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected IApplicationSpecification constructApplicationSpecification() throws ServletException
+ {
+ IResourceLocation specLocation = getApplicationSpecificationLocation();
+
+ if (specLocation == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(Tapestry.getMessage("ApplicationServlet.no-application-specification"));
+
+ return constructStandinSpecification();
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Loading application specification from " + specLocation);
+
+ return parseApplicationSpecification(specLocation);
+ }
+
+ /**
+ * Gets the location of the application specification, if there is one.
+ *
+ * <ul>
+ * <li>Invokes {@link #getApplicationSpecificationPath()} to get the
+ * location of the application specification on the classpath.
+ * <li>If that return null, search for the application specification:
+ * <ul>
+ * <li><i>name</i>.application in /WEB-INF/<i>name</i>/
+ * <li><i>name</i>.application in /WEB-INF/
+ * </ul>
+ * </ul>
+ *
+ * <p>Returns the location of the application specification, or null
+ * if not found.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected IResourceLocation getApplicationSpecificationLocation() throws ServletException
+ {
+ String path = getApplicationSpecificationPath();
+
+ if (path != null)
+ return new ClasspathResourceLocation(_resolver, path);
+
+ ServletContext context = getServletContext();
+ String servletName = getServletName();
+ String expectedName = servletName + ".application";
+
+ IResourceLocation webInfLocation = new ContextResourceLocation(context, "/WEB-INF/");
+ IResourceLocation webInfAppLocation = webInfLocation.getRelativeLocation(servletName + "/");
+
+ IResourceLocation result = check(webInfAppLocation, expectedName);
+ if (result != null)
+ return result;
+
+ return check(webInfLocation, expectedName);
+ }
+
+ /**
+ * Checks for the application specification relative to the specified
+ * location.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private IResourceLocation check(IResourceLocation location, String name)
+ {
+ IResourceLocation result = location.getRelativeLocation(name);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Checking for existence of " + result);
+
+ if (result.getResourceURL() != null)
+ {
+ LOG.debug("Found.");
+ return result;
+ }
+
+ return null;
+ }
+
+ /**
+ * Invoked from {@link #constructApplicationSpecification()} when
+ * the application doesn't have an explicit specification. A
+ * simple specification is constructed and returned. This is useful
+ * for minimal applications and prototypes.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected IApplicationSpecification constructStandinSpecification()
+ {
+ ApplicationSpecification result = new ApplicationSpecification();
+
+ IResourceLocation virtualLocation =
+ new ContextResourceLocation(getServletContext(), "/WEB-INF/");
+
+ result.setSpecificationLocation(virtualLocation);
+
+ result.setName(getServletName());
+ result.setResourceResolver(_resolver);
+
+ return result;
+ }
+
+ /**
+ * Invoked from {@link #constructApplicationSpecification()} to
+ * actually parse the stream (with content provided from the path)
+ * and convert it into an {@link ApplicationSpecification}.
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected IApplicationSpecification parseApplicationSpecification(IResourceLocation location)
+ throws ServletException
+ {
+ try
+ {
+ SpecificationParser parser = new SpecificationParser(_resolver);
+
+ return parser.parseApplicationSpecification(location);
+ }
+ catch (DocumentParseException ex)
+ {
+ show(ex);
+
+ throw new ServletException(
+ Tapestry.format("ApplicationServlet.could-not-parse-spec", location),
+ ex);
+ }
+ }
+
+ /**
+ * Closes the stream, ignoring any exceptions.
+ *
+ **/
+
+ protected void close(InputStream stream)
+ {
+ try
+ {
+ if (stream != null)
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore it.
+ }
+ }
+
+ /**
+ * Reads the servlet init parameter
+ * <code>org.apache.tapestry.application-specification</code>, which
+ * is the location, on the classpath, of the application specification.
+ *
+ * <p>
+ * If the parameter is not set, this method returns null, and a search
+ * for the application specification within the servlet context
+ * will begin.
+ *
+ * @see #getApplicationSpecificationLocation()
+ *
+ **/
+
+ protected String getApplicationSpecificationPath() throws ServletException
+ {
+ return getInitParameter(APP_SPEC_PATH_PARAM);
+ }
+
+ /**
+ * Invoked by {@link #getEngine(RequestContext)} to create
+ * the {@link IEngine} instance specific to the
+ * application, if not already in the
+ * {@link HttpSession}.
+ *
+ * <p>The {@link IEngine} instance returned is stored into the
+ * {@link HttpSession}.
+ *
+ * @see #getEngineClassName()
+ *
+ **/
+
+ protected IEngine createEngine(RequestContext context) throws ServletException
+ {
+ try
+ {
+ String className = getEngineClassName();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating engine from class " + className);
+
+ Class engineClass = getResourceResolver().findClass(className);
+
+ IEngine result = (IEngine) engineClass.newInstance();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Created engine " + result);
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ throw new ServletException(ex);
+ }
+ }
+
+ /**
+ *
+ * Returns the name of the class to use when instantiating
+ * an engine instance for this application.
+ * If the application specification
+ * provides a value, this is returned. Otherwise, a search for
+ * the configuration property
+ * <code>org.apache.tapestry.engine-class</code>
+ * occurs (see {@link #searchConfiguration(String)}).
+ *
+ * <p>If the search is still unsuccessful, then
+ * {@link org.apache.tapestry.engine.BaseEngine} is used.
+ *
+ **/
+
+ protected String getEngineClassName()
+ {
+ if (_engineClassName != null)
+ return _engineClassName;
+
+ _engineClassName = _specification.getEngineClassName();
+
+ if (_engineClassName == null)
+ _engineClassName = searchConfiguration("org.apache.tapestry.engine-class");
+
+ if (_engineClassName == null)
+ _engineClassName = BaseEngine.class.getName();
+
+ return _engineClassName;
+ }
+
+ /**
+ * Searches for a configuration property in:
+ * <ul>
+ * <li>The servlet's initial parameters
+ * <li>The servlet context's initial parameters
+ * <li>JVM system properties
+ * </ul>
+ *
+ * @see #createPropertySource()
+ * @since 3.0
+ *
+ **/
+
+ protected String searchConfiguration(String propertyName)
+ {
+ if (_propertySource == null)
+ _propertySource = createPropertySource();
+
+ return _propertySource.getPropertyValue(propertyName);
+ }
+
+ /**
+ * Creates an instance of {@link IPropertySource} used for
+ * searching of configuration values. Subclasses may override
+ * this method if they want to change the normal locations
+ * that properties are searched for within.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected IPropertySource createPropertySource()
+ {
+ DelegatingPropertySource result = new DelegatingPropertySource();
+
+ result.addSource(new ServletPropertySource(getServletConfig()));
+ result.addSource(new ServletContextPropertySource(getServletContext()));
+ result.addSource(SystemPropertiesPropertySource.getInstance());
+
+ return result;
+ }
+
+ /**
+ * Invoked from the {@link IEngine engine}, just prior to starting to
+ * render a response, when the locale has changed. The servlet writes a
+ * {@link Cookie} so that, on subsequent request cycles, an engine localized
+ * to the selected locale is chosen.
+ *
+ * <p>At this time, the cookie is <em>not</em> persistent. That may
+ * change in subsequent releases.
+ *
+ * @since 1.0.1
+ **/
+
+ public void writeLocaleCookie(Locale locale, IEngine engine, RequestContext cycle)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Writing locale cookie " + locale);
+
+ Cookie cookie = new Cookie(LOCALE_COOKIE_NAME, locale.toString());
+ cookie.setPath(engine.getServletPath());
+
+ cycle.addCookie(cookie);
+ }
+
+ /**
+ * Returns a resource resolver that can access classes and resources related
+ * to the current web application context. Relies on
+ * {@link java.lang.Thread#getContextClassLoader()}, which is set by
+ * most modern servlet containers.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ /**
+ * Ensures that shared janitor thread is terminated.
+ * @see javax.servlet.Servlet#destroy()
+ * @since 3.0.3
+ */
+ public void destroy()
+ {
+ try
+ {
+ JanitorThread.getSharedJanitorThread().interrupt();
+ }
+ finally
+ {
+ super.destroy();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/BaseComponent.java b/tapestry-framework/src/org/apache/tapestry/BaseComponent.java
new file mode 100644
index 0000000..b9f06f4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/BaseComponent.java
@@ -0,0 +1,139 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.engine.IPageSource;
+import org.apache.tapestry.engine.ITemplateSource;
+import org.apache.tapestry.parse.ComponentTemplate;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Base implementation for most components that use an HTML template.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class BaseComponent extends AbstractComponent
+{
+ private static final Log LOG = LogFactory.getLog(BaseComponent.class);
+
+ private static final int OUTER_INIT_SIZE = 5;
+
+ private IRender[] _outer;
+ private int _outerCount = 0;
+
+ /**
+ * Adds an element as an outer element for the receiver. Outer
+ * elements are elements that should be directly rendered by the
+ * receiver's <code>render()</code> method. That is, they are
+ * top-level elements on the HTML template.
+ *
+ *
+ **/
+
+ protected void addOuter(IRender element)
+ {
+ if (_outer == null)
+ {
+ _outer = new IRender[OUTER_INIT_SIZE];
+ _outer[0] = element;
+
+ _outerCount = 1;
+ return;
+ }
+
+ // No more room? Make the array bigger.
+
+ if (_outerCount == _outer.length)
+ {
+ IRender[] newOuter;
+
+ newOuter = new IRender[_outer.length * 2];
+
+ System.arraycopy(_outer, 0, newOuter, 0, _outerCount);
+
+ _outer = newOuter;
+ }
+
+ _outer[_outerCount++] = element;
+ }
+
+ /**
+ *
+ * Reads the receiver's template and figures out which elements wrap which
+ * other elements.
+ *
+ * <P>This is coded as a single, big, ugly method for efficiency.
+ *
+ **/
+
+ private void readTemplate(IRequestCycle cycle, IPageLoader loader)
+ {
+ IPageSource pageSource = loader.getEngine().getPageSource();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(this +" reading template");
+
+ ITemplateSource source = loader.getTemplateSource();
+ ComponentTemplate componentTemplate = source.getTemplate(cycle, this);
+
+ // Most of the work is done inside the loader class.
+ // We instantiate it just to invoke process() on it.
+
+ new BaseComponentTemplateLoader(cycle, loader, this, componentTemplate, pageSource).process();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(this +" finished reading template");
+ }
+
+ /**
+ * Renders the top level components contained by the receiver.
+ *
+ * @since 2.0.3
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Begin render " + getExtendedId());
+
+ for (int i = 0; i < _outerCount; i++)
+ _outer[i].render(writer, cycle);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("End render " + getExtendedId());
+ }
+
+ /**
+ * Loads the template for the component, then invokes
+ * {@link AbstractComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}.
+ * Subclasses must invoke this method first,
+ * before adding any additional behavior, though its usually
+ * simpler to override {@link #finishLoad()} instead.
+ *
+ **/
+
+ public void finishLoad(IRequestCycle cycle, IPageLoader loader, IComponentSpecification specification)
+ {
+ readTemplate(cycle, loader);
+
+ super.finishLoad(cycle, loader, specification);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/BaseComponentTemplateLoader.java b/tapestry-framework/src/org/apache/tapestry/BaseComponentTemplateLoader.java
new file mode 100644
index 0000000..8150771
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/BaseComponentTemplateLoader.java
@@ -0,0 +1,660 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.binding.ExpressionBinding;
+import org.apache.tapestry.binding.StaticBinding;
+import org.apache.tapestry.binding.StringBinding;
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.engine.IPageSource;
+import org.apache.tapestry.engine.ITemplateSource;
+import org.apache.tapestry.parse.AttributeType;
+import org.apache.tapestry.parse.CloseToken;
+import org.apache.tapestry.parse.ComponentTemplate;
+import org.apache.tapestry.parse.LocalizationToken;
+import org.apache.tapestry.parse.OpenToken;
+import org.apache.tapestry.parse.TemplateAttribute;
+import org.apache.tapestry.parse.TemplateToken;
+import org.apache.tapestry.parse.TextToken;
+import org.apache.tapestry.parse.TokenType;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IContainedComponent;
+
+/**
+ * Utility class instantiated by {@link org.apache.tapestry.BaseComponent} to
+ * process the component's {@link org.apache.tapestry.parse.ComponentTemplate template},
+ * which involves working through the nested structure of the template and hooking
+ * the various static template blocks and components together using
+ * {@link IComponent#addBody(IRender)} and
+ * {@link org.apache.tapestry.BaseComponent#addOuter(IRender)}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+public class BaseComponentTemplateLoader
+{
+ private static final Log LOG = LogFactory.getLog(BaseComponentTemplateLoader.class);
+
+ private IPageLoader _pageLoader;
+ private IRequestCycle _requestCycle;
+ private BaseComponent _loadComponent;
+ private IPageSource _pageSource;
+ private ComponentTemplate _template;
+ private IComponent[] _stack;
+ private int _stackx = 0;
+ private IComponent _activeComponent = null;
+ private Set _seenIds = new HashSet();
+
+ /**
+ * A class used with invisible localizations. Constructed
+ * from a {@link TextToken}.
+ */
+
+ private static class LocalizedStringRender implements IRender
+ {
+ private IComponent _component;
+ private String _key;
+ private Map _attributes;
+ private String _value;
+ private boolean _raw;
+
+ private LocalizedStringRender(IComponent component, LocalizationToken token)
+ {
+ _component = component;
+ _key = token.getKey();
+ _raw = token.isRaw();
+ _attributes = token.getAttributes();
+ }
+
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.isRewinding())
+ return;
+
+ if (_attributes != null)
+ {
+ writer.begin("span");
+
+ Iterator i = _attributes.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ String attributeName = (String) entry.getKey();
+ String attributeValue = (String) entry.getValue();
+
+ writer.attribute(attributeName, attributeValue);
+ }
+ }
+
+ if (_value == null)
+ _value = _component.getMessage(_key);
+
+ if (_raw)
+ writer.printRaw(_value);
+ else
+ writer.print(_value);
+
+ if (_attributes != null)
+ writer.end();
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("component", _component);
+ builder.append("key", _key);
+ builder.append("raw", _raw);
+ builder.append("attributes", _attributes);
+
+ return builder.toString();
+ }
+
+ }
+
+ public BaseComponentTemplateLoader(
+ IRequestCycle requestCycle,
+ IPageLoader pageLoader,
+ BaseComponent loadComponent,
+ ComponentTemplate template,
+ IPageSource pageSource)
+ {
+ _requestCycle = requestCycle;
+ _pageLoader = pageLoader;
+ _loadComponent = loadComponent;
+ _template = template;
+ _pageSource = pageSource;
+
+ _stack = new IComponent[template.getTokenCount()];
+ }
+
+ public void process()
+ {
+ int count = _template.getTokenCount();
+
+ for (int i = 0; i < count; i++)
+ {
+ TemplateToken token = _template.getToken(i);
+
+ TokenType type = token.getType();
+
+ if (type == TokenType.TEXT)
+ {
+ process((TextToken) token);
+ continue;
+ }
+
+ if (type == TokenType.OPEN)
+ {
+ process((OpenToken) token);
+ continue;
+ }
+
+ if (type == TokenType.CLOSE)
+ {
+ process((CloseToken) token);
+ continue;
+ }
+
+ if (type == TokenType.LOCALIZATION)
+ {
+ process((LocalizationToken) token);
+ continue;
+ }
+ }
+
+ // This is also pretty much unreachable, and the message is kind of out
+ // of date, too.
+
+ if (_stackx != 0)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("BaseComponent.unbalance-open-tags"),
+ _loadComponent,
+ null,
+ null);
+
+ checkAllComponentsReferenced();
+ }
+
+ /**
+ * Adds the token (which implements {@link IRender})
+ * to the active component (using {@link IComponent#addBody(IRender)}),
+ * or to this component {@link BaseComponent#addOuter(IRender)}.
+ *
+ * <p>
+ * A check is made that the active component allows a body.
+ */
+
+ private void process(TextToken token)
+ {
+ if (_activeComponent == null)
+ {
+ _loadComponent.addOuter(token);
+ return;
+ }
+
+ if (!_activeComponent.getSpecification().getAllowBody())
+ throw createBodylessComponentException(_activeComponent);
+
+ _activeComponent.addBody(token);
+ }
+
+ private void process(OpenToken token)
+ {
+ String id = token.getId();
+ IComponent component = null;
+ String componentType = token.getComponentType();
+
+ if (componentType == null)
+ component = getEmbeddedComponent(id);
+ else
+ {
+ checkForDuplicateId(id, token.getLocation());
+
+ component = createImplicitComponent(id, componentType, token.getLocation());
+ }
+
+ // Make sure the template contains each component only once.
+
+ if (_seenIds.contains(id))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.multiple-component-references",
+ _loadComponent.getExtendedId(),
+ id),
+ _loadComponent,
+ token.getLocation(),
+ null);
+
+ _seenIds.add(id);
+
+ if (_activeComponent == null)
+ _loadComponent.addOuter(component);
+ else
+ {
+ // Note: this code may no longer be reachable (because the
+ // template parser does this check first).
+
+ if (!_activeComponent.getSpecification().getAllowBody())
+ throw createBodylessComponentException(_activeComponent);
+
+ _activeComponent.addBody(component);
+ }
+
+ addTemplateBindings(component, token);
+
+ _stack[_stackx++] = _activeComponent;
+
+ _activeComponent = component;
+ }
+
+ private void checkForDuplicateId(String id, ILocation location)
+ {
+ if (id == null)
+ return;
+
+ IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
+
+ if (cc != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponentTemplateLoader.dupe-component-id",
+ id,
+ location,
+ cc.getLocation()),
+ _loadComponent,
+ location,
+ null);
+ }
+
+ private IComponent createImplicitComponent(String id, String componentType, ILocation location)
+ {
+ IComponent result =
+ _pageLoader.createImplicitComponent(
+ _requestCycle,
+ _loadComponent,
+ id,
+ componentType,
+ location);
+
+ return result;
+ }
+
+ private IComponent getEmbeddedComponent(String id)
+ {
+ return _loadComponent.getComponent(id);
+ }
+
+ private void process(CloseToken token)
+ {
+ // Again, this is pretty much impossible to reach because
+ // the template parser does a great job.
+
+ if (_stackx <= 0)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("BaseComponent.unbalanced-close-tags"),
+ _loadComponent,
+ token.getLocation(),
+ null);
+
+ // Null and forget the top element on the stack.
+
+ _stack[_stackx--] = null;
+
+ _activeComponent = _stack[_stackx];
+ }
+
+ private void process(LocalizationToken token)
+ {
+ IRender render = new LocalizedStringRender(_loadComponent, token);
+
+ if (_activeComponent == null)
+ _loadComponent.addOuter(render);
+ else
+ _activeComponent.addBody(render);
+ }
+
+ /**
+ * Adds bindings based on attributes in the template.
+ */
+
+ private void addTemplateBindings(IComponent component, OpenToken token)
+ {
+ IComponentSpecification spec = component.getSpecification();
+
+ Map attributes = token.getAttributesMap();
+
+ if (attributes != null)
+ {
+ Iterator i = attributes.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String name = (String) entry.getKey();
+ TemplateAttribute attribute = (TemplateAttribute) entry.getValue();
+ AttributeType type = attribute.getType();
+
+ if (type == AttributeType.OGNL_EXPRESSION)
+ {
+ addExpressionBinding(
+ component,
+ spec,
+ name,
+ attribute.getValue(),
+ token.getLocation());
+ continue;
+ }
+
+ if (type == AttributeType.LOCALIZATION_KEY)
+ {
+ addStringBinding(
+ component,
+ spec,
+ name,
+ attribute.getValue(),
+ token.getLocation());
+ continue;
+ }
+
+ if (type == AttributeType.LITERAL)
+ addStaticBinding(
+ component,
+ spec,
+ name,
+ attribute.getValue(),
+ token.getLocation());
+ }
+ }
+
+ // if the component defines a templateTag parameter and
+ // there is no established binding for that parameter,
+ // add a static binding carrying the template tag
+ if (spec.getParameter(ITemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
+ && component.getBinding(ITemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
+ {
+ addStaticBinding(
+ component,
+ spec,
+ ITemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
+ token.getTag(),
+ token.getLocation());
+ }
+
+ }
+
+ /**
+ * Adds an expression binding, checking for errors related
+ * to reserved and informal parameters.
+ *
+ * <p>It is an error to specify expression
+ * bindings in both the specification
+ * and the template.
+ */
+
+ private void addExpressionBinding(
+ IComponent component,
+ IComponentSpecification spec,
+ String name,
+ String expression,
+ ILocation location)
+ {
+
+ // If matches a formal parameter name, allow it to be set
+ // unless there's already a binding.
+
+ boolean isFormal = (spec.getParameter(name) != null);
+
+ if (isFormal)
+ {
+ if (component.getBinding(name) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.dupe-template-expression",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+ }
+ else
+ {
+ if (!spec.getAllowInformalParameters())
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.template-expression-for-informal-parameter",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+
+ // If the name is reserved (matches a formal parameter
+ // or reserved name, caselessly), then skip it.
+
+ if (spec.isReservedParameterName(name))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.template-expression-for-reserved-parameter",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+ }
+
+ IBinding binding =
+ new ExpressionBinding(
+ _pageSource.getResourceResolver(),
+ _loadComponent,
+ expression,
+ location);
+
+ component.setBinding(name, binding);
+ }
+
+ /**
+ * Adds an expression binding, checking for errors related
+ * to reserved and informal parameters.
+ *
+ * <p>It is an error to specify expression
+ * bindings in both the specification
+ * and the template.
+ */
+
+ private void addStringBinding(
+ IComponent component,
+ IComponentSpecification spec,
+ String name,
+ String localizationKey,
+ ILocation location)
+ {
+ // If matches a formal parameter name, allow it to be set
+ // unless there's already a binding.
+
+ boolean isFormal = (spec.getParameter(name) != null);
+
+ if (isFormal)
+ {
+ if (component.getBinding(name) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.dupe-string",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+ }
+ else
+ {
+ if (!spec.getAllowInformalParameters())
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.template-expression-for-informal-parameter",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+
+ // If the name is reserved (matches a formal parameter
+ // or reserved name, caselessly), then skip it.
+
+ if (spec.isReservedParameterName(name))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BaseComponent.template-expression-for-reserved-parameter",
+ name,
+ component.getExtendedId(),
+ _loadComponent.getExtendedId()),
+ component,
+ location,
+ null);
+ }
+
+ IBinding binding = new StringBinding(_loadComponent, localizationKey, location);
+
+ component.setBinding(name, binding);
+ }
+
+ /**
+ * Adds a static binding, checking for errors related
+ * to reserved and informal parameters.
+ *
+ * <p>
+ * Static bindings that conflict with bindings in the
+ * specification are quietly ignored.
+ */
+
+ private void addStaticBinding(
+ IComponent component,
+ IComponentSpecification spec,
+ String name,
+ String staticValue,
+ ILocation location)
+ {
+
+ if (component.getBinding(name) != null)
+ return;
+
+ // If matches a formal parameter name, allow it to be set
+ // unless there's already a binding.
+
+ boolean isFormal = (spec.getParameter(name) != null);
+
+ if (!isFormal)
+ {
+ // Skip informal parameters if the component doesn't allow them.
+
+ if (!spec.getAllowInformalParameters())
+ return;
+
+ // If the name is reserved (matches a formal parameter
+ // or reserved name, caselessly), then skip it.
+
+ if (spec.isReservedParameterName(name))
+ return;
+ }
+
+ IBinding binding = new StaticBinding(staticValue, location);
+
+ component.setBinding(name, binding);
+ }
+
+ private void checkAllComponentsReferenced()
+ {
+ // First, contruct a modifiable copy of the ids of all expected components
+ // (that is, components declared in the specification).
+
+ Map components = _loadComponent.getComponents();
+
+ Set ids = components.keySet();
+
+ // If the seen ids ... ids referenced in the template, matches
+ // all the ids in the specification then we're fine.
+
+ if (_seenIds.containsAll(ids))
+ return;
+
+ // Create a modifiable copy. Remove the ids that are referenced in
+ // the template. The remainder are worthy of note.
+
+ ids = new HashSet(ids);
+ ids.removeAll(_seenIds);
+
+ int count = ids.size();
+
+ String key =
+ (count == 1)
+ ? "BaseComponent.missing-component-spec-single"
+ : "BaseComponent.missing-component-spec-multi";
+
+ StringBuffer buffer =
+ new StringBuffer(Tapestry.format(key, _loadComponent.getExtendedId()));
+
+ Iterator i = ids.iterator();
+ int j = 1;
+
+ while (i.hasNext())
+ {
+ if (j == 1)
+ buffer.append(' ');
+ else
+ if (j == count)
+ {
+ buffer.append(' ');
+ buffer.append(Tapestry.getMessage("BaseComponent.and"));
+ buffer.append(' ');
+ }
+ else
+ buffer.append(", ");
+
+ buffer.append(i.next());
+
+ j++;
+ }
+
+ buffer.append('.');
+
+ LOG.error(buffer.toString());
+ }
+
+ protected ApplicationRuntimeException createBodylessComponentException(IComponent component)
+ {
+ return new ApplicationRuntimeException(
+ Tapestry.getMessage("BaseComponentTemplateLoader.bodyless-component"),
+ component,
+ null,
+ null);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/BindingException.java b/tapestry-framework/src/org/apache/tapestry/BindingException.java
new file mode 100644
index 0000000..094b274
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/BindingException.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A general exception describing an {@link IBinding}
+ * and an {@link IComponent}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class BindingException extends ApplicationRuntimeException
+{
+ private transient IBinding _binding;
+
+ public BindingException(String message, IBinding binding)
+ {
+ this(message, binding, null);
+ }
+
+ public BindingException(String message, IBinding binding, Throwable rootCause)
+ {
+ this(message, null, null, binding, rootCause);
+ }
+
+
+ public BindingException(
+ String message,
+ Object component,
+ ILocation location,
+ IBinding binding,
+ Throwable rootCause)
+ {
+ super(
+ message,
+ component,
+ Tapestry.findLocation(new Object[] { location, binding, component }),
+ rootCause);
+
+ _binding = binding;
+ }
+
+ public IBinding getBinding()
+ {
+ return _binding;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/ConfigurationDefaults.properties b/tapestry-framework/src/org/apache/tapestry/ConfigurationDefaults.properties
new file mode 100644
index 0000000..8451a55
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ConfigurationDefaults.properties
@@ -0,0 +1,4 @@
+# $Id$
+
+org.apache.tapestry.default-script-language=jython
+org.apache.tapestry.visit-class=java.util.HashMap
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/Framework.library b/tapestry-framework/src/org/apache/tapestry/Framework.library
new file mode 100644
index 0000000..b8f4d11
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/Framework.library
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE library-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<!--
+
+ This is a kind of "boostrap" library; when an unadorned page name or alias (a name or
+ alias without a namespace prefix) cannot be located in the appropriate application or
+ library specification, the framework specification is checked. This allows
+ the application or library to override framework's versions of a component.
+
+-->
+
+<library-specification>
+
+ <component-type type="ActionLink" specification-path="link/ActionLink.jwc"/>
+ <component-type type="Any" specification-path="components/Any.jwc"/>
+ <component-type type="Block" specification-path="components/Block.jwc"/>
+ <component-type type="Body" specification-path="html/Body.jwc"/>
+ <component-type type="Button" specification-path="form/Button.jwc"/>
+ <component-type type="Checkbox" specification-path="form/Checkbox.jwc"/>
+ <component-type type="Conditional" specification-path="components/Conditional.jwc"/>
+ <component-type type="DatePicker" specification-path="form/DatePicker.jwc"/>
+ <component-type type="Delegator" specification-path="components/Delegator.jwc"/>
+ <component-type type="DirectLink" specification-path="link/DirectLink.jwc"/>
+ <component-type type="ExternalLink" specification-path="link/ExternalLink.jwc"/>
+ <component-type type="FieldLabel" specification-path="valid/FieldLabel.jwc"/>
+ <component-type type="Foreach" specification-path="components/Foreach.jwc"/>
+ <component-type type="Frame" specification-path="html/Frame.jwc"/>
+ <component-type type="ExceptionDisplay" specification-path="html/ExceptionDisplay.jwc"/>
+ <component-type type="Form" specification-path="form/Form.jwc"/>
+ <component-type type="GenericLink" specification-path="link/GenericLink.jwc"/>
+ <component-type type="Hidden" specification-path="form/Hidden.jwc"/>
+ <component-type type="Image" specification-path="html/Image.jwc"/>
+ <component-type type="ImageSubmit" specification-path="form/ImageSubmit.jwc"/>
+ <component-type type="Insert" specification-path="components/Insert.jwc"/>
+ <component-type type="InsertText" specification-path="html/InsertText.jwc"/>
+ <component-type type="LinkSubmit" specification-path="form/LinkSubmit.jwc"/>
+ <component-type type="ListEdit" specification-path="form/ListEdit.jwc"/>
+ <component-type type="Option" specification-path="form/Option.jwc"/>
+ <component-type type="PageLink" specification-path="link/PageLink.jwc"/>
+ <component-type type="PropertySelection" specification-path="form/PropertySelection.jwc"/>
+ <component-type type="Radio" specification-path="form/Radio.jwc"/>
+ <component-type type="RadioGroup" specification-path="form/RadioGroup.jwc"/>
+ <component-type type="RenderBlock" specification-path="components/RenderBlock.jwc"/>
+ <component-type type="RenderBody" specification-path="components/RenderBody.jwc"/>
+ <component-type type="Rollover" specification-path="html/Rollover.jwc"/>
+ <component-type type="Select" specification-path="form/Select.jwc"/>
+ <component-type type="ServiceLink" specification-path="link/ServiceLink.jwc"/>
+ <component-type type="Script" specification-path="html/Script.jwc"/>
+ <component-type type="Shell" specification-path="html/Shell.jwc"/>
+ <component-type type="Submit" specification-path="form/Submit.jwc"/>
+ <component-type type="TextArea" specification-path="form/TextArea.jwc"/>
+ <component-type type="TextField" specification-path="form/TextField.jwc"/>
+ <component-type type="Upload" specification-path="form/Upload.jwc"/>
+ <component-type type="ValidField" specification-path="valid/ValidField.jwc"/>
+
+ <page name="StaleLink" specification-path="pages/StaleLink.page"/>
+ <page name="StaleSession" specification-path="pages/StaleSession.page"/>
+ <page name="Exception" specification-path="pages/Exception.page"/>
+
+ <page name="WMLException" specification-path="wml/pages/WMLException.page"/>
+ <page name="WMLStaleLink" specification-path="wml/pages/WMLStaleLink.page"/>
+ <page name="WMLStaleSession" specification-path="wml/pages/WMLStaleSession.page"/>
+
+ <!-- These may be overriden but generally shouldn't be, except perhaps for the home service. -->
+
+ <service name="home" class="org.apache.tapestry.engine.HomeService"/>
+ <service name="action" class="org.apache.tapestry.engine.ActionService"/>
+ <service name="direct" class="org.apache.tapestry.engine.DirectService"/>
+ <service name="page" class="org.apache.tapestry.engine.PageService"/>
+ <service name="reset" class="org.apache.tapestry.engine.ResetService"/>
+ <service name="restart" class="org.apache.tapestry.engine.RestartService"/>
+ <service name="asset" class="org.apache.tapestry.asset.AssetService"/>
+ <service name="external" class="org.apache.tapestry.engine.ExternalService"/>
+
+ <!-- Used to support the JSP tags. -->
+
+ <service name="tagsupport" class="org.apache.tapestry.engine.TagSupportService"/>
+
+</library-specification>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IAction.java b/tapestry-framework/src/org/apache/tapestry/IAction.java
new file mode 100644
index 0000000..1c089aa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IAction.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A particular type of component usuable with the
+ * action service.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.1
+ */
+
+public interface IAction extends IComponent
+{
+ /**
+ * Returns true if the component requires
+ * an existing, not new, {@link javax.servlet.http.HttpSession}
+ * to operate. Components who are not dependant on page state
+ * (or the visit object) are non-stateful and can return false.
+ *
+ **/
+
+ public boolean getRequiresSession();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IActionListener.java b/tapestry-framework/src/org/apache/tapestry/IActionListener.java
new file mode 100644
index 0000000..0a59bac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IActionListener.java
@@ -0,0 +1,42 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Defines a listener to an {@link IAction} component, which is way to
+ * get behavior when the component's URL is triggered (or the form
+ * containing the component is submitted). Certain form elements
+ * ({@link org.apache.tapestry.form.Hidden})
+ * also make use of this interface.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IActionListener
+{
+
+ /**
+ * Method invoked by the component (an {@link org.apache.tapestry.link.ActionLink} or
+ * {@link org.apache.tapestry.form.Form}, when its URL is triggered.
+ *
+ * @param component The component which was "triggered".
+ * @param cycle The request cycle in which the component was triggered.
+ *
+ **/
+
+ public void actionTriggered(IComponent component, IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IAsset.java b/tapestry-framework/src/org/apache/tapestry/IAsset.java
new file mode 100644
index 0000000..ef8d24a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IAsset.java
@@ -0,0 +1,65 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.InputStream;
+
+/**
+ * Representation of a asset (GIF, JPEG, etc.) that may be owned by a
+ * {@link IComponent}.
+ *
+ * <p>Assets may be completely external (i.e., on some other web site),
+ * contained by the {@link javax.servlet.ServletContext},
+ * or stored somewhere in the classpath.
+ *
+ * <p>In the latter two cases, the resource may be localized.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IAsset extends ILocatable
+{
+ /**
+ * Returns a URL for the asset, ready to be inserted into the output HTML.
+ * If the asset can be localized, the localized version (matching the
+ * {@link java.util.Locale} of the current {@link IPage page}) is returned.
+ *
+ * @throws ApplicationRuntimeException if the asset does not exist.
+ *
+ **/
+
+ public String buildURL(IRequestCycle cycle);
+
+ /**
+ * Accesses the localized version of the resource (if possible) and returns it as
+ * an input stream. A version of the resource localized to the
+ * current {@link IPage page} is returned.
+ *
+ * @throws ApplicationRuntimeException if the asset does not exist, or
+ * can't be read.
+ *
+ **/
+
+ public InputStream getResourceAsStream(IRequestCycle cycle);
+
+ /**
+ * Returns the underlying location of the asset.
+ *
+ **/
+
+ public IResourceLocation getResourceLocation();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IBeanProvider.java b/tapestry-framework/src/org/apache/tapestry/IBeanProvider.java
new file mode 100644
index 0000000..4c39b93
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IBeanProvider.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.Collection;
+
+/**
+ * An object that provides a component with access to helper beans.
+ * Helper beans are JavaBeans associated with a page or component
+ * that are used to extend the functionality of the component via
+ * aggregation.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.4
+ **/
+
+
+public interface IBeanProvider
+{
+ /**
+ * Returns the JavaBean with the specified name. The bean is created as needed.
+ *
+ * @throws ApplicationRuntimeException if no such bean is available.
+ *
+ **/
+
+ public Object getBean(String name);
+
+ /**
+ * Returns the {@link IComponent} (which may be a
+ * {@link org.apache.tapestry.IPage}) for which
+ * this bean provider is providing beans.
+ *
+ * @since 1.0.5
+ **/
+
+ public IComponent getComponent();
+
+ /**
+ * Returns a collection of the names of any beans which may
+ * be provided.
+ *
+ * @since 1.0.6
+ * @see org.apache.tapestry.spec.IComponentSpecification#getBeanNames()
+ *
+ **/
+
+ public Collection getBeanNames();
+
+ /**
+ * Returns true if the provider can provide the named bean.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public boolean canProvideBean(String name);
+
+ /**
+ * Returns a resource resolver.
+ *
+ * @since 1.0.8
+ *
+ **/
+
+ public IResourceResolver getResourceResolver();
+
+}
+
diff --git a/tapestry-framework/src/org/apache/tapestry/IBinding.java b/tapestry-framework/src/org/apache/tapestry/IBinding.java
new file mode 100644
index 0000000..c0a986a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IBinding.java
@@ -0,0 +1,157 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A binding is the mechanism used to provide values for parameters of
+ * specific {@link IComponent} instances. The component doesn't
+ * care where the required value comes from, it simply requires that
+ * a value be provided when needed.
+ *
+ * <p>Bindings are set inside the containing component's specification.
+ * Bindings may be static or dynamic (though that is irrelevant to the
+ * component). Components may also use a binding to write a value
+ * back through a property to some other object (typically, another component).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IBinding extends ILocatable
+{
+ /**
+ * Invokes {@link #getObject()}, then coerces the value to a boolean.
+ * The following rules are used to perform the coercion:
+ * <ul>
+ * <li>null is always false
+ * <li>A {@link Boolean} value is self-evident
+ * <li>A {@link Number} value is true if non-zero
+ * <li>A {@link String} value is true if non-empty and contains
+ * non-whitespace characters
+ * <li>Any {@link java.util.Collection} value is true if it has a non-zero
+ * {@link java.util.Collection#size() size}
+ * <li>Any array type is true if it has a non-zero length
+ * <li>Any other non-null value is true
+ * </ul>
+ *
+ * @see Tapestry#evaluateBoolean(Object)
+ *
+ **/
+
+ public boolean getBoolean();
+
+ /**
+ * Gets the value of the Binding using {@link #getObject} and coerces it
+ * to an <code>int</code>. Strings will be parsed, and other
+ * <code>java.lang.Number</code> classes will have <code>intValue()</code>
+ * invoked.
+ *
+ * @throws ClassCastException if the binding's value is not of a usable class.
+ * @throws BindingException if the binding's value is null.
+ **/
+
+ public int getInt();
+
+ /**
+ * Gets the value of the Binding using {@link #getObject()} and coerces it
+ * to a <code>double</code>. Strings will be parsed, and other
+ * <code>java.lang.Number</code> classes will have <code>doubleValue()</code>
+ * invoked.
+ *
+ * @throws ClassCastException if the binding's value is not of a usable class.
+ * @throws BindingException if the binding's value is null.
+ **/
+
+ public double getDouble();
+
+ /**
+ * Invokes {@link #getObject()} and converts the result to <code>java.lang.String</code>.
+ **/
+
+ public String getString();
+
+ /**
+ * Returns the value of this binding. This is the essential method. Other methods
+ * get this value and cast or coerce the value.
+ *
+ **/
+
+ public Object getObject();
+
+ /**
+ * Returns the value for the binding after performing some basic checks.
+ *
+ * @param parameterName the name of the parameter (used to build
+ * the message if an exception is thrown).
+ * @param type if not null, the value must be assignable to the specific
+ * class
+ * @throws BindingException if the value is not assignable to the
+ * specified type
+ *
+ * @since 0.2.9
+ **/
+
+ public Object getObject(String parameterName, Class type);
+
+ /**
+ * Returns true if the value is invariant (not changing; the
+ * same value returned each time). Static and field bindings
+ * are always invariant, and {@link org.apache.tapestry.binding.ExpressionBinding}s
+ * may be marked invariant (as an optimization).
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ public boolean isInvariant();
+
+ /**
+ * Constructs a <code>Boolean</code> and invokes {@link #setObject(Object)}.
+ *
+ **/
+
+ public void setBoolean(boolean value);
+
+ /**
+ * Constructs an <code>Integer</code> and invokes {@link #setObject(Object)}.
+ *
+ **/
+
+ public void setInt(int value);
+
+ /**
+ * Constructs an <code>Double</code> and invokes {@link #setObject(Object)}.
+ *
+ **/
+
+ public void setDouble(double value);
+
+ /**
+ * Invokes {@link #setObject(Object)}.
+ *
+ **/
+
+ public void setString(String value);
+
+ /**
+ * Updates the value of the binding, if possible.
+ *
+ * @exception BindingException If the binding is read only.
+ *
+ **/
+
+ public void setObject(Object value);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IComponent.java b/tapestry-framework/src/org/apache/tapestry/IComponent.java
new file mode 100644
index 0000000..5f3b8dd
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IComponent.java
@@ -0,0 +1,354 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Defines an object which may be used to provide dynamic content on a Tapestry web page.
+ *
+ * <p>Components are created dynamically from thier class names (part of the
+ * {@link IComponentSpecification}).
+ *
+ *
+ * @author Howard Leiws Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IComponent extends IRender, ILocationHolder
+{
+
+ /**
+ * Adds an asset to the component. This is invoked from the page loader.
+ *
+ **/
+
+ public void addAsset(String name, IAsset asset);
+
+ /**
+ * Adds a component to a container. Should only be called during the page
+ * loading process, which is responsible for any checking.
+ *
+ * @see IPageLoader
+ *
+ **/
+
+ public void addComponent(IComponent component);
+
+ /**
+ * Adds a new renderable element to the receiver's body. The element may be either
+ * another component, or static HTML. Such elements come from inside
+ * the receiver's tag within its container's template, and represent static
+ * text and other components.
+ *
+ * <p>The method {@link #renderBody(IMarkupWriter, IRequestCycle)} is used
+ * to render these elements.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void addBody(IRender element);
+
+ /**
+ * Returns the asset map for the component, which may be empty but will not be null.
+ *
+ * <p>The return value is unmodifiable.
+ *
+ **/
+
+ public Map getAssets();
+
+ /**
+ * Returns the named asset, or null if not found.
+ *
+ **/
+
+ public IAsset getAsset(String name);
+
+ /**
+ * Returns the binding with the given name or null if not found.
+ *
+ * <p>Bindings are added to a component using {@link #setBinding(String,IBinding)}.
+ **/
+
+ public IBinding getBinding(String name);
+
+ /**
+ * Returns a {@link Collection} of the names of all bindings (which includes
+ * bindings for both formal and informal parameters).
+ *
+ * <p>The return value is unmodifiable. It will be null for a {@link IPage page},
+ * or may simply be empty for a component with no bindings.
+ *
+ **/
+
+ public Collection getBindingNames();
+
+ /**
+ * Returns a {@link Map} of the {@link IBinding bindings} for this component;
+ * this includes informal parameters
+ * as well as formal bindings.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public Map getBindings();
+
+ /**
+ * Retrieves an contained component by its id.
+ * Contained components have unique ids within their container.
+ *
+ * @exception ApplicationRuntimeException runtime exception thrown if the named
+ * component does not exist.
+ *
+ **/
+
+ public IComponent getComponent(String id);
+
+ /**
+ * Returns the component which embeds the receiver. All components are contained within
+ * other components, with the exception of the root page component.
+ *
+ * <p>A page returns null.
+ *
+ **/
+
+ public IComponent getContainer();
+
+ /**
+ * Sets the container of the component. This is write-once,
+ * an attempt to change it later will throw an {@link ApplicationRuntimeException}.
+ *
+ **/
+
+ public void setContainer(IComponent value);
+
+ /**
+ * Returns a string identifying the name of the page and the id path of the reciever within
+ * the page. Pages simply return their name.
+ *
+ * @see #getIdPath()
+ **/
+
+ public String getExtendedId();
+
+ /**
+ * Returns the simple id of the component, as defined in its specification.
+ *
+ * <p>An id will be unique within the
+ * component which contains this component.
+ *
+ * <p>A {@link IPage page} will always return null.
+ *
+ **/
+
+ public String getId();
+
+ /**
+ * Sets the id of the component. This is write-once,
+ * an attempt to change it later will throw an {@link ApplicationRuntimeException}.
+ *
+ **/
+
+ public void setId(String value);
+
+ /**
+ * Returns the qualified id of the component. This represents a path from the
+ * {@link IPage page} to
+ * this component, showing how components contain each other.
+ *
+ * <p>A {@link IPage page} will always return
+ * null. A component contained on a page returns its simple id.
+ * Other components return their container's id path followed by a period and their
+ * own name.
+ *
+ * @see #getId()
+ **/
+
+ public String getIdPath();
+
+ /**
+ * Returns the page which ultimately contains the receiver. A page will return itself.
+ *
+ **/
+
+ public IPage getPage();
+
+ /**
+ * Sets the page which ultimiately contains the component. This is write-once,
+ * an attempt to change it later will throw an {@link ApplicationRuntimeException}.
+ *
+ **/
+
+ public void setPage(IPage value);
+
+ /**
+ * Returns the specification which defines the component.
+ *
+ **/
+
+ public IComponentSpecification getSpecification();
+
+ /**
+ * Sets the specification used by the component. This is write-once, an attempt
+ * to change it later will throw an {@link ApplicationRuntimeException}.
+ *
+ **/
+
+ public void setSpecification(IComponentSpecification value);
+
+ /**
+ * Invoked to make the receiver render its body (the elements and components
+ * its tag wraps around, on its container's template).
+ * This method is public so that the
+ * {@link org.apache.tapestry.components.RenderBody} component may operate.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderBody(IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Adds a binding to a container. Should only be called during the page
+ * loading process (which is responsible for eror checking).
+ *
+ * @see IPageLoader
+ *
+ **/
+
+ public void setBinding(String name, IBinding binding);
+
+ /**
+ * Returns the contained components as an unmodifiable {@link Map}. This
+ * allows peer components to work together without directly involving their
+ * container ... the classic example is to have an {@link org.apache.tapestry.components.Insert}
+ * work with an enclosing {@link org.apache.tapestry.components.Foreach}.
+ *
+ * <p>This is late addition to Tapestry, because it also opens the door
+ * to abuse, since it is quite possible to break the "black box" aspect of
+ * a component by interacting directly with components it embeds. This creates
+ * ugly interelationships between components that should be seperated.
+ *
+ * @return A Map of components keyed on component id. May return an empty map, but won't return
+ * null.
+ *
+ **/
+
+ public Map getComponents();
+
+ /**
+ * Allows a component to finish any setup after it has been constructed.
+ *
+ * <p>The exact timing is not
+ * specified, but any components contained by the
+ * receiving component will also have been constructed
+ * before this method is invoked.
+ *
+ * <p>As of release 1.0.6, this method is invoked <em>before</em>
+ * bindings are set. This should not affect anything, as bindings
+ * should only be used during renderring.
+ *
+ * <p>Release 2.2 added the cycle parameter which is, regretfully, not
+ * backwards compatible.
+ *
+ * @since 0.2.12
+ *
+ **/
+
+ public void finishLoad(
+ IRequestCycle cycle,
+ IPageLoader loader,
+ IComponentSpecification specification);
+
+ /**
+ * Returns a localized string message. Each component has an optional
+ * set of localized message strings that are read from properties
+ * files.
+ *
+ * @param key the key used to locate the message
+ * @return the localized message for the key, or a placeholder
+ * if no message is defined for the key.
+ *
+ * @since 2.0.4
+ * @deprecated To be removed in 3.1, use {@link #getMessage(String)}.
+ **/
+
+ public String getString(String key);
+
+ /**
+ * Returns a localized string message. Each component has an optional
+ * set of localized message strings that are read from properties
+ * files.
+ *
+ * @param key the key used to locate the message
+ * @return the localized message for the key, or a placeholder
+ * if no message is defined for the key.
+ *
+ * @since 3.0
+ **/
+
+ public String getMessage(String key);
+ /**
+ * Returns component strings for the component.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IMessages getMessages();
+
+ /**
+ * Returns the {@link INamespace} in which the component was defined
+ * (as an alias).
+ *
+ * @since 2.2
+ *
+ **/
+
+ public INamespace getNamespace();
+
+ /**
+ * Sets the {@link INamespace} for the component. The namespace
+ * should only be set once.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void setNamespace(INamespace namespace);
+
+ /**
+ * Sets a property of a component.
+ * @param propertyName the property name
+ * @param value the provided value
+ */
+ public void setProperty(String propertyName, Object value);
+
+ /**
+ * Gets a property of a component.
+ * @param propertyName the property name
+ * @return Object the value of property
+ */
+ public Object getProperty(String propertyName);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IDirect.java b/tapestry-framework/src/org/apache/tapestry/IDirect.java
new file mode 100644
index 0000000..f4c95c6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IDirect.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Interface that defines classes that may be messaged by the direct
+ * service.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IDirect extends IComponent
+{
+ /**
+ * Invoked by the direct service to have the component peform
+ * the appropriate action. The {@link org.apache.tapestry.link.DirectLink} component will
+ * notify its listener.
+ *
+ **/
+
+ public void trigger(IRequestCycle cycle);
+
+ /**
+ * Invoked by the direct service to query the component as to
+ * whether it is stateful. If stateful and no
+ * {@link javax.servlet.http.HttpSession} is active, then a
+ * {@link org.apache.tapestry.StaleSessionException} is
+ * thrown by the service.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public boolean isStateful();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IEngine.java b/tapestry-framework/src/org/apache/tapestry/IEngine.java
new file mode 100644
index 0000000..c340012
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IEngine.java
@@ -0,0 +1,386 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.asset.ResourceChecksumSource;
+import org.apache.tapestry.engine.IComponentClassEnhancer;
+import org.apache.tapestry.engine.IComponentMessagesSource;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.IPageRecorder;
+import org.apache.tapestry.engine.IPageSource;
+import org.apache.tapestry.engine.IPropertySource;
+import org.apache.tapestry.engine.IScriptSource;
+import org.apache.tapestry.engine.ISpecificationSource;
+import org.apache.tapestry.engine.ITemplateSource;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.util.io.DataSqueezer;
+import org.apache.tapestry.util.pool.Pool;
+
+/**
+ * Defines the core, session-persistant object used to run a Tapestry
+ * application for a single client (each client will have its own instance of the engine).
+ *
+ * <p>The engine exists to provide core services to the pages and components
+ * that make up the application. The engine is a delegate to the
+ * {@link ApplicationServlet} via the {@link #service(RequestContext)} method.
+ *
+ * <p>Engine instances are persisted in the {@link javax.servlet.http.HttpSession} and are serializable.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IEngine
+{
+ /**
+ * The name ("Home") of the default page presented when a user first accesses the
+ * application.
+ *
+ * @see org.apache.tapestry.engine.HomeService
+ *
+ **/
+
+ public static final String HOME_PAGE = "Home";
+
+ /**
+ * The name ("Exception") of the page used for reporting exceptions.
+ *
+ * <p>Such a page must have
+ * a writable JavaBeans property named 'exception' of type
+ * <code>java.lang.Throwable</code>.
+ *
+ **/
+
+ public static final String EXCEPTION_PAGE = "Exception";
+
+ /**
+ * The name ("StaleLink") of the page used for reporting stale links.
+ *
+ * <p>The page must implement a writeable JavaBeans proeprty named
+ * 'message' of type <code>String</code>.
+ *
+ **/
+
+ public static final String STALE_LINK_PAGE = "StaleLink";
+
+ /**
+ * Returns a recorder for a page. Returns null if the page recorder has
+ * not been created yet.
+ *
+ * @see #createPageRecorder(String, IRequestCycle)
+ *
+ **/
+
+ public IPageRecorder getPageRecorder(String pageName, IRequestCycle cycle);
+
+ /**
+ * The name ("StaleSession") of the page used for reporting state sessions.
+ *
+ **/
+
+ public static final String STALE_SESSION_PAGE = "StaleSession";
+
+ /**
+ * Forgets changes to the named page by discarding the page recorder for the page.
+ * This is used when transitioning from one part
+ * of an application to another. All property changes for the page are lost.
+ *
+ * <p>This should be done if the page is no longer needed or relevant, otherwise
+ * the properties for the page will continue to be recorded by the engine, which
+ * is wasteful (especially if clustering or failover is employed on the application).
+ *
+ * <p>Throws an {@link ApplicationRuntimeException} if there are uncommitted changes
+ * for the recorder (in the current request cycle).
+ *
+ **/
+
+ public void forgetPage(String name);
+
+ /**
+ * Returns the locale for the engine. This locale is used when selecting
+ * templates and assets.
+ **/
+
+ public Locale getLocale();
+
+ /**
+ * Changes the engine's locale. Any subsequently loaded pages will be
+ * in the new locale (though pages already loaded stay in the old locale).
+ * Generally, you should render a new page after changing the locale, to
+ * show that the locale has changed.
+ *
+ **/
+
+ public void setLocale(Locale value);
+
+
+
+ /**
+ * Creates a new page recorder for the named page.
+ *
+ **/
+
+ public IPageRecorder createPageRecorder(String pageName, IRequestCycle cycle);
+
+ /**
+ * Returns the object used to load a page from its specification.
+ *
+ **/
+
+ public IPageSource getPageSource();
+
+ /**
+ * Gets the named service, or throws an {@link
+ * org.apache.tapestry.ApplicationRuntimeException}
+ * if the application can't provide
+ * the named server.
+ *
+ * <p>The object returned has a short lifecycle (it isn't
+ * serialized with the engine). Repeated calls with the
+ * same name are not guarenteed to return the same object,
+ * especially in different request cycles.
+ *
+ **/
+
+ public IEngineService getService(String name);
+
+ /**
+ * Returns the URL path that corresponds to the servlet for the application.
+ * This is required by instances of {@link IEngineService} that need
+ * to construct URLs for the application. This value will include
+ * the context path.
+ **/
+
+ public String getServletPath();
+
+ /**
+ * Returns the context path, a string which is prepended to the names of
+ * any assets or servlets. This may be the empty string, but won't be null.
+ *
+ * <p>This value is obtained from
+ * {@link javax.servlet.http.HttpServletRequest#getContextPath()}.
+ *
+ **/
+
+ public String getContextPath();
+
+ /**
+ * Returns the application specification that defines the application
+ * and its pages.
+ *
+ **/
+
+ public IApplicationSpecification getSpecification();
+
+ /**
+ * Returns the source of all component specifications for the application.
+ * The source is shared between sessions.
+ *
+ * @see org.apache.tapestry.engine.AbstractEngine#createSpecificationSource(RequestContext)
+ *
+ **/
+
+ public ISpecificationSource getSpecificationSource();
+
+ /**
+ * Returns the source for HTML templates.
+ *
+ * @see org.apache.tapestry.engine.AbstractEngine#createTemplateSource(RequestContext)
+ *
+ **/
+
+ public ITemplateSource getTemplateSource();
+
+ /**
+ * Method invoked from the {@link org.apache.tapestry.ApplicationServlet}
+ * to perform processing of the
+ * request. In release 3.0, this has become more of a dirty flag, indicating
+ * if any state stored by the engine instance itself has changed.
+ *
+ * @return true if the state of the engine was, or could have been, changed during
+ * processing.
+ *
+ **/
+
+ public boolean service(RequestContext context) throws ServletException, IOException;
+
+ /**
+ * Returns an object that can resolve resources and classes.
+ *
+ **/
+
+ public IResourceResolver getResourceResolver();
+
+ /**
+ * Returns the visit object, an object that represents the client's visit
+ * to the application. This is where most server-side state is stored (with
+ * the exception of persistent page properties).
+ *
+ * <p>Returns the visit, if it exists, or null if it has not been created.
+ *
+ **/
+
+ public Object getVisit();
+
+ /**
+ * Returns the visit object, creating it if necessary.
+ *
+ **/
+
+ public Object getVisit(IRequestCycle cycle);
+
+ /**
+ * Allows the visit object to be removed; typically done when "shutting down"
+ * a user's session (by setting the visit to null).
+ *
+ **/
+
+ public void setVisit(Object value);
+
+ /**
+ * Returns the globally shared application object. The global object is
+ * stored in the servlet context and shared by all instances of the engine
+ * for the same application (within the same JVM; the global is
+ * <em>not</em> shared between nodes in a cluster).
+ *
+ * <p>Returns the global object, if it exists, or null if not defined.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public Object getGlobal();
+
+ /**
+ * Returns true if the application allows the reset service.
+ *
+ * @since 0.2.9
+ *
+ **/
+
+ public boolean isResetServiceEnabled();
+
+ /**
+ * Returns a source for parsed
+ * {@link org.apache.tapestry.IScript}s. The source is
+ * shared between all sessions.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public IScriptSource getScriptSource();
+
+ /**
+ * Returns true if the engine has state and, therefore, should be stored
+ * in the HttpSession. This starts as false, but becomes true when
+ * the engine requires state (such as when a visit object is created,
+ * or when a peristent page property is set).
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public boolean isStateful();
+
+ /**
+ * Returns a shared object that allows components to find
+ * their set of localized strings.
+ *
+ * @since 2.0.4
+ *
+ * @see org.apache.tapestry.engine.AbstractEngine#createComponentStringsSource(RequestContext)
+ *
+ **/
+
+ public IComponentMessagesSource getComponentMessagesSource();
+
+ /**
+ * Returns a shared instance of {@link org.apache.tapestry.util.io.DataSqueezer}.
+ *
+ * @since 2.2
+ *
+ * @see org.apache.tapestry.engine.AbstractEngine#createDataSqueezer()
+ *
+ **/
+
+ public DataSqueezer getDataSqueezer();
+
+ /**
+ * Returns a {@link org.apache.tapestry.engine.IPropertySource} that should be
+ * used to obtain configuration data. The returned source represents
+ * a search path that includes (at a minimum):
+ *
+ * <ul>
+ * <li>Properties of the {@link org.apache.tapestry.spec.ApplicationSpecification}
+ * <li>Initial Parameters of servlet (configured in the <code>web.xml</code> deployment descriptor)
+ * <li>Initial Parameter of the servlet context (also configured in <code>web.xml</code>)
+ * <li>System properties (defined with the <code>-D</code> JVM command line parameter)
+ * <li>Hard-coded "factory defaults" (for some properties)
+ * </ul>
+ *
+ * @since 2.3
+ * @see org.apache.tapestry.engine.AbstractEngine#createPropertySource(RequestContext)
+ *
+ **/
+
+ public IPropertySource getPropertySource();
+
+ /**
+ * Returns a {@link org.apache.tapestry.util.pool.Pool} that is used
+ * to store all manner of objects that are needed throughout the system.
+ * This is the best way to deal with objects that are both expensive to
+ * create and not threadsafe. The reset service
+ * will clear out this Pool.
+ *
+ * @since 3.0
+ * @see org.apache.tapestry.engine.AbstractEngine#createPool(RequestContext)
+ *
+ **/
+
+ public Pool getPool();
+
+ /**
+ * Returns an object that can create enhanced versions of component classes.
+ *
+ * @since 3.0
+ * @see org.apache.tapestry.engine.AbstractEngine#createComponentClassEnhancer(RequestContext)
+ *
+ **/
+
+ public IComponentClassEnhancer getComponentClassEnhancer();
+
+ /**
+ * Returns the encoding to be used to generate the servlet responses and
+ * accept the servlet requests.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public String getOutputEncoding();
+
+ /**
+ * Returns an object that can compute the checksum of a resource.
+ * @since 3.0.3
+ */
+ public ResourceChecksumSource getResourceChecksumSource();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IExternalPage.java b/tapestry-framework/src/org/apache/tapestry/IExternalPage.java
new file mode 100644
index 0000000..e306845
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IExternalPage.java
@@ -0,0 +1,48 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+
+/**
+ * Defines a page which may be referenced externally via a URL using the
+ * {@link org.apache.tapestry.engine.ExternalService}. External pages may be bookmarked
+ * via their URL for latter display. See the
+ * {@link org.apache.tapestry.link.ExternalLink} for details on how to invoke
+ * <tt>IExternalPage</tt>s.
+ *
+ * @see org.apache.tapestry.callback.ExternalCallback
+ * @see org.apache.tapestry.engine.ExternalService
+ *
+ * @author Howard Lewis Ship
+ * @author Malcolm Edgar
+ * @version $Id$
+ * @since 2.2
+ **/
+
+public interface IExternalPage extends IPage
+{
+ /**
+ * Initialize the external page with the given array of parameters and
+ * request cycle.
+ * <p>
+ * This method is invoked after {@link IPage#validate(IRequestCycle)}.
+ *
+ * @param parameters the array of page parameters
+ * @param cycle current request cycle
+ *
+ **/
+
+ public void activateExternalPage(Object[] parameters, IRequestCycle cycle);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IForm.java b/tapestry-framework/src/org/apache/tapestry/IForm.java
new file mode 100644
index 0000000..dabe67c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IForm.java
@@ -0,0 +1,174 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import org.apache.tapestry.form.FormEventType;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.valid.IValidationDelegate;
+
+/**
+ * A generic way to access a component which defines an HTML form. This interface
+ * exists so that the {@link IRequestCycle} can invoke the
+ * {@link #rewind(IMarkupWriter, IRequestCycle)} method (which is used to deal with
+ * a Form that uses the direct service). In release 1.0.5, more responsibility
+ * for forms was moved here.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ **/
+
+public interface IForm extends IAction
+{
+
+ /**
+ * Attribute name used with the request cycle; allows other components to locate
+ * the IForm.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.Form";
+
+ /**
+ * Invoked by the {@link IRequestCycle} to allow a form that uses
+ * the direct service, to respond to the form submission.
+ *
+ **/
+
+ public void rewind(IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Adds an additional event handler. The type determines when the
+ * handler will be invoked, {@link FormEventType#SUBMIT}
+ * is most typical.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public void addEventHandler(FormEventType type, String functionName);
+
+ /**
+ * Constructs a unique identifier (within the Form). The identifier
+ * consists of the component's id, with an index number added to
+ * ensure uniqueness.
+ *
+ * <p>Simply invokes {@link #getElementId(IFormComponent, String)} with the component's id.
+ *
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public String getElementId(IFormComponent component);
+
+ /**
+ * Constructs a unique identifier from the base id. If possible, the
+ * id is used as-is. Otherwise, a unique identifier is appended
+ * to the id.
+ *
+ * <p>This method is provided simply so that some components
+ * ({@link org.apache.tapestry.form.ImageSubmit}) have more specific control over
+ * their names.
+ *
+ * <p>Invokes {@link IFormComponent#setName(String)} with the result, as well
+ * as returning it.
+ *
+ * @throws StaleLinkException if, when the form itself is rewinding, the
+ * element id allocated does not match the expected id (as allocated when the form rendered).
+ * This indicates that the state of the application has changed between the time the
+ * form renderred and the time it was submitted.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public String getElementId(IFormComponent component, String baseId);
+
+ /**
+ * Returns the name of the form.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public String getName();
+
+ /**
+ * Returns true if the form is rewinding (meaning, the form was the subject
+ * of the request cycle).
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public boolean isRewinding();
+
+ /**
+ * Returns the validation delegate for the form.
+ * Returns null if the form does not have a delegate.
+ *
+ * @since 1.0.8
+ *
+ **/
+
+ public IValidationDelegate getDelegate();
+
+ /**
+ * May be invoked by a component to force the encoding type of the
+ * form to a particular value.
+ *
+ * @see org.apache.tapestry.form.Upload
+ * @throws ApplicationRuntimeException if the current encoding type is not null
+ * and doesn't match the suggested encoding type
+ * @since 3.0
+ *
+ **/
+
+ public void setEncodingType(String encodingType);
+
+
+ /**
+ * Adds a hidden field value to be stored in the form. This ensures that all
+ * of the <input type="hidden"> (or equivalent) are grouped together,
+ * which ensures that the output HTML is valid (ie. doesn't
+ * have <input> improperly nested with <tr>, etc.).
+ *
+ * <p>
+ * It is acceptible to add multiple hidden fields with the same name.
+ * They will be written in the order they are received.
+ *
+ * @since 3.0
+ */
+
+ public void addHiddenValue(String name, String value);
+
+ /**
+ * Adds a hidden field value to be stored in the form. This ensures that all
+ * of the <input type="hidden"> (or equivalent) are grouped together,
+ * which ensures that the output HTML is valid (ie. doesn't
+ * have <input> improperly nested with <tr>, etc.).
+ *
+ * <p>
+ * It is acceptible to add multiple hidden fields with the same name.
+ * They will be written in the order they are received.
+ *
+ * @since 3.0
+ */
+
+ public void addHiddenValue(String name, String id, String value);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/ILocatable.java b/tapestry-framework/src/org/apache/tapestry/ILocatable.java
new file mode 100644
index 0000000..02752e1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ILocatable.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Interface for classes that may be linked to a specific
+ * {@link org.apache.tapestry.ILocation location}. This
+ * is primarily used when reporting errors.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface ILocatable
+{
+ /**
+ * Returns the {@link org.apache.tapestry.ILocation location} from which
+ * this object orginates, or null if not known.
+ *
+ **/
+
+ public ILocation getLocation();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/ILocation.java b/tapestry-framework/src/org/apache/tapestry/ILocation.java
new file mode 100644
index 0000000..a3e9ddd
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ILocation.java
@@ -0,0 +1,28 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * TODO Add Type comment
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface ILocation
+{
+ public abstract IResourceLocation getResourceLocation();
+ public abstract int getLineNumber();
+ public abstract int getColumnNumber();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/ILocationHolder.java b/tapestry-framework/src/org/apache/tapestry/ILocationHolder.java
new file mode 100644
index 0000000..3fd9fe1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/ILocationHolder.java
@@ -0,0 +1,32 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Interface for objects that are
+ * read from resource files, used to backtrace
+ * live objects to the resources they
+ * came from.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface ILocationHolder extends ILocatable
+{
+ public void setLocation(ILocation location);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IMarkupWriter.java b/tapestry-framework/src/org/apache/tapestry/IMarkupWriter.java
new file mode 100644
index 0000000..5da6b66
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IMarkupWriter.java
@@ -0,0 +1,253 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Defines an object that can write markup (XML, HTML, XHTML) style output.
+ * A <code>IMarkupWriter</code> handles translation from unicode to
+ * the markup language (escaping characters such as '<' and '>' to
+ * their entity equivalents, '&lt;' and '&gt;') as well as assisting
+ * with nested elements, closing tags, etc.
+ *
+ * @author Howard Ship, David Solis
+ * @version $Id$
+ **/
+
+public interface IMarkupWriter
+{
+ /**
+ * Writes an integer attribute into the currently open tag.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ **/
+
+ public void attribute(String name, int value);
+
+ /**
+ * Writes a boolean attribute into the currently open tag.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ * @since 3.0
+ **/
+
+ public void attribute(String name, boolean value);
+
+ /**
+ * Writes an attribute into the most recently opened tag. This must be called after
+ * {@link #begin(String)}
+ * and before any other kind of writing (which closes the tag).
+ *
+ * <p>The value may be null.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ **/
+
+ public void attribute(String name, String value);
+
+ /**
+ * Similar to {@link #attribute(String, String)} but no escaping of invalid elements
+ * is done for the value.
+ *
+ * @throws IllegalStateException if there is no open tag.
+ *
+ * @since 3.0
+ **/
+
+ public void attributeRaw(String name, String value);
+
+ /**
+ * Closes any existing tag then starts a new element. The new element is pushed
+ * onto the active element stack.
+ **/
+
+ public void begin(String name);
+
+ /**
+ * Starts an element that will not later be matched with an <code>end()</code>
+ * call. This is useful for elements that
+ * do not need closing tags.
+ *
+ **/
+
+ public void beginEmpty(String name);
+
+ /**
+ * Invokes checkError() on the <code>PrintWriter</code> used to
+ * format output.
+ **/
+
+ public boolean checkError();
+
+ /**
+ * Closes this <code>IMarkupWriter</code>. Close tags are
+ * written for any active elements. The <code>PrintWriter</code>
+ * is then sent <code>close()</code>. A nested writer will commit
+ * its buffer to its containing writer.
+ *
+ **/
+
+ public void close();
+
+ /**
+ * Closes the most recently opened element by writing the '>' that ends
+ * it. Once this is invoked, the <code>attribute()</code> methods
+ * may not be used until a new element is opened with {@link #begin(String)} or
+ * or {@link #beginEmpty(String)}.
+ **/
+
+ public void closeTag();
+
+ /**
+ * Writes an XML/HTML comment. Any open tag is first closed.
+ * The method takes care of
+ * providing the <code><!--</code> and <code>--></code>, and
+ * provides a blank line after the close of the comment.
+ *
+ * <p><em>Most</em> characters are valid inside a comment, so no check
+ * of the contents is made (much like {@link #printRaw(String)}.
+ *
+ **/
+
+ public void comment(String value);
+
+ /**
+ * Ends the element most recently started by {@link
+ * #begin(String)}. The name of the tag is popped off of the
+ * active element stack and used to form an HTML close tag.
+ *
+ **/
+
+ public void end();
+
+ /**
+ * Ends the most recently started element with the given
+ * name. This will also end any other intermediate
+ * elements. This is very useful for easily ending a table or
+ * even an entire page.
+ *
+ **/
+
+ public void end(String name);
+
+ /**
+ * Forwards <code>flush()</code> to this
+ * <code>IMarkupWriter</code>'s <code>PrintWriter</code>.
+ *
+ **/
+
+ public void flush();
+
+ /**
+ * Returns a nested writer, one that accumulates its changes in a
+ * buffer. When the nested writer is closed, it writes its
+ * buffer into its containing <code>IMarkupWriter</code>.
+ *
+ **/
+
+ public IMarkupWriter getNestedWriter();
+
+ /**
+ *
+ * The primary <code>print()</code> method, used by most other
+ * methods.
+ *
+ * <p>Prints the character array, first closing any open tag. Problematic characters
+ * ('<', '>' and '&') are converted to appropriate
+ * entities.
+ *
+ * <p>Does <em>nothing</em> if <code>data</code> is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(char[] data, int offset, int length);
+
+ /**
+ * Prints a single character, or its equivalent entity.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(char value);
+
+ /**
+ * Prints an integer.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void print(int value);
+
+ /**
+ * Invokes {@link #print(char[], int, int)} to print the string. Use
+ * {@link #printRaw(String)} if the character data is known to be safe.
+ *
+ * <p>Does <em>nothing</em> if <code>value</code> is null.
+ *
+ * <p>Closes any open tag.
+ *
+ * @see #print(char[], int, int)
+ *
+ **/
+
+ public void print(String value);
+
+ /**
+ * Closes the open tag (if any), then prints a line seperator to
+ * the output stream.
+ *
+ **/
+
+ public void println();
+
+ /**
+ * Prints a portion of an output buffer to the stream.
+ * No escaping of invalid elements is done, which
+ * makes this more effecient than <code>print()</code>.
+ * Does <em>nothing</em> if <code>buffer</code>
+ * is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void printRaw(char[] buffer, int offset, int length);
+
+ /**
+ * Prints output to the stream. No escaping of invalid elements is done, which
+ * makes this more effecient than <code>print()</code>.
+ *
+ * <p>Does <em>nothing</em> if <code>value</code>
+ * is null.
+ *
+ * <p>Closes any open tag.
+ *
+ **/
+
+ public void printRaw(String value);
+
+ /**
+ * Returns the type of content generated by this response writer, as
+ * a MIME type.
+ *
+ **/
+
+ public String getContentType();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IMessages.java b/tapestry-framework/src/org/apache/tapestry/IMessages.java
new file mode 100644
index 0000000..f497754
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IMessages.java
@@ -0,0 +1,98 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A set of localized message strings. This is somewhat like
+ * a {@link java.util.ResourceBundle}, but with more
+ * flexibility about where the messages come from. In addition,
+ * it includes methods similar to {@link java.text.MessageFormat}
+ * for formatting the strings.
+ *
+ * @see org.apache.tapestry.IComponent#getMessages
+ * @see org.apache.tapestry.engine.IComponentMessagesSource
+ *
+ * @author Howard Lewis Ship
+ * @since 2.0.4
+ *
+ */
+
+public interface IMessages
+{
+ /**
+ * Searches for a localized string with the given key.
+ * If not found, a modified version of the key
+ * is returned (all upper-case and surrounded by square
+ * brackets).
+ *
+ */
+
+ public String getMessage(String key);
+
+ /**
+ * Searches for a localized string with the given key.
+ * If not found, then the default value (which should already
+ * be localized) is returned. Passing a default of null
+ * is useful when trying to determine if the strings contains
+ * a given key.
+ *
+ */
+
+ public String getMessage(String key, String defaultValue);
+
+ /**
+ * Formats a string, using
+ * {@link java.text.MessageFormat#format(java.lang.String, java.lang.Object[])}.
+ *
+ * <p>
+ * In addition, special processing occurs for any of the arguments that
+ * inherit from {@link Throwable}: such arguments are replaced with the Throwable's message
+ * (if non blank), or the Throwable's class name (if the message is blank).
+ *
+ * @param key the key used to obtain a localized pattern using
+ * {@link #getMessage(String)}
+ * @param arguments passed to the formatter
+ *
+ * @since 3.0
+ *
+ */
+
+ public String format(String key, Object[] arguments);
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ * @since 3.0
+ *
+ */
+ public String format(String key, Object argument);
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ */
+
+ public String format(String key, Object argument1, Object argument2);
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ */
+
+ public String format(String key, Object argument1, Object argument2, Object argument3);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/INamespace.java b/tapestry-framework/src/org/apache/tapestry/INamespace.java
new file mode 100644
index 0000000..6d9f13f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/INamespace.java
@@ -0,0 +1,270 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.List;
+
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.ILibrarySpecification;
+
+/**
+ * Organizes different libraries of Tapestry pages, components
+ * and services into "frameworks", used to disambiguate names.
+ *
+ * <p>
+ * Tapestry release 3.0 includes dynamic discovery of pages and components; an application
+ * or library may contain a page or component that won't be "known" until the name
+ * is resolved (because it involves searching for a particular named file).
+ *
+ * @see org.apache.tapestry.resolver.PageSpecificationResolver
+ * @see org.apache.tapestry.resolver.ComponentSpecificationResolver
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public interface INamespace extends ILocatable
+{
+ /**
+ * Reserved name of a the implicit Framework library.
+ *
+ **/
+
+ public static final String FRAMEWORK_NAMESPACE = "framework";
+
+ /**
+ * Character used to seperate the namespace prefix from the page name
+ * or component type.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public static final char SEPARATOR = ':';
+
+ /**
+ * Returns an identifier for the namespace. Identifiers
+ * are simple names (they start with a letter,
+ * and may contain letters, numbers, underscores and dashes).
+ * An identifier must be unique among a namespaces siblings.
+ *
+ * <p>The application namespace has a null id; the framework
+ * namespace has an id of "framework".
+ *
+ **/
+
+ public String getId();
+
+ /**
+ * Returns the extended id for this namespace, which is
+ * a dot-seperated sequence of ids.
+ *
+ **/
+
+ public String getExtendedId();
+
+ /**
+ * Returns a version of the extended id appropriate for error
+ * messages. This is the based on
+ * {@link #getExtendedId()}, unless this is the
+ * application or framework namespace, in which case
+ * special strings are returned.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public String getNamespaceId();
+
+ /**
+ * Returns the parent namespace; the namespace which
+ * contains this namespace.
+ *
+ * <p>
+ * The application and framework namespaces return null
+ * as the parent.
+ *
+ **/
+
+ public INamespace getParentNamespace();
+
+ /**
+ * Returns a namespace contained by this namespace.
+ *
+ * @param id either a simple name (of a directly contained namespace),
+ * or a dot-seperarated name sequence.
+ * @return the child namespace
+ * @throws ApplicationRuntimeException if no such namespace exist.
+ *
+ **/
+
+ public INamespace getChildNamespace(String id);
+
+ /**
+ * Returns a sorted, immutable list of the ids of the immediate
+ * children of this namespace. May return the empty list,
+ * but won't return null.
+ *
+ **/
+
+ public List getChildIds();
+
+ /**
+ * Returns the page specification of the named
+ * page (defined within the namespace).
+ *
+ * @param name the name of the page
+ * @return the specification
+ * @throws ApplicationRuntimeException if the page specification
+ * doesn't exist or can't be loaded
+ *
+ **/
+
+ public IComponentSpecification getPageSpecification(String name);
+
+ /**
+ * Returns true if this namespace contains the specified
+ * page name.
+ *
+ **/
+
+ public boolean containsPage(String name);
+
+ /**
+ * Returns a sorted list of page names. May return an empty
+ * list, but won't return null. The return list is immutable.
+ *
+ **/
+
+ public List getPageNames();
+
+ /**
+ * Returns the path for the named component (within the namespace).
+ *
+ * @param type the component alias
+ * @return the specification path of the component
+ * @throws ApplicationRuntimeException if the specification
+ * doesn't exist or can't be loaded
+ *
+ **/
+
+ public IComponentSpecification getComponentSpecification(String type);
+
+ /**
+ * Returns true if the namespace contains the indicated component type.
+ *
+ * @param type a simple component type (no namespace prefix is allowed)
+ *
+ **/
+
+ public boolean containsComponentType(String type);
+
+ /**
+ * Returns a sorted list of component types. May return
+ * an empty list, but won't return null. The return list
+ * is immutable. Represents just the known component types
+ * (additional types may be discoverred dynamically).
+ *
+ * <p>Is this method even needed?
+ *
+ * @since 3.0
+ *
+ **/
+
+ public List getComponentTypes();
+
+ /**
+ * Returns the class name of a service provided by the
+ * namespace.
+ *
+ * @param name the name of the service.
+ * @return the complete class name of the service, or null
+ * if the namespace does not contain the named service.
+ *
+ **/
+
+ public String getServiceClassName(String name);
+
+ /**
+ * Returns the names of all services provided by the
+ * namespace, as a sorted, immutable list. May return
+ * the empty list, but won't return null.
+ *
+ **/
+
+ public List getServiceNames();
+
+ /**
+ * Returns the {@link org.apache.tapestry.spec.LibrarySpecification} from which
+ * this namespace was created.
+ *
+ **/
+
+ public ILibrarySpecification getSpecification();
+
+ /**
+ * Constructs a qualified name for the given simple page name by
+ * applying the correct prefix (if any).
+ *
+ * @since 2.3
+ *
+ **/
+
+ public String constructQualifiedName(String pageName);
+
+ /**
+ * Returns the location of the resource from which the
+ * specification for this namespace was read.
+ *
+ **/
+
+ public IResourceLocation getSpecificationLocation();
+
+ /**
+ * Returns true if the namespace is the special
+ * application namespace (which has special search rules
+ * for handling undeclared pages and components).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public boolean isApplicationNamespace();
+
+ /**
+ * Used to specify additional pages beyond those that came from
+ * the namespace's specification. This is used when pages
+ * in the application namespace are dynamically discovered.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void installPageSpecification(String pageName, IComponentSpecification specification);
+
+ /**
+ * Used to specify additional components beyond those that came from
+ * the namespace's specification. This is used when components
+ * in the application namespace are dynamically discovered.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void installComponentSpecification(String type, IComponentSpecification specification);
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IPage.java b/tapestry-framework/src/org/apache/tapestry/IPage.java
new file mode 100644
index 0000000..6470f17
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IPage.java
@@ -0,0 +1,312 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.OutputStream;
+import java.util.Locale;
+
+import org.apache.tapestry.event.ChangeObserver;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.event.PageValidateListener;
+
+/**
+ * A root level component responsible for generating an entire a page
+ * within the application.
+ *
+ * <p>Pages are created dynamically from thier class names (part of the
+ * {@link org.apache.tapestry.spec.IComponentSpecification}).
+ *
+ * @see org.apache.tapestry.engine.IPageSource
+ * @see org.apache.tapestry.engine.IPageLoader
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IPage extends IComponent
+{
+ /**
+ * Invoked on a page when it is no longer needed by
+ * the engine, just before is is
+ * returned to the pool. The page is expected to
+ * null the engine, visit and changeObserver properties.
+ *
+ * <p>Classes should also reset any properties to
+ * default values (as if the instance
+ * was freshly instantiated).
+ *
+ * @see org.apache.tapestry.engine.IPageSource#releasePage(IPage)
+ *
+ **/
+
+ public void detach();
+
+ /**
+ * Returns the {@link IEngine} that the page is currently
+ * attached to.
+ *
+ **/
+
+ public IEngine getEngine();
+
+ /**
+ * Returns the object (effectively, an
+ * {@link org.apache.tapestry.engine.IPageRecorder}) that is notified
+ * of any changes to persistant properties of the page.
+ *
+ **/
+
+ public ChangeObserver getChangeObserver();
+
+ /**
+ * Returns the <code>Locale</code> of the page.
+ * The locale may be used to determine what template is
+ * used by the page and the components contained by the page.
+ *
+ **/
+
+ public Locale getLocale();
+
+ /**
+ * Updates the page's locale. This is write-once, a subsequent attempt
+ * will throw an {@link ApplicationRuntimeException}.
+ *
+ **/
+
+ public void setLocale(Locale value);
+
+ /**
+ * Returns the fully qualified name of the page, including its
+ * namespace prefix, if any.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public String getPageName();
+
+ /**
+ * Sets the name of the page.
+ *
+ * @param pageName fully qualified page name (including namespace prefix, if any)
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void setPageName(String pageName);
+
+ /**
+ * Returns a particular component from within the page. The path is a dotted
+ * name sequence identifying the component. It may be null
+ * in which case the page returns itself.
+ *
+ * @exception ApplicationRuntimeException runtime exception
+ * thrown if the path does not identify a component.
+ *
+ **/
+
+ public IComponent getNestedComponent(String path);
+
+ /**
+ * Attaches the page to the {@link IEngine engine}.
+ * This method is used when a pooled page is
+ * claimed for use with a particular engine; it will stay attached
+ * to the engine until the end of the current request cycle,
+ * then be returned to the pool.
+ *
+ * <p>This method is rarely overriden; to initialize
+ * page properties before a render, override
+ * {@link #beginResponse(IMarkupWriter, IRequestCycle)}.
+ *
+ **/
+
+ public void attach(IEngine value);
+
+ /**
+ * Invoked to render the entire page. This should only be invoked by
+ * {@link IRequestCycle#renderPage(IMarkupWriter writer)}.
+ *
+ * <p>The page performs a render using the following steps:
+ *
+ * <ul>
+ * <li>Invokes {@link PageRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)}
+ * <li>Invokes {@link #beginResponse(IMarkupWriter, IRequestCycle)}
+ * <li>Invokes {@link IRequestCycle#commitPageChanges()} (if not rewinding)
+ * <li>Invokes {@link #render(IMarkupWriter, IRequestCycle)}
+ * <li>Invokes {@link PageRenderListener#pageEndRender(org.apache.tapestry.event.PageEvent)} (this occurs
+ * even if a previous step throws an exception).
+ * </ul>
+ *
+ **/
+
+ public void renderPage(IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Invoked before a partial render of the page occurs
+ * (this happens when rewinding a {@link org.apache.tapestry.form.Form}
+ * within the page). The page is expected to fire appopriate
+ * events.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void beginPageRender();
+
+ /**
+ * Invoked after a partial render of the page occurs
+ * (this happens when rewinding a {@link org.apache.tapestry.form.Form}
+ * within the page). The page is expected to fire
+ * appropriate events.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void endPageRender();
+
+ public void setChangeObserver(ChangeObserver value);
+
+ /**
+ * Method invoked by the page, action and direct services
+ * to validate that the
+ * user is allowed to visit the page.
+ *
+ * <p>Most web applications have a concept of 'logging in' and
+ * pages that an anonymous (not logged in) user should not be
+ * able to visit directly. This method acts as the first line of
+ * defense against a malicous user hacking URLs.
+ *
+ * <p>Pages that should be protected will typically throw a {@link
+ * PageRedirectException}, to redirect the user to an appropriate
+ * part of the system (such as, a login page).
+ *
+ * <p>Since 3.0, it is easiest to not override this method,
+ * but to implement the {@link PageValidateListener} interface
+ * instead.
+ *
+ **/
+
+ public void validate(IRequestCycle cycle);
+
+ /**
+ * Invoked to create a response writer appropriate to the page
+ * (i.e., appropriate to the content of the page).
+ *
+ **/
+
+ public IMarkupWriter getResponseWriter(OutputStream out);
+
+ /**
+ * Invoked just before rendering of the page is initiated. This gives
+ * the page a chance to perform any additional setup. One possible behavior is
+ * to set HTTP headers and cookies before any output is generated.
+ *
+ * <p>The timing of this explicitly <em>before</em> {@link org.apache.tapestry.engine.IPageRecorder page recorder}
+ * changes are committed. Rendering occurs <em>after</em> the recorders
+ * are committed, when it is too late to make changes to dynamic page
+ * properties.
+ *
+ *
+ **/
+
+ public void beginResponse(IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Returns the current {@link IRequestCycle}. This is set when the
+ * page is loaded (or obtained from the pool) and attached to the
+ * {@link IEngine engine}.
+ *
+ **/
+
+ public IRequestCycle getRequestCycle();
+
+ /**
+ * Invoked by the {@link IRequestCycle} to inform the page of the cycle,
+ * as it is loaded.
+ *
+ **/
+
+ public void setRequestCycle(IRequestCycle cycle);
+
+ /**
+ * Returns the visit object for the application; the visit object
+ * contains application-specific information.
+ *
+ **/
+
+ public Object getVisit();
+
+ /**
+ * Returns the globally shared application object. The global object is
+ * stored in the servlet context.
+ *
+ * <p>Returns the global object, if it exists, or null if not defined.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public Object getGlobal();
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ public void addPageRenderListener(PageRenderListener listener);
+
+ /**
+ *
+ * @since 2.1
+ *
+ **/
+
+ public void removePageRenderListener(PageRenderListener listener);
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ public void addPageDetachListener(PageDetachListener listener);
+
+ /**
+ *
+ * @since 2.1
+ *
+ **/
+
+ public void removePageDetachListener(PageDetachListener listener);
+
+ /**
+ * @since 3.0
+ *
+ **/
+
+ public void addPageValidateListener(PageValidateListener listener);
+
+ /**
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void removePageValidateListener(PageValidateListener listener);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IRender.java b/tapestry-framework/src/org/apache/tapestry/IRender.java
new file mode 100644
index 0000000..e4bfc90
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IRender.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * An element that may be asked to render itself to an
+ * {@link IMarkupWriter} using a {@link IRequestCycle}.
+ *
+ * <p>This primarily includes {@link IComponent} and {@link IPage},
+ * but also extends to other things, such as objects responsible for
+ * rendering static markup text.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IRender
+{
+ /**
+ * The principal rendering/rewinding method. This will cause
+ * the receiving component to render its top level elements (HTML
+ * text and components).
+ *
+ * <p>Renderring and rewinding are the exact same process. The
+ * same code that renders must be able to restore state by going
+ * through the exact same operations (even though the output is
+ * discarded).
+ *
+ **/
+
+ public void render(IMarkupWriter writer, IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IRequestCycle.java b/tapestry-framework/src/org/apache/tapestry/IRequestCycle.java
new file mode 100644
index 0000000..23d555e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IRequestCycle.java
@@ -0,0 +1,313 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.IMonitor;
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Controller object that manages a single request cycle. A request cycle
+ * is one 'hit' on the web server. In the case of a Tapestry application,
+ * this will involve:
+ * <ul>
+ * <li>Responding to the URL by finding an {@link IEngineService} object
+ * <li>Determining the result page
+ * <li>Renderring the result page
+ * <li>Releasing any resources
+ * </ul>
+ *
+ * <p>Mixed in with this is:
+ * <ul>
+ * <li>Exception handling
+ * <li>Loading of pages and templates from resources
+ * <li>Tracking changes to page properties, and restoring pages to prior states
+ * <li>Pooling of page objects
+ * </ul>
+ *
+ * <p>A request cycle is broken up into two phases. The <em>rewind</em> phase
+ * is optional, as it tied to {@link org.apache.tapestry.link.ActionLink} or
+ * {@link org.apache.tapestry.form.Form} components. In the rewind phase,
+ * a previous page render is redone (discarding output) until a specific component
+ * of the page is reached. This rewinding ensures that the page
+ * is restored to the exact state it had when the URL for the request cycle was
+ * generated, taking into account the dynamic nature of the page ({@link org.apache.tapestry.components.Foreach},
+ * {@link org.apache.tapestry.components.Conditional}, etc.). Once this component is reached, it can notify
+ * its {@link IActionListener}. The listener has the ability to update the state
+ * of any pages and select a new result page.
+ *
+ * <p>Following the rewind phase is the <em>render</em> phase. During the render phase,
+ * a page is actually rendered and output sent to the client web browser.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IRequestCycle
+{
+ /**
+ * Invoked after the request cycle is no longer needed, to release any resources
+ * it may have. This includes releasing any loaded pages back to the page source.
+ *
+ **/
+
+ public void cleanup();
+
+ /**
+ * Passes the String through
+ * {@link javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)}, which
+ * ensures that the session id is encoded in the URL (if necessary).
+ *
+ **/
+
+ public String encodeURL(String URL);
+
+ /**
+ * Returns the engine which is processing this request cycle.
+ *
+ **/
+
+ public IEngine getEngine();
+
+ /**
+ * Retrieves a previously stored attribute, returning null
+ * if not found. Attributes allow components to locate each other; primarily
+ * they allow a wrapped component to locate a component which wraps it.
+ *
+ **/
+
+ public Object getAttribute(String name);
+
+ public IMonitor getMonitor();
+
+ /**
+ * Returns the next action id. ActionLink ids are used to identify different actions on a
+ * page (URLs that are related to dynamic page state).
+ *
+ **/
+
+ public String getNextActionId();
+
+ /**
+ * Identifies the active page, the page which will ultimately render the response.
+ *
+ **/
+
+ public IPage getPage();
+
+ /**
+ * Returns the page with the given name. If the page has been
+ * previously loaded in the current request cycle, that page is
+ * returned. Otherwise, the engine's page loader is used to
+ * load the page.
+ *
+ * @see IEngine#getPageSource()
+ **/
+
+ public IPage getPage(String name);
+
+ public RequestContext getRequestContext();
+
+ /**
+ * Returns true if the context is being used to rewind a prior
+ * state of the page. This is only true when there is a target
+ * action id.
+ *
+ **/
+
+ public boolean isRewinding();
+
+ /**
+ * Checks to see if the current action id matches the target
+ * action id. Returns true only if they match. Returns false if
+ * there is no target action id (that is, during page rendering).
+ *
+ * <p>If theres a match on action id, then the component
+ * is compared against the target component. If there's a mismatch
+ * then a {@link StaleLinkException} is thrown.
+ **/
+
+ public boolean isRewound(IComponent component) throws StaleLinkException;
+
+ /**
+ * Removes a previously stored attribute, if one with the given name exists.
+ *
+ **/
+
+ public void removeAttribute(String name);
+
+ /**
+ * Renders the given page. Applications should always use this
+ * method to render the page, rather than directly invoking
+ * {@link IPage#render(IMarkupWriter, IRequestCycle)} since the
+ * request cycle must perform some setup before rendering.
+ *
+ **/
+
+ public void renderPage(IMarkupWriter writer);
+
+ /**
+ * Rewinds a page and executes some form of action when the
+ * component with the specified action id is reached.
+ *
+ * @see IAction
+ *
+ **/
+
+ public void rewindPage(String targetActionId, IComponent targetComponent);
+
+ /**
+ * Allows a temporary object to be stored in the request cycle,
+ * which allows otherwise unrelated objects to communicate. This
+ * is similar to <code>HttpServletRequest.setAttribute()</code>,
+ * except that values can be changed and removed as well.
+ *
+ * <p>This is used by components to locate each other. A component, such
+ * as {@link org.apache.tapestry.html.Body}, will write itself under a well-known name
+ * into the request cycle, and components it wraps can locate it by that name.
+ **/
+
+ public void setAttribute(String name, Object value);
+
+ /**
+ * Sets the page to be rendered. This is called by a component
+ * during the rewind phase to specify an alternate page to render
+ * during the response phase.
+ *
+ * @deprecated To be removed in 3.1. Use {@link #activate(IPage)}.
+ *
+ **/
+
+ public void setPage(IPage page);
+
+ /**
+ * Sets the page to be rendered. This is called by a component
+ * during the rewind phase to specify an alternate page to render
+ * during the response phase.
+ *
+ * @deprecated To be removed in 3.1. Use {@link #activate(String)}.
+ *
+ **/
+
+ public void setPage(String name);
+
+ /**
+ * Invoked just before rendering the response page to get all
+ * {@link org.apache.tapestry.engine.IPageRecorder page recorders} touched in this request cycle
+ * to commit their changes (save them to persistant storage).
+ *
+ * @see org.apache.tapestry.engine.IPageRecorder#commit()
+ **/
+
+ public void commitPageChanges();
+
+ /**
+ * Returns the service which initiated this request cycle. This may return
+ * null (very early during the request cycle) if the service has not
+ * yet been determined.
+ *
+ * @since 1.0.1
+ **/
+
+ public IEngineService getService();
+
+ /**
+ * Used by {@link IForm forms} to perform a <em>partial</em> rewind
+ * so as to respond to the form submission (using the direct service).
+ *
+ * @since 1.0.2
+ **/
+
+ public void rewindForm(IForm form, String targetActionId);
+
+ /**
+ * Much like {@link IEngine#forgetPage(String)}, but the page stays active and can even
+ * record changes, until the end of the request cycle, at which point it is discarded
+ * (and any recorded changes are lost).
+ * This is used in certain rare cases where a page has persistent state but is
+ * being renderred "for the last time".
+ *
+ * @since 2.0.2
+ *
+ **/
+
+ public void discardPage(String name);
+
+ /**
+ * Invoked by a {@link IEngineService service} to store an array of application-specific parameters.
+ * These can later be retrieved (typically, by an application-specific listener method)
+ * by invoking {@link #getServiceParameters()}.
+ *
+ * <p>Through release 2.1, parameters was of type String[]. This is
+ * an incompatible change in 2.2.
+ *
+ * @see org.apache.tapestry.engine.DirectService
+ * @since 2.0.3
+ *
+ **/
+
+ public void setServiceParameters(Object[] parameters);
+
+ /**
+ * Returns parameters previously stored by {@link #setServiceParameters(Object[])}.
+ *
+ * <p>
+ * Through release 2.1, the return type was String[]. This is
+ * an incompatible change in 2.2.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ public Object[] getServiceParameters();
+
+ /**
+ * A convienience for invoking {@link #activate(IPage)}. Invokes
+ * {@link #getPage(String)} to get an instance of the named page.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void activate(String name);
+
+ /**
+ * Sets the active page for the request. The active page is the page
+ * which will ultimately render the response. The activate page
+ * is typically set by the {@link IEngineService service}. Frequently,
+ * the active page is changed (from a listener method) to choose
+ * an alternate page to render the response).
+ *
+ * <p>
+ * {@link IPage#validate(IRequestCycle)} is invoked on the
+ * page to be activated. {@link PageRedirectException} is caught
+ * and the page specified in the exception will be the
+ * active page instead (that is, a page may "pass the baton" to another
+ * page using the exception). The new page is also validated. This
+ * continues until a page does not throw {@link PageRedirectException}.
+ *
+ * <p>
+ * Validation loops can occur, where page A redirects to page B and then page B
+ * redirects back to page A (possibly with intermediate steps). This is detected and results
+ * in an {@link ApplicationRuntimeException}.
+ *
+ * @since 3.0
+ *
+ */
+ public void activate(IPage page);
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IResourceLocation.java b/tapestry-framework/src/org/apache/tapestry/IResourceLocation.java
new file mode 100644
index 0000000..933e597
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IResourceLocation.java
@@ -0,0 +1,114 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.net.URL;
+import java.util.Locale;
+
+/**
+ * Describes the location of a resource, such as a specification
+ * or template. Resources may be located within the classpath,
+ * or within the Web context root or somewhere else entirely.
+ *
+ * <p>
+ * Resources may be either base or localized. A localized
+ * version of a base resource may be obtained
+ * via {@link #getLocalization(Locale)}.
+ *
+ * <p>
+ * Resource locations are used as Map keys, they must
+ * implement {@link java.lang.Object#hashCode()} and
+ * {@link java.lang.Object#equals(java.lang.Object)}
+ * properly.
+ *
+ * <p>
+ * Resource locations are valid even if the corresponding
+ * resource <i>doesn't exist</i>. To verify if a localization
+ * actually exists, use {@link #getResourceURL()}, which returns
+ * null if the resource doesn't exist. {@link #getLocalization(Locale)}
+ * returns only real resource locations, where the resource exists.
+ *
+ * <p>
+ * Folders must be represented with a trailing slash.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface IResourceLocation
+{
+ /**
+ * Returns a URL for the resource.
+ *
+ * @return the URL for the resource if it exists, or null if it does not
+ *
+ **/
+
+ public URL getResourceURL();
+
+ /**
+ * Returns the file name portion of the resource location.
+ *
+ **/
+
+ public String getName();
+
+ /**
+ * Returns a localized version of this resource (or this resource, if no
+ * appropriate localization is found). Should only be invoked
+ * on a base resource.
+ *
+ * @param locale to localize for, or null for no localization.
+ * @return a localized version of this resource, of null if the resource
+ * itself does not exist.
+ *
+ **/
+
+ public IResourceLocation getLocalization(Locale locale);
+
+ /**
+ * Returns at a relative location to this resource.
+ * The new resource may or may not exist; this can be determined
+ * via {@link #getResourceURL()}.
+ *
+ * @param name name of new resource, possibly as a relative path, or
+ * as an absolute path (starting with a slash).
+ *
+ **/
+
+ public IResourceLocation getRelativeLocation(String name);
+
+ /**
+ * Returns the path that represents the resource. This should
+ * only be used when the type of resource is known.
+ *
+ **/
+
+ public String getPath();
+
+ /**
+ * Returns the locale for which this resource has been localized
+ * or null if the resource has not been localized. This should
+ * only be used when the type of resource is known.
+ *
+ * This locale is the same or more general than the locale for which localization
+ * was requested. For example, if the requested locale was en_US, but only the file
+ * Home_en was found, this locale returned would be en.
+ **/
+
+ public Locale getLocale();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/IResourceResolver.java b/tapestry-framework/src/org/apache/tapestry/IResourceResolver.java
new file mode 100644
index 0000000..d83717a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IResourceResolver.java
@@ -0,0 +1,67 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.net.URL;
+
+import ognl.ClassResolver;
+
+/**
+ * An object which is used to resolve classes and class-path resources.
+ * This is needed because, in an application server, different class loaders
+ * will be loading the Tapestry framework and the specific Tapestry application.
+ *
+ * <p>The class loader for the framework needs to be able to see resources in
+ * the application, but the application's class loader is a descendent of the
+ * framework's class loader. To resolve this, we need a 'hook', an instance
+ * that provides access to the application's class loader.
+ *
+ * <p>To more easily support OGNL, this interface now extends
+ * {@link ognl.ClassResolver}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IResourceResolver extends ClassResolver
+{
+ /**
+ * Forwarded, unchanged, to the class loader. Returns null if the
+ * resource is not found.
+ *
+ **/
+
+ public URL getResource(String name);
+
+ /**
+ * Forwarded, to the the method
+ * <code>Class.forName(String, boolean, ClassLoader)</code>, using
+ * the application's class loader.
+ *
+ * Throws an {@link ApplicationRuntimeException} on any error.
+ **/
+
+ public Class findClass(String name);
+
+ /**
+ * Returns a {@link java.lang.ClassLoader} that can see
+ * all the classes the resolver can access.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public ClassLoader getClassLoader();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IScript.java b/tapestry-framework/src/org/apache/tapestry/IScript.java
new file mode 100644
index 0000000..3dddea5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IScript.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.util.Map;
+
+/**
+ * An object that can convert a set of symbols into a collection of JavaScript statements.
+ *
+ * <p>IScript implementation must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ *
+ **/
+
+public interface IScript
+{
+ /**
+ * Returns the location from which the script was loaded.
+ *
+ **/
+
+ public IResourceLocation getScriptLocation();
+
+ /**
+ * Executes the script, which will read and modify the symbols {@link Map}. The
+ * script works with the {@link IScriptProcessor} to get the generated JavaScript
+ * included on the page.
+ *
+ * @param cycle the current request cycle
+ * @param processor an object that processes the results of the script, typically
+ * an instance of {@link org.apache.tapestry.html.Body}
+ * @param symbols Map of input symbols; execution of the script may modify the map,
+ * creating new output symbols
+ *
+ * @see org.apache.tapestry.html.Body#get(IRequestCycle)
+ *
+ */
+
+ public void execute(IRequestCycle cycle, IScriptProcessor processor, Map symbols);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/IScriptProcessor.java b/tapestry-framework/src/org/apache/tapestry/IScriptProcessor.java
new file mode 100644
index 0000000..ec5179c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/IScriptProcessor.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Defines methods needed by a {@link org.apache.tapestry.IScript} to
+ * execute.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ * @see org.apache.tapestry.html.Body
+ */
+
+public interface IScriptProcessor
+{
+ /**
+ * Adds scripting code to the main body. During the render, multiple scripts may
+ * render multiple bodies; all are concatinated together to form
+ * a single block.
+ */
+
+ public void addBodyScript(String script);
+
+ /**
+ * Adds initialization script. Initialization script is executed once, when
+ * the containing page loads. Effectively, this means that initialization script
+ * is stored inside the HTML <body> element's <code>onload</code>
+ * event handler.
+ */
+ public void addInitializationScript(String script);
+
+ /**
+ * Adds an external script. The processor is expected to ensure
+ * that external scripts are only loaded a single time per page.
+ */
+
+ public void addExternalScript(IResourceLocation location);
+
+ /**
+ * Ensures that the given string is unique. The string
+ * is either returned unchanged, or a suffix is appended to
+ * ensure uniqueness.
+ */
+
+ public String getUniqueString(String baseValue);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/Location.java b/tapestry-framework/src/org/apache/tapestry/Location.java
new file mode 100644
index 0000000..cd71e84
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/Location.java
@@ -0,0 +1,114 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * Implementation of the {@link org.apache.tapestry.ILocation} interface.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class Location implements ILocation
+{
+ private IResourceLocation _resourceLocation;
+ private int _lineNumber = -1;
+ private int _columnNumber = -1;
+
+ public Location(IResourceLocation location)
+ {
+ _resourceLocation = location;
+ }
+
+ public Location(IResourceLocation location, int lineNumber)
+ {
+ this(location);
+
+ _lineNumber = lineNumber;
+ }
+
+ public Location(IResourceLocation location, int lineNumber, int columnNumber)
+ {
+ this(location);
+
+ _lineNumber = lineNumber;
+ _columnNumber = columnNumber;
+ }
+
+ public IResourceLocation getResourceLocation()
+ {
+ return _resourceLocation;
+ }
+
+ public int getLineNumber()
+ {
+ return _lineNumber;
+ }
+
+ public int getColumnNumber()
+ {
+ return _columnNumber;
+ }
+
+ public int hashCode()
+ {
+ HashCodeBuilder builder = new HashCodeBuilder(237, 53);
+
+ builder.append(_resourceLocation);
+ builder.append(_lineNumber);
+ builder.append(_columnNumber);
+
+ return builder.toHashCode();
+ }
+
+ public boolean equals(Object other)
+ {
+ if (!(other instanceof ILocation))
+ return false;
+
+ ILocation l = (ILocation) other;
+
+ EqualsBuilder builder = new EqualsBuilder();
+ builder.append(_lineNumber, l.getLineNumber());
+ builder.append(_columnNumber, l.getColumnNumber());
+ builder.append(_resourceLocation, l.getResourceLocation());
+
+ return builder.isEquals();
+ }
+
+ public String toString()
+ {
+ if (_lineNumber <= 0 && _columnNumber <= 0)
+ return _resourceLocation.toString();
+ StringBuffer buffer = new StringBuffer(_resourceLocation.toString());
+ if (_lineNumber > 0)
+ {
+ buffer.append(", line ");
+ buffer.append(_lineNumber);
+ }
+
+ if (_columnNumber > 0)
+ {
+ buffer.append(", column ");
+ buffer.append(_columnNumber);
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/PageRedirectException.java b/tapestry-framework/src/org/apache/tapestry/PageRedirectException.java
new file mode 100644
index 0000000..c990fef
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/PageRedirectException.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Exception thrown by a {@link IComponent component} or {@link org.apache.tapestry.engine.IEngineService}
+ * that wishes to force the application to a particular page. This is often used
+ * to protect a sensitive page until the user is authenticated.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public class PageRedirectException extends ApplicationRuntimeException
+{
+ private String _targetPageName;
+
+ public PageRedirectException(String targetPageName)
+ {
+ this(targetPageName, null, null, targetPageName);
+ }
+
+ public PageRedirectException(IPage page)
+ {
+ this(page.getPageName());
+ }
+
+ public PageRedirectException(
+ String message,
+ Object component,
+ Throwable rootCause,
+ String targetPageName)
+ {
+ super(message, component, null, rootCause);
+
+ _targetPageName = targetPageName;
+ }
+
+ public String getTargetPageName()
+ {
+ return _targetPageName;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/RedirectException.java b/tapestry-framework/src/org/apache/tapestry/RedirectException.java
new file mode 100644
index 0000000..3bc8d44
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/RedirectException.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Exception thrown to force a redirection to an arbitrary location.
+ * This is used when, after processing a request (such as a form
+ * submission or a link being clicked), it is desirable to go
+ * to some arbitrary new location.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.6
+ *
+ **/
+
+public class RedirectException extends ApplicationRuntimeException
+{
+ private String _redirectLocation;
+
+ public RedirectException(String redirectLocation)
+ {
+ this(null, redirectLocation);
+ }
+
+ /**
+ * @param message A message describing why the redirection is taking place.
+ * @param redirectLocation The location to redirect to, may be a relative path (relative
+ * to the {@link javax.servlet.ServletContext}).
+ *
+ * @see javax.servlet.http.HttpServletResponse#sendRedirect(String)
+ * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(String)
+ *
+ **/
+
+ public RedirectException(String message, String redirectLocation)
+ {
+ super(message);
+
+ _redirectLocation = redirectLocation;
+ }
+
+ public String getRedirectLocation()
+ {
+ return _redirectLocation;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/RedirectFilter.java b/tapestry-framework/src/org/apache/tapestry/RedirectFilter.java
new file mode 100644
index 0000000..de679b8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/RedirectFilter.java
@@ -0,0 +1,109 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Filter used to redirect a root context URL (i.e., "/context" or "/context/"
+ * to the Tapestry application servlet (typically, "/context/app"). This
+ * servlet is mapped to "/" and must have a <init-parameter&;gt;
+ * <code>redirect-path</code> that is the application servlet's path (i.e.,
+ * "/app"). If no value is specified, then "/app" is used. The path
+ * is always relative to the servlet context, and should always
+ * begin with a leading slash.
+ *
+ * <p>Filters are only available in Servlet API 2.3 and above.
+ *
+ * <p>Servlet API 2.4 is expected to allow a servlets in the welcome list
+ * (equivalent to index.html or index.jsp), at which point this filter
+ * should no longer be necessary.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+public class RedirectFilter implements Filter
+{
+ private static final Log LOG = LogFactory.getLog(RedirectFilter.class);
+ public static final String REDIRECT_PATH_PARAM = "redirect-path";
+
+ private String _redirectPath;
+
+ public void init(FilterConfig config) throws ServletException
+ {
+ _redirectPath = config.getInitParameter(REDIRECT_PATH_PARAM);
+
+ if (Tapestry.isBlank(_redirectPath))
+ _redirectPath = "/app";
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(Tapestry.format("RedirectServlet.redirect-path", _redirectPath));
+ }
+
+ public void destroy()
+ {
+
+ }
+
+ /**
+ * This filter intercepts the so-called "default" servlet, whose job is
+ * to provide access to standard resources packaged within the web application
+ * context. This code is interested in only the very root, redirecting
+ * to the appropriate Tapestry application servlet. Other values
+ * are passed through unchanged.
+ */
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException
+ {
+ HttpServletRequest hrequest = (HttpServletRequest) request;
+ HttpServletResponse hresponse = (HttpServletResponse) response;
+
+ String servletPath = hrequest.getServletPath();
+ String pathInfo = hrequest.getPathInfo();
+
+ // Been experimenting with different servlet containers. In Jetty 4.2.8 and Tomcat 4.1,
+ // resources have a non-null servletPath. If JBossWeb 3.0.6, the servletPath is
+ // null and the pathInfo indicates the relative location of the resource.
+
+ if ((Tapestry.isBlank(servletPath) || servletPath.equals("/"))
+ && (Tapestry.isBlank(pathInfo) || pathInfo.equals("/")))
+ {
+ String path = hrequest.getContextPath() + _redirectPath;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(Tapestry.format("RedirectServlet.redirecting", path));
+
+ hresponse.sendRedirect(path);
+ return;
+ }
+
+ chain.doFilter(request, response);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/RenderRewoundException.java b/tapestry-framework/src/org/apache/tapestry/RenderRewoundException.java
new file mode 100644
index 0000000..6c31ddc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/RenderRewoundException.java
@@ -0,0 +1,32 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A special subclass of {@link ApplicationRuntimeException} that can be thrown
+ * when a component has determined that the state of the page has been
+ * rewound.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public class RenderRewoundException extends ApplicationRuntimeException
+{
+ public RenderRewoundException(Object component)
+ {
+ super(null, component, null, null);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/StaleLinkException.java b/tapestry-framework/src/org/apache/tapestry/StaleLinkException.java
new file mode 100644
index 0000000..ac10e88
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/StaleLinkException.java
@@ -0,0 +1,115 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Exception thrown by an {@link org.apache.tapestry.engine.IEngineService} when it discovers that
+ * the an action link was for an out-of-date version of the page.
+ *
+ * <p>The application should redirect to the StaleLink page.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class StaleLinkException extends ApplicationRuntimeException
+{
+ private transient IPage _page;
+ private String _pageName;
+ private String _targetIdPath;
+ private String _targetActionId;
+
+ public StaleLinkException()
+ {
+ super(null, null, null, null);
+ }
+
+ /**
+ * Constructor used when the action id is found, but the target id path
+ * did not match the actual id path.
+ *
+ **/
+
+ public StaleLinkException(IComponent component, String targetActionId, String targetIdPath)
+ {
+ super(
+ Tapestry.format(
+ "StaleLinkException.action-mismatch",
+ new String[] { targetActionId, component.getIdPath(), targetIdPath }),
+ component,
+ null,
+ null);
+
+ _page = component.getPage();
+ _pageName = _page.getPageName();
+
+ _targetActionId = targetActionId;
+ _targetIdPath = targetIdPath;
+ }
+
+ /**
+ * Constructor used when the target action id is not found.
+ *
+ **/
+
+ public StaleLinkException(IPage page, String targetActionId, String targetIdPath)
+ {
+ this(
+ Tapestry.format(
+ "StaleLinkException.component-mismatch",
+ targetActionId,
+ targetIdPath),
+ page);
+
+ _targetActionId = targetActionId;
+ _targetIdPath = targetIdPath;
+ }
+
+ public StaleLinkException(String message, IComponent component)
+ {
+ super(message, component, null, null);
+ }
+
+
+
+ public String getPageName()
+ {
+ return _pageName;
+ }
+
+ /**
+ * Returns the page referenced by the service URL, if known,
+ * or null otherwise.
+ *
+ **/
+
+ public IPage getPage()
+ {
+ return _page;
+ }
+
+ public String getTargetActionId()
+ {
+ return _targetActionId;
+ }
+
+ public String getTargetIdPath()
+ {
+ return _targetIdPath;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/StaleSessionException.java b/tapestry-framework/src/org/apache/tapestry/StaleSessionException.java
new file mode 100644
index 0000000..d30d372
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/StaleSessionException.java
@@ -0,0 +1,64 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * Exception thrown by an {@link org.apache.tapestry.engine.IEngineService} when it discovers that
+ * the {@link javax.servlet.http.HttpSession}
+ * has timed out (and been replaced by a new, empty
+ * one).
+ *
+ * <p>The application should redirect to the stale-session page.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class StaleSessionException extends ApplicationRuntimeException
+{
+ private transient IPage _page;
+ private String _pageName;
+
+ public StaleSessionException()
+ {
+ this(null, null);
+ }
+
+ public StaleSessionException(String message, IPage page)
+ {
+ super(message, page, null, null);
+ _page = page;
+
+ if (page != null)
+ _pageName = page.getPageName();
+ }
+
+ public String getPageName()
+ {
+ return _pageName;
+ }
+
+ /**
+ * Returns the page referenced by the service URL, if known, or null otherwise.
+ *
+ **/
+
+ public IPage getPage()
+ {
+ return _page;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/Tapestry.java b/tapestry-framework/src/org/apache/tapestry/Tapestry.java
new file mode 100644
index 0000000..bcf3824
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/Tapestry.java
@@ -0,0 +1,1571 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+
+import org.apache.tapestry.event.ChangeObserver;
+import org.apache.tapestry.event.ObservedChangeEvent;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.resource.ContextResourceLocation;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.util.AdaptorRegistry;
+import org.apache.tapestry.util.StringSplitter;
+
+/**
+ * A placeholder for a number of (static) methods that don't belong elsewhere, as well
+ * as a global location for static constants.
+ *
+ * @since 1.0.1
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public final class Tapestry
+{
+ /**
+ * Name of a request attribute used with the
+ * {@link #TAGSUPPORT_SERVICE} service. The attribute
+ * defines the underlying service to for which a URL will be generated.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public final static String TAG_SUPPORT_SERVICE_ATTRIBUTE =
+ "org.apache.tapestry.tagsupport.service";
+
+ /**
+ * Name of a request attribute used with the
+ * {@link #TAGSUPPORT_SERVICE} service. The attribute
+ * defines the correct servlet path for the
+ * Tapestry application (which, for the odd-man-out TAGSUPPORT_SERVICE
+ * may not match HttpServletRequest.getServletPath() because of
+ * the use of an include.
+ *
+ * @since 3.0
+ */
+
+ public final static String TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE =
+ "org.apache.tapestry.tagsupport.servlet-path";
+
+ /**
+ * Name of a request attribute used with the
+ * {@link #TAGSUPPORT_SERVICE} service. The attribute
+ * defines an array of objects to be converted into
+ * service parameters (i.e., for use with the
+ * {@link #EXTERNAL_SERVICE}).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public final static String TAG_SUPPORT_PARAMETERS_ATTRIBUTE =
+ "org.apache.tapestry.tagsupport.parameters";
+
+ /**
+ * Service used to support rendering of JSP tags. tagsupport is provided
+ * with a service and service parameters via request attributes
+ * and creates a URI from the result, which is output to the response.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String TAGSUPPORT_SERVICE = "tagsupport";
+
+ /**
+ * The name ("action") of a service that allows behavior to be associated with
+ * an {@link IAction} component, such as {@link org.apache.tapestry.link.ActionLink} or
+ * {@link org.apache.tapestry.form.Form}.
+ *
+ * <p>This service is used with actions that are tied to the
+ * dynamic state of the page, and which require a rewind of the page.
+ *
+ **/
+
+ public final static String ACTION_SERVICE = "action";
+
+ /**
+ * The name ("direct") of a service that allows stateless behavior for an {@link
+ * org.apache.tapestry.link.DirectLink} component.
+ *
+ * <p>This service rolls back the state of the page but doesn't
+ * rewind the the dynamic state of the page the was the action
+ * service does, which is more efficient but less powerful.
+ *
+ * <p>An array of String parameters may be included with the
+ * service URL; these will be made available to the {@link org.apache.tapestry.link.DirectLink}
+ * component's listener.
+ *
+ **/
+
+ public final static String DIRECT_SERVICE = "direct";
+
+ /**
+ * The name ("external") of a service that a allows {@link IExternalPage} to be selected.
+ * Associated with a {@link org.apache.tapestry.link.ExternalLink} component.
+ *
+ * <p>This service enables {@link IExternalPage}s to be accessed via a URL.
+ * External pages may be booked marked using their URL for future reference.
+ *
+ * <p>An array of Object parameters may be included with the
+ * service URL; these will be passed to the
+ * {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method.
+ *
+ **/
+
+ public final static String EXTERNAL_SERVICE = "external";
+
+ /**
+ * The name ("page") of a service that allows a new page to be selected.
+ * Associated with a {@link org.apache.tapestry.link.PageLink} component.
+ *
+ * <p>The service requires a single parameter: the name of the target page.
+ **/
+
+ public final static String PAGE_SERVICE = "page";
+
+ /**
+ * The name ("home") of a service that jumps to the home page. A stand-in for
+ * when no service is provided, which is typically the entrypoint
+ * to the application.
+ *
+ **/
+
+ public final static String HOME_SERVICE = "home";
+
+ /**
+ * The name ("restart") of a service that invalidates the session and restarts
+ * the application. Typically used just
+ * to recover from an exception.
+ *
+ **/
+
+ public static final String RESTART_SERVICE = "restart";
+
+ /**
+ * The name ("asset") of a service used to access internal assets.
+ *
+ **/
+
+ public static final String ASSET_SERVICE = "asset";
+
+ /**
+ * The name ("reset") of a service used to clear cached template
+ * and specification data and remove all pooled pages.
+ * This is only used when debugging as
+ * a quick way to clear the out cached data, to allow updated
+ * versions of specifications and templates to be loaded (without
+ * stopping and restarting the servlet container).
+ *
+ * <p>This service is only available if the Java system property
+ * <code>org.apache.tapestry.enable-reset-service</code>
+ * is set to <code>true</code>.
+ *
+ **/
+
+ public static final String RESET_SERVICE = "reset";
+
+ /**
+ * Query parameter that identfies the service for the
+ * request.
+ *
+ * @since 1.0.3
+ *
+ **/
+
+ public static final String SERVICE_QUERY_PARAMETER_NAME = "service";
+
+ /**
+ * The query parameter for application specific parameters to the
+ * service (this is used with the direct service). Each of these
+ * values is encoded with {@link java.net.URLEncoder#encode(String)} before
+ * being added to the URL. Multiple values are handle by repeatedly
+ * establishing key/value pairs (this is a change from behavior in
+ * 2.1 and earlier).
+ *
+ * @since 1.0.3
+ *
+ **/
+
+ public static final String PARAMETERS_QUERY_PARAMETER_NAME = "sp";
+
+ /**
+ * Property name used to get the extension used for templates. This
+ * may be set in the page or component specification, or in the page (or
+ * component's) immediate container (library or application specification).
+ * Unlike most properties, value isn't inherited all the way up the chain.
+ * The default template extension is "html".
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String TEMPLATE_EXTENSION_PROPERTY =
+ "org.apache.tapestry.template-extension";
+
+ /**
+ * The default extension for templates, "html".
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String DEFAULT_TEMPLATE_EXTENSION = "html";
+
+ /**
+ * The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the
+ * currently rendering {@link org.apache.tapestry.components.ILinkComponent}
+ * is stored. Link components do not nest.
+ *
+ **/
+
+ public static final String LINK_COMPONENT_ATTRIBUTE_NAME =
+ "org.apache.tapestry.active-link-component";
+
+ /**
+ * Suffix appended to a parameter name to form the name of a property that stores the
+ * binding for the parameter.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String PARAMETER_PROPERTY_NAME_SUFFIX = "Binding";
+
+ /**
+ * Name of application extension used to resolve page and component
+ * specifications that can't be located by the normal means. The
+ * extension must implement
+ * {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String SPECIFICATION_RESOLVER_DELEGATE_EXTENSION_NAME =
+ "org.apache.tapestry.specification-resolver-delegate";
+
+ /**
+ * Name of application extension used to resolve page and component
+ * templates that can't be located by the normal means.
+ * The extension must implement
+ * {@link org.apache.tapestry.engine.ITemplateSourceDelegate}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME =
+ "org.apache.tapestry.template-source-delegate";
+
+ /**
+ * Key used to obtain an extension from the application specification. The extension,
+ * if it exists, implements {@link org.apache.tapestry.request.IRequestDecoder}.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String REQUEST_DECODER_EXTENSION_NAME =
+ "org.apache.tapestry.request-decoder";
+
+ /**
+ * Name of optional application extension for the multipart decoder
+ * used by the application. The extension must implement
+ * {@link org.apache.tapestry.multipart.IMultipartDecoder}
+ * (and is generally a configured instance of
+ * {@link org.apache.tapestry.multipart.DefaultMultipartDecoder}).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String MULTIPART_DECODER_EXTENSION_NAME =
+ "org.apache.tapestry.multipart-decoder";
+
+ /**
+ * Method id used to check that {@link IPage#validate(IRequestCycle)}
+ * is invoked.
+ * @see #checkMethodInvocation(Object, String, Object)
+ * @since 3.0
+ */
+
+ public static final String ABSTRACTPAGE_VALIDATE_METHOD_ID = "AbstractPage.validate()";
+
+ /**
+ * Method id used to check that {@link IPage#detach()} is invoked.
+ * @see #checkMethodInvocation(Object, String, Object)
+ * @since 3.0
+ */
+
+ public static final String ABSTRACTPAGE_DETACH_METHOD_ID = "AbstractPage.detach()";
+
+ /**
+ * Regular expression defining a simple property name. Used by several different
+ * parsers. Simple property names match Java variable names; a leading letter
+ * (or underscore), followed by letters, numbers and underscores.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String SIMPLE_PROPERTY_NAME_PATTERN = "^_?[a-zA-Z]\\w*$";
+
+ /**
+ * Name of an application extension used as a factory for
+ * {@link org.apache.tapestry.engine.IMonitor} instances. The extension
+ * must implement {@link org.apache.tapestry.engine.IMonitorFactory}.
+ *
+ * @since 3.0
+ */
+
+ public static final String MONITOR_FACTORY_EXTENSION_NAME =
+ "org.apache.tapestry.monitor-factory";
+
+ /**
+ * Class name of an {@link ognl.TypeConverter} implementing class
+ * to use as a type converter for {@link org.apache.tapestry.binding.ExpressionBinding}
+ */
+ public static final String OGNL_TYPE_CONVERTER = "org.apache.tapestry.ognl-type-converter";
+
+ /**
+ * Prevent instantiation.
+ *
+ **/
+
+ private Tapestry()
+ {
+ }
+
+ /**
+ * The version of the framework; this is updated for major releases.
+ *
+ **/
+
+ public static final String VERSION = readVersion();
+
+ /**
+ * Contains strings loaded from TapestryStrings.properties.
+ *
+ * @since 1.0.8
+ *
+ **/
+
+ private static ResourceBundle _strings;
+
+ /**
+ * A {@link Map} that links Locale names (as in {@link Locale#toString()} to
+ * {@link Locale} instances. This prevents needless duplication
+ * of Locales.
+ *
+ **/
+
+ private static final Map _localeMap = new HashMap();
+
+ static {
+ Locale[] locales = Locale.getAvailableLocales();
+ for (int i = 0; i < locales.length; i++)
+ {
+ _localeMap.put(locales[i].toString(), locales[i]);
+ }
+ }
+
+ /**
+ * Used for tracking if a particular super-class method has been invoked.
+ */
+
+ private static final ThreadLocal _invokedMethodIds = new ThreadLocal();
+
+ /**
+ * A {@link org.apache.tapestry.util.AdaptorRegistry} used to coerce arbitrary objects
+ * to boolean values.
+ *
+ * @see #evaluateBoolean(Object)
+ **/
+
+ private static final AdaptorRegistry _booleanAdaptors = new AdaptorRegistry();
+
+ private static abstract class BoolAdaptor
+ {
+ /**
+ * Implemented by subclasses to coerce an object to a boolean.
+ *
+ **/
+
+ public abstract boolean coerce(Object value);
+ }
+
+ private static class BooleanAdaptor extends BoolAdaptor
+ {
+ public boolean coerce(Object value)
+ {
+ Boolean b = (Boolean) value;
+
+ return b.booleanValue();
+ }
+ }
+
+ private static class NumberAdaptor extends BoolAdaptor
+ {
+ public boolean coerce(Object value)
+ {
+ Number n = (Number) value;
+
+ return n.intValue() > 0;
+ }
+ }
+
+ private static class CollectionAdaptor extends BoolAdaptor
+ {
+ public boolean coerce(Object value)
+ {
+ Collection c = (Collection) value;
+
+ return c.size() > 0;
+ }
+ }
+
+ private static class StringAdaptor extends BoolAdaptor
+ {
+ public boolean coerce(Object value)
+ {
+ String s = (String) value;
+
+ if (s.length() == 0)
+ return false;
+
+ String ts = s.trim();
+ if (ts.length() == 0)
+ return false;
+
+ // Here probably Boolean.getBoolean(s) should be used,
+ // but we need the opposite check
+ if (ts.equalsIgnoreCase("false"))
+ return false;
+
+ return true;
+ }
+ }
+
+ static {
+ _booleanAdaptors.register(Boolean.class, new BooleanAdaptor());
+ _booleanAdaptors.register(Number.class, new NumberAdaptor());
+ _booleanAdaptors.register(Collection.class, new CollectionAdaptor());
+ _booleanAdaptors.register(String.class, new StringAdaptor());
+
+ // Register a default, catch-all adaptor.
+
+ _booleanAdaptors.register(Object.class, new BoolAdaptor()
+ {
+ public boolean coerce(Object value)
+ {
+ return true;
+ }
+ });
+ }
+
+ /**
+ * {@link AdaptorRegistry} used to extract an {@link Iterator} from
+ * an arbitrary object.
+ *
+ **/
+
+ private static AdaptorRegistry _iteratorAdaptors = new AdaptorRegistry();
+
+ private abstract static class IteratorAdaptor
+ {
+ /**
+ * Coeerces the object into an {@link Iterator}.
+ *
+ **/
+
+ abstract public Iterator coerce(Object value);
+ }
+
+ private static class DefaultIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ return (Iterator) value;
+ }
+
+ }
+
+ private static class CollectionIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ Collection c = (Collection) value;
+
+ if (c.size() == 0)
+ return null;
+
+ return c.iterator();
+ }
+ }
+
+ private static class ObjectIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ return Collections.singleton(value).iterator();
+ }
+ }
+
+ private static class ObjectArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ Object[] array = (Object[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ return Arrays.asList(array).iterator();
+ }
+ }
+
+ private static class BooleanArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ boolean[] array = (boolean[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(array[i] ? Boolean.TRUE : Boolean.FALSE);
+
+ return l.iterator();
+ }
+ }
+
+ private static class ByteArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ byte[] array = (byte[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Byte(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class CharArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ char[] array = (char[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Character(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class ShortArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ short[] array = (short[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Short(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class IntArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ int[] array = (int[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Integer(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class LongArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ long[] array = (long[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Long(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class FloatArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ float[] array = (float[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Float(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ private static class DoubleArrayIteratorAdaptor extends IteratorAdaptor
+ {
+ public Iterator coerce(Object value)
+ {
+ double[] array = (double[]) value;
+
+ if (array.length == 0)
+ return null;
+
+ List l = new ArrayList(array.length);
+
+ for (int i = 0; i < array.length; i++)
+ l.add(new Double(array[i]));
+
+ return l.iterator();
+ }
+ }
+
+ static {
+ _iteratorAdaptors.register(Iterator.class, new DefaultIteratorAdaptor());
+ _iteratorAdaptors.register(Collection.class, new CollectionIteratorAdaptor());
+ _iteratorAdaptors.register(Object.class, new ObjectIteratorAdaptor());
+ _iteratorAdaptors.register(Object[].class, new ObjectArrayIteratorAdaptor());
+ _iteratorAdaptors.register(boolean[].class, new BooleanArrayIteratorAdaptor());
+ _iteratorAdaptors.register(byte[].class, new ByteArrayIteratorAdaptor());
+ _iteratorAdaptors.register(char[].class, new CharArrayIteratorAdaptor());
+ _iteratorAdaptors.register(short[].class, new ShortArrayIteratorAdaptor());
+ _iteratorAdaptors.register(int[].class, new IntArrayIteratorAdaptor());
+ _iteratorAdaptors.register(long[].class, new LongArrayIteratorAdaptor());
+ _iteratorAdaptors.register(float[].class, new FloatArrayIteratorAdaptor());
+ _iteratorAdaptors.register(double[].class, new DoubleArrayIteratorAdaptor());
+ }
+
+ /**
+ * Copys all informal {@link IBinding bindings} from a source component
+ * to the destination component. Informal bindings are bindings for
+ * informal parameters. This will overwrite parameters (formal or
+ * informal) in the
+ * destination component if there is a naming conflict.
+ *
+ *
+ **/
+
+ public static void copyInformalBindings(IComponent source, IComponent destination)
+ {
+ Collection names = source.getBindingNames();
+
+ if (names == null)
+ return;
+
+ IComponentSpecification specification = source.getSpecification();
+ Iterator i = names.iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+
+ // If not a formal parameter, then copy it over.
+
+ if (specification.getParameter(name) == null)
+ {
+ IBinding binding = source.getBinding(name);
+
+ destination.setBinding(name, binding);
+ }
+ }
+ }
+
+ /**
+ * Evaluates an object to determine its boolean value.
+ *
+ * <table border=1>
+ * <tr> <th>Class</th> <th>Test</th> </tr>
+ * <tr>
+ * <td>{@link Boolean}</td>
+ * <td>Self explanatory.</td>
+ * </tr>
+ * <tr> <td>{@link Number}</td>
+ * <td>True if non-zero, false otherwise.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Collection}</td>
+ * <td>True if contains any elements (non-zero size), false otherwise.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link String}</td>
+ * <td>True if contains any non-whitespace characters, false otherwise.</td>
+ * </tr>
+ * <tr>
+ * <td>Any Object array type</td>
+ * <td>True if contains any elements (non-zero length), false otherwise.</td>
+ * <tr>
+ *</table>
+ *
+ * <p>Any other non-null object evaluates to true.
+ *
+ **/
+
+ public static boolean evaluateBoolean(Object value)
+ {
+ if (value == null)
+ return false;
+
+ Class valueClass = value.getClass();
+ if (valueClass.isArray())
+ {
+ Object[] array = (Object[]) value;
+
+ return array.length > 0;
+ }
+
+ BoolAdaptor adaptor = (BoolAdaptor) _booleanAdaptors.getAdaptor(valueClass);
+
+ return adaptor.coerce(value);
+ }
+
+ /**
+ * Converts an Object into an {@link Iterator}, following some basic rules.
+ *
+ * <table border=1>
+ * <tr><th>Input Class</th> <th>Result</th> </tr>
+ * <tr><td>array</td> <td>Converted to a {@link List} and iterator returned.
+ * null returned if the array is empty. This works with both object arrays and
+ * arrays of scalars. </td>
+ * </tr>
+ * <tr><td>{@link Iterator}</td> <td>Returned as-is.</td>
+ * <tr><td>{@link Collection}</td> <td>Iterator returned, or null
+ * if the Collection is empty</td> </tr>
+
+ * <tr><td>Any other</td> <td>{@link Iterator} for singleton collection returned</td> </tr>
+ * <tr><td>null</td> <td>null returned</td> </tr>
+ * </table>
+ *
+ **/
+
+ public static Iterator coerceToIterator(Object value)
+ {
+ if (value == null)
+ return null;
+
+ IteratorAdaptor adaptor = (IteratorAdaptor) _iteratorAdaptors.getAdaptor(value.getClass());
+
+ return adaptor.coerce(value);
+ }
+
+ /**
+ * Gets the {@link Locale} for the given string, which is the result
+ * of {@link Locale#toString()}. If no such locale is already registered,
+ * a new instance is created, registered and returned.
+ *
+ *
+ **/
+
+ public static Locale getLocale(String s)
+ {
+ Locale result = null;
+
+ synchronized (_localeMap)
+ {
+ result = (Locale) _localeMap.get(s);
+ }
+
+ if (result == null)
+ {
+ StringSplitter splitter = new StringSplitter('_');
+ String[] terms = splitter.splitToArray(s);
+
+ switch (terms.length)
+ {
+ case 1 :
+
+ result = new Locale(terms[0], "");
+ break;
+
+ case 2 :
+
+ result = new Locale(terms[0], terms[1]);
+ break;
+
+ case 3 :
+
+ result = new Locale(terms[0], terms[1], terms[2]);
+ break;
+
+ default :
+
+ throw new IllegalArgumentException(
+ "Unable to convert '" + s + "' to a Locale.");
+ }
+
+ synchronized (_localeMap)
+ {
+ _localeMap.put(s, result);
+ }
+
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Closes the stream (if not null), ignoring any {@link IOException} thrown.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public static void close(InputStream stream)
+ {
+ if (stream != null)
+ {
+ try
+ {
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+
+ /**
+ * Gets a string from the TapestryStrings resource bundle.
+ * The string in the bundle
+ * is treated as a pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
+ *
+ * @since 1.0.8
+ *
+ **/
+
+ public static String format(String key, Object[] args)
+ {
+ if (_strings == null)
+ _strings = ResourceBundle.getBundle("org.apache.tapestry.TapestryStrings");
+
+ String pattern = _strings.getString(key);
+
+ if (args == null)
+ return pattern;
+
+ return MessageFormat.format(pattern, args);
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ **/
+
+ public static String getMessage(String key)
+ {
+ return format(key, null);
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ **/
+
+ public static String format(String key, Object arg)
+ {
+ return format(key, new Object[] { arg });
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static String format(String key, Object arg1, Object arg2)
+ {
+ return format(key, new Object[] { arg1, arg2 });
+ }
+
+ /**
+ * Convienience method for invoking {@link #format(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static String format(String key, Object arg1, Object arg2, Object arg3)
+ {
+ return format(key, new Object[] { arg1, arg2, arg3 });
+ }
+
+ private static final String UNKNOWN_VERSION = "Unknown";
+
+ /**
+ * Invoked when the class is initialized to read the current version file.
+ *
+ **/
+
+ private static final String readVersion()
+ {
+ Properties props = new Properties();
+
+ try
+ {
+ InputStream in = Tapestry.class.getResourceAsStream("Version.properties");
+
+ if (in == null)
+ return UNKNOWN_VERSION;
+
+ props.load(in);
+
+ in.close();
+
+ return props.getProperty("framework.version", UNKNOWN_VERSION);
+ }
+ catch (IOException ex)
+ {
+ return UNKNOWN_VERSION;
+ }
+
+ }
+
+ /**
+ * Returns the size of a collection, or zero if the collection is null.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static int size(Collection c)
+ {
+ if (c == null)
+ return 0;
+
+ return c.size();
+ }
+
+ /**
+ * Returns the length of the array, or 0 if the array is null.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static int size(Object[] array)
+ {
+ if (array == null)
+ return 0;
+
+ return array.length;
+ }
+
+ /**
+ * Returns true if the Map is null or empty.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static boolean isEmpty(Map map)
+ {
+ return map == null || map.isEmpty();
+ }
+
+ /**
+ * Returns true if the Collection is null or empty.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static boolean isEmpty(Collection c)
+ {
+ return c == null || c.isEmpty();
+ }
+
+ /**
+ * Converts a {@link Map} to an even-sized array of key/value
+ * pairs. This may be useful when using a Map as service parameters
+ * (with {@link org.apache.tapestry.link.DirectLink}. Assuming the keys
+ * and values are simple objects (String, Boolean, Integer, etc.), then
+ * the representation as an array will encode more efficiently
+ * (via {@link org.apache.tapestry.util.io.DataSqueezer} than
+ * serializing the Map and its contents.
+ *
+ * @return the array of keys and values, or null if the input
+ * Map is null or empty
+ *
+ * @since 2.2
+ **/
+
+ public static Object[] convertMapToArray(Map map)
+ {
+ if (isEmpty(map))
+ return null;
+
+ Set entries = map.entrySet();
+
+ Object[] result = new Object[2 * entries.size()];
+ int x = 0;
+
+ Iterator i = entries.iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ result[x++] = entry.getKey();
+ result[x++] = entry.getValue();
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts an even-sized array of objects back
+ * into a {@link Map}.
+ *
+ * @see #convertMapToArray(Map)
+ * @return a Map, or null if the array is null or empty
+ * @since 2.2
+ *
+ **/
+
+ public static Map convertArrayToMap(Object[] array)
+ {
+ if (array == null || array.length == 0)
+ return null;
+
+ if (array.length % 2 != 0)
+ throw new IllegalArgumentException(getMessage("Tapestry.even-sized-array"));
+
+ Map result = new HashMap();
+
+ int x = 0;
+ while (x < array.length)
+ {
+ Object key = array[x++];
+ Object value = array[x++];
+
+ result.put(key, value);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the application root location, which is in the
+ * {@link javax.servlet.ServletContext}, based on
+ * the {@link javax.servlet.http.HttpServletRequest#getServletPath() servlet path}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static IResourceLocation getApplicationRootLocation(IRequestCycle cycle)
+ {
+ RequestContext context = cycle.getRequestContext();
+ ServletContext servletContext = context.getServlet().getServletContext();
+ String servletPath = context.getRequest().getServletPath();
+
+ // Could strip off the servlet name (i.e., "app" in "/app") but
+ // there's no need.
+
+ return new ContextResourceLocation(servletContext, servletPath);
+ }
+
+ /**
+ * Given a Class, creates a presentable name for the class, even if the
+ * class is a scalar type or Array type.
+ *
+ * @since 3.0
+ */
+
+ public static String getClassName(Class subject)
+ {
+ if (subject.isArray())
+ return getClassName(subject.getComponentType()) + "[]";
+
+ return subject.getName();
+ }
+
+ /**
+ * Selects the first {@link org.apache.tapestry.ILocation} in an array of objects.
+ * Skips over nulls. The objects may be instances of
+ * {Location or {@link org.apache.tapestry.ILocatable}. May return null
+ * if no Location found found.
+ *
+ **/
+
+ public static ILocation findLocation(Object[] locations)
+ {
+ for (int i = 0; i < locations.length; i++)
+ {
+ Object location = locations[i];
+
+ if (location == null)
+ continue;
+
+ if (location instanceof ILocation)
+ return (ILocation) location;
+
+ if (location instanceof ILocatable)
+ {
+ ILocatable locatable = (ILocatable) location;
+ ILocation result = locatable.getLocation();
+
+ if (result != null)
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates an exception indicating the binding value is null.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static BindingException createNullBindingException(IBinding binding)
+ {
+ return new BindingException(getMessage("null-value-for-binding"), binding);
+ }
+
+ /** @since 3.0 **/
+
+ public static ApplicationRuntimeException createNoSuchComponentException(
+ IComponent component,
+ String id,
+ ILocation location)
+ {
+ return new ApplicationRuntimeException(
+ format("no-such-component", component.getExtendedId(), id),
+ component,
+ location,
+ null);
+ }
+
+ /** @since 3.0 **/
+
+ public static BindingException createRequiredParameterException(
+ IComponent component,
+ String parameterName)
+ {
+ return new BindingException(
+ format("required-parameter", parameterName, component.getExtendedId()),
+ component,
+ null,
+ component.getBinding(parameterName),
+ null);
+ }
+
+ /** @since 3.0 **/
+
+ public static ApplicationRuntimeException createRenderOnlyPropertyException(
+ IComponent component,
+ String propertyName)
+ {
+ return new ApplicationRuntimeException(
+ format("render-only-property", propertyName, component.getExtendedId()),
+ component,
+ null,
+ null);
+ }
+
+ /**
+ * Clears the list of method invocations.
+ * @see #checkMethodInvocation(Object, String, Object)
+ *
+ * @since 3.0
+ */
+
+ public static void clearMethodInvocations()
+ {
+ _invokedMethodIds.set(null);
+ }
+
+ /**
+ * Adds a method invocation to the list of invocations. This is done
+ * in a super-class implementations.
+ *
+ * @see #checkMethodInvocation(Object, String, Object)
+ * @since 3.0
+ *
+ */
+
+ public static void addMethodInvocation(Object methodId)
+ {
+ List methodIds = (List) _invokedMethodIds.get();
+
+ if (methodIds == null)
+ {
+ methodIds = new ArrayList();
+ _invokedMethodIds.set(methodIds);
+ }
+
+ methodIds.add(methodId);
+ }
+
+ /**
+ * Checks to see if a particular method has been invoked. The method is identified by a
+ * methodId (usually a String). The methodName and object are used to create an
+ * error message.
+ *
+ * <p>
+ * The caller should invoke {@link #clearMethodInvocations()}, then invoke a method on
+ * the object. The super-class implementation should invoke {@link #addMethodInvocation(Object)}
+ * to indicate that it was, in fact, invoked. The caller then invokes
+ * this method to vlaidate that the super-class implementation was invoked.
+ *
+ * <p>
+ * The list of method invocations is stored in a {@link ThreadLocal} variable.
+ *
+ * @since 3.0
+ */
+
+ public static void checkMethodInvocation(Object methodId, String methodName, Object object)
+ {
+ List methodIds = (List) _invokedMethodIds.get();
+
+ if (methodIds != null && methodIds.contains(methodId))
+ return;
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "Tapestry.missing-method-invocation",
+ object.getClass().getName(),
+ methodName));
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(
+ IComponent component,
+ String propertyName,
+ Object newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event = new ObservedChangeEvent(component, propertyName, newValue);
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(
+ IComponent component,
+ String propertyName,
+ boolean newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(
+ component,
+ propertyName,
+ newValue ? Boolean.TRUE : Boolean.FALSE);
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(
+ IComponent component,
+ String propertyName,
+ double newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Double(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(
+ IComponent component,
+ String propertyName,
+ float newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Float(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(IComponent component, String propertyName, int newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Integer(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(IComponent component, String propertyName, long newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Long(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(IComponent component, String propertyName, char newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Character(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(IComponent component, String propertyName, byte newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Byte(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Method used by pages and components to send notifications about
+ * property changes.
+ *
+ * @param component the component containing the property
+ * @param propertyName the name of the property which changed
+ * @param newValue the new value for the property
+ *
+ * @since 3.0
+ */
+ public static void fireObservedChange(
+ IComponent component,
+ String propertyName,
+ short newValue)
+ {
+ ChangeObserver observer = component.getPage().getChangeObserver();
+
+ if (observer == null)
+ return;
+
+ ObservedChangeEvent event =
+ new ObservedChangeEvent(component, propertyName, new Short(newValue));
+
+ observer.observeChange(event);
+ }
+
+ /**
+ * Returns true if the input is null or contains only whitespace.
+ *
+ * <p>
+ * Note: Yes, you'd think we'd use <code>StringUtils</code>, but with
+ * the change in names and behavior between releases, it is smarter
+ * to just implement our own little method!
+ *
+ * @since 3.0
+ */
+
+ public static boolean isBlank(String input)
+ {
+ if (input == null || input.length() == 0)
+ return true;
+
+ return input.trim().length() == 0;
+ }
+
+ /**
+ * Returns true if the input is not null and not empty (or only whitespace).
+ *
+ * @since 3.0
+ *
+ */
+
+ public static boolean isNonBlank(String input)
+ {
+ return !isBlank(input);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/TapestryStrings.properties b/tapestry-framework/src/org/apache/tapestry/TapestryStrings.properties
new file mode 100644
index 0000000..cac3ec4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/TapestryStrings.properties
@@ -0,0 +1,483 @@
+# $Id$
+# Copyright 2004 The Apache Software Foundation
+#
+# Licensed 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.
+#
+# Contains String contants used throughout the Tapestry framework.
+# To keep things organized, each key is in two parts: the simple class name
+# and a subkey within the class name.
+
+
+# Some general messages
+
+service-no-parameters=Service {0} requires no service parameters.
+service-single-parameter=Service {0} requires exactly one service parameter.
+service-single-context-parameter=Service {0} requires exactly one context parameter.
+service-requires-parameters=Service {0} requires at least one service parameter.
+service-incorrect-parameter-count=Service {0} requires exactly {1} service parameters.
+missing-resource=Could not locate resource {0}.
+invalid-field-name=Invalid field name: {0}.
+unable-to-resolve-class=Unable to resolve class {0}.
+field-not-defined=Field {0} does not exist.
+illegal-field-access=Cannot access field {0}.
+field-is-instance=Field {0} is an instance variable, not a class variable.
+deprecated-component-param=Parameter ''{1}'' of component {0} is deprecated, use parameter ''{2}'' instead.
+must-be-wrapped-by-form={1} components must be enclosed by a Form component.
+invalid-null-parameter=Parameter ''{0}'' may not be null.
+null-value-for-binding=Binding value is null.
+no-such-component=Component {0} does not contain a component {1}.
+required-parameter=Value for parameter ''{0}'' in component {1} is null, and a non-null value is required.
+render-only-property=Property ''{0}'' of {1} may only be accessed while the component is rendering.
+unsupported-property=Property ''{1}'' is not supported by {0}.
+must-be-contained-by-body={0} components must be contained by a Body component.
+illegal-encoding=The encoding ''{0}'' is not recognized.
+
+# org.apache.tapestry
+
+AbstractComponent.attempt-to-change-container=Attempt to change existing container.
+AbstractComponent.attempt-to-change-component-id=Attempt to change existing component id.
+AbstractComponent.null-container={0} container is null.
+AbstractComponent.attempt-to-change-page=Attempt to change existing containing page.
+AbstractComponent.attempt-to-change-spec=Attempt to change existing component specification.
+
+AbstractPage.attempt-to-change-locale=Attempt to change existing locale for a page.
+AbstractPage.attempt-to-change-name=Attempt to change existing name for a page.
+
+AbstractMarkupWriter.missing-constructor-parameters=Incomplete parameters to AbstractMarkupWriter constructor.
+AbstractMarkupWriter.tag-not-open=A tag must be open before attributes may be set in an IMarkupWriter.
+
+ApplicationServlet.could-not-locate-engine=Could not locate an engine to service this request.
+ApplicationServlet.could-not-parse-spec=Unable to parse application specification {0}.
+ApplicationServlet.get-app-path-not-overriden=Application servlet {0} does not provide an implementation of method getApplicationServletPath().
+ApplicationServlet.no-application-specification=Running application without an application specification.
+ApplicationServlet.engine-stateful-without-session=Engine {0} is stateful even though there is no HttpSession. Discarding the engine.
+
+BaseComponent.multiple-component-references=Template for component {0} contains multiple references to embedded component {1}.
+BaseComponent.unbalanced-close-tags=More closing tags the open tags in template.
+BaseComponent.unbalance-open-tags=Not all tags closed in template.
+BaseComponent.missing-component-spec-single=Template for component {0} does not reference embedded component:
+BaseComponent.missing-component-spec-multi=Template for component {0} does not reference embedded components:
+BaseComponent.and=and
+BaseComponent.dupe-template-expression=An expression for parameter ''{0}'' of component {1} in the template for {2} conflicts with an existing binding in the specification.
+BaseComponent.template-expression-for-informal-parameter=The template for {2} contains an expression for parameter ''{0}'' of component {1}, but {1} does not allow informal parameters.
+BaseComponent.template-expression-for-reserved-parameter=The template for {2} contains an expression for parameter ''{0}'' of component {1}, but ''{0}'' is a reserved parameter name.
+BaseComponent.dupe-string=A localized string reference for parameter ''{0}'' of component {1} in the template for {2} conflicts with an existing binding in the specification.
+
+BaseComponentTemplateLoader.dupe-component-id=Component {0} (at {1}) conflicts with a prior declaration in the specification (at {2}).
+BaseComponentTemplateLoader.bodyless-component=This component may not have a body.
+
+RedirectServlet.redirect-path=Redirecting to servlet at path {0}.
+RedirectServlet.redirecting=Redirecting servlet context URL to {0}.
+
+ResponseOutputStream.content-type-not-set=Content type of response never set.
+
+StaleLinkException.action-mismatch=Action id {0} matched component {1}, not {2}.
+StaleLinkException.component-mismatch=Action id {0} does not match component {1}.
+
+Tapestry.even-sized-array=An even-sized array of keys and values is required.
+Tapestry.missing-method-invocation=Class {0} overrides method ''{1}'' but does not invoke the super-class implementation.
+
+# org.apache.tapestry.asset
+
+AssetExternalizer.externalize-failure=Could not externalize asset {0} to {1}.
+AssetService.exception-report-title=Failure to export asset {0}.
+AssetService.checksum-failure=Checksum {0} does not match that of resource {1}.
+AssetService.checksum-compute-failure=Failed to compute checksum for resource {1}.
+
+ExternalAsset.resource-missing=Could not access external asset {0}.
+
+# org.apache.tapestry.bean
+
+BeanProvider.bean-not-defined=Component {0} does not define a bean name {1}.
+BeanProvider.instantiation-error=Unable to instantiate bean ''{0}'' (for component {1}) as class {2}: {3}
+AbstractBeanInitializer.unable-to-set-property=Unable to set property ''{0}'' of {1} to {2}.
+
+# org.apache.tapestry.binding
+
+AbstractBinding.wrong-type=Parameter {0} ({1}) is an instance of {2}, which does not inherit from {3}.
+AbstractBinding.wrong-interface=Parameter {0} ({1}) is an instance of {2}, which does not implement interface {3}.
+AbstractBinding.read-only-binding=Binding value may not be updated.
+
+ExpressionBinding.unable-to-resolve-expression=Unable to resolve expression ''{0}'' for {1}.
+ExpressionBinding.unable-to-update-expression=Unable to update expression ''{0}'' for {1} to {2}.
+
+ListenerBinding.invalid-access=Inappropriate invocation of {0} on instance of ListenerBinding.
+ListenerBinding.bsf-exception=Unable to execute listener script "{0}": {1}
+ListenerBinding.unable-to-undeclare-bean=Unable to undeclare bean ''{0}'' after executing "{0}".
+
+# org.apache.tapestry.callback
+
+DirectCallback.wrong-type=Component {0} does not implement the IDirect interface.
+
+# org.apache.tapestry.component
+
+Insert.unable-to-format=Unable to format object {0}.
+Any.element-not-defined=The Any component is not used in a template and the 'element' property is not bound.
+
+# org.apache.tapestry.param
+
+ParameterManager.no-accessor=Component {0} does not have accessor methods for property {1}.
+ParameterManager.property-not-read-write=Property {1} of component {0} is not read-write.
+ParameterManager.java-type-not-specified=No Java type was specified for parameter {0} of component {1}.
+ParameterManager.type-mismatch=Parameter {0} of component {1} is declared as {2}, but the property is {3}.
+ParameterManager.static-initialization-failure=Unable to set property {0} of component {1} from {2}.
+ParameterManager.incompatible-direction-and-binding=Parameter {0} of component {1} is direction {2} which is incompatible with {3}.
+
+# org.apache.tapestry.engine
+
+AbstractEngine.unable-to-process-client-request=Unable to process client request.
+AbstractEngine.unable-to-present-exception-page=Unable to present exception page.
+AbstractEngine.unknown-specification=<Unknown specification>
+AbstractEngine.unknown-service=Engine does not implement a service named ''{0}''.
+AbstractEngine.unable-to-begin-request=Tapestry unable to begin processing request.
+AbstractEngine.unable-to-cleanup-page=Unable to cleanup page {0}.
+AbstractEngine.unable-to-instantiate-visit=Unable to instantiate visit object from class {0}.
+AbstractEngine.unable-to-instantiate-global=Unable to instantiate global object from class {0}.
+AbstractEngine.unable-to-redirect=Unable to redirect to {0}.
+AbstractEngine.service-name-mismatch=Class {1} is registered as service {0} but provides service {2} instead.
+AbstractEngine.unable-to-instantiate-service=Unable to instantiate class {1} as service {0}.
+AbstractEngine.unable-to-find-dispatcher=Unable to find a request dispatcher for local resource ''{0}''.
+AbstractEngine.unable-to-forward=Unable to forward to local resource ''{0}''.
+AbstractEngine.unable-to-create-cleanup-context=Unable to create an instance of RequestContext to process end-of-session page cleanups.
+AbstractEngine.exception-during-cleanup=Exception during post-request cleanup.
+AbstractEngine.exception-during-cache-clear=Exception while clearing caches after request.
+AbstractEngine.validate-cycle=A validate cycle during page activation was detected: {0}.
+
+ActionService.context-parameters=Service action requires either three or four service contect parameters.
+ActionService.action-component-wrong-type=Component {0} does not implement the IAction interface.
+
+DefaultScriptSource.unable-to-parse-script=Unable to parse script {0}.
+
+DefaultSpecificationSource.no-match-for-alias=Could not find a component matching alias {0}.
+DefaultSpecificationSource.unable-to-locate-specification=Could not locate resource {0} in the classpath.
+DefaultSpecificationSource.unable-to-open-specification=Could not open specification {0}.
+DefaultSpecificationSource.unable-to-parse-specification=Could not parse specification {0}.
+
+DefaultTemplateSource.no-template-for-component=Could not find template for component {0} in locale {1}.
+DefaultTemplateSource.no-template-for-page=Could not find template for page {0} in locale {1}.
+DefaultTemplateSource.unable-to-parse-template=Could not parse template {0}.
+DefaultTemplateSource.unable-to-read-template=Could not read template {0}.
+
+DirectService.context-parameters=Service direct requires either three or four service context parameters.
+DirectService.component-wrong-type=Component {0} does not implement the IDirect interface.
+DirectService.stale-session-exception=Component {0} is stateful, but the HttpSession has expired (or has not yet been created).
+
+EngineServiceLink.unknown-parameter-name=Unknown parameter name ''{0}''.
+
+Namespace.no-such-page=Page ''{0}'' not found in {1}.
+Namespace.no-such-component-type=Component ''{0}'' not found in {1}.
+Namespace.application-namespace=application namespace
+Namespace.framework-namespace=framework namespace
+Namespace.nested-namespace=namespace ''{0}''
+Namespace.library-id-not-found=Library ''{0}'' not found in {1}.
+
+RequestCycle.invalid-null-name=Parameter name may not be null in RequestCycle.getPage(String).
+RequestCycle.form-rewind-failure=Failure to rewind form {0}.
+
+ResourceResolver.unable-to-load-class=Could not load class {0} from {1}: {2}
+
+TagSupportService.service-only=The tagsupport service does not support tag generation.
+TagSupportService.null-attribute=Request attribute ''{0}'' is required by the tagsupport service, but the value is null.
+TagSupportService.attribute-not-string=Request attribute ''{0}'' is an instance of {1}, not a string.
+TagSupportService.attribute-not-array=Request attribute ''{0}'' is an instance of {0}, not an object array.
+BaseEngine.recorder-has-uncommited-changes=Could not forget changes to page {0} because the page's recorder has uncommitted changes.
+BaseEngine.duplicate-page-recorder=Could not create a second page recorder for page {0}.
+
+ExternalService.page-not-compatible=Page {0} does not implement the IExternalPage interface.
+
+# org.apache.tapestry.enhance
+
+ComponentClassFactory.bad-property-type=Unable to convert ''{0}'' to a property type.
+ComponentClassFactory.property-type-mismatch=Unable to enhance class {0} because it contains property ''{1}'' of type {2}, not the expected type {3}.
+ComponentClassFactory.non-abstract-read=Unable to enhance class {0} because it implements a non-abstract read method for property ''{1}''.
+ComponentClassFactory.non-abstract-write=Unable to enhance class {0} because it implements a non-abstract write method for property ''{1}''.
+ComponentClassFactory.unable-to-introspect-class=Unable to introspect properties of class {0}.
+ComponentClassFactory.auto-must-be-required=Parameter ''{0}'' must be required or have a default value as it uses direction ''auto''.
+ComponentClassFactory.code-generation-error=A code generation error occured while enhancing class {0}.
+
+
+EnhancedClassLoader.unable-to-define-class=Unable to define class {0}: {1}
+
+MethodFabricator.no-more-arguments=No more arguments may be added once any local variables are added.
+
+DefaultComponentClassEnhancer.no-impl-for-abstract-method=Method ''{0}'' (declared in {1}) has no implementation in class {2} (or enhanced subclass {3}).
+
+
+# org.apache.tapestry.event
+
+ObservedChangeEvent.null-property-name=Must specify a non-null propertyName when creating ObservedChangeEvent for {0}.
+ObservedChangeEvent.must-be-serializable=Must specify a serializable object as the new value of property when creating an ObservedChangeEvent.
+
+# org.apache.tapestry.form
+
+AbstractFormComponent.must-be-contained-by-form=This component must be contained within a Form.
+
+Form.forms-may-not-nest=Forms may not be nested.
+Form.needs-body-for-event-handlers=A Form with event handlers must be enclosed by a Body component.
+Form.too-many-ids=Rewind of form {0} expected only {1} form elements, but an additional id was requested by component {2}.
+Form.too-few-ids=Rewind of form {0} expected {1} more form elements, starting with id ''{2}''.
+Form.id-mismatch=Rewind of form {0} expected allocated id #{1} to be ''{2}'', but was ''{3}'' (requested by component {4}).
+Form.encoding-type-contention=Components within Form {0} have requested conflicting encoding types ''{1}'' and ''{2}''.
+
+ListEdit.unable-to-convert-value=Unable to convert {0} to an external string in ListEdit component.
+ListEdit.unable-to-convert-string=Unable to convert {0} back into an object in ListEdit component.
+
+FormConditional.unable-to-convert-value=Unable to convert {0} to an external string in FormConditional component.
+FormConditional.unable-to-convert-string=Unable to convert {0} back into an object in FormConditional component.
+
+Option.must-be-contained-by-select=Option component must be contained within a Select.
+
+Radio.must-be-contained-by-group=Radio component must be contained within a RadioGroup.
+
+RadioGroup.may-not-nest=RadioGroup components may not be nested.
+
+Select.may-not-nest=Select components may not be nested.
+
+LinkSubmit.may-not-nest=LinkSubmit components may not be nested.
+
+# org.apache.tapestry.html
+
+Body.may-not-nest=Body components may not be nested.
+Body.include-classpath-script-only=Unable to include external script {0}: only classpath resources are supported.
+
+InsertText.conversion-error=Error converting text to lines (for InsertText component).
+
+Rollover.must-be-contained-by-body=Rollover components must be contained within a Body component.
+Rollover.must-be-contained-by-link=Rollover components must be contained within an ILinkComponent.
+
+Script.must-be-contained-by-body=Script components must be contained within a Body component.
+
+# org.apache.tapestry.contrib.inspector
+
+ShowEngine.could-not-serialize=Could not serialize the application engine.
+
+InspectorButton.must-be-contained-by-body=InspectorButton component must be contained within a Body component.
+
+# org.apache.tapestry.jsp
+
+URLRetriever.unable-to-find-dispatcher=Unable to find request dispatcher for servlet at ''{0}''.
+URLRetriever.io-exception=I/O exception messaging servlet {0}: {1}
+URLRetriever.servlet-exception=Servlet exception messaging servlet {0}: {1}
+
+AbstractLinkTag.io-exception=I/O exception writing output: {1}
+
+AbstractTapestryTag.unable-to-evaluate-expression=Unable to evaluate OGNL expression ''{0}'': {1}
+
+# org.apache.tapestry.link
+
+GestureLink.missing-service=No engine service name {0}.
+
+AbstractLinkComponent.no-nesting=ILinkComponents may not be nested.
+AbstractLinkComponent.events-need-body=A link component with multiple functions for a single event type must be contained within a Body.
+
+# org.apache.tapestry.listener
+
+ListenerMap.object-missing-method=Object {0} does not implement a listener method named ''{1}''.
+ListenerMap.unable-to-invoke-method=Unable to invoke method {0} on {1}: {2}
+
+# org.apache.tapestry.multipart
+
+UploadPart.unable-to-open-content-file=Unable to open uploaded file ''{0}''.
+UploadPart.write-failure=Error writing uploaded content to {0}: {1}
+
+DefaultMultipartDecoder.unable-to-decode=Unable to decode request: {0}
+DefaultMultipartDecoder.encoding-not-set=No encoding has been set for this request.
+
+# org.apache.tapestry.pageload
+
+PageLoader.formal-parameters-only=Component {0} allows only formal parameters, binding {1} is not allowed.
+PageLoader.required-parameter-not-bound=Required parameter {0} of component {1} is not bound.
+PageLoader.unable-to-load-specification=Unable to load component specification.
+PageLoader.class-not-component=Class {0} does not implement the IComponent interface.
+PageLoader.unable-to-instantiate=Unable to instantiate an instance of class {0}.
+PageLoader.page-not-allowed=Component {0} may not implement the IPage interface.
+PageLoader.class-not-page=Class {0} does not implement the IPage interface.
+PageLoader.unable-to-instantiate-component=Unable to instantiate component {0}: {1}
+PageLoader.missing-asset=Unable to locate asset ''{0}'' of component {1} as {2}.
+PageLoader.unable-to-initialize-property=Unable to initialize property {0} of {1}: {2}
+PageLoader.inherit-informal-invalid-component-formal-only=Component {0} allows only formal parameters, but has inherit-informal-parameters set.
+PageLoader.inherit-informal-invalid-container-formal-only=Component {0} allows only formal parameters, but it contains component {1} that has inherit-informal-parameters set.
+
+EstablishDefaultParameterValuesVisitor.parameter-must-have-no-default-value=Parameter {1} of component {0} is required and must not have a default value.
+
+# org.apache.tapestry.parse
+
+TextToken.error-trimming={0}: Failure trimming leading and trailing whitespace.
+
+SpecificationParser.fail-convert-boolean=Could not convert ''{0}'' to boolean.
+SpecificationParser.fail-convert-int=Could not convert ''{0}'' to integer.
+SpecificationParser.fail-convert-double=Could not convert ''{0}'' to double.
+SpecificationParser.fail-convert-long=Could not convert ''{0}'' to long.
+SpecificationParser.unexpected-component-public-id=Unexpected component specification with public identifier {0}.
+SpecificationParser.unexpected-application-public-id=Unexpected application specification with public identifier {0}.
+SpecificationParser.both-type-and-copy-of=Contained component {0} contains both type and copy-of attributes.
+SpecificationParser.missing-type-or-copy-of=Contained component {0} does not specify attribute type or copy-of.
+SpecificationParser.unable-to-copy=Unable to copy component {0}, which does not exist.
+SpecificationParser.invalid-parameter-name=Parameter ''{0}'' is an invalid name. Parameter names should be valid Java identifiers.
+SpecificationParser.invalid-page-name=''{0}'' is not a valid page name. Page names must start with a letter and consist only of letters, numbers, period, dash and underscore.
+SpecificationParser.invalid-component-type=''{0}'' is not a valid component type. Types must be valid Java identifiers.
+SpecificationParser.invalid-property-name=''{0}'' is not a valid JavaBean property name. Property names must be valid Java identifiers.
+SpecificationParser.invalid-bean-name=''{0}'' is not a valid helper bean name. Helper bean names must be valid Java identifiers.
+SpecificationParser.unknown-static-value-type=Unknown <static-value> type: ''{0}''.
+SpecificationParser.invalid-component-id=''{0}'' is not a valid component id. Component ids must be valid Java identifiers.
+SpecificationParser.invalid-asset-name=''{0}'' is not a valid asset name. Asset names must be valid Java identifiers.
+SpecificationParser.invalid-service-name=''{0}'' is not a valid service name. Service names must start with a letter, and contain only letters, numbers, dash, underscore and period.
+SpecificationParser.invalid-library-id=''{0}'' is not a valid library id. Library ids must be valid Java identifiers.
+SpecificationParser.invalid-extension-name=''{0}'' is not a valid extension name. Extension names must start with a letter, and contain only letters, numbers, dash and underscore.
+SpecificationParser.invalid-component-type=''{0}'' is not a valid component type.
+SpecificationParser.framework-library-id-is-reserved=The library id ''{0}'' is reserved and may not be used.
+SpecificationParser.no-attribute-and-body=It is not valid to specify a value for attribute ''{0}'' of <{1}> and provide a value in the body of the element.
+SpecificationParser.required-extended-attribute=Element <{0}> does not specify a value for attribute ''{1}'', or contain a body value.
+SpecificationParser.error-reading-resource=Unable to read {0}: {1}
+
+ValidatePublicIdRule.no-public-id=Document {0} does not define a public id.
+
+TemplateParser.comment-not-ended=Comment on line {0} did not end.
+TemplateParser.unclosed-tag=Tag <{0}> on line {1} is never closed.
+TemplateParser.unclosed-unknown-tag=Tag on line {1} is never closed.
+TemplateParser.missing-attribute-value=Tag <{0}> on line {1} is missing a value for attribute {2}.
+TemplateParser.content-block-may-not-be-ignored=Tag <{0}> on line {1} is the template content, and may not be in an ignored block.
+TemplateParser.content-block-may-not-be-empty=Tag <{0}> on line {1} is the template content, and may not be empty.
+TemplateParser.unknown-component-id=Tag <{0}> on line {1} references unknown component id ''{2}''.
+TemplateParser.component-may-not-be-ignored=Tag <{0}> on line {1} is a dynamic component, and may not appear inside an ignored block.
+TemplateParser.nested-ignore=Tag <{0}> on line {1} should be ignored, but is already inside an ignored block (ignored blocks may not be nested).
+TemplateParser.incomplete-close-tag=Incomplete close tag on line {0}.
+TemplateParser.improperly-nested-close-tag=Closing tag </{0}> on line {1} is improperly nested with tag <{2}> on line {3}.
+TemplateParser.unmatched-close-tag=Closing tag </{0}> on line {1} does not have a matching open tag.
+TemplateParser.component-id-invalid=Tag <{0}> on line {1} contains an invalid jwcid ''{2}''.
+TemplateParser.duplicate-tag-attribute=Tag <{0}> on line {1} contains more than one ''{2}'' attribute.
+
+
+TextToken.range-error={0}: out of range for template length {1}.
+TemplateToken.may-not-render={0} tokens may not render.
+
+# org.apache.tapestry.record
+
+PageRecorder.change-after-lock=Page recorder for page {0} is locked after a commit(), but received a change to property {1} of component {2}.
+PageRecorder.unable-to-persist=Unable to persist property {0} of component {1} as {2}.
+PageRecorder.null-property-name=A change event for component {0} failed to specify the name of the updated property.
+PageRecorder.unable-to-rollback=Unable to set property {0} of component {1} to {2}: {3}
+
+# org.apache.tapestry.resource
+
+ContextResourceLocation.unable-to-reference-context-path=Unable to reference context path ''{0}''.
+
+
+# org.apache.tapestry.script
+
+ScriptParser.unknown-public-id=Script uses unknown public indentifier {0}.
+ScriptParser.invalid-key=''{0}'' is not a valid key. Symbol keys must be valid Java identifiers.
+ScriptParser.unable-to-resolve-class=''{0}'' is not a resolvable class name.
+
+InputSymbolToken.required=Script symbol ''{0}'' is required, but not specified.
+InputSymbolToken.wrong-type=Script symbol ''{0}'' is {1}, not {2}.
+
+# org.apache.tapestry.spec
+
+LibrarySpecification.duplicate-child-namespace-id=A child namespace with id ''{0}'' already exists.
+LibrarySpecification.duplicate-page-name=A page named ''{0}'' already exists in this namespace.
+LibrarySpecification.duplicate-component-alias=A component alias ''{0}'' already exists in this namespace.
+LibrarySpecification.duplicate-service-name=A service named ''{0}'' already exists in this namespace.
+LibrarySpecification.duplicate-extension-name=An extension named ''{0}'' already exists in this namespace.
+LibrarySpecification.no-such-extension=No extension named ''{0}'' exists in this namespace.
+LibrarySpecification.extension-does-not-implement-interface=Extension ''{0}'' (class {1}) does not implement interface {2}.
+LibrarySpecification.extension-not-a-subclass=Extension ''{0}'' (class {1}) does not inherit from class {2}.
+
+ComponentSpecification.duplicate-asset={0}: already contains asset ''{1}''.
+ComponentSpecification.duplicate-component={0}: already contains component ''{1}''.
+ComponentSpecification.duplicate-parameter={0}: already contains parameter ''{1}''.
+ComponentSpecification.duplicate-bean={0}: already contains bean definition for ''{1}''.
+ComponentSpecification.duplicate-property-specification={0}: already contains property specification for property ''{1}''.
+
+ExtensionSpecification.duplicate-property={0}: already contains property configuration for ''{1}''.
+ExtensionSpecification.bad-class=Unable to locate class {0}.
+
+Direction.IN=in
+Direction.FORM=form
+Direction.CUSTOM=custom
+Direction.AUTO=auto
+
+# org.apache.tapestry.util
+
+AdaptorRegistry.duplicate-registration=A registration for class {0} already exists.
+AdaptorRegistry.adaptor-not-found=Could not find an adaptor for class {0}.
+
+JanitorThread.interval-locked=The interval for this janitor thread is locked.
+JanitorThread.illegal-interval=The interval for a janitor thread may not be less than 1 millisecond.
+
+MultiKey.null-keys=Must pass in non-empty array of keys.
+MultiKey.first-element-may-not-be-null=First element of keys may not be null.
+MultiKey.no-keys=No keys for this MultiKey.
+
+Pool.unable-to-instantiate-instance=Unable to instantiate new instance of class {0}.
+
+
+# org.apache.tapestry.util.io
+
+DataSqueezer.short-prefix=The adaptor prefix must contain at least one character.
+DataSqueezer.null-class=The dataClass may not be null.
+DataSqueezer.null-adaptor=The adaptor may not be null.
+DataSqueezer.prefix-out-of-range=DataSqueezer prefix must be in the range ''!'' to ''z''.
+DataSqueezer.adaptor-prefix-taken=An adaptor for prefix ''{0}'' is already registered.
+
+SerializableAdaptor.class-not-found=Class {0} not found.
+SerializableAdaptor.unable-to-convert=Cannot convert {0} into a modified Base64 character.
+SerializableAdaptor.unable-to-interpret-char=Cannot interpret ''{0}'' as a modified Base64 character.
+
+ComponentAddressAdaptor.no-separator=Invalid ComponentAddress encoding -- separator not present
+
+# org.apache.tapestry.util.prop
+
+PropertyFinder.unable-to-introspect-class=Unable to instrospect properties of class {0}.
+
+OgnlUtils.unable-to-update-expression=Unable to update expression ''{0}'' of {1} to {2}.
+OgnlUtils.unable-to-read-expression=Unable to read expression ''{0}'' of {1}.
+OgnlUtils.unable-to-parse-expression=Unable to parse expression ''{0}''.
+
+# org.apache.tapestry.util.xml
+
+AbstractDocumentParser.incorrect-document-type=Incorrect document type; expected {0} but received {1}.
+AbstractDocumentParser.unable-to-parse=Unable to parse {0}: {1}
+AbstractDocumentParser.unable-to-read=Error reading {0}: {1}
+AbstractDocumentParser.unable-to-construct-builder=Unable to construct DocumentBuilder: {0}
+AbstractDocumentParser.invalid-identifier={0} is not a valid identifier (in element {1}).
+AbstractDocumentParser.missing-resource=Resource at {0} does not exist.
+AbstractDocumentParser.unknown-public-id=Document {0} has an unexpected public id of ''{1}''.
+
+RuleDrivenParser.no-rule-for-element=No rule is defined for parsing element ''{0}''.
+RuleDrivenParser.resource-missing=Unable to find resource {0}.
+RuleDrivenParser.unable-to-open-resource=Unable to open resource {0}.
+RuleDrivenParser.parse-error=Unable to parse {0}: {1}
+
+# org.apache.tapestry.valid
+
+FieldLabel.no-display-name=Display name not specified and not provided by field {0}.
+FieldLabel.must-be-contained-by-form=This component must be contained within a Form.
+FieldLabel.no-delegate=No IValidationDelegate is available to ValidField {0}; it is specified as the delegate parameter of Form {1}.
+
+ValidField.no-delegate=No IValidationDelegate is available to ValidField {0}; it is specified as the delegate parameter of Form {1}.
+ValidField.must-be-contained-by-body=A ValidField using client-side validation must be enclosed by a Body component.
+
+NumberValidator.unknown-type=Unknown value type {0}.
+NumberValidator.no-adaptor-for-field=Unable to provide validation for field {0} (value type {1}).
+
+PatternValidator.pattern-match-error=Unable to match pattern {0} for field {1}.
+
+# org.apache.tapestry.wml
+
+Card.cards-may-not-nest=Cards may not be nested.
+Postfield.must-be-contained-by-go=This postfield must be contained within a Go.
+
+# org.apache.tapestry.contrib.components
+
+When.must-be-contained-by-choose=When component must be contained within a Choose.
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/AbstractAsset.java b/tapestry-framework/src/org/apache/tapestry/asset/AbstractAsset.java
new file mode 100644
index 0000000..d69dc68
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/AbstractAsset.java
@@ -0,0 +1,62 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+
+/**
+ * Base class for {@link org.apache.tapestry.IAsset} implementations. Provides
+ * the location property.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractAsset implements IAsset
+{
+ private IResourceLocation _resourceLocation;
+ private ILocation _location;
+
+ protected AbstractAsset(IResourceLocation resourceLocation, ILocation location)
+ {
+ _resourceLocation = resourceLocation;
+ _location = location;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public IResourceLocation getResourceLocation()
+ {
+ return _resourceLocation;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("resourceLocation", _resourceLocation);
+ builder.append("location", _location);
+
+ return builder.toString();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/AssetExternalizer.java b/tapestry-framework/src/org/apache/tapestry/asset/AssetExternalizer.java
new file mode 100644
index 0000000..22b6600
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/AssetExternalizer.java
@@ -0,0 +1,287 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IPropertySource;
+import org.apache.tapestry.util.StringSplitter;
+
+/**
+ * Responsible for copying assets from the classpath to an external directory that
+ * is visible to the web server. The externalizer is stored inside
+ * the {@link ServletContext} as a named attribute.
+ *
+ * <p>The externalizer uses the name <code>org.apache.tapestry.AssetExternalizer.<i>application name</i>
+ * </code>. It configures itself using two additional
+ * properties (searching in
+ * {@link org.apache.tapestry.IEngine#getPropertySource()}.
+ *
+ * <table border=1>
+ * <tr> <th>Parameter</th> <th>Description</th> </tr>
+ * <tr valign=top>
+ * <td><code>org.apache.tapestry.asset.dir</code> </td>
+ * <td>The directory to which assets will be copied.</td> </tr>
+ * <tr valign=top>
+ * <td><code>org.apache.tapestry.asset.URL</code> </td>
+ * <td>The corresponding URL for the asset directory.</td> </tr>
+ * </table>
+ *
+ * <p>If either of these parameters is null, then no externalization occurs.
+ * Private assets will still be available, just less efficiently, as the application
+ * will be invoked via its servlet and, ultimately, the {@link AssetService} will need
+ * to retrieve the asset.
+ *
+ * <p>Assets maintain thier directory structure when copied. For example,
+ * an asset with a resource path of <code>/com/skunkworx/Banner.gif</code> would
+ * be copied to the file system as <code><i>dir</i>/com/skunkworx/Banner.gif</code> and
+ * would have a URL of <code><i>URL</i>/com/skunkworx/Banner.gif</code>.
+ *
+ * <p>The externalizer will create any directories as needed.
+ *
+ * <p>The externalizer will not overwrite existing files. When a new version of the application
+ * is deployed with changed assets, there are two deployment stategies:
+ * <ul>
+ * <li>Delete the existing asset directory and allow the externalizer to recreate and
+ * repopulate it.
+ * <li>Change the asset directory and URL, allowing the old and new assets to exist
+ * side-by-side.
+ * </ul>
+ *
+ * <p>When using the second approach, it is best to use a directory that has
+ * a version number in it, for example, <code>D:/inetpub/assets/0</code> mapped to the URL
+ * <code>/assets/0</code>. When a new version of the application is deployed, the trailing
+ * version number is incremented from 0 to 1.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class AssetExternalizer
+{
+ private static final Log LOG = LogFactory.getLog(AssetExternalizer.class);
+
+ private IResourceResolver _resolver;
+ private File _assetDir;
+ private String _URL;
+
+ /**
+ * A map from resource path (as a String) to final URL (as a String).
+ *
+ **/
+
+ private Map _resources = new HashMap();
+
+ private static final int BUFFER_SIZE = 2048;
+
+ protected AssetExternalizer(IRequestCycle cycle)
+ {
+ _resolver = cycle.getEngine().getResourceResolver();
+
+ IPropertySource properties = cycle.getEngine().getPropertySource();
+
+
+ String directory = properties.getPropertyValue("org.apache.tapestry.asset.dir");
+
+ if (directory == null)
+ return;
+
+ _URL = properties.getPropertyValue("org.apache.tapestry.asset.URL");
+
+ if (_URL == null)
+ return;
+
+ _assetDir = new File(directory);
+
+ LOG.debug("Initialized with directory " + _assetDir + " mapped to " + _URL);
+ }
+
+ protected void externalize(String resourcePath) throws IOException
+ {
+ String[] path;
+ int i;
+ File file;
+ StringSplitter splitter;
+ InputStream in;
+ OutputStream out;
+ int bytesRead;
+ URL inputURL;
+ byte[] buffer;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Externalizing " + resourcePath);
+
+ file = _assetDir;
+
+ // Resources are always split by the unix seperator, even on Win32.
+
+ splitter = new StringSplitter('/');
+
+ path = splitter.splitToArray(resourcePath);
+
+ // The path is expected to start with a leading slash, but the StringSplitter
+ // will ignore that leading slash.
+
+ for (i = 0; i < path.length - 1; i++)
+ {
+ // Doing it this way makes sure the path seperators are right.
+
+ file = new File(file, path[i]);
+ }
+
+ // Make sure the directories exist.
+
+ file.mkdirs();
+
+ file = new File(file, path[path.length - 1]);
+
+ // If the file exists, then assume all is well. This is OK for development,
+ // but there may be multithreading (or even multiprocess) race conditions
+ // around the creation of the file.
+
+ if (file.exists())
+ return;
+
+ // Get the resource and copy it to the file.
+
+ inputURL = _resolver.getResource(resourcePath);
+ if (inputURL == null)
+ throw new IOException(Tapestry.format("missing-resource", resourcePath));
+
+ in = inputURL.openStream();
+
+ out = new FileOutputStream(file);
+
+ buffer = new byte[BUFFER_SIZE];
+
+ while (true)
+ {
+ bytesRead = in.read(buffer, 0, BUFFER_SIZE);
+ if (bytesRead < 0)
+ break;
+
+ out.write(buffer, 0, bytesRead);
+ }
+
+ in.close();
+ out.close();
+
+ // The file is copied!
+ }
+
+ /**
+ * Gets the externalizer singleton for the application. If it does not already
+ * exist, it is created and stored into the {@link ServletContext}.
+ *
+ * <p>Each Tapestry application within a single {@link ServletContext}
+ * will have its own externalizer; they are differentiated by the
+ * application name.
+ *
+ * @see org.apache.tapestry.spec.ApplicationSpecification#getName()
+ *
+ **/
+
+ public static AssetExternalizer get(IRequestCycle cycle)
+ {
+ HttpServlet servlet = cycle.getRequestContext().getServlet();
+ ServletContext context = servlet.getServletContext();
+
+ String servletName = servlet.getServletName();
+
+ String attributeName = "org.apache.tapestry.AssetExternalizer:" + servletName;
+
+ AssetExternalizer result = (AssetExternalizer) context.getAttribute(attributeName);
+
+ if (result == null)
+ {
+ result = new AssetExternalizer(cycle);
+ context.setAttribute(attributeName, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the URL to a private resource. If the resource was
+ * previously copied out of the classpath, the previously
+ * generated URL is returned.
+ *
+ * <p>If the asset directory and URL are not configured, then
+ * returns null.
+ *
+ * <p>Otherwise, the asset is copied out to the asset directory,
+ * the URL is constructed (and recorded for later) and the URL is
+ * returned.
+ *
+ * <p>This method is not explicitly synchronized but should work
+ * multi-threaded. It synchronizes on the internal
+ * <code>Map</code> used to map resource paths to URLs.
+ *
+ * @param resourcePath The full path of the resource within the
+ * classpath. This is expected to include a leading slash. For
+ * example: <code>/com/skunkworx/Banner.gif</code>.
+ *
+ **/
+
+ public String getURL(String resourcePath)
+ {
+ String result;
+
+ if (_assetDir == null)
+ return null;
+
+ synchronized (_resources)
+ {
+ result = (String) _resources.get(resourcePath);
+
+ if (result != null)
+ return result;
+
+ try
+ {
+ externalize(resourcePath);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AssetExternalizer.externalize-failure", resourcePath, _assetDir),
+ ex);
+ }
+
+ result = _URL + resourcePath;
+
+ _resources.put(resourcePath, result);
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/AssetService.java b/tapestry-framework/src/org/apache/tapestry/asset/AssetService.java
new file mode 100644
index 0000000..8872c6d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/AssetService.java
@@ -0,0 +1,220 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.AbstractService;
+import org.apache.tapestry.engine.IEngineServiceView;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * A service for building URLs to and accessing {@link org.apache.tapestry.IAsset}s.
+ * Most of the work is deferred to the {@link org.apache.tapestry.IAsset} instance.
+ *
+ * <p>The retrieval part is directly linked to {@link PrivateAsset}.
+ * The service responds to a URL that encodes the path of a resource
+ * within the classpath. The
+ * {@link #service(IEngineServiceView, IRequestCycle, ResponseOutputStream)}
+ * method reads the resource and streams it out.
+ *
+ * <p>TBD: Security issues. Should only be able to retrieve a
+ * resource that was previously registerred in some way
+ * ... otherwise, hackers will be able to suck out the .class files
+ * of the application!
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public class AssetService extends AbstractService
+{
+ /**
+ * Defaults MIME types, by extension, used when the servlet container
+ * doesn't provide MIME types. ServletExec Debugger, for example,
+ * fails to do provide these.
+ *
+ **/
+
+ private final static Map _mimeTypes;
+
+ static {
+ _mimeTypes = new HashMap(17);
+ _mimeTypes.put("css", "text/css");
+ _mimeTypes.put("gif", "image/gif");
+ _mimeTypes.put("jpg", "image/jpeg");
+ _mimeTypes.put("jpeg", "image/jpeg");
+ _mimeTypes.put("htm", "text/html");
+ _mimeTypes.put("html", "text/html");
+ }
+
+ private static final int BUFFER_SIZE = 10240;
+
+ /**
+ * Builds a {@link ILink} for a {@link PrivateAsset}.
+ *
+ * <p>A single parameter is expected, the resource path of the asset
+ * (which is expected to start with a leading slash).
+ *
+ **/
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (Tapestry.size(parameters) != 2)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-incorrect-parameter-count", Tapestry.ASSET_SERVICE, new Integer(2)));
+
+ // Service is stateless
+
+ return constructLink(cycle, Tapestry.ASSET_SERVICE, null, parameters, false);
+ }
+
+ public String getName()
+ {
+ return Tapestry.ASSET_SERVICE;
+ }
+
+ private static String getMimeType(String path)
+ {
+ String key;
+ String result;
+ int dotx;
+
+ dotx = path.lastIndexOf('.');
+ key = path.substring(dotx + 1).toLowerCase();
+
+ result = (String) _mimeTypes.get(key);
+
+ if (result == null)
+ result = "text/plain";
+
+ return result;
+ }
+
+ /**
+ * Retrieves a resource from the classpath and returns it to the
+ * client in a binary output stream.
+ *
+ * <p>TBD: Security issues. Hackers can download .class files.
+ *
+ *
+ **/
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws IOException
+ {
+ Object[] parameters = getParameters(cycle);
+
+ if (Tapestry.size(parameters) != 2)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-incorrect-parameter-count", Tapestry.ASSET_SERVICE, new Integer(2)));
+
+ String resourcePath = (String) parameters[0];
+ String checksum = (String) parameters[1];
+
+ URL resourceURL = engine.getResourceResolver().getResource(resourcePath);
+
+ if (resourceURL == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("missing-resource", resourcePath));
+
+ String actualChecksum = engine.getResourceChecksumSource().getChecksum(resourceURL);
+
+ if (!actualChecksum.equals(checksum))
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AssetService.checksum-failure", checksum, resourcePath));
+ }
+
+ URLConnection resourceConnection = resourceURL.openConnection();
+
+ ServletContext servletContext = cycle.getRequestContext().getServlet().getServletContext();
+
+ writeAssetContent(engine, cycle, output, resourcePath, resourceConnection, servletContext);
+ }
+
+ /** @since 2.2 **/
+
+ private void writeAssetContent(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output,
+ String resourcePath,
+ URLConnection resourceConnection,
+ ServletContext servletContext)
+ {
+ // Getting the content type and length is very dependant
+ // on support from the application server (represented
+ // here by the servletContext).
+
+ String contentType = servletContext.getMimeType(resourcePath);
+ int contentLength = resourceConnection.getContentLength();
+
+ try
+ {
+ if (contentLength > 0)
+ cycle.getRequestContext().getResponse().setContentLength(contentLength);
+
+ // Set the content type. If the servlet container doesn't
+ // provide it, try and guess it by the extension.
+
+ if (contentType == null || contentType.length() == 0)
+ contentType = getMimeType(resourcePath);
+
+ output.setContentType(contentType);
+
+ // Disable any further buffering inside the ResponseOutputStream
+
+ output.forceFlush();
+
+ InputStream input = resourceConnection.getInputStream();
+
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ while (true)
+ {
+ int bytesRead = input.read(buffer);
+
+ if (bytesRead < 0)
+ break;
+
+ output.write(buffer, 0, bytesRead);
+ }
+
+ input.close();
+ }
+ catch (Throwable ex)
+ {
+ String title = Tapestry.format("AssetService.exception-report-title", resourcePath);
+
+ engine.reportException(title, ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/ContextAsset.java b/tapestry-framework/src/org/apache/tapestry/asset/ContextAsset.java
new file mode 100644
index 0000000..ec62510
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/ContextAsset.java
@@ -0,0 +1,80 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.resource.ContextResourceLocation;
+
+/**
+ * An asset whose path is relative to the {@link javax.servlet.ServletContext} containing
+ * the application.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public class ContextAsset extends AbstractAsset implements IAsset
+{
+ private String _resolvedURL;
+
+ public ContextAsset(ContextResourceLocation resourceLocation, ILocation location)
+ {
+ super(resourceLocation, location);
+ }
+
+ /**
+ * Generates a URL for the client to retrieve the asset. The context path
+ * is prepended to the asset path, which means that assets deployed inside
+ * web applications will still work (if things are configured properly).
+ *
+ **/
+
+ public String buildURL(IRequestCycle cycle)
+ {
+ if (_resolvedURL == null)
+ {
+ IEngine engine = cycle.getEngine();
+ String contextPath = engine.getContextPath();
+
+ _resolvedURL = contextPath + getResourceLocation().getPath();
+ }
+
+ return _resolvedURL;
+ }
+
+ public InputStream getResourceAsStream(IRequestCycle cycle)
+ {
+ try
+ {
+ URL url = getResourceLocation().getResourceURL();
+
+ return url.openStream();
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ContextAsset.resource-missing", getResourceLocation()),
+ ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/ExternalAsset.java b/tapestry-framework/src/org/apache/tapestry/asset/ExternalAsset.java
new file mode 100644
index 0000000..75b4c6e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/ExternalAsset.java
@@ -0,0 +1,78 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A reference to an external URL. {@link ExternalAsset}s are not
+ * localizable.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ExternalAsset extends AbstractAsset
+{
+ private String _URL;
+
+ public ExternalAsset(String URL, ILocation location)
+ {
+ super(null, location);
+
+ _URL = URL;
+ }
+
+ /**
+ * Simply returns the URL of the external asset.
+ *
+ **/
+
+ public String buildURL(IRequestCycle cycle)
+ {
+ return _URL;
+ }
+
+ public InputStream getResourceAsStream(IRequestCycle cycle)
+ {
+ URL url;
+
+ try
+ {
+ url = new URL(_URL);
+
+ return url.openStream();
+ }
+ catch (Exception ex)
+ {
+ // MalrformedURLException or IOException
+
+ throw new ApplicationRuntimeException(Tapestry.format("ExternalAsset.resource-missing", _URL), ex);
+ }
+
+ }
+
+ public String toString()
+ {
+ return "ExternalAsset[" + _URL + "]";
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/PrivateAsset.java b/tapestry-framework/src/org/apache/tapestry/asset/PrivateAsset.java
new file mode 100644
index 0000000..fe2a701
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/PrivateAsset.java
@@ -0,0 +1,105 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+
+/**
+ * An implementation of {@link org.apache.tapestry.IAsset} for localizable assets within
+ * the JVM's classpath.
+ *
+ * <p>The localization code here is largely cut-and-paste from
+ * {@link ContextAsset}.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public class PrivateAsset extends AbstractAsset
+{
+
+ private AssetExternalizer _externalizer;
+
+ public PrivateAsset(ClasspathResourceLocation resourceLocation, ILocation location)
+ {
+ super(resourceLocation, location);
+ }
+
+ /**
+ * Gets the localized version of the resource. Build
+ * the URL for the resource. If possible, the application's
+ * {@link AssetExternalizer} is located, to copy the resource to
+ * a directory visible to the web server.
+ *
+ **/
+
+ public String buildURL(IRequestCycle cycle)
+ {
+ if (_externalizer == null)
+ _externalizer = AssetExternalizer.get(cycle);
+
+ String path = getResourceLocation().getPath();
+
+ String externalURL = _externalizer.getURL(path);
+
+ if (externalURL != null)
+ return externalURL;
+
+ // Otherwise, the service is responsible for dynamically retrieving the
+ // resource.
+
+ IEngine engine = cycle.getEngine();
+
+ URL resourceURL = engine.getResourceResolver().getResource(path);
+ String checksum = engine.getResourceChecksumSource().getChecksum(resourceURL);
+
+ String[] parameters = new String[] { path, checksum };
+
+ AssetService service = (AssetService) engine.getService(Tapestry.ASSET_SERVICE);
+ ILink link = service.getLink(cycle, null, parameters);
+
+ return link.getURL();
+ }
+
+ public InputStream getResourceAsStream(IRequestCycle cycle)
+ {
+ IResourceLocation location = getResourceLocation();
+
+ try
+ {
+ URL url = location.getResourceURL();
+
+ return url.openStream();
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PrivateAsset.resource-missing", location),
+ ex);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSource.java b/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSource.java
new file mode 100644
index 0000000..6c21fe8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSource.java
@@ -0,0 +1,42 @@
+// Copyright 2004, 2005 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.net.URL;
+
+/**
+ * Calculates the checksum value, as a string, for a particular classpath resource. This is primarily
+ * used by the {@link org.apache.tapestry.asset.AssetService} to authenticate requests (you are not
+ * allowed access to a resource unless you can provide the correct checksum value).
+ *
+ * This code is based on code from Howard Lewis Ship from the upcoming 3.1 release.
+ *
+ * @author Paul Ferraro
+ * @since 3.0.3
+ */
+public interface ResourceChecksumSource
+{
+ /**
+ * Returns the checksum value for the given resource.
+ * @param resourceURL the url of a resource
+ * @return the checksum value of the specified resource
+ */
+ public String getChecksum(URL resourceURL);
+
+ /**
+ * Clears the internal cache.
+ */
+ public void reset();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSourceImpl.java b/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSourceImpl.java
new file mode 100644
index 0000000..c9015e2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/ResourceChecksumSourceImpl.java
@@ -0,0 +1,120 @@
+//Copyright 2005 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.asset;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Implementation of {@link org.apache.tapestry.asset.ResourceDigestSource} that calculates an
+ * checksum using a message digest and configured encoder.
+ *
+ * This code is based on code from Howard Lewis Ship from the upcoming 3.1 release.
+ *
+ * @author Paul Ferraro
+ * @since 3.0.3
+ */
+public class ResourceChecksumSourceImpl implements ResourceChecksumSource
+{
+ private static final int BUFFER_SIZE = 4096;
+
+ private Map _cache = new HashMap();
+
+ private String _digestAlgorithm;
+
+ private BinaryEncoder _encoder;
+
+ public ResourceChecksumSourceImpl(String digestAlgorithm, BinaryEncoder encoder)
+ {
+ _digestAlgorithm = digestAlgorithm;
+ _encoder = encoder;
+ }
+
+ /**
+ * Checksum is obtained from cache if possible.
+ * If not, checksum is computed using {@link #computeChecksum(URL)}
+ * @see org.apache.tapestry.asset.ResourceDigestSource#getChecksum(java.net.URL)
+ */
+ public String getChecksum(URL resourceURL)
+ {
+ synchronized (_cache)
+ {
+ String checksum = (String) _cache.get(resourceURL);
+
+ if (checksum == null)
+ {
+ checksum = computeChecksum(resourceURL);
+
+ _cache.put(resourceURL, checksum);
+ }
+
+ return checksum;
+ }
+ }
+
+ /**
+ * @see org.apache.tapestry.asset.ResourceDigestSource#reset()
+ */
+ public void reset()
+ {
+ synchronized (_cache)
+ {
+ _cache.clear();
+ }
+ }
+
+ /**
+ * Computes a message digest of the specified resource and encodes it into a string.
+ * @param resourceURL the url of a resource
+ * @return the checksum value of the specified resource
+ */
+ protected String computeChecksum(URL resourceURL)
+ {
+ try
+ {
+ MessageDigest digest = MessageDigest.getInstance(_digestAlgorithm);
+
+ InputStream inputStream = new BufferedInputStream(resourceURL.openStream(), BUFFER_SIZE);
+
+ byte[] block = new byte[BUFFER_SIZE];
+
+ int read = inputStream.read(block);
+
+ while (read >= 0)
+ {
+ digest.update(block, 0, read);
+
+ read = inputStream.read(block);
+ }
+
+ inputStream.close();
+
+ return new String(_encoder.encode(digest.digest()));
+ }
+ catch (Exception e)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AssetService.checksum-compute-failure", resourceURL), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/asset/package.html b/tapestry-framework/src/org/apache/tapestry/asset/package.html
new file mode 100644
index 0000000..3b1ac80
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/asset/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Implementations of {@link org.apache.tapestry.IAsset}, as well as
+the {@link org.apache.tapestry.asset.AssetExternalizer}, used to handle private assets.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/AbstractBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/AbstractBeanInitializer.java
new file mode 100644
index 0000000..cec1d08
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/AbstractBeanInitializer.java
@@ -0,0 +1,67 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.BaseLocatable;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Base class for initializing a property of a JavaBean.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+abstract public class AbstractBeanInitializer extends BaseLocatable implements IBeanInitializer
+{
+ protected String _propertyName;
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+ /** @since 3.0 **/
+
+ public void setPropertyName(String propertyName)
+ {
+ _propertyName = propertyName;
+ }
+
+ protected void setBeanProperty(IResourceResolver resolver, Object bean, Object value)
+ {
+ try
+ {
+ OgnlUtils.set(_propertyName, resolver, bean, value);
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ String message =
+ Tapestry.format(
+ "AbstractBeanInitializer.unable-to-set-property",
+ _propertyName,
+ bean,
+ value);
+
+ throw new ApplicationRuntimeException(message, getLocation(), ex);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/BeanProvider.java b/tapestry-framework/src/org/apache/tapestry/bean/BeanProvider.java
new file mode 100644
index 0000000..325d081
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/BeanProvider.java
@@ -0,0 +1,319 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBeanProvider;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.event.PageRenderListener;
+import org.apache.tapestry.spec.BeanLifecycle;
+import org.apache.tapestry.spec.IBeanSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Basic implementation of the {@link IBeanProvider} interface.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.4
+ **/
+
+public class BeanProvider implements IBeanProvider, PageDetachListener, PageRenderListener
+{
+ private static final Log LOG = LogFactory.getLog(BeanProvider.class);
+
+ /**
+ * Indicates whether this instance has been registered with its
+ * page as a PageDetachListener. Registration only occurs
+ * the first time a bean with lifecycle REQUEST is instantiated.
+ *
+ **/
+
+ private boolean _registeredForDetach = false;
+
+ /**
+ * Indicates whether this instance has been registered as a render
+ * listener with the page.
+ *
+ **/
+
+ private boolean _registeredForRender = false;
+
+ /**
+ * The component for which beans are being created and tracked.
+ *
+ **/
+
+ private IComponent _component;
+
+ /**
+ * Used for instantiating classes.
+ *
+ **/
+
+ private IResourceResolver _resolver;
+
+ /**
+ * Map of beans, keyed on name.
+ *
+ **/
+
+ private Map _beans;
+
+ /**
+ * Set of bean names provided by this provider.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private Set _beanNames;
+
+ public BeanProvider(IComponent component)
+ {
+ this._component = component;
+ IEngine engine = component.getPage().getEngine();
+ _resolver = engine.getResourceResolver();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Created BeanProvider for " + component);
+
+ }
+
+ /** @since 1.0.6 **/
+
+ public Collection getBeanNames()
+ {
+ if (_beanNames == null)
+ {
+ Collection c = _component.getSpecification().getBeanNames();
+
+ if (c == null || c.isEmpty())
+ _beanNames = Collections.EMPTY_SET;
+ else
+ _beanNames = Collections.unmodifiableSet(new HashSet(c));
+ }
+
+ return _beanNames;
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ public IComponent getComponent()
+ {
+ return _component;
+ }
+
+ public Object getBean(String name)
+ {
+ Object bean = null;
+
+ if (_beans != null)
+ bean = _beans.get(name);
+
+ if (bean != null)
+ return bean;
+
+ IBeanSpecification spec = _component.getSpecification().getBeanSpecification(name);
+
+ if (spec == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BeanProvider.bean-not-defined",
+ _component.getExtendedId(),
+ name));
+
+ bean = instantiateBean(name, spec);
+
+ BeanLifecycle lifecycle = spec.getLifecycle();
+
+ if (lifecycle == BeanLifecycle.NONE)
+ return bean;
+
+ if (_beans == null)
+ _beans = new HashMap();
+
+ _beans.put(name, bean);
+
+ // The first time in a request that a REQUEST lifecycle bean is created,
+ // register with the page to be notified at the end of the
+ // request cycle.
+
+ if (lifecycle == BeanLifecycle.REQUEST && !_registeredForDetach)
+ {
+ _component.getPage().addPageDetachListener(this);
+ _registeredForDetach = true;
+ }
+
+ if (lifecycle == BeanLifecycle.RENDER && !_registeredForRender)
+ {
+ _component.getPage().addPageRenderListener(this);
+ _registeredForRender = true;
+ }
+
+ // No need to register if a PAGE lifecycle bean; those can stick around
+ // forever.
+
+ return bean;
+ }
+
+ private Object instantiateBean(String beanName, IBeanSpecification spec)
+ {
+ String className = spec.getClassName();
+ Object bean = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Instantiating instance of " + className);
+
+ // Do it the hard way!
+
+ try
+ {
+ Class beanClass = _resolver.findClass(className);
+
+ bean = beanClass.newInstance();
+ }
+ catch (Exception ex)
+ {
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "BeanProvider.instantiation-error",
+ new Object[] {
+ beanName,
+ _component.getExtendedId(),
+ className,
+ ex.getMessage()}),
+ spec.getLocation(),
+ ex);
+ }
+
+ // OK, have the bean, have to initialize it.
+
+ List initializers = spec.getInitializers();
+
+ if (initializers == null)
+ return bean;
+
+ Iterator i = initializers.iterator();
+ while (i.hasNext())
+ {
+ IBeanInitializer iz = (IBeanInitializer) i.next();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Initializing property " + iz.getPropertyName());
+
+ iz.setBeanProperty(this, bean);
+ }
+
+ return bean;
+ }
+
+ /**
+ * Removes all beans with the REQUEST lifecycle. Beans with
+ * the PAGE lifecycle stick around, and beans with no lifecycle
+ * were never stored in the first place.
+ *
+ **/
+
+ public void pageDetached(PageEvent event)
+ {
+ removeBeans(BeanLifecycle.REQUEST);
+ }
+
+ /**
+ * Removes any beans with the specified lifecycle.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private void removeBeans(BeanLifecycle lifecycle)
+ {
+ if (_beans == null)
+ return;
+
+ IComponentSpecification spec = null;
+
+ Iterator i = _beans.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry e = (Map.Entry) i.next();
+ String name = (String) e.getKey();
+
+ if (spec == null)
+ spec = _component.getSpecification();
+
+ IBeanSpecification s = spec.getBeanSpecification(name);
+
+ if (s.getLifecycle() == lifecycle)
+ {
+ Object bean = e.getValue();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Removing " + lifecycle.getName() + " bean " + name + ": " + bean);
+
+ i.remove();
+ }
+ }
+ }
+
+ /** @since 1.0.8 **/
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ /** @since 2.2 **/
+
+ public void pageBeginRender(PageEvent event)
+ {
+ }
+
+ /** @since 2.2 **/
+
+ public void pageEndRender(PageEvent event)
+ {
+ removeBeans(BeanLifecycle.RENDER);
+ }
+
+ /** @since 2.2 **/
+
+ public boolean canProvideBean(String name)
+ {
+ return getBeanNames().contains(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/BeanProviderPropertyAccessor.java b/tapestry-framework/src/org/apache/tapestry/bean/BeanProviderPropertyAccessor.java
new file mode 100644
index 0000000..02fbf26
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/BeanProviderPropertyAccessor.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import java.util.Map;
+
+import ognl.ObjectPropertyAccessor;
+import ognl.OgnlException;
+
+import org.apache.tapestry.IBeanProvider;
+
+/**
+ * Adapts a {@link org.apache.tapestry.IBeanProvider} to
+ * <a href="http://www.ognl.org">OGNL</a> by exposing the named
+ * beans provided by the provider as read-only properties of
+ * the provider.
+ *
+ * <p>This is registered by {@link org.apache.tapestry.AbstractComponent}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class BeanProviderPropertyAccessor extends ObjectPropertyAccessor
+{
+ /**
+ * Checks to see if the name matches the name of a bean inside
+ * the provider and returns that bean if so.
+ * Otherwise, invokes the super implementation.
+ *
+ **/
+
+ public Object getProperty(Map context, Object target, Object name) throws OgnlException
+ {
+ IBeanProvider provider = (IBeanProvider)target;
+ String beanName = (String)name;
+
+ if (provider.canProvideBean(beanName))
+ return provider.getBean(beanName);
+
+ return super.getProperty(context, target, name);
+ }
+
+ /**
+ * Returns true if the name matches a bean provided by the provider.
+ * Otherwise invokes the super implementation.
+ *
+ **/
+
+ public boolean hasGetProperty(Map context, Object target, Object oname) throws OgnlException
+ {
+ IBeanProvider provider = (IBeanProvider)target;
+ String beanName = (String)oname;
+
+ if (provider.canProvideBean(beanName))
+ return true;
+
+ return super.hasGetProperty(context, target, oname);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/Default.java b/tapestry-framework/src/org/apache/tapestry/bean/Default.java
new file mode 100644
index 0000000..b1ecd74
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/Default.java
@@ -0,0 +1,89 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.util.pool.IPoolable;
+
+/**
+ * A helper bean to assist with providing defaults for unspecified
+ * parameters. It is initalized
+ * with an {@link IBinding} and a default value. It's value property
+ * is either the value of the binding, but if the binding is null,
+ * or the binding returns null, the default value is returned.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public class Default implements IPoolable
+{
+ private IBinding binding;
+ private Object defaultValue;
+
+ public void resetForPool()
+ {
+ binding = null;
+ defaultValue = null;
+ }
+
+ public void setBinding(IBinding value)
+ {
+ binding = value;
+ }
+
+ public IBinding getBinding()
+ {
+ return binding;
+ }
+
+ public void setDefaultValue(Object value)
+ {
+ defaultValue = value;
+ }
+
+ public Object getDefaultValue()
+ {
+ return defaultValue;
+ }
+
+ /**
+ * Returns the value of the binding. However, if the binding is null, or the binding
+ * returns null, then the defaultValue is returned instead.
+ *
+ **/
+
+ public Object getValue()
+ {
+ if (binding == null)
+ return defaultValue;
+
+ Object value = binding.getObject();
+
+ if (value == null)
+ return defaultValue;
+
+ return value;
+ }
+
+ /** @since 3.0 **/
+
+ public void discardFromPool()
+ {
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/EvenOdd.java b/tapestry-framework/src/org/apache/tapestry/bean/EvenOdd.java
new file mode 100644
index 0000000..22639c8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/EvenOdd.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+/**
+ * Used to emit a stream of alterating string values: "even", "odd", etc. This
+ * is often used in the Inspector pages to make the class of a <tr> alternate
+ * for presentation reasons.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class EvenOdd
+{
+ private boolean even = true;
+
+ /**
+ * Returns "even" or "odd". Whatever it returns on one invocation, it will
+ * return the opposite on the next. By default, the first value
+ * returned is "even".
+ *
+ **/
+
+ public String getNext()
+ {
+ String result = even ? "even" : "odd";
+
+ even = !even;
+
+ return result;
+ }
+
+ public boolean isEven()
+ {
+ return even;
+ }
+
+ /**
+ * Overrides the even flag.
+ *
+ **/
+
+ public void setEven(boolean value)
+ {
+ even = value;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/ExpressionBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/ExpressionBeanInitializer.java
new file mode 100644
index 0000000..390465d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/ExpressionBeanInitializer.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.IBeanProvider;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ *
+ * Initializes a helper bean property from an OGNL expression (relative
+ * to the bean's {@link IComponent}).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class ExpressionBeanInitializer extends AbstractBeanInitializer
+{
+ protected String _expression;
+
+ public void setBeanProperty(IBeanProvider provider, Object bean)
+ {
+ IResourceResolver resolver = provider.getResourceResolver();
+ IComponent component = provider.getComponent();
+
+ Object value = OgnlUtils.get(_expression, resolver, component);
+
+ setBeanProperty(resolver, bean, value);
+ }
+
+ /** @since 3.0 **/
+
+ public String getExpression()
+ {
+ return _expression;
+ }
+
+ /** @since 3.0 **/
+
+ public void setExpression(String expression)
+ {
+ _expression = expression;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/FieldBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/FieldBeanInitializer.java
new file mode 100644
index 0000000..7666ce8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/FieldBeanInitializer.java
@@ -0,0 +1,119 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import java.lang.reflect.Field;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBeanProvider;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Initializes a bean with the value of a public static field.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class FieldBeanInitializer extends AbstractBeanInitializer
+{
+ protected String _fieldName;
+ protected Object _fieldValue;
+ private boolean _fieldResolved = false;
+
+ public synchronized void setBeanProperty(IBeanProvider provider, Object bean)
+ {
+ IResourceResolver resolver = provider.getResourceResolver();
+
+ if (!_fieldResolved)
+ resolveField(resolver);
+
+ setBeanProperty(resolver, bean, _fieldValue);
+ }
+
+ private void resolveField(IResourceResolver resolver)
+ {
+ if (_fieldResolved)
+ return;
+
+ // This is all copied out of of FieldBinding!!
+
+ int dotx = _fieldName.lastIndexOf('.');
+
+ if (dotx < 0)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("invalid-field-name", _fieldName));
+
+ String className = _fieldName.substring(0, dotx);
+ String simpleFieldName = _fieldName.substring(dotx + 1);
+
+ // Simple class names are assumed to be in the java.lang package.
+
+ if (className.indexOf('.') < 0)
+ className = "java.lang." + className;
+
+ Class targetClass = null;
+
+ try
+ {
+ targetClass = resolver.findClass(className);
+ }
+ catch (Throwable t)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("unable-to-resolve-class", className),
+ t);
+ }
+
+ Field field = null;
+
+ try
+ {
+ field = targetClass.getField(simpleFieldName);
+ }
+ catch (NoSuchFieldException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("field-not-defined", _fieldName),
+ ex);
+ }
+
+ // Get the value of the field. null means look for it as a static
+ // variable.
+
+ try
+ {
+ _fieldValue = field.get(null);
+ }
+ catch (IllegalAccessException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("illegal-field-access", _fieldName),
+ ex);
+ }
+ catch (NullPointerException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("field-is-instance", _fieldName),
+ ex);
+ }
+
+ _fieldResolved = true;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/IBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/IBeanInitializer.java
new file mode 100644
index 0000000..6ccf1da
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/IBeanInitializer.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.IBeanProvider;
+import org.apache.tapestry.ILocationHolder;
+
+/**
+ * Interface for a set of classes used to initialize helper beans.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public interface IBeanInitializer extends ILocationHolder
+{
+ /**
+ * Invoked by the {@link IBeanProvider} to initialize
+ * a property of the bean.
+ *
+ **/
+
+ public void setBeanProperty(IBeanProvider provider, Object bean);
+
+ /**
+ * Returns the name of the property this initializer
+ * will set.
+ *
+ **/
+
+ public String getPropertyName();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/MessageBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/MessageBeanInitializer.java
new file mode 100644
index 0000000..bae2270
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/MessageBeanInitializer.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.IBeanProvider;
+import org.apache.tapestry.IComponent;
+
+/**
+ * A bean initializer that uses a localized string from the containing
+ * component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class MessageBeanInitializer extends AbstractBeanInitializer
+{
+ protected String _key;
+
+
+ public void setBeanProperty(IBeanProvider provider, Object bean)
+ {
+ IComponent component = provider.getComponent();
+ String value = component.getMessage(_key);
+
+ setBeanProperty(provider.getResourceResolver(), bean, value);
+ }
+
+ /** @since 3.0 **/
+
+ public String getKey()
+ {
+ return _key;
+ }
+
+ /** @since 3.0 **/
+
+ public void setKey(String key)
+ {
+ _key = key;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/StaticBeanInitializer.java b/tapestry-framework/src/org/apache/tapestry/bean/StaticBeanInitializer.java
new file mode 100644
index 0000000..9183f56
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/StaticBeanInitializer.java
@@ -0,0 +1,36 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.bean;
+
+import org.apache.tapestry.IBeanProvider;
+
+/**
+ * Initializes a bean with a static value.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public class StaticBeanInitializer extends AbstractBeanInitializer
+{
+ protected Object _value;
+
+ public void setBeanProperty(IBeanProvider provider, Object bean)
+ {
+ setBeanProperty(provider.getResourceResolver(), bean, _value);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/bean/package.html b/tapestry-framework/src/org/apache/tapestry/bean/package.html
new file mode 100644
index 0000000..2c03eb9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/bean/package.html
@@ -0,0 +1,16 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Contains useful helper beans, an implementation of
+the {@link org.apache.tapestry.IBeanProvider} interface, and
+several interfaces and classes related to initializing helper beans.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/AbstractBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/AbstractBinding.java
new file mode 100644
index 0000000..4b13b2c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/AbstractBinding.java
@@ -0,0 +1,245 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Base class for {@link IBinding} implementations.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class AbstractBinding implements IBinding
+{
+ /** @since 3.0 **/
+
+ private ILocation _location;
+
+ /**
+ * A mapping from primitive types to wrapper types.
+ *
+ **/
+
+ private static final Map PRIMITIVE_TYPES = new HashMap();
+
+ static {
+ PRIMITIVE_TYPES.put(boolean.class, Boolean.class);
+ PRIMITIVE_TYPES.put(byte.class, Byte.class);
+ PRIMITIVE_TYPES.put(char.class, Character.class);
+ PRIMITIVE_TYPES.put(short.class, Short.class);
+ PRIMITIVE_TYPES.put(int.class, Integer.class);
+ PRIMITIVE_TYPES.put(long.class, Long.class);
+ PRIMITIVE_TYPES.put(float.class, Float.class);
+ PRIMITIVE_TYPES.put(double.class, Double.class);
+ }
+
+ /** @since 3.0 **/
+
+ protected AbstractBinding(ILocation location)
+ {
+ _location = location;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ /**
+ * Cooerces the raw value into a true or false, according to the
+ * rules set by {@link Tapestry#evaluateBoolean(Object)}.
+ *
+ **/
+
+ public boolean getBoolean()
+ {
+ return Tapestry.evaluateBoolean(getObject());
+ }
+
+ public int getInt()
+ {
+ Object raw;
+
+ raw = getObject();
+ if (raw == null)
+ throw Tapestry.createNullBindingException(this);
+
+ if (raw instanceof Number)
+ {
+ return ((Number) raw).intValue();
+ }
+
+ if (raw instanceof Boolean)
+ {
+ return ((Boolean) raw).booleanValue() ? 1 : 0;
+ }
+
+ // Save parsing for last. This may also throw a number format exception.
+
+ return Integer.parseInt((String) raw);
+ }
+
+ public double getDouble()
+ {
+ Object raw;
+
+ raw = getObject();
+ if (raw == null)
+ throw Tapestry.createNullBindingException(this);
+
+ if (raw instanceof Number)
+ {
+ return ((Number) raw).doubleValue();
+ }
+
+ if (raw instanceof Boolean)
+ {
+ return ((Boolean) raw).booleanValue() ? 1 : 0;
+ }
+
+ // Save parsing for last. This may also throw a number format exception.
+
+ return Double.parseDouble((String) raw);
+ }
+
+ /**
+ * Gets the value for the binding. If null, returns null,
+ * otherwise, returns the String (<code>toString()</code>) version of
+ * the value.
+ *
+ **/
+
+ public String getString()
+ {
+ Object value;
+
+ value = getObject();
+ if (value == null)
+ return null;
+
+ return value.toString();
+ }
+
+ /**
+ * @throws BindingException always.
+ *
+ **/
+
+ public void setBoolean(boolean value)
+ {
+ throw createReadOnlyBindingException(this);
+ }
+
+ /**
+ * @throws BindingException always.
+ *
+ **/
+
+ public void setInt(int value)
+ {
+ throw createReadOnlyBindingException(this);
+ }
+
+ /**
+ * @throws BindingException always.
+ *
+ **/
+
+ public void setDouble(double value)
+ {
+ throw createReadOnlyBindingException(this);
+ }
+
+ /**
+ * @throws BindingException always.
+ *
+ **/
+
+ public void setString(String value)
+ {
+ throw createReadOnlyBindingException(this);
+ }
+
+ /**
+ * @throws BindingException always.
+ *
+ **/
+
+ public void setObject(Object value)
+ {
+ throw createReadOnlyBindingException(this);
+ }
+
+ /**
+ * Default implementation: returns true.
+ *
+ * @since 2.0.3
+ *
+ **/
+
+ public boolean isInvariant()
+ {
+ return true;
+ }
+
+ public Object getObject(String parameterName, Class type)
+ {
+ Object result = getObject();
+
+ if (result == null)
+ return result;
+
+ Class resultClass = result.getClass();
+
+ if (type.isAssignableFrom(resultClass))
+ return result;
+
+ if (type.isPrimitive() && isWrapper(type, resultClass))
+ return result;
+
+ String key =
+ type.isInterface() ? "AbstractBinding.wrong-interface" : "AbstractBinding.wrong-type";
+
+ String message =
+ Tapestry.format(
+ key,
+ new Object[] { parameterName, result, resultClass.getName(), type.getName()});
+
+ throw new BindingException(message, this);
+ }
+
+ public boolean isWrapper(Class primitiveType, Class subjectClass)
+ {
+ return PRIMITIVE_TYPES.get(primitiveType).equals(subjectClass);
+ }
+
+ /** @since 3.0 **/
+
+ protected BindingException createReadOnlyBindingException(IBinding binding)
+ {
+ return new BindingException(
+ Tapestry.getMessage("AbstractBinding.read-only-binding"),
+ binding);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/ExpressionBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/ExpressionBinding.java
new file mode 100644
index 0000000..15419e5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/ExpressionBinding.java
@@ -0,0 +1,600 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import java.util.Map;
+
+import ognl.Ognl;
+import ognl.OgnlException;
+import ognl.TypeConverter;
+
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.BeanLifecycle;
+import org.apache.tapestry.spec.IBeanSpecification;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.util.StringSplitter;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Implements a dynamic binding, based on getting and fetching
+ * values using JavaBeans property access. This is built
+ * upon the <a href="http://www.ognl.org">OGNL</a> library.
+ *
+ * <p><b>Optimization of the Expression</b>
+ *
+ * <p>There's a lot of room for optimization here because we can
+ * count on some portions of the expression to be
+ * effectively static. Note that we type the root object as
+ * {@link IComponent}. We have some expectations that
+ * certain properties of the root (and properties reachable from the root)
+ * will be constant for the lifetime of the binding. For example,
+ * components never change thier page or container. This means
+ * that certain property prefixes can be optimized:
+ *
+ * <ul>
+ * <li>page
+ * <li>container
+ * <li>components.<i>name</i>
+ * </ul>
+ *
+ * <p>This means that once an ExpressionBinding has been triggered,
+ * the {@link #toString()} method may return different values for the root
+ * component and the expression than was originally set.
+ *
+ * <p><b>Identifying Invariants</b>
+ *
+ * <p>Most expressions are fully dynamic; they must be
+ * resolved each time they are accessed. This can be somewhat inefficient.
+ * Tapestry can identify certain paths as invariant:
+ *
+ * <ul>
+ * <li>A component within the page hierarchy
+ * <li>An {@link org.apache.tapestry.IAsset} from then assets map (property <code>assets</code>)
+ * <li>A {@link org.apache.tapestry.IActionListener}
+ * from the listener map (property <code>listeners</code>)
+ * <li>A bean with a {@link org.apache.tapestry.spec.BeanLifecycle#PAGE}
+ * lifecycle (property <code>beans</code>)
+ * <li>A binding (property <code>bindings</code>)
+ * </ul>
+ *
+ * <p>
+ * These optimizations have some inherent dangers; they assume that
+ * the components have not overidden the specified properties;
+ * the last one (concerning helper beans) assumes that the
+ * component does inherit from {@link org.apache.tapestry.AbstractComponent}.
+ * If this becomes a problem in the future, it may be necessary to
+ * have the component itself involved in these determinations.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class ExpressionBinding extends AbstractBinding
+{
+ /**
+ * The root object against which the nested property name is evaluated.
+ *
+ **/
+
+ private IComponent _root;
+
+ /**
+ * The OGNL expression, as a string.
+ *
+ **/
+
+ private String _expression;
+
+ /**
+ * If true, then the binding is invariant, and cachedValue
+ * is the ultimate value.
+ *
+ **/
+
+ private boolean _invariant = false;
+
+ /**
+ * Stores the cached value for the binding, if invariant
+ * is true.
+ *
+ **/
+
+ private Object _cachedValue;
+
+ /**
+ * Parsed OGNL expression.
+ *
+ **/
+
+ private Object _parsedExpression;
+
+ /**
+ * Flag set once the binding has initialized.
+ * _cachedValue, _invariant and _final value
+ * for _expression
+ * are not valid until after initialization.
+ *
+ *
+ **/
+
+ private boolean _initialized;
+
+ private IResourceResolver _resolver;
+
+ /**
+ * The OGNL context for this binding. It is retained
+ * for the lifespan of the binding once created.
+ *
+ **/
+
+ private Map _context;
+
+ /**
+ * Creates a {@link ExpressionBinding} from the root object
+ * and an OGNL expression.
+ *
+ **/
+
+ public ExpressionBinding(
+ IResourceResolver resolver,
+ IComponent root,
+ String expression,
+ ILocation location)
+ {
+ super(location);
+
+ _resolver = resolver;
+ _root = root;
+ _expression = expression;
+ }
+
+ public String getExpression()
+ {
+ return _expression;
+ }
+
+ public IComponent getRoot()
+ {
+ return _root;
+ }
+
+ /**
+ * Gets the value of the property path, with the assistance of a
+ * OGNL.
+ *
+ * @throws BindingException if an exception is thrown accessing the property.
+ *
+ **/
+
+ public Object getObject()
+ {
+ initialize();
+
+ if (_invariant)
+ return _cachedValue;
+
+ return resolveProperty();
+ }
+
+ private Object resolveProperty()
+ {
+ try
+ {
+ return Ognl.getValue(_parsedExpression, getOgnlContext(), _root);
+ }
+ catch (OgnlException t)
+ {
+ throw new BindingException(
+ Tapestry.format(
+ "ExpressionBinding.unable-to-resolve-expression",
+ _expression,
+ _root),
+ this,
+ t);
+ }
+ }
+
+ /**
+ * Creates an OGNL context used to get or set a value.
+ * We may extend this in the future to set additional
+ * context variables (such as page, request cycle and engine).
+ * An optional type converter will be added to the OGNL context
+ * if it is specified as an application extension with the name
+ * {@link Tapestry#OGNL_TYPE_CONVERTER}.
+ *
+ **/
+
+ private Map getOgnlContext()
+ {
+ if (_context == null)
+ _context = Ognl.createDefaultContext(_root, _resolver);
+
+ if (_root.getPage() != null)
+ {
+ if (_root.getPage().getEngine() != null)
+ {
+ IApplicationSpecification appSpec = _root.getPage().getEngine().getSpecification();
+
+ if (appSpec != null && appSpec.checkExtension(Tapestry.OGNL_TYPE_CONVERTER))
+ {
+ TypeConverter typeConverter =
+ (TypeConverter) appSpec.getExtension(
+ Tapestry.OGNL_TYPE_CONVERTER,
+ TypeConverter.class);
+
+ Ognl.setTypeConverter(_context, typeConverter);
+ }
+ }
+ }
+
+ return _context;
+ }
+
+ /**
+ * Returns true if the binding is expected to always
+ * return the same value.
+ *
+ *
+ **/
+
+ public boolean isInvariant()
+ {
+ initialize();
+
+ return _invariant;
+ }
+
+ public void setBoolean(boolean value)
+ {
+ setObject(value ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ public void setInt(int value)
+ {
+ setObject(new Integer(value));
+ }
+
+ public void setDouble(double value)
+ {
+ setObject(new Double(value));
+ }
+
+ public void setString(String value)
+ {
+ setObject(value);
+ }
+
+ /**
+ * Sets up the helper object, but also optimizes the property path
+ * and determines if the binding is invarant.
+ *
+ **/
+
+ private void initialize()
+ {
+ if (_initialized)
+ return;
+
+ _initialized = true;
+
+ try
+ {
+ _parsedExpression = OgnlUtils.getParsedExpression(_expression);
+ }
+ catch (Exception ex)
+ {
+ throw new BindingException(ex.getMessage(), this, ex);
+ }
+
+ if (checkForConstant())
+ return;
+
+ try
+ {
+ if (!Ognl.isSimpleNavigationChain(_parsedExpression, getOgnlContext()))
+ return;
+ }
+ catch (OgnlException ex)
+ {
+ throw new BindingException(ex.getMessage(), this, ex);
+ }
+
+ // Split the expression into individual property names.
+ // We then optimize what we can from the expression. This will
+ // shorten the expression and, in some cases, eliminate
+ // it. We also check to see if the binding can be an invariant.
+
+ String[] split = new StringSplitter('.').splitToArray(_expression);
+
+ int count = optimizeRootObject(split);
+
+ // We'ver removed some or all of the initial elements of split
+ // but have to account for anthing left over.
+
+ if (count == split.length)
+ {
+ // The property path was something like "page" or "component.foo"
+ // and was completely eliminated.
+
+ _expression = null;
+ _parsedExpression = null;
+
+ _invariant = true;
+ _cachedValue = _root;
+
+ return;
+ }
+
+ _expression = reassemble(count, split);
+ _parsedExpression = OgnlUtils.getParsedExpression(_expression);
+
+ checkForInvariant(count, split);
+ }
+
+ /**
+ * Looks for common prefixes on the expression (provided pre-split) that
+ * are recognized as references to other components.
+ *
+ * @return the number of leading elements of the split expression that
+ * have been removed.
+ *
+ **/
+
+ private int optimizeRootObject(String[] split)
+ {
+ int i;
+
+ for (i = 0; i < split.length; i++)
+ {
+
+ if (split[i].equals("page"))
+ {
+ _root = _root.getPage();
+ continue;
+ }
+
+ if (split[i].equals("container"))
+ {
+ _root = _root.getContainer();
+ continue;
+ }
+
+ // Here's the tricky one ... if its of the form
+ // "components.foo" we can get the named component
+ // directly.
+
+ if (split[i].equals("components") && i + 1 < split.length)
+ {
+ _root = _root.getComponent(split[i + 1]);
+ i++;
+ continue;
+ }
+
+ // Not a recognized prefix, break the loop
+
+ break;
+ }
+
+ return i;
+ }
+
+ private boolean checkForConstant()
+ {
+ try
+ {
+ if (Ognl.isConstant(_parsedExpression, getOgnlContext()))
+ {
+ _invariant = true;
+
+ _cachedValue = resolveProperty();
+
+ return true;
+ }
+ }
+ catch (OgnlException ex)
+ {
+ throw new BindingException(
+ Tapestry.format(
+ "ExpressionBinding.unable-to-resolve-expression",
+ _expression,
+ _root),
+ this,
+ ex);
+ }
+
+ return false;
+ }
+
+ /**
+ * Reassembles the remainder of the split property path
+ * from the start point.
+ *
+ **/
+
+ private String reassemble(int start, String[] split)
+ {
+ int count = split.length - start;
+
+ if (count == 0)
+ return null;
+
+ if (count == 1)
+ return split[split.length - 1];
+
+ StringBuffer buffer = new StringBuffer();
+
+ for (int i = start; i < split.length; i++)
+ {
+ if (i > start)
+ buffer.append('.');
+
+ buffer.append(split[i]);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Checks to see if the binding can be converted to an invariant.
+ *
+ **/
+
+ private void checkForInvariant(int start, String[] split)
+ {
+ // For now, all of our conditions are two properties
+ // from a root component.
+
+ if (split.length - start != 2)
+ return;
+
+ try
+ {
+ if (!Ognl.isSimpleNavigationChain(_parsedExpression, getOgnlContext()))
+ return;
+ }
+ catch (OgnlException ex)
+ {
+ throw new BindingException(
+ Tapestry.format(
+ "ExpressionBinding.unable-to-resolve-expression",
+ _expression,
+ _root),
+ this,
+ ex);
+ }
+
+ String first = split[start];
+
+ if (first.equals("listeners"))
+ {
+ _invariant = true;
+
+ // Could cast to AbstractComponent, get listenersMap, etc.,
+ // but this is easier.
+
+ _cachedValue = resolveProperty();
+ return;
+ }
+
+ if (first.equals("assets"))
+ {
+ String name = split[start + 1];
+
+ _invariant = true;
+ _cachedValue = _root.getAsset(name);
+ return;
+ }
+
+ if (first.equals("beans"))
+ {
+ String name = split[start + 1];
+
+ IBeanSpecification bs = _root.getSpecification().getBeanSpecification(name);
+
+ if (bs == null || bs.getLifecycle() != BeanLifecycle.PAGE)
+ return;
+
+ // Again, could cast to AbstractComponent, but this
+ // is easier.
+
+ _invariant = true;
+ _cachedValue = resolveProperty();
+ return;
+ }
+
+ if (first.equals("bindings"))
+ {
+ String name = split[start + 1];
+
+ _invariant = true;
+ _cachedValue = _root.getBinding(name);
+ return;
+ }
+
+ // Not a recognized pattern for conversion
+ // to invariant.
+ }
+
+ /**
+ * Updates the property for the binding to the given value.
+ *
+ * @throws BindingException if the property can't be updated (typically
+ * due to an security problem, or a missing mutator method).
+ * @throws BindingException if the binding is invariant.
+ **/
+
+ public void setObject(Object value)
+ {
+ initialize();
+
+ if (_invariant)
+ throw createReadOnlyBindingException(this);
+
+ try
+ {
+ Ognl.setValue(_parsedExpression, getOgnlContext(), _root, value);
+ }
+ catch (OgnlException ex)
+ {
+ throw new BindingException(
+ Tapestry.format(
+ "ExpressionBinding.unable-to-update-expression",
+ _expression,
+ _root,
+ value),
+ this,
+ ex);
+ }
+ }
+
+ /**
+ * Returns the a String representing the property path. This includes
+ * the {@link IComponent#getExtendedId() extended id} of the root component
+ * and the property path ... once the binding is used, these may change
+ * due to optimization of the property path.
+ *
+ **/
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("ExpressionBinding[");
+ buffer.append(_root.getExtendedId());
+
+ if (_expression != null)
+ {
+ buffer.append(' ');
+ buffer.append(_expression);
+ }
+
+ if (_invariant)
+ {
+ buffer.append(" cachedValue=");
+ buffer.append(_cachedValue);
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/FieldBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/FieldBinding.java
new file mode 100644
index 0000000..249f243
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/FieldBinding.java
@@ -0,0 +1,149 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import java.lang.reflect.Field;
+
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+
+/**
+ *
+ * A type of static {@link org.apache.tapestry.IBinding} that gets it value from a public field
+ * (static class variable) of some class or interface.
+ *
+ * <p>The binding uses a field name, which consists of a fully qualified class name and
+ * a static field of that class seperated by a dot. For example: <code>com.foobar.SomeClass.SOME_FIELD</code>.
+ *
+ * <p>If the class specified is for the <code>java.lang</code> package, then the package may be
+ * ommitted. This allows <code>Boolean.TRUE</code> to be recognized as a valid value.
+ *
+ * <p>The {@link org.apache.tapestry.engine.IPageSource} maintains a cache of FieldBindings. This means that
+ * each field will be represented by a single binding ... that means that for any field,
+ * the <code>accessValue()</code> method (which obtains the value for the field using
+ * reflection) will only be invoked once.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @deprecated To be removed in 2.5 with no replacement. Can be accomplished using OGNL expressions.
+ *
+ **/
+
+public class FieldBinding extends AbstractBinding
+{
+ private String fieldName;
+ private boolean accessed;
+ private Object value;
+ private IResourceResolver resolver;
+
+ public FieldBinding(IResourceResolver resolver, String fieldName, ILocation location)
+ {
+ super(location);
+
+ this.resolver = resolver;
+ this.fieldName = fieldName;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer;
+
+ buffer = new StringBuffer("FieldBinding[");
+ buffer.append(fieldName);
+
+ if (accessed)
+ {
+ buffer.append(" (");
+ buffer.append(value);
+ buffer.append(')');
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ public Object getObject()
+ {
+ if (!accessed)
+ accessValue();
+
+ return value;
+ }
+
+ private void accessValue()
+ {
+ String className;
+ String simpleFieldName;
+ int dotx;
+ Class targetClass;
+ Field field;
+
+ dotx = fieldName.lastIndexOf('.');
+
+ if (dotx < 0)
+ throw new BindingException(Tapestry.format("invalid-field-name", fieldName), this);
+
+ // Hm. Should validate that there's a dot!
+
+ className = fieldName.substring(0, dotx);
+ simpleFieldName = fieldName.substring(dotx + 1);
+
+ // Simple class names are assumed to be in the java.lang package.
+
+ if (className.indexOf('.') < 0)
+ className = "java.lang." + className;
+
+ try
+ {
+ targetClass = resolver.findClass(className);
+ }
+ catch (Throwable t)
+ {
+ throw new BindingException(Tapestry.format("unable-to-resolve-class", className), this, t);
+ }
+
+ try
+ {
+ field = targetClass.getField(simpleFieldName);
+ }
+ catch (NoSuchFieldException ex)
+ {
+ throw new BindingException(Tapestry.format("field-not-defined", fieldName), this, ex);
+ }
+
+ // Get the value of the field. null means look for it as a static
+ // variable.
+
+ try
+ {
+ value = field.get(null);
+ }
+ catch (IllegalAccessException ex)
+ {
+ throw new BindingException(Tapestry.format("illegal-field-acccess", fieldName), this, ex);
+ }
+ catch (NullPointerException ex)
+ {
+ throw new BindingException(Tapestry.format("field-is-instance", fieldName), this, ex);
+ }
+
+ // Don't look for it again, even if the value is itself null.
+
+ accessed = true;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/ListenerBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/ListenerBinding.java
new file mode 100644
index 0000000..fd24bd8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/ListenerBinding.java
@@ -0,0 +1,206 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import org.apache.bsf.BSFException;
+import org.apache.bsf.BSFManager;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.pool.Pool;
+
+/**
+ * A very specialized binding that can be used as an {@link org.apache.tapestry.IActionListener},
+ * executing a script in a scripting language, via
+ * <a href="http://jakarta.apache.org/bsf">Bean Scripting Framework</a>.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ListenerBinding extends AbstractBinding implements IActionListener
+{
+ private static final Log LOG = LogFactory.getLog(ListenerBinding.class);
+
+ private static final String BSF_POOL_KEY = "org.apache.tapestry.BSFManager";
+
+ private String _language;
+ private String _script;
+ private IComponent _component;
+
+ public ListenerBinding(IComponent component, String language, String script, ILocation location)
+ {
+ super(location);
+
+ _component = component;
+ _language = language;
+ _script = script;
+ }
+
+ /**
+ * Always returns true.
+ *
+ **/
+
+ public boolean getBoolean()
+ {
+ return true;
+ }
+
+ public int getInt()
+ {
+ throw new BindingException(
+ Tapestry.format("ListenerBinding.invalid-access", "getInt()"),
+ this);
+ }
+
+ public double getDouble()
+ {
+ throw new BindingException(
+ Tapestry.format("ListenerBinding.invalid-access", "getDouble()"),
+ this);
+
+ }
+
+ /**
+ * Returns the underlying script.
+ *
+ **/
+
+ public String getString()
+ {
+ return _script;
+ }
+
+ /**
+ * Returns this.
+ *
+ **/
+
+ public Object getObject()
+ {
+ return this;
+ }
+
+ /**
+ * A ListenerBinding is also a {@link org.apache.tapestry.IActionListener}. It
+ * registers a number of beans with the BSF manager and invokes the
+ * script.
+ *
+ * <p>
+ * Registers the following bean:
+ * <ul>
+ * <li>component - the relevant {@link IComponent}, typically the same as the page
+ * <li>page - the {@link IPage} trigged by the request (obtained by {@link IRequestCycle#getPage()}
+ * <li>cycle - the {@link IRequestCycle}, from which can be found
+ * the {@link IEngine}, etc.
+ * </ul>
+ *
+ **/
+
+ public void actionTriggered(IComponent component, IRequestCycle cycle)
+ {
+ boolean debug = LOG.isDebugEnabled();
+
+ long startTime = debug ? System.currentTimeMillis() : 0;
+
+ BSFManager bsf = obtainBSFManager(cycle);
+
+ ILocation location = getLocation();
+
+ try
+ {
+ IPage page = cycle.getPage();
+
+ bsf.declareBean("component", _component, _component.getClass());
+ bsf.declareBean("page", page, page.getClass());
+ bsf.declareBean("cycle", cycle, cycle.getClass());
+
+ bsf.exec(
+ _language,
+ location.getResourceLocation().toString(),
+ location.getLineNumber(),
+ location.getLineNumber(),
+ _script);
+ }
+ catch (BSFException ex)
+ {
+ String message =
+ Tapestry.format("ListenerBinding.bsf-exception", location, ex.getMessage());
+
+ throw new ApplicationRuntimeException(message, _component, getLocation(), ex);
+ }
+ finally
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Cleaning up " + bsf);
+
+ undeclare(bsf, "component");
+ undeclare(bsf, "page");
+ undeclare(bsf, "cycle");
+
+ cycle.getEngine().getPool().store(BSF_POOL_KEY, bsf);
+
+ if (debug)
+ {
+ long endTime = System.currentTimeMillis();
+
+ LOG.debug(
+ "Execution of \"" + location + "\" took " + (endTime - startTime) + " millis");
+ }
+ }
+ }
+
+ private void undeclare(BSFManager bsf, String name)
+ {
+ try
+ {
+ bsf.undeclareBean(name);
+ }
+ catch (BSFException ex)
+ {
+ LOG.warn(Tapestry.format("ListenerBinding.unable-to-undeclare-bean", ex));
+ }
+ }
+
+ private BSFManager obtainBSFManager(IRequestCycle cycle)
+ {
+ IEngine engine = cycle.getEngine();
+ Pool pool = engine.getPool();
+
+ BSFManager result = (BSFManager) pool.retrieve(BSF_POOL_KEY);
+
+ if (result == null)
+ {
+ LOG.debug("Creating new BSFManager instance.");
+
+ result = new BSFManager();
+
+ result.setClassLoader(engine.getResourceResolver().getClassLoader());
+ }
+
+ return result;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/StaticBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/StaticBinding.java
new file mode 100644
index 0000000..80bf953
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/StaticBinding.java
@@ -0,0 +1,90 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * Stores a static (invariant) String as the value.
+ *
+ * <p>It may be useful to cache static bindings the way {@link FieldBinding}s are cached.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class StaticBinding extends AbstractBinding
+{
+ private String _value;
+ private boolean _parsedInt;
+ private int _intValue;
+ private boolean _parsedDouble;
+ private double _doubleValue;
+
+ public StaticBinding(String value, ILocation location)
+ {
+ super(location);
+
+ _value = value;
+ }
+
+ /**
+ * Interprets the static value as an integer.
+ *
+ **/
+
+ public int getInt()
+ {
+ if (!_parsedInt)
+ {
+ _intValue = Integer.parseInt(_value);
+ _parsedInt = true;
+ }
+
+ return _intValue;
+ }
+
+ /**
+ * Interprets the static value as a double.
+ *
+ **/
+
+ public double getDouble()
+ {
+ if (!_parsedDouble)
+ {
+ _doubleValue = Double.parseDouble(_value);
+ _parsedDouble = true;
+ }
+
+ return _doubleValue;
+ }
+
+ public String getString()
+ {
+ return _value;
+ }
+
+ public Object getObject()
+ {
+ return _value;
+ }
+
+ public String toString()
+ {
+ return "StaticBinding[" + _value + "]";
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/StringBinding.java b/tapestry-framework/src/org/apache/tapestry/binding/StringBinding.java
new file mode 100644
index 0000000..fd9ff7e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/StringBinding.java
@@ -0,0 +1,87 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.binding;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.ILocation;
+
+/**
+ * A binding that connects directly to a localized string for
+ * a component.
+ *
+ * @see IComponent#getString(String)
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.4
+ *
+ **/
+
+public class StringBinding extends AbstractBinding
+{
+ private IComponent _component;
+ private String _key;
+
+ public StringBinding(IComponent component, String key, ILocation location)
+ {
+ super(location);
+
+ _component = component;
+ _key = key;
+ }
+
+ public IComponent getComponent()
+ {
+ return _component;
+ }
+
+ public String getKey()
+ {
+ return _key;
+ }
+
+ /**
+ * Accesses the specified localized string. Never returns null.
+ *
+ **/
+
+ public Object getObject()
+ {
+ return _component.getMessages().getMessage(_key);
+ }
+
+ /**
+ * Returns true. Localized component strings are
+ * read-only.
+ *
+ **/
+
+ public boolean isInvariant()
+ {
+ return true;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("StringBinding");
+ buffer.append('[');
+ buffer.append(_component.getExtendedId());
+ buffer.append(' ');
+ buffer.append(_key);
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/binding/package.html b/tapestry-framework/src/org/apache/tapestry/binding/package.html
new file mode 100644
index 0000000..a893063
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/binding/package.html
@@ -0,0 +1,14 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Implementations of {@link org.apache.tapestry.IBinding}.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/callback/DirectCallback.java b/tapestry-framework/src/org/apache/tapestry/callback/DirectCallback.java
new file mode 100644
index 0000000..8b890df
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/callback/DirectCallback.java
@@ -0,0 +1,117 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.callback;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Simple callback for re-invoking a {@link IDirect} component trigger..
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @since 0.2.9
+ *
+ **/
+
+public class DirectCallback implements ICallback
+{
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = -8888847655917503471L;
+
+ private String _pageName;
+ private String _componentIdPath;
+ private Object[] _parameters;
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("DirectCallback[");
+
+ buffer.append(_pageName);
+ buffer.append('/');
+ buffer.append(_componentIdPath);
+
+ if (_parameters != null)
+ {
+ String sep = " ";
+
+ for (int i = 0; i < _parameters.length; i++)
+ {
+ buffer.append(sep);
+ buffer.append(_parameters[i]);
+
+ sep = ", ";
+ }
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+
+ }
+
+ /**
+ * Creates a new DirectCallback for the component. The parameters
+ * (which may be null) is retained, not copied.
+ *
+ **/
+
+ public DirectCallback(IDirect component, Object[] parameters)
+ {
+ _pageName = component.getPage().getPageName();
+ _componentIdPath = component.getIdPath();
+ _parameters = parameters;
+ }
+
+ /**
+ * Locates the {@link IDirect} component that was previously identified
+ * (and whose page and id path were stored).
+ * Invokes {@link IRequestCycle#setServiceParameters(Object[])} to
+ * restore the service parameters, then
+ * invokes {@link IDirect#trigger(IRequestCycle)} on the component.
+ *
+ **/
+
+ public void performCallback(IRequestCycle cycle)
+ {
+ IPage page = cycle.getPage(_pageName);
+ IComponent component = page.getNestedComponent(_componentIdPath);
+ IDirect direct = null;
+
+ try
+ {
+ direct = (IDirect) component;
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DirectCallback.wrong-type", component.getExtendedId()),
+ component,
+ null,
+ ex);
+ }
+
+ cycle.setServiceParameters(_parameters);
+ direct.trigger(cycle);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/callback/ExternalCallback.java b/tapestry-framework/src/org/apache/tapestry/callback/ExternalCallback.java
new file mode 100644
index 0000000..80d50c3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/callback/ExternalCallback.java
@@ -0,0 +1,170 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.callback;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IExternalPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A callback for returning to an {@link org.apache.tapestry.IExternalPage}.
+ * <p>
+ * Example usage of <tt>ExternalCallback</tt>:
+ * <p>
+ * The External page ensure a user is authenticated in the
+ * {@link org.apache.tapestry.IPage#validate(IRequestCycle)} method.
+ * If the user is not authenticated, they are redirected to the Login page, after
+ * setting a callback in the Login page.
+ * <p>
+ * The Login page <tt>formSubmit()</tt> {@link org.apache.tapestry.IActionListener}
+ * authenticates the user and then invokes {@link ICallback#performCallback(IRequestCycle)}
+ * to the External page.
+ * <pre>
+ * public class External extends BasePage implements IExternalPage {
+ *
+ * private Integer _itemId;
+ *
+ * public void validate(IRequestCycle cycle) throws RequestCycleException {
+ * Visit visit = (Visit) getVisit();
+ *
+ * if (!visit.isAuthenticated()) {
+ * Login login = (Login) cycle.getPage("Login");
+ *
+ * login.setCallback
+ * (new ExternalCallback(this, cycle.getServiceParameters()));
+ *
+ * throw new PageRedirectException(login);
+ * }
+ * }
+ *
+ * public void activateExternalPage(Object[] params, IRequestCycle cycle)
+ * throws RequestCycleException {
+ * _itemId = (Integer) params[0];
+ * }
+ * }
+ *
+ * public Login extends BasePage {
+ *
+ * private ICallback _callback;
+ *
+ * public void setCallback(ICallback _callback) {
+ * _callback = callback;
+ * }
+ *
+ * public void formSubmit(IRequestCycle cycle) {
+ * // Authentication code
+ * ..
+ *
+ * Visit visit = (Visit) getVisit();
+ *
+ * visit.setAuthenticated(true);
+ *
+ * if (_callback != null) {
+ * _callback.performCallback(cycle);
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @see org.apache.tapestry.IExternalPage
+ * @see org.apache.tapestry.engine.ExternalService
+ *
+ * @version $Id$
+ * @author Malcolm Edgar
+ * @since 2.3
+ *
+ **/
+
+public class ExternalCallback implements ICallback
+{
+ private String _pageName;
+ private Object[] _parameters;
+
+ /**
+ * Creates a new ExternalCallback for the named <tt>IExternalPage</tt>.
+ * The parameters (which may be null) is retained, not copied.
+ *
+ **/
+
+ public ExternalCallback(String pageName, Object[] parameters)
+ {
+ _pageName = pageName;
+ _parameters = parameters;
+ }
+
+ /**
+ * Creates a new ExternalCallback for the page. The parameters
+ * (which may be null) is retained, not copied.
+ *
+ **/
+
+ public ExternalCallback(IExternalPage page, Object[] parameters)
+ {
+ _pageName = page.getPageName();
+ _parameters = parameters;
+ }
+
+ /**
+ * Invokes {@link IRequestCycle#setPage(String)} to select the previously
+ * identified <tt>IExternalPage</tt> as the response page and activates
+ * the page by invoking <tt>activateExternalPage()</tt> with the callback
+ * parameters and request cycle.
+ *
+ **/
+
+ public void performCallback(IRequestCycle cycle)
+ {
+ try
+ {
+ IExternalPage page = (IExternalPage) cycle.getPage(_pageName);
+
+ cycle.activate(page);
+
+ page.activateExternalPage(_parameters, cycle);
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ExternalCallback.page-not-compatible", _pageName),
+ ex);
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("ExternalCallback[");
+
+ buffer.append(_pageName);
+ buffer.append('/');
+
+ if (_parameters != null)
+ {
+ String sep = " ";
+
+ for (int i = 0; i < _parameters.length; i++)
+ {
+ buffer.append(sep);
+ buffer.append(_parameters[i]);
+
+ sep = ", ";
+ }
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/callback/ICallback.java b/tapestry-framework/src/org/apache/tapestry/callback/ICallback.java
new file mode 100644
index 0000000..a6028ef
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/callback/ICallback.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.callback;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Defines a callback, an object which is used to invoke or reinvoke a method
+ * on an object or component in a later request cycle. This is used to
+ * allow certain operations (say, submitting an order) to defer to other processes
+ * (say, logging in and/or registerring).
+ *
+ * <p>Callbacks must be {@link Serializable}, to ensure that they can be stored
+ * between request cycles.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @since 0.2.9
+ *
+ **/
+
+public interface ICallback extends Serializable
+{
+ /**
+ * Performs the call back. Typical implementation will locate a particular
+ * page or component and invoke a method upon it, or
+ * invoke a method on the {@link IRequestCycle cycle}.
+ *
+ **/
+
+ public void performCallback(IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/callback/PageCallback.java b/tapestry-framework/src/org/apache/tapestry/callback/PageCallback.java
new file mode 100644
index 0000000..987c7bc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/callback/PageCallback.java
@@ -0,0 +1,114 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.callback;
+
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Simple callback for returning to a page.
+ * <p>
+ * Example usage of <tt>PageCallback</tt>:
+ * <p>
+ * The Home page ensure a user is
+ * authenticated in the {@link org.apache.tapestry.IPage#validate(IRequestCycle)}
+ * method. If the user is not authenticated, they are redirected to the Login
+ * page, after setting a callback in the Login page.
+ * <p>
+ * The Login page <tt>formSubmit()</tt> {@link org.apache.tapestry.IActionListener}
+ * authenticates the user and then invokes {@link ICallback#performCallback(IRequestCycle)}
+ * to the Home page.
+ * <pre>
+ * public class Home extends BasePage {
+ *
+ * public void validate(IRequestCycle cycle) {
+ * Visit visit = (Visit) getVisit();
+ *
+ * if (!visit.isAuthenticated()) {
+ * Login login = (Login) cycle.getPage("Login");
+ *
+ * login.setCallback(new PageCallback(this));
+ *
+ * throw new PageRedirectException(login);
+ * }
+ * }
+ * }
+ *
+ * public Login extends BasePage {
+ *
+ * private ICallback _callback;
+ *
+ * public void setCallback(ICallback _callback) {
+ * _callback = callback;
+ * }
+ *
+ * public void formSubmit(IRequestCycle cycle) {
+ * // Authentication code
+ * ..
+ *
+ * Visit visit = (Visit) getVisit();
+ *
+ * visit.setAuthenticated(true);
+ *
+ * if (_callback != null) {
+ * _callback.performCallback(cycle);
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @since 0.2.9
+ *
+ **/
+
+public class PageCallback implements ICallback
+{
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = -3286806776105690068L;
+
+ private String _pageName;
+
+ public PageCallback(String pageName)
+ {
+ _pageName = pageName;
+ }
+
+ public PageCallback(IPage page)
+ {
+ this(page.getPageName());
+ }
+
+ public String toString()
+ {
+ return "PageCallback[" + _pageName + "]";
+ }
+
+ /**
+ * Invokes {@link IRequestCycle#activate(String)} to select the previously
+ * identified page as the response page.
+ *
+ **/
+
+ public void performCallback(IRequestCycle cycle)
+ {
+ cycle.activate(_pageName);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/callback/package.html b/tapestry-framework/src/org/apache/tapestry/callback/package.html
new file mode 100644
index 0000000..6623396
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/callback/package.html
@@ -0,0 +1,24 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Provides implementations of callbacks, objects that encapsulate a server request that is deferred,
+typically to allow a user to login or otherwise authenticate before proceeding with
+some other activity.
+
+<p>In practice, an implementation of {@link org.apache.tapestry.IPage#validate(IRequestCycle)} or
+{@link org.apache.tapestry.IActionListener} will create a callback, and assign it as a
+persistent page property of an application-specific login page. After the login completes, it
+can use the callback to return the user to the functionality that was deferred.
+
+<p>Another example use would be to collect billing and shipping information as part of
+an e-commerce site's checkout wizard.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Any.java b/tapestry-framework/src/org/apache/tapestry/components/Any.java
new file mode 100644
index 0000000..00d1ede
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Any.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A component that can substitute for any HTML element.
+ *
+ * [<a href="../../../../../ComponentReference/Any.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Any extends AbstractComponent
+{
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String element = getElement();
+
+ if (element == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Any.element-not-defined"),
+ this,
+ null,
+ null);
+
+ if (!cycle.isRewinding())
+ {
+ writer.begin(element);
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (!cycle.isRewinding())
+ {
+ writer.end(element);
+ }
+
+ }
+
+ public abstract String getElement();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Any.jwc b/tapestry-framework/src/org/apache/tapestry/components/Any.jwc
new file mode 100644
index 0000000..7807fbd
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Any.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+<component-specification class="org.apache.tapestry.components.Any">
+
+ <description>
+ Dynamically emulates any element, including attributes (provided as
+ informal parameters).
+ </description>
+
+ <parameter name="element" type="java.lang.String" direction="in" required="no" default-value="templateTag">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+ <parameter name="templateTag" type="java.lang.String" direction="auto" required="no" default-value="null">
+ <description>
+ The tag used to add this component in a template.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Block.java b/tapestry-framework/src/org/apache/tapestry/components/Block.java
new file mode 100644
index 0000000..b926828
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Block.java
@@ -0,0 +1,73 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Prevents its contents from being rendered until triggered by
+ * an {@link RenderBlock} component.
+ *
+ * [<a href="../../../../../ComponentReference/Block.html">Component Reference</a>]
+ *
+ * <p>Block and {@link RenderBlock} are used to build a certain class
+ * of complicated component that can't be assembled using the normal
+ * wrapping containment. Such a super component would have two or more
+ * sections that need to be supplied by the containing page (or component).
+ *
+ * <p>Using Blocks, the blocks can be provided as parameters to the super
+ * component.
+ *
+ * <p>The inserter property gives the components inside the block access to
+ * the component (typically an {@link RenderBlock}) that inserted the block,
+ * including access to its informal bindings which allows components contained
+ * by the Block to be passed parameters. Note - it is the responsibility of the
+ * inserting component to set itself as the Block's inserter.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @since 0.2.9
+ *
+ **/
+
+public class Block extends AbstractComponent
+{
+ private IComponent _inserter;
+
+ /**
+ * Does nothing; the idea of a Block is to defer the rendering of
+ * the body of the block until an {@link RenderBlock} forces it
+ * out.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ // Nothing!
+ }
+
+ public IComponent getInserter()
+ {
+ return _inserter;
+ }
+
+ public void setInserter(IComponent value)
+ {
+ _inserter = value;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Block.jwc b/tapestry-framework/src/org/apache/tapestry/components/Block.jwc
new file mode 100644
index 0000000..99323f9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Block.jwc
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.components.Block" allow-informal-parameters="no">
+ <description>
+ A block of dynamic content. Blocks don't render until a RenderBlock component
+ triggers them.
+ </description>
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/BlockRenderer.java b/tapestry-framework/src/org/apache/tapestry/components/BlockRenderer.java
new file mode 100644
index 0000000..236c975
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/BlockRenderer.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * An implementation of IRender that renders a Block component.
+ *
+ * <p>The BlockRenderer allows the contents of a {@link Block} to be rendered
+ * via {@link IRender}. It can be used in cases when an {@link IRender} object is
+ * required as an argument or a binding to render a part of a Component.
+ * To provide a complicated view, it could be defined in a {@link Block} and then
+ * returned encapsulated in a BlockRenderer.
+ *
+ * <p>It is important to note that a special care has to be taken if
+ * the BlockRenderer is used within an inner class of a component or a page.
+ * In such a case the instance of the component that created the inner class
+ * may not be the currently active instance in the RequestCycle when the
+ * BlockRenderer is required. Thus, calling getComponent("blockName") to get the
+ * block component may return a Block component that is not initialized for this
+ * RequestCycle.
+ *
+ * <p>To avoid similar problems, the ComponentAddress class could be used in
+ * conjunction with BlockRenderer.
+ * Here is a quick example of how BlockRenderer could be used with ComponentAddress:
+ * <p>
+ * <code>
+ * <br>// Create a component address for the current component
+ * <br>final ComponentAddress address = new ComponentAddress(this);
+ * <br>return new SomeClass() {
+ * <br> IRender getRenderer(IRequestCycle cycle) {
+ * <br> MyComponent component = (MyComponent) address.findComponent(cycle);
+ * <br> // initialize variables in the component that will be used by the block here
+ * <br> return new BlockRenderer(component.getComponent("block"));
+ * <br> }
+ * <br>}
+ * </code>
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.2
+ */
+public class BlockRenderer implements IRender
+{
+ private Block m_objBlock;
+
+ /**
+ * Creates a new BlockRenderer that will render the content of the argument
+ * @param objBlock the Block to be rendered
+ */
+ public BlockRenderer(Block objBlock)
+ {
+ m_objBlock = objBlock;
+ }
+
+ /**
+ * @see org.apache.tapestry.IRender#render(IMarkupWriter, IRequestCycle)
+ */
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ m_objBlock.renderBody(writer, cycle);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Conditional.java b/tapestry-framework/src/org/apache/tapestry/components/Conditional.java
new file mode 100644
index 0000000..9f59742
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Conditional.java
@@ -0,0 +1,72 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A conditional element on a page which will render its wrapped elements
+ * zero or one times.
+ *
+ * [<a href="../../../../../ComponentReference/Conditional.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship, David Solis
+ * @version $Id$
+ *
+ **/
+
+public abstract class Conditional extends AbstractComponent
+{
+ /**
+ * Renders its wrapped components only if the condition is true (technically,
+ * if condition matches invert).
+ * Additionally, if element is specified, can emulate that HTML element if condition is met
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (evaluateCondition())
+ {
+ String element = getElement();
+
+ boolean render = !cycle.isRewinding() && Tapestry.isNonBlank(element);
+
+ if (render)
+ {
+ writer.begin(element);
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (render)
+ writer.end(element);
+ }
+ }
+
+ protected boolean evaluateCondition()
+ {
+ return getCondition() != getInvert();
+ }
+
+ public abstract boolean getCondition();
+ public abstract boolean getInvert();
+
+ public abstract String getElement();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Conditional.jwc b/tapestry-framework/src/org/apache/tapestry/components/Conditional.jwc
new file mode 100644
index 0000000..cbff668
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Conditional.jwc
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.components.Conditional">
+ <description>
+ Conditionally emulates an element and its attributes (if element is specified) and/or includes a block of content if a condition is met.
+ </description>
+
+ <parameter name="condition" type="boolean" direction="in" required="yes">
+ <description>
+ The condition to evaluate.
+ </description>
+ </parameter>
+
+ <parameter name="invert" type="boolean" direction="in">
+ <description>
+ If true, inverts the condition, so that a false condition causes the
+ content to be included. If false (the default), then the condition
+ is evaluated normally.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" direction="in" required="no">
+ <description>
+ The element to emulate.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Delegator.java b/tapestry-framework/src/org/apache/tapestry/components/Delegator.java
new file mode 100644
index 0000000..d817160
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Delegator.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * A component which delegates it's behavior to another object.
+ *
+ * [<a href="../../../../../ComponentReference/Delegator.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Delegator extends AbstractComponent
+{
+ /**
+ * Gets its delegate and invokes {@link IRender#render(IMarkupWriter, IRequestCycle)}
+ * on it.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IRender delegate = getDelegate();
+
+ if (delegate != null)
+ delegate.render(writer, cycle);
+ }
+
+ public abstract IRender getDelegate();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Delegator.jwc b/tapestry-framework/src/org/apache/tapestry/components/Delegator.jwc
new file mode 100644
index 0000000..6000319
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Delegator.jwc
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.components.Delegator"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+ Delegates rendering to an object that implements the IRender interface.
+ </description>
+
+ <parameter name="delegate" type="org.apache.tapestry.IRender" direction="in">
+ <description>
+ The object which will perform the render.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Foreach.java b/tapestry-framework/src/org/apache/tapestry/components/Foreach.java
new file mode 100644
index 0000000..e43ef9d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Foreach.java
@@ -0,0 +1,174 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Repeatedly renders its wrapped contents while iterating through
+ * a list of values.
+ *
+ * [<a href="../../../../../ComponentReference/Foreach.html">Component Reference</a>]
+ *
+ * <p>
+ * While the component is rendering, the property
+ * {@link #getValue() value} (accessed as
+ * <code>components.<i>foreach</i>.value</code>
+ * is set to each successive value from the source,
+ * and the property
+ * {@link #getIndex() index} is set to each successive index
+ * into the source (starting with zero).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Foreach extends AbstractComponent
+{
+ private Object _value;
+ private int _index;
+ private boolean _rendering;
+
+ public abstract IBinding getIndexBinding();
+
+
+ /**
+ * Gets the source binding and returns an {@link Iterator}
+ * representing
+ * the values identified by the source. Returns an empty {@link Iterator}
+ * if the binding, or the binding value, is null.
+ *
+ * <p>Invokes {@link Tapestry#coerceToIterator(Object)} to perform
+ * the actual conversion.
+ *
+ **/
+
+ protected Iterator getSourceData()
+ {
+ Object source = getSource();
+
+ if (source == null)
+ return null;
+
+ return Tapestry.coerceToIterator(source);
+ }
+
+ public abstract IBinding getValueBinding();
+
+ /**
+ * Gets the source binding and iterates through
+ * its values. For each, it updates the value binding and render's its wrapped elements.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Iterator dataSource = getSourceData();
+
+ // The dataSource was either not convertable, or was empty.
+
+ if (dataSource == null)
+ return;
+
+
+ try
+ {
+ _rendering = true;
+ _value = null;
+ _index = 0;
+
+ IBinding indexBinding = getIndexBinding();
+ IBinding valueBinding = getValueBinding();
+ String element = getElement();
+
+ boolean hasNext = dataSource.hasNext();
+
+ while (hasNext)
+ {
+ _value = dataSource.next();
+ hasNext = dataSource.hasNext();
+
+ if (indexBinding != null)
+ indexBinding.setInt(_index);
+
+ if (valueBinding != null)
+ valueBinding.setObject(_value);
+
+ if (element != null)
+ {
+ writer.begin(element);
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (element != null)
+ writer.end();
+
+ _index++;
+ }
+ }
+ finally
+ {
+ _value = null;
+ _rendering = false;
+ }
+ }
+
+ /**
+ * Returns the most recent value extracted from the source parameter.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the Foreach is not currently rendering.
+ *
+ **/
+
+ public Object getValue()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "value");
+
+ return _value;
+ }
+
+ public abstract String getElement();
+
+ public abstract Object getSource();
+
+ /**
+ * The index number, within the {@link #getSource() source}, of the
+ * the current value.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the Foreach is not currently rendering.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public int getIndex()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "index");
+
+ return _index;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Foreach.jwc b/tapestry-framework/src/org/apache/tapestry/components/Foreach.jwc
new file mode 100644
index 0000000..1e5ca90
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Foreach.jwc
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification
+ class="org.apache.tapestry.components.Foreach"
+ allow-informal-parameters="yes">
+
+ <description>
+ Loops over a collection of source values. May also emulate an element (like an Any
+ component).
+ </description>
+
+ <parameter name="source" type="java.lang.Object" direction="in" required="yes">
+ <description>
+ The source of values, a Java collection or array.
+ </description>
+ </parameter>
+
+ <parameter name="value" direction="custom">
+ <description>
+ If provided, then on each iteration, the value property is updated.
+ </description>
+ </parameter>
+
+ <parameter name="index" type="int" direction="custom">
+ <description>
+ If provided, then the index of the loop is set on each iteration.
+ </description>
+ </parameter>
+
+ <parameter name="element" type="java.lang.String" direction="in">
+ <description>
+ If provided, then the Foreach creates an element wrapping its content.
+ Informal parameters become attributes of the element.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/ILinkComponent.java b/tapestry-framework/src/org/apache/tapestry/components/ILinkComponent.java
new file mode 100644
index 0000000..fae2537
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/ILinkComponent.java
@@ -0,0 +1,89 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component that renders an HTML <a> element. It exposes some
+ * properties to the components it wraps. This is basically to facilitate
+ * the {@link org.apache.tapestry.html.Rollover} component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ILinkComponent extends IComponent
+{
+
+ /**
+ * Returns whether this service link component is enabled or disabled.
+ *
+ * @since 0.2.9
+ *
+ **/
+
+ public boolean isDisabled();
+
+ /**
+ * Returns the anchor defined for this link, or null for no anchor.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public String getAnchor();
+
+ /**
+ * Adds a new event handler. When the event occurs, the JavaScript function
+ * specified is executed. Multiple functions can be specified, in which case
+ * all of them are executed.
+ *
+ * <p>This was created for use by
+ * {@link org.apache.tapestry.html.Rollover} to set mouse over and mouse out handlers on
+ * the {@link ILinkComponent} that wraps it, but can be used for
+ * many other things as well.
+ *
+ * @since 0.2.9
+ **/
+
+ public void addEventHandler(LinkEventType type, String functionName);
+
+ /**
+ * Invoked by the {@link org.apache.tapestry.link.ILinkRenderer} (if
+ * the link is not disabled) to provide a
+ * {@link org.apache.tapestry.engine.EngineServiceLink} that the renderer can convert
+ * into a URL.
+ *
+ **/
+
+ public ILink getLink(IRequestCycle cycle);
+
+ /**
+ * Invoked (by the {@link org.apache.tapestry.link.ILinkRenderer})
+ * to make the link render any additional attributes. These
+ * are informal parameters, plus any attributes related to events.
+ * This is only invoked for non-disabled links.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void renderAdditionalAttributes(IMarkupWriter writer, IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Insert.java b/tapestry-framework/src/org/apache/tapestry/components/Insert.java
new file mode 100644
index 0000000..3820819
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Insert.java
@@ -0,0 +1,106 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import java.text.Format;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Used to insert some text (from a parameter) into the HTML.
+ *
+ * [<a href="../../../../../ComponentReference/Insert.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Insert extends AbstractComponent
+{
+ public abstract IBinding getFormatBinding();
+
+ /**
+ * Prints its value parameter, possibly formatted by its format parameter.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.isRewinding())
+ return;
+
+ Object value = getValue();
+
+ if (value == null)
+ return;
+
+ String insert = null;
+
+ Format format = getFormat();
+
+ if (format == null)
+ {
+ insert = value.toString();
+ }
+ else
+ {
+ try
+ {
+ insert = format.format(value);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Insert.unable-to-format", value),
+ this,
+ getFormatBinding().getLocation(),
+ ex);
+ }
+ }
+
+ String styleClass = getStyleClass();
+
+ if (styleClass != null)
+ {
+ writer.begin("span");
+ writer.attribute("class", styleClass);
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ if (getRaw())
+ writer.printRaw(insert);
+ else
+ writer.print(insert);
+
+ if (styleClass != null)
+ writer.end(); // <span>
+ }
+
+ public abstract Object getValue();
+
+ public abstract Format getFormat();
+
+ public abstract String getStyleClass();
+
+ public abstract boolean getRaw();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/Insert.jwc b/tapestry-framework/src/org/apache/tapestry/components/Insert.jwc
new file mode 100644
index 0000000..1cb5409
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/Insert.jwc
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.components.Insert"
+ allow-body="no">
+
+ <description>
+ Emits a value into the response page.
+ </description>
+
+
+ <parameter name="value" type="java.lang.Object" direction="in">
+ <description>
+ The value to be emitted. Non-strings are converted to strings.
+ </description>
+ </parameter>
+
+ <parameter name="format" type="java.text.Format" direction="in">
+ <description>
+ A Format object used to convert the value to a string.
+ </description>
+ </parameter>
+
+ <parameter name="raw" type="boolean" direction="in">
+ <description>
+ If false (the default), then HTML characters in the value are escaped. If
+ true, then value is emitted exactly as is.
+ </description>
+ </parameter>
+
+ <parameter name="class"
+ type="java.lang.String"
+ property-name="styleClass"
+ direction="in">
+ <description>
+ If specified, then any output is wrapped in an HTML span tag with the given CSS class.
+ </description>
+ </parameter>
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/LinkEventType.java b/tapestry-framework/src/org/apache/tapestry/components/LinkEventType.java
new file mode 100644
index 0000000..7682dd6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/LinkEventType.java
@@ -0,0 +1,110 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Different types of JavaScript events that an {@link ILinkComponent}
+ * can provide handlers for.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+public class LinkEventType extends Enum
+{
+ private String _attributeName;
+
+ /**
+ * Type for <code>onMouseOver</code>. This may also be called "focus".
+ *
+ **/
+
+ public static final LinkEventType MOUSE_OVER = new LinkEventType("MOUSE_OVER", "onMouseOver");
+
+ /**
+ * Type for <code>onMouseOut</code>. This may also be called "blur".
+ *
+ **/
+
+ public static final LinkEventType MOUSE_OUT = new LinkEventType("MOUSE_OUT", "onMouseOut");
+
+ /**
+ * Type for <code>onClick</code>.
+ *
+ * @since 1.0.1
+ *
+ **/
+
+ public static final LinkEventType CLICK = new LinkEventType("CLICK", "onClick");
+
+ /**
+ * Type for <code>onDblClick</code>.
+ *
+ * @since 1.0.1
+ *
+ **/
+
+ public static final LinkEventType DOUBLE_CLICK =
+ new LinkEventType("DOUBLE_CLICK", "onDblClick");
+
+ /**
+ * Type for <code>onMouseDown</code>.
+ *
+ * @since 1.0.1.
+ *
+ **/
+
+ public static final LinkEventType MOUSE_DOWN = new LinkEventType("MOUSE_DOWN", "onMouseDown");
+
+ /**
+ * Type for <code>onMouseUp</code>.
+ *
+ * @since 1.0.1
+ *
+ **/
+
+ public static final LinkEventType MOUSE_UP = new LinkEventType("MOUSE_UP", "onMouseUp");
+
+ /**
+ * Constructs a new type of event. The name should match the
+ * static final variable (i.e., MOUSE_OVER) and the attributeName
+ * is the name of the HTML attribute to be managed (i.e., "onMouseOver").
+ *
+ * <p>This method is protected so that subclasses can be created
+ * to provide additional managed event types.
+ **/
+
+ protected LinkEventType(String name, String attributeName)
+ {
+ super(name);
+
+ _attributeName = attributeName;
+ }
+
+ /**
+ * Returns the name of the HTML attribute corresponding to this
+ * type.
+ *
+ **/
+
+ public String getAttributeName()
+ {
+ return _attributeName;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.java b/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.java
new file mode 100644
index 0000000..9d08eac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.java
@@ -0,0 +1,80 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Renders the text and components wrapped by a {@link Block} component.
+ *
+ * [<a href="../../../../../ComponentReference/RenderBlock.html">Component Reference</a>]
+ *
+ * <p>It is possible for an RenderBlock to obtain a Block
+ * from a page <em>other than</em> the render page. This works, even when
+ * the Block contains links, forms and form components. The action and
+ * direct services will create URLs that properly address this situation.
+ *
+ * <p>However, because the rendering page can't know
+ * ahead of time about these foriegn Blocks,
+ * {@link org.apache.tapestry.event.PageRenderListener} methods
+ * (for components and objects of the foriegn page)
+ * via RenderBlock will <em>not</em> be executed. This specifically
+ * affects the methods of the {@link org.apache.tapestry.event.PageRenderListener}
+ * interface.
+ *
+ * <p>Before rendering its {@link Block}, RenderBlock will set itself as the
+ * Block's inserter, and will reset the inserter after the {@link Block} is
+ * rendered. This gives the components contained in the {@link Block} access
+ * to its inserted environment via the RenderBlock. In particular this allows
+ * the contained components to access the informal parameters of the RenderBlock
+ * which effectively allows parameters to be passed to the components contained
+ * in a Block.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class RenderBlock extends AbstractComponent
+{
+ /**
+ * If block is not null,
+ * then the block's inserter is set (to this),
+ * {@link org.apache.tapestry.IComponent#renderBody(IMarkupWriter, IRequestCycle)}
+ * is invoked on it, and the Block's inserter is set back to its previous state.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Block block = getBlock();
+
+ if (block != null)
+ {
+ // make a copy of the inserter so we don't overwrite completely
+ IComponent previousInserter = block.getInserter();
+ block.setInserter(this);
+ block.renderBody(writer, cycle);
+ // reset the inserter as it was before we changed it
+ block.setInserter(previousInserter);
+ }
+ }
+
+ public abstract Block getBlock();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.jwc b/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.jwc
new file mode 100644
index 0000000..7c384d7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/RenderBlock.jwc
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.components.RenderBlock"
+ allow-body="no"
+ allow-informal-parameters="yes">
+
+ <parameter name="block"
+ type="org.apache.tapestry.components.Block"
+ direction="in"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/RenderBody.java b/tapestry-framework/src/org/apache/tapestry/components/RenderBody.java
new file mode 100644
index 0000000..00af581
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/RenderBody.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.components;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Renders the text and components wrapped by a component.
+ *
+ * [<a href="../../../../../ComponentReference/RenderBody.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class RenderBody extends AbstractComponent
+{
+ /**
+ * Finds this <code>RenderBody</code>'s container, and invokes
+ * {@link IComponent#renderBody(IMarkupWriter, IRequestCycle)}
+ * on it.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IComponent container = getContainer();
+
+ container.renderBody(writer, cycle);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/components/RenderBody.jwc b/tapestry-framework/src/org/apache/tapestry/components/RenderBody.jwc
new file mode 100644
index 0000000..e21fd48
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/RenderBody.jwc
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification
+ class="org.apache.tapestry.components.RenderBody"
+ allow-body="no"
+ allow-informal-parameters="no"/>
diff --git a/tapestry-framework/src/org/apache/tapestry/components/package.html b/tapestry-framework/src/org/apache/tapestry/components/package.html
new file mode 100644
index 0000000..3ccd00e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/components/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Basic, fundamental components used to construct more complex components, or pages.
+These components are independant of any particular markup language.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/AbstractEngine.java b/tapestry-framework/src/org/apache/tapestry/engine/AbstractEngine.java
new file mode 100644
index 0000000..471b657
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/AbstractEngine.java
@@ -0,0 +1,2369 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.apache.bsf.BSFManager;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ApplicationServlet;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.PageRedirectException;
+import org.apache.tapestry.RedirectException;
+import org.apache.tapestry.StaleLinkException;
+import org.apache.tapestry.StaleSessionException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.asset.ResourceChecksumSource;
+import org.apache.tapestry.asset.ResourceChecksumSourceImpl;
+import org.apache.tapestry.enhance.DefaultComponentClassEnhancer;
+import org.apache.tapestry.listener.ListenerMap;
+import org.apache.tapestry.pageload.PageSource;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.request.ResponseOutputStream;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.util.DelegatingPropertySource;
+import org.apache.tapestry.util.PropertyHolderPropertySource;
+import org.apache.tapestry.util.ResourceBundlePropertySource;
+import org.apache.tapestry.util.ServletContextPropertySource;
+import org.apache.tapestry.util.ServletPropertySource;
+import org.apache.tapestry.util.SystemPropertiesPropertySource;
+import org.apache.tapestry.util.exception.ExceptionAnalyzer;
+import org.apache.tapestry.util.io.DataSqueezer;
+import org.apache.tapestry.util.pool.Pool;
+
+/**
+ * Basis for building real Tapestry applications. Immediate subclasses
+ * provide different strategies for managing page state and other resources
+ * between request cycles.
+ *
+ * Uses a shared instance of
+ * {@link ITemplateSource}, {@link ISpecificationSource},
+ * {@link IScriptSource} and {@link IComponentMessagesSource}
+ * stored as attributes of the {@link ServletContext}
+ * (they will be shared by all sessions).
+ *
+ * <p>An application is designed to be very lightweight.
+ * Particularily, it should <b>never</b> hold references to any
+ * {@link IPage} or {@link org.apache.tapestry.IComponent} objects. The entire system is
+ * based upon being able to quickly rebuild the state of any page(s).
+ *
+ * <p>Where possible, instance variables should be transient. They
+ * can be restored inside {@link #setupForRequest(RequestContext)}.
+ *
+ * <p>In practice, a subclass (usually {@link BaseEngine})
+ * is used without subclassing. Instead, a
+ * visit object is specified. To facilitate this, the application specification
+ * may include a property, <code>org.apache.tapestry.visit-class</code>
+ * which is the class name to instantiate when a visit object is first needed. See
+ * {@link #createVisit(IRequestCycle)} for more details.
+ *
+ * <p>Some of the classes' behavior is controlled by JVM system properties
+ * (typically only used during development):
+ *
+ * <table border=1>
+ * <tr> <th>Property</th> <th>Description</th> </tr>
+ * <tr> <td>org.apache.tapestry.enable-reset-service</td>
+ * <td>If true, enabled an additional service, reset, that
+ * allow page, specification and template caches to be cleared on demand.
+ * See {@link #isResetServiceEnabled()}. </td>
+ * </tr>
+ * <tr>
+ * <td>org.apache.tapestry.disable-caching</td>
+ * <td>If true, then the page, specification, template and script caches
+ * will be cleared after each request. This slows things down,
+ * but ensures that the latest versions of such files are used.
+ * Care should be taken that the source directories for the files
+ * preceeds any versions of the files available in JARs or WARs. </td>
+ * </tr>
+ * </table>
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class AbstractEngine
+ implements IEngine, IEngineServiceView, Externalizable, HttpSessionBindingListener
+{
+ private static final Log LOG = LogFactory.getLog(AbstractEngine.class);
+
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = 6884834397673817117L;
+
+ private transient String _contextPath;
+ private transient String _servletPath;
+ private transient String _clientAddress;
+ private transient String _sessionId;
+ private transient boolean _stateful;
+ private transient ListenerMap _listeners;
+
+ /** @since 2.2 **/
+
+ private transient DataSqueezer _dataSqueezer;
+
+ /**
+ * An object used to contain application-specific server side state.
+ *
+ **/
+
+ private Object _visit;
+
+ /**
+ * The globally shared application object. Typically, this is created
+ * when first needed, shared between sessions and engines, and
+ * stored in the {@link ServletContext}.
+ *
+ * @since 2.3
+ *
+ **/
+
+ private transient Object _global;
+
+ /**
+ * The base name for the servlet context key used to store
+ * the application-defined Global object, if any.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public static final String GLOBAL_NAME = "org.apache.tapestry.global";
+
+ /**
+ * The name of the application property that will be used to
+ * determine the encoding to use when generating the output
+ *
+ * @since 3.0
+ **/
+
+ public static final String OUTPUT_ENCODING_PROPERTY_NAME =
+ "org.apache.tapestry.output-encoding";
+
+ /**
+ * The default encoding that will be used when generating the output.
+ * It is used if no output encoding property has been specified.
+ *
+ * @since 3.0
+ */
+
+ public static final String DEFAULT_OUTPUT_ENCODING = "UTF-8";
+
+ /**
+ * The curent locale for the engine, which may be changed at any time.
+ *
+ **/
+
+ private Locale _locale;
+
+ /**
+ * Set by {@link #setLocale(Locale)} when the locale is changed;
+ * this allows the locale cookie to be updated.
+ *
+ **/
+
+ private boolean _localeChanged;
+
+ /**
+ * The specification for the application, which
+ * lives in the {@link ServletContext}. If the
+ * session (and application) moves to a different context (i.e.,
+ * a different JVM), then
+ * we want to reconnect to the specification in the new context.
+ * A check is made on every request
+ * cycle as needed.
+ *
+ **/
+
+ protected transient IApplicationSpecification _specification;
+
+ /**
+ * The source for template data. The template source is stored
+ * in the {@link ServletContext} as a named attribute.
+ * After de-serialization, the application can re-connect to
+ * the template source (or create a new one).
+ *
+ **/
+
+ protected transient ITemplateSource _templateSource;
+
+ /**
+ * The source for component specifications, stored in the
+ * {@link ServletContext} (like {@link #_templateSource}).
+ *
+ **/
+
+ protected transient ISpecificationSource _specificationSource;
+
+ /**
+ * The source for parsed scripts, again, stored in the
+ * {@link ServletContext}.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ private transient IScriptSource _scriptSource;
+
+ /**
+ * The name of the context attribute for the {@link IScriptSource} instance.
+ * The application's name is appended.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ protected static final String SCRIPT_SOURCE_NAME = "org.apache.tapestry.ScriptSource";
+
+ /**
+ * The name of the context attribute for the {@link IComponentMessagesSource}
+ * instance. The application's name is appended.
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ protected static final String STRINGS_SOURCE_NAME = "org.apache.tapestry.StringsSource";
+
+ private transient IComponentMessagesSource _stringsSource;
+
+ /**
+ * The name of the application specification property used to specify the
+ * class of the visit object.
+ *
+ **/
+
+ public static final String VISIT_CLASS_PROPERTY_NAME = "org.apache.tapestry.visit-class";
+
+ /**
+ * Servlet context attribute name for the default {@link ITemplateSource}
+ * instance. The application's name is appended.
+ *
+ **/
+
+ protected static final String TEMPLATE_SOURCE_NAME = "org.apache.tapestry.TemplateSource";
+
+ /**
+ * Servlet context attribute name for the default {@link ISpecificationSource}
+ * instance. The application's name is appended.
+ *
+ **/
+
+ protected static final String SPECIFICATION_SOURCE_NAME =
+ "org.apache.tapestry.SpecificationSource";
+
+ /**
+ * Servlet context attribute name for the {@link IPageSource}
+ * instance. The application's name is appended.
+ *
+ **/
+
+ protected static final String PAGE_SOURCE_NAME = "org.apache.tapestry.PageSource";
+
+ /**
+ * Servlet context attribute name for a shared instance
+ * of {@link DataSqueezer}. The instance is actually shared
+ * between Tapestry applications within the same context
+ * (which will have the same ClassLoader).
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected static final String DATA_SQUEEZER_NAME = "org.apache.tapestry.DataSqueezer";
+
+ /**
+ * Servlet context attribute name for a shared instance
+ * of {@link ResourceChecksumSource}.
+ * @since 3.0.3
+ */
+ protected static final String RESOURCE_CHECKSUM_SOURCE_NAME =
+ "org.apache.tapestry.ResourceChecksumSource";
+
+ /**
+ * The source for pages, which acts as a pool, but is capable of
+ * creating pages as needed. Stored in the
+ * {@link ServletContext}, like {@link #_templateSource}.
+ *
+ **/
+
+ private transient IPageSource _pageSource;
+
+ /**
+ * If true (set from JVM system parameter
+ * <code>org.apache.tapestry.enable-reset-service</code>)
+ * then the reset service will be enabled, allowing
+ * the cache of pages, specifications and template
+ * to be cleared on demand.
+ *
+ **/
+
+ private static final boolean _resetServiceEnabled =
+ Boolean.getBoolean("org.apache.tapestry.enable-reset-service");
+
+ /**
+ * If true (set from the JVM system parameter
+ * <code>org.apache.tapestry.disable-caching</code>)
+ * then the cache of pages, specifications and template
+ * will be cleared after each request.
+ *
+ **/
+
+ private static final boolean _disableCaching =
+ Boolean.getBoolean("org.apache.tapestry.disable-caching");
+
+ private transient IResourceResolver _resolver;
+
+ /**
+ * Constant used to store a {@link org.apache.tapestry.util.IPropertyHolder}
+ * in the servlet context.
+ *
+ * @since 2.3
+ *
+ **/
+
+ protected static final String PROPERTY_SOURCE_NAME = "org.apache.tapestry.PropertySource";
+
+ /**
+ * A shared instance of {@link IPropertySource}
+ *
+ * @since 3.0
+ * @see #createPropertySource(RequestContext)
+ *
+ **/
+
+ private transient IPropertySource _propertySource;
+
+ /**
+ * Map from service name to service instance.
+ *
+ * @since 1.0.9
+ *
+ **/
+
+ private transient Map _serviceMap;
+
+ protected static final String SERVICE_MAP_NAME = "org.apache.tapestry.ServiceMap";
+
+ /**
+ * A shared instance of {@link Pool}.
+ *
+ * @since 3.0
+ * @see #createPool(RequestContext)
+ *
+ **/
+
+ private transient Pool _pool;
+
+ protected static final String POOL_NAME = "org.apache.tapestry.Pool";
+
+ /**
+ * Name of a shared instance of {@link org.apache.tapestry.engine.IComponentClassEnhancer}
+ * stored in the {@link ServletContext}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected static final String ENHANCER_NAME = "org.apache.tapestry.ComponentClassEnhancer";
+
+ /**
+ * A shared instance of {@link org.apache.tapestry.engine.IComponentClassEnhancer}.
+ *
+ * @since 3.0
+ * @see #createComponentClassEnhancer(RequestContext)
+ *
+ **/
+
+ private transient IComponentClassEnhancer _enhancer;
+
+ /**
+ * Set to true when there is a (potential)
+ * change to the internal state of the engine, set
+ * to false when the engine is stored into the
+ * {@link HttpSession}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private transient boolean _dirty;
+
+ /**
+ * The instance of {@link IMonitorFactory} used to create a monitor.
+ *
+ * @since 3.0
+ */
+
+ private transient IMonitorFactory _monitorFactory;
+
+ /**
+ * Used to obtain resource checksums for the asset service.
+ * @since 3.0.3
+ */
+ private transient ResourceChecksumSource _resourceChecksumSource;
+
+ /**
+ * Sets the Exception page's exception property, then renders the Exception page.
+ *
+ * <p>If the render throws an exception, then copious output is sent to
+ * <code>System.err</code> and a {@link ServletException} is thrown.
+ *
+ **/
+
+ protected void activateExceptionPage(
+ IRequestCycle cycle,
+ ResponseOutputStream output,
+ Throwable cause)
+ throws ServletException
+ {
+ try
+ {
+ IPage exceptionPage = cycle.getPage(getExceptionPageName());
+
+ exceptionPage.setProperty("exception", cause);
+
+ cycle.activate(exceptionPage);
+
+ renderResponse(cycle, output);
+
+ }
+ catch (Throwable ex)
+ {
+ // Worst case scenario. The exception page itself is broken, leaving
+ // us with no option but to write the cause to the output.
+
+ reportException(
+ Tapestry.getMessage("AbstractEngine.unable-to-process-client-request"),
+ cause);
+
+ // Also, write the exception thrown when redendering the exception
+ // page, so that can get fixed as well.
+
+ reportException(
+ Tapestry.getMessage("AbstractEngine.unable-to-present-exception-page"),
+ ex);
+
+ // And throw the exception.
+
+ throw new ServletException(ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Writes a detailed report of the exception to <code>System.err</code>.
+ *
+ **/
+
+ public void reportException(String reportTitle, Throwable ex)
+ {
+ LOG.warn(reportTitle, ex);
+
+ System.err.println("\n\n**********************************************************\n\n");
+
+ System.err.println(reportTitle);
+
+ System.err.println(
+ "\n\n Session id: "
+ + _sessionId
+ + "\n Client address: "
+ + _clientAddress
+ + "\n\nExceptions:\n");
+
+ new ExceptionAnalyzer().reportException(ex, System.err);
+
+ System.err.println("\n**********************************************************\n");
+
+ }
+
+ /**
+ * Invoked at the end of the request cycle to release any resources specific
+ * to the request cycle.
+ *
+ **/
+
+ protected abstract void cleanupAfterRequest(IRequestCycle cycle);
+
+ /**
+ * Extends the description of the class generated by {@link #toString()}.
+ * If a subclass adds additional instance variables that should be described
+ * in the instance description, it may overide this method. This implementation
+ * does nothing.
+ *
+ * @see #toString()
+ *
+ **/
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+
+ }
+
+ /**
+ * Returns the locale for the engine. This is initially set
+ * by the {@link ApplicationServlet} but may be updated
+ * by the application.
+ *
+ **/
+
+ public Locale getLocale()
+ {
+ return _locale;
+ }
+
+ /**
+ * Overriden in subclasses that support monitoring. Should create and return
+ * an instance of {@link IMonitor} that is appropriate for the request cycle described
+ * by the {@link RequestContext}.
+ *
+ * <p>The monitor is used to create a {@link RequestCycle}.
+ *
+ * <p>This implementation uses a {@link IMonitorFactory}
+ * to create the monitor instance. The factory
+ * is provided as an application extension. If the application
+ * extension does not exist, {@link DefaultMonitorFactory} is used.
+ *
+ * <p>As of release 3.0, this method should <em>not</em> return null.
+ *
+ *
+ */
+
+ public IMonitor getMonitor(RequestContext context)
+ {
+ if (_monitorFactory == null)
+ {
+ if (_specification.checkExtension(Tapestry.MONITOR_FACTORY_EXTENSION_NAME))
+ _monitorFactory =
+ (IMonitorFactory) _specification.getExtension(
+ Tapestry.MONITOR_FACTORY_EXTENSION_NAME,
+ IMonitorFactory.class);
+ else
+ _monitorFactory = DefaultMonitorFactory.SHARED;
+ }
+
+ return _monitorFactory.createMonitor(context);
+ }
+
+ public IPageSource getPageSource()
+ {
+ return _pageSource;
+ }
+
+ /**
+ * Returns a service with the given name. Services are created by the
+ * first call to {@link #setupForRequest(RequestContext)}.
+ **/
+
+ public IEngineService getService(String name)
+ {
+ IEngineService result = (IEngineService) _serviceMap.get(name);
+
+ if (result == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unknown-service", name));
+
+ return result;
+ }
+
+ public String getServletPath()
+ {
+ return _servletPath;
+ }
+
+ /**
+ * Returns the context path, the prefix to apply to any URLs so that they
+ * are recognized as belonging to the Servlet 2.2 context.
+ *
+ * @see org.apache.tapestry.asset.ContextAsset
+ *
+ **/
+
+ public String getContextPath()
+ {
+ return _contextPath;
+ }
+
+ /**
+ * Returns the specification, if available, or null otherwise.
+ *
+ * <p>To facilitate deployment across multiple servlet containers, the
+ * application is serializable. However, the reference to the specification
+ * is transient. When an application instance is deserialized, it reconnects
+ * with the application specification by locating it in the {@link ServletContext}
+ * or parsing it fresh.
+ *
+ **/
+
+ public IApplicationSpecification getSpecification()
+ {
+ return _specification;
+ }
+
+ public ISpecificationSource getSpecificationSource()
+ {
+ return _specificationSource;
+ }
+
+ public ITemplateSource getTemplateSource()
+ {
+ return _templateSource;
+ }
+
+ /**
+ * Reads the state serialized by {@link #writeExternal(ObjectOutput)}.
+ *
+ * <p>This always set the stateful flag. By default, a deserialized
+ * session is stateful (else, it would not have been serialized).
+ **/
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
+ {
+ _stateful = true;
+
+ String localeName = in.readUTF();
+ _locale = Tapestry.getLocale(localeName);
+
+ _visit = in.readObject();
+ }
+
+ /**
+ * Writes the following properties:
+ *
+ * <ul>
+ * <li>locale name ({@link Locale#toString()})
+ * <li>visit
+ * </ul>
+ *
+ **/
+
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ out.writeUTF(_locale.toString());
+ out.writeObject(_visit);
+ }
+
+ /**
+ * Invoked, typically, when an exception occurs while servicing the request.
+ * This method resets the output, sets the new page and renders it.
+ *
+ **/
+
+ protected void redirect(
+ String pageName,
+ IRequestCycle cycle,
+ ResponseOutputStream out,
+ ApplicationRuntimeException exception)
+ throws IOException, ServletException
+ {
+ // Discard any output from the previous page.
+
+ out.reset();
+
+ IPage page = cycle.getPage(pageName);
+
+ cycle.activate(page);
+
+ renderResponse(cycle, out);
+ }
+
+ public void renderResponse(IRequestCycle cycle, ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Begin render response.");
+
+ // If the locale has changed during this request cycle then
+ // do the work to propogate the locale change into
+ // subsequent request cycles.
+
+ if (_localeChanged)
+ {
+ _localeChanged = false;
+
+ RequestContext context = cycle.getRequestContext();
+ ApplicationServlet servlet = context.getServlet();
+
+ servlet.writeLocaleCookie(_locale, this, context);
+ }
+
+ // Commit all changes and ignore further changes.
+
+ IPage page = cycle.getPage();
+
+ IMarkupWriter writer = page.getResponseWriter(output);
+
+ output.setContentType(writer.getContentType());
+
+ boolean discard = true;
+
+ try
+ {
+ cycle.renderPage(writer);
+
+ discard = false;
+ }
+ finally
+ {
+ // Closing the writer closes its PrintWriter and a whole stack of java.io objects,
+ // which tend to stream a lot of output that eventually hits the
+ // ResponseOutputStream. If we are discarding output anyway (due to an exception
+ // getting thrown during the render), we can save ourselves some trouble
+ // by ignoring it.
+
+ if (discard)
+ output.setDiscard(true);
+
+ writer.close();
+
+ if (discard)
+ output.setDiscard(false);
+ }
+
+ }
+
+ /**
+ * Invalidates the session, then redirects the client web browser to
+ * the servlet's prefix, starting a new visit.
+ *
+ * <p>Subclasses should perform their own restart (if necessary, which is
+ * rarely) before invoking this implementation.
+ *
+ **/
+
+ public void restart(IRequestCycle cycle) throws IOException
+ {
+ RequestContext context = cycle.getRequestContext();
+
+ HttpSession session = context.getSession();
+
+ if (session != null)
+ {
+ try
+ {
+ session.invalidate();
+ }
+ catch (IllegalStateException ex)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Exception thrown invalidating HttpSession.", ex);
+
+ // Otherwise, ignore it.
+ }
+ }
+
+ // Make isStateful() return false, so that the servlet doesn't
+ // try to store the engine back into the (now invalid) session.
+
+ _stateful = false;
+
+ String url = context.getAbsoluteURL(_servletPath);
+
+ context.redirect(url);
+ }
+
+ /**
+ * Delegate method for the servlet. Services the request.
+ *
+ **/
+
+ public boolean service(RequestContext context) throws ServletException, IOException
+ {
+ ApplicationServlet servlet = context.getServlet();
+ IRequestCycle cycle = null;
+ ResponseOutputStream output = null;
+ IMonitor monitor = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Begin service " + context.getRequestURI());
+
+ if (_specification == null)
+ _specification = servlet.getApplicationSpecification();
+
+ // The servlet invokes setLocale() before invoking service(). We want
+ // to ignore that setLocale() ... that is, not force a cookie to be
+ // written.
+
+ _localeChanged = false;
+
+ if (_resolver == null)
+ _resolver = servlet.getResourceResolver();
+
+ try
+ {
+ setupForRequest(context);
+
+ monitor = getMonitor(context);
+
+ output = new ResponseOutputStream(context.getResponse());
+ }
+ catch (Exception ex)
+ {
+ reportException(Tapestry.getMessage("AbstractEngine.unable-to-begin-request"), ex);
+
+ throw new ServletException(ex.getMessage(), ex);
+ }
+
+ IEngineService service = null;
+
+ try
+ {
+ try
+ {
+ String serviceName;
+
+ try
+ {
+ serviceName = extractServiceName(context);
+
+ if (Tapestry.isBlank(serviceName))
+ serviceName = Tapestry.HOME_SERVICE;
+
+ // Must have a service to create the request cycle.
+ // Must have a request cycle to report an exception.
+
+ service = getService(serviceName);
+ }
+ catch (Exception ex)
+ {
+ service = getService(Tapestry.HOME_SERVICE);
+ cycle = createRequestCycle(context, service, monitor);
+
+ throw ex;
+ }
+
+ cycle = createRequestCycle(context, service, monitor);
+
+ monitor.serviceBegin(serviceName, context.getRequestURI());
+
+ // Invoke the service, which returns true if it may have changed
+ // the state of the engine (most do return true).
+
+ service.service(this, cycle, output);
+
+ // Return true only if the engine is actually dirty. This cuts down
+ // on the number of times the engine is stored into the
+ // session unceccesarily.
+
+ return _dirty;
+ }
+ catch (PageRedirectException ex)
+ {
+ handlePageRedirectException(ex, cycle, output);
+ }
+ catch (RedirectException ex)
+ {
+ handleRedirectException(cycle, ex);
+ }
+ catch (StaleLinkException ex)
+ {
+ handleStaleLinkException(ex, cycle, output);
+ }
+ catch (StaleSessionException ex)
+ {
+ handleStaleSessionException(ex, cycle, output);
+ }
+ }
+ catch (Exception ex)
+ {
+ monitor.serviceException(ex);
+
+ // Discard any output (if possible). If output has already been sent to
+ // the client, then things get dicey. Note that this block
+ // gets activated if the StaleLink or StaleSession pages throws
+ // any kind of exception.
+
+ // Attempt to switch to the exception page. However, this may itself fail
+ // for a number of reasons, in which case a ServletException is thrown.
+
+ output.reset();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Uncaught exception", ex);
+
+ activateExceptionPage(cycle, output, ex);
+ }
+ finally
+ {
+ if (service != null)
+ monitor.serviceEnd(service.getName());
+
+ try
+ {
+ cycle.cleanup();
+
+ // Closing the buffered output closes the underlying stream as well.
+
+ if (output != null)
+ output.forceFlush();
+ }
+ catch (Exception ex)
+ {
+ reportException(Tapestry.getMessage("AbstractEngine.exception-during-cleanup"), ex);
+ }
+ finally
+ {
+ cleanupAfterRequest(cycle);
+ }
+
+ if (_disableCaching)
+ {
+ try
+ {
+ clearCachedData();
+ }
+ catch (Exception ex)
+ {
+ reportException(
+ Tapestry.getMessage("AbstractEngine.exception-during-cache-clear"),
+ ex);
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("End service");
+
+ }
+
+ return _dirty;
+ }
+
+ /**
+ * Handles {@link PageRedirectException} which involves
+ * executing {@link IPage#validate(IRequestCycle)} on the target page
+ * (of the exception), until either a loop is found, or a page
+ * succesfully validates and can be activated.
+ *
+ * <p>This should generally not be overriden in subclasses.
+ *
+ * @since 3.0
+ */
+
+ protected void handlePageRedirectException(
+ PageRedirectException ex,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws IOException, ServletException
+ {
+ List pageNames = new ArrayList();
+
+ String pageName = ex.getTargetPageName();
+
+ while (true)
+ {
+ if (pageNames.contains(pageName))
+ {
+ // Add the offending page to pageNames so it shows in the
+ // list.
+
+ pageNames.add(pageName);
+
+ StringBuffer buffer = new StringBuffer();
+ int count = pageNames.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i > 0)
+ buffer.append("; ");
+
+ buffer.append(pageNames.get(i));
+ }
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.validate-cycle", buffer.toString()));
+ }
+
+ // Record that this page has been a target.
+
+ pageNames.add(pageName);
+
+ try
+ {
+ // Attempt to activate the new page.
+
+ cycle.activate(pageName);
+
+ break;
+ }
+ catch (PageRedirectException ex2)
+ {
+ pageName = ex2.getTargetPageName();
+ }
+ }
+
+ // Discard any output from the previous page.
+
+ output.reset();
+
+ renderResponse(cycle, output);
+ }
+
+ /**
+ * Invoked from {@link #service(RequestContext)} to create an instance of
+ * {@link IRequestCycle} for the current request. This implementation creates
+ * an returns an instance of {@link RequestCycle}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected IRequestCycle createRequestCycle(
+ RequestContext context,
+ IEngineService service,
+ IMonitor monitor)
+ {
+ return new RequestCycle(this, context, service, monitor);
+ }
+
+ /**
+ * Invoked by {@link #service(RequestContext)} if a {@link StaleLinkException}
+ * is thrown by the {@link IEngineService service}. This implementation
+ * sets the message property of the StaleLink page to the
+ * message provided in the exception,
+ * then invokes
+ * {@link #redirect(String, IRequestCycle, ResponseOutputStream, ApplicationRuntimeException)}
+ * to render the StaleLink page.
+ *
+ * <p>Subclasses may overide this method (without
+ * invoking this implementation). A common practice
+ * is to present an error message on the application's
+ * Home page.
+ *
+ * <p>Alternately, the application may provide its own version of
+ * the StaleLink page, overriding
+ * the framework's implementation (probably a good idea, because the
+ * default page hints at "application errors" and isn't localized).
+ * The overriding StaleLink implementation must
+ * implement a message property of type String.
+ *
+ * @since 0.2.10
+ *
+ **/
+
+ protected void handleStaleLinkException(
+ StaleLinkException ex,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws IOException, ServletException
+ {
+ String staleLinkPageName = getStaleLinkPageName();
+ IPage page = cycle.getPage(staleLinkPageName);
+
+ page.setProperty("message", ex.getMessage());
+
+ redirect(staleLinkPageName, cycle, output, ex);
+ }
+
+ /**
+ * Invoked by {@link #service(RequestContext)} if a {@link StaleSessionException}
+ * is thrown by the {@link IEngineService service}. This implementation
+ * invokes
+ * {@link #redirect(String, IRequestCycle, ResponseOutputStream, ApplicationRuntimeException)}
+ * to render the StaleSession page.
+ *
+ * <p>Subclasses may overide this method (without
+ * invoking this implementation). A common practice
+ * is to present an eror message on the application's
+ * Home page.
+ *
+ * @since 0.2.10
+ **/
+
+ protected void handleStaleSessionException(
+ StaleSessionException ex,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws IOException, ServletException
+ {
+ redirect(getStaleSessionPageName(), cycle, output, ex);
+ }
+
+ /**
+ * Discards all cached pages, component specifications and templates.
+ * Subclasses who override this method should invoke this implementation
+ * as well.
+ *
+ * @since 1.0.1
+ *
+ **/
+
+ public void clearCachedData()
+ {
+ _pool.clear();
+ _pageSource.reset();
+ _specificationSource.reset();
+ _templateSource.reset();
+ _scriptSource.reset();
+ _stringsSource.reset();
+ _enhancer.reset();
+ _resourceChecksumSource.reset();
+ }
+
+ /**
+ * Changes the locale for the engine.
+ *
+ **/
+
+ public void setLocale(Locale value)
+ {
+ if (value == null)
+ throw new IllegalArgumentException("May not change engine locale to null.");
+
+ // Because locale changes are expensive (it involves writing a cookie and all that),
+ // we're careful not to really change unless there's a true change in value.
+
+ if (!value.equals(_locale))
+ {
+ _locale = value;
+ _localeChanged = true;
+ markDirty();
+ }
+ }
+
+ /**
+ * Invoked from {@link #service(RequestContext)} to ensure that the engine's
+ * instance variables are setup. This allows the application a chance to
+ * restore transient variables that will not have survived deserialization.
+ *
+ * Determines the servlet prefix: this is the base URL used by
+ * {@link IEngineService services} to build URLs. It consists
+ * of two parts: the context path and the servlet path.
+ *
+ * <p>The servlet path is retrieved from {@link HttpServletRequest#getServletPath()}.
+ *
+ * <p>The context path is retrieved from {@link HttpServletRequest#getContextPath()}.
+ *
+ * <p>The global object is retrieved from {@link IEngine#getGlobal()} method.
+ *
+ * <p>The final path is available via the {@link #getServletPath()} method.
+ *
+ * <p>In addition, this method locates and/or creates the:
+ * <ul>
+ * <li>{@link IComponentClassEnhancer}
+ * <li>{@link Pool}
+ * <li>{@link ITemplateSource}
+ * <li>{@link ISpecificationSource}
+ * <li>{@link IPageSource}
+ * <li>{@link IEngineService} {@link Map}
+ * <ll>{@link IScriptSource}
+ * <li>{@link IComponentMessagesSource}
+ * <li>{@link IPropertySource}
+ * </ul>
+ *
+ * <p>This order is important, because some of the later shared objects
+ * depend on some of the earlier shared objects already having
+ * been located or created
+ * (especially {@link #getPool() pool}).
+ *
+ * <p>Subclasses should invoke this implementation first, then perform their
+ * own setup.
+ *
+ **/
+
+ protected void setupForRequest(RequestContext context)
+ {
+ HttpServlet servlet = context.getServlet();
+ ServletContext servletContext = servlet.getServletContext();
+ HttpServletRequest request = context.getRequest();
+ HttpSession session = context.getSession();
+
+ if (session != null)
+ _sessionId = context.getSession().getId();
+ else
+ _sessionId = null;
+
+ // Previously, this used getRemoteHost(), but that requires an
+ // expensive reverse DNS lookup. Possibly, the host name lookup
+ // should occur ... but only if there's an actual error message
+ // to display.
+
+ if (_clientAddress == null)
+ _clientAddress = request.getRemoteAddr();
+
+ // servletPath is null, so this means either we're doing the
+ // first request in this session, or we're handling a subsequent
+ // request in another JVM (i.e. another server in the cluster).
+ // In any case, we have to do some late (re-)initialization.
+
+ if (_servletPath == null)
+ {
+ // Get the path *within* the servlet context
+
+ // In rare cases related to the tagsupport service, getServletPath() is wrong
+ // (its a JSP, which invokes Tapestry as an include, thus muddling what
+ // the real servlet and servlet path is). In those cases, the JSP tag
+ // will inform us.
+
+ String path =
+ (String) request.getAttribute(Tapestry.TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE);
+
+ if (path == null)
+ path = request.getServletPath();
+
+ // Get the context path, which may be the empty string
+ // (but won't be null).
+
+ _contextPath = request.getContextPath();
+
+ _servletPath = _contextPath + path;
+ }
+
+ String servletName = context.getServlet().getServletName();
+
+ if (_propertySource == null)
+ {
+ String name = PROPERTY_SOURCE_NAME + ":" + servletName;
+
+ _propertySource = (IPropertySource) servletContext.getAttribute(name);
+
+ if (_propertySource == null)
+ {
+ _propertySource = createPropertySource(context);
+
+ servletContext.setAttribute(name, _propertySource);
+ }
+ }
+
+ if (_enhancer == null)
+ {
+ String name = ENHANCER_NAME + ":" + servletName;
+
+ _enhancer = (IComponentClassEnhancer) servletContext.getAttribute(name);
+
+ if (_enhancer == null)
+ {
+ _enhancer = createComponentClassEnhancer(context);
+
+ servletContext.setAttribute(name, _enhancer);
+ }
+ }
+
+ if (_pool == null)
+ {
+ String name = POOL_NAME + ":" + servletName;
+
+ _pool = (Pool) servletContext.getAttribute(name);
+
+ if (_pool == null)
+ {
+ _pool = createPool(context);
+
+ servletContext.setAttribute(name, _pool);
+ }
+ }
+
+ if (_templateSource == null)
+ {
+ String name = TEMPLATE_SOURCE_NAME + ":" + servletName;
+
+ _templateSource = (ITemplateSource) servletContext.getAttribute(name);
+
+ if (_templateSource == null)
+ {
+ _templateSource = createTemplateSource(context);
+
+ servletContext.setAttribute(name, _templateSource);
+ }
+ }
+
+ if (_specificationSource == null)
+ {
+ String name = SPECIFICATION_SOURCE_NAME + ":" + servletName;
+
+ _specificationSource = (ISpecificationSource) servletContext.getAttribute(name);
+
+ if (_specificationSource == null)
+ {
+ _specificationSource = createSpecificationSource(context);
+
+ servletContext.setAttribute(name, _specificationSource);
+ }
+ }
+
+ if (_pageSource == null)
+ {
+ String name = PAGE_SOURCE_NAME + ":" + servletName;
+
+ _pageSource = (IPageSource) servletContext.getAttribute(name);
+
+ if (_pageSource == null)
+ {
+ _pageSource = createPageSource(context);
+
+ servletContext.setAttribute(name, _pageSource);
+ }
+ }
+
+ if (_scriptSource == null)
+ {
+ String name = SCRIPT_SOURCE_NAME + ":" + servletName;
+
+ _scriptSource = (IScriptSource) servletContext.getAttribute(name);
+
+ if (_scriptSource == null)
+ {
+ _scriptSource = createScriptSource(context);
+
+ servletContext.setAttribute(name, _scriptSource);
+ }
+ }
+
+ if (_serviceMap == null)
+ {
+ String name = SERVICE_MAP_NAME + ":" + servletName;
+
+ _serviceMap = (Map) servletContext.getAttribute(name);
+
+ if (_serviceMap == null)
+ {
+ _serviceMap = createServiceMap();
+
+ servletContext.setAttribute(name, _serviceMap);
+ }
+ }
+
+ if (_stringsSource == null)
+ {
+ String name = STRINGS_SOURCE_NAME + ":" + servletName;
+
+ _stringsSource = (IComponentMessagesSource) servletContext.getAttribute(name);
+
+ if (_stringsSource == null)
+ {
+ _stringsSource = createComponentStringsSource(context);
+
+ servletContext.setAttribute(name, _stringsSource);
+ }
+ }
+
+ if (_dataSqueezer == null)
+ {
+ String name = DATA_SQUEEZER_NAME + ":" + servletName;
+
+ _dataSqueezer = (DataSqueezer) servletContext.getAttribute(name);
+
+ if (_dataSqueezer == null)
+ {
+ _dataSqueezer = createDataSqueezer();
+
+ servletContext.setAttribute(name, _dataSqueezer);
+ }
+ }
+
+ if (_global == null)
+ {
+ String name = GLOBAL_NAME + ":" + servletName;
+
+ _global = servletContext.getAttribute(name);
+
+ if (_global == null)
+ {
+ _global = createGlobal(context);
+
+ servletContext.setAttribute(name, _global);
+ }
+ }
+
+ if (_resourceChecksumSource == null)
+ {
+ String name = RESOURCE_CHECKSUM_SOURCE_NAME + ":" + servletName;
+
+ _resourceChecksumSource = (ResourceChecksumSource) servletContext.getAttribute(name);
+
+ if (_resourceChecksumSource == null)
+ {
+ _resourceChecksumSource = createResourceChecksumSource();
+
+ servletContext.setAttribute(name, _resourceChecksumSource);
+ }
+ }
+
+ String encoding = request.getCharacterEncoding();
+ if (encoding == null)
+ {
+ encoding = getOutputEncoding();
+ try
+ {
+ request.setCharacterEncoding(encoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new IllegalArgumentException(Tapestry.format("illegal-encoding", encoding));
+ }
+ catch (NoSuchMethodError e)
+ {
+ // Servlet API 2.2 compatibility
+ // Behave okay if the setCharacterEncoding() method is unavailable
+ }
+ catch (AbstractMethodError e)
+ {
+ // Servlet API 2.2 compatibility
+ // Behave okay if the setCharacterEncoding() method is unavailable
+ }
+ }
+ }
+
+ /**
+ *
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * a new instance of {@link IComponentMessagesSource}.
+ *
+ * @return an instance of {@link DefaultComponentMessagesSource}
+ * @since 2.0.4
+ *
+ **/
+
+ public IComponentMessagesSource createComponentStringsSource(RequestContext context)
+ {
+ return new DefaultComponentMessagesSource();
+ }
+
+ /**
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * an instance of {@link IScriptSource} that will be stored into
+ * the {@link ServletContext}. Subclasses may override this method
+ * to provide a different implementation.
+ *
+ *
+ * @return an instance of {@link DefaultScriptSource}
+ * @since 1.0.9
+ *
+ **/
+
+ protected IScriptSource createScriptSource(RequestContext context)
+ {
+ return new DefaultScriptSource(getResourceResolver());
+ }
+
+ /**
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * an instance of {@link IPageSource} that will be stored into
+ * the {@link ServletContext}. Subclasses may override this method
+ * to provide a different implementation.
+ *
+ * @return an instance of {@link PageSource}
+ * @since 1.0.9
+ *
+ **/
+
+ protected IPageSource createPageSource(RequestContext context)
+ {
+ return new PageSource(this);
+ }
+
+ /**
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * an instance of {@link ISpecificationSource} that will be stored into
+ * the {@link ServletContext}. Subclasses may override this method
+ * to provide a different implementation.
+ *
+ * @return an instance of {@link DefaultSpecificationSource}
+ * @since 1.0.9
+ **/
+
+ protected ISpecificationSource createSpecificationSource(RequestContext context)
+ {
+ return new DefaultSpecificationSource(getResourceResolver(), _specification, _pool);
+ }
+
+ /**
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * an instance of {@link ITemplateSource} that will be stored into
+ * the {@link ServletContext}. Subclasses may override this method
+ * to provide a different implementation.
+ *
+ * @return an instance of {@link DefaultTemplateSource}
+ * @since 1.0.9
+ *
+ **/
+
+ protected ITemplateSource createTemplateSource(RequestContext context)
+ {
+ return new DefaultTemplateSource();
+ }
+
+ /**
+ * Invoked from {@link #setupForRequest(RequestContext)} to provide
+ * an instance of {@link ResourceChecksumSource} that will be stored into
+ * the {@link ServletContext}. Subclasses may override this method
+ * to provide a different implementation.
+ * @return an instance of {@link ResourceChecksumSourceImpl} that uses MD5 and Hex encoding.
+ * @since 3.0.3
+ */
+ protected ResourceChecksumSource createResourceChecksumSource()
+ {
+ return new ResourceChecksumSourceImpl("MD5", new Hex());
+ }
+
+ /**
+ * Returns an object which can find resources and classes.
+ *
+ **/
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ /**
+ * Generates a description of the instance.
+ * Invokes {@link #extendDescription(ToStringBuilder)}
+ * to fill in details about the instance.
+ *
+ * @see #extendDescription(ToStringBuilder)
+ *
+ **/
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append(
+ "name",
+ _specification == null
+ ? Tapestry.getMessage("AbstractEngine.unknown-specification")
+ : _specification.getName());
+
+ builder.append("dirty", _dirty);
+ builder.append("locale", _locale);
+ builder.append("stateful", _stateful);
+ builder.append("visit", _visit);
+
+ extendDescription(builder);
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns true if the reset service is curently enabled.
+ *
+ **/
+
+ public boolean isResetServiceEnabled()
+ {
+ return _resetServiceEnabled;
+ }
+
+ /**
+ * Implemented by subclasses to return the names of the active pages
+ * (pages for which recorders exist). May return the empty list,
+ * but should not return null.
+ *
+ **/
+
+ abstract public Collection getActivePageNames();
+
+ /**
+ * Gets the visit object, if it has been created already.
+ *
+ * <p>
+ * If the visit is non-null then
+ * the {@link #isDirty()} flag is set (because
+ * the engine can't tell what the caller will
+ * <i>do</i> with the visit).
+ *
+ **/
+
+ public Object getVisit()
+ {
+ if (_visit != null)
+ markDirty();
+
+ return _visit;
+ }
+
+ /**
+ * Gets the visit object, invoking {@link #createVisit(IRequestCycle)} to create
+ * it lazily if needed. If cycle is null, the visit will not be lazily created.
+ *
+ * <p>
+ * After creating the visit, but before returning,
+ * the {@link HttpSession} will be created, and
+ * {@link #setStateful()} will be invoked.
+ *
+ * <p>
+ * Sets the {@link #isDirty()} flag, if the return value
+ * is not null.
+ *
+ *
+ **/
+
+ public Object getVisit(IRequestCycle cycle)
+ {
+ if (_visit == null && cycle != null)
+ {
+ _visit = createVisit(cycle);
+
+ // Now that a visit object exists, we need to force the creation
+ // of a HttpSession.
+
+ cycle.getRequestContext().createSession();
+
+ setStateful();
+ }
+
+ if (_visit != null)
+ markDirty();
+
+ return _visit;
+ }
+
+ /**
+ * Updates the visit object and
+ * sets the {@link #isDirty() dirty flag}.
+ *
+ **/
+
+ public void setVisit(Object value)
+ {
+ _visit = value;
+
+ markDirty();
+ }
+
+ public boolean getHasVisit()
+ {
+ return _visit != null;
+ }
+
+ /**
+ * Invoked to lazily create a new visit object when it is first
+ * referenced (by {@link #getVisit(IRequestCycle)}). This implementation works
+ * by looking up the name of the class to instantiate
+ * in the {@link #getPropertySource() configuration}.
+ *
+ * <p>Subclasses may want to overide this method if some other means
+ * of instantiating a visit object is required.
+ **/
+
+ protected Object createVisit(IRequestCycle cycle)
+ {
+ String visitClassName;
+ Class visitClass;
+ Object result = null;
+
+ visitClassName = _propertySource.getPropertyValue(VISIT_CLASS_PROPERTY_NAME);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating visit object as instance of " + visitClassName);
+
+ visitClass = _resolver.findClass(visitClassName);
+
+ try
+ {
+ result = visitClass.newInstance();
+ }
+ catch (Throwable t)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-instantiate-visit", visitClassName),
+ t);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the global object for the application. The global object is created at the start
+ * of the request ({@link #setupForRequest(RequestContext)} invokes
+ * {@link #createGlobal(RequestContext)} if needed),
+ * and is stored into the {@link ServletContext}. All instances of the engine for
+ * the application share
+ * the global object; however, the global object is explicitly <em>not</em>
+ * replicated to other servers within
+ * a cluster.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public Object getGlobal()
+ {
+ return _global;
+ }
+
+ public IScriptSource getScriptSource()
+ {
+ return _scriptSource;
+ }
+
+ public boolean isStateful()
+ {
+ return _stateful;
+ }
+
+ /**
+ * Invoked by subclasses to indicate that some state must now be stored
+ * in the engine (and that the engine should now be stored in the
+ * HttpSession). The caller is responsible for actually creating
+ * the HttpSession (it will have access to the {@link RequestContext}).
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ protected void setStateful()
+ {
+ _stateful = true;
+ }
+
+ /**
+ * Allows subclasses to include listener methods easily.
+ *
+ * @since 1.0.2
+ **/
+
+ public ListenerMap getListeners()
+ {
+ if (_listeners == null)
+ _listeners = new ListenerMap(this);
+
+ return _listeners;
+ }
+
+ private static class RedirectAnalyzer
+ {
+ private boolean _internal;
+ private String _location;
+
+ private RedirectAnalyzer(String location)
+ {
+ if (Tapestry.isBlank(location))
+ {
+ _location = "";
+ _internal = true;
+
+ return;
+ }
+
+ _location = location;
+
+ _internal = !(location.startsWith("/") || location.indexOf("://") > 0);
+ }
+
+ public void process(IRequestCycle cycle)
+ {
+ RequestContext context = cycle.getRequestContext();
+
+ if (_internal)
+ forward(context);
+ else
+ redirect(context);
+ }
+
+ private void forward(RequestContext context)
+ {
+ HttpServletRequest request = context.getRequest();
+ HttpServletResponse response = context.getResponse();
+
+ RequestDispatcher dispatcher = request.getRequestDispatcher("/" + _location);
+
+ if (dispatcher == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-find-dispatcher", _location));
+
+ try
+ {
+ dispatcher.forward(request, response);
+ }
+ catch (ServletException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-forward", _location),
+ ex);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-forward", _location),
+ ex);
+ }
+ }
+
+ private void redirect(RequestContext context)
+ {
+ HttpServletResponse response = context.getResponse();
+
+ String finalURL = response.encodeRedirectURL(_location);
+
+ try
+ {
+ response.sendRedirect(finalURL);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-redirect", _location),
+ ex);
+ }
+ }
+
+ }
+
+ /**
+ * Invoked when a {@link RedirectException} is thrown during the processing of a request.
+ *
+ * @throws ApplicationRuntimeException if an {@link IOException},
+ * {@link ServletException} is thrown by the redirect, or if no
+ * {@link RequestDispatcher} can be found for local resource.
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected void handleRedirectException(IRequestCycle cycle, RedirectException ex)
+ {
+ String location = ex.getRedirectLocation();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Redirecting to: " + location);
+
+ RedirectAnalyzer analyzer = new RedirectAnalyzer(location);
+
+ analyzer.process(cycle);
+ }
+
+ /**
+ * Creates a Map of all the services available to the application.
+ *
+ * <p>Note: the Map returned is not synchronized, on the theory that returned
+ * map is not further modified and therefore threadsafe.
+ *
+ **/
+
+ private Map createServiceMap()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating service map.");
+
+ ISpecificationSource source = getSpecificationSource();
+
+ // Build the initial version of the result map,
+ // where each value is the *name* of a class.
+
+ Map result = new HashMap();
+
+ // Do the framework first.
+
+ addServices(source.getFrameworkNamespace(), result);
+
+ // And allow the application to override the framework.
+
+ addServices(source.getApplicationNamespace(), result);
+
+ IResourceResolver resolver = getResourceResolver();
+
+ Iterator i = result.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String name = (String) entry.getKey();
+ String className = (String) entry.getValue();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating service " + name + " as instance of " + className);
+
+ Class serviceClass = resolver.findClass(className);
+
+ try
+ {
+ IEngineService service = (IEngineService) serviceClass.newInstance();
+ String serviceName = service.getName();
+
+ if (!service.getName().equals(name))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "AbstractEngine.service-name-mismatch",
+ name,
+ className,
+ serviceName));
+
+ // Replace the class name with an instance
+ // of the named class.
+
+ entry.setValue(service);
+ }
+ catch (InstantiationException ex)
+ {
+ String message =
+ Tapestry.format(
+ "AbstractEngine.unable-to-instantiate-service",
+ name,
+ className);
+
+ LOG.error(message, ex);
+
+ throw new ApplicationRuntimeException(message, ex);
+ }
+ catch (IllegalAccessException ex)
+ {
+ String message =
+ Tapestry.format(
+ "AbstractEngine.unable-to-instantiate-service",
+ name,
+ className);
+
+ LOG.error(message, ex);
+
+ throw new ApplicationRuntimeException(message, ex);
+ }
+ }
+
+ // Result should not be modified after this point, for threadsafety issues.
+ // We could wrap it in an unmodifiable, but for efficiency we don't.
+
+ return result;
+ }
+
+ /**
+ * Locates all services in the namespace and adds key/value
+ * pairs to the map (name and class name). Then recursively
+ * descendends into child namespaces to collect more
+ * service names.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private void addServices(INamespace namespace, Map map)
+ {
+ List names = namespace.getServiceNames();
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) names.get(i);
+
+ map.put(name, namespace.getServiceClassName(name));
+ }
+
+ List namespaceIds = namespace.getChildIds();
+ count = namespaceIds.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String id = (String) namespaceIds.get(i);
+
+ addServices(namespace.getChildNamespace(id), map);
+ }
+ }
+
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ public IComponentMessagesSource getComponentMessagesSource()
+ {
+ return _stringsSource;
+ }
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ public DataSqueezer getDataSqueezer()
+ {
+ return _dataSqueezer;
+ }
+
+ /**
+ *
+ * Invoked from {@link #setupForRequest(RequestContext)} to create
+ * a {@link DataSqueezer} when needed (typically, just the very first time).
+ * This implementation returns a standard, new instance.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public DataSqueezer createDataSqueezer()
+ {
+ return new DataSqueezer(_resolver);
+ }
+
+ /**
+ * Invoked from {@link #service(RequestContext)} to extract, from the URL,
+ * the name of the service. The current implementation expects the first
+ * pathInfo element to be the service name. At some point in the future,
+ * the method of constructing and parsing URLs may be abstracted into
+ * a developer-selected class.
+ *
+ * <p>Subclasses may override this method if the application defines
+ * specific services with unusual URL encoding rules.
+ *
+ * <p>This implementation simply extracts the value for
+ * query parameter {@link Tapestry#SERVICE_QUERY_PARAMETER_NAME}
+ * and extracts the service name from that.
+ *
+ * <p>
+ * For supporting the JSP tags, this method first
+ * checks for attribute {@link Tapestry#TAG_SUPPORT_SERVICE_ATTRIBUTE}. If non-null,
+ * then {@link Tapestry#TAGSUPPORT_SERVICE} is returned.
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected String extractServiceName(RequestContext context)
+ {
+ if (context.getRequest().getAttribute(Tapestry.TAG_SUPPORT_SERVICE_ATTRIBUTE) != null)
+ return Tapestry.TAGSUPPORT_SERVICE;
+
+ String serviceData = context.getParameter(Tapestry.SERVICE_QUERY_PARAMETER_NAME);
+
+ if (serviceData == null)
+ return Tapestry.HOME_SERVICE;
+
+ // The service name is anything before the first slash,
+ // if there is one.
+
+ int slashx = serviceData.indexOf('/');
+
+ if (slashx < 0)
+ return serviceData;
+
+ return serviceData.substring(0, slashx);
+ }
+
+ /** @since 2.3 **/
+
+ public IPropertySource getPropertySource()
+ {
+ return _propertySource;
+ }
+
+ /** @since 3.0.3 */
+
+ public ResourceChecksumSource getResourceChecksumSource()
+ {
+ return _resourceChecksumSource;
+ }
+
+ /** @since 3.0 **/
+
+ protected String getExceptionPageName()
+ {
+ return EXCEPTION_PAGE;
+ }
+
+ /** @since 3.0 **/
+
+ protected String getStaleLinkPageName()
+ {
+ return STALE_LINK_PAGE;
+ }
+
+ /** @since 3.0 **/
+
+ protected String getStaleSessionPageName()
+ {
+ return STALE_SESSION_PAGE;
+ }
+
+ /**
+ * Name of an application extension that can provide configuration properties.
+ *
+ * @see #createPropertySource(RequestContext)
+ * @since 2.3
+ *
+ **/
+
+ private static final String EXTENSION_PROPERTY_SOURCE_NAME =
+ "org.apache.tapestry.property-source";
+
+ /**
+ * Creates a shared property source that will be stored into
+ * the servlet context.
+ * Subclasses may override this method to build thier
+ * own search path.
+ *
+ * <p>If the application specification contains an extension
+ * named "org.apache.tapestry.property-source" it is inserted
+ * in the search path just before
+ * the property source for JVM System Properties. This is a simple
+ * hook at allow application-specific methods of obtaining
+ * configuration values (typically, from a database or from JMX,
+ * in some way). Alternately, subclasses may
+ * override this method to provide whatever search path
+ * is appropriate.
+ *
+ *
+ * @since 2.3
+ *
+ **/
+
+ protected IPropertySource createPropertySource(RequestContext context)
+ {
+ DelegatingPropertySource result = new DelegatingPropertySource();
+
+ ApplicationServlet servlet = context.getServlet();
+ IApplicationSpecification spec = servlet.getApplicationSpecification();
+
+ result.addSource(new PropertyHolderPropertySource(spec));
+ result.addSource(new ServletPropertySource(servlet.getServletConfig()));
+ result.addSource(new ServletContextPropertySource(servlet.getServletContext()));
+
+ if (spec.checkExtension(EXTENSION_PROPERTY_SOURCE_NAME))
+ {
+ IPropertySource source =
+ (IPropertySource) spec.getExtension(
+ EXTENSION_PROPERTY_SOURCE_NAME,
+ IPropertySource.class);
+
+ result.addSource(source);
+ }
+
+ result.addSource(SystemPropertiesPropertySource.getInstance());
+
+ // Lastly, add a final source to handle "factory defaults".
+
+ ResourceBundle bundle =
+ ResourceBundle.getBundle("org.apache.tapestry.ConfigurationDefaults");
+
+ result.addSource(new ResourceBundlePropertySource(bundle));
+
+ return result;
+ }
+
+ /**
+ * Creates the shared Global object. This implementation looks for an configuration
+ * property, <code>org.apache.tapestry.global-class</code>, and instantiates that class
+ * using a no-arguments
+ * constructor. If the property is not defined, a synchronized
+ * {@link java.util.HashMap} is created.
+ *
+ * @since 2.3
+ *
+ **/
+
+ protected Object createGlobal(RequestContext context)
+ {
+ String className = _propertySource.getPropertyValue("org.apache.tapestry.global-class");
+
+ if (className == null)
+ return Collections.synchronizedMap(new HashMap());
+
+ Class globalClass = _resolver.findClass(className);
+
+ try
+ {
+ return globalClass.newInstance();
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("AbstractEngine.unable-to-instantiate-global", className),
+ ex);
+ }
+ }
+
+ /**
+ * Returns an new instance of {@link Pool}, with the standard
+ * set of adaptors, plus {@link BSFManagerPoolableAdaptor} for
+ * {@link BSFManager}.
+ *
+ * <p>Subclasses may override this
+ * method to configure the Pool differently.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected Pool createPool(RequestContext context)
+ {
+ Pool result = new Pool();
+
+ result.registerAdaptor(BSFManager.class, new BSFManagerPoolableAdaptor());
+
+ return result;
+ }
+
+ /** @since 3.0 **/
+
+ public Pool getPool()
+ {
+ return _pool;
+ }
+
+ /**
+ *
+ * Invoked from {@link #setupForRequest(RequestContext)}. Creates
+ * a new instance of {@link DefaultComponentClassEnhancer}. Subclasses
+ * may override to return a different object.
+ *
+ * <p>
+ * Check the property <code>org.apache.tapestry.enhance.disable-abstract-method-validation</code>
+ * and, if true, disables abstract method validation. This is used in some
+ * errant JDK's (such as IBM's 1.3.1) that incorrectly report concrete methods from
+ * abstract classes as abstract.
+ *
+ * @since 3.0
+ */
+
+ protected IComponentClassEnhancer createComponentClassEnhancer(RequestContext context)
+ {
+ boolean disableValidation =
+ "true".equals(
+ _propertySource.getPropertyValue(
+ "org.apache.tapestry.enhance.disable-abstract-method-validation"));
+
+ return new DefaultComponentClassEnhancer(_resolver, disableValidation);
+ }
+
+ /** @since 3.0 **/
+
+ public IComponentClassEnhancer getComponentClassEnhancer()
+ {
+ return _enhancer;
+ }
+
+ /**
+ * Returns true if the engine has (potentially) changed
+ * state since the last time it was stored
+ * into the {@link javax.servlet.http.HttpSession}. Various
+ * events set this property to true.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public boolean isDirty()
+ {
+ return _dirty;
+ }
+
+ /**
+ * Invoked to set the dirty flag, indicating that the
+ * engine should be stored into the
+ * {@link javax.servlet.http.HttpSession}.
+ *
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void markDirty()
+ {
+ if (!_dirty)
+ LOG.debug("Setting dirty flag.");
+
+ _dirty = true;
+ }
+
+ /**
+ *
+ * Clears the dirty flag when a engine is stored into the
+ * {@link HttpSession}.
+ *
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void valueBound(HttpSessionBindingEvent arg0)
+ {
+ LOG.debug(_dirty ? "Clearing dirty flag." : "Dirty flag already cleared.");
+
+ _dirty = false;
+ }
+
+ /**
+ * Does nothing.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void valueUnbound(HttpSessionBindingEvent arg0)
+ {
+ }
+
+ /**
+ *
+ * The encoding to be used if none has been defined using the output encoding property.
+ * Override this method to change the default.
+ *
+ * @return the default output encoding
+ * @since 3.0
+ *
+ **/
+ protected String getDefaultOutputEncoding()
+ {
+ return DEFAULT_OUTPUT_ENCODING;
+ }
+
+ /**
+ *
+ * Returns the encoding to be used to generate the servlet responses and
+ * accept the servlet requests.
+ *
+ * The encoding is defined using the org.apache.tapestry.output-encoding
+ * and is UTF-8 by default
+ *
+ * @since 3.0
+ * @see org.apache.tapestry.IEngine#getOutputEncoding()
+ *
+ **/
+ public String getOutputEncoding()
+ {
+ IPropertySource source = getPropertySource();
+
+ String encoding = source.getPropertyValue(OUTPUT_ENCODING_PROPERTY_NAME);
+ if (encoding == null)
+ encoding = getDefaultOutputEncoding();
+
+ return encoding;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/AbstractService.java b/tapestry-framework/src/org/apache/tapestry/engine/AbstractService.java
new file mode 100644
index 0000000..8cfd3d2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/AbstractService.java
@@ -0,0 +1,127 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.util.StringSplitter;
+import org.apache.tapestry.util.io.DataSqueezer;
+
+/**
+ * Abstract base class for implementing engine services. Instances of services
+ * are shared by many engines and threads, so they must be threadsafe.
+ *
+ * <p>
+ * Note; too much of the URL encoding/decoding stategy is fixed here.
+ * A future release of Tapestry may extract this strategy, allowing developers
+ * to choose the method via which URLs are encoded.
+ *
+ * @see org.apache.tapestry.engine.AbstractEngine#getService(String)
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.3
+ *
+ **/
+
+public abstract class AbstractService implements IEngineService
+{
+ /**
+ * Constructs a link for the service.
+ *
+ * @param cycle the request cycle
+ * @param serviceName the name of the service
+ * @param serviceContext context related to the service itself which is added to the URL as-is
+ * @param parameters additional service parameters provided by the component;
+ * this is application specific information, and is encoded with
+ * {@link java.net.URLEncoder#encode(String)} before being added
+ * to the query.
+ * @param stateful if true, the final URL must be encoded with the HttpSession id
+ *
+ **/
+
+ protected ILink constructLink(
+ IRequestCycle cycle,
+ String serviceName,
+ String[] serviceContext,
+ Object[] parameters,
+ boolean stateful)
+ {
+ DataSqueezer squeezer = cycle.getEngine().getDataSqueezer();
+ String[] squeezed = null;
+
+ try
+ {
+ squeezed = squeezer.squeeze(parameters);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+
+ return new EngineServiceLink(cycle, serviceName, serviceContext, squeezed, stateful);
+ }
+
+ /**
+ * Returns the service context as an array of Strings.
+ * Returns null if there are no service context strings.
+ *
+ **/
+
+ protected String[] getServiceContext(RequestContext context)
+ {
+ String service = context.getParameter(Tapestry.SERVICE_QUERY_PARAMETER_NAME);
+
+ int slashx = service.indexOf('/');
+
+ if (slashx < 0)
+ return null;
+
+ String serviceContext = service.substring(slashx + 1);
+
+ return new StringSplitter('/').splitToArray(serviceContext);
+ }
+
+ /**
+ * Returns the service parameters as an array of Strings.
+ *
+ **/
+
+ protected Object[] getParameters(IRequestCycle cycle)
+ {
+ RequestContext context = cycle.getRequestContext();
+
+ String[] squeezed = context.getParameters(Tapestry.PARAMETERS_QUERY_PARAMETER_NAME);
+
+ if (Tapestry.size(squeezed) == 0)
+ return squeezed;
+
+ try
+ {
+ DataSqueezer squeezer = cycle.getEngine().getDataSqueezer();
+
+ return squeezer.unsqueeze(squeezed);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ActionService.java b/tapestry-framework/src/org/apache/tapestry/engine/ActionService.java
new file mode 100644
index 0000000..ae48d8f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ActionService.java
@@ -0,0 +1,173 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IAction;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.StaleSessionException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * A context-sensitive service related to {@link org.apache.tapestry.form.Form}
+ * and {@link org.apache.tapestry.link.ActionLink}. Encodes
+ * the page, component and an action id in the service context.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ **/
+
+public class ActionService extends AbstractService
+{
+ /**
+ * Encoded into URL if engine was stateful.
+ *
+ * @since 3.0
+ **/
+
+ private static final String STATEFUL_ON = "1";
+
+ /**
+ * Encoded into URL if engine was not stateful.
+ *
+ * @since 3.0
+ **/
+
+ private static final String STATEFUL_OFF = "0";
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (parameters == null || parameters.length != 1)
+ throw new IllegalArgumentException(
+ Tapestry.format("service-single-parameter", Tapestry.ACTION_SERVICE));
+
+ String stateful = cycle.getEngine().isStateful() ? STATEFUL_ON : STATEFUL_OFF;
+ IPage componentPage = component.getPage();
+ IPage responsePage = cycle.getPage();
+
+ boolean complex = (componentPage != responsePage);
+
+ String[] serviceContext = new String[complex ? 5 : 4];
+
+ int i = 0;
+
+ serviceContext[i++] = stateful;
+ serviceContext[i++] = responsePage.getPageName();
+ serviceContext[i++] = (String) parameters[0];
+
+ // Because of Block/InsertBlock, the component may not be on
+ // the same page as the response page and we need to make
+ // allowances for this.
+
+ if (complex)
+ serviceContext[i++] = componentPage.getPageName();
+
+ serviceContext[i++] = component.getIdPath();
+
+ return constructLink(cycle, Tapestry.ACTION_SERVICE, serviceContext, null, true);
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ IAction action = null;
+ String componentPageName;
+ int count = 0;
+
+ String[] serviceContext = getServiceContext(cycle.getRequestContext());
+
+ if (serviceContext != null)
+ count = serviceContext.length;
+
+ if (count != 4 && count != 5)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("ActionService.context-parameters"));
+
+ boolean complex = count == 5;
+
+ int i = 0;
+ String stateful = serviceContext[i++];
+ String pageName = serviceContext[i++];
+ String targetActionId = serviceContext[i++];
+
+ if (complex)
+ componentPageName = serviceContext[i++];
+ else
+ componentPageName = pageName;
+
+ String targetIdPath = serviceContext[i++];
+
+ IPage page = cycle.getPage(pageName);
+
+ // Setup the page for the rewind, then do the rewind.
+
+ cycle.activate(page);
+
+ IPage componentPage = cycle.getPage(componentPageName);
+ IComponent component = componentPage.getNestedComponent(targetIdPath);
+
+ try
+ {
+ action = (IAction) component;
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ActionService.component-wrong-type", component.getExtendedId()),
+ component,
+ null,
+ ex);
+ }
+
+ // Only perform the stateful check if the application was stateful
+ // when the URL was rendered.
+
+ if (stateful.equals(STATEFUL_ON) && action.getRequiresSession())
+ {
+ HttpSession session = cycle.getRequestContext().getSession();
+
+ if (session == null || session.isNew())
+ throw new StaleSessionException();
+ }
+
+ cycle.rewindPage(targetActionId, action);
+
+ // During the rewind, a component may change the page. This will take
+ // effect during the second render, which renders the HTML response.
+
+ // Render the response.
+
+ engine.renderResponse(cycle, output);
+ }
+
+ public String getName()
+ {
+ return Tapestry.ACTION_SERVICE;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/BSFManagerPoolableAdaptor.java b/tapestry-framework/src/org/apache/tapestry/engine/BSFManagerPoolableAdaptor.java
new file mode 100644
index 0000000..fc20c7e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/BSFManagerPoolableAdaptor.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.bsf.BSFManager;
+import org.apache.tapestry.util.pool.IPoolableAdaptor;
+
+/**
+ * Allows a {@link org.apache.tapestry.util.pool.Pool} to
+ * properly terminate a {@link org.apache.bsf.BSFManager}
+ * when it is discarded.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class BSFManagerPoolableAdaptor implements IPoolableAdaptor
+{
+ /**
+ * Does nothing.
+ *
+ **/
+
+ public void resetForPool(Object object)
+ {
+ }
+
+ /**
+ * Invokes {@link org.apache.bsf.BSFManager#terminate()}.
+ *
+ **/
+
+ public void discardFromPool(Object object)
+ {
+ BSFManager manager = (BSFManager)object;
+
+ manager.terminate();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/BaseEngine.java b/tapestry-framework/src/org/apache/tapestry/engine/BaseEngine.java
new file mode 100644
index 0000000..46f95c2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/BaseEngine.java
@@ -0,0 +1,239 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.record.SessionPageRecorder;
+
+/**
+ * Concrete implementation of {@link org.apache.tapestry.IEngine} used for ordinary
+ * applications. All page state information is maintained in
+ * the {@link javax.servlet.http.HttpSession} using
+ * instances of {@link org.apache.tapestry.record.SessionPageRecorder}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class BaseEngine extends AbstractEngine
+{
+ private static final long serialVersionUID = -7051050643746333380L;
+
+ private final static int MAP_SIZE = 3;
+
+ private transient Map _recorders;
+
+ private transient Set _activePageNames;
+
+ /**
+ * Removes all page recorders that contain no changes, or
+ * are marked for discard. Subclasses
+ * should invoke this implementation in addition to providing
+ * thier own.
+ *
+ **/
+
+ protected void cleanupAfterRequest(IRequestCycle cycle)
+ {
+ if (Tapestry.isEmpty(_recorders))
+ return;
+
+ boolean markDirty = false;
+ Iterator i = _recorders.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ String pageName = (String) entry.getKey();
+ IPageRecorder recorder = (IPageRecorder) entry.getValue();
+
+ if (!recorder.getHasChanges() || recorder.isMarkedForDiscard())
+ {
+ recorder.discard();
+
+ i.remove();
+
+ _activePageNames.remove(pageName);
+
+ markDirty = true;
+ }
+ }
+
+ if (markDirty)
+ markDirty();
+ }
+
+ public void forgetPage(String name)
+ {
+ if (_recorders == null)
+ return;
+
+ IPageRecorder recorder = (IPageRecorder) _recorders.get(name);
+ if (recorder == null)
+ return;
+
+ if (recorder.isDirty())
+ throw new ApplicationRuntimeException(
+ Tapestry.format("BaseEngine.recorder-has-uncommited-changes", name));
+
+ recorder.discard();
+ _recorders.remove(name);
+ _activePageNames.remove(name);
+
+ markDirty();
+ }
+
+ /**
+ * Returns an unmodifiable {@link Collection} of the page names for which
+ * {@link IPageRecorder} instances exist.
+ *
+ *
+ **/
+
+ public Collection getActivePageNames()
+ {
+ if (_activePageNames == null)
+ return Collections.EMPTY_LIST;
+
+ return Collections.unmodifiableCollection(_activePageNames);
+ }
+
+ public IPageRecorder getPageRecorder(String pageName, IRequestCycle cycle)
+ {
+ if (_activePageNames == null || !_activePageNames.contains(pageName))
+ return null;
+
+ IPageRecorder result = null;
+
+ if (_recorders != null)
+ return result = (IPageRecorder) _recorders.get(pageName);
+
+ // So the page is active, but not in the cache of page recoders,
+ // so (re-)create the page recorder.
+
+ if (result == null)
+ result = createPageRecorder(pageName, cycle);
+
+ return result;
+ }
+
+ public IPageRecorder createPageRecorder(String pageName, IRequestCycle cycle)
+ {
+ if (_recorders == null)
+ _recorders = new HashMap(MAP_SIZE);
+ else
+ {
+ if (_recorders.containsKey(pageName))
+ throw new ApplicationRuntimeException(
+ Tapestry.format("BaseEngine.duplicate-page-recorder", pageName));
+ }
+
+ // Force the creation of the HttpSession
+
+ cycle.getRequestContext().createSession();
+ setStateful();
+
+
+ IPageRecorder result = new SessionPageRecorder();
+ result.initialize(pageName, cycle);
+
+ _recorders.put(pageName, result);
+
+ if (_activePageNames == null)
+ _activePageNames = new HashSet();
+
+ _activePageNames.add(pageName);
+
+ markDirty();
+
+ return result;
+ }
+
+ /**
+ * Reconstructs the list of active page names
+ * written by {@link #writeExternal(ObjectOutput)}.
+ *
+ **/
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
+ {
+ super.readExternal(in);
+
+ int count = in.readInt();
+
+ if (count > 0)
+ _activePageNames = new HashSet(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = in.readUTF();
+
+ _activePageNames.add(name);
+ }
+
+ }
+
+ /**
+ * Writes the engine's persistent state; this is simply the list of active page
+ * names. For efficiency, this is written as a count followed by each name
+ * as a UTF String.
+ *
+ **/
+
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ super.writeExternal(out);
+
+ if (Tapestry.isEmpty(_activePageNames))
+ {
+ out.writeInt(0);
+ return;
+ }
+
+ int count = _activePageNames.size();
+
+ out.writeInt(count);
+
+ Iterator i = _activePageNames.iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+
+ out.writeUTF(name);
+ }
+ }
+
+ public void extendDescription(ToStringBuilder builder)
+ {
+ builder.append("activePageNames", _activePageNames);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ComponentMessages.java b/tapestry-framework/src/org/apache/tapestry/engine/ComponentMessages.java
new file mode 100644
index 0000000..e081309
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ComponentMessages.java
@@ -0,0 +1,109 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.Properties;
+
+import org.apache.tapestry.IMessages;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Implementation of {@link org.apache.tapestry.IMessages}. This is basically
+ * a wrapper around an instance of {@link Properties}. This ensures
+ * that the properties are, in fact, read-only (which ensures that
+ * they don't have to be synchronized).
+ *
+ * @author Howard Lewis Ship
+ * @since 2.0.4
+ *
+ **/
+
+public class ComponentMessages implements IMessages
+{
+ private Properties _properties;
+ private Locale _locale;
+
+ public ComponentMessages(Locale locale, Properties properties)
+ {
+ _locale = locale;
+ _properties = properties;
+ }
+
+ public String getMessage(String key, String defaultValue)
+ {
+ return _properties.getProperty(key, defaultValue);
+ }
+
+ public String getMessage(String key)
+ {
+ String result = _properties.getProperty(key);
+
+ if (result == null)
+ result = "[" + key.toUpperCase() + "]";
+
+ return result;
+ }
+
+ public String format(String key, Object argument1, Object argument2, Object argument3)
+ {
+ return format(key, new Object[] { argument1, argument2, argument3 });
+ }
+
+ public String format(String key, Object argument1, Object argument2)
+ {
+ return format(key, new Object[] { argument1, argument2 });
+ }
+
+ public String format(String key, Object argument)
+ {
+ return format(key, new Object[] { argument });
+ }
+
+ public String format(String key, Object[] arguments)
+ {
+ String pattern = getMessage(key);
+
+ // This ugliness is mandated for JDK 1.3 compatibility, which has a bug
+ // in MessageFormat ... the
+ // pattern is applied in the constructor, using the system default Locale,
+ // regardless of what locale is later specified!
+ // It appears that the problem does not exist in JDK 1.4.
+
+ MessageFormat messageFormat = new MessageFormat("");
+ messageFormat.setLocale(_locale);
+ messageFormat.applyPattern(pattern);
+
+ int count = Tapestry.size(arguments);
+
+ for (int i = 0; i < count; i++)
+ {
+ if (arguments[i] instanceof Throwable)
+ {
+ Throwable t = (Throwable) arguments[i];
+ String message = t.getMessage();
+
+ if (Tapestry.isNonBlank(message))
+ arguments[i] = message;
+ else
+ arguments[i] = t.getClass().getName();
+ }
+ }
+
+ return messageFormat.format(arguments);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DefaultComponentMessagesSource.java b/tapestry-framework/src/org/apache/tapestry/engine/DefaultComponentMessagesSource.java
new file mode 100644
index 0000000..253c438
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DefaultComponentMessagesSource.java
@@ -0,0 +1,245 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMessages;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.MultiKey;
+
+/**
+ * Global object (stored in the servlet context) that accesses
+ * localized properties for a component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.4
+ *
+ **/
+
+public class DefaultComponentMessagesSource implements IComponentMessagesSource
+{
+ private static final Log LOG = LogFactory.getLog(DefaultComponentMessagesSource.class);
+
+ private Properties _emptyProperties = new Properties();
+
+ /**
+ * Map of {@link Properties}, keyed on a {@link MultiKey} of
+ * component specification path and locale.
+ *
+ **/
+
+ private Map _cache = new HashMap();
+
+ /**
+ * Returns an instance of {@link Properties} containing
+ * the properly localized messages for the component,
+ * in the {@link Locale} identified by the component's
+ * containing page.
+ *
+ **/
+
+ protected synchronized Properties getLocalizedProperties(IComponent component)
+ {
+ if (component == null)
+ throw new IllegalArgumentException(
+ Tapestry.format("invalid-null-parameter", "component"));
+
+ IResourceLocation specificationLocation =
+ component.getSpecification().getSpecificationLocation();
+ Locale locale = component.getPage().getLocale();
+
+ // Check to see if already in the cache
+
+ MultiKey key = buildKey(specificationLocation, locale);
+
+ Properties result = (Properties) _cache.get(key);
+
+ if (result != null)
+ return result;
+
+ // Not found, create it now.
+
+ result = assembleProperties(specificationLocation, locale);
+
+ _cache.put(key, result);
+
+ return result;
+ }
+
+ private static final String SUFFIX = ".properties";
+
+ private Properties assembleProperties(IResourceLocation baseResourceLocation, Locale locale)
+ {
+ boolean debug = LOG.isDebugEnabled();
+ if (debug)
+ LOG.debug("Assembling properties for " + baseResourceLocation + " " + locale);
+
+ String name = baseResourceLocation.getName();
+
+ int dotx = name.indexOf('.');
+ String baseName = name.substring(0, dotx);
+
+ String language = locale.getLanguage();
+ String country = locale.getCountry();
+ String variant = locale.getVariant();
+
+ Properties parent = (Properties) _cache.get(baseResourceLocation);
+
+ if (parent == null)
+ {
+ parent = readProperties(baseResourceLocation, baseName, null, null);
+
+ if (parent == null)
+ parent = _emptyProperties;
+
+ _cache.put(baseResourceLocation, parent);
+ }
+
+ Properties result = parent;
+
+ if (!Tapestry.isBlank(language))
+ {
+ Locale l = new Locale(language, "");
+ MultiKey key = buildKey(baseResourceLocation, l);
+
+ result = (Properties) _cache.get(key);
+
+ if (result == null)
+ result = readProperties(baseResourceLocation, baseName, l, parent);
+
+ _cache.put(key, result);
+
+ parent = result;
+ }
+ else
+ language = "";
+
+ if (Tapestry.isNonBlank(country))
+ {
+ Locale l = new Locale(language, country);
+ MultiKey key = buildKey(baseResourceLocation, l);
+
+ result = (Properties) _cache.get(key);
+
+ if (result == null)
+ result = readProperties(baseResourceLocation, baseName, l, parent);
+
+ _cache.put(key, result);
+
+ parent = result;
+ }
+ else
+ country = "";
+
+ if (Tapestry.isNonBlank(variant))
+ {
+ Locale l = new Locale(language, country, variant);
+ MultiKey key = buildKey(baseResourceLocation, l);
+
+ result = (Properties) _cache.get(key);
+
+ if (result == null)
+ result = readProperties(baseResourceLocation, baseName, l, parent);
+
+ _cache.put(key, result);
+ }
+
+ return result;
+ }
+
+ private MultiKey buildKey(IResourceLocation location, Locale locale)
+ {
+ return new MultiKey(new Object[] { location, locale.toString()}, false);
+ }
+
+ private Properties readProperties(
+ IResourceLocation baseLocation,
+ String baseName,
+ Locale locale,
+ Properties parent)
+ {
+ StringBuffer buffer = new StringBuffer(baseName);
+
+ if (locale != null)
+ {
+ buffer.append('_');
+ buffer.append(locale.toString());
+ }
+
+ buffer.append(SUFFIX);
+
+ IResourceLocation localized = baseLocation.getRelativeLocation(buffer.toString());
+
+ URL propertiesURL = localized.getResourceURL();
+
+ if (propertiesURL == null)
+ return parent;
+
+ Properties result = null;
+
+ if (parent == null)
+ result = new Properties();
+ else
+ result = new Properties(parent);
+
+ try
+ {
+ InputStream input = propertiesURL.openStream();
+
+ result.load(input);
+
+ input.close();
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ComponentPropertiesStore.unable-to-read-input", propertiesURL),
+ ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Clears the cache of read properties files.
+ *
+ **/
+
+ public void reset()
+ {
+ _cache.clear();
+ }
+
+ public IMessages getMessages(IComponent component)
+ {
+ return new ComponentMessages(
+ component.getPage().getLocale(),
+ getLocalizedProperties(component));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DefaultMonitorFactory.java b/tapestry-framework/src/org/apache/tapestry/engine/DefaultMonitorFactory.java
new file mode 100644
index 0000000..328524f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DefaultMonitorFactory.java
@@ -0,0 +1,38 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Implementation of {@link org.apache.tapestry.engine.IMonitorFactory}
+ * that returns the {@link org.apache.tapestry.engine.NullMonitor}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ */
+public class DefaultMonitorFactory implements IMonitorFactory
+{
+ public static final IMonitorFactory SHARED = new DefaultMonitorFactory();
+
+ /**
+ * Returns {@link NullMonitor#SHARED}.
+ */
+ public IMonitor createMonitor(RequestContext context)
+ {
+ return NullMonitor.SHARED;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DefaultScriptSource.java b/tapestry-framework/src/org/apache/tapestry/engine/DefaultScriptSource.java
new file mode 100644
index 0000000..38ba857
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DefaultScriptSource.java
@@ -0,0 +1,106 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.script.ScriptParser;
+import org.apache.tapestry.util.xml.DocumentParseException;
+
+/**
+ * Provides basic access to scripts available on the classpath. Scripts are cached in
+ * memory once parsed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ *
+ **/
+
+public class DefaultScriptSource implements IScriptSource
+{
+ private IResourceResolver _resolver;
+
+ private Map _cache = new HashMap();
+
+ public DefaultScriptSource(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ public synchronized void reset()
+ {
+ _cache.clear();
+ }
+
+ public synchronized IScript getScript(IResourceLocation scriptLocation)
+ {
+ IScript result = (IScript) _cache.get(scriptLocation);
+
+ if (result != null)
+ return result;
+
+ result = parse(scriptLocation);
+
+ _cache.put(scriptLocation, result);
+
+ return result;
+ }
+
+ private IScript parse(IResourceLocation location)
+ {
+ ScriptParser parser = new ScriptParser(_resolver);
+
+ try
+ {
+ return parser.parse(location);
+ }
+ catch (DocumentParseException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DefaultScriptSource.unable-to-parse-script", location),
+ ex);
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("DefaultScriptSource@");
+ buffer.append(Integer.toHexString(hashCode()));
+
+ buffer.append('[');
+
+ if (_cache != null)
+ {
+ synchronized (_cache)
+ {
+ buffer.append(_cache.keySet());
+ }
+
+ buffer.append(", ");
+ }
+
+ buffer.append("]");
+
+ return buffer.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DefaultSpecificationSource.java b/tapestry-framework/src/org/apache/tapestry/engine/DefaultSpecificationSource.java
new file mode 100644
index 0000000..96dd020
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DefaultSpecificationSource.java
@@ -0,0 +1,352 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.parse.SpecificationParser;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.ILibrarySpecification;
+import org.apache.tapestry.spec.LibrarySpecification;
+import org.apache.tapestry.util.IRenderDescription;
+import org.apache.tapestry.util.pool.Pool;
+import org.apache.tapestry.util.xml.DocumentParseException;
+
+/**
+ * Default implementation of {@link ISpecificationSource} that
+ * expects to use the normal class loader to locate component
+ * specifications from within the classpath.
+ *
+ * <p>Caches specifications in memory forever, or until {@link #reset()} is invoked.
+ *
+ * <p>An instance of this class acts like a singleton and is shared by multiple sessions,
+ * so it must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class DefaultSpecificationSource implements ISpecificationSource, IRenderDescription
+{
+ private static final Log LOG = LogFactory.getLog(DefaultSpecificationSource.class);
+
+ /**
+ * Key used to get and store {@link SpecificationParser} instances
+ * from the Pool.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private static final String PARSER_POOL_KEY = "org.apache.tapestry.SpecificationParser";
+
+ private IResourceResolver _resolver;
+ private IApplicationSpecification _specification;
+
+ private INamespace _applicationNamespace;
+ private INamespace _frameworkNamespace;
+
+ /**
+ * Contains previously parsed component specifications.
+ *
+ **/
+
+ private Map _componentCache = new HashMap();
+
+ /**
+ * Contains previously parsed page specifications.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private Map _pageCache = new HashMap();
+
+ /**
+ * Contains previously parsed library specifications, keyed
+ * on specification resource path.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private Map _libraryCache = new HashMap();
+
+ /**
+ * Contains {@link INamespace} instances, keyed on id (which will
+ * be null for the application specification).
+ *
+ **/
+
+ private Map _namespaceCache = new HashMap();
+
+ /**
+ * Reference to the shared {@link org.apache.tapestry.util.pool.Pool}.
+ *
+ * @see org.apache.tapestry.IEngine#getPool()
+ *
+ * @since 3.0
+ *
+ **/
+
+ private Pool _pool;
+
+ public DefaultSpecificationSource(
+ IResourceResolver resolver,
+ IApplicationSpecification specification,
+ Pool pool)
+ {
+ _resolver = resolver;
+ _specification = specification;
+ _pool = pool;
+ }
+
+ /**
+ * Clears the specification cache. This is used during debugging.
+ *
+ **/
+
+ public synchronized void reset()
+ {
+ _componentCache.clear();
+ _pageCache.clear();
+ _libraryCache.clear();
+ _namespaceCache.clear();
+
+ _applicationNamespace = null;
+ _frameworkNamespace = null;
+ }
+
+ protected IComponentSpecification parseSpecification(
+ IResourceLocation resourceLocation,
+ boolean asPage)
+ {
+ IComponentSpecification result = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsing component specification " + resourceLocation);
+
+ SpecificationParser parser = getParser();
+
+ try
+ {
+ if (asPage)
+ result = parser.parsePageSpecification(resourceLocation);
+ else
+ result = parser.parseComponentSpecification(resourceLocation);
+ }
+ catch (DocumentParseException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "DefaultSpecificationSource.unable-to-parse-specification",
+ resourceLocation),
+ ex);
+ }
+ finally
+ {
+ discardParser(parser);
+ }
+
+ return result;
+ }
+
+ protected ILibrarySpecification parseLibrarySpecification(IResourceLocation resourceLocation)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsing library specification " + resourceLocation);
+
+ try
+ {
+ return getParser().parseLibrarySpecification(resourceLocation);
+ }
+ catch (DocumentParseException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "DefaultSpecificationSource.unable-to-parse-specification",
+ resourceLocation),
+ ex);
+ }
+
+ }
+
+ public synchronized String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("applicationNamespace", _applicationNamespace);
+ builder.append("frameworkNamespace", _frameworkNamespace);
+ builder.append("specification", _specification);
+
+ return builder.toString();
+ }
+
+ /** @since 1.0.6 **/
+
+ public synchronized void renderDescription(IMarkupWriter writer)
+ {
+ writer.print("DefaultSpecificationSource[");
+
+ writeCacheDescription(writer, "page", _pageCache);
+ writer.beginEmpty("br");
+ writer.println();
+
+ writeCacheDescription(writer, "component", _componentCache);
+ writer.print("]");
+ writer.println();
+ }
+
+ private void writeCacheDescription(IMarkupWriter writer, String name, Map cache)
+ {
+ Set keySet = cache.keySet();
+
+ writer.print(Tapestry.size(keySet));
+ writer.print(" cached ");
+ writer.print(name);
+ writer.print(" specifications:");
+
+ boolean first = true;
+
+ Iterator i = keySet.iterator();
+ while (i.hasNext())
+ {
+ // The keys are now IResourceLocation instances
+
+ Object key = i.next();
+
+ if (first)
+ {
+ writer.begin("ul");
+ first = false;
+ }
+
+ writer.begin("li");
+ writer.print(key.toString());
+ writer.end();
+ }
+
+ if (!first)
+ writer.end(); // <ul>
+ }
+
+ /**
+ * Gets a component specification.
+ *
+ * @param resourceLocation the complete resource path to the specification.
+ * @throws ApplicationRuntimeException if the specification cannot be obtained.
+ *
+ **/
+
+ public synchronized IComponentSpecification getComponentSpecification(IResourceLocation resourceLocation)
+ {
+ IComponentSpecification result =
+ (IComponentSpecification) _componentCache.get(resourceLocation);
+
+ if (result == null)
+ {
+ result = parseSpecification(resourceLocation, false);
+
+ _componentCache.put(resourceLocation, result);
+ }
+
+ return result;
+ }
+
+ public synchronized IComponentSpecification getPageSpecification(IResourceLocation resourceLocation)
+ {
+ IComponentSpecification result = (IComponentSpecification) _pageCache.get(resourceLocation);
+
+ if (result == null)
+ {
+ result = parseSpecification(resourceLocation, true);
+
+ _pageCache.put(resourceLocation, result);
+ }
+
+ return result;
+ }
+
+ public synchronized ILibrarySpecification getLibrarySpecification(IResourceLocation resourceLocation)
+ {
+ ILibrarySpecification result = (LibrarySpecification) _libraryCache.get(resourceLocation);
+
+ if (result == null)
+ {
+ result = parseLibrarySpecification(resourceLocation);
+ _libraryCache.put(resourceLocation, result);
+ }
+
+ return result;
+ }
+
+ /** @since 2.2 **/
+
+ protected SpecificationParser getParser()
+ {
+ SpecificationParser result = (SpecificationParser) _pool.retrieve(PARSER_POOL_KEY);
+
+ if (result == null)
+ result = new SpecificationParser(_resolver);
+
+ return result;
+ }
+
+ /** @since 3.0 **/
+
+ protected void discardParser(SpecificationParser parser)
+ {
+ _pool.store(PARSER_POOL_KEY, parser);
+ }
+
+ public synchronized INamespace getApplicationNamespace()
+ {
+ if (_applicationNamespace == null)
+ _applicationNamespace = new Namespace(null, null, _specification, this);
+
+ return _applicationNamespace;
+ }
+
+ public synchronized INamespace getFrameworkNamespace()
+ {
+ if (_frameworkNamespace == null)
+ {
+ IResourceLocation frameworkLocation =
+ new ClasspathResourceLocation(_resolver, "/org/apache/tapestry/Framework.library");
+
+ ILibrarySpecification ls = getLibrarySpecification(frameworkLocation);
+
+ _frameworkNamespace = new Namespace(INamespace.FRAMEWORK_NAMESPACE, null, ls, this);
+ }
+
+ return _frameworkNamespace;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DefaultTemplateSource.java b/tapestry-framework/src/org/apache/tapestry/engine/DefaultTemplateSource.java
new file mode 100644
index 0000000..44765e1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DefaultTemplateSource.java
@@ -0,0 +1,628 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.parse.ComponentTemplate;
+import org.apache.tapestry.parse.ITemplateParserDelegate;
+import org.apache.tapestry.parse.TemplateParseException;
+import org.apache.tapestry.parse.TemplateParser;
+import org.apache.tapestry.parse.TemplateToken;
+import org.apache.tapestry.resolver.ComponentSpecificationResolver;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.util.DelegatingPropertySource;
+import org.apache.tapestry.util.IRenderDescription;
+import org.apache.tapestry.util.LocalizedPropertySource;
+import org.apache.tapestry.util.MultiKey;
+import org.apache.tapestry.util.PropertyHolderPropertySource;
+
+/**
+ * Default implementation of {@link ITemplateSource}. Templates, once parsed,
+ * stay in memory until explicitly cleared.
+ *
+ * <p>An instance of this class acts as a singleton shared by all sessions, so it
+ * must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class DefaultTemplateSource implements ITemplateSource, IRenderDescription
+{
+ private static final Log LOG = LogFactory.getLog(DefaultTemplateSource.class);
+
+
+ // The name of the component/application/etc property that will be used to
+ // determine the encoding to use when loading the template
+
+ private static final String TEMPLATE_ENCODING_PROPERTY_NAME = "org.apache.tapestry.template-encoding";
+
+ // Cache of previously retrieved templates. Key is a multi-key of
+ // specification resource path and locale (local may be null), value
+ // is the ComponentTemplate.
+
+ private Map _cache = Collections.synchronizedMap(new HashMap());
+
+ // Previously read templates; key is the IResourceLocation, value
+ // is the ComponentTemplate.
+
+ private Map _templates = Collections.synchronizedMap(new HashMap());
+
+ /**
+ * Number of tokens (each template contains multiple tokens).
+ *
+ **/
+
+ private int _tokenCount;
+
+ private static final int BUFFER_SIZE = 2000;
+
+ private TemplateParser _parser;
+
+ /** @since 2.2 **/
+
+ private IResourceLocation _applicationRootLocation;
+
+ /** @since 3.0 **/
+
+ private ITemplateSourceDelegate _delegate;
+
+ /**
+ * Clears the template cache. This is used during debugging.
+ *
+ **/
+
+ public void reset()
+ {
+ _cache.clear();
+ _templates.clear();
+
+ _tokenCount = 0;
+ }
+
+ /**
+ * Reads the template for the component.
+ *
+ * <p>Returns null if the template can't be found.
+ *
+ **/
+
+ public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component)
+ {
+ IComponentSpecification specification = component.getSpecification();
+ IResourceLocation specificationLocation = specification.getSpecificationLocation();
+
+ Locale locale = component.getPage().getLocale();
+
+ Object key = new MultiKey(new Object[] { specificationLocation, locale }, false);
+
+ ComponentTemplate result = searchCache(key);
+ if (result != null)
+ return result;
+
+ result = findTemplate(cycle, specificationLocation, component, locale);
+
+ if (result == null)
+ {
+ result = getTemplateFromDelegate(cycle, component, locale);
+
+ if (result != null)
+ return result;
+
+ String stringKey =
+ component.getSpecification().isPageSpecification()
+ ? "DefaultTemplateSource.no-template-for-page"
+ : "DefaultTemplateSource.no-template-for-component";
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format(stringKey, component.getExtendedId(), locale),
+ component,
+ component.getLocation(),
+ null);
+ }
+
+ saveToCache(key, result);
+
+ return result;
+ }
+
+ private ComponentTemplate searchCache(Object key)
+ {
+ return (ComponentTemplate) _cache.get(key);
+ }
+
+ private void saveToCache(Object key, ComponentTemplate template)
+ {
+ _cache.put(key, template);
+
+ }
+
+ private ComponentTemplate getTemplateFromDelegate(
+ IRequestCycle cycle,
+ IComponent component,
+ Locale locale)
+ {
+ if (_delegate == null)
+ {
+ IEngine engine = cycle.getEngine();
+ IApplicationSpecification spec = engine.getSpecification();
+
+ if (spec.checkExtension(Tapestry.TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME))
+ _delegate =
+ (ITemplateSourceDelegate) spec.getExtension(
+ Tapestry.TEMPLATE_SOURCE_DELEGATE_EXTENSION_NAME,
+ ITemplateSourceDelegate.class);
+ else
+ _delegate = NullTemplateSourceDelegate.getSharedInstance();
+
+ }
+
+ return _delegate.findTemplate(cycle, component, locale);
+ }
+
+ /**
+ * Finds the template for the given component, using the following rules:
+ * <ul>
+ * <li>If the component has a $template asset, use that
+ * <li>Look for a template in the same folder as the component
+ * <li>If a page in the application namespace, search in the application root
+ * <li>Fail!
+ * </ul>
+ *
+ * @return the template, or null if not found
+ *
+ **/
+
+ private ComponentTemplate findTemplate(
+ IRequestCycle cycle,
+ IResourceLocation location,
+ IComponent component,
+ Locale locale)
+ {
+ IAsset templateAsset = component.getAsset(TEMPLATE_ASSET_NAME);
+
+ if (templateAsset != null)
+ return readTemplateFromAsset(cycle, component, templateAsset);
+
+ String name = location.getName();
+ int dotx = name.lastIndexOf('.');
+ String templateBaseName = name.substring(0, dotx + 1) + getTemplateExtension(component);
+
+ ComponentTemplate result =
+ findStandardTemplate(cycle, location, component, templateBaseName, locale);
+
+ if (result == null
+ && component.getSpecification().isPageSpecification()
+ && component.getNamespace().isApplicationNamespace())
+ result = findPageTemplateInApplicationRoot(cycle, component, templateBaseName, locale);
+
+ return result;
+ }
+
+ private ComponentTemplate findPageTemplateInApplicationRoot(
+ IRequestCycle cycle,
+ IComponent component,
+ String templateBaseName,
+ Locale locale)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Checking for " + templateBaseName + " in application root");
+
+ if (_applicationRootLocation == null)
+ _applicationRootLocation = Tapestry.getApplicationRootLocation(cycle);
+
+ IResourceLocation baseLocation =
+ _applicationRootLocation.getRelativeLocation(templateBaseName);
+ IResourceLocation localizedLocation = baseLocation.getLocalization(locale);
+
+ if (localizedLocation == null)
+ return null;
+
+ return getOrParseTemplate(cycle, localizedLocation, component);
+ }
+
+ /**
+ * Reads an asset to get the template.
+ *
+ **/
+
+ private ComponentTemplate readTemplateFromAsset(
+ IRequestCycle cycle,
+ IComponent component,
+ IAsset asset)
+ {
+ InputStream stream = asset.getResourceAsStream(cycle);
+
+ char[] templateData = null;
+
+ try
+ {
+ String encoding = getTemplateEncoding(cycle, component, null);
+
+ templateData = readTemplateStream(stream, encoding);
+
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DefaultTemplateSource.unable-to-read-template", asset),
+ ex);
+ }
+
+ IResourceLocation resourceLocation = asset.getResourceLocation();
+
+ return constructTemplateInstance(cycle, templateData, resourceLocation, component);
+ }
+
+ /**
+ * Search for the template corresponding to the resource and the locale.
+ * This may be in the template map already, or may involve reading and
+ * parsing the template.
+ *
+ * @return the template, or null if not found.
+ *
+ **/
+
+ private ComponentTemplate findStandardTemplate(
+ IRequestCycle cycle,
+ IResourceLocation location,
+ IComponent component,
+ String templateBaseName,
+ Locale locale)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Searching for localized version of template for "
+ + location
+ + " in locale "
+ + locale.getDisplayName());
+
+ IResourceLocation baseTemplateLocation = location.getRelativeLocation(templateBaseName);
+
+ IResourceLocation localizedTemplateLocation = baseTemplateLocation.getLocalization(locale);
+
+ if (localizedTemplateLocation == null)
+ return null;
+
+ return getOrParseTemplate(cycle, localizedTemplateLocation, component);
+
+ }
+
+ /**
+ * Returns a previously parsed template at the specified location (which must already
+ * be localized). If not already in the template Map, then the
+ * location is parsed and stored into the templates Map, then returned.
+ *
+ **/
+
+ private ComponentTemplate getOrParseTemplate(
+ IRequestCycle cycle,
+ IResourceLocation location,
+ IComponent component)
+ {
+
+ ComponentTemplate result = (ComponentTemplate) _templates.get(location);
+ if (result != null)
+ return result;
+
+ // Ok, see if it exists.
+
+ result = parseTemplate(cycle, location, component);
+
+ if (result != null)
+ _templates.put(location, result);
+
+ return result;
+ }
+
+ /**
+ * Reads the template for the given resource; returns null if the
+ * resource doesn't exist. Note that this method is only invoked
+ * from a synchronized block, so there shouldn't be threading
+ * issues here.
+ *
+ **/
+
+ private ComponentTemplate parseTemplate(
+ IRequestCycle cycle,
+ IResourceLocation location,
+ IComponent component)
+ {
+ String encoding = getTemplateEncoding(cycle, component, location.getLocale());
+
+ char[] templateData = readTemplate(location, encoding);
+ if (templateData == null)
+ return null;
+
+ return constructTemplateInstance(cycle, templateData, location, component);
+ }
+
+ /**
+ * This method is currently synchronized, because
+ * {@link TemplateParser} is not threadsafe. Another good candidate
+ * for a pooling mechanism, especially because parsing a template
+ * may take a while.
+ *
+ **/
+
+ private synchronized ComponentTemplate constructTemplateInstance(
+ IRequestCycle cycle,
+ char[] templateData,
+ IResourceLocation location,
+ IComponent component)
+ {
+ if (_parser == null)
+ _parser = new TemplateParser();
+
+ ITemplateParserDelegate delegate = new TemplateParserDelegateImpl(component, cycle);
+
+ TemplateToken[] tokens;
+
+ try
+ {
+ tokens = _parser.parse(templateData, delegate, location);
+ }
+ catch (TemplateParseException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DefaultTemplateSource.unable-to-parse-template", location),
+ ex);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsed " + tokens.length + " tokens from template");
+
+ _tokenCount += tokens.length;
+
+ return new ComponentTemplate(templateData, tokens);
+ }
+
+ /**
+ * Reads the template, given the complete path to the
+ * resource. Returns null if the resource doesn't exist.
+ *
+ **/
+
+ private char[] readTemplate(IResourceLocation location, String encoding)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Reading template " + location);
+
+ URL url = location.getResourceURL();
+
+ if (url == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Template does not exist.");
+
+ return null;
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Reading template from URL " + url);
+
+ InputStream stream = null;
+
+ try
+ {
+ stream = url.openStream();
+
+ return readTemplateStream(stream, encoding);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DefaultTemplateSource.unable-to-read-template", location),
+ ex);
+ }
+ finally
+ {
+ Tapestry.close(stream);
+ }
+
+ }
+
+ /**
+ * Reads a Stream into memory as an array of characters.
+ *
+ **/
+
+ private char[] readTemplateStream(InputStream stream, String encoding) throws IOException
+ {
+ char[] charBuffer = new char[BUFFER_SIZE];
+ StringBuffer buffer = new StringBuffer();
+
+ InputStreamReader reader;
+ if (encoding != null)
+ reader = new InputStreamReader(new BufferedInputStream(stream), encoding);
+ else
+ reader = new InputStreamReader(new BufferedInputStream(stream));
+
+ try
+ {
+ while (true)
+ {
+ int charsRead = reader.read(charBuffer, 0, BUFFER_SIZE);
+
+ if (charsRead <= 0)
+ break;
+
+ buffer.append(charBuffer, 0, charsRead);
+ }
+ }
+ finally
+ {
+ reader.close();
+ }
+
+ // OK, now reuse the charBuffer variable to
+ // produce the final result.
+
+ int length = buffer.length();
+
+ charBuffer = new char[length];
+
+ // Copy the character out of the StringBuffer and into the
+ // array.
+
+ buffer.getChars(0, length, charBuffer, 0);
+
+ return charBuffer;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("tokenCount", _tokenCount);
+
+ builder.append("templates", _templates.keySet());
+
+ return builder.toString();
+ }
+
+ /**
+ * Checks for the {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY} in the component's
+ * specification, then in the component's namespace's specification. Returns
+ * {@link Tapestry#DEFAULT_TEMPLATE_EXTENSION} if not otherwise overriden.
+ *
+ **/
+
+ private String getTemplateExtension(IComponent component)
+ {
+ String extension =
+ component.getSpecification().getProperty(Tapestry.TEMPLATE_EXTENSION_PROPERTY);
+
+ if (extension != null)
+ return extension;
+
+ extension =
+ component.getNamespace().getSpecification().getProperty(
+ Tapestry.TEMPLATE_EXTENSION_PROPERTY);
+
+ if (extension != null)
+ return extension;
+
+ return Tapestry.DEFAULT_TEMPLATE_EXTENSION;
+ }
+
+ /** @since 1.0.6 **/
+
+ public synchronized void renderDescription(IMarkupWriter writer)
+ {
+ writer.print("DefaultTemplateSource[");
+
+ if (_tokenCount > 0)
+ {
+ writer.print(_tokenCount);
+ writer.print(" tokens");
+ }
+
+ if (_cache != null)
+ {
+ boolean first = true;
+ Iterator i = _cache.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ if (first)
+ {
+ writer.begin("ul");
+ first = false;
+ }
+
+ Map.Entry e = (Map.Entry) i.next();
+ Object key = e.getKey();
+ ComponentTemplate template = (ComponentTemplate) e.getValue();
+
+ writer.begin("li");
+ writer.print(key.toString());
+ writer.print(" (");
+ writer.print(template.getTokenCount());
+ writer.print(" tokens)");
+ writer.println();
+ writer.end();
+ }
+
+ if (!first)
+ {
+ writer.end(); // <ul>
+ writer.beginEmpty("br");
+ }
+ }
+
+ writer.print("]");
+
+ }
+
+ private String getTemplateEncoding(IRequestCycle cycle, IComponent component, Locale locale)
+ {
+ IPropertySource source = getComponentPropertySource(cycle, component);
+
+ if (locale != null)
+ source = new LocalizedPropertySource(locale, source);
+
+ return getTemplateEncodingProperty(source);
+ }
+
+ private IPropertySource getComponentPropertySource(IRequestCycle cycle, IComponent component)
+ {
+ DelegatingPropertySource source = new DelegatingPropertySource();
+
+ // Search for the encoding property in the following order:
+ // First search the component specification
+ source.addSource(new PropertyHolderPropertySource(component.getSpecification()));
+
+ // Then search its library specification
+ source.addSource(new PropertyHolderPropertySource(component.getNamespace().getSpecification()));
+
+ // Then search the rest of the standard path
+ source.addSource(cycle.getEngine().getPropertySource());
+
+ return source;
+ }
+
+ private String getTemplateEncodingProperty(IPropertySource source)
+ {
+ return source.getPropertyValue(TEMPLATE_ENCODING_PROPERTY_NAME);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/DirectService.java b/tapestry-framework/src/org/apache/tapestry/engine/DirectService.java
new file mode 100644
index 0000000..ce77ac2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/DirectService.java
@@ -0,0 +1,181 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.StaleSessionException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * Implementation of the direct service, which encodes the page and component id in
+ * the service context, and passes application-defined parameters as well.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ **/
+
+public class DirectService extends AbstractService
+{
+ /**
+ * Encoded into URL if engine was stateful.
+ *
+ * @since 3.0
+ **/
+
+ private static final String STATEFUL_ON = "1";
+
+ /**
+ * Encoded into URL if engine was not stateful.
+ *
+ * @since 3.0
+ **/
+
+ private static final String STATEFUL_OFF = "0";
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+
+ // New since 1.0.1, we use the component to determine
+ // the page, not the cycle. Through the use of tricky
+ // things such as Block/InsertBlock, it is possible
+ // that a component from a page different than
+ // the response page will render.
+ // In 1.0.6, we start to record *both* the render page
+ // and the component page (if different), as the extended
+ // context.
+
+ IPage renderPage = cycle.getPage();
+ IPage componentPage = component.getPage();
+
+ boolean complex = renderPage != componentPage;
+
+ String[] context = complex ? new String[4] : new String[3];
+
+ int i = 0;
+
+ String stateful = cycle.getEngine().isStateful() ? STATEFUL_ON : STATEFUL_OFF;
+
+ context[i++] = stateful;
+
+ if (complex)
+ context[i++] = renderPage.getPageName();
+
+ context[i++] = componentPage.getPageName();
+ context[i++] = component.getIdPath();
+
+ return constructLink(cycle, Tapestry.DIRECT_SERVICE, context, parameters, true);
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ IDirect direct;
+ int count = 0;
+ String componentPageName;
+ IPage componentPage;
+ RequestContext requestContext = cycle.getRequestContext();
+ String[] serviceContext = getServiceContext(requestContext);
+
+ if (serviceContext != null)
+ count = serviceContext.length;
+
+ if (count != 3 && count != 4)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("DirectService.context-parameters"));
+
+ boolean complex = count == 4;
+
+ int i = 0;
+ String stateful = serviceContext[i++];
+ String pageName = serviceContext[i++];
+
+ if (complex)
+ componentPageName = serviceContext[i++];
+ else
+ componentPageName = pageName;
+
+ String componentPath = serviceContext[i++];
+
+ IPage page = cycle.getPage(pageName);
+
+ cycle.activate(page);
+
+ if (complex)
+ componentPage = cycle.getPage(componentPageName);
+ else
+ componentPage = page;
+
+ IComponent component = componentPage.getNestedComponent(componentPath);
+
+ try
+ {
+ direct = (IDirect) component;
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DirectService.component-wrong-type", component.getExtendedId()),
+ component,
+ null,
+ ex);
+ }
+
+ // Check for a StateSession only the session was stateful when
+ // the Gesture was created.
+
+ if (stateful.equals(STATEFUL_ON) && direct.isStateful())
+ {
+ HttpSession session = cycle.getRequestContext().getSession();
+
+ if (session == null || session.isNew())
+ throw new StaleSessionException(
+ Tapestry.format(
+ "DirectService.stale-session-exception",
+ direct.getExtendedId()),
+ direct.getPage());
+ }
+
+ Object[] parameters = getParameters(cycle);
+
+ cycle.setServiceParameters(parameters);
+ direct.trigger(cycle);
+
+ // Render the response. This will be the response page (the first element in the context)
+ // unless the direct (or its delegate) changes it.
+
+ engine.renderResponse(cycle, output);
+ }
+
+ public String getName()
+ {
+ return Tapestry.DIRECT_SERVICE;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/EngineServiceLink.java b/tapestry-framework/src/org/apache/tapestry/engine/EngineServiceLink.java
new file mode 100644
index 0000000..c766048
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/EngineServiceLink.java
@@ -0,0 +1,250 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.codec.net.URLCodec;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * A EngineServiceLink represents a possible action within the client web browser;
+ * either clicking a link or submitting a form, which is constructed primarily
+ * from the {@link org.apache.tapestry.IEngine#getServletPath() servlet path},
+ * with some additional query parameters. A full URL for the EngineServiceLink
+ * can be generated, or the query parameters for the EngineServiceLink can be extracted
+ * (separately from the servlet path). The latter case is used when submitting
+ * constructing {@link org.apache.tapestry.form.Form forms}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class EngineServiceLink implements ILink
+{
+ private static final int DEFAULT_HTTP_PORT = 80;
+ private static final URLCodec _urlCodec = new URLCodec();
+
+ private IRequestCycle _cycle;
+ private String _service;
+ private String[] _parameters;
+ private boolean _stateful;
+
+ /**
+ * Creates a new EngineServiceLink. A EngineServiceLink always names a service to be activated
+ * by the link, has an optional list of service context strings,
+ * an optional list of service parameter strings and may be stateful
+ * or stateless.
+ *
+ * <p>ServiceLink parameter strings may contain any characters.
+ *
+ * <p>ServiceLink context strings must be URL safe, and may not contain
+ * slash ('/') characters. Typically, only letters, numbers and simple
+ * punctuation ('.', '-', '_', ':') is recommended (no checks are currently made,
+ * however). Context strings are generally built from page names
+ * and component ids, which are limited to safe characters.
+ *
+ * @param cycle The {@link IRequestCycle} the EngineServiceLink is to be created for.
+ * @param serviceName The name of the service to be invoked by the EngineServiceLink.
+ * @param serviceContext an optional array of strings to be provided
+ * to the service to provide a context for executing the service. May be null
+ * or empty. <b>Note: copied, not retained.</b>
+ * @param serviceParameters An array of parameters, may be
+ * null or empty. <b>Note: retained, not copied.</b>
+ * @param stateful if true, the service which generated the EngineServiceLink
+ * is stateful and expects that the final URL will be passed through
+ * {@link IRequestCycle#encodeURL(String)}.
+ **/
+
+ public EngineServiceLink(
+ IRequestCycle cycle,
+ String serviceName,
+ String[] serviceContext,
+ String[] serviceParameters,
+ boolean stateful)
+ {
+ _cycle = cycle;
+ _service = constructServiceValue(serviceName, serviceContext);
+ _parameters = serviceParameters;
+ _stateful = stateful;
+ }
+
+ private String constructServiceValue(String serviceName, String[] serviceContext)
+ {
+ int count = Tapestry.size(serviceContext);
+
+ if (count == 0)
+ return serviceName;
+
+ StringBuffer buffer = new StringBuffer(serviceName);
+
+ for (int i = 0; i < count; i++)
+ {
+ buffer.append('/');
+
+ buffer.append(serviceContext[i]);
+ }
+
+ return buffer.toString();
+ }
+
+ public String getURL()
+ {
+ return getURL(null, true);
+ }
+
+ public String getURL(String anchor, boolean includeParameters)
+ {
+ return constructURL(new StringBuffer(), anchor, includeParameters);
+ }
+
+ public String getAbsoluteURL()
+ {
+ return getAbsoluteURL(null, null, 0, null, true);
+ }
+
+ public String getAbsoluteURL(
+ String scheme,
+ String server,
+ int port,
+ String anchor,
+ boolean includeParameters)
+ {
+ StringBuffer buffer = new StringBuffer();
+ RequestContext context = _cycle.getRequestContext();
+
+ if (scheme == null)
+ scheme = context.getScheme();
+
+ buffer.append(scheme);
+ buffer.append("://");
+
+ if (server == null)
+ server = context.getServerName();
+
+ buffer.append(server);
+
+ if (port == 0)
+ port = context.getServerPort();
+
+ if (!(scheme.equals("http") && port == DEFAULT_HTTP_PORT))
+ {
+ buffer.append(':');
+ buffer.append(port);
+ }
+
+ // Add the servlet path and the rest of the URL & query parameters.
+ // The servlet path starts with a leading slash.
+
+ return constructURL(buffer, anchor, includeParameters);
+ }
+
+ private String constructURL(StringBuffer buffer, String anchor, boolean includeParameters)
+ {
+ buffer.append(_cycle.getEngine().getServletPath());
+
+ if (includeParameters)
+ {
+ buffer.append('?');
+ buffer.append(Tapestry.SERVICE_QUERY_PARAMETER_NAME);
+ buffer.append('=');
+ buffer.append(_service);
+
+ int count = Tapestry.size(_parameters);
+
+ for (int i = 0; i < count; i++)
+ {
+ buffer.append('&');
+
+ buffer.append(Tapestry.PARAMETERS_QUERY_PARAMETER_NAME);
+ buffer.append('=');
+
+ String encoding = _cycle.getEngine().getOutputEncoding();
+ try
+ {
+ String encoded = _urlCodec.encode(_parameters[i], encoding);
+ buffer.append(encoded);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("illegal-encoding", encoding),
+ e);
+ }
+ }
+ }
+
+ if (anchor != null)
+ {
+ buffer.append('#');
+ buffer.append(anchor);
+ }
+
+ String result = buffer.toString();
+
+ if (_stateful)
+ result = _cycle.encodeURL(result);
+
+ return result;
+ }
+
+ public String[] getParameterNames()
+ {
+ List list = new ArrayList();
+
+ list.add(Tapestry.SERVICE_QUERY_PARAMETER_NAME);
+
+ if (Tapestry.size(_parameters) != 0)
+ list.add(Tapestry.PARAMETERS_QUERY_PARAMETER_NAME);
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ public String[] getParameterValues(String name)
+ {
+ if (name.equals(Tapestry.SERVICE_QUERY_PARAMETER_NAME))
+ {
+ return new String[] { _service };
+ }
+
+ if (name.equals(Tapestry.PARAMETERS_QUERY_PARAMETER_NAME))
+ {
+ return _parameters;
+ }
+
+ throw new IllegalArgumentException(
+ Tapestry.format("EngineServiceLink.unknown-parameter-name", name));
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("service", _service);
+ builder.append("parameters", _parameters);
+ builder.append("stateful", _stateful);
+
+ return builder.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ExternalService.java b/tapestry-framework/src/org/apache/tapestry/engine/ExternalService.java
new file mode 100644
index 0000000..9e0790f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ExternalService.java
@@ -0,0 +1,185 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IExternalPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * The external service enables external applications
+ * to reference Tapestry pages via a URL. Pages which can be referenced
+ * by the external service must implement the {@link IExternalPage}
+ * interface. The external service enables the bookmarking of pages.
+ *
+ * <p>
+ * The external service may also be used by the Tapestry JSP taglibrary
+ * ({@link org.apache.tapestry.jsp.ExternalURLTag} and {@link org.apache.tapestry.jsp.ExternalTag}).
+ *
+ * <p>
+ * You can try and second guess the URL format used by Tapestry.
+ * The default URL format for the external service is:
+ * <blockquote>
+ * <tt>http://localhost/app?service=external/<i>[Page Name]</i>&sp=[Param 0]&sp=[Param 1]...</tt>
+ * </blockquote>
+ * For example to view the "ViewCustomer" page the service parameters 5056 (customer ID) and
+ * 309 (company ID) the external service URL would be:
+ * <blockquote>
+ * <tt>http://localhost/myapp?service=external&context=<b>ViewCustomer</b>&sp=<b>5056</b>&sp=<b>302</b></tt>
+ * </blockquote>
+ * In this example external service will get a "ViewCustomer" page and invoke the
+ * {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method with the parameters:
+ * Object[] { new Integer(5056), new Integer(302) }.
+ * <p>
+ * Note service parameters (sp) need to be prefixed by valid
+ * {@link org.apache.tapestry.util.io.DataSqueezer} adaptor char. These adaptor chars are automatically provided in
+ * URL's created by the <tt>buildGesture()</tt> method. However if you hand coded an external
+ * service URL you will need to ensure valid prefix chars are present.
+ * <p>
+ * <table border="1" cellpadding="2">
+ * <tr>
+ * <th>Prefix char(s)</th><th>Mapped Java Type</th>
+ * </tr>
+ * <tr>
+ * <td> TF</td><td> boolean</td>
+ * </tr>
+ * <tr>
+ * <td> b</td><td> byte</td>
+ * </tr>
+ * <tr>
+ * <td> c</td><td> char</td>
+ * </tr>
+ * <tr>
+ * <td> d</td><td> double</td>
+ * </tr>
+ * <tr>
+ * <td> -0123456789</td><td> integer</td>
+ * </tr>
+ * <tr>
+ * <td> l</td><td> long</td>
+ * </tr>
+ * <tr>
+ * <td> S</td><td> String</td>
+ * </tr>
+ * <tr>
+ * <td> s</td><td> short</td>
+ * </tr>
+ * <tr>
+ * <td> other chars</td>
+ * <td> <tt>String</tt> without truncation of first char</td>
+ * </tr>
+ * <table>
+ * <p>
+ * <p>
+ * A good rule of thumb is to keep the information encoded in the URL short and simple, and restrict it
+ * to just Strings and Integers. Integers can be encoded as-is. Prefixing all Strings with the letter 'S'
+ * will ensure that they are decoded properly. Again, this is only relevant if an
+ * {@link org.apache.tapestry.IExternalPage} is being referenced from static HTML or JSP and the
+ * URL must be assembled in user code ... when the URL is generated by Tapestry, it is automatically
+ * created with the correct prefixes and encodings (as with any other service).
+ *
+ * @see org.apache.tapestry.IExternalPage
+ * @see org.apache.tapestry.jsp.ExternalTag
+ * @see org.apache.tapestry.jsp.ExternalURLTag
+ *
+ * @author Howard Lewis Ship
+ * @author Malcolm Edgar
+ * @since 2.2
+ *
+ **/
+
+public class ExternalService extends AbstractService
+{
+
+ /**
+ * Builds a URL for a service. This is performed during the
+ * rendering phase of one request cycle and bulds URLs that will
+ * invoke activity in a subsequent request cycle.
+ *
+ * @param cycle Defines the request cycle being processed.
+ * @param component The component requesting the URL. Generally, the
+ * service context is established from the component.
+ * @param parameters Additional parameters specific to the
+ * component requesting the EngineServiceLink.
+ * @return The URL for the service. The URL always be encoded when it is returned.
+ *
+ **/
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (parameters == null || parameters.length == 0)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-requires-parameters", Tapestry.EXTERNAL_SERVICE));
+
+ String pageName = (String) parameters[0];
+ String[] context = new String[] { pageName };
+
+ Object[] pageParameters = new Object[parameters.length - 1];
+ System.arraycopy(parameters, 1, pageParameters, 0, parameters.length - 1);
+
+ return constructLink(cycle, Tapestry.EXTERNAL_SERVICE, context, pageParameters, true);
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ IExternalPage page = null;
+
+ String[] context = getServiceContext(cycle.getRequestContext());
+
+ if (context == null || context.length != 1)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-single-context-parameter", Tapestry.EXTERNAL_SERVICE));
+
+ String pageName = context[0];
+
+ try
+ {
+ page = (IExternalPage) cycle.getPage(pageName);
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ExternalService.page-not-compatible", pageName),
+ ex);
+ }
+
+ Object[] parameters = getParameters(cycle);
+
+ cycle.setServiceParameters(parameters);
+
+ cycle.activate(page);
+
+ page.activateExternalPage(parameters, cycle);
+
+ // Render the response.
+ engine.renderResponse(cycle, output);
+ }
+
+ public String getName()
+ {
+ return Tapestry.EXTERNAL_SERVICE;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/HomeService.java b/tapestry-framework/src/org/apache/tapestry/engine/HomeService.java
new file mode 100644
index 0000000..50d6c33
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/HomeService.java
@@ -0,0 +1,71 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * An implementation of the home service that renders the Home page.
+ * This is the most likely candidate for overriding ... for example,
+ * to select the page to render based on known information about the
+ * user (stored as a cookie).
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ **/
+
+public class HomeService extends AbstractService
+{
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (Tapestry.size(parameters) != 0)
+ throw new IllegalArgumentException(
+ Tapestry.format("service-no-parameters", Tapestry.HOME_SERVICE));
+
+ return constructLink(cycle, Tapestry.HOME_SERVICE, null, null, true);
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ IPage home = cycle.getPage(IEngine.HOME_PAGE);
+
+ cycle.activate(home);
+
+ engine.renderResponse(cycle, output);
+ }
+
+ public String getName()
+ {
+ return Tapestry.HOME_SERVICE;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IComponentClassEnhancer.java b/tapestry-framework/src/org/apache/tapestry/engine/IComponentClassEnhancer.java
new file mode 100644
index 0000000..03fe4d2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IComponentClassEnhancer.java
@@ -0,0 +1,60 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ *
+ * A provider of enhanced classes, classes with new methods
+ * and new attributes, and possibly, implementing new
+ * Java interfaces. The primary use of class enhancement is to
+ * automate the creation of transient and persistant properties.
+ *
+ * <p>
+ * Implementations of this interface must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface IComponentClassEnhancer
+{
+ /**
+ * Clears all cached data for the enhancer; this includes references to
+ * enhanced classes.
+ *
+ **/
+
+ public void reset();
+
+ /**
+ * Used to access the class for a given component (or page). Returns the
+ * specified class, or an enhanced version of the class if the
+ * component requires enhancement.
+ *
+ * @param specification the specification for the component
+ * @param className the name of base class to enhance, as extracted
+ * from the specification (or possibly, from a default).
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the class does not exist, is invalid,
+ * or may not be enhanced.
+ *
+ **/
+
+ public Class getEnhancedClass(IComponentSpecification specification, String className);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IComponentMessagesSource.java b/tapestry-framework/src/org/apache/tapestry/engine/IComponentMessagesSource.java
new file mode 100644
index 0000000..ce6e061
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IComponentMessagesSource.java
@@ -0,0 +1,40 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMessages;
+
+/**
+ * Defines an object that can provide a component with its
+ * {@link org.apache.tapestry.IMessages}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.4
+ *
+ **/
+
+public interface IComponentMessagesSource
+{
+ public IMessages getMessages(IComponent component);
+
+ /**
+ * Clears all cached information for the source.
+ *
+ **/
+
+ public void reset();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IEngineService.java b/tapestry-framework/src/org/apache/tapestry/engine/IEngineService.java
new file mode 100644
index 0000000..62d7a15
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IEngineService.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * A service, provided by the {@link org.apache.tapestry.IEngine}, for its pages and/or components.
+ * Services are
+ * responsible for constructing {@link EngineServiceLink}s (an encoding of URLs)
+ * to represent dynamic application behavior, and for
+ * parsing those URLs when a subsequent request involves them.
+ *
+ * @see org.apache.tapestry.IEngine#getService(String)
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IEngineService
+{
+ /**
+ * Builds a URL for a service. This is performed during the
+ * rendering phase of one request cycle and bulds URLs that will
+ * invoke activity in a subsequent request cycle.
+ *
+ * @param cycle Defines the request cycle being processed.
+ * @param component The component requesting the URL. Generally, the
+ * service context is established from the component.
+ * @param parameters Additional parameters specific to the
+ * component requesting the EngineServiceLink.
+ * @return The URL for the service. The URL will have to be encoded
+ * via {@link javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)}.
+ *
+ **/
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters);
+
+ /**
+ * Perform the service, interpreting the URL (from the
+ * {@link javax.servlet.http.HttpServletRequest})
+ * responding appropriately, and
+ * rendering a result page.
+ *
+ *
+ * @see org.apache.tapestry.IEngine#service(org.apache.tapestry.request.RequestContext)
+ * @param engine a view of the {@link org.apache.tapestry.IEngine} with additional methods needed by services
+ * @param cycle the incoming request
+ * @param output stream to which output should ultimately be directed
+ *
+ **/
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException;
+
+ /**
+ * Returns the name of the service.
+ *
+ * @since 1.0.1
+ **/
+
+ public String getName();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IEngineServiceView.java b/tapestry-framework/src/org/apache/tapestry/engine/IEngineServiceView.java
new file mode 100644
index 0000000..9a612e8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IEngineServiceView.java
@@ -0,0 +1,78 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * Additional methods implemented by the engine that are
+ * exposed to {@link IEngineService engine services}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ */
+
+public interface IEngineServiceView extends IEngine
+{
+ /**
+ * Invoked by a service to force the page selected by the {@link IRequestCycle}
+ * to be renderred. This takes care of a number of bookkeeping issues, such
+ * as committing changes in page recorders.
+ *
+ **/
+
+ public void renderResponse(IRequestCycle cycle, ResponseOutputStream output)
+ throws ServletException, IOException;
+
+ /**
+ * Invoked to restart the application from start; this most frequently follows
+ * some kind of catastrophic failure. This will invalidate any {@link javax.servlet.http.HttpSession}
+ * and force a redirect to the application servlet (i.e., invoking the home service
+ * in a subsequent request cycle).
+ *
+ **/
+
+ public void restart(IRequestCycle cycle) throws IOException;
+
+ /**
+ * Invoked (typically by the reset service) to clear all cached data known
+ * to the engine. This includes
+ * pages, templates, helper beans, specifications,
+ * localized strings, etc., and
+ * is used during debugging.
+ *
+ **/
+
+ public void clearCachedData();
+
+ /**
+ * Writes a detailed report of the exception to <code>System.err</code>.
+ * This is invoked by services that can't write an HTML description
+ * of the error because they don't provide text/html content (such as
+ * an asset that creates an image).
+ *
+ * @since 1.0.10
+ */
+
+ public void reportException(String reportTitle, Throwable ex);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ILink.java b/tapestry-framework/src/org/apache/tapestry/engine/ILink.java
new file mode 100644
index 0000000..67f6a21
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ILink.java
@@ -0,0 +1,111 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+/**
+ * Define a link that may be generated as part of a page render. The vast majority
+ * of links are tied to {@link IEngineService services} and are, in
+ * fact, callbacks. A small number, such as those generated by
+ * {@link org.apache.tapestry.link.GenericLink} component, are to arbitrary locations.
+ * In addition, ILink differentiates between the path portion of the link, and any
+ * query parameters encoded into a link, primarily to benefit {@link org.apache.tapestry.form.Form},
+ * which needs to encode the query parameters as hidden form fields.
+ *
+ * <p>
+ * In addition, an ILink is responsible for
+ * passing constructed URLs through
+ * {@link org.apache.tapestry.IRequestCycle#encodeURL(String)}
+ * as needed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface ILink
+{
+ /**
+ * Returns the relative URL as a String. A relative
+ * URL may include a leading slash, but omits
+ * the scheme, host and port portions of a full URL.
+ *
+ * @return the relative URL, with no anchor, but including
+ * query parameters.
+ *
+ **/
+
+ public String getURL();
+
+ /**
+ * Returns the relative URL as a String. This is used
+ * for most links.
+ *
+ * @param anchor if not null, appended to the URL
+ * @param includeParameters if true, parameters are included
+ *
+ **/
+
+ public String getURL(String anchor, boolean includeParameters);
+
+ /**
+ * Returns the absolute URL as a String, using
+ * default scheme, server and port, including
+ * parameters, and no anchor.
+ *
+ **/
+
+ public String getAbsoluteURL();
+
+ /**
+ * Returns the absolute URL as a String.
+ *
+ * @param scheme if not null, overrides the default scheme.
+ * @param server if not null, overrides the default server
+ * @param port if non-zero, overrides the default port
+ * @param anchor if not null, appended to the URL
+ * @param includeParameters if true, parameters are included
+ *
+ **/
+
+ public String getAbsoluteURL(
+ String scheme,
+ String server,
+ int port,
+ String anchor,
+ boolean includeParameters);
+
+ /**
+ *
+ * Returns an array of parameters names (in
+ * no specified order).
+ *
+ * @see #getParameterValues(String)
+ *
+ **/
+
+ public String[] getParameterNames();
+
+ /**
+ * Returns the values for the named parameter.
+ *
+ * @throws IllegalArgumentException if the
+ * link does not define values for the
+ * specified name.
+ *
+ **/
+
+ public String[] getParameterValues(String name);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IMonitor.java b/tapestry-framework/src/org/apache/tapestry/engine/IMonitor.java
new file mode 100644
index 0000000..259db5b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IMonitor.java
@@ -0,0 +1,121 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+/**
+ * Basic support for application monitoring and metrics.
+ * This interface defines events; the implementation
+ * decides what to do with them (such as record them to a database).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IMonitor
+{
+ /**
+ * Invoked before constructing a page.
+ *
+ **/
+
+ public void pageCreateBegin(String pageName);
+
+ /**
+ * Invoked after successfully constructing a page and all of its components.
+ *
+ **/
+
+ public void pageCreateEnd(String pageName);
+
+ /**
+ * Invoked when a page is loaded. This includes time to locate or create an instance
+ * of the page and rollback its state (to any previously recorded value).
+ *
+ **/
+
+ public void pageLoadBegin(String pageName);
+
+ /**
+ * Invoked once a page is completely loaded and rolled back to its prior state.
+ *
+ **/
+
+ public void pageLoadEnd(String pageName);
+
+ /**
+ * Invoked before a page render begins.
+ *
+ **/
+
+ public void pageRenderBegin(String pageName);
+
+ /**
+ * Invoked after a page has succesfully rendered.
+ *
+ **/
+
+ public void pageRenderEnd(String pageName);
+
+ /**
+ * Invoked before a page rewind (to respond to an action) begins.
+ *
+ **/
+
+ public void pageRewindBegin(String pageName);
+
+ /**
+ * Invoked after a page has succesfully been rewound (which includes
+ * any activity related to the action listener).
+ *
+ **/
+
+ public void pageRewindEnd(String pageName);
+
+ /**
+ * Invoked when a service begins processing.
+ *
+ **/
+
+ public void serviceBegin(String serviceName, String detailMessage);
+
+ /**
+ * Invoked when a service successfully ends.
+ *
+ **/
+
+ public void serviceEnd(String serviceName);
+
+ /**
+ * Invoked when a service throws an exception rather than completing normally.
+ * Processing of the request may continue with the display of an exception
+ * page.
+ *
+ * <p>
+ * serviceException() is always invoked <em>before</em>
+ * {@link #serviceEnd(String)}.
+ *
+ **/
+
+ public void serviceException(Throwable exception);
+
+ /**
+ * Invoked when a session is initiated. This is typically
+ * done from the implementation of the home service.
+ *
+ **/
+
+ public void sessionBegin();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IMonitorFactory.java b/tapestry-framework/src/org/apache/tapestry/engine/IMonitorFactory.java
new file mode 100644
index 0000000..cb7f5a3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IMonitorFactory.java
@@ -0,0 +1,41 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Interface for an object that can create a {@link IMonitor} instance
+ * for a particular {@link org.apache.tapestry.request.RequestContext}.
+ * The engine expects there to be a monitor factory
+ * as application extension
+ * <code>org.apache.tapestry.monitor-factory</code>. If no such
+ * extension exists, then {@link org.apache.tapestry.engine.DefaultMonitorFactory}
+ * is used instead.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+public interface IMonitorFactory
+{
+ /**
+ * Create a new {@link IMonitor} instance. Alternately, return a shared instance.
+ * This method may be invoked by multiple threads.
+ *
+ */
+
+ public IMonitor createMonitor(RequestContext context);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IPageLoader.java b/tapestry-framework/src/org/apache/tapestry/engine/IPageLoader.java
new file mode 100644
index 0000000..eeb1226
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IPageLoader.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Interface exposed to components as they are loaded by the page loader.
+ *
+ * @see IComponent#finishLoad(IRequestCycle, IPageLoader, org.apache.tapestry.spec.IComponentSpecification)
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IPageLoader
+{
+ /**
+ * Returns the engine for which this page loader is curently
+ * constructing a page.
+ *
+ * @since 0.2.12
+ *
+ **/
+
+ public IEngine getEngine();
+
+ /**
+ * A convienience; returns the template source provided by
+ * the {@link IEngine engine}.
+ *
+ * @since 0.2.12
+ *
+ **/
+
+ public ITemplateSource getTemplateSource();
+
+ /**
+ * Invoked to create an implicit component (one which is defined in the
+ * containing component's template, rather that in the containing component's
+ * specification).
+ *
+ * @see org.apache.tapestry.BaseComponentTemplateLoader
+ * @since 3.0
+ *
+ **/
+
+ public IComponent createImplicitComponent(
+ IRequestCycle cycle,
+ IComponent container,
+ String componentId,
+ String componentType,
+ ILocation location);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IPageRecorder.java b/tapestry-framework/src/org/apache/tapestry/engine/IPageRecorder.java
new file mode 100644
index 0000000..60f6bbe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IPageRecorder.java
@@ -0,0 +1,141 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.Collection;
+
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.event.ChangeObserver;
+
+/**
+ * Defines an object that can observe changes to properties of
+ * a page and its components, store the state of the page between request cycles,
+ * and restore a page's state on a subsequent request cycle.
+ *
+ * <p>Concrete implementations of this can store the changes in memory,
+ * as client-side cookies, in a flat file, or in a database.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IPageRecorder extends ChangeObserver
+{
+ /**
+ * Invoked after the recorder is instantiated to initialize
+ * it for the current request cycle.
+ *
+ * @param pageName the fully qualified page name
+ * @param cycle the current request cycle
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void initialize(String pageName, IRequestCycle cycle);
+
+ /**
+ * Invoked at the end of a request cycle in which the
+ * page recorder is discarded (either implicitly, because
+ * the page recorder has no changes, or explicitly
+ * because of {@link org.apache.tapestry.IEngine#forgetPage(String)} or
+ * {@link #markForDiscard()}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void discard();
+
+ /**
+ * Persists all changes that have been accumulated. If the recorder
+ * saves change incrementally, this should ensure that all changes have been persisted.
+ *
+ * <p>After commiting, a page recorder automatically locks itself.
+ *
+ **/
+
+ public void commit();
+
+ /**
+ * Returns a {@link Collection} of {@link org.apache.tapestry.record.IPageChange} objects that represent
+ * the persistant state of the page.
+ *
+ **/
+
+ public Collection getChanges();
+
+ /**
+ * Returns true if the recorder has any changes for the page.
+ *
+ **/
+
+ public boolean getHasChanges();
+
+ /**
+ * Returns true if the recorder has observed any changes that have not
+ * been committed to external storage.
+ *
+ **/
+
+ public boolean isDirty();
+
+ /**
+ * Returns true if the recorder is in a locked state, following
+ * a {@link #commit()}.
+ *
+ **/
+
+ public boolean isLocked();
+
+ /**
+ * Rolls back the page to the currently persisted state.
+ *
+ * <p>A page recorder can only rollback changes to properties
+ * which have changed at some point. This can cause some minor
+ * problems, addressed by
+ * {@link org.apache.tapestry.event.PageDetachListener#pageDetached(org.apache.tapestry.event.PageEvent)}.
+ *
+ **/
+
+ public void rollback(IPage page);
+
+ /**
+ * Invoked to lock or unlock the recorder. Recoders are locked
+ * after they are commited, and stay locked until
+ * explicitly unlocked in a subsequent request cycle.
+ *
+ **/
+
+ public void setLocked(boolean value);
+
+ /**
+ * Invoked to mark the recorder for discarding at the end of the request cycle.
+ *
+ * @since 2.0.2
+ *
+ **/
+
+ public void markForDiscard();
+
+ /**
+ * Returns true if the recorder has been marked for discard.
+ *
+ **/
+
+ public boolean isMarkedForDiscard();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IPageSource.java b/tapestry-framework/src/org/apache/tapestry/engine/IPageSource.java
new file mode 100644
index 0000000..c33c98d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IPageSource.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+
+/**
+ * Abstracts the process of loading pages from thier specifications as
+ * well as pooling of pages once loaded.
+ *
+ * <p>If the required page is not available, a page source may use an
+ * instance of {@link IPageLoader} to actually load the
+ * page (and all of its nested components).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IPageSource
+{
+ /**
+ * Gets a given page for the engine. This may involve using a previously
+ * loaded page from a pool of available pages, or the page may be loaded as needed.
+ *
+ * @param cycle the current request cycle
+ * @param pageName the name of the page. May be qualified with a library id prefix, which
+ * may even be nested. Unqualified names are searched for extensively in the application
+ * namespace, and then in the framework namespace.
+ * @param monitor informed of any page loading activity
+ *
+ **/
+
+ public IPage getPage(IRequestCycle cycle, String pageName, IMonitor monitor);
+
+ /**
+ * Invoked after the engine is done with the page
+ * (typically, after the response to the client has been sent).
+ * The page is returned to the pool for later reuse.
+ *
+ **/
+
+ public void releasePage(IPage page);
+
+ /**
+ * Invoked to have the source clear any internal cache. This is most often
+ * used when debugging an application.
+ *
+ **/
+
+ public void reset();
+
+ /**
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IResourceResolver getResourceResolver();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IPropertySource.java b/tapestry-framework/src/org/apache/tapestry/engine/IPropertySource.java
new file mode 100644
index 0000000..3361e31
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IPropertySource.java
@@ -0,0 +1,38 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+/**
+ * A source for configuration properties.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public interface IPropertySource
+{
+ /**
+ * Returns the value for a given property, or null if the
+ * source does not provide a value for the named property.
+ * Implementations of IPropertySource may use delegation
+ * to resolve the value (that is, if one property source returns null,
+ * it may forward the request to another source).
+ *
+ **/
+
+ public String getPropertyValue(String propertyName);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/IScriptSource.java b/tapestry-framework/src/org/apache/tapestry/engine/IScriptSource.java
new file mode 100644
index 0000000..f9b0ca0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/IScriptSource.java
@@ -0,0 +1,44 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+
+/**
+ * Provides access to an {@link IScript}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ **/
+
+public interface IScriptSource
+{
+ /**
+ * Retrieves the script identified by the location from the source's
+ * cache, reading and parsing the script if necessary.
+ *
+ **/
+
+ public IScript getScript(IResourceLocation scriptLocation);
+
+ /**
+ * Invoked to clear any cached scripts.
+ *
+ **/
+
+ public void reset();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ISpecificationSource.java b/tapestry-framework/src/org/apache/tapestry/engine/ISpecificationSource.java
new file mode 100644
index 0000000..c5d7268
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ISpecificationSource.java
@@ -0,0 +1,104 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.ILibrarySpecification;
+
+/**
+ * Defines access to component specifications.
+ *
+ * @see IComponentSpecification
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ISpecificationSource
+{
+ /**
+ * Retrieves a component specification, parsing it as necessary.
+ *
+ * @param specificationLocation the location where the specification
+ * may be read from.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the specification doesn't
+ * exist, is unreadable or invalid.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public IComponentSpecification getComponentSpecification(IResourceLocation specificationLocation);
+
+ /**
+ * Retrieves a component specification, parsing it as necessary.
+ *
+ * @param specificationLocation the location where the specification
+ * may be read from.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the specification doesn't
+ * exist, is unreadable or invalid.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public IComponentSpecification getPageSpecification(IResourceLocation specificationLocation);
+
+ /**
+ * Invoked to have the source clear any internal cache. This is most often
+ * used when debugging an application.
+ *
+ **/
+
+ public void reset();
+
+ /**
+ * Returns a {@link org.apache.tapestry.spec.LibrarySpecification} with the given path.
+ *
+ * @param specificationLocation the resource path of the specification
+ * to return
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the specification
+ * cannot be read
+ *
+ * @since 2.2
+ *
+ **/
+
+ public ILibrarySpecification getLibrarySpecification(IResourceLocation specificationLocation);
+
+ /**
+ * Returns the {@link INamespace} for the application.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public INamespace getApplicationNamespace();
+
+ /**
+ * Returns the {@link INamespace} for the framework itself.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public INamespace getFrameworkNamespace();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSource.java b/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSource.java
new file mode 100644
index 0000000..febdbcc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSource.java
@@ -0,0 +1,81 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.parse.ComponentTemplate;
+
+/**
+ * A source of localized HTML templates for components.
+ * The cache is the means of access for components to load thier templates,
+ * which they need not do until just before rendering.
+ *
+ * <p>The template cache must be able to locate and parse templates as needed.
+ * It may maintain templates in memory.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ITemplateSource
+{
+ /**
+ * Name of an {@link org.apache.tapestry.IAsset} of a component that provides the template
+ * for the asset. This overrides the default (that the template is in
+ * the same directory as the specification). This allows
+ * pages or component templates to be located properly, relative to static
+ * assets (such as images and stylesheets).
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String TEMPLATE_ASSET_NAME = "$template";
+
+ /**
+ * Name of the component parameter that will be automatically bound to
+ * the HTML tag that is used to insert the component in the parent template.
+ * If the parent component does not have a template (i.e. it extends
+ * AbstractComponent, not BaseComponent), then this parameter is bound to null.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String TEMPLATE_TAG_PARAMETER_NAME = "templateTag";
+
+ /**
+ * Locates the template for the component.
+ *
+ * @param cycle The request cycle loading the template; this is required
+ * in some cases when the template is loaded from an {@link org.apache.tapestry.IAsset}.
+ * @param component The component for which a template should be loaded.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the resource cannot be located or loaded.
+ *
+ **/
+
+ public ComponentTemplate getTemplate(IRequestCycle cycle, IComponent component);
+
+ /**
+ * Invoked to have the source clear any internal cache. This is most often
+ * used when debugging an application.
+ *
+ **/
+
+ public void reset();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSourceDelegate.java b/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSourceDelegate.java
new file mode 100644
index 0000000..b668296
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ITemplateSourceDelegate.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.Locale;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.parse.ComponentTemplate;
+
+/**
+ * Acts as a delegate to the {@link ITemplateSource}, providing access to
+ * page and component templates after the normal search mechanisms have failed.
+ *
+ * <p>
+ * The delegate must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ * @see org.apache.tapestry.engine.DefaultTemplateSource
+ *
+ **/
+
+public interface ITemplateSourceDelegate
+{
+ /**
+ * Invoked by the {@link ITemplateSource} when a template can't be found
+ * by normal means (i.e., in the normal locations). This method
+ * should find the template. The result may be null. The delegate
+ * is responsible for caching the result.
+ *
+ * @param cycle for access to Tapestry and Servlet API objects
+ * @param component component (or page) for which a template is needed
+ * @param locale the desired locale for the template
+ *
+ **/
+
+ public ComponentTemplate findTemplate(IRequestCycle cycle,
+ IComponent component,
+ Locale locale);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/Namespace.java b/tapestry-framework/src/org/apache/tapestry/engine/Namespace.java
new file mode 100644
index 0000000..f94fff1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/Namespace.java
@@ -0,0 +1,395 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.ILibrarySpecification;
+
+/**
+ * Implementation of {@link org.apache.tapestry.INamespace}
+ * that works with a {@link ISpecificationSource} to
+ * obtain page and component specifications as needed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class Namespace implements INamespace
+{
+ private ILibrarySpecification _specification;
+ private ISpecificationSource _specificationSource;
+ private String _id;
+ private String _extendedId;
+ private INamespace _parent;
+ private boolean _frameworkNamespace;
+ private boolean _applicationNamespace;
+
+ /**
+ * Map of {@link org.apache.tapestry.spec.ComponentSpecification} keyed on page name.
+ * The map is synchronized because different threads may
+ * try to update it simultaneously (due to dynamic page
+ * discovery in the application namespace).
+ *
+ **/
+
+ private Map _pages = Collections.synchronizedMap(new HashMap());
+
+ /**
+ * Map of {@link org.apache.tapestry.spec.ComponentSpecification} keyed on
+ * component alias.
+ *
+ **/
+
+ private Map _components = Collections.synchronizedMap(new HashMap());
+
+ /**
+ * Map, keyed on id, of {@link INamespace}.
+ *
+ **/
+
+ private Map _children = Collections.synchronizedMap(new HashMap());
+
+ public Namespace(
+ String id,
+ INamespace parent,
+ ILibrarySpecification specification,
+ ISpecificationSource specificationSource)
+ {
+ _id = id;
+ _parent = parent;
+ _specification = specification;
+ _specificationSource = specificationSource;
+
+ _applicationNamespace = (_id == null);
+ _frameworkNamespace = FRAMEWORK_NAMESPACE.equals(_id);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("Namespace@");
+ buffer.append(Integer.toHexString(hashCode()));
+ buffer.append('[');
+
+ if (_applicationNamespace)
+ buffer.append("<application>");
+ else
+ buffer.append(getExtendedId());
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ public String getId()
+ {
+ return _id;
+ }
+
+ public String getExtendedId()
+ {
+ if (_applicationNamespace)
+ return null;
+
+ if (_extendedId == null)
+ _extendedId = buildExtendedId();
+
+ return _extendedId;
+ }
+
+ public INamespace getParentNamespace()
+ {
+ return _parent;
+ }
+
+ public INamespace getChildNamespace(String id)
+ {
+ String firstId = id;
+ String nextIds = null;
+
+ // Split the id into first and next if it is a dot separated sequence
+ int index = id.indexOf('.');
+ if (index >= 0)
+ {
+ firstId = id.substring(0, index);
+ nextIds = id.substring(index + 1);
+ }
+
+ // Get the first namespace
+ INamespace result = (INamespace) _children.get(firstId);
+
+ if (result == null)
+ {
+ result = createNamespace(firstId);
+
+ _children.put(firstId, result);
+ }
+
+ // If the id is a dot separated sequence, recurse to find
+ // the needed namespace
+ if (result != null && nextIds != null)
+ result = result.getChildNamespace(nextIds);
+
+ return result;
+ }
+
+ public List getChildIds()
+ {
+ return _specification.getLibraryIds();
+ }
+
+ public IComponentSpecification getPageSpecification(String name)
+ {
+ IComponentSpecification result = (IComponentSpecification) _pages.get(name);
+
+ if (result == null)
+ {
+ result = locatePageSpecification(name);
+
+ _pages.put(name, result);
+ }
+
+ return result;
+ }
+
+ public List getPageNames()
+ {
+ Set names = new HashSet();
+
+ names.addAll(_pages.keySet());
+ names.addAll(_specification.getPageNames());
+
+ List result = new ArrayList(names);
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+ public IComponentSpecification getComponentSpecification(String alias)
+ {
+ IComponentSpecification result = (IComponentSpecification) _components.get(alias);
+
+ if (result == null)
+ {
+ result = locateComponentSpecification(alias);
+ _components.put(alias, result);
+ }
+
+ return result;
+ }
+
+ public String getServiceClassName(String name)
+ {
+ return _specification.getServiceClassName(name);
+ }
+
+ public List getServiceNames()
+ {
+ return _specification.getServiceNames();
+ }
+
+ public ILibrarySpecification getSpecification()
+ {
+ return _specification;
+ }
+
+ private String buildExtendedId()
+ {
+ if (_parent == null)
+ return _id;
+
+ String parentId = _parent.getExtendedId();
+
+ // If immediate child of application namespace
+
+ if (parentId == null)
+ return _id;
+
+ return parentId + "." + _id;
+ }
+
+ /**
+ * Returns a string identifying the namespace, for use in
+ * error messages. I.e., "Application namespace" or "namespace 'foo'".
+ *
+ **/
+
+ public String getNamespaceId()
+ {
+ if (_frameworkNamespace)
+ return Tapestry.getMessage("Namespace.framework-namespace");
+
+ if (_applicationNamespace)
+ return Tapestry.getMessage("Namespace.application-namespace");
+
+ return Tapestry.format("Namespace.nested-namespace", getExtendedId());
+ }
+
+ /**
+ * Gets the specification from the specification source.
+ *
+ * @throws ApplicationRuntimeException if the named page is not defined.
+ *
+ **/
+
+ private IComponentSpecification locatePageSpecification(String name)
+ {
+ String path = _specification.getPageSpecificationPath(name);
+
+ if (path == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Namespace.no-such-page", name, getNamespaceId()));
+
+ IResourceLocation location = getSpecificationLocation().getRelativeLocation(path);
+
+ return _specificationSource.getPageSpecification(location);
+ }
+
+ private IComponentSpecification locateComponentSpecification(String type)
+ {
+ String path = _specification.getComponentSpecificationPath(type);
+
+ if (path == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Namespace.no-such-alias", type, getNamespaceId()));
+
+ IResourceLocation location = getSpecificationLocation().getRelativeLocation(path);
+
+ return _specificationSource.getComponentSpecification(location);
+ }
+
+ private INamespace createNamespace(String id)
+ {
+ String path = _specification.getLibrarySpecificationPath(id);
+
+ if (path == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Namespace.library-id-not-found", id, getNamespaceId()));
+
+ IResourceLocation location = getSpecificationLocation().getRelativeLocation(path);
+
+ // Ok, an absolute path to a library for an application whose specification
+ // is in the context root is problematic, cause getRelativeLocation()
+ // will still be looking in the context. Handle this case with the
+ // following little kludge:
+
+ if (location.getResourceURL() == null && path.startsWith("/"))
+ location = new ClasspathResourceLocation(_specification.getResourceResolver(), path);
+
+ ILibrarySpecification ls = _specificationSource.getLibrarySpecification(location);
+
+ return new Namespace(id, this, ls, _specificationSource);
+ }
+
+ public boolean containsPage(String name)
+ {
+ return _pages.containsKey(name) || (_specification.getPageSpecificationPath(name) != null);
+ }
+
+ /** @since 2.3 **/
+
+ public String constructQualifiedName(String pageName)
+ {
+ String prefix = getExtendedId();
+
+ if (prefix == null)
+ return pageName;
+
+ return prefix + SEPARATOR + pageName;
+ }
+
+ /** @since 3.0 **/
+
+ public IResourceLocation getSpecificationLocation()
+ {
+ return _specification.getSpecificationLocation();
+ }
+
+ /** @since 3.0 **/
+
+ public boolean isApplicationNamespace()
+ {
+ return _applicationNamespace;
+ }
+
+ /** @since 3.0 **/
+
+ public synchronized void installPageSpecification(
+ String pageName,
+ IComponentSpecification specification)
+ {
+ _pages.put(pageName, specification);
+ }
+
+ /** @since 3.0 **/
+
+ public synchronized void installComponentSpecification(
+ String type,
+ IComponentSpecification specification)
+ {
+ _components.put(type, specification);
+ }
+
+ /** @since 3.0 **/
+
+ public boolean containsComponentType(String type)
+ {
+ return _components.containsKey(type)
+ || (_specification.getComponentSpecificationPath(type) != null);
+ }
+
+ /** @since 3.0 **/
+
+ public List getComponentTypes()
+ {
+ Set types = new HashSet();
+
+ types.addAll(_components.keySet());
+ types.addAll(_specification.getComponentTypes());
+
+ List result = new ArrayList(types);
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+ /** @since 3.0 **/
+
+ public ILocation getLocation()
+ {
+ if (_specification == null)
+ return null;
+
+ return _specification.getLocation();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/NullMonitor.java b/tapestry-framework/src/org/apache/tapestry/engine/NullMonitor.java
new file mode 100644
index 0000000..d3a2b6c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/NullMonitor.java
@@ -0,0 +1,81 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+
+
+/**
+ * Null implementation of {@link org.apache.tapestry.engine.IMonitor}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class NullMonitor implements IMonitor
+{
+ public static final NullMonitor SHARED = new NullMonitor();
+
+ public void pageCreateBegin(String pageName)
+ {
+ }
+
+ public void pageCreateEnd(String pageName)
+ {
+ }
+
+ public void pageLoadBegin(String pageName)
+ {
+ }
+
+ public void pageLoadEnd(String pageName)
+ {
+ }
+
+ public void pageRenderBegin(String pageName)
+ {
+ }
+
+ public void pageRenderEnd(String pageName)
+ {
+ }
+
+ public void pageRewindBegin(String pageName)
+ {
+ }
+
+ public void pageRewindEnd(String pageName)
+ {
+ }
+
+ public void serviceBegin(String serviceName, String detailMessage)
+ {
+ }
+
+ public void serviceEnd(String serviceName)
+ {
+ }
+
+ public void serviceException(Throwable exception)
+ {
+ }
+
+ public void sessionBegin()
+ {
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/NullTemplateSourceDelegate.java b/tapestry-framework/src/org/apache/tapestry/engine/NullTemplateSourceDelegate.java
new file mode 100644
index 0000000..daa3105
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/NullTemplateSourceDelegate.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.Locale;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.parse.ComponentTemplate;
+
+/**
+ * Null implementation of {@link org.apache.tapestry.engine.ITemplateSourceDelegate}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class NullTemplateSourceDelegate implements ITemplateSourceDelegate
+{
+ private static NullTemplateSourceDelegate _shared;
+
+ /**
+ * Returns a shared instance of NullTemplateSourceDelegate.
+ *
+ **/
+
+ public static NullTemplateSourceDelegate getSharedInstance()
+ {
+ if (_shared == null)
+ _shared = new NullTemplateSourceDelegate();
+
+ return _shared;
+ }
+
+ /**
+ * Simply returns null.
+ *
+ **/
+
+ public ComponentTemplate findTemplate(IRequestCycle cycle, IComponent component, Locale locale)
+ {
+ return null;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/NullWriter.java b/tapestry-framework/src/org/apache/tapestry/engine/NullWriter.java
new file mode 100644
index 0000000..cfb5d8c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/NullWriter.java
@@ -0,0 +1,155 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * A {@link IMarkupWriter} that does absolutely <em>nothing</em>; this
+ * is used during the rewind phase of the request cycle when output
+ * is discarded anyway.
+ *
+ * @author Howard Lewis Ship, David Solis
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+public class NullWriter implements IMarkupWriter
+{
+ private static IMarkupWriter shared;
+
+ public static IMarkupWriter getSharedInstance()
+ {
+ if (shared == null)
+ shared = new NullWriter();
+
+ return shared;
+ }
+
+ public void printRaw(char[] buffer, int offset, int length)
+ {
+ }
+
+ public void printRaw(String value)
+ {
+ }
+
+ public void println()
+ {
+ }
+
+ public void print(char[] data, int offset, int length)
+ {
+ }
+
+ public void print(char value)
+ {
+ }
+
+ public void print(int value)
+ {
+ }
+
+ public void print(String value)
+ {
+ }
+
+ /**
+ * Returns <code>this</code>: since a NullWriter doesn't actually
+ * do anything, one is as good as another!.
+ *
+ **/
+
+ public IMarkupWriter getNestedWriter()
+ {
+ return this;
+ }
+
+ public String getContentType()
+ {
+ return null;
+ }
+
+ public void flush()
+ {
+ }
+
+ public void end()
+ {
+ }
+
+ public void end(String name)
+ {
+ }
+
+ public void comment(String value)
+ {
+ }
+
+ public void closeTag()
+ {
+ }
+
+ public void close()
+ {
+ }
+
+ /**
+ * Always returns false.
+ *
+ **/
+
+ public boolean checkError()
+ {
+ return false;
+ }
+
+ public void beginEmpty(String name)
+ {
+ }
+
+ public void begin(String name)
+ {
+ }
+
+ public void attribute(String name, int value)
+ {
+ }
+
+ public void attribute(String name, String value)
+ {
+ }
+
+ /**
+ * @see org.apache.tapestry.IMarkupWriter#attribute(java.lang.String, boolean)
+ *
+ * @since 3.0
+ **/
+
+ public void attribute(String name, boolean value)
+ {
+ }
+
+ /**
+ * @see org.apache.tapestry.IMarkupWriter#attributeRaw(java.lang.String, java.lang.String)
+ *
+ * @since 3.0
+ **/
+
+ public void attributeRaw(String name, String value)
+ {
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/PageService.java b/tapestry-framework/src/org/apache/tapestry/engine/PageService.java
new file mode 100644
index 0000000..5467408
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/PageService.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * Basic server for creating a link to another page in the application.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ **/
+
+public class PageService extends AbstractService
+{
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (Tapestry.size(parameters) != 1)
+ throw new IllegalArgumentException(
+ Tapestry.format("service-single-parameter", Tapestry.PAGE_SERVICE));
+
+ return constructLink(cycle, Tapestry.PAGE_SERVICE, new String[] {(String) parameters[0]}, null, true);
+
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ RequestContext context = cycle.getRequestContext();
+ String[] serviceContext = getServiceContext(context);
+
+ if (Tapestry.size(serviceContext) != 1)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-single-parameter", Tapestry.PAGE_SERVICE));
+
+ String pageName = serviceContext[0];
+
+ // At one time, the page service required a session, but that is no longer necessary.
+ // Users can now bookmark pages within a Tapestry application. Pages
+ // can implement validate() and throw a PageRedirectException if they don't
+ // want to be accessed this way. For example, most applications have a concept
+ // of a "login" and have a few pages that don't require the user to be logged in,
+ // and other pages that do. The protected pages should redirect to a login page.
+
+ IPage page = cycle.getPage(pageName);
+
+ cycle.activate(page);
+
+ engine.renderResponse(cycle, output);
+ }
+
+ public String getName()
+ {
+ return Tapestry.PAGE_SERVICE;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/RequestCycle.java b/tapestry-framework/src/org/apache/tapestry/engine/RequestCycle.java
new file mode 100644
index 0000000..5b57110
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/RequestCycle.java
@@ -0,0 +1,718 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.RenderRewoundException;
+import org.apache.tapestry.StaleLinkException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.event.ChangeObserver;
+import org.apache.tapestry.event.ObservedChangeEvent;
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Provides the logic for processing a single request cycle. Provides access to
+ * the {@link IEngine engine} and the {@link RequestContext}.
+ *
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class RequestCycle implements IRequestCycle, ChangeObserver
+{
+ private static final Log LOG = LogFactory.getLog(RequestCycle.class);
+
+ private IPage _page;
+ private IEngine _engine;
+ private IEngineService _service;
+
+ private RequestContext _requestContext;
+
+ private IMonitor _monitor;
+
+ private HttpServletResponse _response;
+
+ /**
+ * A mapping of pages loaded during the current request cycle.
+ * Key is the page name, value is the {@link IPage} instance.
+ *
+ **/
+
+ private Map _loadedPages;
+
+ /**
+ * A mapping of page recorders for the current request cycle.
+ * Key is the page name, value is the {@link IPageRecorder} instance.
+ *
+ **/
+
+ private Map _loadedRecorders;
+
+ private boolean _rewinding = false;
+
+ private Map _attributes;
+
+ private int _actionId;
+ private int _targetActionId;
+ private IComponent _targetComponent;
+
+ /** @since 2.0.3 **/
+
+ private Object[] _serviceParameters;
+
+ /**
+ * Standard constructor used to render a response page.
+ *
+ **/
+
+ public RequestCycle(
+ IEngine engine,
+ RequestContext requestContext,
+ IEngineService service,
+ IMonitor monitor)
+ {
+ _engine = engine;
+ _requestContext = requestContext;
+ _service = service;
+ _monitor = monitor;
+ }
+
+ /**
+ * Called at the end of the request cycle (i.e., after all responses have been
+ * sent back to the client), to release all pages loaded during the request cycle.
+ *
+ **/
+
+ public void cleanup()
+ {
+ if (_loadedPages == null)
+ return;
+
+ IPageSource source = _engine.getPageSource();
+ Iterator i = _loadedPages.values().iterator();
+
+ while (i.hasNext())
+ {
+ IPage page = (IPage) i.next();
+
+ source.releasePage(page);
+ }
+
+ _loadedPages = null;
+ _loadedRecorders = null;
+
+ }
+
+ public IEngineService getService()
+ {
+ return _service;
+ }
+
+ public String encodeURL(String URL)
+ {
+ if (_response == null)
+ _response = _requestContext.getResponse();
+
+ return _response.encodeURL(URL);
+ }
+
+ public IEngine getEngine()
+ {
+ return _engine;
+ }
+
+ public Object getAttribute(String name)
+ {
+ if (_attributes == null)
+ return null;
+
+ return _attributes.get(name);
+ }
+
+ public IMonitor getMonitor()
+ {
+ return _monitor;
+ }
+
+ public String getNextActionId()
+ {
+ return Integer.toHexString(++_actionId);
+ }
+
+ public IPage getPage()
+ {
+ return _page;
+ }
+
+ /**
+ * Gets the page from the engines's {@link IPageSource}.
+ *
+ **/
+
+ public IPage getPage(String name)
+ {
+ IPage result = null;
+
+ if (name == null)
+ throw new NullPointerException(Tapestry.getMessage("RequestCycle.invalid-null-name"));
+
+ if (_loadedPages != null)
+ result = (IPage) _loadedPages.get(name);
+
+ if (result == null)
+ {
+ _monitor.pageLoadBegin(name);
+
+ IPageSource pageSource = _engine.getPageSource();
+
+ result = pageSource.getPage(this, name, _monitor);
+
+ // Get the recorder that will eventually observe and record
+ // changes to persistent properties of the page. If the page
+ // has never emitted any page changes, then it will
+ // not have a recorder.
+
+ IPageRecorder recorder = getPageRecorder(name);
+
+ if (recorder != null)
+ {
+ // Have it rollback the page to the prior state. Note that
+ // the page has a null observer at this time.
+
+ recorder.rollback(result);
+
+ // Now, have the page use the recorder for any future
+ // property changes.
+
+ result.setChangeObserver(recorder);
+
+ // And, if this recorder observed changes in a prior request cycle
+ // (and was locked after committing in that cycle), it's time
+ // to unlock.
+
+ recorder.setLocked(false);
+ }
+ else
+ {
+ // No page recorder for the page. We'll observe its
+ // changes and create the page recorder dynamically
+ // if it emits any.
+
+ result.setChangeObserver(this);
+ }
+
+ _monitor.pageLoadEnd(name);
+
+ if (_loadedPages == null)
+ _loadedPages = new HashMap();
+
+ _loadedPages.put(name, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the page recorder for the named page. This may come
+ * from the cycle's cache of page recorders or, if not yet encountered
+ * in this request cycle, the {@link IEngine#getPageRecorder(String, IRequestCycle)} is
+ * invoked to get the recorder, if it exists.
+ *
+ **/
+
+ protected IPageRecorder getPageRecorder(String name)
+ {
+ IPageRecorder result = null;
+
+ if (_loadedRecorders != null)
+ result = (IPageRecorder) _loadedRecorders.get(name);
+
+ if (result != null)
+ return result;
+
+ result = _engine.getPageRecorder(name, this);
+
+ if (result == null)
+ return null;
+
+ if (_loadedRecorders == null)
+ _loadedRecorders = new HashMap();
+
+ _loadedRecorders.put(name, result);
+
+ return result;
+ }
+
+ /**
+ *
+ * Gets the page recorder from the loadedRecorders cache, or from the engine
+ * (putting it into loadedRecorders). If the recorder does not yet exist,
+ * it is created.
+ *
+ * @see IEngine#createPageRecorder(String, IRequestCycle)
+ * @since 2.0.3
+ *
+ **/
+
+ private IPageRecorder createPageRecorder(String name)
+ {
+ IPageRecorder result = getPageRecorder(name);
+
+ if (result == null)
+ {
+ result = _engine.createPageRecorder(name, this);
+
+ if (_loadedRecorders == null)
+ _loadedRecorders = new HashMap();
+
+ _loadedRecorders.put(name, result);
+ }
+
+ return result;
+ }
+
+ public RequestContext getRequestContext()
+ {
+ return _requestContext;
+ }
+
+ public boolean isRewinding()
+ {
+ return _rewinding;
+ }
+
+ public boolean isRewound(IComponent component) throws StaleLinkException
+ {
+ // If not rewinding ...
+
+ if (!_rewinding)
+ return false;
+
+ if (_actionId != _targetActionId)
+ return false;
+
+ // OK, we're there, is the page is good order?
+
+ if (component == _targetComponent)
+ return true;
+
+ // Woops. Mismatch.
+
+ throw new StaleLinkException(
+ component,
+ Integer.toHexString(_targetActionId),
+ _targetComponent.getExtendedId());
+ }
+
+ public void removeAttribute(String name)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Removing attribute " + name);
+
+ if (_attributes == null)
+ return;
+
+ _attributes.remove(name);
+ }
+
+ /**
+ * Renders the page by invoking
+ * {@link IPage#renderPage(IMarkupWriter, IRequestCycle)}.
+ * This clears all attributes.
+ *
+ **/
+
+ public void renderPage(IMarkupWriter writer)
+ {
+ String pageName = _page.getPageName();
+ _monitor.pageRenderBegin(pageName);
+
+ _rewinding = false;
+ _actionId = -1;
+ _targetActionId = 0;
+
+ // Forget any attributes from a previous render cycle.
+
+ if (_attributes != null)
+ _attributes.clear();
+
+ try
+ {
+ _page.renderPage(writer, this);
+
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ // Nothing much to add here.
+
+ throw ex;
+ }
+ catch (Throwable ex)
+ {
+ // But wrap other exceptions in a RequestCycleException ... this
+ // will ensure that some of the context is available.
+
+ throw new ApplicationRuntimeException(ex.getMessage(), _page, null, ex);
+ }
+ finally
+ {
+ _actionId = 0;
+ _targetActionId = 0;
+ }
+
+ _monitor.pageRenderEnd(pageName);
+
+ }
+
+ /**
+ * Rewinds an individual form by invoking
+ * {@link IForm#rewind(IMarkupWriter, IRequestCycle)}.
+ *
+ * <p>The process is expected to end with a {@link RenderRewoundException}.
+ * If the entire page is renderred without this exception being thrown, it means
+ * that the target action id was not valid, and a
+ * {@link ApplicationRuntimeException}
+ * is thrown.
+ *
+ * <p>This clears all attributes.
+ *
+ * @since 1.0.2
+ **/
+
+ public void rewindForm(IForm form, String targetActionId)
+ {
+ IPage page = form.getPage();
+ String pageName = page.getPageName();
+
+ _rewinding = true;
+
+ _monitor.pageRewindBegin(pageName);
+
+ if (_attributes != null)
+ _attributes.clear();
+
+ // Fake things a little for getNextActionId() / isRewound()
+
+ _targetActionId = Integer.parseInt(targetActionId, 16);
+ _actionId = _targetActionId - 1;
+
+ _targetComponent = form;
+
+ try
+ {
+ page.beginPageRender();
+
+ form.rewind(NullWriter.getSharedInstance(), this);
+
+ // Shouldn't get this far, because the form should
+ // throw the RenderRewoundException.
+
+ throw new StaleLinkException(
+ Tapestry.format("RequestCycle.form-rewind-failure", form.getExtendedId()),
+ form);
+ }
+ catch (RenderRewoundException ex)
+ {
+ // This is acceptible and expected.
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ // RequestCycleExceptions don't need to be wrapped.
+ throw ex;
+ }
+ catch (Throwable ex)
+ {
+ // But wrap other exceptions in a ApplicationRuntimeException ... this
+ // will ensure that some of the context is available.
+
+ throw new ApplicationRuntimeException(ex.getMessage(), page, null, ex);
+ }
+ finally
+ {
+ _actionId = 0;
+ _targetActionId = 0;
+ _targetComponent = null;
+
+ page.endPageRender();
+
+ _monitor.pageRewindEnd(pageName);
+
+ _rewinding = false;
+ }
+ }
+
+ /**
+ * Rewinds the page by invoking
+ * {@link IPage#renderPage(IMarkupWriter, IRequestCycle)}.
+ *
+ * <p>The process is expected to end with a {@link RenderRewoundException}.
+ * If the entire page is renderred without this exception being thrown, it means
+ * that the target action id was not valid, and a
+ * {@link ApplicationRuntimeException}
+ * is thrown.
+ *
+ * <p>This clears all attributes.
+ *
+ **/
+
+ public void rewindPage(String targetActionId, IComponent targetComponent)
+ {
+ String pageName = _page.getPageName();
+
+ _rewinding = true;
+
+ _monitor.pageRewindBegin(pageName);
+
+ if (_attributes != null)
+ _attributes.clear();
+
+ _actionId = -1;
+
+ // Parse the action Id as hex since that's whats generated
+ // by getNextActionId()
+ _targetActionId = Integer.parseInt(targetActionId, 16);
+ _targetComponent = targetComponent;
+
+ try
+ {
+ _page.renderPage(NullWriter.getSharedInstance(), this);
+
+ // Shouldn't get this far, because the target component should
+ // throw the RenderRewoundException.
+
+ throw new StaleLinkException(_page, targetActionId, targetComponent.getExtendedId());
+ }
+ catch (RenderRewoundException ex)
+ {
+ // This is acceptible and expected.
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ // ApplicationRuntimeExceptions don't need to be wrapped.
+ throw ex;
+ }
+ catch (Throwable ex)
+ {
+ // But wrap other exceptions in a RequestCycleException ... this
+ // will ensure that some of the context is available.
+
+ throw new ApplicationRuntimeException(ex.getMessage(), _page, null, ex);
+ }
+ finally
+ {
+
+ _actionId = 0;
+ _targetActionId = 0;
+ _targetComponent = null;
+
+ _monitor.pageRewindEnd(pageName);
+
+ _rewinding = false;
+ }
+
+ }
+
+ public void setAttribute(String name, Object value)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Set attribute " + name + " to " + value);
+
+ if (_attributes == null)
+ _attributes = new HashMap();
+
+ _attributes.put(name, value);
+ }
+
+ public void setPage(IPage value)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Set page to " + value);
+
+ _page = value;
+ }
+
+ public void setPage(String name)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Set page to " + name);
+
+ _page = getPage(name);
+ }
+
+ /**
+ * Invokes {@link IPageRecorder#commit()} on each page recorder loaded
+ * during the request cycle (even recorders marked for discard).
+ *
+ **/
+
+ public void commitPageChanges()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Committing page changes");
+
+ if (_loadedRecorders == null || _loadedRecorders.isEmpty())
+ return;
+
+ Iterator i = _loadedRecorders.values().iterator();
+
+ while (i.hasNext())
+ {
+ IPageRecorder recorder = (IPageRecorder) i.next();
+
+ recorder.commit();
+ }
+ }
+
+ /**
+ * For pages without a {@link IPageRecorder page recorder},
+ * we're the {@link ChangeObserver change observer}.
+ * If such a page actually emits a change, then
+ * we'll obtain a new page recorder from the
+ * {@link IEngine engine}, set the recorder
+ * as the page's change observer, and forward the event
+ * to the newly created recorder. In addition, the
+ * new page recorder is remembered so that it will
+ * be committed by {@link #commitPageChanges()}.
+ *
+ **/
+
+ public void observeChange(ObservedChangeEvent event)
+ {
+ IPage page = event.getComponent().getPage();
+ String pageName = page.getPageName();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Observed change in page " + pageName + "; creating page recorder.");
+
+ IPageRecorder recorder = createPageRecorder(pageName);
+
+ page.setChangeObserver(recorder);
+
+ recorder.observeChange(event);
+ }
+
+ /**
+ * Finds the page and its page recorder, creating the page recorder if necessary.
+ * The page recorder is marked for discard regardless of its current state.
+ *
+ * <p>This may make the application stateful even if the page recorder does
+ * not yet exist.
+ *
+ * <p>The page recorder will be discarded at the end of the current request cycle.
+ *
+ * @since 2.0.2
+ *
+ **/
+
+ public void discardPage(String name)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Discarding page " + name);
+
+ IPageRecorder recorder = _engine.getPageRecorder(name, this);
+
+ if (recorder == null)
+ {
+ _page = getPage(name);
+
+ recorder = createPageRecorder(name);
+
+ _page.setChangeObserver(recorder);
+ }
+
+ recorder.markForDiscard();
+ }
+
+ /** @since 2.0.3 **/
+
+ public Object[] getServiceParameters()
+ {
+ return _serviceParameters;
+ }
+
+ /** @since 2.0.3 **/
+
+ public void setServiceParameters(Object[] serviceParameters)
+ {
+ _serviceParameters = serviceParameters;
+ }
+
+ /** @since 3.0 **/
+
+ public void activate(String name)
+ {
+ IPage page = getPage(name);
+
+ activate(page);
+ }
+
+ /** @since 3.0 */
+
+ public void activate(IPage page)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Activating page " + page);
+
+ Tapestry.clearMethodInvocations();
+
+ page.validate(this);
+
+ Tapestry.checkMethodInvocation(
+ Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID,
+ "validate()",
+ page);
+
+ setPage(page);
+ }
+
+ /**
+ * @since 3.0
+ */
+ public String toString()
+ {
+ ToStringBuilder b = new ToStringBuilder(this);
+
+ b.append("rewinding", _rewinding);
+
+ if (_service != null)
+ b.append("service", _service.getName());
+
+ b.append("serviceParameters", _serviceParameters);
+
+ if (_loadedPages != null)
+ b.append("loadedPages", _loadedPages.keySet());
+
+ b.append("attributes", _attributes);
+ b.append("targetActionId", _targetActionId);
+ b.append("targetComponent", _targetComponent);
+
+ return b.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/ResetService.java b/tapestry-framework/src/org/apache/tapestry/engine/ResetService.java
new file mode 100644
index 0000000..f76e955
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/ResetService.java
@@ -0,0 +1,88 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * ServiceLink used to discard all cached data (templates, specifications, et cetera).
+ * This is primarily used during development. It could be a weakness of a Tapestry
+ * application, making it susceptible to denial of service attacks, which is why
+ * it is disabled by default. The link generated by the ResetService redisplays the
+ * current page after discarding all data.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ * @see org.apache.tapestry.IEngine#isResetServiceEnabled()
+ *
+ **/
+
+public class ResetService extends AbstractService
+{
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (Tapestry.size(parameters) != 0)
+ throw new IllegalArgumentException(
+ Tapestry.format("service-no-parameters", Tapestry.RESET_SERVICE));
+
+ String[] context = new String[1];
+ context[0] = component.getPage().getPageName();
+
+ return constructLink(cycle, Tapestry.RESET_SERVICE, context, null, true);
+ }
+
+ public String getName()
+ {
+ return Tapestry.RESET_SERVICE;
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ String[] context = getServiceContext(cycle.getRequestContext());
+
+ if (Tapestry.size(context) != 1)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("service-single-parameter", Tapestry.RESET_SERVICE));
+
+ String pageName = context[0];
+
+ if (engine.isResetServiceEnabled())
+ engine.clearCachedData();
+
+ IPage page = cycle.getPage(pageName);
+
+ cycle.activate(page);
+
+ // Render the same page (that contained the reset link).
+
+ engine.renderResponse(cycle, output);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/RestartService.java b/tapestry-framework/src/org/apache/tapestry/engine/RestartService.java
new file mode 100644
index 0000000..bdb64bc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/RestartService.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * Restarts the Tapestry application. This is normally reserved for dealing with
+ * catastrophic failures of the application. Discards the {@link javax.servlet.http.HttpSession}, if any,
+ * and redirects to the Tapestry application servlet URL (invoking the {@link HomeService}).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.9
+ *
+ **/
+
+public class RestartService extends AbstractService
+{
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ if (Tapestry.size(parameters) != 0)
+ throw new IllegalArgumentException(
+ Tapestry.format("service-no-parameters", Tapestry.RESTART_SERVICE));
+
+ return constructLink(cycle, Tapestry.RESTART_SERVICE, null, null, true);
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ engine.restart(cycle);
+ }
+
+ public String getName()
+ {
+ return Tapestry.RESTART_SERVICE;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/TagSupportService.java b/tapestry-framework/src/org/apache/tapestry/engine/TagSupportService.java
new file mode 100644
index 0000000..faaedd3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/TagSupportService.java
@@ -0,0 +1,160 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.html.HTMLWriter;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.request.ResponseOutputStream;
+
+/**
+ * A very specialized service used by JSPs to access Tapestry URLs.
+ * This is used by the Tapestry JSP tags, such as
+ * {@link org.apache.tapestry.jsp.PageTag}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ * @see org.apache.tapestry.jsp.URLRetriever
+ *
+ **/
+
+public class TagSupportService implements IEngineService
+{
+ private static final Log LOG = LogFactory.getLog(TagSupportService.class);
+
+ /**
+ * Not to be invoked; this service is different than the others.
+ *
+ * @throws ApplicationRuntimeException always
+ *
+ **/
+
+ public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("TagSupportService.service-only"));
+ }
+
+ public void service(
+ IEngineServiceView engine,
+ IRequestCycle cycle,
+ ResponseOutputStream output)
+ throws ServletException, IOException
+ {
+ RequestContext context = cycle.getRequestContext();
+ HttpServletRequest request = context.getRequest();
+
+ String serviceName = getAttribute(request, Tapestry.TAG_SUPPORT_SERVICE_ATTRIBUTE);
+
+ Object raw = request.getAttribute(Tapestry.TAG_SUPPORT_PARAMETERS_ATTRIBUTE);
+ Object[] parameters = null;
+
+ try
+ {
+ parameters = (Object[]) raw;
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ServletException(
+ Tapestry.format(
+ "TagSupportService.attribute-not-array",
+ Tapestry.TAG_SUPPORT_PARAMETERS_ATTRIBUTE,
+ Tapestry.getClassName(raw.getClass())));
+ }
+
+ IEngineService service = cycle.getEngine().getService(serviceName);
+
+ ILink link = service.getLink(cycle, null, parameters);
+
+ String URI = link.getURL();
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Request servlet path = " + request.getServletPath());
+
+ Enumeration e = request.getParameterNames();
+ while (e.hasMoreElements())
+ {
+ String name = (String) e.nextElement();
+ LOG.debug("Request parameter " + name + " = " + request.getParameter(name));
+ }
+ e = request.getAttributeNames();
+ while (e.hasMoreElements())
+ {
+ String name = (String) e.nextElement();
+ LOG.debug("Request attribute " + name + " = " + request.getAttribute(name));
+ }
+
+ LOG.debug("Result URI: " + URI);
+ }
+
+ HttpServletResponse response = context.getResponse();
+ PrintWriter servletWriter = response.getWriter();
+
+ IMarkupWriter writer = new HTMLWriter(servletWriter);
+
+ writer.print(URI);
+
+ writer.flush();
+ }
+
+ private String getAttribute(HttpServletRequest request, String name) throws ServletException
+ {
+ Object result = request.getAttribute(name);
+
+ if (result == null)
+ throw new ServletException(Tapestry.format("TagSupportService.null-attribute", name));
+
+ try
+ {
+ return (String) result;
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ServletException(
+ Tapestry.format(
+ "TagSupportService.attribute-not-string",
+ name,
+ Tapestry.getClassName(result.getClass())));
+
+ }
+ }
+
+ /**
+ * @return {@link org.apache.tapestry.Tapestry#TAGSUPPORT_SERVICE}.
+ *
+ **/
+
+ public String getName()
+ {
+ return Tapestry.TAGSUPPORT_SERVICE;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/TemplateParserDelegateImpl.java b/tapestry-framework/src/org/apache/tapestry/engine/TemplateParserDelegateImpl.java
new file mode 100644
index 0000000..b5cbe1f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/TemplateParserDelegateImpl.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.engine;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.parse.ITemplateParserDelegate;
+import org.apache.tapestry.resolver.ComponentSpecificationResolver;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Basic implementation of the {@link org.apache.tapestry.parse.ITemplateParserDelegate} interface.
+ *
+ * @author Howard Lewis Ship
+ */
+public class TemplateParserDelegateImpl implements ITemplateParserDelegate
+{
+ private IComponent _component;
+ private ComponentSpecificationResolver _resolver;
+ private IRequestCycle _cycle;
+
+ public TemplateParserDelegateImpl(IComponent component, IRequestCycle cycle)
+ {
+ _component = component;
+ _resolver = new ComponentSpecificationResolver(cycle);
+ _cycle = cycle;
+ }
+
+ public boolean getKnownComponent(String componentId)
+ {
+ return _component.getSpecification().getComponent(componentId) != null;
+ }
+
+ public boolean getAllowBody(String componentId, ILocation location)
+ {
+ IComponent embedded = _component.getComponent(componentId);
+
+ if (embedded == null)
+ throw Tapestry.createNoSuchComponentException(_component, componentId, location);
+
+ return embedded.getSpecification().getAllowBody();
+ }
+
+ public boolean getAllowBody(String libraryId, String type, ILocation location)
+ {
+ INamespace namespace = _component.getNamespace();
+
+ _resolver.resolve(_cycle, namespace, libraryId, type, location);
+
+ IComponentSpecification spec = _resolver.getSpecification();
+
+ return spec.getAllowBody();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/engine/package.html b/tapestry-framework/src/org/apache/tapestry/engine/package.html
new file mode 100644
index 0000000..d6ea383
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/engine/package.html
@@ -0,0 +1,19 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Implementations of the {@link org.apache.tapestry.IEngine} interface, including
+the standard implementation:
+{@link org.apache.tapestry.engine.BaseEngine}. Also located here are
+default implementations of all the basic support objects, including
+{@link org.apache.tapestry.engine.RequestCycle}
+(which implements {@link org.apache.tapestry.IRequestCycle}).
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/BaseEnhancedClass.java b/tapestry-framework/src/org/apache/tapestry/enhance/BaseEnhancedClass.java
new file mode 100644
index 0000000..49b0bce
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/BaseEnhancedClass.java
@@ -0,0 +1,74 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ *
+ */
+public abstract class BaseEnhancedClass implements IEnhancedClass
+{
+
+ /**
+ * List of {@link IEnhancer}.
+ *
+ **/
+ private List _enhancers;
+
+ protected List getEnhancers()
+ {
+ return _enhancers;
+ }
+
+ public void addEnhancer(IEnhancer enhancer)
+ {
+ if (_enhancers == null)
+ _enhancers = new ArrayList();
+
+ _enhancers.add(enhancer);
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#hasModifications()
+ */
+ public boolean hasModifications()
+ {
+ return _enhancers != null && !_enhancers.isEmpty();
+ }
+
+ public void performEnhancement()
+ {
+ List enhancers = getEnhancers();
+
+ if (enhancers == null)
+ return;
+
+ int count = enhancers.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ IEnhancer enhancer = (IEnhancer) enhancers.get(i);
+
+ enhancer.performEnhancement(this);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/CodeGenerationException.java b/tapestry-framework/src/org/apache/tapestry/enhance/CodeGenerationException.java
new file mode 100644
index 0000000..a7c91b1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/CodeGenerationException.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+/**
+ * This is an unrecoverable error during code generation.
+ * It should not occur and would typically be the result
+ * of a bug in the Tapestry code.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class CodeGenerationException extends RuntimeException
+{
+ Throwable _cause;
+
+ public CodeGenerationException()
+ {
+ super();
+ }
+
+ public CodeGenerationException(String message)
+ {
+ super(message);
+ }
+
+ public CodeGenerationException(String message, Throwable cause)
+ {
+ super(message);
+ _cause = cause;
+ }
+
+ public CodeGenerationException(Throwable cause)
+ {
+ super();
+ _cause = cause;
+ }
+
+ public Throwable getCause()
+ {
+ return _cause;
+ }
+
+
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/ComponentClassFactory.java b/tapestry-framework/src/org/apache/tapestry/enhance/ComponentClassFactory.java
new file mode 100644
index 0000000..e56bad4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/ComponentClassFactory.java
@@ -0,0 +1,529 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.Direction;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IParameterSpecification;
+import org.apache.tapestry.spec.IPropertySpecification;
+
+/**
+ * Contains the logic for analyzing and enhancing a single component class.
+ * Internally, this class makes use of {@link IEnhancedClassFactory}.
+ *
+ * @author Howard Lewis Ship
+ * @since 3.0
+ *
+ **/
+
+public class ComponentClassFactory
+{
+ private static final Log LOG = LogFactory.getLog(ComponentClassFactory.class);
+
+ /**
+ * Package prefix to be added if the enhanced object is in a 'sysem' package
+ */
+ private static final String PACKAGE_PREFIX = "org.apache.tapestry.";
+
+ /**
+ * UID used to generate new class names.
+ **/
+ private static int _uid = 0;
+
+ /**
+ * Mapping between a primitive type and its Java VM representation
+ * Used for the encoding of array types
+ **/
+ private static Map _primitiveTypes = new HashMap();
+
+ static {
+ _primitiveTypes.put("boolean", "Z");
+ _primitiveTypes.put("short", "S");
+ _primitiveTypes.put("int", "I");
+ _primitiveTypes.put("long", "J");
+ _primitiveTypes.put("float", "F");
+ _primitiveTypes.put("double", "D");
+ _primitiveTypes.put("char", "C");
+ _primitiveTypes.put("byte", "B");
+ }
+
+ private IResourceResolver _resolver;
+
+ private IEnhancedClassFactory _enhancedClassFactory;
+ private IEnhancedClass _enhancedClass;
+ private Map _beanProperties = new HashMap();
+ private IComponentSpecification _specification;
+ private Class _componentClass;
+ private JavaClassMapping _classMapping = new JavaClassMapping();
+
+ public ComponentClassFactory(
+ IResourceResolver resolver,
+ IComponentSpecification specification,
+ Class componentClass,
+ IEnhancedClassFactory enhancedClassFactory)
+ {
+ _resolver = resolver;
+
+ _specification = specification;
+
+ _componentClass = componentClass;
+
+ _enhancedClassFactory = enhancedClassFactory;
+
+ buildBeanProperties();
+ }
+
+ private void buildBeanProperties()
+ {
+ BeanInfo info = null;
+
+ try
+ {
+ info = Introspector.getBeanInfo(_componentClass);
+
+ }
+ catch (IntrospectionException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "ComponentClassFactory.unable-to-introspect-class",
+ _componentClass.getName()),
+ ex);
+ }
+
+ PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
+
+ for (int i = 0; i < descriptors.length; i++)
+ {
+ _beanProperties.put(descriptors[i].getName(), descriptors[i]);
+ }
+ }
+
+ protected PropertyDescriptor getPropertyDescriptor(String name)
+ {
+ return (PropertyDescriptor) _beanProperties.get(name);
+ }
+
+ /**
+ * Invokes {@link #scanForEnhancements()} to identify any
+ * enhancements needed on the class, returning true
+ * if there are any enhancements to be performed.
+ *
+ **/
+
+ public boolean needsEnhancement()
+ {
+ scanForEnhancements();
+
+ return _enhancedClass != null && _enhancedClass.hasModifications();
+ }
+
+ /**
+ * @return true if pd is not null and both read/write methods are implemented
+ */
+ public boolean isImplemented(PropertyDescriptor pd)
+ {
+ if (pd == null)
+ return false;
+
+ return isImplemented(pd.getReadMethod()) && isImplemented(pd.getWriteMethod());
+ }
+
+ /**
+ * @return true if m is not null and is abstract.
+ */
+ public boolean isAbstract(Method m)
+ {
+ if (m == null)
+ return false;
+
+ return Modifier.isAbstract(m.getModifiers());
+ }
+
+ /**
+ * @return true if m is not null and not abstract
+ */
+ public boolean isImplemented(Method m)
+ {
+ if (m == null)
+ return false;
+
+ return !Modifier.isAbstract(m.getModifiers());
+ }
+
+ /**
+ * Given a class name, returns the corresponding class. In addition,
+ * scalar types, arrays of scalar types, java.lang.Object[] and
+ * java.lang.String[] are supported.
+ *
+ * @param type to convert to a Class
+ * @param location of the involved specification element (for exception reporting)
+ *
+ **/
+
+ public Class convertPropertyType(String type, ILocation location)
+ {
+ Class result = _classMapping.getType(type);
+
+ if (result == null)
+ {
+ try
+ {
+ String typeName = translateClassName(type);
+ result = _resolver.findClass(typeName);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ComponentClassFactory.bad-property-type", type),
+ location,
+ ex);
+ }
+
+ _classMapping.recordType(type, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Translates types from standard Java format to Java VM format.
+ * For example, java.util.Locale remains java.util.Locale, but
+ * int[][] is translated to [[I and java.lang.Object[] to
+ * [Ljava.lang.Object;
+ * This method and its static Map should go into a utility class
+ */
+ protected String translateClassName(String type)
+ {
+ // if it is not an array, just return the type itself
+ if (!type.endsWith("[]"))
+ return type;
+
+ // if it is an array, convert it to JavaVM-style format
+ StringBuffer javaType = new StringBuffer();
+ while (type.endsWith("[]"))
+ {
+ javaType.append("[");
+ type = type.substring(0, type.length() - 2);
+ }
+
+ String primitiveIdentifier = (String) _primitiveTypes.get(type);
+ if (primitiveIdentifier != null)
+ javaType.append(primitiveIdentifier);
+ else
+ javaType.append("L" + type + ";");
+
+ return javaType.toString();
+ }
+
+ protected void checkPropertyType(PropertyDescriptor pd, Class propertyType, ILocation location)
+ {
+ if (!pd.getPropertyType().equals(propertyType))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "ComponentClassFactory.property-type-mismatch",
+ new Object[] {
+ _componentClass.getName(),
+ pd.getName(),
+ pd.getPropertyType().getName(),
+ propertyType.getName()}),
+ location,
+ null);
+ }
+
+ /**
+ * Checks to see that that class either doesn't provide the property, or does
+ * but the accessor(s) are abstract. Returns the name of the read accessor,
+ * or null if there is no such accessor (this is helpful if the beanClass
+ * defines a boolean property, where the name of the accessor may be isXXX or
+ * getXXX).
+ *
+ **/
+
+ protected String checkAccessors(String propertyName, Class propertyType, ILocation location)
+ {
+ PropertyDescriptor d = getPropertyDescriptor(propertyName);
+
+ if (d == null)
+ return null;
+
+ checkPropertyType(d, propertyType, location);
+
+ Method write = d.getWriteMethod();
+ Method read = d.getReadMethod();
+
+ if (isImplemented(write))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "ComponentClassFactory.non-abstract-write",
+ write.getDeclaringClass().getName(),
+ propertyName),
+ location,
+ null);
+
+ if (isImplemented(read))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "ComponentClassFactory.non-abstract-read",
+ read.getDeclaringClass().getName(),
+ propertyName),
+ location,
+ null);
+
+ return read == null ? null : read.getName();
+ }
+
+ protected boolean isMissingProperty(String propertyName)
+ {
+ PropertyDescriptor pd = getPropertyDescriptor(propertyName);
+
+ return !isImplemented(pd);
+ }
+
+ /**
+ * Invoked by {@link org.apache.tapestry.enhance.DefaultComponentClassEnhancer} to
+ * create an enahanced
+ * subclass of the component class. This means creating a default constructor,
+ * new fields, and new accessor and mutator methods. Properties are created
+ * for connected parameters, for all formal parameters (the binding property),
+ * and for all specified parameters (which may be transient or persistent).
+ *
+ **/
+
+ public Class createEnhancedSubclass()
+ {
+ IEnhancedClass enhancedClass = getEnhancedClass();
+
+ String startClassName = _componentClass.getName();
+ String subclassName = enhancedClass.getClassName();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Enhancing subclass of "
+ + startClassName
+ + " for "
+ + _specification.getSpecificationLocation());
+
+ Class result = enhancedClass.createEnhancedSubclass();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Finished creating enhanced class " + subclassName);
+
+ return result;
+ }
+
+ /**
+ * Invoked by {@link #needsEnhancement()} to find any enhancements
+ * that may be needed. Should create an {@link org.apache.tapestry.enhance.IEnhancer}
+ * for each one, and add it to the queue.
+ *
+ **/
+
+ protected void scanForEnhancements()
+ {
+ scanForParameterEnhancements();
+ scanForSpecifiedPropertyEnhancements();
+ scanForAbstractClass();
+ }
+
+ protected void scanForAbstractClass()
+ {
+ if (Modifier.isAbstract(_componentClass.getModifiers()))
+ getEnhancedClass().addEnhancer(new NoOpEnhancer());
+
+ }
+
+ /**
+ * Invoked by {@link #scanForEnhancements()} to locate
+ * any enhancements needed for component parameters (this includes
+ * binding properties and connected parameter property).
+ *
+ **/
+
+ protected void scanForParameterEnhancements()
+ {
+ List names = _specification.getParameterNames();
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) names.get(i);
+
+ IParameterSpecification ps = _specification.getParameter(name);
+
+ scanForBindingProperty(name, ps);
+
+ scanForParameterProperty(name, ps);
+ }
+
+ }
+
+ protected void scanForSpecifiedPropertyEnhancements()
+ {
+ List names = _specification.getPropertySpecificationNames();
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) names.get(i);
+
+ IPropertySpecification ps = _specification.getPropertySpecification(name);
+
+ scanForSpecifiedProperty(ps);
+ }
+ }
+
+ protected void scanForBindingProperty(String parameterName, IParameterSpecification ps)
+ {
+ String propertyName = parameterName + Tapestry.PARAMETER_PROPERTY_NAME_SUFFIX;
+ PropertyDescriptor pd = getPropertyDescriptor(propertyName);
+
+ // only enhance custom parameter binding properties if they are declared abstract
+ if (ps.getDirection() == Direction.CUSTOM)
+ {
+ if (pd == null)
+ return;
+
+ if (!(isAbstract(pd.getReadMethod()) || isAbstract(pd.getWriteMethod())))
+ return;
+ }
+
+ if (isImplemented(pd))
+ return;
+
+ // Need to create the property.
+ getEnhancedClass().createProperty(propertyName, IBinding.class.getName());
+ }
+
+ protected void scanForParameterProperty(String parameterName, IParameterSpecification ps)
+ {
+ Direction direction = ps.getDirection();
+
+ if (direction == Direction.CUSTOM)
+ return;
+
+ if (direction == Direction.AUTO)
+ {
+ addAutoParameterEnhancer(parameterName, ps);
+ return;
+ }
+
+ String propertyName = ps.getPropertyName();
+
+ // Yes, but does it *need* a property created?
+
+ if (!isMissingProperty(propertyName))
+ return;
+
+ ILocation location = ps.getLocation();
+
+ Class propertyType = convertPropertyType(ps.getType(), location);
+
+ String readMethodName = checkAccessors(propertyName, propertyType, location);
+
+ getEnhancedClass().createProperty(propertyName, ps.getType(), readMethodName, false);
+ }
+
+ protected void addAutoParameterEnhancer(String parameterName, IParameterSpecification ps)
+ {
+ ILocation location = ps.getLocation();
+ String propertyName = ps.getPropertyName();
+
+ if (!ps.isRequired() && ps.getDefaultValue() == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ComponentClassFactory.auto-must-be-required", parameterName),
+ location,
+ null);
+
+ Class propertyType = convertPropertyType(ps.getType(), location);
+
+ String readMethodName = checkAccessors(propertyName, propertyType, location);
+
+ getEnhancedClass().createAutoParameter(
+ propertyName,
+ parameterName,
+ ps.getType(),
+ readMethodName);
+ }
+
+ protected void scanForSpecifiedProperty(IPropertySpecification ps)
+ {
+ String propertyName = ps.getName();
+ ILocation location = ps.getLocation();
+ Class propertyType = convertPropertyType(ps.getType(), location);
+
+ PropertyDescriptor pd = getPropertyDescriptor(propertyName);
+
+ if (isImplemented(pd))
+ {
+ // Make sure the property is at least the right type.
+
+ checkPropertyType(pd, propertyType, location);
+ return;
+ }
+
+ String readMethodName = checkAccessors(propertyName, propertyType, location);
+
+ getEnhancedClass().createProperty(
+ propertyName,
+ ps.getType(),
+ readMethodName,
+ ps.isPersistent());
+ }
+
+ public IEnhancedClass getEnhancedClass()
+ {
+ if (_enhancedClass == null)
+ {
+ String startClassName = _componentClass.getName();
+ String subclassName = startClassName + "$Enhance_" + generateUID();
+
+ // If the new class is located in a 'restricted' package,
+ // add a neutral package prefix to the name.
+ // The class enhancement will likely fail anyway, since the original object
+ // would not implement IComponent, but we do not know what the enhancement
+ // will do in the future -- it might implement that interface automatically.
+ if (subclassName.startsWith("java.") || subclassName.startsWith("javax."))
+ subclassName = PACKAGE_PREFIX + subclassName;
+
+ _enhancedClass =
+ _enhancedClassFactory.createEnhancedClass(subclassName, _componentClass);
+ }
+ return _enhancedClass;
+ }
+
+ private static synchronized int generateUID()
+ {
+ return _uid++;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/DefaultComponentClassEnhancer.java b/tapestry-framework/src/org/apache/tapestry/enhance/DefaultComponentClassEnhancer.java
new file mode 100644
index 0000000..46c38df
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/DefaultComponentClassEnhancer.java
@@ -0,0 +1,264 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IComponentClassEnhancer;
+import org.apache.tapestry.enhance.javassist.EnhancedClassFactory;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Default implementation of {@link IComponentClassEnhancer}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class DefaultComponentClassEnhancer implements IComponentClassEnhancer
+{
+ private static final Log LOG = LogFactory.getLog(DefaultComponentClassEnhancer.class);
+
+ /**
+ * Map of Class, keyed on IComponentSpecification.
+ *
+ **/
+
+ private Map _cachedClasses;
+ private IResourceResolver _resolver;
+ private IEnhancedClassFactory _factory;
+ private boolean _disableValidation;
+
+ /**
+ * @param resolver resource resolver used to locate classes
+ * @param disableValidation if true, then validation (of unimplemented abstract methods)
+ * is skipped
+ */
+ public DefaultComponentClassEnhancer(IResourceResolver resolver, boolean disableValidation)
+ {
+ _cachedClasses = Collections.synchronizedMap(new HashMap());
+ _resolver = resolver;
+ _factory = createEnhancedClassFactory();
+ _disableValidation = disableValidation;
+ }
+
+ protected IEnhancedClassFactory createEnhancedClassFactory()
+ {
+ return new EnhancedClassFactory(getResourceResolver());
+ }
+
+ public synchronized void reset()
+ {
+ _cachedClasses.clear();
+ _factory.reset();
+ }
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ public Class getEnhancedClass(IComponentSpecification specification, String className)
+ {
+ synchronized (specification)
+ {
+ Class result = getCachedClass(specification);
+
+ if (result == null)
+ {
+ result = constructComponentClass(specification, className);
+ storeCachedClass(specification, result);
+ }
+
+ return result;
+ }
+ }
+
+ protected void storeCachedClass(IComponentSpecification specification, Class cachedClass)
+ {
+ _cachedClasses.put(specification, cachedClass);
+ }
+
+ protected Class getCachedClass(IComponentSpecification specification)
+ {
+ return (Class) _cachedClasses.get(specification);
+ }
+
+ /**
+ * Returns the class to be used for the component, which is either
+ * the class with the given name, or an enhanced subclass.
+ *
+ **/
+
+ protected Class constructComponentClass(
+ IComponentSpecification specification,
+ String className)
+ {
+ Class result = null;
+
+ try
+ {
+ result = _resolver.findClass(className);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), specification.getLocation(), ex);
+ }
+
+ try
+ {
+ ComponentClassFactory factory = createComponentClassFactory(specification, result);
+
+ if (factory.needsEnhancement())
+ {
+ result = factory.createEnhancedSubclass();
+
+ if (!_disableValidation)
+ validateEnhancedClass(result, className, specification);
+ }
+ }
+ catch (CodeGenerationException e)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ComponentClassFactory.code-generation-error", className),
+ e);
+ }
+
+ return result;
+ }
+
+ /**
+ * Constructs a new factory for enhancing the specified class. Advanced users
+ * may want to provide thier own enhancements to classes and this method
+ * is the hook that allows them to provide a subclass of
+ * {@link org.apache.tapestry.enhance.ComponentClassFactory} adding those
+ * enhancements.
+ *
+ **/
+
+ protected ComponentClassFactory createComponentClassFactory(
+ IComponentSpecification specification,
+ Class componentClass)
+ {
+ return new ComponentClassFactory(_resolver, specification, componentClass, _factory);
+ }
+
+ /**
+ * Invoked to validate that an enhanced class is acceptible. Primarily, this is to ensure
+ * that the class contains no unimplemented abstract methods or fields. Normally,
+ * this kind of checking is done at compile time, but for generated
+ * classes, there is no compile time check (!) and you can get runtime
+ * errors when accessing unimplemented abstract methods.
+ *
+ *
+ **/
+
+ protected void validateEnhancedClass(
+ Class subject,
+ String className,
+ IComponentSpecification specification)
+ {
+ boolean debug = LOG.isDebugEnabled();
+
+ if (debug)
+ LOG.debug("Validating " + subject);
+
+ Set implementedMethods = new HashSet();
+ Class current = subject;
+
+ while (true)
+ {
+ Method m = checkForAbstractMethods(current, implementedMethods);
+
+ if (m != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "DefaultComponentClassEnhancer.no-impl-for-abstract-method",
+ new Object[] { m, current, className, subject.getName()}),
+ specification.getLocation(),
+ null);
+
+ // An earlier version of this code walked the interfaces directly,
+ // but it appears that implementing an interface actually
+ // puts abstract method declarations into the class
+ // (at least, in terms of what getDeclaredMethods() returns).
+
+ // March up to the super class.
+
+ current = current.getSuperclass();
+
+ // Once advanced up to a concrete class, we trust that
+ // the compiler did its checking.
+
+ if (!Modifier.isAbstract(current.getModifiers()))
+ break;
+ }
+
+ }
+
+ /**
+ * Searches the class for abstract methods, returning the first found.
+ * Records non-abstract methods in the implementedMethods set.
+ *
+ **/
+
+ private Method checkForAbstractMethods(Class current, Set implementedMethods)
+ {
+ boolean debug = LOG.isDebugEnabled();
+
+ if (debug)
+ LOG.debug("Searching for abstract methods in " + current);
+
+ Method[] methods = current.getDeclaredMethods();
+
+ for (int i = 0; i < methods.length; i++)
+ {
+ Method m = methods[i];
+
+ if (debug)
+ LOG.debug("Checking " + m);
+
+ boolean isAbstract = Modifier.isAbstract(m.getModifiers());
+
+ MethodSignature s = new MethodSignature(m);
+
+ if (isAbstract)
+ {
+ if (implementedMethods.contains(s))
+ continue;
+
+ return m;
+ }
+
+ implementedMethods.add(s);
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/EnhancedClassLoader.java b/tapestry-framework/src/org/apache/tapestry/enhance/EnhancedClassLoader.java
new file mode 100644
index 0000000..2162b78
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/EnhancedClassLoader.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.security.ProtectionDomain;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A class loader that can be used to create new classes
+ * as needed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class EnhancedClassLoader extends ClassLoader
+{
+
+ public EnhancedClassLoader(ClassLoader parentClassLoader)
+ {
+ super(parentClassLoader);
+ }
+
+ /**
+ * Defines the new class.
+ *
+ * @throws ApplicationRuntimeException if defining the class fails.
+ *
+ **/
+
+ public Class defineClass(String enhancedClassName, byte[] byteCode, ProtectionDomain domain)
+ {
+ try
+ {
+ return defineClass(enhancedClassName, byteCode, 0, byteCode.length, domain);
+ }
+ catch (Throwable ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "EnhancedClassLoader.unable-to-define-class",
+ enhancedClassName,
+ ex.getMessage()),
+ ex);
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClass.java b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClass.java
new file mode 100644
index 0000000..d432990
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClass.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+/**
+ * This interface represents a class to be enhanced. An implementation
+ * is generated by {@link org.apache.tapestry.enhance.IEnhancedClassFactory}
+ * and is specific to the selected system of enhancement.
+ *
+ * @author Mindbridge
+ * @since 3.0
+ */
+public interface IEnhancedClass
+{
+ String getClassName();
+
+ /**
+ * Adds an enhancer for creating the specified property.
+ */
+ void createProperty(String propertyName, String propertyType);
+
+ void createProperty(
+ String propertyName,
+ String propertyType,
+ String readMethodName,
+ boolean persistent);
+
+ void createAutoParameter(
+ String propertyName,
+ String parameterName,
+ String typeClassName,
+ String readMethodName);
+
+ boolean hasModifications();
+
+ Class createEnhancedSubclass();
+
+ /**
+ * Adds an arbitrary enhancer.
+ */
+ void addEnhancer(IEnhancer enhancer);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClassFactory.java b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClassFactory.java
new file mode 100644
index 0000000..7287270
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancedClassFactory.java
@@ -0,0 +1,31 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+/**
+ * An interface defining the factory for creation of new objects representing
+ * an enhanced class. This object is used essentially as a singleton -- there is
+ * typically only one instance of it in the system. Common functionality, such as
+ * caches, can be stored here.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public interface IEnhancedClassFactory
+{
+ void reset();
+ IEnhancedClass createEnhancedClass(String className, Class parentClass);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancer.java b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancer.java
new file mode 100644
index 0000000..b947f35
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/IEnhancer.java
@@ -0,0 +1,32 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+/**
+ * Defines an object which may work with a
+ * {@link org.apache.tapestry.enhance.ComponentClassFactory}
+ * to create an enhancement to a class. These enhancements are
+ * typically in the form of adding new fields and methods.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface IEnhancer
+{
+ public void performEnhancement(IEnhancedClass enhancedClass);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/JavaClassMapping.java b/tapestry-framework/src/org/apache/tapestry/enhance/JavaClassMapping.java
new file mode 100644
index 0000000..cc2aa18
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/JavaClassMapping.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class JavaClassMapping
+{
+
+ /**
+ * Map of type (as Class), keyed on type name.
+ *
+ **/
+
+ private Map _typeMap = new HashMap();
+
+
+ {
+ recordType("boolean", boolean.class);
+ recordType("boolean[]", boolean[].class);
+
+ recordType("short", short.class);
+ recordType("short[]", short[].class);
+
+ recordType("int", int.class);
+ recordType("int[]", int[].class);
+
+ recordType("long", long.class);
+ recordType("long[]", long[].class);
+
+ recordType("float", float.class);
+ recordType("float[]", float[].class);
+
+ recordType("double", double.class);
+ recordType("double[]", double[].class);
+
+ recordType("char", char.class);
+ recordType("char[]", char[].class);
+
+ recordType("byte", byte.class);
+ recordType("byte[]", byte[].class);
+
+ recordType("java.lang.Object", Object.class);
+ recordType("java.lang.Object[]", Object[].class);
+
+ recordType("java.lang.String", String.class);
+ recordType("java.lang.String[]", String[].class);
+ }
+
+
+ public void recordType(String name, Class type)
+ {
+ _typeMap.put(name, type);
+ }
+
+ public Class getType(String name)
+ {
+ return (Class) _typeMap.get(name);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/MethodSignature.java b/tapestry-framework/src/org/apache/tapestry/enhance/MethodSignature.java
new file mode 100644
index 0000000..cabbe9e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/MethodSignature.java
@@ -0,0 +1,102 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * The signature of a {@link java.lang.reflect.Method}, including
+ * the name, return type, and parameter types. Used when checking
+ * for unimplemented methods in enhanced subclasses.
+ *
+ * <p>
+ * The modifiers (i.e., "public", "abstract") and thrown
+ * exceptions are not relevant for these purposes, and
+ * are not part of the signature.
+ *
+ * <p>
+ * Instances of MethodSignature are immutable and
+ * implement equals() and hashCode() properly for use
+ * in Sets or as Map keys.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class MethodSignature
+{
+ private String _name;
+ private Class _returnType;
+ private Class[] _parameterTypes;
+ private int _hashCode = 0;
+
+ public MethodSignature(Method m)
+ {
+ _name = m.getName();
+ _returnType = m.getReturnType();
+
+ // getParameterTypes() returns a copy for us to keep.
+
+ _parameterTypes = m.getParameterTypes();
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj == null || !(obj instanceof MethodSignature))
+ return false;
+
+ MethodSignature other = (MethodSignature) obj;
+
+ EqualsBuilder builder = new EqualsBuilder();
+ builder.append(_name, other._name);
+ builder.append(_returnType, other._returnType);
+ builder.append(_parameterTypes, other._parameterTypes);
+
+ return builder.isEquals();
+ }
+
+ public int hashCode()
+ {
+ if (_hashCode == 0)
+ {
+ HashCodeBuilder builder = new HashCodeBuilder(253, 97);
+
+ builder.append(_name);
+ builder.append(_returnType);
+ builder.append(_parameterTypes);
+
+ _hashCode = builder.toHashCode();
+ }
+
+ return _hashCode;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+ builder.append("name", _name);
+ builder.append("returnType", _returnType);
+ builder.append("parameterTypes", _parameterTypes);
+
+ return builder.toString();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/NoOpEnhancer.java b/tapestry-framework/src/org/apache/tapestry/enhance/NoOpEnhancer.java
new file mode 100644
index 0000000..d5b9c09
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/NoOpEnhancer.java
@@ -0,0 +1,35 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance;
+
+/**
+ * Does nothing; added to all abstract classes to force them to be subclassed as concrete
+ * (even if no other enhancement takes place).
+ *
+ * @author Howard Lewis Ship
+ * @sincd 3.0
+ */
+public class NoOpEnhancer implements IEnhancer
+{
+
+ /**
+ * Does nothing.
+ */
+ public void performEnhancement(IEnhancedClass enhancedClass)
+ {
+
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java
new file mode 100644
index 0000000..926ae98
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassFabricator.java
@@ -0,0 +1,285 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import javassist.*;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.enhance.CodeGenerationException;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+/**
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class ClassFabricator
+{
+ private static final Log LOG = LogFactory.getLog(ClassFabricator.class);
+
+ /**
+ * The code template for the standard property accessor method.
+ * <p>
+ * Legend: <br>
+ * {0} = property field name <br>
+ */
+ private static final String PROPERTY_ACCESSOR_TEMPLATE = "" +
+ "'{'" +
+ " return {0}; " +
+ "'}'";
+
+ /**
+ * The code template for the standard property mutator method.
+ * <p>
+ * Legend: <br>
+ * {0} = property field name <br>
+ */
+ private static final String PROPERTY_MUTATOR_TEMPLATE = "" +
+ "'{'" +
+ " {0} = $1; " +
+ "'}'";
+
+ /**
+ * The code template for the standard persistent property mutator method.
+ * <p>
+ * Legend: <br>
+ * {0} = property field name <br>
+ * {1} = property name <br>
+ */
+ private static final String PERSISTENT_PROPERTY_MUTATOR_TEMPLATE =
+ "" +
+ "'{'" +
+ " {0} = $1;" +
+ " fireObservedChange(\"{1}\", {0}); " +
+ "'}'";
+
+ private ClassPool _classPool;
+ private CtClass _genClass;
+
+ public ClassFabricator(String className, CtClass parentClass, ClassPool classPool)
+ {
+ _classPool = classPool;
+ _genClass = _classPool.makeClass(className, parentClass);
+ }
+
+ public CtField getField(String fieldName)
+ {
+ try
+ {
+ return _genClass.getField(fieldName);
+ }
+ catch (NotFoundException e)
+ {
+ return null;
+ }
+ }
+
+ public void createField(CtClass fieldType, String fieldName)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating field: " + fieldName);
+
+ try
+ {
+ CtField field = new CtField(fieldType, fieldName, _genClass);
+ _genClass.addField(field);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+ public void createField(CtClass fieldType, String fieldName, String init)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating field: " + fieldName + " with initializer: " + init);
+
+ try
+ {
+ CtField field = new CtField(fieldType, fieldName, _genClass);
+ _genClass.addField(field, init);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+ public CtMethod getMethod(String name, String signature)
+ {
+ try
+ {
+ return _genClass.getMethod(name, signature);
+ }
+ catch (NotFoundException e)
+ {
+ return null;
+ }
+ }
+
+ public void addMethod(CtMethod method) throws CannotCompileException
+ {
+ _genClass.addMethod(method);
+ }
+
+ /**
+ * Constructs an accessor method name.
+ *
+ **/
+
+ public String buildMethodName(String prefix, String propertyName)
+ {
+ StringBuffer result = new StringBuffer(prefix);
+
+ char ch = propertyName.charAt(0);
+
+ result.append(Character.toUpperCase(ch));
+
+ result.append(propertyName.substring(1));
+
+ return result.toString();
+ }
+
+ public CtMethod createMethod(
+ CtClass returnType,
+ String methodName,
+ CtClass[] arguments)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating method: " + methodName);
+
+ CtMethod method = new CtMethod(returnType, methodName, arguments, _genClass);
+
+ return method;
+ }
+
+ public CtMethod createAccessor(
+ CtClass fieldType,
+ String propertyName,
+ String readMethodName)
+ {
+ String methodName =
+ readMethodName == null ? buildMethodName("get", propertyName) : readMethodName;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating accessor: " + methodName);
+
+ CtMethod method = new CtMethod(fieldType, methodName, new CtClass[0], _genClass);
+
+ return method;
+ }
+
+ /**
+ * Creates an accessor (getter) method for the property.
+ *
+ * @param fieldType the return type for the method
+ * @param fieldName the name of the field (not the name of the property)
+ * @param propertyName the name of the property (used to build the name of the method)
+ * @param readMethodName if not null, the name of the method to use
+ *
+ **/
+
+ public void createPropertyAccessor(
+ CtClass fieldType,
+ String fieldName,
+ String propertyName,
+ String readMethodName)
+ {
+ try
+ {
+ String accessorBody =
+ MessageFormat.format(PROPERTY_ACCESSOR_TEMPLATE, new Object[] { fieldName, propertyName });
+
+ CtMethod method = createAccessor(fieldType, propertyName, readMethodName);
+ method.setBody(accessorBody);
+ _genClass.addMethod(method);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+ public CtMethod createMutator(
+ CtClass fieldType,
+ String propertyName)
+ {
+ String methodName = buildMethodName("set", propertyName);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating mutator: " + methodName);
+
+ CtMethod method =
+ new CtMethod(CtClass.voidType, methodName, new CtClass[] { fieldType }, _genClass);
+
+ return method;
+ }
+
+ /**
+ * Creates a mutator (aka "setter") method.
+ *
+ * @param fieldType type of field value (and type of parameter value)
+ * @param fieldName name of field (not property!)
+ * @param propertyName name of property (used to construct method name)
+ * @param isPersistent if true, adds a call to fireObservedChange()
+ *
+ **/
+
+ public void createPropertyMutator(
+ CtClass fieldType,
+ String fieldName,
+ String propertyName,
+ boolean isPersistent)
+ {
+ String bodyTemplate = isPersistent ? PERSISTENT_PROPERTY_MUTATOR_TEMPLATE : PROPERTY_MUTATOR_TEMPLATE;
+ String body = MessageFormat.format(bodyTemplate, new Object[] { fieldName, propertyName });
+
+ try
+ {
+ CtMethod method = createMutator(fieldType, propertyName);
+ method.setBody(body);
+ _genClass.addMethod(method);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+
+ public void commit()
+ {
+ }
+
+ public byte[] getByteCode()
+ {
+ try
+ {
+ return _genClass.toBytecode();
+ }
+ catch (IOException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassMapping.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassMapping.java
new file mode 100644
index 0000000..6c99b83
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/ClassMapping.java
@@ -0,0 +1,102 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
+
+import org.apache.tapestry.enhance.CodeGenerationException;
+
+/**
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class ClassMapping
+{
+
+ /**
+ * Map of type (as Type), keyed on type name.
+ *
+ * This should be kept in synch with ParameterManager, which maintains
+ * a similar list.
+ *
+ **/
+
+ private Map _objectTypeMap = new HashMap();
+ private ClassPool _classPool;
+
+ public ClassMapping(ClassPool classPool)
+ {
+ _classPool = classPool;
+ initialize();
+ }
+
+ protected void initialize()
+ {
+ recordType("boolean", CtClass.booleanType);
+ recordType("short", CtClass.shortType);
+ recordType("int", CtClass.intType);
+ recordType("long", CtClass.longType);
+ recordType("float", CtClass.floatType);
+ recordType("double", CtClass.doubleType);
+ recordType("char", CtClass.charType);
+ recordType("byte", CtClass.byteType);
+
+ try
+ {
+ loadType("boolean[]");
+ loadType("short[]");
+ loadType("int[]");
+ loadType("long[]");
+ loadType("float[]");
+ loadType("double[]");
+ loadType("char[]");
+ loadType("byte[]");
+
+ loadType("java.lang.Object");
+ loadType("java.lang.Object[]");
+
+ loadType("java.lang.String");
+ loadType("java.lang.String[]");
+ }
+ catch (NotFoundException e)
+ {
+ // This exception should not occur since the types above must exist.
+ // Nevertheless...
+ throw new CodeGenerationException(e);
+ }
+ }
+
+ public void loadType(String type) throws NotFoundException
+ {
+ CtClass objectType = _classPool.get(type);
+ _objectTypeMap.put(type, objectType);
+ }
+
+ public void recordType(String type, CtClass objectType)
+ {
+ _objectTypeMap.put(type, objectType);
+ }
+
+ public CtClass getType(String type)
+ {
+ return (CtClass) _objectTypeMap.get(type);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreateAutoParameterEnhancer.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreateAutoParameterEnhancer.java
new file mode 100644
index 0000000..d1bafc7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreateAutoParameterEnhancer.java
@@ -0,0 +1,220 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import javassist.CannotCompileException;
+import javassist.CtClass;
+import javassist.CtMethod;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.enhance.CodeGenerationException;
+import org.apache.tapestry.enhance.IEnhancedClass;
+import org.apache.tapestry.enhance.IEnhancer;
+
+/**
+ * Creates a synthetic property for a
+ * {@link org.apache.tapestry.spec.Direction#AUTO}
+ * parameter.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class CreateAutoParameterEnhancer implements IEnhancer
+{
+ private static final Log LOG = LogFactory.getLog(CreateAutoParameterEnhancer.class);
+
+ /**
+ * The code template for the parameter accessor method.
+ * <p>
+ * Legend: <br>
+ * {0} = readBindingMethodName <br>
+ * {1} = binding value accessor <br>
+ * {2} = cast (if needed) <br>
+ */
+ protected static final String PARAMETER_ACCESSOR_TEMPLATE =
+ ""
+ + "'{'"
+ + " org.apache.tapestry.IBinding binding = {0}();"
+ + " return {2} binding.{1}(); "
+ + "'}'";
+
+ /**
+ * The code template for the parameter mutator method.
+ * <p>
+ * Legend: <br>
+ * {0} = readBindingMethodName <br>
+ * {1} = binding value mutator <br>
+ * {2} = value cast
+ */
+ protected static final String PARAMETER_MUTATOR_TEMPLATE =
+ ""
+ + "'{'"
+ + " org.apache.tapestry.IBinding binding = {0}();"
+ + " binding.{1}({2} $1); "
+ + "'}'";
+
+ /**
+ * The list of types that have accessors and mutators
+ * other than getObject()/setObject.
+ * The key in the Map is the type, the value is the property name in IBinding
+ */
+ private static final Map SPECIAL_BINDING_TYPES = new HashMap();
+
+ static {
+ SPECIAL_BINDING_TYPES.put("boolean", "boolean");
+ SPECIAL_BINDING_TYPES.put("int", "int");
+ SPECIAL_BINDING_TYPES.put("double", "double");
+ SPECIAL_BINDING_TYPES.put("java.lang.String", "string");
+ }
+
+ private static final Map VALUE_CAST_TYPES = new HashMap();
+
+ static {
+ VALUE_CAST_TYPES.put("byte", "($w)");
+ VALUE_CAST_TYPES.put("long", "($w)");
+ VALUE_CAST_TYPES.put("short", "($w)");
+ VALUE_CAST_TYPES.put("char", "($w)");
+ VALUE_CAST_TYPES.put("float", "($w)");
+ }
+
+ private EnhancedClass _enhancedClass;
+ private String _propertyName;
+ private String _parameterName;
+ private CtClass _type;
+ private String _readMethodName;
+
+ public CreateAutoParameterEnhancer(
+ EnhancedClass enhancedClass,
+ String propertyName,
+ String parameterName,
+ CtClass type,
+ String readMethodName)
+ {
+ _enhancedClass = enhancedClass;
+ _propertyName = propertyName;
+ _parameterName = parameterName;
+ _type = type;
+ _readMethodName = readMethodName;
+ }
+
+ public void performEnhancement(IEnhancedClass enhancedClass)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating auto property: " + _propertyName);
+
+ EnhancedClass jaEnhancedClass = (EnhancedClass) enhancedClass;
+ ClassFabricator cf = jaEnhancedClass.getClassFabricator();
+
+ String readBindingMethodName =
+ cf.buildMethodName("get", _parameterName + Tapestry.PARAMETER_PROPERTY_NAME_SUFFIX);
+
+ createReadMethod(cf, readBindingMethodName);
+ createWriteMethod(cf, readBindingMethodName);
+ }
+
+ private String getSpecialBindingType()
+ {
+ String typeName = _type.getName();
+ return (String) SPECIAL_BINDING_TYPES.get(typeName);
+ }
+
+ private String getValueCastType()
+ {
+ String typeName = _type.getName();
+
+ return (String) VALUE_CAST_TYPES.get(typeName);
+ }
+
+ private void createReadMethod(ClassFabricator cf, String readBindingMethodName)
+ {
+ String castToType;
+ String bindingValueAccessor;
+
+ String specialBindingType = getSpecialBindingType();
+ if (specialBindingType != null)
+ {
+ castToType = "";
+ bindingValueAccessor = cf.buildMethodName("get", specialBindingType);
+ }
+ else
+ {
+ castToType = "($r)";
+ bindingValueAccessor = "getObject";
+ }
+
+ String readMethodBody =
+ MessageFormat.format(
+ PARAMETER_ACCESSOR_TEMPLATE,
+ new Object[] { readBindingMethodName, bindingValueAccessor, castToType });
+
+ try
+ {
+ CtMethod method = cf.createAccessor(_type, _propertyName, _readMethodName);
+ method.setBody(readMethodBody);
+ cf.addMethod(method);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+ private void createWriteMethod(ClassFabricator cf, String readBindingMethodName)
+ {
+ String bindingValueAccessor;
+ String valueCast = "";
+
+ String specialBindingType = getSpecialBindingType();
+ if (specialBindingType != null)
+ {
+ bindingValueAccessor = cf.buildMethodName("set", specialBindingType);
+ }
+ else
+ {
+ bindingValueAccessor = "setObject";
+
+ String castForType = getValueCastType();
+
+ if (castForType != null)
+ valueCast = castForType;
+ }
+
+ String writeMethodBody =
+ MessageFormat.format(
+ PARAMETER_MUTATOR_TEMPLATE,
+ new Object[] { readBindingMethodName, bindingValueAccessor, valueCast });
+
+ try
+ {
+ CtMethod method = cf.createMutator(_type, _propertyName);
+ method.setBody(writeMethodBody);
+ cf.addMethod(method);
+ }
+ catch (CannotCompileException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreatePropertyEnhancer.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreatePropertyEnhancer.java
new file mode 100644
index 0000000..ad16879
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/CreatePropertyEnhancer.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import javassist.CtClass;
+
+import org.apache.tapestry.enhance.IEnhancedClass;
+import org.apache.tapestry.enhance.IEnhancer;
+
+/**
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class CreatePropertyEnhancer implements IEnhancer
+{
+ private String _propertyName;
+ private CtClass _propertyType;
+ private boolean _persistent;
+ private String _readMethodName;
+
+ public CreatePropertyEnhancer(String propertyName, CtClass propertyType)
+ {
+ this(propertyName, propertyType, null, false);
+ }
+
+ public CreatePropertyEnhancer(
+ String propertyName,
+ CtClass propertyType,
+ String readMethodName,
+ boolean persistent)
+ {
+ _propertyName = propertyName;
+ _propertyType = propertyType;
+ _readMethodName = readMethodName;
+ _persistent = persistent;
+ }
+
+ public void performEnhancement(IEnhancedClass enhancedClass)
+ {
+ String fieldName = "_$" + _propertyName;
+
+ EnhancedClass jaEnhancedClass = (EnhancedClass) enhancedClass;
+ ClassFabricator classFabricator = jaEnhancedClass.getClassFabricator();
+
+ classFabricator.createField(_propertyType, fieldName);
+ classFabricator.createPropertyAccessor(_propertyType, fieldName, _propertyName, _readMethodName);
+ classFabricator.createPropertyMutator(_propertyType, fieldName, _propertyName, _persistent);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClass.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClass.java
new file mode 100644
index 0000000..eef0adc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClass.java
@@ -0,0 +1,139 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.enhance.BaseEnhancedClass;
+import org.apache.tapestry.enhance.EnhancedClassLoader;
+import org.apache.tapestry.enhance.IEnhancer;
+
+/**
+ * Represents a class to be enhanced using Javassist.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class EnhancedClass extends BaseEnhancedClass
+{
+ private static final Log LOG = LogFactory.getLog(EnhancedClass.class);
+
+ private String _className;
+ private Class _parentClass;
+ private EnhancedClassFactory _classFactory;
+
+ private ClassFabricator _classFabricator = null;
+
+ public EnhancedClass(String className, Class parentClass, EnhancedClassFactory classFactory)
+ {
+ _className = className;
+ _parentClass = parentClass;
+ _classFactory = classFactory;
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#getClassName()
+ */
+ public String getClassName()
+ {
+ return _className;
+ }
+
+ public CtClass getObjectType(String type)
+ {
+ return _classFactory.getObjectType(type);
+ }
+
+ public ClassFabricator getClassFabricator()
+ {
+ if (_classFabricator == null)
+ {
+ CtClass jaParentClass = getObjectType(_parentClass.getName());
+ ClassPool classPool = _classFactory.getClassPool();
+ _classFabricator = new ClassFabricator(_className, jaParentClass, classPool);
+ }
+ return _classFabricator;
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#createProperty(java.lang.String, java.lang.String)
+ */
+ public void createProperty(String propertyName, String propertyType)
+ {
+ createProperty(propertyName, propertyType, null, false);
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#createProperty(java.lang.String, java.lang.String, java.lang.String, boolean)
+ */
+ public void createProperty(
+ String propertyName,
+ String propertyType,
+ String readMethodName,
+ boolean persistent)
+ {
+ IEnhancer enhancer =
+ new CreatePropertyEnhancer(
+ propertyName,
+ getObjectType(propertyType),
+ readMethodName,
+ persistent);
+ addEnhancer(enhancer);
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#createAutoParameter(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void createAutoParameter(
+ String propertyName,
+ String parameterName,
+ String typeClassName,
+ String readMethodName)
+ {
+ IEnhancer enhancer =
+ new CreateAutoParameterEnhancer(
+ this,
+ propertyName,
+ parameterName,
+ getObjectType(typeClassName),
+ readMethodName);
+ addEnhancer(enhancer);
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClass#createEnhancedSubclass()
+ */
+ public Class createEnhancedSubclass()
+ {
+ performEnhancement();
+
+ ClassFabricator cf = getClassFabricator();
+ cf.commit();
+
+ String enhancedClassName = getClassName();
+ byte[] enhancedClassBytes = cf.getByteCode();
+
+ EnhancedClassLoader loader = _classFactory.getEnhancedClassLoader();
+ return loader.defineClass(
+ enhancedClassName,
+ enhancedClassBytes,
+ _parentClass.getProtectionDomain());
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClassFactory.java b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClassFactory.java
new file mode 100644
index 0000000..d858219
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/javassist/EnhancedClassFactory.java
@@ -0,0 +1,134 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.enhance.javassist;
+
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.enhance.CodeGenerationException;
+import org.apache.tapestry.enhance.EnhancedClassLoader;
+import org.apache.tapestry.enhance.IEnhancedClass;
+import org.apache.tapestry.enhance.IEnhancedClassFactory;
+
+/**
+ * This class defines the factory for creation of new Javassist enhanced classes.
+ * There is typically only one object of this class in the system.
+ * Common functionality objects for Javassist enhancement are stored here.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class EnhancedClassFactory implements IEnhancedClassFactory
+{
+ private IResourceResolver _resourceResolver;
+ private EnhancedClassLoader _enhancedClassLoader;
+ private ClassPool _classPool;
+
+ private ClassMapping _typeMap = null;
+
+ public EnhancedClassFactory(IResourceResolver resourceResolver)
+ {
+ _resourceResolver = resourceResolver;
+
+ reset();
+ }
+
+ protected ClassPool createClassPool()
+ {
+ ClassLoader loader = _resourceResolver.getClassLoader();
+
+ // create a new ClassPool and make sure it uses the application resource resolver
+ ClassPool classPool = new ClassPool(null);
+ classPool.insertClassPath(new LoaderClassPath(loader));
+
+ return classPool;
+ }
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClassFactory#reset()
+ */
+ public synchronized void reset()
+ {
+ // create a new class pool and discard the previous one
+ _classPool = createClassPool();
+ _typeMap = new ClassMapping(_classPool);
+
+ ClassLoader loader = _resourceResolver.getClassLoader();
+ _enhancedClassLoader = new EnhancedClassLoader(loader);
+ }
+
+
+ /**
+ * @see org.apache.tapestry.enhance.IEnhancedClassFactory#createEnhancedClass(java.lang.String, java.lang.Class)
+ */
+ public IEnhancedClass createEnhancedClass(String className, Class parentClass)
+ {
+ return new EnhancedClass(className, parentClass, this);
+ }
+
+ public ClassPool getClassPool()
+ {
+ return _classPool;
+ }
+
+ public ClassMapping getClassMapping()
+ {
+ return _typeMap;
+ }
+
+ /**
+ * Given the java class, returns the equivalent {@link CtClass type}. In addition,
+ * knows about scalar types, arrays of scalar types, java.lang.Object[] and
+ * java.lang.String[].
+ *
+ **/
+
+ public CtClass getObjectType(String type)
+ {
+
+ synchronized (this) {
+ CtClass result = getClassMapping().getType(type);
+
+ if (result == null)
+ {
+ try
+ {
+ result = _classPool.get(type);
+ getClassMapping().recordType(type, result);
+ }
+ catch (NotFoundException e)
+ {
+ throw new CodeGenerationException(e);
+ }
+ }
+ return result;
+ }
+
+ }
+
+
+ /**
+ * @return The class loader to be used to create the enhanced class
+ */
+ public EnhancedClassLoader getEnhancedClassLoader()
+ {
+ return _enhancedClassLoader;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/enhance/package.html b/tapestry-framework/src/org/apache/tapestry/enhance/package.html
new file mode 100644
index 0000000..390492b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/enhance/package.html
@@ -0,0 +1,14 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+Classes used for performing dynamic bytecode enhancement of component and page classes.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/event/ChangeObserver.java b/tapestry-framework/src/org/apache/tapestry/event/ChangeObserver.java
new file mode 100644
index 0000000..8f7b632
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/ChangeObserver.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+/**
+ * May observe changes in an object's properties. This is a "weak" variation
+ * on JavaBean's style bound properties. It is used when there will be at most
+ * a single listener on property changes, and that the listener is not interested
+ * in the old value.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ChangeObserver
+{
+ /**
+ * Sent when the observed object changes a property. The event identifies
+ * the object, the property and the new value.
+ *
+ **/
+
+ public void observeChange(ObservedChangeEvent event);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/event/ObservedChangeEvent.java b/tapestry-framework/src/org/apache/tapestry/event/ObservedChangeEvent.java
new file mode 100644
index 0000000..3adcf22
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/ObservedChangeEvent.java
@@ -0,0 +1,143 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+import java.util.EventObject;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Event which describes a change to a particular {@link IComponent}.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public class ObservedChangeEvent extends EventObject
+{
+ private IComponent _component;
+ private String _propertyName;
+ private Object _newValue;
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, char newValue)
+ {
+ this(component, propertyName, new Character(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, byte newValue)
+ {
+ this(component, propertyName, new Byte(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, short newValue)
+ {
+ this(component, propertyName, new Short(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, int newValue)
+ {
+ this(component, propertyName, new Integer(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, long newValue)
+ {
+ this(component, propertyName, new Long(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, double newValue)
+ {
+ this(component, propertyName, new Double(newValue));
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, float newValue)
+ {
+ this(component, propertyName, new Float(newValue));
+ }
+
+ /**
+ * Creates the event. The new value must be null, or be a serializable object.
+ * (It is declared as Object as a concession to the Java 2 collections framework, where
+ * the implementations are serializable but the interfaces (Map, List, etc.) don't
+ * extend Serializable ... so we wait until runtime to check).
+ *
+ * @param component The component (not necessarily a page) whose property changed.
+ * @param propertyName the name of the property which was changed.
+ * @param newValue The new value of the property.
+ *
+ * @throws IllegalArgumentException if propertyName is null, or
+ * if the new value is not serializable
+ *
+ **/
+
+ public ObservedChangeEvent(IComponent component, String propertyName, Object newValue)
+ {
+ super(component);
+
+ if (propertyName == null)
+ throw new IllegalArgumentException(
+ Tapestry.format("ObservedChangeEvent.null-property-name", component));
+
+ _component = component;
+ _propertyName = propertyName;
+ _newValue = newValue;
+ }
+
+ /**
+ * @deprecated To be removed in 3.1. Use {@link #ObservedChangeEvent(IComponent, String, Object)} instead.
+ */
+ public ObservedChangeEvent(IComponent component, String propertyName, boolean newValue)
+ {
+ this(component, propertyName, newValue ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ public IComponent getComponent()
+ {
+ return _component;
+ }
+
+ public Object getNewValue()
+ {
+ return _newValue;
+ }
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/event/PageDetachListener.java b/tapestry-framework/src/org/apache/tapestry/event/PageDetachListener.java
new file mode 100644
index 0000000..f9c79b1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/PageDetachListener.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+import java.util.EventListener;
+
+/**
+ * An interface for objects that want to know when the end of the
+ * request cycle occurs, so that any resources that should be limited
+ * to just one request cycle can be released.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ **/
+
+public interface PageDetachListener extends EventListener
+{
+ /**
+ * Invoked by the page from its {@link org.apache.tapestry.IPage#detach()} method.
+ *
+ **/
+
+ public void pageDetached(PageEvent event);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/event/PageEvent.java b/tapestry-framework/src/org/apache/tapestry/event/PageEvent.java
new file mode 100644
index 0000000..eaa3335
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/PageEvent.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+import java.util.EventObject;
+
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Encapsulates information related to the page listener
+ * interfaces.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public class PageEvent extends EventObject
+{
+ private transient IPage page;
+ private transient IRequestCycle requestCycle;
+
+ /**
+ * Constructs a new instance of the event. The
+ * {@link EventObject#getSource()} of the event will
+ * be the {@link IPage}.
+ *
+ **/
+
+ public PageEvent(IPage page, IRequestCycle cycle)
+ {
+ super(page);
+
+ this.page = page;
+ this.requestCycle = cycle;
+ }
+
+ public IPage getPage()
+ {
+ return page;
+ }
+
+ public IRequestCycle getRequestCycle()
+ {
+ return requestCycle;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/event/PageRenderListener.java b/tapestry-framework/src/org/apache/tapestry/event/PageRenderListener.java
new file mode 100644
index 0000000..1a2fd99
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/PageRenderListener.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+import java.util.EventListener;
+
+/**
+ * An object that listens to page events. The {@link org.apache.tapestry.IPage page} generates
+ * events before and after rendering a response. These events also occur before and
+ * after a form rewinds.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public interface PageRenderListener extends EventListener
+{
+ /**
+ * Invoked before just before the page renders a response. This provides
+ * listeners with a last chance to initialize themselves for the render.
+ * This initialization can include modifying peristent page properties.
+ *
+ *
+ **/
+
+ public void pageBeginRender(PageEvent event);
+
+ /**
+ * Invoked after a successful render of the page.
+ * Allows objects to release any resources they needed during the
+ * the render.
+ *
+ * @see org.apache.tapestry.AbstractComponent#pageEndRender(PageEvent)
+ *
+ **/
+
+ public void pageEndRender(PageEvent event);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/event/PageValidateListener.java b/tapestry-framework/src/org/apache/tapestry/event/PageValidateListener.java
new file mode 100644
index 0000000..2c3087e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/PageValidateListener.java
@@ -0,0 +1,38 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.event;
+
+import java.util.EventListener;
+
+/**
+ * An interface for objects that want to take part in the validation of the page.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ **/
+
+public interface PageValidateListener extends EventListener
+{
+ /**
+ * Invoked by the page from its
+ * {@link org.apache.tapestry.IPage#validate(org.apache.tapestry.IRequestCycle)} method.
+ *
+ * <p>May throw a {@link org.apache.tapestry.PageRedirectException}, to redirect the user
+ * to an appropriate part of the system (such as, a login page).
+ **/
+
+ public void pageValidate(PageEvent event);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/event/package.html b/tapestry-framework/src/org/apache/tapestry/event/package.html
new file mode 100644
index 0000000..70e3206
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/event/package.html
@@ -0,0 +1,28 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+Defines events and listener interfaces for Tapestry.
+
+<p>
+{@link org.apache.tapestry.event.ChangeObserver}
+and
+{@link org.apache.tapestry.event.ObservedChangeEvent}
+are used to communicate changes in persistent properties
+from pages and components to page recorders.
+
+<p>
+The remaining interfaces
+{@link org.apache.tapestry.event.PageDetachListener} and
+{@link org.apache.tapestry.event.PageRenderListener} allow
+objects to know about key lifecycle events regarding
+a page.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/AbstractFormComponent.java b/tapestry-framework/src/org/apache/tapestry/form/AbstractFormComponent.java
new file mode 100644
index 0000000..134dfe3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/AbstractFormComponent.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.valid.IValidationDelegate;
+
+/**
+ * A base class for building components that correspond to HTML form elements.
+ * All such components must be wrapped (directly or indirectly) by
+ * a {@link Form} component.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ * @since 1.0.3
+ *
+ **/
+
+public abstract class AbstractFormComponent extends AbstractComponent implements IFormComponent
+{
+ /**
+ * Returns the {@link Form} wrapping this component. Invokes
+ * {@link #setForm(IForm)} (so that the component may know, later, what the
+ * form is). Also, if the form has a delegate,
+ * then {@link IValidationDelegate#setFormComponent(IFormComponent)} is invoked.
+ *
+ * @throws ApplicationRuntimeException if the component is not wrapped by a {@link Form}.
+ *
+ **/
+
+ public IForm getForm(IRequestCycle cycle)
+ {
+ IForm result = Form.get(cycle);
+
+ if (result == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractFormComponent.must-be-contained-by-form"),
+ this,
+ null,
+ null);
+
+ setForm(result);
+
+ IValidationDelegate delegate = result.getDelegate();
+
+ if (delegate != null)
+ delegate.setFormComponent(this);
+
+ return result;
+ }
+
+ public abstract IForm getForm();
+ public abstract void setForm(IForm form);
+
+ public abstract String getName();
+ public abstract void setName(String name);
+
+ /**
+ * Implemented in some subclasses to provide a display name (suitable
+ * for presentation to the user as a label or error message). This implementation
+ * return null.
+ *
+ **/
+
+ public String getDisplayName()
+ {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/AbstractTextField.java b/tapestry-framework/src/org/apache/tapestry/form/AbstractTextField.java
new file mode 100644
index 0000000..37cd8e4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/AbstractTextField.java
@@ -0,0 +1,127 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Base class for implementing various types of text input fields.
+ * This includes {@link TextField} and
+ * {@link org.apache.tapestry.valid.ValidField}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ *
+ **/
+
+public abstract class AbstractTextField extends AbstractFormComponent
+{
+ /**
+ * Renders the form element, or responds when the form containing the element
+ * is submitted (by checking {@link Form#isRewinding()}.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String value;
+
+ IForm form = getForm(cycle);
+
+ // It isn't enough to know whether the cycle in general is rewinding, need to know
+ // specifically if the form which contains this component is rewinding.
+
+ boolean rewinding = form.isRewinding();
+
+ // If the cycle is rewinding, but the form containing this field is not,
+ // then there's no point in doing more work.
+
+ if (!rewinding && cycle.isRewinding())
+ return;
+
+ // Used whether rewinding or not.
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ if (!isDisabled())
+ {
+ value = cycle.getRequestContext().getParameter(name);
+
+ updateValue(value);
+ }
+
+ return;
+ }
+
+ writer.beginEmpty("input");
+
+ writer.attribute("type", isHidden() ? "password" : "text");
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ writer.attribute("name", name);
+
+ value = readValue();
+ if (value != null)
+ writer.attribute("value", value);
+
+ renderInformalParameters(writer, cycle);
+
+ beforeCloseTag(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ /**
+ * Invoked from {@link #render(IMarkupWriter, IRequestCycle)}
+ * just before the tag is closed. This implementation does nothing,
+ * subclasses may override.
+ *
+ **/
+
+ protected void beforeCloseTag(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ // Do nothing.
+ }
+
+ /**
+ * Invoked by {@link #render(IMarkupWriter writer, IRequestCycle cycle)}
+ * when a value is obtained from the
+ * {@link javax.servlet.http.HttpServletRequest}.
+ *
+ **/
+
+ abstract protected void updateValue(String value);
+
+ /**
+ * Invoked by {@link #render(IMarkupWriter writer, IRequestCycle cycle)}
+ * when rendering a response.
+ *
+ * @return the current value for the field, as a String, or null.
+ **/
+
+ abstract protected String readValue();
+
+ public abstract boolean isHidden();
+
+ public abstract boolean isDisabled();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Button.java b/tapestry-framework/src/org/apache/tapestry/form/Button.java
new file mode 100644
index 0000000..190e6f3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Button.java
@@ -0,0 +1,73 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implements a component that manages an HTML <input type=button> form element.
+ *
+ * [<a href="../../../../../ComponentReference/Button.html">Component Reference</a>]
+ *
+ * <p>This component is useful for attaching JavaScript onclick event handlers.
+ *
+ * @author Howard Lewis Ship
+ * @author Paul Geerts
+ * @author Malcolm Edgar
+ * @version $Id$
+ **/
+
+public abstract class Button extends AbstractFormComponent
+{
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ return;
+ }
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "button");
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ {
+ writer.attribute("disabled", "disabled");
+ }
+
+ String label = getLabel();
+
+ if (label != null)
+ {
+ writer.attribute("value", label);
+ }
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ public abstract String getLabel();
+
+ public abstract boolean isDisabled();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Button.jwc b/tapestry-framework/src/org/apache/tapestry/form/Button.jwc
new file mode 100644
index 0000000..53b3dad
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Button.jwc
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Button" allow-body="no">
+
+ <description>
+ Creates a labeled button within a form.
+ </description>
+
+ <parameter name="label" type="java.lang.String" direction="in"/>
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="type"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Checkbox.java b/tapestry-framework/src/org/apache/tapestry/form/Checkbox.java
new file mode 100644
index 0000000..9c47ee5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Checkbox.java
@@ -0,0 +1,87 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implements a component that manages an HTML <input type=checkbox>
+ * form element.
+ *
+ * [<a href="../../../../../ComponentReference/Checkbox.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Checkbox extends AbstractFormComponent
+{
+ /**
+ * Renders the form elements, or responds when the form containing the element
+ * is submitted (by checking {@link Form#isRewinding()}.
+ *
+ * <p>In traditional HTML, many checkboxes would have the same name but different values.
+ * Under Tapestry, it makes more sense to have different names and a fixed value.
+ * For a checkbox, we only care about whether the name appears as a request parameter.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ // Used whether rewinding or not.
+
+ String name = form.getElementId(this);
+
+ if (form.isRewinding())
+ {
+ String value = cycle.getRequestContext().getParameter(name);
+
+ setSelected((value != null));
+
+ return;
+ }
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "checkbox");
+
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ if (isSelected())
+ writer.attribute("checked", "checked");
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ public abstract boolean isDisabled();
+
+ /** @since 2.2 **/
+
+ public abstract boolean isSelected();
+
+ /** @since 2.2 **/
+
+ public abstract void setSelected(boolean selected);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Checkbox.jwc b/tapestry-framework/src/org/apache/tapestry/form/Checkbox.jwc
new file mode 100644
index 0000000..4f1b307
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Checkbox.jwc
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Checkbox" allow-body="no">
+
+ <description>
+ Implements a checkbox within a Form.
+ </description>
+
+ <parameter name="selected"
+ type="boolean"
+ required="yes"
+ direction="form">
+ <description>
+ The property read and updated by the Checkbox.
+ </description>
+ </parameter>
+
+ <parameter name="disabled" type="boolean" direction="in">
+ <description>
+ If true, then the checkbox will be disabled and any input from the checkbox
+ will be ignored.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="checked"/>
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/DatePicker.java b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.java
new file mode 100644
index 0000000..dcf1de8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.java
@@ -0,0 +1,250 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IScriptSource;
+import org.apache.tapestry.html.Body;
+
+/**
+ * Provides a Form <tt>java.util.Date</tt> field component for selecting dates.
+ *
+ * [<a href="../../../../../ComponentReference/DatePicker.html">Component Reference</a>]
+ *
+ * @author Paul Geerts
+ * @author Malcolm Edgar
+ * @version $Id$
+ * @since 2.2
+ *
+ */
+
+public abstract class DatePicker extends AbstractFormComponent
+{
+ public abstract String getFormat();
+
+ public abstract Date getValue();
+
+ public abstract void setValue(Date value);
+
+ public abstract boolean isDisabled();
+
+ public abstract boolean getIncludeWeek();
+
+ public abstract IAsset getIcon();
+
+ private IScript _script;
+
+ private static final String SYM_NAME = "name";
+ private static final String SYM_FORMNAME = "formName";
+ private static final String SYM_MONTHNAMES = "monthNames";
+ private static final String SYM_SHORT_MONTHNAMES = "shortMonthNames";
+ private static final String SYM_WEEKDAYNAMES = "weekDayNames";
+ private static final String SYM_SHORT_WEEKDAYNAMES = "shortWeekDayNames";
+ private static final String SYM_FIRSTDAYINWEEK = "firstDayInWeek";
+ private static final String SYM_MINDAYSINFIRSTWEEK = "minimalDaysInFirstWeek";
+ private static final String SYM_FORMAT = "format";
+ private static final String SYM_INCL_WEEK = "includeWeek";
+ private static final String SYM_VALUE = "value";
+ private static final String SYM_BUTTONONCLICKHANDLER = "buttonOnclickHandler";
+
+ // Output symbol
+
+ private static final String SYM_BUTTONNAME = "buttonName";
+
+ protected void finishLoad()
+ {
+ IEngine engine = getPage().getEngine();
+ IScriptSource source = engine.getScriptSource();
+
+ IResourceLocation location =
+ getSpecification().getSpecificationLocation().getRelativeLocation("DatePicker.script");
+
+ _script = source.getScript(location);
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ String name = form.getElementId(this);
+
+ String format = getFormat();
+
+ if (format == null)
+ format = "dd MMM yyyy";
+
+ SimpleDateFormat formatter = new SimpleDateFormat(format, getPage().getLocale());
+
+ boolean disabled = isDisabled();
+
+ if (!cycle.isRewinding())
+ {
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("must-be-contained-by-body", "DatePicker"),
+ this,
+ null,
+ null);
+
+ Locale locale = getPage().getLocale();
+ DateFormatSymbols dfs = new DateFormatSymbols(locale);
+ Calendar cal = Calendar.getInstance(locale);
+
+ Date value = getValue();
+
+ Map symbols = new HashMap();
+
+ symbols.put(SYM_NAME, name);
+ symbols.put(SYM_FORMAT, format);
+ symbols.put(SYM_INCL_WEEK, getIncludeWeek() ? Boolean.TRUE : Boolean.FALSE);
+
+ symbols.put(SYM_MONTHNAMES, makeStringList(dfs.getMonths(), 0, 12));
+ symbols.put(SYM_SHORT_MONTHNAMES, makeStringList(dfs.getShortMonths(), 0, 12));
+ symbols.put(SYM_WEEKDAYNAMES, makeStringList(dfs.getWeekdays(), 1, 8));
+ symbols.put(SYM_SHORT_WEEKDAYNAMES, makeStringList(dfs.getShortWeekdays(), 1, 8));
+ symbols.put(SYM_FIRSTDAYINWEEK, new Integer(cal.getFirstDayOfWeek() - 1));
+ symbols.put(SYM_MINDAYSINFIRSTWEEK, new Integer(cal.getMinimalDaysInFirstWeek()));
+ symbols.put(SYM_FORMNAME, form.getName());
+ symbols.put(SYM_VALUE, value);
+
+ _script.execute(cycle, body, symbols);
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "text");
+ writer.attribute("name", name);
+ writer.attribute("title", formatter.toLocalizedPattern());
+
+ if (value != null)
+ writer.attribute("value", formatter.format(value));
+
+ if (disabled)
+ writer.attribute("disabled", "disabled");
+
+ renderInformalParameters(writer, cycle);
+
+ writer.printRaw(" ");
+
+ if (!disabled)
+ {
+ writer.begin("a");
+ writer.attribute("href", (String) symbols.get(SYM_BUTTONONCLICKHANDLER));
+ }
+
+ IAsset icon = getIcon();
+
+ writer.beginEmpty("img");
+ writer.attribute("src", icon.buildURL(cycle));
+ writer.attribute("border", 0);
+
+ if (!disabled)
+ writer.end(); // <a>
+
+ }
+
+ if (form.isRewinding())
+ {
+ if (disabled)
+ return;
+
+ String textValue = cycle.getRequestContext().getParameter(name);
+
+ if (Tapestry.isBlank(textValue))
+ return;
+
+ try
+ {
+ Date value = formatter.parse(textValue);
+
+ setValue(value);
+ }
+ catch (ParseException ex)
+ {
+ }
+ }
+
+ }
+
+ /**
+ * Create a list of quoted strings. The list is suitable for
+ * initializing a JavaScript array.
+ */
+ private String makeStringList(String[] a, int offset, int length)
+ {
+ StringBuffer b = new StringBuffer();
+ for (int i = offset; i < length; i++)
+ {
+ // JavaScript is sensitive to some UNICODE characters. So for
+ // the sake of simplicity, we just escape everything
+ b.append('"');
+ char[] ch = a[i].toCharArray();
+ for (int j = 0; j < ch.length; j++)
+ {
+ if (ch[j] < 128)
+ {
+ b.append(ch[j]);
+ }
+ else
+ {
+ b.append(escape(ch[j]));
+ }
+ }
+
+ b.append('"');
+ if (i < length - 1)
+ {
+ b.append(", ");
+ }
+ }
+ return b.toString();
+
+ }
+
+ /**
+ * Create an escaped Unicode character
+ * @param c
+ * @return The unicode character in escaped string form
+ */
+ private static String escape(char c)
+ {
+ StringBuffer b = new StringBuffer();
+ for (int i = 0; i < 4; i++)
+ {
+ b.append(Integer.toHexString(c & 0x000F).toUpperCase());
+ c >>>= 4;
+ }
+ b.append("u\\");
+ return b.reverse().toString();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/form/DatePicker.js b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.js
new file mode 100644
index 0000000..c74d6ce
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.js
@@ -0,0 +1,992 @@
+//
+// calendar -- a javascript date picker designed for easy localization.
+//
+// $Id$
+//
+//
+// Author: Per Norrman (pernorrman@telia.com)
+//
+// Based on Tapestry 2.3-beta1 Datepicker by Paul Geerts
+//
+// Thanks to:
+// Vladimir [vyc@quorus-ms.ru] for fixing the IE6 zIndex problem.
+//
+// The normal setup would be to have one text field for displaying the
+// selected date, and one button to show/hide the date picker control.
+// This is the recommended javascript code:
+//
+// <script language="javascript">
+// var cal;
+//
+// function init() {
+// cal = new Calendar();
+// cal.setIncludeWeek(true);
+// cal.setFormat("yyyy-MM-dd");
+// cal.setMonthNames(.....);
+// cal.setShortMonthNames(....);
+// cal.create();
+//
+// document.form.button1.onclick = function() {
+// cal.toggle(document.form.button1);
+// }
+// cal.onchange = function() {
+// document.form.textfield1.value = cal.formatDate();
+// }
+// }
+// </script>
+//
+// The init function is invoked when the body is loaded.
+//
+//
+
+function Calendar(date) {
+ if (arguments.length == 0) {
+ this._currentDate = new Date();
+ this._selectedDate = null;
+ }
+ else {
+ this._currentDate = new Date(date);
+ this._selectedDate = new Date(date);
+ }
+
+ // Accumulated days per month, for normal and for leap years.
+ // Used in week number calculations.
+ Calendar.NUM_DAYS = [0,31,59,90,120,151,181,212,243,273,304,334];
+
+ Calendar.LEAP_NUM_DAYS = [0,31,60,91,121,152,182,213,244,274,305,335];
+
+
+ this._bw = new bw_check();
+ this._showing = false;
+ this._includeWeek = false;
+ this._hideOnSelect = true;
+ this._alwaysVisible = false;
+
+ this._dateSlot = new Array(42);
+ this._weekSlot = new Array(6);
+
+ this._firstDayOfWeek = 1;
+ this._minimalDaysInFirstWeek = 4;
+
+ this._monthNames = [
+ "January", "February", "March", "April",
+ "May", "June", "July", "August",
+ "September", "October", "November", "December"
+ ];
+
+ this._shortMonthNames = [
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec"
+ ];
+
+ // Week days start with Sunday=0, ... Saturday=6
+ this._weekDayNames = [
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+ ];
+
+ this._shortWeekDayNames =
+ ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];
+
+ this._defaultFormat = "yyyy-MM-dd";
+
+ this._format = this._defaultFormat;
+
+ this._calDiv = null;
+
+
+}
+
+/**
+ * CREATE the Calendar DOM element
+ */
+Calendar.prototype.create = function() {
+ var div;
+ var table;
+ var tbody;
+ var tr;
+ var td;
+ var dp = this;
+
+ // Create the top-level div element
+ this._calDiv = document.createElement("div");
+ this._calDiv.className = "calendar";
+ this._calDiv.style.position = "absolute";
+ this._calDiv.style.display = "none";
+ this._calDiv.style.border = "1px solid WindowText";
+ this._calDiv.style.textAlign = "center";
+ this._calDiv.style.background = "Window";
+ this._calDiv.style.zIndex = "400";
+
+
+ // header div
+ div = document.createElement("div");
+ div.className = "calendarHeader";
+ div.style.background = "ActiveCaption";
+ div.style.padding = "3px";
+ div.style.borderBottom = "1px solid WindowText";
+ this._calDiv.appendChild(div);
+
+ table = document.createElement("table");
+ table.style.cellSpacing = 0;
+ div.appendChild(table);
+
+ tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+
+ tr = document.createElement("tr");
+ tbody.appendChild(tr);
+
+ // Previous Month Button
+ td = document.createElement("td");
+ this._previousMonth = document.createElement("button");
+ this._previousMonth.className = "prevMonthButton"
+ this._previousMonth.appendChild(document.createTextNode("<<"));
+ //this._previousMonth.appendChild(document.createTextNode(String.fromCharCode(9668)));
+ td.appendChild(this._previousMonth);
+ tr.appendChild(td);
+
+
+
+ //
+ // Create the month drop down
+ //
+ td = document.createElement("td");
+ td.className = "labelContainer";
+ tr.appendChild(td);
+ this._monthSelect = document.createElement("select");
+ for (var i = 0 ; i < this._monthNames.length ; i++) {
+ var opt = document.createElement("option");
+ opt.innerHTML = this._monthNames[i];
+ opt.value = i;
+ if (i == this._currentDate.getMonth()) {
+ opt.selected = true;
+ }
+ this._monthSelect.appendChild(opt);
+ }
+ td.appendChild(this._monthSelect);
+
+
+ //
+ // Create the year drop down
+ //
+ td = document.createElement("td");
+ td.className = "labelContainer";
+ tr.appendChild(td);
+ this._yearSelect = document.createElement("select");
+ for(var i=1920; i < 2050; ++i) {
+ var opt = document.createElement("option");
+ opt.innerHTML = i;
+ opt.value = i;
+ if (i == this._currentDate.getFullYear()) {
+ opt.selected = false;
+ }
+ this._yearSelect.appendChild(opt);
+ }
+ td.appendChild(this._yearSelect);
+
+
+ td = document.createElement("td");
+ this._nextMonth = document.createElement("button");
+ this._nextMonth.appendChild(document.createTextNode(">>"));
+ //this._nextMonth.appendChild(document.createTextNode(String.fromCharCode(9654)));
+ this._nextMonth.className = "nextMonthButton";
+ td.appendChild(this._nextMonth);
+ tr.appendChild(td);
+
+ // Calendar body
+ div = document.createElement("div");
+ div.className = "calendarBody";
+ this._calDiv.appendChild(div);
+ this._table = div;
+
+ // Create the inside of calendar body
+
+ var text;
+ table = document.createElement("table");
+ //table.style.width="100%";
+ table.className = "grid";
+ table.style.font = "small-caption";
+ table.style.fontWeight = "normal";
+ table.style.textAalign = "center";
+ table.style.color = "WindowText";
+ table.style.cursor = "default";
+ table.cellPadding = "3";
+ table.cellSpacing = "0";
+
+ div.appendChild(table);
+ var thead = document.createElement("thead");
+ table.appendChild(thead);
+ tr = document.createElement("tr");
+ thead.appendChild(tr);
+
+ // weekdays header
+ if (this._includeWeek) {
+ td = document.createElement("th");
+ text = document.createTextNode("w");
+ td.appendChild(text);
+ td.className = "weekNumberHead";
+ td.style.textAlign = "left";
+ tr.appendChild(td);
+ }
+ for(i=0; i < 7; ++i) {
+ td = document.createElement("th");
+ text = document.createTextNode(this._shortWeekDayNames[(i+this._firstDayOfWeek)%7]);
+ td.appendChild(text);
+ td.className = "weekDayHead";
+ td.style.fontWeight = "bold";
+ td.style.borderBottom = "1px solid WindowText";
+ tr.appendChild(td);
+ }
+
+ // Date grid
+ tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+
+ for(week=0; week<6; ++week) {
+ tr = document.createElement("tr");
+ tbody.appendChild(tr);
+
+ if (this._includeWeek) {
+ td = document.createElement("td");
+ td.className = "weekNumber";
+ td.style.fontWeight = "normal";
+ td.style.borderRight = "1px solid WindowText";
+ td.style.textAlign = "left";
+ text = document.createTextNode(String.fromCharCode(160));
+ td.appendChild(text);
+ //setCursor(td);
+ td.align="center";
+ tr.appendChild(td);
+ var tmp = new Object();
+ tmp.tag = "WEEK";
+ tmp.value = -1;
+ tmp.data = text;
+ this._weekSlot[week] = tmp;
+ }
+
+ for(day=0; day<7; ++day) {
+ td = document.createElement("td");
+ text = document.createTextNode(String.fromCharCode(160));
+ td.appendChild(text);
+ setCursor(td);
+ td.align="center";
+ td.style.fontWeight="normal";
+
+ tr.appendChild(td);
+ var tmp = new Object();
+ tmp.tag = "DATE";
+ tmp.value = -1;
+ tmp.data = text;
+ this._dateSlot[(week*7)+day] = tmp;
+
+ }
+ }
+
+ // Calendar Footer
+ div = document.createElement("div");
+ div.className = "calendarFooter";
+ this._calDiv.appendChild(div);
+
+ table = document.createElement("table");
+ //table.style.width="100%";
+ table.className = "footerTable";
+ table.cellSpacing = 0;
+ div.appendChild(table);
+
+ tbody = document.createElement("tbody");
+ table.appendChild(tbody);
+
+ tr = document.createElement("tr");
+ tbody.appendChild(tr);
+
+ //
+ // The TODAY button
+ //
+ td = document.createElement("td");
+ this._todayButton = document.createElement("button");
+ var today = new Date();
+ var buttonText = today.getDate() + " " + this._monthNames[today.getMonth()] + ", " + today.getFullYear();
+ this._todayButton.appendChild(document.createTextNode(buttonText));
+ td.appendChild(this._todayButton);
+ tr.appendChild(td);
+
+ //
+ // The CLEAR button
+ //
+ td = document.createElement("td");
+ this._clearButton = document.createElement("button");
+ var today = new Date();
+ buttonText = "Clear";
+ this._clearButton.appendChild(document.createTextNode(buttonText));
+ td.appendChild(this._clearButton);
+ tr.appendChild(td);
+
+
+ this._update();
+ this._updateHeader();
+
+
+
+ // IE55+ extension
+ this._previousMonth.hideFocus = true;
+ this._nextMonth.hideFocus = true;
+ this._todayButton.hideFocus = true;
+ // end IE55+ extension
+
+ // hook up events
+ // buttons
+ this._previousMonth.onclick = function () {
+ dp.prevMonth();
+ };
+
+ this._nextMonth.onclick = function () {
+ dp.nextMonth();
+ };
+
+ this._todayButton.onclick = function () {
+ dp.setSelectedDate(new Date());
+ dp.hide();
+ };
+
+ this._clearButton.onclick = function () {
+ dp.clearSelectedDate();
+ dp.hide();
+ };
+
+
+ this._calDiv.onselectstart = function () {
+ return false;
+ };
+
+ this._table.onclick = function (e) {
+ // find event
+ if (e == null) e = document.parentWindow.event;
+
+ // find td
+ var el = e.target != null ? e.target : e.srcElement;
+ while (el.nodeType != 1)
+ el = el.parentNode;
+ while (el != null && el.tagName && el.tagName.toLowerCase() != "td")
+ el = el.parentNode;
+
+ // if no td found, return
+ if (el == null || el.tagName == null || el.tagName.toLowerCase() != "td")
+ return;
+
+ var d = new Date(dp._currentDate);
+ var n = Number(el.firstChild.data);
+ if (isNaN(n) || n <= 0 || n == null)
+ return;
+
+ if (el.className == "weekNumber")
+ return;
+
+ d.setDate(n);
+ dp.setSelectedDate(d);
+
+ if (!dp._alwaysVisible && dp._hideOnSelect) {
+ dp.hide();
+ }
+
+ };
+
+
+ this._calDiv.onkeydown = function (e) {
+ if (e == null) e = document.parentWindow.event;
+ var kc = e.keyCode != null ? e.keyCode : e.charCode;
+
+ if(kc == 13) {
+ var d = new Date(dp._currentDate).valueOf();
+ dp.setSelectedDate(d);
+
+ if (!dp._alwaysVisible && dp._hideOnSelect) {
+ dp.hide();
+ }
+ return false;
+ }
+
+
+ if (kc < 37 || kc > 40) return true;
+
+ var d = new Date(dp._currentDate).valueOf();
+ if (kc == 37) // left
+ d -= 24 * 60 * 60 * 1000;
+ else if (kc == 39) // right
+ d += 24 * 60 * 60 * 1000;
+ else if (kc == 38) // up
+ d -= 7 * 24 * 60 * 60 * 1000;
+ else if (kc == 40) // down
+ d += 7 * 24 * 60 * 60 * 1000;
+
+ dp.setCurrentDate(new Date(d));
+ return false;
+ }
+
+ // ie6 extension
+ this._calDiv.onmousewheel = function (e) {
+ if (e == null) e = document.parentWindow.event;
+ var n = - e.wheelDelta / 120;
+ var d = new Date(dp._currentDate);
+ var m = d.getMonth() + n;
+ d.setMonth(m);
+
+
+ dp.setCurrentDate(d);
+
+ return false;
+ }
+
+ this._monthSelect.onchange = function(e) {
+ if (e == null) e = document.parentWindow.event;
+ e = getEventObject(e);
+ dp.setMonth(e.value);
+ }
+
+ this._monthSelect.onclick = function(e) {
+ if (e == null) e = document.parentWindow.event;
+ e = getEventObject(e);
+ e.cancelBubble = true;
+ }
+
+ this._yearSelect.onchange = function(e) {
+ if (e == null) e = document.parentWindow.event;
+ e = getEventObject(e);
+ dp.setYear(e.value);
+ }
+
+
+ document.body.appendChild(this._calDiv);
+
+
+ return this._calDiv;
+}
+
+Calendar.prototype._update = function() {
+
+
+ // Calculate the number of days in the month for the selected date
+ var date = this._currentDate;
+ var today = toISODate(new Date());
+
+
+ var selected = "";
+ if (this._selectedDate != null) {
+ selected = toISODate(this._selectedDate);
+ }
+ var current = toISODate(this._currentDate);
+ var d1 = new Date(date.getFullYear(), date.getMonth(), 1);
+ var d2 = new Date(date.getFullYear(), date.getMonth()+1, 1);
+ var monthLength = Math.round((d2 - d1) / (24 * 60 * 60 * 1000));
+
+ // Find out the weekDay index for the first of this month
+ var firstIndex = (d1.getDay() - this._firstDayOfWeek) % 7 ;
+ if (firstIndex < 0) {
+ firstIndex += 7;
+ }
+
+ var index = 0;
+ while (index < firstIndex) {
+ this._dateSlot[index].value = -1;
+ this._dateSlot[index].data.data = String.fromCharCode(160);
+ this._dateSlot[index].data.parentNode.className = "";
+ this._dateSlot[index].data.parentNode.style.fontWeight = "normal";
+ this._dateSlot[index].data.parentNode.style.border= "none";
+ index++;
+ }
+
+ for (i = 1; i <= monthLength; i++, index++) {
+ this._dateSlot[index].value = i;
+ this._dateSlot[index].data.data = i;
+ this._dateSlot[index].data.parentNode.className = "";
+ this._dateSlot[index].data.parentNode.style.fontWeight = "normal";
+ this._dateSlot[index].data.parentNode.style.border= "none";
+ if (toISODate(d1) == today) {
+ this._dateSlot[index].data.parentNode.className = "today";
+ this._dateSlot[index].data.parentNode.style.fontWeight = "bold";
+ }
+ if (toISODate(d1) == current) {
+ this._dateSlot[index].data.parentNode.className += " current";
+ this._dateSlot[index].data.parentNode.style.border= "1px dotted WindowText";
+ }
+ if (toISODate(d1) == selected) {
+ this._dateSlot[index].data.parentNode.className += " selected";
+ this._dateSlot[index].data.parentNode.style.border= "1px solid WindowText";
+ }
+ d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+1);
+ }
+
+ var lastDateIndex = index;
+
+ while(index < 42) {
+ this._dateSlot[index].value = -1;
+ this._dateSlot[index].data.data = String.fromCharCode(160);
+ this._dateSlot[index].data.parentNode.className = "";
+ this._dateSlot[index].data.parentNode.style.fontWeight = "normal";
+ this._dateSlot[index].data.parentNode.style.border= "none";
+ ++index;
+ }
+
+ // Week numbers
+ if (this._includeWeek) {
+ d1 = new Date(date.getFullYear(), date.getMonth(), 1);
+ for (i=0; i < 6; ++i) {
+ if (i == 5 && lastDateIndex < 36) {
+ this._weekSlot[i].data.data = String.fromCharCode(160);
+ this._weekSlot[i].data.parentNode.style.borderRight = "none";
+ } else {
+ week = weekNumber(this, d1);
+ this._weekSlot[i].data.data = week;
+ this._weekSlot[i].data.parentNode.style.borderRight = "1px solid WindowText";
+ }
+ d1 = new Date(d1.getFullYear(), d1.getMonth(), d1.getDate()+7);
+ }
+ }
+}
+
+Calendar.prototype.show = function(element) {
+ if(!this._showing) {
+ var p = getPoint(element);
+ this._calDiv.style.display = "block";
+ this._calDiv.style.top = (p.y + element.offsetHeight + 1) + "px";
+ this._calDiv.style.left = p.x + "px";
+ this._showing = true;
+
+ /* -------- */
+ if( this._bw.ie6 )
+ {
+ dw = this._calDiv.offsetWidth;
+ dh = this._calDiv.offsetHeight;
+ var els = document.getElementsByTagName("body");
+ var body = els[0];
+ if( !body ) return;
+
+ //paste iframe under the modal
+ var underDiv = this._calDiv.cloneNode(false);
+ underDiv.style.zIndex="390";
+ underDiv.style.margin = "0px";
+ underDiv.style.padding = "0px";
+ underDiv.style.display = "block";
+ underDiv.style.width = dw;
+ underDiv.style.height = dh;
+ underDiv.style.border = "1px solid WindowText";
+ underDiv.innerHTML = "<iframe width=\"100%\" height=\"100%\" frameborder=\"0\"></iframe>";
+ body.appendChild(underDiv);
+ this._underDiv = underDiv;
+ }
+ /* -------- */
+ this._calDiv.focus();
+
+ }
+};
+
+Calendar.prototype.hide = function() {
+ if(this._showing) {
+ this._calDiv.style.display = "none";
+ this._showing = false;
+ if( this._bw.ie6 ) {
+ if( this._underDiv ) this._underDiv.removeNode(true);
+ }
+ }
+}
+
+Calendar.prototype.toggle = function(element) {
+ if(this._showing) {
+ this.hide();
+ } else {
+ this.show(element);
+ }
+}
+
+
+
+Calendar.prototype.onchange = function() {};
+
+
+Calendar.prototype.setCurrentDate = function(date) {
+ if (date == null) {
+ return;
+ }
+
+ // if string or number create a Date object
+ if (typeof date == "string" || typeof date == "number") {
+ date = new Date(date);
+ }
+
+
+ // do not update if not really changed
+ if (this._currentDate.getDate() != date.getDate() ||
+ this._currentDate.getMonth() != date.getMonth() ||
+ this._currentDate.getFullYear() != date.getFullYear()) {
+
+ this._currentDate = new Date(date);
+
+ this._updateHeader();
+ this._update();
+
+ }
+
+}
+
+Calendar.prototype.setSelectedDate = function(date) {
+ this._selectedDate = new Date(date);
+ this.setCurrentDate(this._selectedDate);
+ if (typeof this.onchange == "function") {
+ this.onchange();
+ }
+}
+
+Calendar.prototype.clearSelectedDate = function() {
+ this._selectedDate = null;
+ if (typeof this.onchange == "function") {
+ this.onchange();
+ }
+}
+
+Calendar.prototype.getElement = function() {
+ return this._calDiv;
+}
+
+Calendar.prototype.setIncludeWeek = function(v) {
+ if (this._calDiv == null) {
+ this._includeWeek = v;
+ }
+}
+
+Calendar.prototype.getSelectedDate = function () {
+ if (this._selectedDate == null) {
+ return null;
+ } else {
+ return new Date(this._selectedDate);
+ }
+}
+
+
+
+Calendar.prototype._updateHeader = function () {
+
+ //
+ var options = this._monthSelect.options;
+ var m = this._currentDate.getMonth();
+ for(var i=0; i < options.length; ++i) {
+ options[i].selected = false;
+ if (options[i].value == m) {
+ options[i].selected = true;
+ }
+ }
+
+ options = this._yearSelect.options;
+ var year = this._currentDate.getFullYear();
+ for(var i=0; i < options.length; ++i) {
+ options[i].selected = false;
+ if (options[i].value == year) {
+ options[i].selected = true;
+ }
+ }
+
+}
+
+Calendar.prototype.setYear = function(year) {
+ var d = new Date(this._currentDate);
+ d.setFullYear(year);
+ this.setCurrentDate(d);
+}
+
+Calendar.prototype.setMonth = function (month) {
+ var d = new Date(this._currentDate);
+ d.setMonth(month);
+ this.setCurrentDate(d);
+}
+
+Calendar.prototype.nextMonth = function () {
+ this.setMonth(this._currentDate.getMonth()+1);
+}
+
+Calendar.prototype.prevMonth = function () {
+ this.setMonth(this._currentDate.getMonth()-1);
+}
+
+Calendar.prototype.setFirstDayOfWeek = function (nFirstWeekDay) {
+ this._firstDayOfWeek = nFirstWeekDay;
+}
+
+Calendar.prototype.getFirstDayOfWeek = function () {
+ return this._firstDayOfWeek;
+}
+
+Calendar.prototype.setMinimalDaysInFirstWeek = function(n) {
+ this._minimalDaysInFirstWeek = n;
+}
+
+
+Calendar.prototype.getMinimalDaysInFirstWeek = function () {
+ return this._minimalDaysInFirstWeek;
+}
+
+Calendar.prototype.setMonthNames = function(a) {
+ // sanity test
+ this._monthNames = a;
+}
+
+Calendar.prototype.setShortMonthNames = function(a) {
+ // sanity test
+ this._shortMonthNames = a;
+}
+
+Calendar.prototype.setWeekDayNames = function(a) {
+ // sanity test
+ this._weekDayNames = a;
+}
+
+Calendar.prototype.setShortWeekDayNames = function(a) {
+ // sanity test
+ this._shortWeekDayNames = a;
+}
+
+Calendar.prototype.getFormat = function() {
+ return this._format;
+}
+
+Calendar.prototype.setFormat = function(f) {
+ this._format = f;
+}
+
+Calendar.prototype.formatDate = function() {
+ if (this._selectedDate == null) {
+ return "";
+ }
+
+ var bits = new Array();
+ // work out what each bit should be
+ var date = this._selectedDate;
+ bits['d'] = date.getDate();
+ bits['dd'] = pad(date.getDate(),2);
+ bits['ddd'] = this._shortWeekDayNames[date.getDay()];
+ bits['dddd'] = this._weekDayNames[date.getDay()];
+
+ bits['M'] = date.getMonth()+1;
+ bits['MM'] = pad(date.getMonth()+1,2);
+ bits['MMM'] = this._shortMonthNames[date.getMonth()];
+ bits['MMMM'] = this._monthNames[date.getMonth()];
+
+ var yearStr = "" + date.getFullYear();
+ yearStr = (yearStr.length == 2) ? '19' + yearStr: yearStr;
+ bits['yyyy'] = yearStr;
+ bits['yy'] = bits['yyyy'].toString().substr(2,2);
+
+ // do some funky regexs to replace the format string
+ // with the real values
+ var frm = new String(this._format);
+ var sect;
+ for (sect in bits) {
+ frm = eval("frm.replace(/\\b" + sect + "\\b/,'" + bits[sect] + "');");
+ }
+
+ return frm;
+}
+
+
+function isLeapYear(year) {
+ return ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0)));
+}
+
+function yearLength(year) {
+ if (isLeapYear(year))
+ return 366;
+ else
+ return 365;
+}
+
+function dayOfYear(date) {
+ var a = Calendar.NUM_DAYS;
+ if (isLeapYear(date.getFullYear())) {
+ a = Calendar.LEAP_NUM_DAYS;
+ }
+ var month = date.getMonth();
+
+ return a[month] + date.getDate();
+}
+
+// ---------------------------------------------
+// Week number stuff
+// ---------------------------------------------
+
+function weekNumber(cal, date) {
+
+ var dow = date.getDay();
+ var doy = dayOfYear(date);
+ var year = date.getFullYear();
+
+ // Compute the week of the year. Valid week numbers run from 1 to 52
+ // or 53, depending on the year, the first day of the week, and the
+ // minimal days in the first week. Days at the start of the year may
+ // fall into the last week of the previous year; days at the end of
+ // the year may fall into the first week of the next year.
+ var relDow = (dow + 7 - cal.getFirstDayOfWeek()) % 7; // 0..6
+ var relDowJan1 = (dow - doy + 701 - cal.getFirstDayOfWeek()) % 7; // 0..6
+ var week = Math.floor((doy - 1 + relDowJan1) / 7); // 0..53
+ if ((7 - relDowJan1) >= cal.getMinimalDaysInFirstWeek()) {
+ ++week;
+ }
+
+ if (doy > 359) { // Fast check which eliminates most cases
+ // Check to see if we are in the last week; if so, we need
+ // to handle the case in which we are the first week of the
+ // next year.
+ var lastDoy = yearLength(year);
+ var lastRelDow = (relDow + lastDoy - doy) % 7;
+ if (lastRelDow < 0) {
+ lastRelDow += 7;
+ }
+ if (((6 - lastRelDow) >= cal.getMinimalDaysInFirstWeek())
+ && ((doy + 7 - relDow) > lastDoy)) {
+ week = 1;
+ }
+ } else if (week == 0) {
+ // We are the last week of the previous year.
+ var prevDoy = doy + yearLength(year - 1);
+ week = weekOfPeriod(cal, prevDoy, dow);
+ }
+
+ return week;
+}
+
+function weekOfPeriod(cal, dayOfPeriod, dayOfWeek) {
+ // Determine the day of the week of the first day of the period
+ // in question (either a year or a month). Zero represents the
+ // first day of the week on this calendar.
+ var periodStartDayOfWeek =
+ (dayOfWeek - cal.getFirstDayOfWeek() - dayOfPeriod + 1) % 7;
+ if (periodStartDayOfWeek < 0) {
+ periodStartDayOfWeek += 7;
+ }
+
+ // Compute the week number. Initially, ignore the first week, which
+ // may be fractional (or may not be). We add periodStartDayOfWeek in
+ // order to fill out the first week, if it is fractional.
+ var weekNo = Math.floor((dayOfPeriod + periodStartDayOfWeek - 1) / 7);
+
+ // If the first week is long enough, then count it. If
+ // the minimal days in the first week is one, or if the period start
+ // is zero, we always increment weekNo.
+ if ((7 - periodStartDayOfWeek) >= cal.getMinimalDaysInFirstWeek()) {
+ ++weekNo;
+ }
+
+ return weekNo;
+}
+
+
+
+
+function getEventObject(e) { // utility function to retrieve object from event
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ return e.srcElement;
+ } else { // is mozilla/netscape
+ // need to crawl up the tree to get the first "real" element
+ // i.e. a tag, not raw text
+ var o = e.target;
+ while (!o.tagName) {
+ o = o.parentNode;
+ }
+ return o;
+ }
+}
+
+function addEvent(name, obj, funct) { // utility function to add event handlers
+
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ obj.attachEvent("on"+name, funct);
+ } else { // is mozilla/netscape
+ obj.addEventListener(name, funct, false);
+ }
+}
+
+
+function deleteEvent(name, obj, funct) { // utility function to delete event handlers
+
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ obj.detachEvent("on"+name, funct);
+ } else { // is mozilla/netscape
+ obj.removeEventListener(name, funct, false);
+ }
+}
+
+function setCursor(obj) {
+ if (navigator.appName == "Microsoft Internet Explorer") {
+ obj.style.cursor = "hand";
+ } else { // is mozilla/netscape
+ obj.style.cursor = "pointer";
+ }
+}
+
+function Point(iX, iY)
+{
+ this.x = iX;
+ this.y = iY;
+}
+
+
+function getPoint(aTag)
+{
+ var oTmp = aTag;
+ var point = new Point(0,0);
+
+ do
+ {
+ point.x += oTmp.offsetLeft;
+ point.y += oTmp.offsetTop;
+ oTmp = oTmp.offsetParent;
+ }
+ while (oTmp.tagName != "BODY");
+
+ return point;
+}
+
+function toISODate(date) {
+ var s = date.getFullYear();
+ var m = date.getMonth() + 1;
+ if (m < 10) {
+ m = "0" + m;
+ }
+ var day = date.getDate();
+ if (day < 10) {
+ day = "0" + day;
+ }
+ return String(s) + String(m) + String(day);
+
+}
+
+function pad(number,X) { // utility function to pad a number to a given width
+ X = (!X ? 2 : X);
+ number = ""+number;
+ while (number.length < X) {
+ number = "0" + number;
+ }
+ return number;
+}
+
+function bw_check()
+{
+ var is_major = parseInt( navigator.appVersion );
+ this.nver = is_major;
+ this.ver = navigator.appVersion;
+ this.agent = navigator.userAgent;
+ this.dom = document.getElementById ? 1 : 0;
+ this.opera = window.opera ? 1 : 0;
+ this.ie5 = ( this.ver.indexOf( "MSIE 5" ) > -1 && this.dom && !this.opera ) ? 1 : 0;
+ this.ie6 = ( this.ver.indexOf( "MSIE 6" ) > -1 && this.dom && !this.opera ) ? 1 : 0;
+ this.ie4 = ( document.all && !this.dom && !this.opera ) ? 1 : 0;
+ this.ie = this.ie4 || this.ie5 || this.ie6;
+ this.mac = this.agent.indexOf( "Mac" ) > -1;
+ this.ns6 = ( this.dom && parseInt( this.ver ) >= 5 ) ? 1 : 0;
+ this.ie3 = ( this.ver.indexOf( "MSIE" ) && ( is_major < 4 ) );
+ this.hotjava = ( this.agent.toLowerCase().indexOf( 'hotjava' ) != -1 ) ? 1 : 0;
+ this.ns4 = ( document.layers && !this.dom && !this.hotjava ) ? 1 : 0;
+ this.bw = ( this.ie6 || this.ie5 || this.ie4 || this.ns4 || this.ns6 || this.opera );
+ this.ver3 = ( this.hotjava || this.ie3 );
+ this.opera7 = ( ( this.agent.toLowerCase().indexOf( 'opera 7' ) > -1 ) || ( this.agent.toLowerCase().indexOf( 'opera/7' ) > -1 ) );
+ this.operaOld = this.opera && !this.opera7;
+ return this;
+};
+
+
diff --git a/tapestry-framework/src/org/apache/tapestry/form/DatePicker.jwc b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.jwc
new file mode 100644
index 0000000..72cff6f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.jwc
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification
+ class="org.apache.tapestry.form.DatePicker"
+ allow-informal-parameters="yes"
+ allow-body="no">
+
+ <parameter name="value" direction="form" type="java.util.Date" required="yes"/>
+ <parameter name="format" direction="in" type="java.lang.String" required="no"/>
+ <parameter name="disabled" direction="in" type="boolean" required="no"/>
+ <parameter name="includeWeek" type="boolean" direction="in" required="no"/>
+ <parameter name="icon" type="org.apache.tapestry.IAsset" direction="in" required="no"
+ default-value="assets.defaultIcon"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+ <private-asset name="script" resource-path="DatePicker.js"/>
+
+ <private-asset name="defaultIcon" resource-path="DatePickerIcon.png"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/DatePicker.script b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.script
new file mode 100644
index 0000000..b1ca083
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/DatePicker.script
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+
+<script>
+
+<include-script resource-path="DatePicker.js"/>
+
+<input-symbol key="name" class="java.lang.String" required="yes"/>
+<input-symbol key="formName" class="java.lang.String" required="yes"/>
+<input-symbol key="monthNames" required="yes"/>
+<input-symbol key="shortMonthNames" required="yes"/>
+<input-symbol key="weekDayNames" required="yes"/>
+<input-symbol key="shortWeekDayNames" required="yes"/>
+<input-symbol key="firstDayInWeek" required="yes"/>
+<input-symbol key="minimalDaysInFirstWeek" required="yes"/>
+<input-symbol key="format" required="yes"/>
+<input-symbol key="includeWeek" required="yes"/>
+<input-symbol key="value" required="no"/>
+
+<let key="calendarObject" unique="yes">
+ calendar_${name}
+</let>
+
+<let key="buttonOnclickHandler">
+ javascript:${calendarObject}.toggle(document.${formName}.${name});
+</let>
+
+<body>
+var ${calendarObject};
+</body>
+
+<initialization>
+
+<if expression="value == null">
+${calendarObject} = new Calendar();
+</if>
+<if expression="value != null">
+${calendarObject} = new Calendar(${value.time});
+</if>
+
+${calendarObject}.setMonthNames(new Array(${monthNames}));
+${calendarObject}.setShortMonthNames(new Array(${shortMonthNames}));
+${calendarObject}.setWeekDayNames(new Array(${weekDayNames}));
+${calendarObject}.setShortWeekDayNames(new Array(${shortWeekDayNames}));
+${calendarObject}.setFormat("${format}");
+${calendarObject}.setFirstDayOfWeek(${firstDayInWeek});
+${calendarObject}.setMinimalDaysInFirstWeek(${minimalDaysInFirstWeek});
+${calendarObject}.setIncludeWeek(${includeWeek});
+
+${calendarObject}.create();
+
+${calendarObject}.onchange = function() {
+ var field = document.${formName}.${name};
+ var value = ${calendarObject}.formatDate();
+ if (field.value != value) {
+ field.value = value;
+ if (field.onchange) {
+ field.onchange();
+ }
+ }
+}
+
+</initialization>
+</script>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/DatePickerIcon.png b/tapestry-framework/src/org/apache/tapestry/form/DatePickerIcon.png
new file mode 100644
index 0000000..0b9a96e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/DatePickerIcon.png
Binary files differ
diff --git a/tapestry-framework/src/org/apache/tapestry/form/EnumPropertySelectionModel.java b/tapestry-framework/src/org/apache/tapestry/form/EnumPropertySelectionModel.java
new file mode 100644
index 0000000..0ffdfad
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/EnumPropertySelectionModel.java
@@ -0,0 +1,141 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.util.ResourceBundle;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Implementation of {@link IPropertySelectionModel} that wraps around
+ * a set of {@link Enum}s.
+ *
+ * <p>Uses a simple index number as the value (used to represent the option).
+ *
+ * <p>The resource bundle from which labels are extracted is usually
+ * a resource within the Tapestry application. Since
+ * {@link ResourceBundle#getBundle(String, java.util.Locale)} uses its caller's class loader,
+ * and that classloader will be the Tapestry framework's classloader, the application's
+ * resources won't be visible. This requires that the application resolve
+ * the resource to a {@link ResourceBundle} before creating this model.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class EnumPropertySelectionModel implements IPropertySelectionModel
+{
+ private Enum[] _options;
+ private String[] _labels;
+
+ private String _resourcePrefix;
+ private ResourceBundle _bundle;
+
+ /**
+ * Standard constructor.
+ *
+ * <p>Labels for the options are extracted from a resource bundle. resourceBaseName
+ * identifies the bundle. Typically, the bundle will be a <code>.properties</code>
+ * file within the classpath. Specify the fully qualified class name equivalent, i.e.,
+ * for file <code>/com/example/foo/LabelStrings.properties</code> use
+ * <code>com.example.foo.LabelStrings</code> as the resource base name.
+ *
+ * <p>Normally (when resourcePrefix is null), the keys used to extract labels
+ * matches the {@link Enum#getName() enumeration id} of the option. By
+ * convention, the enumeration id matches the name of the static variable.
+ *
+ * <p>To avoid naming conflicts when using a single resource bundle for multiple
+ * models, use a resource prefix. This is a string which is prepended to
+ * the enumeration id (they prefix and enumeration id are seperated with a period).
+ *
+ * @param options The list of possible values for this model, in the order they
+ * should appear. This exact array is retained (not copied).
+ *
+ * @param bundle The {@link ResourceBundle} from which labels may be extracted.
+ *
+ * @param resourcePrefix An optional prefix used when accessing keys within the bundle.
+ * Used to allow a single ResouceBundle to contain labels for multiple Enums.
+ **/
+
+ public EnumPropertySelectionModel(Enum[] options, ResourceBundle bundle, String resourcePrefix)
+ {
+ _options = options;
+ _bundle = bundle;
+ _resourcePrefix = resourcePrefix;
+ }
+
+ /**
+ * Simplified constructor using no prefix.
+ *
+ **/
+
+ public EnumPropertySelectionModel(Enum[] options, ResourceBundle bundle)
+ {
+ this(options, bundle, null);
+ }
+
+ public int getOptionCount()
+ {
+ return _options.length;
+ }
+
+ public Object getOption(int index)
+ {
+ return _options[index];
+ }
+
+ public String getLabel(int index)
+ {
+ if (_labels == null)
+ readLabels();
+
+ return _labels[index];
+ }
+
+ public String getValue(int index)
+ {
+ return Integer.toString(index);
+ }
+
+ public Object translateValue(String value)
+ {
+ int index;
+
+ index = Integer.parseInt(value);
+
+ return _options[index];
+ }
+
+ private void readLabels()
+ {
+ _labels = new String[_options.length];
+
+ for (int i = 0; i < _options.length; i++)
+ {
+ String enumerationId = _options[i].getName();
+
+ String key;
+
+ if (_resourcePrefix == null)
+ key = enumerationId;
+ else
+ key = _resourcePrefix + "." + enumerationId;
+
+ _labels[i] = _bundle.getString(key);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Form.java b/tapestry-framework/src/org/apache/tapestry/form/Form.java
new file mode 100644
index 0000000..e0e3175
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Form.java
@@ -0,0 +1,828 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.RenderRewoundException;
+import org.apache.tapestry.StaleLinkException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.html.Body;
+import org.apache.tapestry.util.IdAllocator;
+import org.apache.tapestry.util.StringSplitter;
+import org.apache.tapestry.valid.IValidationDelegate;
+
+/**
+ * Component which contains form element components. Forms use the
+ * action or direct services to handle the form submission. A Form will wrap
+ * other components and static HTML, including
+ * form components such as {@link TextArea}, {@link TextField}, {@link Checkbox}, etc.
+ *
+ * [<a href="../../../../../ComponentReference/Form.html">Component Reference</a>]
+ *
+ * <p>When a form is submitted, it continues through the rewind cycle until
+ * <em>after</em> all of its wrapped elements have renderred. As the form
+ * component render (in the rewind cycle), they will be updating
+ * properties of the containing page and notifying thier listeners. Again:
+ * each form component is responsible not only for rendering HTML (to present the
+ * form), but for handling it's share of the form submission.
+ *
+ * <p>Only after all that is done will the Form notify its listener.
+ *
+ * <p>Starting in release 1.0.2, a Form can use either the direct service or
+ * the action service. The default is the direct service, even though
+ * in earlier releases, only the action service was available.
+ *
+ * @author Howard Lewis Ship, David Solis
+ * @version $Id$
+ **/
+
+public abstract class Form extends AbstractComponent implements IForm, IDirect
+{
+ private static class HiddenValue
+ {
+ String _name;
+ String _value;
+ String _id;
+
+ private HiddenValue(String name, String value)
+ {
+ this(name, null, value);
+ }
+
+ private HiddenValue(String name, String id, String value)
+ {
+ _name = name;
+ _id = id;
+ _value = value;
+ }
+ }
+
+ private boolean _rewinding;
+ private boolean _rendering;
+ private String _name;
+
+ /**
+ * Used when rewinding the form to figure to match allocated ids (allocated during
+ * the rewind) against expected ids (allocated in the previous request cycle, when
+ * the form was rendered).
+ *
+ * @since 3.0
+ *
+ **/
+
+ private int _allocatedIdIndex;
+
+ /**
+ * The list of allocated ids for form elements within this form. This list
+ * is constructed when a form renders, and is validated against when the
+ * form is rewound.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private List _allocatedIds = new ArrayList();
+
+ /**
+ * {@link Map}, keyed on {@link FormEventType}. Values are either a String (the name
+ * of a single event), or a {@link List} of Strings.
+ *
+ * @since 1.0.2
+ **/
+
+ private Map _events;
+
+ private static final int EVENT_MAP_SIZE = 3;
+
+ private IdAllocator _elementIdAllocator = new IdAllocator();
+
+ private String _encodingType;
+
+ private List _hiddenValues;
+
+ /**
+ * Returns the currently active {@link IForm}, or null if no form is
+ * active. This is a convienience method, the result will be
+ * null, or an instance of {@link IForm}, but not necessarily a
+ * <code>Form</code>.
+ *
+ **/
+
+ public static IForm get(IRequestCycle cycle)
+ {
+ return (IForm) cycle.getAttribute(ATTRIBUTE_NAME);
+ }
+
+ /**
+ * Indicates to any wrapped form components that they should respond to the form
+ * submission.
+ *
+ * @throws ApplicationRuntimeException if not rendering.
+ **/
+
+ public boolean isRewinding()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
+
+ return _rewinding;
+ }
+
+ /**
+ * Returns true if this Form is configured to use the direct
+ * service.
+ *
+ * <p>This is derived from the direct parameter, and defaults
+ * to true if not bound.
+ *
+ * @since 1.0.2
+ **/
+
+ public abstract boolean isDirect();
+
+ /**
+ * Returns true if the stateful parameter is bound to
+ * a true value. If stateful is not bound, also returns
+ * the default, true.
+ *
+ * @since 1.0.1
+ **/
+
+ public boolean getRequiresSession()
+ {
+ return isStateful();
+ }
+
+ /**
+ * Constructs a unique identifier (within the Form). The identifier
+ * consists of the component's id, with an index number added to
+ * ensure uniqueness.
+ *
+ * <p>Simply invokes {@link #getElementId(org.apache.tapestry.form.IFormComponent, java.lang.String)}
+ * with the component's id.
+ *
+ *
+ * @since 1.0.2
+ **/
+
+ public String getElementId(IFormComponent component)
+ {
+ return getElementId(component, component.getId());
+ }
+
+ /**
+ * Constructs a unique identifier from the base id. If possible, the
+ * id is used as-is. Otherwise, a unique identifier is appended
+ * to the id.
+ *
+ * <p>This method is provided simply so that some components
+ * ({@link ImageSubmit}) have more specific control over
+ * their names.
+ *
+ * @since 1.0.3
+ *
+ **/
+
+ public String getElementId(IFormComponent component, String baseId)
+ {
+ String result = _elementIdAllocator.allocateId(baseId);
+
+ if (_rewinding)
+ {
+ if (_allocatedIdIndex >= _allocatedIds.size())
+ {
+ throw new StaleLinkException(
+ Tapestry.format(
+ "Form.too-many-ids",
+ getExtendedId(),
+ Integer.toString(_allocatedIds.size()),
+ component.getExtendedId()),
+ this);
+ }
+
+ String expected = (String) _allocatedIds.get(_allocatedIdIndex);
+
+ if (!result.equals(expected))
+ throw new StaleLinkException(
+ Tapestry.format(
+ "Form.id-mismatch",
+ new Object[] {
+ getExtendedId(),
+ Integer.toString(_allocatedIdIndex + 1),
+ expected,
+ result,
+ component.getExtendedId()}),
+ this);
+ }
+ else
+ {
+ _allocatedIds.add(result);
+ }
+
+ _allocatedIdIndex++;
+
+ component.setName(result);
+
+ return result;
+ }
+
+ /**
+ * Returns the name generated for the form. This is used to faciliate
+ * components that write JavaScript and need to access the form or
+ * its contents.
+ *
+ * <p>This value is generated when the form renders, and is not cleared.
+ * If the Form is inside a {@link org.apache.tapestry.components.Foreach},
+ * this will be the most recently
+ * generated name for the Form.
+ *
+ * <p>This property is exposed so that sophisticated applications can write
+ * JavaScript handlers for the form and components within the form.
+ *
+ * @see AbstractFormComponent#getName()
+ *
+ **/
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ /** @since 3.0 **/
+
+ protected void prepareForRender(IRequestCycle cycle)
+ {
+ super.prepareForRender(cycle);
+
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Form.forms-may-not-nest"),
+ this,
+ null,
+ null);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ _rendering = false;
+
+ _allocatedIdIndex = 0;
+ _allocatedIds.clear();
+
+ _events = null;
+
+ _elementIdAllocator.clear();
+
+ if (_hiddenValues != null)
+ _hiddenValues.clear();
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+
+ _encodingType = null;
+
+ IValidationDelegate delegate = getDelegate();
+
+ if (delegate != null)
+ delegate.setFormComponent(null);
+
+ super.cleanupAfterRender(cycle);
+ }
+
+ protected void writeAttributes(IMarkupWriter writer, ILink link)
+ {
+ String method = getMethod();
+
+ writer.begin(getTag());
+ writer.attribute("method", (method == null) ? "post" : method);
+ writer.attribute("name", _name);
+ writer.attribute("action", link.getURL(null, false));
+
+ if (_encodingType != null)
+ writer.attribute("enctype", _encodingType);
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String actionId = cycle.getNextActionId();
+ _name = getDisplayName() + actionId;
+
+ boolean renderForm = !cycle.isRewinding();
+ boolean rewound = cycle.isRewound(this);
+
+ _rewinding = rewound;
+
+ _allocatedIdIndex = 0;
+
+ _rendering = true;
+
+ if (rewound)
+ {
+ String storedIdList = cycle.getRequestContext().getParameter(_name);
+
+ reconstructAllocatedIds(storedIdList);
+ }
+
+ ILink link = getLink(cycle, actionId);
+
+ // When rendering, use a nested writer so that an embedded Upload
+ // component can force the encoding type.
+
+ IMarkupWriter nested = writer.getNestedWriter();
+
+ renderBody(nested, cycle);
+
+ if (renderForm)
+ {
+ writeAttributes(writer, link);
+
+ renderInformalParameters(writer, cycle);
+ writer.println();
+ }
+
+ // Write the hidden's, or at least, reserve the query parameters
+ // required by the Gesture.
+
+ writeLinkParameters(writer, link, !renderForm);
+
+ if (renderForm)
+ {
+ // What's this for? It's part of checking for stale links.
+ // We record the list of allocated ids.
+ // On rewind, we check that the stored list against which
+ // ids were allocated. If the persistent state of the page or
+ // application changed between render (previous request cycle)
+ // and rewind (current request cycle), then the list
+ // of ids will change as well.
+
+ writeHiddenField(writer, _name, buildAllocatedIdList());
+ writeHiddenValues(writer);
+
+ nested.close();
+
+ writer.end(getTag());
+
+ // Write out event handlers collected during the rendering.
+
+ emitEventHandlers(writer, cycle);
+ }
+
+ if (rewound)
+ {
+ int expected = _allocatedIds.size();
+
+ // The other case, _allocatedIdIndex > expected, is
+ // checked for inside getElementId(). Remember that
+ // _allocatedIdIndex is incremented after allocating.
+
+ if (_allocatedIdIndex < expected)
+ {
+ String nextExpectedId = (String) _allocatedIds.get(_allocatedIdIndex);
+
+ throw new StaleLinkException(
+ Tapestry.format(
+ "Form.too-few-ids",
+ getExtendedId(),
+ Integer.toString(expected - _allocatedIdIndex),
+ nextExpectedId),
+ this);
+ }
+
+ IActionListener listener = getListener();
+
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+
+ // Abort the rewind render.
+
+ throw new RenderRewoundException(this);
+ }
+ }
+
+ /**
+ * Adds an additional event handler.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public void addEventHandler(FormEventType type, String functionName)
+ {
+ if (_events == null)
+ _events = new HashMap(EVENT_MAP_SIZE);
+
+ Object value = _events.get(type);
+
+ // The value can either be a String, or a List of String. Since
+ // it is rare for there to be more than one event handling function,
+ // we start with just a String.
+
+ if (value == null)
+ {
+ _events.put(type, functionName);
+ return;
+ }
+
+ // The second function added converts it to a List.
+
+ if (value instanceof String)
+ {
+ List list = new ArrayList();
+ list.add(value);
+ list.add(functionName);
+
+ _events.put(type, list);
+ return;
+ }
+
+ // The third and subsequent function just
+ // adds to the List.
+
+ List list = (List) value;
+ list.add(functionName);
+ }
+
+ protected void emitEventHandlers(IMarkupWriter writer, IRequestCycle cycle)
+ {
+
+ if (_events == null || _events.isEmpty())
+ return;
+
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Form.needs-body-for-event-handlers"),
+ this,
+ null,
+ null);
+
+ StringBuffer buffer = new StringBuffer();
+
+ Iterator i = _events.entrySet().iterator();
+ while (i.hasNext())
+ {
+
+ Map.Entry entry = (Map.Entry) i.next();
+ FormEventType type = (FormEventType) entry.getKey();
+ Object value = entry.getValue();
+
+ buffer.append("document.");
+ buffer.append(_name);
+ buffer.append(".");
+ buffer.append(type.getPropertyName());
+ buffer.append(" = ");
+
+ // The typical case; one event one event handler. Easy enough.
+
+ if (value instanceof String)
+ {
+ buffer.append(value.toString());
+ buffer.append(";");
+ }
+ else
+ {
+ // Build a composite function in-place
+
+ buffer.append("function ()\n{\n");
+
+ boolean combineWithAnd = type.getCombineUsingAnd();
+
+ List l = (List) value;
+ int count = l.size();
+
+ for (int j = 0; j < count; j++)
+ {
+ String functionName = (String) l.get(j);
+
+ if (j > 0)
+ {
+
+ if (combineWithAnd)
+ buffer.append(" &&");
+ else
+ buffer.append(";");
+ }
+
+ buffer.append("\n ");
+
+ if (combineWithAnd)
+ {
+ if (j == 0)
+ buffer.append("return ");
+ else
+ buffer.append(" ");
+ }
+
+ buffer.append(functionName);
+ buffer.append("()");
+ }
+
+ buffer.append(";\n}");
+ }
+
+ buffer.append("\n\n");
+ }
+
+ body.addInitializationScript(buffer.toString());
+ }
+
+ /**
+ * Simply invokes {@link #render(IMarkupWriter, IRequestCycle)}.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public void rewind(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ render(writer, cycle);
+ }
+
+ /**
+ * Method invoked by the direct service.
+ *
+ * @since 1.0.2
+ *
+ **/
+
+ public void trigger(IRequestCycle cycle)
+ {
+ Object[] parameters = cycle.getServiceParameters();
+
+ cycle.rewindForm(this, (String) parameters[0]);
+ }
+
+ /**
+ * Builds the EngineServiceLink for the form, using either the direct or
+ * action service.
+ *
+ * @since 1.0.3
+ *
+ **/
+
+ private ILink getLink(IRequestCycle cycle, String actionId)
+ {
+ String serviceName = null;
+
+ if (isDirect())
+ serviceName = Tapestry.DIRECT_SERVICE;
+ else
+ serviceName = Tapestry.ACTION_SERVICE;
+
+ IEngine engine = cycle.getEngine();
+ IEngineService service = engine.getService(serviceName);
+
+ // A single service parameter is used to store the actionId.
+
+ return service.getLink(cycle, this, new String[] { actionId });
+ }
+
+ private void writeLinkParameters(IMarkupWriter writer, ILink link, boolean reserveOnly)
+ {
+ String[] names = link.getParameterNames();
+ int count = Tapestry.size(names);
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = names[i];
+
+ // Reserve the name.
+
+ _elementIdAllocator.allocateId(name);
+
+ if (!reserveOnly)
+ writeHiddenFieldsForParameter(writer, link, name);
+ }
+ }
+
+ /**
+ * @since 3.0
+ *
+ **/
+
+ protected void writeHiddenField(IMarkupWriter writer, String name, String value)
+ {
+ writeHiddenField(writer, name, null, value);
+ }
+
+ protected void writeHiddenField(IMarkupWriter writer, String name, String id, String value)
+ {
+ writer.beginEmpty("input");
+ writer.attribute("type", "hidden");
+ writer.attribute("name", name);
+
+ if(id != null && id.length() != 0)
+ writer.attribute("id", id);
+
+ writer.attribute("value", value);
+ writer.println();
+ }
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ private void writeHiddenFieldsForParameter(
+ IMarkupWriter writer,
+ ILink link,
+ String parameterName)
+ {
+ String[] values = link.getParameterValues(parameterName);
+
+ for (int i = 0; i < values.length; i++)
+ {
+ writeHiddenField(writer, parameterName, values[i]);
+ }
+ }
+
+ /**
+ * Converts the allocateIds property into a string, a comma-separated list of ids.
+ * This is included as a hidden field in the form and is used to identify
+ * discrepencies when the form is submitted.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected String buildAllocatedIdList()
+ {
+ StringBuffer buffer = new StringBuffer();
+ int count = _allocatedIds.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i > 0)
+ buffer.append(',');
+
+ buffer.append(_allocatedIds.get(i));
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Converts a string passed as a parameter (and containing a comma
+ * separated list of ids) back into the allocateIds property.
+ *
+ * @see #buildAllocatedIdList()
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void reconstructAllocatedIds(String storedIdList)
+ {
+ if (Tapestry.isBlank(storedIdList))
+ return;
+
+ StringSplitter splitter = new StringSplitter(',');
+
+ String[] ids = splitter.splitToArray(storedIdList);
+
+ for (int i = 0; i < ids.length; i++)
+ _allocatedIds.add(ids[i]);
+ }
+
+ public abstract IValidationDelegate getDelegate();
+
+ public abstract void setDelegate(IValidationDelegate delegate);
+
+ public abstract void setDirect(boolean direct);
+
+ public abstract IActionListener getListener();
+
+ public abstract String getMethod();
+
+ /**
+ * Invoked when not rendering, so it uses the stateful binding.
+ * If not bound, returns true.
+ *
+ **/
+
+ public boolean isStateful()
+ {
+ IBinding statefulBinding = getStatefulBinding();
+
+ if (statefulBinding == null)
+ return true;
+
+ return statefulBinding.getBoolean();
+ }
+
+ public abstract IBinding getStatefulBinding();
+
+ protected void finishLoad()
+ {
+ setDirect(true);
+ }
+
+ public void setEncodingType(String encodingType)
+ {
+ if (_encodingType != null && !_encodingType.equals(encodingType))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "Form.encoding-type-contention",
+ getExtendedId(),
+ _encodingType,
+ encodingType),
+ this,
+ null,
+ null);
+
+ _encodingType = encodingType;
+ }
+
+ /**
+ * Returns the tag of the form.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected String getTag()
+ {
+ return "form";
+ }
+
+ /**
+ * Returns the name of the element.
+ *
+ *
+ * @since 3.0
+ **/
+
+ protected String getDisplayName()
+ {
+ return "Form";
+ }
+
+ /** @since 3.0 */
+
+ public void addHiddenValue(String name, String value)
+ {
+ if (_hiddenValues == null)
+ _hiddenValues = new ArrayList();
+
+ _hiddenValues.add(new HiddenValue(name, value));
+ }
+
+ /** @since 3.0 */
+
+ public void addHiddenValue(String name, String id, String value)
+ {
+ if (_hiddenValues == null)
+ _hiddenValues = new ArrayList();
+
+ _hiddenValues.add(new HiddenValue(name, id, value));
+ }
+
+ /**
+ * Writes hidden values accumulated during the render
+ * (by components invoking {@link #addHiddenValue(String, String)}.
+ *
+ * @since 3.0
+ */
+
+ protected void writeHiddenValues(IMarkupWriter writer)
+ {
+ int count = Tapestry.size(_hiddenValues);
+
+ for (int i = 0; i < count; i++)
+ {
+ HiddenValue hv = (HiddenValue) _hiddenValues.get(i);
+
+ writeHiddenField(writer, hv._name, hv._id, hv._value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Form.jwc b/tapestry-framework/src/org/apache/tapestry/form/Form.jwc
new file mode 100644
index 0000000..1d4c2c8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Form.jwc
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Form">
+
+ <description>
+ Used to implement an HTML form.
+ </description>
+
+ <parameter name="method" type="java.lang.String" direction="in">
+ <description>
+ The method used by the form when it is submitted, defaults to POST.
+ </description>
+ </parameter>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ required="no"
+ direction="in">
+ <description>
+ Object invoked when the form is submitted, after all form components have responded
+ to the submission.
+ </description>
+ </parameter>
+
+ <parameter name="stateful"
+ type="boolean"
+ direction="custom">
+ <description>
+ If true (the default), then an active HttpSession is required.
+ </description>
+ </parameter>
+
+ <parameter name="direct" type="boolean" direction="in">
+ <description>
+ If true (the default), then the more efficient direct service is used.
+ If false, then the action service is used.
+ </description>
+ </parameter>
+
+ <parameter name="delegate"
+ type="org.apache.tapestry.valid.IValidationDelegate" direction="in">
+ <description>
+ Specifies the delegate to be used by fields to track input errors.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="action"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/FormEventType.java b/tapestry-framework/src/org/apache/tapestry/form/FormEventType.java
new file mode 100644
index 0000000..80b1f9d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/FormEventType.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Lists different types of JavaScript events that can be associated
+ * with a {@link Form} via {@link Form#addEventHandler(FormEventType, String)}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ **/
+
+public class FormEventType extends Enum
+{
+ /**
+ * Form event triggered when the form is submitted. Allows an event handler
+ * to perform any final changes before the results are posted to the server.
+ *
+ * <p>The JavaScript method should return <code>true</code> or
+ * <code>false</code>. If there are multiple event handlers for the form
+ * they will be combined using the binary and operator (<code>&&</code>).
+ *
+ **/
+
+ public static final FormEventType SUBMIT = new FormEventType("SUBMIT", "onsubmit");
+
+ /**
+ * Form event triggered when the form is reset; this allows an event handler
+ * to deal with any special cases related to resetting.
+ *
+ **/
+
+ public static final FormEventType RESET = new FormEventType("RESET", "onreset");
+
+ private String _propertyName;
+
+ private FormEventType(String name, String propertyName)
+ {
+ super(name);
+
+ _propertyName = propertyName;
+ }
+
+ /**
+ * Returns the DOM property corresponding to event type (used when generating
+ * client-side scripting).
+ *
+ **/
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+ /**
+ * Returns true if multiple functions should be combined
+ * with the <code>&&</code> operator. Otherwise,
+ * the event handler functions are simply invoked
+ * sequentially (as a series of JavaScript statements).
+ *
+ **/
+
+ public boolean getCombineUsingAnd()
+ {
+ return this == FormEventType.SUBMIT;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Hidden.java b/tapestry-framework/src/org/apache/tapestry/form/Hidden.java
new file mode 100644
index 0000000..7643de1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Hidden.java
@@ -0,0 +1,176 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.io.IOException;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.util.io.DataSqueezer;
+
+/**
+ * Implements a hidden field within a {@link Form}.
+ *
+ * [<a href="../../../../../ComponentReference/Hidden.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Hidden extends AbstractFormComponent
+{
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+ boolean formRewound = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ // If the form containing the Hidden isn't rewound, then render.
+
+ if (!formRewound)
+ {
+ // Optimiziation: if the page is rewinding (some other action or
+ // form was submitted), then don't bother rendering.
+
+ if (cycle.isRewinding())
+ return;
+
+ String externalValue = null;
+
+ if (getEncode())
+ {
+ Object value = getValueBinding().getObject();
+
+ try
+ {
+ externalValue = getDataSqueezer().squeeze(value);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), this, null, ex);
+ }
+ }
+ else
+ externalValue = (String) getValueBinding().getObject("value", String.class);
+
+ String id = getElementId();
+ //if we would like to test the IForm.addHiddenValue(name, externalValue) method with
+ //Hidden JUnit test the following code must be default. But from the performance issue
+ //I don't use the id parameter clauses.
+/* if(id == null || id.length() == 0){
+ form.addHiddenValue(name, externalValue);
+ }else{
+ form.addHiddenValue(name, id, externalValue);
+ }
+*/
+ form.addHiddenValue(name, id, externalValue);
+
+
+ return;
+ }
+
+ String externalValue = cycle.getRequestContext().getParameter(name);
+ Object value = null;
+
+ if (getEncode())
+ {
+ try
+ {
+ value = getDataSqueezer().unsqueeze(externalValue);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), this, null, ex);
+ }
+ }
+ else
+ value = externalValue;
+
+ // A listener is not always necessary ... it's easy to code
+ // the synchronization as a side-effect of the accessor method.
+
+ getValueBinding().setObject(value);
+
+ IActionListener listener = getListener();
+
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+ }
+
+ public String getElementId(){
+ String value = null;
+ IBinding idBinding = getIdBinding();
+ if(idBinding != null){
+ value = idBinding.getString();
+ }
+ return value;
+ }
+
+ /** @since 2.2 **/
+
+ private DataSqueezer getDataSqueezer()
+ {
+ return getPage().getEngine().getDataSqueezer();
+ }
+
+ public abstract IActionListener getListener();
+
+ public abstract IBinding getValueBinding();
+ public abstract IBinding getIdBinding();
+
+ /**
+ *
+ * Returns false. Hidden components are never disabled.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public boolean isDisabled()
+ {
+ return false;
+ }
+
+ /**
+ *
+ * Returns true if the compent encodes object values using a
+ * {@link org.apache.tapestry.util.io.DataSqueezer}, false
+ * if values are always Strings.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public abstract boolean getEncode();
+
+ public abstract void setEncode(boolean encode);
+
+ /**
+ * Sets the encode parameter property to its default, true.
+ *
+ * @since 3.0
+ */
+ protected void finishLoad()
+ {
+ setEncode(true);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Hidden.jwc b/tapestry-framework/src/org/apache/tapestry/form/Hidden.jwc
new file mode 100644
index 0000000..faec149
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Hidden.jwc
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Hidden"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+ Stores a value in a hidden field of the form.
+ </description>
+
+ <parameter name="value"
+ required="yes"
+ type="java.lang.Object"
+ direction="custom">
+ <description>
+ Value to save in the form.
+ </description>
+ </parameter>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ direction="in">
+ <description>
+ Listener notified after the value is restored.
+ </description>
+ </parameter>
+
+ <parameter name="id"
+ required="no"
+ type="java.lang.String"
+ direction="custom">
+ <description>
+ ID parameter of HTML hidden object.
+ </description>
+ </parameter>
+
+ <parameter name="encode" type="boolean" direction="in"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/IFormComponent.java b/tapestry-framework/src/org/apache/tapestry/form/IFormComponent.java
new file mode 100644
index 0000000..5951813
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/IFormComponent.java
@@ -0,0 +1,87 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IForm;
+
+/**
+ * A common interface implemented by all form components (components that
+ * create interactive elements in the rendered page).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IFormComponent extends IComponent
+{
+ /**
+ * Returns the {@link org.apache.tapestry.IForm} which contains the component,
+ * or null if the component is not contained by a form,
+ * of if the containing Form is not currently renderring.
+ *
+ **/
+
+ public IForm getForm();
+
+ /**
+ * Returns the name of the component, which is automatically generated
+ * during renderring.
+ *
+ * <p>This value is set inside the component's render method and is
+ * <em>not</em> cleared. If the component is inside a {@link org.apache.tapestry.components.Foreach}, the
+ * value returned is the most recent name generated for the component.
+ *
+ * <p>This property is made available to facilitate writing JavaScript that
+ * allows components (in the client web browser) to interact.
+ *
+ * <p>In practice, a {@link org.apache.tapestry.html.Script} component
+ * works with the {@link org.apache.tapestry.html.Body} component to get the
+ * JavaScript code inserted and referenced.
+ *
+ **/
+
+ public String getName();
+
+ /**
+ * Invoked by {@link IForm#getElementId(IFormComponent)} when a name is created
+ * for a form component.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void setName(String name);
+
+ /**
+ * May be implemented to return a user-presentable, localized name for the component,
+ * which is used in labels or error messages. Most components simply return null.
+ *
+ * @since 1.0.9
+ *
+ **/
+
+ public String getDisplayName();
+
+ /**
+ * Returns true if the component is disabled. This is important when the containing
+ * form is submitted, since disabled parameters do not update their bindings.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public boolean isDisabled();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionModel.java b/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionModel.java
new file mode 100644
index 0000000..ba31ec6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionModel.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+/**
+ * Used by a {@link PropertySelection} to provide labels for options.
+ *
+ * <p>The component requires three different representations
+ * of each option:
+ * <ul>
+ * <li>The option value, a Java object that will eventually be assigned to
+ * a property
+ * <li>The label, a String which is incorprated into the HTML to identify the
+ * option to the user
+ * <li>The value, a String which is used to represent the option as the value
+ * of the <option> or <input type=radio> generated by the
+ * {@link PropertySelection}.
+ * </ul>
+ *
+ * <p>The option is usually either an {@link org.apache.commons.lang.enum.Enum}
+ * (see {@link EnumPropertySelectionModel})
+ * or some kind of business object. The label is often a property of the
+ * option object (for example, for a list of customers, it could be the customer name).
+ *
+ * <p>It should be easy to convert between the value and the option. It may simply
+ * be an index into an array. For business objects, it is often the primary key
+ * of the object, expressed as a String.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public interface IPropertySelectionModel
+{
+ /**
+ * Returns the number of possible options.
+ *
+ **/
+
+ public int getOptionCount();
+
+ /**
+ * Returns one possible option.
+ *
+ **/
+
+ public Object getOption(int index);
+
+ /**
+ * Returns the label for an option. It is the responsibility of the
+ * adaptor to make this value localized.
+ *
+ **/
+
+ public String getLabel(int index);
+
+ /**
+ * Returns a String used to represent the option in the HTML (as the
+ * value of an <option> or <input type=radio>.
+ *
+ **/
+
+ public String getValue(int index);
+
+ /**
+ * Returns the option corresponding to a value. This is used when
+ * interpreting submitted form parameters.
+ *
+ **/
+
+ public Object translateValue(String value);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionRenderer.java b/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionRenderer.java
new file mode 100644
index 0000000..137cf6c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/IPropertySelectionRenderer.java
@@ -0,0 +1,58 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Defines an object that works with a {@link PropertySelection} component
+ * to render the individual elements obtained from the {@link IPropertySelectionModel model}.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public interface IPropertySelectionRenderer
+{
+ /**
+ * Begins the rendering of the {@link PropertySelection}.
+ *
+ **/
+
+ public void beginRender(PropertySelection component, IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Invoked for each element obtained from the {@link IPropertySelectionModel model}.
+ *
+ **/
+
+ public void renderOption(
+ PropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionModel model,
+ Object option,
+ int index,
+ boolean selected);
+
+ /**
+ * Ends the rendering of the {@link PropertySelection}.
+ *
+ **/
+
+ public void endRender(PropertySelection component, IMarkupWriter writer, IRequestCycle cycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.java b/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.java
new file mode 100644
index 0000000..611ae2f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.java
@@ -0,0 +1,163 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.awt.Point;
+
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Used to create an image button inside a {@link Form}. Although it
+ * is occasionally useful to know the {@link Point} on the image that was clicked
+ * (i.e., use the image as a kind of image map, which was the original intent
+ * of the HTML element), it is more commonly used to provide a graphic
+ * image for the user to click, rather than the rather plain <input type=submit>.
+ *
+ * [<a href="../../../../../ComponentReference/ImageSubmit.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public abstract class ImageSubmit extends AbstractFormComponent
+{
+
+ public abstract IBinding getPointBinding();
+
+ public abstract IBinding getSelectedBinding();
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ String nameOverride = getNameOverride();
+
+ String name =
+ nameOverride == null ? form.getElementId(this) : form.getElementId(this, nameOverride);
+
+ if (rewinding)
+ {
+ // If disabled, do nothing.
+
+ if (isDisabled())
+ return;
+
+ RequestContext context = cycle.getRequestContext();
+
+ // Image clicks get submitted as two request parameters:
+ // foo.x and foo.y
+
+ String parameterName = name + ".x";
+
+ String value = context.getParameter(parameterName);
+
+ if (value == null)
+ return;
+
+ // The point parameter is not really used, unless the
+ // ImageButton is used for its original purpose (as a kind
+ // of image map). In modern usage, we only care about
+ // whether the user clicked on the image (and thus submitted
+ // the form), not where in the image the user actually clicked.
+
+ IBinding pointBinding = getPointBinding();
+
+ if (pointBinding != null)
+ {
+ int x = Integer.parseInt(value);
+
+ parameterName = name + ".y";
+ value = context.getParameter(parameterName);
+
+ int y = Integer.parseInt(value);
+
+ pointBinding.setObject(new Point(x, y));
+ }
+
+ // Notify the application, by setting the select parameter
+ // to the tag parameter.
+
+ IBinding selectedBinding = getSelectedBinding();
+
+ if (selectedBinding != null)
+ selectedBinding.setObject(getTag());
+
+ IActionListener listener = getListener();
+
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+
+ return;
+ }
+
+ // Not rewinding, do the real render
+
+ boolean disabled = isDisabled();
+ IAsset disabledImage = getDisabledImage();
+
+ IAsset finalImage = (disabled && disabledImage != null) ? disabledImage : getImage();
+
+ String imageURL = finalImage.buildURL(cycle);
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "image");
+ writer.attribute("name", name);
+
+ if (disabled)
+ writer.attribute("disabled", "disabled");
+
+ // NN4 places a border unless you tell it otherwise.
+ // IE ignores the border attribute and never shows a border.
+
+ writer.attribute("border", 0);
+
+ writer.attribute("src", imageURL);
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract IAsset getDisabledImage();
+
+ public abstract IAsset getImage();
+
+ public abstract IActionListener getListener();
+
+ public abstract Object getTag();
+
+ public abstract String getNameOverride();
+
+ protected void prepareForRender(IRequestCycle cycle)
+ {
+ super.prepareForRender(cycle);
+
+ if (getImage() == null)
+ throw Tapestry.createRequiredParameterException(this, "image");
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.jwc b/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.jwc
new file mode 100644
index 0000000..df5c8de
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/ImageSubmit.jwc
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.ImageSubmit" allow-body="no">
+
+ <description>
+ Creates a clickable image within a form.
+ </description>
+
+ <parameter name="image"
+ type="org.apache.tapestry.IAsset"
+ required="yes"
+ direction="in">
+ <description>
+ Image used for the button.
+ </description>
+ </parameter>
+
+ <parameter name="name"
+ property-name="nameOverride"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="disabledImage"
+ type="org.apache.tapestry.IAsset"
+ direction="in">
+ <description>
+ Image used for the button, if disabled.
+ </description>
+ </parameter>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="point" type="java.awt.Point"/>
+
+ <parameter name="selected"
+ type="java.lang.Object">
+ <description>
+ Property updated when the button is clicked.
+ </description>
+ </parameter>
+
+ <parameter name="tag"
+ type="java.lang.Object"
+ direction="in">
+ <description>
+ Value used when updating the selected parameter.
+ </description>
+ </parameter>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ direction="in">
+ <description>
+ Notified when the button is clicked.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="src"/>
+ <reserved-parameter name="border"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.java b/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.java
new file mode 100644
index 0000000..bd5860b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.java
@@ -0,0 +1,181 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.html.Body;
+
+/**
+ * Implements a component that submits its enclosing form via a JavaScript link.
+ *
+ * [<a href="../../../../../ComponentReference/LinkSubmit.html">Component Reference</a>]
+ *
+ * @author Richard Lewis-Shell
+ * @version $Id: Submit.java,v 1.6 2003/04/21 13:15:41 glongman Exp $
+ *
+ **/
+
+public abstract class LinkSubmit extends AbstractFormComponent
+{
+ /**
+ * The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the
+ * current submit link is stored. LinkSubmits do not nest.
+ *
+ **/
+
+ public static final String ATTRIBUTE_NAME = "org.apache.tapestry.form.LinkSubmit";
+
+ /**
+ * The name of an {@link org.apache.tapestry.IRequestCycle} attribute in which the
+ * link submit component that generates the javascript function is stored. The
+ * function is only required once per page (containing a form with a non-disabled
+ * LinkSubmit)
+ *
+ **/
+ public static final String ATTRIBUTE_FUNCTION_NAME =
+ "org.apache.tapestry.form.LinkSubmit_function";
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+
+ IForm form = getForm(cycle);
+ String formName = form.getName();
+
+ boolean rewinding = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ IMarkupWriter wrappedWriter;
+
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("LinkSubmit.may-not-nest"),
+ this,
+ null,
+ null);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ boolean disabled = isDisabled();
+ if (!disabled)
+ {
+ if (!rewinding)
+ {
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("must-be-contained-by-body", "LinkSubmit"),
+ this,
+ null,
+ null);
+
+ // make sure the submit function is on the page (once)
+ if (cycle.getAttribute(ATTRIBUTE_FUNCTION_NAME) == null)
+ {
+ body.addBodyScript(
+ "function submitLink(form, elementId) { form._linkSubmit.value = elementId; if (form.onsubmit == null || form.onsubmit()) form.submit(); }");
+ cycle.setAttribute(ATTRIBUTE_FUNCTION_NAME, this);
+ }
+
+ // one hidden field per form:
+ String formHiddenFieldAttributeName = ATTRIBUTE_FUNCTION_NAME + formName;
+ if (cycle.getAttribute(formHiddenFieldAttributeName) == null)
+ {
+ body.addInitializationScript("document." + formName + "._linkSubmit.value = null;");
+ writer.beginEmpty("input");
+ writer.attribute("type", "hidden");
+ writer.attribute("name", "_linkSubmit");
+ cycle.setAttribute(formHiddenFieldAttributeName, this);
+ }
+ }
+ else
+ {
+ // How to know which Submit link was actually
+ // clicked? When submitted, it sets its elementId into a hidden field
+
+ String value = cycle.getRequestContext().getParameter("_linkSubmit");
+
+ // If the value isn't the elementId of this component, then this link wasn't
+ // selected.
+
+ if (value != null && value.equals(name))
+ {
+ IBinding selectedBinding = getSelectedBinding();
+ if (selectedBinding != null)
+ selectedBinding.setObject(getTag());
+ IActionListener listener = getListener();
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+ }
+ }
+
+ writer.begin("a");
+ writer.attribute(
+ "href",
+ "javascript:submitLink(document." + formName + ",\"" + name + "\");");
+
+ // Allow the wrapped components a chance to render.
+ // Along the way, they may interact with this component
+ // and cause the name variable to get set.
+
+ wrappedWriter = writer.getNestedWriter();
+ }
+ else
+ wrappedWriter = writer;
+
+ renderBody(wrappedWriter, cycle);
+
+ if (!disabled)
+ {
+ // Generate additional attributes from informal parameters.
+
+ renderInformalParameters(writer, cycle);
+
+ // Dump in HTML provided by wrapped components
+
+ wrappedWriter.close();
+
+ // Close the <a> tag
+
+ writer.end();
+ }
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract void setDisabled(boolean disabled);
+
+ public abstract IActionListener getListener();
+
+ public abstract void setListener(IActionListener listener);
+
+ public abstract Object getTag();
+
+ public abstract void setTag(Object tag);
+
+ public abstract void setSelectedBinding(IBinding value);
+
+ public abstract IBinding getSelectedBinding();
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.jwc b/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.jwc
new file mode 100644
index 0000000..f41452b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/LinkSubmit.jwc
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id: Submit.jwc,v 1.2 2003/03/17 18:10:48 hlship Exp $ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.LinkSubmit" allow-body="yes">
+
+ <description>
+ Creates a hyperlink that submits its enclosing form using JavaScript.
+ </description>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ direction="in">
+ <description>
+ The listener is invoked during the rewind as the link component is encountered. This is
+ both attractive and dangerous when combined with a form. When the listener is
+ invoked, the form has not completely rewound, so not all form values have necessarily
+ been processed, so the listener might be performing its logic based on inconsistent
+ data.
+ </description>
+ </parameter>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+ <parameter name="selected"/>
+ <parameter name="tag" type="java.lang.Object" direction="in"/>
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="href"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/ListEdit.java b/tapestry-framework/src/org/apache/tapestry/form/ListEdit.java
new file mode 100644
index 0000000..2222752
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/ListEdit.java
@@ -0,0 +1,184 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.util.io.DataSqueezer;
+
+/**
+ * A specialized component used to edit a list of items
+ * within a form; it is similar to a {@link org.apache.tapestry.components.Foreach} but leverages
+ * hidden inputs within the <form> to store the items in the list.
+ *
+ * [<a href="../../../../../ComponentReference/ListEdit.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.2
+ *
+ **/
+
+public abstract class ListEdit extends AbstractFormComponent
+{
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Iterator i = null;
+
+ IForm form = getForm(cycle);
+
+ boolean cycleRewinding = cycle.isRewinding();
+
+ // If the cycle is rewinding, but not this particular form,
+ // then do nothing (don't even render the body).
+
+ if (cycleRewinding && !form.isRewinding())
+ return;
+
+ String name = form.getElementId(this);
+
+ if (!cycleRewinding)
+ {
+ i = Tapestry.coerceToIterator(getSourceBinding().getObject());
+ }
+ else
+ {
+ RequestContext context = cycle.getRequestContext();
+ String[] submittedValues = context.getParameters(name);
+
+ i = Tapestry.coerceToIterator(submittedValues);
+ }
+
+ // If the source (when rendering), or the submitted values (on submit)
+ // are null, then skip the remainder (nothing to update, nothing to
+ // render).
+
+ if (i == null)
+ return;
+
+ int index = 0;
+
+ IBinding indexBinding = getIndexBinding();
+ IBinding valueBinding = getValueBinding();
+ IActionListener listener = getListener();
+ String element = getElement();
+
+ while (i.hasNext())
+ {
+ Object value = null;
+
+ if (indexBinding != null)
+ indexBinding.setInt(index++);
+
+ if (cycleRewinding)
+ value = convertValue((String) i.next());
+ else
+ {
+ value = i.next();
+ writeValue(form, name, value);
+ }
+
+ valueBinding.setObject(value);
+
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+
+ if (element != null)
+ {
+ writer.begin(element);
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (element != null)
+ writer.end();
+
+ }
+ }
+
+ private void writeValue(IForm form, String name, Object value)
+ {
+ String externalValue;
+
+ try
+ {
+ externalValue = getDataSqueezer().squeeze(value);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ListEdit.unable-to-convert-value", value),
+ this,
+ null,
+ ex);
+ }
+
+ form.addHiddenValue(name, externalValue);
+ }
+
+ private Object convertValue(String value)
+ {
+ try
+ {
+ return getDataSqueezer().unsqueeze(value);
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ListEdit.unable-to-convert-string", value),
+ this,
+ null,
+ ex);
+ }
+ }
+
+ public abstract String getElement();
+
+ private DataSqueezer getDataSqueezer()
+ {
+ return getPage().getEngine().getDataSqueezer();
+ }
+
+ /** @since 2.2 **/
+
+ public abstract IActionListener getListener();
+
+ /** @since 3.0 **/
+
+ public abstract IBinding getSourceBinding();
+
+ public abstract IBinding getValueBinding();
+
+ public abstract IBinding getIndexBinding();
+
+ /** @since 3.0 **/
+
+ public boolean isDisabled()
+ {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/ListEdit.jwc b/tapestry-framework/src/org/apache/tapestry/form/ListEdit.jwc
new file mode 100644
index 0000000..4a03c43
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/ListEdit.jwc
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification allow-body="yes" allow-informal-parameters="yes"
+ class="org.apache.tapestry.form.ListEdit">
+
+ <description>
+ A looping component, like Foreach, which works well in a form
+ because it stores each element as a hidden field.
+ </description>
+
+ <parameter name="source" required="yes" type="java.lang.Object"/>
+
+ <parameter name="listener" type="org.apache.tapestry.IActionListener" direction="in"/>
+
+ <parameter name="value" required="yes" type="java.lang.Object"/>
+
+ <parameter name="index" type="int"/>
+
+ <parameter name="element" type="java.lang.String" direction="in"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
+
+
diff --git a/tapestry-framework/src/org/apache/tapestry/form/ListEditMap.java b/tapestry-framework/src/org/apache/tapestry/form/ListEditMap.java
new file mode 100644
index 0000000..8befa71
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/ListEditMap.java
@@ -0,0 +1,271 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A utility class often used with the {@link org.apache.tapestry.form.ListEdit}component. A
+ * ListEditMap is loaded with data objects before the ListEdit renders, and again before the
+ * ListEdit rewinds. This streamlines the synchronization of the form against data on the server. It
+ * is most useful when the set of objects is of a manageable size (say, no more than a few hundred
+ * objects).
+ * <p>
+ * The map stores a list of keys, and relates each key to a value. It also tracks a deleted flag for
+ * each key.
+ * <p>
+ * Usage: <br>
+ * The page or component should implement {@link org.apache.tapestry.event.PageRenderListener}and
+ * implement
+ * {@link org.apache.tapestry.event.PageRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)}
+ * to initialize the map.
+ * <p>
+ * The external data (from which keys and values are obtained) is queried, and each key/value pair
+ * is {@link #add(Object, Object) added}to the map, in the order that items should be presented.
+ * <p>
+ * The {@link org.apache.tapestry.form.ListEdit}'s source parameter should be bound to the map's
+ * {@link #getKeys() keys}property. The value parameter should be bound to the map's
+ * {@link #setKey(Object) key}property.
+ * <p>
+ * The {@link org.apache.tapestry.form.ListEdit}'s listener parameter should be bound to a listener
+ * method to synchronize a property of the component from the map.
+ *
+ * <pre><code>
+ *
+ *
+ *
+ * public void synchronize({@link org.apache.tapestry.IRequestCycle} cycle)
+ * {
+ * ListEditMap map = ...;
+ * <i>Type</i> object = (<i>Type</i>)map.getValue();
+ *
+ * if (object == null)
+ * ...
+ *
+ * set<i>Property</i>(object);
+ * }
+ *
+ *
+ *
+ * </code></pre>
+ *
+ * <p>
+ * You may also connect a {@link org.apache.tapestry.form.Checkbox}'s selected parameter to the
+ * map's {@link #isDeleted() deleted}property.
+ * <p>
+ * You may track inclusion in other sets by subclasses and implementing new boolean properties. The
+ * accessor method should be a call to {@link #checkSet(Set)}and the mutator method should be a
+ * call to {@link #updateSet(Set, boolean)}.
+ *
+ * @author Howard Lewis Ship
+ * @since 3.0
+ */
+
+public class ListEditMap
+{
+ private Map _map = new HashMap();
+
+ private List _keys = new ArrayList();
+
+ private Set _deletedKeys;
+
+ private Object _currentKey;
+
+ /**
+ * Records the key and value into this map. The keys may be obtained, in the order in which they
+ * are added, using {@link #getKeys()}. This also sets the current key (so that you may invoke
+ * {@link #setDeleted(boolean)}, for example).
+ */
+
+ public void add(Object key, Object value)
+ {
+ _currentKey = key;
+
+ _keys.add(_currentKey);
+ _map.put(_currentKey, value);
+ }
+
+ /**
+ * Returns a List of keys, in the order that keys were added to the map (using
+ * {@link #add(Object, Object)}. The caller must not modify the List.
+ */
+
+ public List getKeys()
+ {
+ return _keys;
+ }
+
+ /**
+ * Sets the key for the map. This defines the key used with the other methods:
+ * {@link #getValue()},{@link #isDeleted()},{@link #setDeleted(boolean)}.
+ */
+
+ public void setKey(Object key)
+ {
+ _currentKey = key;
+ }
+
+ /**
+ * Returns the current key within the map.
+ */
+
+ public Object getKey()
+ {
+ return _currentKey;
+ }
+
+ /**
+ * Returns the value for the key (set using {@link #setKey(Object)}). May return null if no
+ * such key has been added (this can occur if a data object is deleted between the time a form
+ * is rendered and the time a form is submitted).
+ */
+
+ public Object getValue()
+ {
+ return _map.get(_currentKey);
+ }
+
+ /**
+ * Returns true if the {@link #setKey(Object) current key}is in the set of deleted keys.
+ */
+
+ public boolean isDeleted()
+ {
+ return checkSet(_deletedKeys);
+ }
+
+ /**
+ * Returns true if the set contains the {@link #getKey() current key}. Returns false is the set
+ * is null, or doesn't contain the current key.
+ */
+
+ protected boolean checkSet(Set set)
+ {
+ if (set == null)
+ return false;
+
+ return set.contains(_currentKey);
+ }
+
+ /**
+ * Adds or removes the {@link #setKey(Object) current key}from the set of deleted keys.
+ */
+
+ public void setDeleted(boolean value)
+ {
+ _deletedKeys = updateSet(_deletedKeys, value);
+ }
+
+ /**
+ * Updates the set, adding or removing the {@link #getKey() current key}from it. Returns the
+ * set passed in. If the value is true and the set is null, an new instance of {@link HashSet}
+ * is created and returned.
+ */
+
+ protected Set updateSet(Set set, boolean value)
+ {
+ if (value)
+ {
+ if (set == null)
+ set = new HashSet();
+
+ set.add(_currentKey);
+ }
+ else
+ {
+ if (set != null)
+ set.remove(_currentKey);
+ }
+
+ return set;
+ }
+
+ /**
+ * Returns the deleted keys in an unspecified order. May return an empty list.
+ */
+
+ public List getDeletedKeys()
+ {
+ return convertSetToList(_deletedKeys);
+ }
+
+ protected List convertSetToList(Set set)
+ {
+ if (Tapestry.isEmpty(set))
+ return Collections.EMPTY_LIST;
+
+ return new ArrayList(set);
+ }
+
+ /**
+ * Returns all the values stored in the map, in the order in which values were added to the map
+ * using {@link #add(Object, Object)}.
+ */
+
+ public List getAllValues()
+ {
+ int count = _keys.size();
+ List result = new ArrayList(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ Object key = _keys.get(i);
+ Object value = _map.get(key);
+
+ result.add(value);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns all the values stored in the map, excluding those whose id has been marked deleted,
+ * in the order in which values were added to the map using {@link #add(Object, Object)}.
+ */
+
+ public List getValues()
+ {
+ int deletedCount = Tapestry.size(_deletedKeys);
+
+ if (deletedCount == 0)
+ return getAllValues();
+
+ int count = _keys.size();
+
+ List result = new ArrayList(count - deletedCount);
+
+ for (int i = 0; i < count; i++)
+ {
+ Object key = _keys.get(i);
+
+ if (_deletedKeys.contains(key))
+ continue;
+
+ Object value = _map.get(key);
+ result.add(value);
+ }
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Option.java b/tapestry-framework/src/org/apache/tapestry/form/Option.java
new file mode 100644
index 0000000..0959e3e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Option.java
@@ -0,0 +1,97 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A component that renders an HTML <option> form element.
+ * Such a component must be wrapped (possibly indirectly)
+ * inside a {@link Select} component.
+ *
+ * [<a href="../../../../../ComponentReference/Option.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Option extends AbstractComponent
+{
+ /**
+ * Renders the <option> element, or responds when the form containing the element
+ * is submitted (by checking {@link Form#isRewinding()}.
+ *
+ * <p>If the <code>label</code> property is set, it is inserted inside the
+ * <option> element.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ Select select = Select.get(cycle);
+ if (select == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Option.must-be-contained-by-select"),
+ this,
+ null,
+ null);
+
+ // It isn't enough to know whether the cycle in general is rewinding, need to know
+ // specifically if the form which contains this component is rewinding.
+
+ boolean rewinding = select.isRewinding();
+
+ String value = select.getNextOptionId();
+
+ if (rewinding)
+ {
+ if (!select.isDisabled())
+ getSelectedBinding().setBoolean(select.isSelected(value));
+
+ renderBody(writer, cycle);
+ }
+ else
+ {
+ writer.begin("option");
+
+ writer.attribute("value", value);
+
+ if (getSelectedBinding().getBoolean())
+ writer.attribute("selected", "selected");
+
+ renderInformalParameters(writer, cycle);
+
+ String label = getLabel();
+
+ if (label != null)
+ writer.print(label);
+
+ renderBody(writer, cycle);
+
+ writer.end();
+ }
+
+ }
+
+ public abstract IBinding getSelectedBinding();
+
+ public abstract String getLabel();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Option.jwc b/tapestry-framework/src/org/apache/tapestry/form/Option.jwc
new file mode 100644
index 0000000..06040e4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Option.jwc
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Option" allow-body="yes">
+
+ <description>
+ A single option within a Select.
+ </description>
+
+ <parameter name="selected" type="java.lang.Boolean" required="yes"/>
+ <parameter name="label" type="java.lang.String" direction="in"/>
+
+ <reserved-parameter name="value"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.java b/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.java
new file mode 100644
index 0000000..fb19c52
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.java
@@ -0,0 +1,248 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A component used to render a drop-down list of options that
+ * the user may select.
+ *
+ * [<a href="../../../../../ComponentReference/PropertySelection.html">Component Reference</a>]
+ *
+ * <p>Earlier versions of PropertySelection (through release 2.2)
+ * were more flexible, they included a <b>renderer</b> property
+ * that controlled how the selection was rendered. Ultimately,
+ * this proved of little value and this portion of
+ * functionality was deprecated in 2.3 and will be removed in 2.3.
+ *
+ * <p>Typically, the values available to be selected
+ * are defined using an {@link org.apache.commons.lang.enum.Enum}.
+ * A PropertySelection is dependent on
+ * an {@link IPropertySelectionModel} to provide the list of possible values.
+ *
+ * <p>Often, this is used to select a particular
+ * {@link org.apache.commons.lang.enum.Enum} to assign to a property; the
+ * {@link EnumPropertySelectionModel} class simplifies this.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public abstract class PropertySelection extends AbstractFormComponent
+{
+ /**
+ * A shared instance of {@link SelectPropertySelectionRenderer}.
+ *
+ *
+ **/
+
+ public static final IPropertySelectionRenderer DEFAULT_SELECT_RENDERER =
+ new SelectPropertySelectionRenderer();
+
+ /**
+ * A shared instance of {@link RadioPropertySelectionRenderer}.
+ *
+ *
+ **/
+
+ public static final IPropertySelectionRenderer DEFAULT_RADIO_RENDERER =
+ new RadioPropertySelectionRenderer();
+
+ /**
+ * Renders the component, much of which is the responsiblity
+ * of the {@link IPropertySelectionRenderer renderer}. The possible options,
+ * thier labels, and the values to be encoded in the form are provided
+ * by the {@link IPropertySelectionModel model}.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ // If disabled, ignore anything that comes up from the client.
+
+ if (isDisabled())
+ return;
+
+ String optionValue = cycle.getRequestContext().getParameter(name);
+
+ Object value = (optionValue == null) ? null : getModel().translateValue(optionValue);
+
+ setValue(value);
+
+ return;
+ }
+
+ IPropertySelectionRenderer renderer = getRenderer();
+
+ if (renderer != null)
+ {
+ renderWithRenderer(writer, cycle, renderer);
+ return;
+ }
+
+ writer.begin("select");
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ if (getSubmitOnChange())
+ writer.attribute("onchange", "javascript:this.form.submit();");
+
+ // Apply informal attributes.
+
+ renderInformalParameters(writer, cycle);
+
+ writer.println();
+
+ IPropertySelectionModel model = getModel();
+
+ if (model == null)
+ throw Tapestry.createRequiredParameterException(this, "model");
+
+ int count = model.getOptionCount();
+ boolean foundSelected = false;
+ boolean selected = false;
+ Object value = getValue();
+
+ for (int i = 0; i < count; i++)
+ {
+ Object option = model.getOption(i);
+
+ if (!foundSelected)
+ {
+ selected = isEqual(option, value);
+ if (selected)
+ foundSelected = true;
+ }
+
+ writer.begin("option");
+ writer.attribute("value", model.getValue(i));
+
+ if (selected)
+ writer.attribute("selected", "selected");
+
+ writer.print(model.getLabel(i));
+
+ writer.end();
+
+ writer.println();
+
+ selected = false;
+ }
+
+ writer.end(); // <select>
+
+ }
+
+ /**
+ * Renders the property selection using a {@link IPropertySelectionRenderer}.
+ * Support for this will be removed in 2.3.
+ *
+ **/
+
+ private void renderWithRenderer(
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionRenderer renderer)
+ {
+ renderer.beginRender(this, writer, cycle);
+
+ IPropertySelectionModel model = getModel();
+
+ int count = model.getOptionCount();
+
+ boolean foundSelected = false;
+ boolean selected = false;
+
+ Object value = getValue();
+
+ for (int i = 0; i < count; i++)
+ {
+ Object option = model.getOption(i);
+
+ if (!foundSelected)
+ {
+ selected = isEqual(option, value);
+ if (selected)
+ foundSelected = true;
+ }
+
+ renderer.renderOption(this, writer, cycle, model, option, i, selected);
+
+ selected = false;
+ }
+
+ // A PropertySelection doesn't allow a body, so no need to worry about
+ // wrapped components.
+
+ renderer.endRender(this, writer, cycle);
+ }
+
+ private boolean isEqual(Object left, Object right)
+ {
+ // Both null, or same object, then are equal
+
+ if (left == right)
+ return true;
+
+ // If one is null, the other isn't, then not equal.
+
+ if (left == null || right == null)
+ return false;
+
+ // Both non-null; use standard comparison.
+
+ return left.equals(right);
+ }
+
+ public abstract IPropertySelectionModel getModel();
+
+ public abstract IPropertySelectionRenderer getRenderer();
+
+ /** @since 2.2 **/
+
+ public abstract boolean getSubmitOnChange();
+
+ /** @since 2.2 **/
+
+ public abstract Object getValue();
+
+ /** @since 2.2 **/
+
+ public abstract void setValue(Object value);
+
+ /**
+ * Returns true if this PropertySelection's disabled parameter yields true.
+ * The corresponding HTML control(s) should be disabled.
+ **/
+
+ public abstract boolean isDisabled();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.jwc b/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.jwc
new file mode 100644
index 0000000..4a88edc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/PropertySelection.jwc
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.PropertySelection"
+ allow-body="no"
+ allow-informal-parameters="yes">
+
+ <description>
+ Creates an HTML select to choose a single property from a list of options.
+ </description>
+
+ <parameter name="value" required="yes" type="java.lang.Object" direction="form"/>
+
+ <parameter name="model"
+ type="org.apache.tapestry.form.IPropertySelectionModel"
+ required="yes"
+ direction="in"/>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.form.IPropertySelectionRenderer"
+ direction="in">
+ <description>
+ An alternate rendered for the property selection.
+ </description>
+ </parameter>
+
+ <parameter name="submitOnChange"
+ type="boolean"
+ direction="in">
+ <description>
+ Enables logic to submit containing form when value changes.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Radio.java b/tapestry-framework/src/org/apache/tapestry/form/Radio.java
new file mode 100644
index 0000000..120be4a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Radio.java
@@ -0,0 +1,105 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Implements a component that manages an HTML <input type=radio> form element.
+ * Such a component must be wrapped (possibly indirectly)
+ * inside a {@link RadioGroup} component.
+ *
+ * [<a href="../../../../../ComponentReference/Radio.html">Component Reference</a>]
+ *
+ *
+ * <p>{@link Radio} and {@link RadioGroup} are generally not used (except
+ * for very special cases). Instead, a {@link PropertySelection} component is used.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Radio extends AbstractComponent
+{
+ /**
+ * Renders the form element, or responds when the form containing the element
+ * is submitted (by checking {@link Form#isRewinding()}.
+ *
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+
+ RadioGroup group = RadioGroup.get(cycle);
+ if (group == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Radio.must-be-contained-by-group"),
+ this,
+ null,
+ null);
+
+ // The group determines rewinding from the form.
+
+ boolean rewinding = group.isRewinding();
+
+ int option = group.getNextOptionId();
+
+ if (rewinding)
+ {
+ // If not disabled and this is the selected button within the radio group,
+ // then update set the selection from the group to the value for this
+ // radio button. This will update the selected parameter of the RadioGroup.
+
+ if (!isDisabled() && !group.isDisabled() && group.isSelected(option))
+ group.updateSelection(getValue());
+ return;
+ }
+
+ writer.beginEmpty("input");
+
+ writer.attribute("type", "radio");
+
+ writer.attribute("name", group.getName());
+
+ // As the group if the value for this Radio matches the selection
+ // for the group as a whole; if so this is the default radio and is checked.
+
+ if (group.isSelection(getValue()))
+ writer.attribute("checked", "checked");
+
+ if (isDisabled() || group.isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ // The value for the Radio matches the option number (provided by the RadioGroup).
+ // When the form is submitted, the RadioGroup will know which option was,
+ // in fact, selected by the user.
+
+ writer.attribute("value", option);
+
+ renderInformalParameters(writer, cycle);
+
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract Object getValue();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Radio.jwc b/tapestry-framework/src/org/apache/tapestry/form/Radio.jwc
new file mode 100644
index 0000000..5785794
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Radio.jwc
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Radio" allow-body="no">
+
+ <description>
+ A single possible selection within a RadioGroup.
+ </description>
+
+ <parameter name="value" type="java.lang.Object" direction="in"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <reserved-parameter name="checked"/>
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="name"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.java b/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.java
new file mode 100644
index 0000000..ca441ac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.java
@@ -0,0 +1,196 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A special type of form component that is used to contain {@link Radio}
+ * components. The Radio and {@link Radio} group components work together to
+ * update a property of some other object, much like a more flexible
+ * version of a {@link PropertySelection}.
+ *
+ * [<a href="../../../../../ComponentReference/RadioGroup.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class RadioGroup extends AbstractFormComponent
+{
+ // Cached copy of the value from the selectedBinding
+ private Object _selection;
+
+ // The value from the HTTP request indicating which
+ // Radio was selected by the user.
+ private int _selectedOption;
+
+ private boolean _rewinding;
+ private boolean _rendering;
+ private int _nextOptionId;
+
+ /**
+ * A <code>RadioGroup</code> places itself into the {@link IRequestCycle} as
+ * an attribute, so that its wrapped {@link Radio} components can identify thier
+ * state.
+ *
+ **/
+
+ private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.RadioGroup";
+
+ public static RadioGroup get(IRequestCycle cycle)
+ {
+ return (RadioGroup) cycle.getAttribute(ATTRIBUTE_NAME);
+ }
+
+ public abstract IBinding getSelectedBinding();
+
+ public int getNextOptionId()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "nextOptionId");
+
+ return _nextOptionId++;
+ }
+
+ /**
+ * Used by {@link Radio} components wrapped by this <code>RadioGroup</code> to see
+ * if the group as a whole is disabled.
+ *
+ **/
+
+ public abstract boolean isDisabled();
+
+ public boolean isRewinding()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
+
+ return _rewinding;
+ }
+
+ /**
+ * Returns true if the value is equal to the current selection for the
+ * group. This is invoked by a {@link Radio} during rendering
+ * to determine if it should be marked 'checked'.
+ *
+ **/
+
+ public boolean isSelection(Object value)
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "selection");
+
+ if (_selection == value)
+ return true;
+
+ if (_selection == null || value == null)
+ return false;
+
+ return _selection.equals(value);
+ }
+
+ /**
+ * Invoked by the {@link Radio} which is selected to update the
+ * property bound to the selected parameter.
+ *
+ **/
+
+ public void updateSelection(Object value)
+ {
+ getSelectedBinding().setObject(value);
+ }
+
+ /**
+ * Used by {@link Radio} components when rewinding to see if their value was submitted.
+ *
+ **/
+
+ public boolean isSelected(int option)
+ {
+ return _selectedOption == option;
+ }
+
+ /**
+ * Doesn't actual render an HTML element as there is no direct equivalent for
+ * an HTML element. A <code>RadioGroup</code> component exists to organize the
+ * {@link Radio} components it wraps (directly or indirectly).
+ *
+ * A {@link Radio} can finds its {@link RadioGroup} as a {@link IRequestCycle} attribute.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("RadioGroup.may-not-nest"),
+ this,
+ null,
+ null);
+
+ // It isn't enough to know whether the cycle in general is rewinding, need to know
+ // specifically if the form which contains this component is rewinding.
+
+ _rewinding = form.isRewinding();
+
+ // Used whether rewinding or not.
+
+ String name = form.getElementId(this);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ // When rewinding, find out which (if any) radio was selected by
+ // the user.
+
+ if (_rewinding)
+ {
+ String value = cycle.getRequestContext().getParameter(name);
+ if (value == null)
+ _selectedOption = -1;
+ else
+ _selectedOption = Integer.parseInt(value);
+ }
+
+ try
+ {
+ _rendering = true;
+ _nextOptionId = 0;
+
+ // For rendering, the Radio components need to know what the current
+ // selection is, so that the correct one can mark itself 'checked'.
+
+ if (!_rewinding)
+ _selection = getSelectedBinding().getObject();
+
+ renderBody(writer, cycle);
+ }
+ finally
+ {
+ _rendering = false;
+ _selection = null;
+ }
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.jwc b/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.jwc
new file mode 100644
index 0000000..2f3f6dd
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/RadioGroup.jwc
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.RadioGroup" allow-informal-parameters="no">
+
+ <description>
+ Groups together a number of Radio components.
+ </description>
+
+ <parameter name="selected" required="yes"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/RadioPropertySelectionRenderer.java b/tapestry-framework/src/org/apache/tapestry/form/RadioPropertySelectionRenderer.java
new file mode 100644
index 0000000..8eb27a2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/RadioPropertySelectionRenderer.java
@@ -0,0 +1,95 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implementation of {@link IPropertySelectionRenderer} that
+ * produces a table of radio (<input type=radio>) elements.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class RadioPropertySelectionRenderer implements IPropertySelectionRenderer
+{
+
+ /**
+ * Writes the <table> element.
+ *
+ **/
+
+ public void beginRender(PropertySelection component, IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writer.begin("table");
+ writer.attribute("border", 0);
+ writer.attribute("cellpadding", 0);
+ writer.attribute("cellspacing", 2);
+ }
+
+ /**
+ * Closes the <table> element.
+ *
+ **/
+
+ public void endRender(PropertySelection component, IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writer.end(); // <table>
+ }
+
+ /**
+ * Writes a row of the table. The table contains two cells; the first is the radio
+ * button, the second is the label for the radio button.
+ *
+ **/
+
+ public void renderOption(
+ PropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionModel model,
+ Object option,
+ int index,
+ boolean selected)
+ {
+ writer.begin("tr");
+ writer.begin("td");
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "radio");
+ writer.attribute("name", component.getName());
+ writer.attribute("value", model.getValue(index));
+
+ if (component.isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ if (selected)
+ writer.attribute("checked", "checked");
+
+ writer.end(); // <td>
+
+ writer.println();
+
+ writer.begin("td");
+ writer.print(model.getLabel(index));
+ writer.end(); // <td>
+ writer.end(); // <tr>
+
+ writer.println();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Select.java b/tapestry-framework/src/org/apache/tapestry/form/Select.java
new file mode 100644
index 0000000..fe2bedf
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Select.java
@@ -0,0 +1,187 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+
+/**
+ * Implements a component that manages an HTML <select> form element.
+ * The most common situation, using a <select> to set a specific
+ * property of some object, is best handled using a {@link PropertySelection} component.
+ *
+ * [<a href="../../../../../ComponentReference/Select.html">Component Reference</a>]
+ *
+ * <p>Otherwise, this component is very similar to {@link RadioGroup}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Select extends AbstractFormComponent
+{
+ private boolean _rewinding;
+ private boolean _rendering;
+
+ private Set _selections;
+ private int _nextOptionId;
+
+ /**
+ * Used by the <code>Select</code> to record itself as a
+ * {@link IRequestCycle} attribute, so that the
+ * {@link Option} components it wraps can have access to it.
+ *
+ **/
+
+ private final static String ATTRIBUTE_NAME = "org.apache.tapestry.active.Select";
+
+ public static Select get(IRequestCycle cycle)
+ {
+ return (Select) cycle.getAttribute(ATTRIBUTE_NAME);
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract boolean isMultiple();
+
+ public boolean isRewinding()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "rewinding");
+
+ return _rewinding;
+ }
+
+ public String getNextOptionId()
+ {
+ if (!_rendering)
+ throw Tapestry.createRenderOnlyPropertyException(this, "nextOptionId");
+
+ // Return it as a hex value.
+
+ return Integer.toString(_nextOptionId++);
+ }
+
+ public boolean isSelected(String value)
+ {
+ if (_selections == null)
+ return false;
+
+ return _selections.contains(value);
+ }
+
+ /**
+ * Renders the <option> element, or responds when the form containing the element
+ * is submitted (by checking {@link IForm#isRewinding()}.
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Select.may-not-nest"),
+ this,
+ null,
+ null);
+
+ // It isn't enough to know whether the cycle in general is rewinding, need to know
+ // specifically if the form which contains this component is rewinding.
+
+ _rewinding = form.isRewinding();
+
+ // Used whether rewinding or not.
+
+ String name = form.getElementId(this);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ if (_rewinding)
+ {
+ _selections = buildSelections(cycle, name);
+ }
+ else
+ {
+ writer.begin("select");
+
+ writer.attribute("name", name);
+
+ if (isMultiple())
+ writer.attribute("multiple", "multiple");
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ _rendering = true;
+ _nextOptionId = 0;
+
+ renderBody(writer, cycle);
+
+ if (!_rewinding)
+ {
+ writer.end();
+ }
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ _rendering = false;
+ _selections = null;
+
+ super.cleanupAfterRender(cycle);
+ }
+
+ /**
+ * Cut-and-paste with {@link RadioGroup}!
+ *
+ **/
+
+ private Set buildSelections(IRequestCycle cycle, String parameterName)
+ {
+ RequestContext context = cycle.getRequestContext();
+
+ String[] parameters = context.getParameters(parameterName);
+
+ if (parameters == null)
+ return null;
+
+ int length = parameters.length;
+
+ int size = (parameters.length > 30) ? 101 : 7;
+
+ Set result = new HashSet(size);
+
+ for (int i = 0; i < length; i++)
+ result.add(parameters[i]);
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Select.jwc b/tapestry-framework/src/org/apache/tapestry/form/Select.jwc
new file mode 100644
index 0000000..f8507ed
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Select.jwc
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Select">
+
+ <description>
+ Creates an HTML select populated with a number of options.
+ </description>
+
+ <parameter name="multiple" type="boolean" direction="in"/>
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/SelectPropertySelectionRenderer.java b/tapestry-framework/src/org/apache/tapestry/form/SelectPropertySelectionRenderer.java
new file mode 100644
index 0000000..0a46ad6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/SelectPropertySelectionRenderer.java
@@ -0,0 +1,93 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implementation of {@link IPropertySelectionRenderer} that
+ * produces a <select> element (containing <option> elements).
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class SelectPropertySelectionRenderer
+ implements IPropertySelectionRenderer
+{
+ /**
+ * Writes the <select> element. If the
+ * {@link PropertySelection} is {@link PropertySelection#isDisabled() disabled}
+ * then a <code>disabled</code> attribute is written into the tag
+ * (though Navigator 4 will ignore this).
+ *
+ **/
+
+ public void beginRender(
+ PropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ writer.begin("select");
+ writer.attribute("name", component.getName());
+
+ if (component.isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ writer.println();
+ }
+
+ /**
+ * Closes the <select> element.
+ *
+ **/
+
+ public void endRender(
+ PropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ writer.end(); // <select>
+ }
+
+ /**
+ * Writes an <option> element.
+ *
+ **/
+
+ public void renderOption(
+ PropertySelection component,
+ IMarkupWriter writer,
+ IRequestCycle cycle,
+ IPropertySelectionModel model,
+ Object option,
+ int index,
+ boolean selected)
+ {
+ writer.beginEmpty("option");
+ writer.attribute("value", model.getValue(index));
+
+ if (selected)
+ writer.attribute("selected", "selected");
+
+ writer.print(model.getLabel(index));
+
+ writer.end();
+
+ writer.println();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/StringPropertySelectionModel.java b/tapestry-framework/src/org/apache/tapestry/form/StringPropertySelectionModel.java
new file mode 100644
index 0000000..f9a591f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/StringPropertySelectionModel.java
@@ -0,0 +1,84 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+/**
+ * Implementation of {@link IPropertySelectionModel} that allows one String from
+ * an array of Strings to be selected as the property.
+ *
+ * <p>Uses a simple index number as the value (used to represent the selected String).
+ * This assumes that the possible values for the Strings will remain constant between
+ * request cycles.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class StringPropertySelectionModel implements IPropertySelectionModel
+{
+ private String[] options;
+
+ /**
+ * Standard constructor.
+ *
+ * The options are retained (not copied).
+ **/
+
+ public StringPropertySelectionModel(String[] options)
+ {
+ this.options = options;
+ }
+
+ public int getOptionCount()
+ {
+ return options.length;
+ }
+
+ public Object getOption(int index)
+ {
+ return options[index];
+ }
+
+ /**
+ * Labels match options.
+ *
+ **/
+
+ public String getLabel(int index)
+ {
+ return options[index];
+ }
+
+ /**
+ * Values are indexes into the array of options.
+ *
+ **/
+
+ public String getValue(int index)
+ {
+ return Integer.toString(index);
+ }
+
+ public Object translateValue(String value)
+ {
+ int index;
+
+ index = Integer.parseInt(value);
+
+ return options[index];
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Submit.java b/tapestry-framework/src/org/apache/tapestry/form/Submit.java
new file mode 100644
index 0000000..9de335c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Submit.java
@@ -0,0 +1,112 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+
+/**
+ * Implements a component that manages an HTML <input type=submit> form element.
+ *
+ * [<a href="../../../../../ComponentReference/Submit.html">Component Reference</a>]
+ *
+ * <p>This component is generally only used when the form has multiple
+ * submit buttons, and it is important for the application to know
+ * which one was pressed. You may also want to use
+ * {@link ImageSubmit} which accomplishes much the same thing, but uses
+ * a graphic image instead.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Submit extends AbstractFormComponent{
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ // Don't bother doing anything if disabled.
+
+ if (isDisabled())
+ return;
+
+ // How to know which Submit button was actually
+ // clicked? When submitted, it produces a request parameter
+ // with its name and value (the value serves double duty as both
+ // the label on the button, and the parameter value).
+
+ String value = cycle.getRequestContext().getParameter(name);
+
+ // If the value isn't there, then this button wasn't
+ // selected.
+
+ if (value == null)
+ return;
+
+ IBinding selectedBinding = getSelectedBinding();
+
+ if (selectedBinding != null)
+ selectedBinding.setObject(getTag());
+
+ IActionListener listener = getListener();
+
+ if (listener != null)
+ listener.actionTriggered(this, cycle);
+
+ return;
+ }
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "submit");
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ String label = getLabel();
+
+ if (label != null)
+ writer.attribute("value", label);
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ public abstract String getLabel();
+
+ public abstract IBinding getSelectedBinding();
+
+ public abstract boolean isDisabled();
+
+ public abstract IActionListener getListener();
+
+ public abstract Object getTag();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Submit.jwc b/tapestry-framework/src/org/apache/tapestry/form/Submit.jwc
new file mode 100644
index 0000000..b3a8d3f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Submit.jwc
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.Submit" allow-body="no">
+
+ <description>
+ Creates a labeled submit button within a form.
+ </description>
+
+ <parameter name="label" type="java.lang.String" direction="in"/>
+ <parameter name="disabled" type="boolean" direction="in"/>
+ <parameter name="selected"/>
+ <parameter name="tag" type="java.lang.Object" direction="in"/>
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ direction="in"/>
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="type"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/TextArea.java b/tapestry-framework/src/org/apache/tapestry/form/TextArea.java
new file mode 100644
index 0000000..b6c3516
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/TextArea.java
@@ -0,0 +1,88 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implements a component that manages an HTML <textarea> form element.
+ *
+ * [<a href="../../../../../ComponentReference/TextArea.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class TextArea extends AbstractFormComponent
+{
+
+ /**
+ * Renders the form element, or responds when the form containing the element
+ * is submitted (by checking {@link Form#isRewinding()}.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ // It isn't enough to know whether the cycle in general is rewinding, need to know
+ // specifically if the form which contains this component is rewinding.
+
+ boolean rewinding = form.isRewinding();
+
+ // Used whether rewinding or not.
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ if (!isDisabled())
+ setValue(cycle.getRequestContext().getParameter(name));
+
+ return;
+ }
+
+ if (cycle.isRewinding())
+ return;
+
+ writer.begin("textarea");
+
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ renderInformalParameters(writer, cycle);
+
+ String value = getValue();
+
+ if (value != null)
+ writer.print(value);
+
+ writer.end();
+
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract String getValue();
+
+ public abstract void setValue(String value);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/TextArea.jwc b/tapestry-framework/src/org/apache/tapestry/form/TextArea.jwc
new file mode 100644
index 0000000..416e584
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/TextArea.jwc
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.TextArea" allow-body="no">
+
+ <description>
+ A multi-line text area.
+ </description>
+
+ <parameter name="value" type="java.lang.String" required="yes" direction="form"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/TextField.java b/tapestry-framework/src/org/apache/tapestry/form/TextField.java
new file mode 100644
index 0000000..f632e56
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/TextField.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IBinding;
+
+/**
+ * Implements a component that manages an HTML <input type=text> or
+ * <input type=password> form element.
+ *
+ * [<a href="../../../../../ComponentReference/TextField.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class TextField extends AbstractTextField
+{
+ public abstract IBinding getValueBinding();
+
+ public String readValue()
+ {
+ return (String) getValueBinding().getObject("value", String.class);
+ }
+
+ public void updateValue(String value)
+ {
+ getValueBinding().setString(value);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/TextField.jwc b/tapestry-framework/src/org/apache/tapestry/form/TextField.jwc
new file mode 100644
index 0000000..d00208d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/TextField.jwc
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.form.TextField" allow-body="no">
+
+ <description>
+ A text input field.
+ </description>
+
+ <parameter name="value" type="java.lang.String" required="yes"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <parameter name="hidden" type="boolean" direction="in"/>
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="value"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Upload.java b/tapestry-framework/src/org/apache/tapestry/form/Upload.java
new file mode 100644
index 0000000..e71dbc1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Upload.java
@@ -0,0 +1,72 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.form;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.request.IUploadFile;
+
+/**
+ * Form element used to upload files. For the momement, it is necessary to
+ * explicitly set the form's enctype to "multipart/form-data".
+ *
+ * [<a href="../../../../../ComponentReference/Upload.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public abstract class Upload extends AbstractFormComponent
+{
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ String name = form.getElementId(this);
+
+ if (form.isRewinding())
+ {
+ if (!isDisabled())
+ setFile(cycle.getRequestContext().getUploadFile(name));
+
+ return;
+ }
+
+ // Force the form to use the correct encoding type for
+ // file uploads.
+
+ form.setEncodingType("multipart/form-data");
+
+ writer.beginEmpty("input");
+ writer.attribute("type", "file");
+ writer.attribute("name", name);
+
+ if (isDisabled())
+ writer.attribute("disabled", "disabled");
+
+ // Size, width, etc. can be specified as informal parameters
+ // (Not making the same mistake here that was made with TextField
+ // and friends).
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ public abstract boolean isDisabled();
+
+ public abstract void setFile(IUploadFile file);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/form/Upload.jwc b/tapestry-framework/src/org/apache/tapestry/form/Upload.jwc
new file mode 100644
index 0000000..d082cbe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/Upload.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+
+<!-- $Id$ -->
+
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+
+<component-specification class="org.apache.tapestry.form.Upload" allow-body="no">
+
+ <description>
+ Allows a file to be uploaded as part of a form.
+ </description>
+
+ <parameter name="file" type="org.apache.tapestry.request.IUploadFile" required="yes" direction="form">
+ <description>
+ Parameter updated with the information (filename and content) of the file
+ when the form is submitted.
+ </description>
+ </parameter>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/form/package.html b/tapestry-framework/src/org/apache/tapestry/form/package.html
new file mode 100644
index 0000000..e6b9701
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/form/package.html
@@ -0,0 +1,20 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Components for implementing basic HTML Forms. Most
+components are straight forward 1:1 mappings between Tapestry components and a
+corresponding HTML element. {@link org.apache.tapestry.form.PropertySelection} is more complicated,
+as it manages way more of the process of implementing a <select> and its <option>s.
+
+<p>Package {@link org.apache.tapestry.valid} contains more complex components that not only collect
+input, but validate it as well.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/BasePage.java b/tapestry-framework/src/org/apache/tapestry/html/BasePage.java
new file mode 100644
index 0000000..452b422
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/BasePage.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.io.OutputStream;
+
+import org.apache.tapestry.AbstractPage;
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * Concrete class for HTML pages. Most pages
+ * should be able to simply subclass this, adding new properties and
+ * methods. An unlikely exception would be a page that was not based
+ * on a template.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ **/
+
+public class BasePage extends AbstractPage
+{
+ /**
+ * Returns a new {@link HTMLWriter}.
+ *
+ **/
+
+ public IMarkupWriter getResponseWriter(OutputStream out)
+ {
+ return new HTMLWriter(out, getOutputEncoding());
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Body.java b/tapestry-framework/src/org/apache/tapestry/html/Body.java
new file mode 100644
index 0000000..6dcca9c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Body.java
@@ -0,0 +1,406 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScriptProcessor;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.asset.PrivateAsset;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+import org.apache.tapestry.util.IdAllocator;
+
+/**
+ * The body of a Tapestry page. This is used since it allows components on the
+ * page access to an initialization script (that is written the start, just inside
+ * the <body> tag). This is currently used by {@link Rollover} and {@link Script}
+ * components.
+ *
+ * [<a href="../../../../../ComponentReference/Body.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Body extends AbstractComponent implements IScriptProcessor
+{
+ // Lines that belong inside the onLoad event handler for the <body> tag.
+ private StringBuffer _initializationScript;
+
+ // The writer initially passed to render() ... wrapped elements render
+ // into a nested response writer.
+
+ private IMarkupWriter _outerWriter;
+
+ // Any other scripting desired
+
+ private StringBuffer _bodyScript;
+
+ // Contains text lines related to image initializations
+
+ private StringBuffer _imageInitializations;
+
+ /**
+ * Map of URLs to Strings (preloaded image references).
+ *
+ **/
+
+ private Map _imageMap;
+
+ /**
+ * List of included scripts. Values are Strings.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ private List _externalScripts;
+
+ private IdAllocator _idAllocator;
+
+ private static final String ATTRIBUTE_NAME = "org.apache.tapestry.active.Body";
+
+ /**
+ * Tracks a particular preloaded image.
+ *
+ **/
+
+ /**
+ * Adds to the script an initialization for the named variable as
+ * an Image(), to the given URL.
+ *
+ * <p>Returns a reference, a string that can be used to represent
+ * the preloaded image in a JavaScript function.
+ *
+ * @since 1.0.2
+ **/
+
+ public String getPreloadedImageReference(String URL)
+ {
+ if (_imageMap == null)
+ _imageMap = new HashMap();
+
+ String reference = (String) _imageMap.get(URL);
+
+ if (reference == null)
+ {
+ int count = _imageMap.size();
+ String varName = "tapestry_preload[" + count + "]";
+ reference = varName + ".src";
+
+ if (_imageInitializations == null)
+ _imageInitializations = new StringBuffer();
+
+ _imageInitializations.append(" ");
+ _imageInitializations.append(varName);
+ _imageInitializations.append(" = new Image();\n");
+ _imageInitializations.append(" ");
+ _imageInitializations.append(reference);
+ _imageInitializations.append(" = \"");
+ _imageInitializations.append(URL);
+ _imageInitializations.append("\";\n");
+
+ _imageMap.put(URL, reference);
+ }
+
+ return reference;
+ }
+
+ /**
+ * Adds other initialization, in the form of additional JavaScript
+ * code to execute from the <body>'s <code>onLoad</code> event
+ * handler. The caller is responsible for adding a semicolon (statement
+ * terminator). This method will add a newline after the script.
+ *
+ **/
+
+ public void addInitializationScript(String script)
+ {
+ if (_initializationScript == null)
+ _initializationScript = new StringBuffer(script.length() + 1);
+
+ _initializationScript.append(script);
+ _initializationScript.append('\n');
+
+ }
+
+ /**
+ * Adds additional scripting code to the page. This code
+ * will be added to a large block of scripting code at the
+ * top of the page (i.e., the before the <body> tag).
+ *
+ * <p>This is typically used to add some form of JavaScript
+ * event handler to a page. For example, the
+ * {@link Rollover} component makes use of this.
+ *
+ * <p>Another way this is invoked is by using the
+ * {@link Script} component.
+ *
+ * <p>The string will be added, as-is, within
+ * the <script> block generated by this <code>Body</code> component.
+ * The script should <em>not</em> contain HTML comments, those will
+ * be supplied by this Body component.
+ *
+ * <p>A frequent use is to add an initialization function using
+ * this method, then cause it to be executed using
+ * {@link #addInitializationScript(String)}.
+ *
+ **/
+
+ public void addBodyScript(String script)
+ {
+ if (_bodyScript == null)
+ _bodyScript = new StringBuffer(script.length());
+
+ _bodyScript.append(script);
+ }
+
+ /**
+ * Used to include a script from an outside URL (the scriptLocation
+ * is a URL, probably obtained from an asset. This adds
+ * an <script src="..."> tag before the main
+ * <script> tag. The Body component ensures
+ * that each URL is included only once.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public void addExternalScript(IResourceLocation scriptLocation)
+ {
+ if (_externalScripts == null)
+ _externalScripts = new ArrayList();
+
+ if (_externalScripts.contains(scriptLocation))
+ return;
+
+ // Alas, this won't give a good ILocation for the actual problem.
+
+ if (!(scriptLocation instanceof ClasspathResourceLocation))
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Body.include-classpath-script-only", scriptLocation),
+ this,
+ null,
+ null);
+
+ // Record the URL so we don't include it twice.
+
+ _externalScripts.add(scriptLocation);
+ }
+
+ /**
+ * Writes <script> elements for all the external scripts.
+ */
+ private void writeExternalScripts(IMarkupWriter writer)
+ {
+ int count = Tapestry.size(_externalScripts);
+ for (int i = 0; i < count; i++)
+ {
+ ClasspathResourceLocation scriptLocation =
+ (ClasspathResourceLocation) _externalScripts.get(i);
+
+ // This is still very awkward! Should move the code inside PrivateAsset somewhere
+ // else, so that an asset does not have to be created to to build the URL.
+ PrivateAsset asset = new PrivateAsset(scriptLocation, null);
+ String url = asset.buildURL(getPage().getRequestCycle());
+
+ // Note: important to use begin(), not beginEmpty(), because browser don't
+ // interpret <script .../> properly.
+
+ writer.begin("script");
+ writer.attribute("language", "JavaScript");
+ writer.attribute("type", "text/javascript");
+ writer.attribute("src", url);
+ writer.end();
+ writer.println();
+ }
+
+ }
+
+ /**
+ * Retrieves the <code>Body</code> that was stored into the
+ * request cycle. This allows components wrapped by the
+ * <code>Body</code> to locate it and access the services it
+ * provides.
+ *
+ **/
+
+ public static Body get(IRequestCycle cycle)
+ {
+ return (Body) cycle.getAttribute(ATTRIBUTE_NAME);
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Body.may-not-nest"),
+ this,
+ null,
+ null);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ _outerWriter = writer;
+
+ IMarkupWriter nested = writer.getNestedWriter();
+
+ renderBody(nested, cycle);
+
+ // Start the body tag.
+ writer.println();
+ writer.begin(getElement());
+ renderInformalParameters(writer, cycle);
+
+ writer.println();
+
+ // Write the page's scripting. This is included scripts
+ // and dynamic JavaScript, including initialization.
+
+ writeScript(_outerWriter);
+
+ // Close the nested writer, which dumps its buffered content
+ // into its parent.
+
+ nested.close();
+
+ writer.end(); // <body>
+
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ super.cleanupAfterRender(cycle);
+
+ if (_idAllocator != null)
+ _idAllocator.clear();
+
+ if (_imageMap != null)
+ _imageMap.clear();
+
+ if (_externalScripts != null)
+ _externalScripts.clear();
+
+ if (_initializationScript != null)
+ _initializationScript.setLength(0);
+
+ if (_imageInitializations != null)
+ _imageInitializations.setLength(0);
+
+ if (_bodyScript != null)
+ _bodyScript.setLength(0);
+
+ _outerWriter = null;
+ }
+
+ /**
+ * Writes a single large JavaScript block containing:
+ * <ul>
+ * <li>Any image initializations
+ * <li>Any scripting
+ * <li>Any initializations
+ * </ul>
+ *
+ * <p>The script is written into a nested markup writer.
+ *
+ * <p>If there are any other initializations
+ * (see {@link #addInitializationScript(String)}),
+ * then a function to execute them is created.
+ **/
+
+ protected void writeScript(IMarkupWriter writer)
+ {
+ if (!Tapestry.isEmpty(_externalScripts))
+ writeExternalScripts(writer);
+
+ if (!(any(_initializationScript) || any(_bodyScript) || any(_imageInitializations)))
+ return;
+
+ writer.begin("script");
+ writer.attribute("language", "JavaScript");
+ writer.attribute("type", "text/javascript");
+ writer.printRaw("<!--");
+
+ if (any(_imageInitializations))
+ {
+ writer.printRaw("\n\nvar tapestry_preload = new Array();\n");
+ writer.printRaw("if (document.images)\n");
+ writer.printRaw("{\n");
+ writer.printRaw(_imageInitializations.toString());
+ writer.printRaw("}\n");
+ }
+
+ if (any(_bodyScript))
+ {
+ writer.printRaw("\n\n");
+ writer.printRaw(_bodyScript.toString());
+ }
+
+ if (any(_initializationScript))
+ {
+
+ writer.printRaw("\n\n" + "window.onload = function ()\n" + "{\n");
+
+ writer.printRaw(_initializationScript.toString());
+
+ writer.printRaw("}");
+ }
+
+ writer.printRaw("\n\n// -->");
+ writer.end();
+ }
+
+ private boolean any(StringBuffer buffer)
+ {
+ if (buffer == null)
+ return false;
+
+ return buffer.length() > 0;
+ }
+
+ public abstract String getElement();
+
+ public abstract void setElement(String element);
+
+ /**
+ * Sets the element parameter property to its default, "body".
+ *
+ * @since 3.0
+ */
+ protected void finishLoad()
+ {
+ setElement("body");
+ }
+
+ /** @since 3.0 */
+
+ public String getUniqueString(String baseValue)
+ {
+ if (_idAllocator == null)
+ _idAllocator = new IdAllocator();
+
+ return _idAllocator.allocateId(baseValue);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Body.jwc b/tapestry-framework/src/org/apache/tapestry/html/Body.jwc
new file mode 100644
index 0000000..102bc5c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Body.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Body">
+
+ <description>
+ Takes the place of the normal HTML <body> element, providing various
+ forms of support to all components it wraps.
+ </description>
+
+ <parameter
+ name="element"
+ type="java.lang.String"
+ direction="in">
+ <description>
+ Name of element to use, defaults to "body".
+ </description>
+ </parameter>
+
+ <reserved-parameter name="onload"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.html b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.html
new file mode 100644
index 0000000..5a9fa6a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.html
@@ -0,0 +1,71 @@
+<!-- $Id$ -->
+<html>
+<link rel="stylesheet" type="text/css" href="../pages/Exception.css">
+
+<body>
+
+<span jwcid="$content$">
+<p>
+
+<table class="exception-display">
+
+<span jwcid="eException">
+
+ <tr class="exception-name">
+ <td colspan="2"><span jwcid="insertClass">some.exception.Class</span></td>
+ </tr>
+
+ <tr class="exception-message">
+ <td colspan="2"><span jwcid="insertMessage">A message describing the exception.</span></td>
+ </tr>
+
+ <tr jwcid="eProperty">
+ <th><span jwcid="insertPropertyName">Property Name</span>:</th>
+ <td><span jwcid="insertPropertyValue">Property Value</span></td>
+ </tr>
+
+ <tr jwcid="$remove$" class="odd">
+ <th>Property Name 2:</th>
+ <td>Property Value 2</td>
+ </tr>
+
+ <tr jwcid="$remove$" class="even">
+ <th>Property Name 3:</th>
+ <td>Property Value 3</td>
+ </tr>
+
+ <tr jwcid="$remove$" class="odd">
+ <th>Property Name 4:</th>
+ <td>Property Value 4</td>
+ </tr>
+
+<span jwcid="ifNotLast">
+ <tr> <td colspan=2> </td> </tr>
+</span>
+
+<span jwcid="ifLast">
+ <tr class="stack-trace-label">
+ <td colspan="2">Stack Trace:</td>
+ </tr>
+
+ <tr class="stack-trace">
+ <td colspan=2>
+ <ul>
+ <li jwcid="eStack"><span jwcid="insertStackTrace">foo.bar.baz(Line:xyz)</span>
+ </li>
+ <li jwcid="$remove$">foo.bar.baz(Line:xyz)</li>
+ <li jwcid="$remove$">foo.bar.baz(Line:xyz)</li>
+ <li jwcid="$remove$">foo.bar.baz(Line:xyz)</li>
+ <li jwcid="$remove$">foo.bar.baz(Line:xyz)</li>
+ </ul>
+ </td>
+ </tr>
+</span> <!-- ifLast -->
+</span> <!-- e-exception -->
+
+</table>
+
+</span> <!-- $content$ -->
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.java b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.java
new file mode 100644
index 0000000..24982d9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.java
@@ -0,0 +1,101 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.bean.EvenOdd;
+import org.apache.tapestry.util.exception.ExceptionDescription;
+
+/**
+ * Component used to display an already formatted exception.
+ *
+ * [<a href="../../../../../ComponentReference/ExceptionDisplay.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ExceptionDisplay extends BaseComponent
+{
+ private IBinding _exceptionsBinding;
+ private ExceptionDescription _exception;
+ private int _count;
+ private int _index;
+ private EvenOdd _evenOdd;
+
+ public void setExceptionsBinding(IBinding value)
+ {
+ _exceptionsBinding = value;
+ }
+
+ public IBinding getExceptionsBinding()
+ {
+ return _exceptionsBinding;
+ }
+
+ /**
+ * Each time the current exception is set, as a side effect,
+ * the evenOdd helper bean is reset to even.
+ *
+ **/
+
+ public void setException(ExceptionDescription value)
+ {
+ _exception = value;
+
+ _evenOdd.setEven(true);
+ }
+
+ public ExceptionDescription getException()
+ {
+ return _exception;
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ ExceptionDescription[] exceptions =
+ (ExceptionDescription[]) _exceptionsBinding.getObject(
+ "exceptions",
+ ExceptionDescription[].class);
+
+ _count = exceptions.length;
+
+ try
+ {
+ _evenOdd = (EvenOdd)getBeans().getBean("evenOdd");
+
+ super.renderComponent(writer, cycle);
+ }
+ finally
+ {
+ _exception = null;
+ _evenOdd = null;
+ }
+ }
+
+ public void setIndex(int value)
+ {
+ _index = value;
+ }
+
+ public boolean isLast()
+ {
+ return _index == (_count - 1);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.jwc b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.jwc
new file mode 100644
index 0000000..c7cf8ae
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/ExceptionDisplay.jwc
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.ExceptionDisplay"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+ Used to present a detail exception description.
+ </description>
+
+ <parameter name="exceptions" required="yes">
+ <description>
+ An array of ExceptionDescription objects.
+ </description>
+ </parameter>
+
+ <bean name="evenOdd" class="org.apache.tapestry.bean.EvenOdd"/>
+
+ <component id="eException" type="Foreach">
+ <inherited-binding name="source" parameter-name="exceptions"/>
+ <binding name="value" expression="exception"/>
+ <binding name="index" expression="index"/>
+ </component>
+
+ <component id="insertClass" type="Insert">
+ <binding name="value" expression="exception.exceptionClassName"/>
+ </component>
+
+ <component id="insertMessage" type="Insert">
+ <binding name="value" expression="exception.message"/>
+ </component>
+
+ <component id="eProperty" type="Foreach">
+ <static-binding name="element" value="tr"/>
+ <binding name="class" expression="beans.evenOdd.next"/>
+ <binding name="source" expression="exception.properties"/>
+ </component>
+
+ <component id="insertPropertyName" type="Insert">
+ <binding name="value" expression="components.eProperty.value.name"/>
+ </component>
+
+ <component id="insertPropertyValue" type="Insert">
+ <binding name="value" expression="components.eProperty.value.value"/>
+ </component>
+
+ <component id="ifLast" type="Conditional">
+ <binding name="condition" expression="last"/>
+ </component>
+
+ <component id="ifNotLast" type="Conditional">
+ <binding name="condition" expression="! last"/>
+ </component>
+
+ <component id="eStack" type="Foreach">
+ <static-binding name="element" value="li"/>
+ <binding name="source" expression="exception.stackTrace"/>
+ </component>
+
+ <component id="insertStackTrace" type="Insert">
+ <binding name="value" expression="components.eStack.value"/>
+ </component>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Frame.java b/tapestry-framework/src/org/apache/tapestry/html/Frame.java
new file mode 100644
index 0000000..071387f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Frame.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * Implements a <frame> within a <frameset>.
+ *
+ * [<a href="../../../../../ComponentReference/Frame.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Frame extends AbstractComponent
+{
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.isRewinding())
+ return;
+
+ IEngine engine = cycle.getEngine();
+ IEngineService pageService = engine.getService(Tapestry.PAGE_SERVICE);
+ ILink link = pageService.getLink(cycle, this, new String[] { getTargetPage() });
+
+ writer.beginEmpty("frame");
+ writer.attribute("src", link.getURL());
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ public abstract String getTargetPage();
+
+ public abstract void setTargetPage(String targetPage);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Frame.jwc b/tapestry-framework/src/org/apache/tapestry/html/Frame.jwc
new file mode 100644
index 0000000..81ce7bc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Frame.jwc
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Frame"
+ allow-body="no"
+ allow-informal-parameters="yes">
+
+ <description>
+ Identifies a page as the contents of a frame within a frameset.
+ </description>
+
+ <parameter name="page"
+ property-name="targetPage"
+ type="java.lang.String"
+ required="yes"
+ direction="in">
+ <description>
+ The page to display in the frame.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="src"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/HTMLWriter.java b/tapestry-framework/src/org/apache/tapestry/html/HTMLWriter.java
new file mode 100644
index 0000000..66c010d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/HTMLWriter.java
@@ -0,0 +1,109 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import org.apache.tapestry.AbstractMarkupWriter;
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * This class is used to create HTML output.
+ *
+ * <p>The <code>HTMLWriter</code> handles the necessary escaping
+ * of invalid characters.
+ * Specifically, the '<', '>' and '&' characters are properly
+ * converted to their HTML entities by the <code>print()</code> methods.
+ * Similar measures are taken by the {@link #attribute(String, String)} method.
+ * Other invalid characters are converted to their numeric entity equivalent.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ **/
+
+public class HTMLWriter extends AbstractMarkupWriter
+{
+
+ private static final String[] entities = new String[64];
+ private static final boolean[] safe = new boolean[128];
+
+ private static final String SAFE_CHARACTERS =
+ "01234567890"
+ + "abcdefghijklmnopqrstuvwxyz"
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "\t\n\r !\"#$%'()*+,-./:;=?@[\\]^_`{|}~";
+
+ static {
+ entities['"'] = """;
+ entities['<'] = "<";
+ entities['>'] = ">";
+ entities['&'] = "&";
+
+ int length = SAFE_CHARACTERS.length();
+ for (int i = 0; i < length; i++)
+ safe[SAFE_CHARACTERS.charAt(i)] = true;
+ }
+
+ /**
+ * Creates a new markup writer around the {@link PrintWriter}.
+ * The writer will not be closed when the markup writer closes.
+ * The content type is currently hard-wired to
+ * <code>text/html</code>.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public HTMLWriter(PrintWriter writer)
+ {
+ super(safe, entities, "text/html", writer);
+ }
+
+ public HTMLWriter(String contentType, OutputStream outputStream)
+ {
+ super(safe, entities, contentType, outputStream);
+ }
+
+ public HTMLWriter(String contentType, String encoding, OutputStream outputStream)
+ {
+ super(safe, entities, contentType, encoding, outputStream);
+ }
+
+ protected HTMLWriter(String contentType)
+ {
+ super(safe, entities, contentType);
+ }
+
+ /**
+ * Creates a default writer for content type "text/html; charset=utf-8".
+ *
+ **/
+
+ public HTMLWriter(OutputStream outputStream)
+ {
+ this(outputStream, "UTF-8");
+ }
+
+ public HTMLWriter(OutputStream outputStream, String encoding)
+ {
+ this("text/html", encoding, outputStream);
+ }
+
+ public IMarkupWriter getNestedWriter()
+ {
+ return new NestedHTMLWriter(this);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Image.java b/tapestry-framework/src/org/apache/tapestry/html/Image.java
new file mode 100644
index 0000000..0e84cfe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Image.java
@@ -0,0 +1,74 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Used to insert an image. To create a rollover image, use the
+ * {@link Rollover} class, which integrates a link with the image assets
+ * used with the button.
+ *
+ * [<a href="../../../../../ComponentReference/Image.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Image extends AbstractComponent
+{
+ /**
+ * Renders the <img> element.
+ *
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ // Doesn't contain a body so no need to do anything on rewind (assumes no
+ // sideffects to accessor methods via bindings).
+
+ if (cycle.isRewinding())
+ return;
+
+ IAsset imageAsset = getImage();
+
+ if (imageAsset == null)
+ throw Tapestry.createRequiredParameterException(this, "image");
+
+ String imageURL = imageAsset.buildURL(cycle);
+
+ writer.beginEmpty("img");
+
+ writer.attribute("src", imageURL);
+
+ writer.attribute("border", getBorder());
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+
+ }
+
+ public abstract IAsset getImage();
+
+ public abstract int getBorder();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Image.jwc b/tapestry-framework/src/org/apache/tapestry/html/Image.jwc
new file mode 100644
index 0000000..1728279
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Image.jwc
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Image" allow-body="no">
+
+ <description>
+ Displays an image, deriving the source URL for the image from an asset.
+ </description>
+
+ <parameter name="image"
+ type="org.apache.tapestry.IAsset"
+ required="yes"
+ direction="in">
+ <description>
+ The asset to display.
+ </description>
+ </parameter>
+
+ <parameter name="border"
+ type="int"
+ direction="in">
+ <description>
+ Number of pixels of border.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="src"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/InsertText.java b/tapestry-framework/src/org/apache/tapestry/html/InsertText.java
new file mode 100644
index 0000000..244a7f1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/InsertText.java
@@ -0,0 +1,129 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.io.StringReader;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Inserts formatted text (possibly collected using a {@link org.apache.tapestry.form.TextArea}
+ * component.
+ *
+ * [<a href="../../../../../ComponentReference/InsertText.html">Component Reference</a>]
+ *
+ * <p>To maintain the line breaks provided originally, this component will
+ * break the input into individual lines and insert additional
+ * HTML to make each line seperate.
+ *
+ * <p>This can be down more simply, using the <pre> HTML element, but
+ * that usually renders the text in a non-proportional font.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class InsertText extends AbstractComponent
+{
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String value = getValue();
+
+ if (value == null)
+ return;
+
+ StringReader reader = null;
+ LineNumberReader lineReader = null;
+ InsertTextMode mode = getMode();
+
+ try
+ {
+ reader = new StringReader(value);
+
+ lineReader = new LineNumberReader(reader);
+
+ int lineNumber = 0;
+
+ while (true)
+ {
+ String line = lineReader.readLine();
+
+ // Exit loop at end of file.
+
+ if (line == null)
+ break;
+
+ mode.writeLine(lineNumber, line, writer);
+
+ lineNumber++;
+ }
+
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("InsertText.conversion-error"),
+ this,
+ null,
+ ex);
+ }
+ finally
+ {
+ close(lineReader);
+ close(reader);
+ }
+
+ }
+
+ private void close(Reader reader)
+ {
+ if (reader == null)
+ return;
+
+ try
+ {
+ reader.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+
+ public abstract InsertTextMode getMode();
+
+ public abstract void setMode(InsertTextMode mode);
+
+ public abstract String getValue();
+
+ /**
+ * Sets the mode parameter property to its default,
+ * {@link InsertTextMode#BREAK}.
+ *
+ * @since 3.0
+ */
+ protected void finishLoad()
+ {
+ setMode(InsertTextMode.BREAK);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/InsertText.jwc b/tapestry-framework/src/org/apache/tapestry/html/InsertText.jwc
new file mode 100644
index 0000000..d28af07
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/InsertText.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.InsertText"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+ Inserts line-oriented text into the response HTML, inserting additional
+ markup to delimit lines.
+ </description>
+
+ <parameter name="value" type="java.lang.String" direction="in">
+ <description>
+ The text to insert.
+ </description>
+ </parameter>
+
+ <parameter name="mode"
+ type="org.apache.tapestry.html.InsertTextMode"
+ direction="in">
+ <description>
+ Determines which mode to use: breaks after each line, or wrap each line
+ as a paragraph. The default is breaks.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/InsertTextMode.java b/tapestry-framework/src/org/apache/tapestry/html/InsertTextMode.java
new file mode 100644
index 0000000..ac6789d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/InsertTextMode.java
@@ -0,0 +1,96 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import org.apache.commons.lang.enum.Enum;
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * Defines a number of ways to format multi-line text for proper
+ * renderring.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public abstract class InsertTextMode extends Enum
+{
+ /**
+ * Mode where each line (after the first) is preceded by a <br> tag.
+ *
+ **/
+
+ public static final InsertTextMode BREAK = new BreakMode();
+
+ /**
+ * Mode where each line is wrapped with a <p> element.
+ *
+ **/
+
+ public static final InsertTextMode PARAGRAPH = new ParagraphMode();
+
+ protected InsertTextMode(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Invoked by the {@link InsertText} component to write the next line.
+ *
+ * @param lineNumber the line number of the line, starting with 0 for the first line.
+ * @param line the String for the current line.
+ * @param writer the {@link IMarkupWriter} to send output to.
+ **/
+
+ public abstract void writeLine(
+ int lineNumber,
+ String line,
+ IMarkupWriter writer);
+
+ private static class BreakMode extends InsertTextMode
+ {
+ private BreakMode()
+ {
+ super("BREAK");
+ }
+
+ public void writeLine(int lineNumber, String line, IMarkupWriter writer)
+ {
+ if (lineNumber > 0)
+ writer.beginEmpty("br");
+
+ writer.print(line);
+ }
+ }
+
+ private static class ParagraphMode extends InsertTextMode
+ {
+ private ParagraphMode()
+ {
+ super("PARAGRAPH");
+ }
+
+ public void writeLine(int lineNumber, String line, IMarkupWriter writer)
+ {
+ writer.begin("p");
+
+ writer.print(line);
+
+ writer.end();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/NestedHTMLWriter.java b/tapestry-framework/src/org/apache/tapestry/html/NestedHTMLWriter.java
new file mode 100644
index 0000000..a9032ff
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/NestedHTMLWriter.java
@@ -0,0 +1,66 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * Subclass of {@link HTMLWriter} that is nested. A nested writer
+ * buffers its output, then inserts it into its parent writer when it is
+ * closed.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ */
+
+public class NestedHTMLWriter extends HTMLWriter
+{
+ private IMarkupWriter _parent;
+ private CharArrayWriter _internalBuffer;
+
+ public NestedHTMLWriter(IMarkupWriter parent)
+ {
+ super(parent.getContentType());
+
+ _parent = parent;
+
+ _internalBuffer = new CharArrayWriter();
+
+ setWriter(new PrintWriter(_internalBuffer));
+ }
+
+ /**
+ * Invokes the {@link HTMLWriter#close() super-class
+ * implementation}, then gets the data accumulated in the
+ * internal buffer and provides it to the containing writer using
+ * {@link IMarkupWriter#printRaw(char[], int, int)}.
+ *
+ */
+
+ public void close()
+ {
+ super.close();
+
+ char[] data = _internalBuffer.toCharArray();
+
+ _parent.printRaw(data, 0, data.length);
+
+ _internalBuffer = null;
+ _parent = null;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/PracticalBrowserSniffer.js b/tapestry-framework/src/org/apache/tapestry/html/PracticalBrowserSniffer.js
new file mode 100644
index 0000000..95b9360
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/PracticalBrowserSniffer.js
@@ -0,0 +1,165 @@
+// PracticalBrowserSniffer.js - Detect Browser
+// Requires JavaScript 1.1
+/*
+The contents of this file are subject to the Netscape Public
+License Version 1.1 (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.mozilla.org/NPL/
+
+Software distributed under the License is distributed on an "AS
+IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+implied. See the License for the specific language governing
+rights and limitations under the License.
+
+The Initial Developer of the Original Code is Bob Clary.
+
+Contributor(s): Bob Clary, Original Work, Copyright 1999-2000
+ Bob Clary, Netscape Communications, Copyright 2001
+
+
+Note:
+
+Acquired from: http://developer.netscape.com/evangelism/tools/practical-browser-sniffing/
+Last update: July 17, 2001
+
+*/
+
+// work around bug in xpcdom Mozilla 0.9.1
+window.saveNavigator = window.navigator;
+
+// Handy functions
+function noop() {}
+function noerror() { return true; }
+
+function defaultOnError(msg, url, line)
+{
+ // customize this for your site
+ if (top.location.href.indexOf('_files/errors/') == -1)
+ top.location = '/evangelism/xbProjects/_files/errors/index.html?msg=' + escape(msg) + '&url=' + escape(url) + '&line=' + escape(line);
+}
+
+// Display Error page...
+// XXX: more work to be done here
+//
+function reportError(message)
+{
+ // customize this for your site
+ if (top.location.href.indexOf('_files/errors/') == -1)
+ top.location = '/evangelism/xbProjects/_files/errors/index.html?msg=' + escape(message);
+}
+
+function pageRequires(cond, msg, redirectTo)
+{
+ if (!cond)
+ {
+ msg = 'This page requires ' + msg;
+ top.location = redirectTo + '?msg=' + escape(msg);
+ }
+ // return cond so can use in <A> onclick handlers to exclude browsers
+ // from pages they do not support.
+ return cond;
+}
+
+function detectBrowser()
+{
+ var oldOnError = window.onerror;
+ var element = null;
+
+ window.onerror = defaultOnError;
+
+ navigator.OS = '';
+ navigator.version = 0;
+ navigator.org = '';
+ navigator.family = '';
+
+ var platform;
+ if (typeof(window.navigator.platform) != 'undefined')
+ {
+ platform = window.navigator.platform.toLowerCase();
+ if (platform.indexOf('win') != -1)
+ navigator.OS = 'win';
+ else if (platform.indexOf('mac') != -1)
+ navigator.OS = 'mac';
+ else if (platform.indexOf('unix') != -1 || platform.indexOf('linux') != -1 || platform.indexOf('sun') != -1)
+ navigator.OS = 'nix';
+ }
+
+ var i = 0;
+ var ua = window.navigator.userAgent.toLowerCase();
+
+ if (ua.indexOf('safari') != -1) {
+ navigator.family = 'nn4';
+ navigator.version = 4;
+ navigator.org = 'netscape';
+ }
+ else if (ua.indexOf('opera') != -1)
+ {
+ i = ua.indexOf('opera');
+ navigator.family = 'opera';
+ navigator.org = 'opera';
+ navigator.version = parseFloat('0' + ua.substr(i+6), 10);
+ }
+ else if ((i = ua.indexOf('msie')) != -1)
+ {
+ navigator.org = 'microsoft';
+ navigator.version = parseFloat('0' + ua.substr(i+5), 10);
+
+ if (navigator.version < 4)
+ navigator.family = 'ie3';
+ else
+ navigator.family = 'ie4'
+ }
+ else if (typeof(window.controllers) != 'undefined' && typeof(window.locationbar) != 'undefined')
+ {
+ i = ua.lastIndexOf('/')
+ navigator.version = parseFloat('0' + ua.substr(i+1), 10);
+ navigator.family = 'gecko';
+
+ if (ua.indexOf('netscape') != -1)
+ navigator.org = 'netscape';
+ else if (ua.indexOf('compuserve') != -1)
+ navigator.org = 'compuserve';
+ else
+ navigator.org = 'mozilla';
+ }
+ else if ((ua.indexOf('mozilla') !=-1) && (ua.indexOf('spoofer')==-1) && (ua.indexOf('compatible') == -1) && (ua.indexOf('opera')==-1)&& (ua.indexOf('webtv')==-1) && (ua.indexOf('hotjava')==-1))
+ {
+ var is_major = parseFloat(navigator.appVersion);
+
+ if (is_major < 4)
+ navigator.version = is_major;
+ else
+ {
+ i = ua.lastIndexOf('/')
+ navigator.version = parseFloat('0' + ua.substr(i+1), 10);
+ }
+ navigator.org = 'netscape';
+ navigator.family = 'nn' + parseInt(navigator.appVersion);
+ }
+ else if ((i = ua.indexOf('aol')) != -1 )
+ {
+ // aol
+ navigator.family = 'aol';
+ navigator.org = 'aol';
+ navigator.version = parseFloat('0' + ua.substr(i+4), 10);
+ }
+
+ navigator.DOMCORE1 = (typeof(document.getElementsByTagName) != 'undefined' && typeof(document.createElement) != 'undefined');
+ navigator.DOMCORE2 = (navigator.DOMCORE1 && typeof(document.getElementById) != 'undefined' && typeof(document.createElementNS) != 'undefined');
+ navigator.DOMHTML = (navigator.DOMCORE1 && typeof(document.getElementById) != 'undefined');
+ navigator.DOMCSS1 = ( (navigator.family == 'gecko') || (navigator.family == 'ie4') );
+
+ navigator.DOMCSS2 = false;
+ if (navigator.DOMCORE1)
+ {
+ element = document.createElement('p');
+ navigator.DOMCSS2 = (typeof(element.style) == 'object');
+ }
+
+ navigator.DOMEVENTS = (typeof(document.createEvent) != 'undefined');
+
+ window.onerror = oldOnError;
+}
+
+detectBrowser();
+
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Rollover.java b/tapestry-framework/src/org/apache/tapestry/html/Rollover.java
new file mode 100644
index 0000000..ad9388b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Rollover.java
@@ -0,0 +1,197 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.components.ILinkComponent;
+import org.apache.tapestry.components.LinkEventType;
+import org.apache.tapestry.engine.IScriptSource;
+
+/**
+ * Combines a link component (such as {@link org.apache.tapestry.link.DirectLink})
+ * with an <img> and JavaScript code
+ * to create a rollover effect that works with both Netscape Navigator and
+ * Internet Explorer.
+ *
+ * [<a href="../../../../../ComponentReference/Rollover.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Rollover extends AbstractComponent
+{
+ private IScript _parsedScript;
+
+ /**
+ * Converts an {@link IAsset} binding into a usable URL. Returns null
+ * if the binding does not exist or the binding's value is null.
+ *
+ **/
+
+ protected String getAssetURL(IAsset asset, IRequestCycle cycle)
+ {
+ if (asset == null)
+ return null;
+
+ return asset.buildURL(cycle);
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ // No body, so we skip it all if not rewinding (assumes no side effects on
+ // accessors).
+
+ if (cycle.isRewinding())
+ return;
+
+ String imageURL = null;
+ String focusURL = null;
+ String blurURL = null;
+ boolean dynamic = false;
+ String imageName = null;
+
+ Body body = Body.get(cycle);
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Rollover.must-be-contained-by-body"),
+ this,
+ null,
+ null);
+
+ ILinkComponent serviceLink =
+ (ILinkComponent) cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
+
+ if (serviceLink == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Rollover.must-be-contained-by-link"),
+ this,
+ null,
+ null);
+
+ boolean linkDisabled = serviceLink.isDisabled();
+
+ if (linkDisabled)
+ {
+ imageURL = getAssetURL(getDisabled(), cycle);
+
+ if (imageURL == null)
+ imageURL = getAssetURL(getImage(), cycle);
+ }
+ else
+ {
+ imageURL = getAssetURL(getImage(), cycle);
+ focusURL = getAssetURL(getFocus(), cycle);
+ blurURL = getAssetURL(getBlur(), cycle);
+
+ dynamic = (focusURL != null) || (blurURL != null);
+ }
+
+ if (imageURL == null)
+ throw Tapestry.createRequiredParameterException(this, "image");
+
+ writer.beginEmpty("img");
+
+ writer.attribute("src", imageURL);
+
+ writer.attribute("border", 0);
+
+ if (dynamic)
+ {
+ if (focusURL == null)
+ focusURL = imageURL;
+
+ if (blurURL == null)
+ blurURL = imageURL;
+
+ imageName = writeScript(cycle, body, serviceLink, focusURL, blurURL);
+
+ writer.attribute("name", imageName);
+ }
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+
+ }
+
+ private IScript getParsedScript()
+ {
+ if (_parsedScript == null)
+ {
+ IEngine engine = getPage().getEngine();
+ IScriptSource source = engine.getScriptSource();
+
+ IResourceLocation scriptLocation =
+ getSpecification().getSpecificationLocation().getRelativeLocation(
+ "Rollover.script");
+
+ _parsedScript = source.getScript(scriptLocation);
+ }
+
+ return _parsedScript;
+ }
+
+ private String writeScript(
+ IRequestCycle cycle,
+ Body body,
+ ILinkComponent link,
+ String focusURL,
+ String blurURL)
+ {
+ String imageName = body.getUniqueString(getId());
+ String focusImageURL = body.getPreloadedImageReference(focusURL);
+ String blurImageURL = body.getPreloadedImageReference(blurURL);
+
+ Map symbols = new HashMap();
+
+ symbols.put("imageName", imageName);
+ symbols.put("focusImageURL", focusImageURL);
+ symbols.put("blurImageURL", blurImageURL);
+
+ getParsedScript().execute(cycle, body, symbols);
+
+ // Add attributes to the link to control mouse over/out.
+ // Because the script is written before the <body> tag,
+ // there won't be any timing issues (such as cause
+ // bug #113893).
+
+ link.addEventHandler(LinkEventType.MOUSE_OVER, (String) symbols.get("onMouseOverName"));
+ link.addEventHandler(LinkEventType.MOUSE_OUT, (String) symbols.get("onMouseOutName"));
+
+ return imageName;
+ }
+
+ public abstract IAsset getBlur();
+
+ public abstract IAsset getDisabled();
+
+ public abstract IAsset getFocus();
+
+ public abstract IAsset getImage();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Rollover.jwc b/tapestry-framework/src/org/apache/tapestry/html/Rollover.jwc
new file mode 100644
index 0000000..f752d7c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Rollover.jwc
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Rollover" allow-body="no">
+
+ <description>
+ A complex image component which must be wrapped by a link component. Rollovers
+ can reflect the enabled status of the link, and display rollover effects.
+ </description>
+
+ <parameter name="image"
+ type="org.apache.tapestry.IAsset"
+ required="yes"
+ direction="in">
+ <description>
+ The normal or default image to display, used as a default image for
+ the other parameters.
+ </description>
+ </parameter>
+
+ <parameter name="focus"
+ type="org.apache.tapestry.IAsset"
+ direction="in">
+ <description>
+ If specified, provides an image displayed when the cursor is moved
+ over the link.
+ </description>
+ </parameter>
+
+ <parameter name="blur"
+ type="org.apache.tapestry.IAsset"
+ direction="in">
+ <description>
+ If specified, provides an image displayed when the cursor is moved
+ off of the link.
+ </description>
+ </parameter>
+
+ <parameter name="disabled"
+ type="org.apache.tapestry.IAsset"
+ direction="in">
+ <description>
+ If specified, provides an image displayed when the surrounding
+ link is disabled.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="name"/>
+ <reserved-parameter name="src"/>
+ <reserved-parameter name="border"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Rollover.script b/tapestry-framework/src/org/apache/tapestry/html/Rollover.script
new file mode 100644
index 0000000..fd2f641
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Rollover.script
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+<script>
+<!--
+
+input symbols:
+
+uniqueId - uniqueId used to build names
+focusImageURL - URL for focus (mouse over)
+blurImageURL - URL for blur image (mouse out)
+
+output symbols:
+
+imageName - name for the image (i.e. name attribute of <img> element)
+onMouseOverName - name of mouse over function
+onMouseOutName - name of mouse out function
+
+-->
+
+<input-symbol key="imageName" class="java.lang.String" required="yes"/>
+<input-symbol key="focusImageURL" class="java.lang.String" required="yes"/>
+<input-symbol key="blurImageURL" class="java.lang.String" required="yes"/>
+
+
+<let key="onMouseOverName">
+ focus_${imageName}
+</let>
+<let key="onMouseOutName">
+ blur_${imageName}
+</let>
+
+<let key="attribute">
+ document.${imageName}.src
+</let>
+
+<body>
+
+function ${onMouseOverName}()
+{
+ if (document.images)
+ ${attribute} = ${focusImageURL};
+}
+
+function ${onMouseOutName}()
+{
+ if (document.images)
+ ${attribute} = ${blurImageURL};
+}
+
+</body>
+</script>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Script.java b/tapestry-framework/src/org/apache/tapestry/html/Script.java
new file mode 100644
index 0000000..1e56f5f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Script.java
@@ -0,0 +1,185 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IScriptSource;
+
+/**
+ * Works with the {@link Body} component to add a script (and perhaps some initialization)
+ * to the HTML response.
+ *
+ * [<a href="../../../../../ComponentReference/Script.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class Script extends AbstractComponent
+{
+ private Map _baseSymbols;
+
+ /**
+ * A Map of input and output symbols visible to the body of the Script.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private Map _symbols;
+
+ /**
+ * Constructs the symbols {@link Map}. This starts with the
+ * contents of the symbols parameter (if specified) to which is added
+ * any informal parameters. If both a symbols parameter and informal
+ * parameters are bound, then a copy of the symbols parameter's value is made
+ * (that is, the {@link Map} provided by the symbols parameter is read, but not modified).
+ *
+ **/
+
+ private Map getInputSymbols()
+ {
+ Map result = new HashMap();
+
+ if (_baseSymbols != null)
+ result.putAll(_baseSymbols);
+
+ // Now, iterate through all the binding names (which includes both
+ // formal and informal parmeters). Skip the formal ones and
+ // access the informal ones.
+
+ Iterator i = getBindingNames().iterator();
+ while (i.hasNext())
+ {
+ String bindingName = (String) i.next();
+
+ // Skip formal parameters
+
+ if (getSpecification().getParameter(bindingName) != null)
+ continue;
+
+ IBinding binding = getBinding(bindingName);
+
+ Object value = binding.getObject();
+
+ result.put(bindingName, value);
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the {@link IScript} for the correct script.
+ *
+ *
+ **/
+
+ private IScript getParsedScript(IRequestCycle cycle)
+ {
+ String scriptPath = getScriptPath();
+
+ if (scriptPath == null)
+ throw Tapestry.createRequiredParameterException(this, "scriptPath");
+
+ IEngine engine = cycle.getEngine();
+ IScriptSource source = engine.getScriptSource();
+
+ // If the script path is relative, it should be relative to the Script component's
+ // container (i.e., relative to a page in the application).
+
+ IResourceLocation rootLocation =
+ getContainer().getSpecification().getSpecificationLocation();
+ IResourceLocation scriptLocation = rootLocation.getRelativeLocation(scriptPath);
+
+ try
+ {
+ return source.getScript(scriptLocation);
+ }
+ catch (RuntimeException ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), this, null, ex);
+ }
+
+ }
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (!cycle.isRewinding())
+ {
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Script.must-be-contained-by-body"),
+ this,
+ null,
+ null);
+
+ _symbols = getInputSymbols();
+
+ getParsedScript(cycle).execute(cycle, body, _symbols);
+ }
+
+ // Render the body of the Script;
+ renderBody(writer, cycle);
+ }
+
+ public abstract String getScriptPath();
+
+ public Map getBaseSymbols()
+ {
+ return _baseSymbols;
+ }
+
+ public void setBaseSymbols(Map baseSymbols)
+ {
+ _baseSymbols = baseSymbols;
+ }
+
+ /**
+ * Returns the complete set of symbols (input and output)
+ * from the script execution. This is visible to the body
+ * of the Script, but is cleared after the Script
+ * finishes rendering.
+ *
+ * @since 2.2
+ **/
+
+ public Map getSymbols()
+ {
+ return _symbols;
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ _symbols = null;
+
+ super.cleanupAfterRender(cycle);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Script.jwc b/tapestry-framework/src/org/apache/tapestry/html/Script.jwc
new file mode 100644
index 0000000..937850e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Script.jwc
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Script" allow-body="yes">
+
+ <description>
+ Constructs dynamic JavaScript which is added to the page.
+ </description>
+
+ <parameter name="script"
+ property-name="scriptPath"
+ type="java.lang.String"
+ required="yes"
+ direction="in">
+ <description>
+ The resource path of the script to execute.
+ </description>
+ </parameter>
+
+ <parameter name="symbols"
+ property-name="baseSymbols"
+ type="java.util.Map"
+ direction="in">
+ <description>
+ Provides a base set of symbols to which, in a copy, are added
+ any informal parameters.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Shell.java b/tapestry-framework/src/org/apache/tapestry/html/Shell.java
new file mode 100644
index 0000000..eba3eb9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Shell.java
@@ -0,0 +1,208 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.html;
+
+import java.util.Date;
+import java.util.Iterator;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * Component for creating a standard 'shell' for a page, which comprises
+ * the <html> and <head> portions of the page.
+ *
+ * [<a href="../../../../../ComponentReference/Shell.html">Component Reference</a>]
+ *
+ * <p>Specifically does <em>not</em> provide a <body> tag, that is
+ * usually accomplished using a {@link Body} component.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public abstract class Shell extends AbstractComponent
+{
+
+ private static final String generatorContent =
+ "Tapestry Application Framework, version " + Tapestry.VERSION;
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ long startTime = 0;
+
+ boolean rewinding = cycle.isRewinding();
+
+ if (!rewinding)
+ {
+ startTime = System.currentTimeMillis();
+
+ writeDocType(writer, cycle);
+
+ IPage page = getPage();
+
+ writer.comment("Application: " + page.getEngine().getSpecification().getName());
+
+ writer.comment("Page: " + page.getPageName());
+ writer.comment("Generated: " + new Date());
+
+ writer.begin("html");
+ renderInformalParameters(writer, cycle);
+ writer.println();
+ writer.begin("head");
+ writer.println();
+
+ writer.beginEmpty("meta");
+ writer.attribute("name", "generator");
+ writer.attribute("content", generatorContent);
+ writer.println();
+
+ if (getRenderContentType()) {
+ // This should not be necessary (the HTTP content type should be sufficient),
+ // but some browsers require it for some reason
+ writer.beginEmpty("meta");
+ writer.attribute("http-equiv", "Content-Type");
+ writer.attribute("content", writer.getContentType());
+ writer.println();
+ }
+
+ writer.begin("title");
+
+ writer.print(getTitle());
+ writer.end(); // title
+ writer.println();
+
+ IRender delegate = getDelegate();
+
+ if (delegate != null)
+ delegate.render(writer, cycle);
+
+ IAsset stylesheet = getStylesheet();
+
+ if (stylesheet != null)
+ writeStylesheetLink(writer, cycle, stylesheet);
+
+ Iterator i = Tapestry.coerceToIterator(getStylesheets());
+
+ if (i != null)
+ {
+ while (i.hasNext())
+ {
+ stylesheet = (IAsset) i.next();
+
+ writeStylesheetLink(writer, cycle, stylesheet);
+ }
+ }
+
+ writeRefresh(writer, cycle);
+
+ writer.end(); // head
+ }
+
+ // Render the body, the actual page content
+
+ renderBody(writer, cycle);
+
+ if (!rewinding)
+ {
+ writer.end(); // html
+ writer.println();
+
+ long endTime = System.currentTimeMillis();
+
+ writer.comment("Render time: ~ " + (endTime - startTime) + " ms");
+ }
+
+ }
+
+ private void writeDocType(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ // This code is deprecated and is here only for backward compatibility
+ String DTD = getDTD();
+ if (Tapestry.isNonBlank(DTD)) {
+ writer.printRaw("<!DOCTYPE HTML PUBLIC \"" + DTD + "\">");
+ writer.println();
+ return;
+ }
+
+ // This is the real code
+ String doctype = getDoctype();
+ if (Tapestry.isNonBlank(doctype)) {
+ writer.printRaw("<!DOCTYPE " + doctype + ">");
+ writer.println();
+ }
+ }
+
+ private void writeStylesheetLink(IMarkupWriter writer, IRequestCycle cycle, IAsset stylesheet)
+ {
+ writer.beginEmpty("link");
+ writer.attribute("rel", "stylesheet");
+ writer.attribute("type", "text/css");
+ writer.attribute("href", stylesheet.buildURL(cycle));
+ writer.println();
+ }
+
+ private void writeRefresh(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ int refresh = getRefresh();
+
+ if (refresh <= 0)
+ return;
+
+ // Here comes the tricky part ... have to assemble a complete URL
+ // for the current page.
+
+ IEngineService pageService = cycle.getEngine().getService(Tapestry.PAGE_SERVICE);
+ String pageName = getPage().getPageName();
+
+ ILink link = pageService.getLink(cycle, null, new String[] { pageName });
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(refresh);
+ buffer.append("; URL=");
+ buffer.append(link.getAbsoluteURL());
+
+ // Write out the <meta> tag
+
+ writer.beginEmpty("meta");
+ writer.attribute("http-equiv", "Refresh");
+ writer.attribute("content", buffer.toString());
+ }
+
+ public abstract IRender getDelegate();
+
+ public abstract int getRefresh();
+
+ public abstract IAsset getStylesheet();
+
+ public abstract String getTitle();
+
+ public abstract String getDoctype();
+
+ public abstract String getDTD();
+
+ public abstract Object getStylesheets();
+
+ public abstract boolean getRenderContentType();
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/html/Shell.jwc b/tapestry-framework/src/org/apache/tapestry/html/Shell.jwc
new file mode 100644
index 0000000..65b7894
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/Shell.jwc
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.html.Shell" allow-informal-parameters="yes">
+
+ <description>
+ Provides the outer tags in an HTML page: <html>, <head> and <title>.
+ </description>
+
+ <parameter name="title"
+ type="java.lang.String"
+ required="yes"
+ direction="in">
+ <description>
+ The title for the page.
+ </description>
+ </parameter>
+
+ <parameter name="stylesheet"
+ type="org.apache.tapestry.IAsset"
+ direction="in">
+ <description>
+ If specified, provides an external stylesheet for the page.
+ </description>
+ </parameter>
+
+ <parameter name="stylesheets" type="java.lang.Object" direction="in">
+ <description>
+ Array or collection of stylesheet assets.
+ </description>
+ </parameter>
+
+ <parameter name="doctype"
+ type="java.lang.String"
+ direction="in"
+ default-value='"HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\""'>
+ <description>
+ Used to specify the full definition of the DOCTYPE element in the response page,
+ for example 'math SYSTEM "http://www.w3.org/Math/DTD/mathml1/mathml.dtd"'
+
+ The list of currently valid DOCTYPE settings can be found here:
+ http://www.w3.org/QA/2002/04/valid-dtd-list.html
+
+ If the parameter is null or empty, no DOCTYPE tag will be rendered
+ </description>
+ </parameter>
+
+ <parameter name="DTD"
+ type="java.lang.String"
+ direction="in"
+ default-value='null'>
+ <description>
+ This parameter is deprecated. Please use the 'doctype' parameter instead.
+
+ Used to specify the DOCTYPE DTD of the response page.
+ </description>
+ </parameter>
+
+ <parameter
+ name="renderContentType"
+ type="boolean"
+ direction="in"
+ default-value="true">
+ <description>
+ Determines whether to render an http-equiv element with the Content Type of this response.
+ </description>
+ </parameter>
+
+ <parameter
+ name="refresh"
+ type="int"
+ direction="in">
+ <description>
+ If specified, the page will refresh itself after the specified delay (in seconds).
+ </description>
+ </parameter>
+
+ <parameter
+ name="delegate"
+ type="org.apache.tapestry.IRender"
+ direction="in">
+ <description>
+ If specified, the delegate is rendered before the close of the <head>
+ tag (typically used to provide <meta> tags).
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/html/package.html b/tapestry-framework/src/org/apache/tapestry/html/package.html
new file mode 100644
index 0000000..a74d010
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/html/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Components specific to the creation of HTML pages, including sophisticated
+DHTML JavaScript effects.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/AbstractLinkTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractLinkTag.java
new file mode 100644
index 0000000..dfda6d6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractLinkTag.java
@@ -0,0 +1,119 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Abstract super-class of Tapestry JSP tags that produce a hyperlink
+ * (<code><a></code>) tag. Tags use a
+ * {@link org.apache.tapestry.jsp.URLRetriever} for the <code>href</code>
+ * attribute, and may include a
+ * <code>class</code> attribute (based on
+ * the {@link #getStyleClass() styleClass property}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractLinkTag extends AbstractTapestryTag
+{
+ private String _styleClass;
+
+ public String getStyleClass()
+ {
+ return _styleClass;
+ }
+
+ public void setStyleClass(String styleClass)
+ {
+ _styleClass = styleClass;
+ }
+
+ /**
+ * Writes a <code></a></code> tag.
+ *
+ * @return {@link javax.servlet.jsp.tagext.Tag#EVAL_PAGE}
+ *
+ **/
+
+ public int doEndTag() throws JspException
+ {
+ JspWriter out = pageContext.getOut();
+
+ try
+ {
+ out.print("</a>");
+ }
+ catch (IOException ex)
+ {
+ throw new JspException(
+ Tapestry.format("AbstractLinkTag.io-exception", ex.getMessage()));
+ }
+
+ return EVAL_PAGE;
+ }
+
+ /**
+ * Writes a <code><a> tag. The tag may
+ * have a <code>class</code> attribute if the
+ * {@link #getStyleClass() styleClass property}
+ * is not null. The <code>href</code>
+ * attribute is provided via
+ * a {@link #getURLRetriever() URLRetriever}.
+ *
+ * @return {@link javax.servlet.jsp.tagext.Tag#EVAL_BODY_INCLUDE}
+ *
+ **/
+
+ public int doStartTag() throws JspException
+ {
+ JspWriter out = pageContext.getOut();
+
+ try
+ {
+ out.print("<a");
+
+ if (_styleClass != null)
+ {
+ out.print(" class=\"");
+ out.print(_styleClass);
+ out.print('"');
+ }
+
+ out.print(" href=\"");
+
+ getURLRetriever().insertURL(getServlet());
+
+ // And we're back! Finish off the tag.
+
+ out.print("\">");
+ }
+ catch (IOException ex)
+ {
+ throw new JspException(
+ Tapestry.format("AbstractLinkTag.io-exception", ex.getMessage()));
+ }
+
+ return EVAL_BODY_INCLUDE;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/AbstractTapestryTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractTapestryTag.java
new file mode 100644
index 0000000..8bbed55
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractTapestryTag.java
@@ -0,0 +1,163 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+import ognl.ClassResolver;
+import ognl.DefaultClassResolver;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.link.DirectLink;
+import org.apache.tapestry.parse.TemplateParser;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Contains common code and methods for all the Tapestry JSP tag implementations.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractTapestryTag extends TagSupport
+{
+
+ private String _servlet = "/app";
+
+ public String getServlet()
+ {
+ return _servlet;
+ }
+
+ public void setServlet(String servlet)
+ {
+ _servlet = servlet;
+ }
+
+ /**
+ * Implemented in subclasses to provide a
+ * {@link org.apache.tapestry.jsp.URLRetriever} instance that
+ * can insert the correct URL into the output.
+ *
+ **/
+
+ protected abstract URLRetriever getURLRetriever() throws JspException;
+
+ /**
+ * Builds an object array appropriate for use as the service parameters
+ * for the external service. The first object in the array is the name
+ * of the page. Any additional objects are service parameters to be
+ * supplied to the listener method.
+ *
+ * <p>
+ * The parameters are converted to an array of objects
+ * via {@link #convertParameters(String)}.
+ *
+ **/
+
+ protected Object[] constructExternalServiceParameters(String pageName, String parameters)
+ throws JspException
+ {
+
+ Object[] resolvedParameters = convertParameters(parameters);
+
+ int count = Tapestry.size(resolvedParameters);
+
+ if (count == 0)
+ return new Object[] { pageName };
+
+ List list = new ArrayList();
+ list.add(pageName);
+
+ for (int i = 0; i < count; i++)
+ list.add(resolvedParameters[i]);
+
+ return list.toArray();
+ }
+
+ /**
+ * <p>The external service allows service parameters (an array of
+ * objects) to be passed along inside the URL. This method converts
+ * the input string into an array of parameter objects.
+ * <ul>
+ * <li>If parameters is null, the no parameters are passed
+ * <li>If parameters starts with "ognl:" it is treated as an OGNL expression:
+ * <ul>
+ * <li>The expression is evaluated using the
+ * {@link javax.servlet.jsp.PageContext page context} as the root object
+ * <li>If the expression value is a Map, then the Map is converted to
+ * an array via (@link org.apache.tapestry.Tapestry#convertMapToArray(Map)}
+ * <li>Otherwise, the expression value is converted using
+ * {@link org.apache.tapestry.link.DirectLink#constructServiceParameters(Object)}.
+ * </ul>
+ * <li>Otherwise, parameters are simply a string, which is included as the lone
+ * service parameter
+ * </ul>
+ **/
+
+ protected Object[] convertParameters(String _parameters) throws JspException
+ {
+ if (_parameters == null)
+ return null;
+
+ if (_parameters.startsWith(TemplateParser.OGNL_EXPRESSION_PREFIX))
+ {
+ String expression =
+ _parameters.substring(TemplateParser.OGNL_EXPRESSION_PREFIX.length() + 1);
+
+ return convertExpression(expression);
+ }
+
+ return new Object[] { _parameters };
+ }
+
+ private Object[] convertExpression(String expression) throws JspException
+ {
+ Object value = evaluateExpression(expression);
+
+ if (value == null)
+ return null;
+
+ if (value instanceof Map)
+ return Tapestry.convertMapToArray((Map) value);
+
+ return DirectLink.constructServiceParameters(value);
+ }
+
+ private Object evaluateExpression(String expression) throws JspException
+ {
+ ClassResolver resolver = new DefaultClassResolver();
+
+ try
+ {
+ return OgnlUtils.get(expression, resolver, pageContext);
+ }
+ catch (Throwable t)
+ {
+ throw new JspException(
+ Tapestry.format(
+ "AbstractTapestryTag.unable-to-evaluate-expression",
+ expression,
+ t.getMessage()));
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/AbstractURLTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractURLTag.java
new file mode 100644
index 0000000..f516312
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/AbstractURLTag.java
@@ -0,0 +1,42 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import javax.servlet.jsp.JspException;
+
+/**
+ * Base class for tags which simply insert a URL into the output.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractURLTag extends AbstractTapestryTag
+{
+
+ /**
+ * Inserts the URL and returns {@link javax.servlet.jsp.tagext.Tag#SKIP_BODY}.
+ *
+ **/
+
+ public int doStartTag() throws JspException
+ {
+ getURLRetriever().insertURL(getServlet());
+
+ return SKIP_BODY;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/ExternalTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/ExternalTag.java
new file mode 100644
index 0000000..6dd80e4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/ExternalTag.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import javax.servlet.jsp.JspException;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * JSP tag that makes use of the Tapestry external service.
+ * Parameters may be passed in the URL.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ExternalTag extends AbstractLinkTag
+{
+ private String _parameters;
+ private String _page;
+
+ protected URLRetriever getURLRetriever() throws JspException
+ {
+ return new URLRetriever(pageContext, Tapestry.EXTERNAL_SERVICE,
+ constructExternalServiceParameters(_page, _parameters));
+ }
+
+
+ public void setParameters(String parameters)
+ {
+ _parameters = parameters;
+ }
+
+ public String getPage()
+ {
+ return _page;
+ }
+
+ public void setPage(String page)
+ {
+ _page = page;
+ }
+
+ public String getParameters()
+ {
+ return _parameters;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/ExternalURLTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/ExternalURLTag.java
new file mode 100644
index 0000000..9999453
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/ExternalURLTag.java
@@ -0,0 +1,64 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import javax.servlet.jsp.JspException;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Like the {@link org.apache.tapestry.jsp.ExternalTag}, but inserts just
+ * the URL.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class ExternalURLTag extends AbstractURLTag
+{
+
+ private String _parameters;
+ private String _page;
+
+ protected URLRetriever getURLRetriever() throws JspException
+ {
+ return new URLRetriever(
+ pageContext,
+ Tapestry.EXTERNAL_SERVICE,
+ constructExternalServiceParameters(_page, _parameters));
+ }
+
+ public void setParameters(String parameters)
+ {
+ _parameters = parameters;
+ }
+
+ public String getPage()
+ {
+ return _page;
+ }
+
+ public void setPage(String page)
+ {
+ _page = page;
+ }
+
+ public String getParameters()
+ {
+ return _parameters;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/PageTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/PageTag.java
new file mode 100644
index 0000000..5e587ad
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/PageTag.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Creates a link from a JSP page to a Tapestry application page.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class PageTag extends AbstractLinkTag
+{
+ private String _page;
+
+ public String getPage()
+ {
+ return _page;
+ }
+
+ public void setPage(String pageName)
+ {
+ _page = pageName;
+ }
+
+ protected URLRetriever getURLRetriever()
+ {
+ return new URLRetriever(pageContext, Tapestry.PAGE_SERVICE, new String[] { _page });
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/PageURLTag.java b/tapestry-framework/src/org/apache/tapestry/jsp/PageURLTag.java
new file mode 100644
index 0000000..721820f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/PageURLTag.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import javax.servlet.jsp.JspException;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Inserts just the URL for a page service request into the output.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class PageURLTag extends AbstractURLTag
+{
+ private String _page;
+
+ protected URLRetriever getURLRetriever() throws JspException
+ {
+ return new URLRetriever(pageContext, Tapestry.PAGE_SERVICE, new String[] { _page });
+ }
+
+ public String getPage()
+ {
+ return _page;
+ }
+
+ public void setPage(String page)
+ {
+ _page = page;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/URLRetriever.java b/tapestry-framework/src/org/apache/tapestry/jsp/URLRetriever.java
new file mode 100644
index 0000000..ad45431
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/URLRetriever.java
@@ -0,0 +1,102 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.jsp;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Encapsulates the process of calling into the Tapestry servlet to retrieve
+ * a URL.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class URLRetriever
+{
+ private static final Log LOG = LogFactory.getLog(URLRetriever.class);
+
+ private PageContext _pageContext;
+ private String _serviceName;
+ private Object[] _serviceParameters;
+
+ public URLRetriever(PageContext pageContext, String serviceName, Object[] serviceParameters)
+ {
+ _pageContext = pageContext;
+ _serviceName = serviceName;
+ _serviceParameters = serviceParameters;
+ }
+
+ /**
+ * Invokes the servlet to retrieve the URL. The URL is inserted
+ * into the output (as with
+ * {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}).
+ *
+ *
+ **/
+
+ public void insertURL(String servletPath) throws JspException
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Obtaining Tapestry URL for service " + _serviceName + " at " + servletPath);
+
+ ServletRequest request = _pageContext.getRequest();
+
+ RequestDispatcher dispatcher = request.getRequestDispatcher(servletPath);
+
+ if (dispatcher == null)
+ throw new JspException(
+ Tapestry.format("URLRetriever.unable-to-find-dispatcher", servletPath));
+
+ request.setAttribute(Tapestry.TAG_SUPPORT_SERVICE_ATTRIBUTE, _serviceName);
+ request.setAttribute(Tapestry.TAG_SUPPORT_PARAMETERS_ATTRIBUTE, _serviceParameters);
+ request.setAttribute(Tapestry.TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE, servletPath);
+
+ try
+ {
+ _pageContext.getOut().flush();
+
+ dispatcher.include(request, _pageContext.getResponse());
+ }
+ catch (IOException ex)
+ {
+ throw new JspException(
+ Tapestry.format("URLRetriever.io-exception", servletPath, ex.getMessage()));
+ }
+ catch (ServletException ex)
+ {
+ throw new JspException(
+ Tapestry.format("URLRetriever.servlet-exception", servletPath, ex.getMessage()));
+ }
+ finally
+ {
+ request.removeAttribute(Tapestry.TAG_SUPPORT_SERVICE_ATTRIBUTE);
+ request.removeAttribute(Tapestry.TAG_SUPPORT_PARAMETERS_ATTRIBUTE);
+ request.removeAttribute(Tapestry.TAG_SUPPORT_SERVLET_PATH_ATTRIBUTE);
+ }
+
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/jsp/package.html b/tapestry-framework/src/org/apache/tapestry/jsp/package.html
new file mode 100644
index 0000000..bd9219b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/jsp/package.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<body>
+
+A simple JSP tag library that allows JSPs to include links to a Tapestry application.
+Currently supports just the page and external services.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/AbsoluteLinkRenderer.java b/tapestry-framework/src/org/apache/tapestry/link/AbsoluteLinkRenderer.java
new file mode 100644
index 0000000..709ee1c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/AbsoluteLinkRenderer.java
@@ -0,0 +1,94 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * Renders a link using an absolute URL, not simply a URI
+ * (as with {@link org.apache.tapestry.link.DefaultLinkRenderer}. In addition,
+ * the scheme, server and port may be changed (this may be appropriate when
+ * switching between secure and insecure portions of an application).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class AbsoluteLinkRenderer extends DefaultLinkRenderer
+{
+ private String _scheme;
+ private String _serverName;
+ private int _port;
+
+ public int getPort()
+ {
+ return _port;
+ }
+
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ public String getServerName()
+ {
+ return _serverName;
+ }
+
+ /**
+ * Used to override the port in the final URL, if specified. If not specified,
+ * the port provided by the {@link javax.servlet.ServletRequest#getServerPort() request}
+ * is used (typically, the value 80).
+ *
+ **/
+
+ public void setPort(int port)
+ {
+ _port = port;
+ }
+
+ /**
+ * Used to override the scheme in the final URL, if specified. If not specified,
+ * the scheme provided by the {@link javax.servlet.ServletRequest#getScheme() request}
+ * is used (typically, <code>http</code>).
+ *
+ **/
+
+ public void setScheme(String scheme)
+ {
+ _scheme = scheme;
+ }
+
+ /**
+ * Used to override the server name in the final URL, if specified. If not specified,
+ * the port provided by the {@link javax.servlet.ServletRequest#getServerName() request}
+ * is used.
+ *
+ **/
+
+ public void setServerName(String serverName)
+ {
+ _serverName = serverName;
+ }
+
+ protected String constructURL(ILink link, String anchor, IRequestCycle cycle)
+ {
+ return link.getAbsoluteURL(_scheme, _serverName, _port, anchor, true);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/link/AbstractLinkComponent.java b/tapestry-framework/src/org/apache/tapestry/link/AbstractLinkComponent.java
new file mode 100644
index 0000000..ccbb76e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/AbstractLinkComponent.java
@@ -0,0 +1,234 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.components.ILinkComponent;
+import org.apache.tapestry.components.LinkEventType;
+import org.apache.tapestry.engine.IEngineService;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.html.Body;
+
+/**
+ * Base class for
+ * implementations of {@link ILinkComponent}. Includes a disabled attribute
+ * (that should be bound to a disabled parameter),
+ * an anchor attribute, and a
+ * renderer attribute (that should be bound to a renderer parameter). A default,
+ * shared instance of {@link org.apache.tapestry.link.DefaultLinkRenderer} is
+ * used when no specific renderer is provided.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class AbstractLinkComponent extends AbstractComponent implements ILinkComponent
+{
+ private Map _eventHandlers;
+
+ public abstract boolean isDisabled();
+
+ /**
+ * Adds an event handler (typically, from a wrapped component such
+ * as a {@link org.apache.tapestry.html.Rollover}).
+ *
+ **/
+
+ public void addEventHandler(LinkEventType eventType, String functionName)
+ {
+ Object currentValue;
+
+ if (_eventHandlers == null)
+ _eventHandlers = new HashMap();
+
+ currentValue = _eventHandlers.get(eventType);
+
+ // The first value is added as a String
+
+ if (currentValue == null)
+ {
+ _eventHandlers.put(eventType, functionName);
+ return;
+ }
+
+ // When adding the second value, convert to a List
+
+ if (currentValue instanceof String)
+ {
+ List list = new ArrayList();
+ list.add(currentValue);
+ list.add(functionName);
+
+ _eventHandlers.put(eventType, list);
+ return;
+ }
+
+ // For the third and up, add the new function to the List
+
+ List list = (List) currentValue;
+ list.add(functionName);
+ }
+
+ /**
+ * Renders the link by delegating to an instance
+ * of {@link ILinkRenderer}.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ getRenderer().renderLink(writer, cycle, this);
+ }
+
+ protected void cleanupAfterRender(IRequestCycle cycle)
+ {
+ _eventHandlers = null;
+
+ super.cleanupAfterRender(cycle);
+ }
+
+ protected void writeEventHandlers(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ String name = null;
+
+ if (_eventHandlers == null)
+ return;
+
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractLinkComponent.events-need-body"),
+ this,
+ null,
+ null);
+
+ Iterator i = _eventHandlers.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ LinkEventType type = (LinkEventType) entry.getKey();
+
+ name = writeEventHandler(writer, body, name, type.getAttributeName(), entry.getValue());
+ }
+
+ }
+
+ protected String writeEventHandler(
+ IMarkupWriter writer,
+ Body body,
+ String name,
+ String attributeName,
+ Object value)
+ {
+ String wrapperFunctionName;
+
+ if (value instanceof String)
+ {
+ wrapperFunctionName = (String) value;
+ }
+ else
+ {
+ String finalName = name == null ? body.getUniqueString("Link") : name;
+
+ wrapperFunctionName = attributeName + "_" + finalName;
+
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("function ");
+ buffer.append(wrapperFunctionName);
+ buffer.append(" ()\n{\n");
+
+ Iterator i = ((List) value).iterator();
+ while (i.hasNext())
+ {
+ String functionName = (String) i.next();
+ buffer.append(" ");
+ buffer.append(functionName);
+ buffer.append("();\n");
+ }
+
+ buffer.append("}\n\n");
+
+ body.addBodyScript(buffer.toString());
+ }
+
+ writer.attribute(attributeName, "javascript:" + wrapperFunctionName + "();");
+
+ return name;
+ }
+
+ /** @since 3.0 **/
+
+ public abstract ILinkRenderer getRenderer();
+
+ public abstract void setRenderer(ILinkRenderer renderer);
+
+ public void renderAdditionalAttributes(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ writeEventHandlers(writer, cycle);
+
+ // Generate additional attributes from informal parameters.
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ /**
+ * Utility method for subclasses; Gets the named service from the engine
+ * and invokes {@link IEngineService#getLink(IRequestCycle, org.apache.tapestry.IComponent, Object[])}
+ * on it.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected ILink getLink(IRequestCycle cycle, String serviceName, Object[] serviceParameters)
+ {
+ IEngineService service = cycle.getEngine().getService(serviceName);
+
+ return service.getLink(cycle, this, serviceParameters);
+ }
+
+ public abstract String getAnchor();
+
+ public ILink getLink(IRequestCycle cycle)
+ {
+ return null;
+ }
+
+ /**
+ * Sets the renderer parameter property to its default value
+ * {@link DefaultLinkRenderer#SHARED_INSTANCE}.
+ *
+ * @since 3.0
+ */
+ protected void finishLoad()
+ {
+ setRenderer(DefaultLinkRenderer.SHARED_INSTANCE);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ActionLink.java b/tapestry-framework/src/org/apache/tapestry/link/ActionLink.java
new file mode 100644
index 0000000..fa01d01
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ActionLink.java
@@ -0,0 +1,77 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IAction;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.RenderRewoundException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component for creating a link that is handled using the action service.
+ *
+ * [<a href="../../../../../ComponentReference/ActionLink.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class ActionLink extends AbstractLinkComponent implements IAction
+{
+ /**
+ * Returns true if the stateful parameter is bound to
+ * a true value. If stateful is not bound, also returns
+ * the default, true.
+ *
+ * <p>Note that this method can be called when the
+ * component is not rendering, therefore it must
+ * directly access the {@link IBinding} for the stateful
+ * parameter.
+ *
+ **/
+
+ public boolean getRequiresSession()
+ {
+ IBinding statefulBinding = getStatefulBinding();
+
+ if (statefulBinding == null)
+ return true;
+
+ return statefulBinding.getBoolean();
+ }
+
+ public ILink getLink(IRequestCycle cycle)
+ {
+ String actionId = cycle.getNextActionId();
+
+ if (cycle.isRewound(this))
+ {
+ getListener().actionTriggered(this, cycle);
+
+ throw new RenderRewoundException(this);
+ }
+
+ return getLink(cycle, Tapestry.ACTION_SERVICE, new Object[] { actionId });
+ }
+
+ public abstract IBinding getStatefulBinding();
+
+ public abstract IActionListener getListener();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ActionLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/ActionLink.jwc
new file mode 100644
index 0000000..658ad5e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ActionLink.jwc
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.ActionLink">
+
+ <description>
+ Creates a contextual link within the response page. The page will be rewound to its
+ original state before the listener is invoked.
+ </description>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ required="yes"
+ direction="in"/>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+ <parameter name="stateful" type="boolean" direction="custom" property-name="stateful"/>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/DefaultLinkRenderer.java b/tapestry-framework/src/org/apache/tapestry/link/DefaultLinkRenderer.java
new file mode 100644
index 0000000..4a3ceb6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/DefaultLinkRenderer.java
@@ -0,0 +1,162 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.components.ILinkComponent;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * Default implementation of {@link org.apache.tapestry.link.ILinkRenderer}, which
+ * does nothing special. Can be used as a base class to provide
+ * additional handling.
+ *
+ * @author Howard Lewis Ship, David Solis
+ * @version $Id$
+ * @since 3.0
+ **/
+
+public class DefaultLinkRenderer implements ILinkRenderer
+{
+ /**
+ * A shared instance used as a default for any link that doesn't explicitly
+ * override.
+ *
+ **/
+
+ public static final ILinkRenderer SHARED_INSTANCE = new DefaultLinkRenderer();
+
+ public void renderLink(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent linkComponent)
+ {
+ IMarkupWriter wrappedWriter = null;
+
+ if (cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("AbstractLinkComponent.no-nesting"),
+ linkComponent,
+ null,
+ null);
+
+ cycle.setAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME, linkComponent);
+
+ boolean hasBody = getHasBody();
+
+ boolean disabled = linkComponent.isDisabled();
+
+ if (!disabled)
+ {
+ ILink l = linkComponent.getLink(cycle);
+
+ if (hasBody)
+ writer.begin(getElement());
+ else
+ writer.beginEmpty(getElement());
+
+ writer.attribute(getUrlAttribute(), constructURL(l, linkComponent.getAnchor(), cycle));
+
+ beforeBodyRender(writer, cycle, linkComponent);
+
+ // Allow the wrapped components a chance to render.
+ // Along the way, they may interact with this component
+ // and cause the name variable to get set.
+
+ wrappedWriter = writer.getNestedWriter();
+ }
+ else
+ wrappedWriter = writer;
+
+ if (hasBody)
+ linkComponent.renderBody(wrappedWriter, cycle);
+
+ if (!disabled)
+ {
+ afterBodyRender(writer, cycle, linkComponent);
+
+ linkComponent.renderAdditionalAttributes(writer, cycle);
+
+ if (hasBody)
+ {
+ wrappedWriter.close();
+
+ // Close the <element> tag
+
+ writer.end();
+ }
+ else
+ writer.closeTag();
+ }
+
+ cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
+ }
+
+ /**
+ * Converts the EngineServiceLink into a URI or URL. This implementation
+ * simply invokes {@link ILink#getURL(String, boolean)}.
+ *
+ **/
+
+ protected String constructURL(ILink link, String anchor, IRequestCycle cycle)
+ {
+ return link.getURL(anchor, true);
+ }
+
+ /**
+ * Invoked after the href attribute has been written but before
+ * the body of the link is rendered (but only if the link
+ * is not disabled).
+ *
+ * <p>
+ * This implementation does nothing.
+ *
+ **/
+
+ protected void beforeBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
+ {
+ }
+
+ /**
+ * Invoked after the body of the link is rendered, but before
+ * {@link ILinkComponent#renderAdditionalAttributes(IMarkupWriter, IRequestCycle)} is invoked
+ * (but only if the link is not disabled).
+ *
+ * <p>
+ * This implementation does nothing.
+ *
+ **/
+
+ protected void afterBodyRender(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent link)
+ {
+ }
+
+ /** @since 3.0 **/
+
+ protected String getElement()
+ {
+ return "a";
+ }
+
+ protected String getUrlAttribute()
+ {
+ return "href";
+ }
+
+ protected boolean getHasBody()
+ {
+ return true;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/link/DirectLink.java b/tapestry-framework/src/org/apache/tapestry/link/DirectLink.java
new file mode 100644
index 0000000..2c7ff20
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/DirectLink.java
@@ -0,0 +1,125 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import java.util.List;
+
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IDirect;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component for creating a link using the direct service; used for actions that
+ * are not dependant on dynamic page state.
+ *
+ * [<a href="../../../../../ComponentReference/DirectLink.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class DirectLink extends AbstractLinkComponent implements IDirect
+{
+
+ public abstract IBinding getStatefulBinding();
+ public abstract IActionListener getListener();
+
+ /**
+ * Returns true if the stateful parameter is bound to
+ * a true value. If stateful is not bound, also returns
+ * the default, true. May be invoked when not renderring.
+ *
+ **/
+
+ public boolean isStateful()
+ {
+ IBinding statefulBinding = getStatefulBinding();
+
+ if (statefulBinding == null)
+ return true;
+
+ return statefulBinding.getBoolean();
+ }
+
+ public ILink getLink(IRequestCycle cycle)
+ {
+ return getLink(cycle, Tapestry.DIRECT_SERVICE, constructServiceParameters(getParameters()));
+ }
+
+ /**
+ * Converts a service parameters value to an array
+ * of objects.
+ * This is used by the {@link DirectLink}, {@link ServiceLink}
+ * and {@link ExternalLink}
+ * components.
+ *
+ * @param parameterValue the input value which may be
+ * <ul>
+ * <li>null (returns null)
+ * <li>An array of Object (returns the array)
+ * <li>A {@link List} (returns an array of the values in the List})
+ * <li>A single object (returns the object as a single-element array)
+ * </ul>
+ *
+ * @return An array representation of the input object.
+ *
+ * @since 2.2
+ **/
+
+ public static Object[] constructServiceParameters(Object parameterValue)
+ {
+ if (parameterValue == null)
+ return null;
+
+ if (parameterValue instanceof Object[])
+ return (Object[]) parameterValue;
+
+ if (parameterValue instanceof List)
+ {
+ List list = (List) parameterValue;
+
+ return list.toArray();
+ }
+
+ return new Object[] { parameterValue };
+ }
+
+ /**
+ * Invoked by the direct service to trigger the application-specific
+ * action by notifying the {@link IActionListener listener}.
+ *
+ * @throws org.apache.tapestry.StaleSessionException if the component is stateful, and
+ * the session is new.
+ *
+ **/
+
+ public void trigger(IRequestCycle cycle)
+ {
+ IActionListener listener = getListener();
+
+ if (listener == null)
+ throw Tapestry.createRequiredParameterException(this, "listener");
+
+ listener.actionTriggered(this, cycle);
+ }
+
+ /** @since 2.2 **/
+
+ public abstract Object getParameters();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/DirectLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/DirectLink.jwc
new file mode 100644
index 0000000..e3a214e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/DirectLink.jwc
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.DirectLink">
+
+ <description>
+ Creates a non-contextual link. Non-persistent state can be stored within the link
+ using the context.
+ </description>
+
+ <parameter name="listener"
+ type="org.apache.tapestry.IActionListener"
+ required="yes"
+ direction="auto"/>
+
+ <parameter name="parameters" type="java.lang.Object" direction="in">
+ <description>
+ An object, or list of objects, encoded into the URL
+ as service parameters.
+ </description>
+ </parameter>
+
+ <parameter name="stateful" type="boolean" direction="custom"/>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.java b/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.java
new file mode 100644
index 0000000..a6e6958
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.java
@@ -0,0 +1,62 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component for creating a link to {@link org.apache.tapestry.IExternalPage} using the
+ * {@link org.apache.tapestry.engine.ExternalService}.
+ *
+ * [<a href="../../../../../ComponentReference/ExternalLink.html">Component Reference</a>]
+ *
+ * @see org.apache.tapestry.IExternalPage
+ * @see org.apache.tapestry.engine.ExternalService
+ *
+ * @author Malcolm Edgar
+ * @version $Id$
+ *
+ **/
+
+public abstract class ExternalLink extends AbstractLinkComponent
+{
+ public ILink getLink(IRequestCycle cycle)
+ {
+ return getLink(cycle, Tapestry.EXTERNAL_SERVICE, getServiceParameters());
+ }
+
+ private Object[] getServiceParameters()
+ {
+ Object[] pageParameters = DirectLink.constructServiceParameters(getParameters());
+ String targetPage = getTargetPage();
+
+ if (pageParameters == null)
+ return new Object[] { targetPage };
+
+ Object[] parameters = new Object[pageParameters.length + 1];
+
+ parameters[0] = targetPage;
+
+ System.arraycopy(pageParameters, 0, parameters, 1, pageParameters.length);
+
+ return parameters;
+ }
+
+ public abstract Object getParameters();
+
+ public abstract String getTargetPage();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.jwc
new file mode 100644
index 0000000..c1b31e5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ExternalLink.jwc
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.ExternalLink">
+
+ <description>Creates a IExternalPage link.</description>
+
+ <parameter name="page"
+ type="java.lang.String"
+ required="yes"
+ property-name="targetPage"
+ direction="in"/>
+
+ <parameter name="parameters" type="java.lang.Object" direction="in">
+ <description>
+ An object, or list of objects, encoded into the URL
+ as service parameters.
+ </description>
+ </parameter>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/GenericLink.java b/tapestry-framework/src/org/apache/tapestry/link/GenericLink.java
new file mode 100644
index 0000000..7c42f6a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/GenericLink.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * An implementation of {@link org.apache.tapestry.components.ILinkComponent}
+ * that allows
+ * the exact HREF to be specified, usually used for client side
+ * scripting.
+ *
+ * [<a href="../../../../../ComponentReference/GenericLink.html">Component Reference</a>]
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.2
+ *
+ **/
+
+public abstract class GenericLink extends AbstractLinkComponent
+{
+ public abstract String getHref();
+
+ public ILink getLink(IRequestCycle cycle)
+ {
+ return new StaticLink(getHref());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/GenericLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/GenericLink.jwc
new file mode 100644
index 0000000..2e33130
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/GenericLink.jwc
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.GenericLink">
+
+ <description>
+ Creates a link to an application specified URL.
+ </description>
+
+ <parameter name="href" type="java.lang.String" required="yes" direction="in"/>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ILinkRenderer.java b/tapestry-framework/src/org/apache/tapestry/link/ILinkRenderer.java
new file mode 100644
index 0000000..14d1309
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ILinkRenderer.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.components.ILinkComponent;
+
+/**
+ * Used by various instances of {@link org.apache.tapestry.components.ILinkComponent} to
+ * actually renderer a link. Implementations of the interface can manipulate
+ * some of the details of how the link is written.
+ *
+ * <p>
+ * A link rendered may be used in many threads, and must be threadsafe.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface ILinkRenderer
+{
+ /**
+ * Renders the link, taking into account whether the link is
+ * {@link org.apache.tapestry.components.ILinkComponent#isDisabled() disabled}.
+ * This is complicated by the fact that the rendering of the body must be done
+ * within a nested writer, since the Link component will not render its tag
+ * until after its body renders (to allow for any wrapped components that need
+ * to write event handlers for the link).
+ *
+ * <p>
+ * The renderer is expected to call back into the link component to handle
+ * any informal parameters, and to handle events output.
+ *
+ *
+ **/
+
+ public void renderLink(IMarkupWriter writer, IRequestCycle cycle, ILinkComponent linkComponent);
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/link/PageLink.java b/tapestry-framework/src/org/apache/tapestry/link/PageLink.java
new file mode 100644
index 0000000..9e76785
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/PageLink.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component for creating a navigation link to another page,
+ * using the page service.
+ *
+ * [<a href="../../../../../ComponentReference/PageLink.html">Component Reference</a>]
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class PageLink extends AbstractLinkComponent
+{
+ public ILink getLink(IRequestCycle cycle)
+ {
+ String parameter = null;
+ INamespace namespace = getTargetNamespace();
+ String targetPage = getTargetPage();
+
+ if (namespace == null)
+ parameter = targetPage;
+ else
+ parameter = namespace.constructQualifiedName(targetPage);
+
+ return getLink(cycle, Tapestry.PAGE_SERVICE, new String[] { parameter });
+ }
+
+ public abstract String getTargetPage();
+
+ /** @since 2.2 **/
+
+ public abstract INamespace getTargetNamespace();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/PageLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/PageLink.jwc
new file mode 100644
index 0000000..7a07237
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/PageLink.jwc
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.PageLink">
+
+ <description>
+ Creates a link to another page within the application.
+ </description>
+
+ <parameter name="page"
+ type="java.lang.String"
+ required="yes"
+ property-name="targetPage"
+ direction="in"/>
+
+ <parameter name="namespace"
+ type="org.apache.tapestry.INamespace"
+ required="no"
+ property-name="targetNamespace"
+ direction="in"/>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.java b/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.java
new file mode 100644
index 0000000..46378a5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * A component for creating a link for an arbitrary {@link org.apache.tapestry.engine.IEngineService
+ * engine service}. A ServiceLink component can emulate an {@link ActionLink},
+ * {@link PageLink} or {@link DirectLink} component, but is most often used in
+ * conjunction with an application-specific service.
+ *
+ * [<a href="../../../../../ComponentReference/ServiceLink.html">Component Reference</a>]
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class ServiceLink extends AbstractLinkComponent
+{
+ public ILink getLink(IRequestCycle cycle)
+ {
+ Object[] parameters = DirectLink.constructServiceParameters(getParameters());
+
+ return getLink(cycle, getService(), parameters);
+ }
+
+ public abstract String getService();
+
+ public abstract Object getParameters();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.jwc b/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.jwc
new file mode 100644
index 0000000..66c2047
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/ServiceLink.jwc
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.link.ServiceLink">
+
+ <description>
+ Creates a link using an arbitrary engine service.
+ </description>
+
+ <parameter name="service" type="java.lang.String" required="yes" direction="in"/>
+
+ <parameter name="parameters" type="java.lang.Object" direction="in">
+ <description>
+ A object or string, or array of objects and strings, encoded into the URL
+ as service parameters.
+ </description>
+ </parameter>
+
+ <parameter name="disabled"
+ type="boolean"
+ direction="in"/>
+
+ <parameter name="anchor"
+ type="java.lang.String"
+ direction="in"/>
+
+ <parameter name="renderer"
+ type="org.apache.tapestry.link.ILinkRenderer"
+ direction="in"/>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/link/StaticLink.java b/tapestry-framework/src/org/apache/tapestry/link/StaticLink.java
new file mode 100644
index 0000000..7ee0836
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/StaticLink.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.link;
+
+import org.apache.tapestry.engine.ILink;
+
+/**
+ * Used by {@link org.apache.tapestry.link.GenericLink} to represent
+ * an external, static URL.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class StaticLink implements ILink
+{
+ private String _url;
+
+ public StaticLink(String url)
+ {
+ _url = url;
+ }
+
+ public String getURL()
+ {
+ return _url;
+ }
+
+ public String getURL(String anchor, boolean includeParameters)
+ {
+ if (anchor == null)
+ return _url;
+
+ return _url + "#" + anchor;
+ }
+
+ public String getAbsoluteURL()
+ {
+ return _url;
+ }
+
+ public String getAbsoluteURL(
+ String scheme,
+ String server,
+ int port,
+ String anchor,
+ boolean includeParameters)
+ {
+ return getURL(anchor, false);
+ }
+
+ public String[] getParameterNames()
+ {
+ return null;
+ }
+
+ public String[] getParameterValues(String name)
+ {
+ throw new IllegalArgumentException();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/link/package.html b/tapestry-framework/src/org/apache/tapestry/link/package.html
new file mode 100644
index 0000000..4c99d7f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/link/package.html
@@ -0,0 +1,26 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Components for creating links on the page that trigger application behavior
+when clicked. These links are compatible with HTML and WML (they
+use the basic <a> element).
+
+<p>Each component is related to a different
+{@link org.apache.tapestry.engine.IEngineService}, except for {@link org.apache.tapestry.link.ServiceLink}
+which is parameterized to use any of the available services ... which is useful with
+applications that define their own services.
+
+<p>Link components have a second function, to provide event handling support to the
+components they wrap. This is how a {@link org.apache.tapestry.html.Rollover} component manages
+to include DHTML and JavaScript that preloads the images and changes the displayed image as the
+mouse enters and exits the "hot" area defined by the link.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/listener/ListenerMap.java b/tapestry-framework/src/org/apache/tapestry/listener/ListenerMap.java
new file mode 100644
index 0000000..4258188
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/listener/ListenerMap.java
@@ -0,0 +1,319 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.listener;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import ognl.OgnlRuntime;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IActionListener;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Maps a class to a set of listeners based on the public methods of the class.
+ * {@link org.apache.tapestry.listener.ListenerMapPropertyAccessor} is setup
+ * to provide these methods as named properties of the ListenerMap.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ * @since 1.0.2
+ *
+ **/
+
+public class ListenerMap
+{
+ private static final Log LOG = LogFactory.getLog(ListenerMap.class);
+
+ static {
+ OgnlRuntime.setPropertyAccessor(ListenerMap.class, new ListenerMapPropertyAccessor());
+ }
+
+ private Object _target;
+
+ /**
+ * A {@link Map} of relevant {@link Method}s, keyed on method name.
+ * This is just the public void methods that take an {@link IRequestCycle}
+ * and throw nothing or just {@link ApplicationRuntimeException}.
+ */
+
+ private Map _methodMap;
+
+ /**
+ * A {@link Map} of cached listener instances, keyed on method name
+ *
+ **/
+
+ private Map _listenerCache = new HashMap();
+
+ /**
+ * A {@link Map}, keyed on Class, of {@link Map} ... the method map
+ * for any particular instance of the given class.
+ *
+ **/
+
+ private static Map _classMap = new HashMap();
+
+ /**
+ * Implements both listener interfaces.
+ *
+ **/
+
+ private class SyntheticListener implements IActionListener
+ {
+ private Method _method;
+
+ SyntheticListener(Method method)
+ {
+ _method = method;
+ }
+
+ private void invoke(IRequestCycle cycle)
+ {
+ Object[] args = new Object[] { cycle };
+
+ invokeTargetMethod(_target, _method, args);
+ }
+
+ public void actionTriggered(IComponent component, IRequestCycle cycle)
+ {
+ invoke(cycle);
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("SyntheticListener[");
+
+ buffer.append(_target);
+ buffer.append(' ');
+ buffer.append(_method);
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ }
+
+ public ListenerMap(Object target)
+ {
+ _target = target;
+ }
+
+ /**
+ * Gets a listener for the given name (which is both a property name
+ * and a method name). The listener is created as needed, but is
+ * also cached for later use.
+ *
+ * @throws ApplicationRuntimeException if the listener can not be created.
+ **/
+
+ public synchronized Object getListener(String name)
+ {
+ Object listener = null;
+
+ listener = _listenerCache.get(name);
+
+ if (listener == null)
+ {
+ listener = createListener(name);
+
+ _listenerCache.put(name, listener);
+ }
+
+ return listener;
+ }
+
+ /**
+ * Returns an object that implements {@link IActionListener}.
+ * This involves looking up the method by name and determining which
+ * inner class to create.
+ **/
+
+ private synchronized Object createListener(String name)
+ {
+ if (_methodMap == null)
+ getMethodMap();
+
+ Method method = (Method) _methodMap.get(name);
+
+ if (method == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ListenerMap.object-missing-method", _target, name));
+
+ return new SyntheticListener(method);
+ }
+
+ /**
+ * Gets the method map for the current instance. If necessary, it is constructed and cached (for other instances
+ * of the same class).
+ *
+ **/
+
+ private synchronized Map getMethodMap()
+ {
+ if (_methodMap != null)
+ return _methodMap;
+
+ Class beanClass = _target.getClass();
+
+ synchronized (_classMap)
+ {
+ _methodMap = (Map) _classMap.get(beanClass);
+
+ if (_methodMap == null)
+ {
+ _methodMap = buildMethodMap(beanClass);
+
+ _classMap.put(beanClass, _methodMap);
+ }
+ }
+
+ return _methodMap;
+ }
+
+ private static Map buildMethodMap(Class beanClass)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Building method map for class " + beanClass.getName());
+
+ Map result = new HashMap();
+ Method[] methods = beanClass.getMethods();
+
+ for (int i = 0; i < methods.length; i++)
+ {
+ Method m = methods[i];
+ int mods = m.getModifiers();
+
+ if (Modifier.isStatic(mods))
+ continue;
+
+ // Probably not necessary, getMethods() returns only public
+ // methods.
+
+ if (!Modifier.isPublic(mods))
+ continue;
+
+ // Must return void
+
+ if (m.getReturnType() != Void.TYPE)
+ continue;
+
+ Class[] parmTypes = m.getParameterTypes();
+
+ if (parmTypes.length != 1)
+ continue;
+
+ // parm must be IRequestCycle
+
+ if (!parmTypes[0].equals(IRequestCycle.class))
+ continue;
+
+ // Ha! Passed all tests.
+
+ result.put(m.getName(), m);
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Invoked by the inner listener/adaptor classes to
+ * invoke the method.
+ *
+ **/
+
+ private static void invokeTargetMethod(Object target, Method method, Object[] args)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Invoking listener method " + method + " on " + target);
+
+ try
+ {
+ try
+ {
+ method.invoke(target, args);
+ }
+ catch (InvocationTargetException ex)
+ {
+ Throwable inner = ex.getTargetException();
+
+ if (inner instanceof ApplicationRuntimeException)
+ throw (ApplicationRuntimeException) inner;
+
+ // Edit out the InvocationTargetException, if possible.
+
+ if (inner instanceof RuntimeException)
+ throw (RuntimeException) inner;
+
+ throw ex;
+ }
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ throw ex;
+ }
+ catch (Exception ex)
+ {
+ // Catch InvocationTargetException or, preferrably,
+ // the inner exception here (if its a runtime exception).
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ListenerMap.unable-to-invoke-method", method.getName(), target, ex.getMessage()),
+ ex);
+ }
+ }
+
+ /**
+ * Returns an unmodifiable collection of the
+ * names of the listeners implemented by the target class.
+ *
+ * @since 1.0.6
+ *
+ **/
+
+ public synchronized Collection getListenerNames()
+ {
+ return Collections.unmodifiableCollection(getMethodMap().keySet());
+ }
+
+ /**
+ * Returns true if this ListenerMap can provide a listener
+ * with the given name.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public synchronized boolean canProvideListener(String name)
+ {
+ return getMethodMap().containsKey(name);
+ }
+
+ public String toString()
+ {
+ return "ListenerMap[" + _target + "]";
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/listener/ListenerMapPropertyAccessor.java b/tapestry-framework/src/org/apache/tapestry/listener/ListenerMapPropertyAccessor.java
new file mode 100644
index 0000000..2d58bcf
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/listener/ListenerMapPropertyAccessor.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.listener;
+
+import java.util.Map;
+
+import ognl.ObjectPropertyAccessor;
+import ognl.OgnlException;
+
+/**
+ * Exposes {@link org.apache.tapestry.IActionListener} listeners
+ * provided by the {@link ListenerMap} as read-only properties
+ * of the ListenerMap.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class ListenerMapPropertyAccessor extends ObjectPropertyAccessor
+{
+ /**
+ * Checks to see if the ListenerMap provides the named
+ * listener, returning the listener if it does. Otherwise,
+ * invokes the super implementation.
+ *
+ **/
+
+ public Object getProperty(Map context, Object target, Object name) throws OgnlException
+ {
+ ListenerMap map = (ListenerMap) target;
+ String listenerName = (String) name;
+
+ if (map.canProvideListener(listenerName))
+ return map.getListener(listenerName);
+
+ return super.getProperty(context, target, name);
+ }
+
+ /**
+ * Returns true if the ListenerMap contains the named listener,
+ * otherwise invokes super-implementation.
+ *
+ **/
+
+ public boolean hasGetProperty(Map context, Object target, Object oname) throws OgnlException
+ {
+ ListenerMap map = (ListenerMap) target;
+ String listenerName = (String) oname;
+
+ if (map.canProvideListener(listenerName))
+ return true;
+
+ return super.hasGetProperty(context, target, oname);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/listener/package.html b/tapestry-framework/src/org/apache/tapestry/listener/package.html
new file mode 100644
index 0000000..9b10c07
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/listener/package.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<body>
+
+<p>Support classes that allows an object
+to expose listener <em>methods</em> instead of listener <em>properties</em>.
+
+<p>
+Normally, a listener property must be an object that implement
+{@link org.apache.tapestry.IActionListener}. This can be cumbersome, in practice, as it
+typically involves creating an anonymous inner class.
+
+<p>
+Using this mechanism, classes can instead implement listener <em>methods</em>.
+A listener method takes the form:
+
+<pre>
+public void <em>method-name</em>({@link org.apache.tapestry.IRequestCycle} cycle)
+throws {@link org.apache.tapestry.ApplicationRuntimeException}</code>
+</pre>
+
+<p>The <code>throws</code> clause is optional, but may not throw any
+additional exceptions.
+
+<p>Tapestry will create an appropriate listener object that will invoke the
+corresponding method.
+
+<p>The methods can be accessed using the property path "<code>listeners.<em>method-name</em></code>"
+
+@see org.apache.tapestry.listener.ListenerMap
+@see org.apache.tapestry.AbstractComponent#getListeners()
+@see org.apache.tapestry.engine.AbstractEngine#getListeners()
+@since 1.0.2
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/DefaultMultipartDecoder.java b/tapestry-framework/src/org/apache/tapestry/multipart/DefaultMultipartDecoder.java
new file mode 100644
index 0000000..ce3f327
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/DefaultMultipartDecoder.java
@@ -0,0 +1,247 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.multipart;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.fileupload.DiskFileUpload;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.IUploadFile;
+
+/**
+ * Decodes the data in a <code>multipart/form-data</code> HTTP request, handling
+ * file uploads and multi-valued parameters. After decoding, the class is used
+ * to access the parameter values.
+ *
+ * <p>This implementation is a thin wrapper around the Apache Jakarta
+ * <a href="http://jakarta.apache.org/commons/fileupload/">FileUpload</a>.
+ *
+ * <p>Supports single valued parameters, multi-valued parameters and individual
+ * file uploads. That is, for file uploads, each upload must be a unique parameter
+ * (that is all the {@link org.apache.tapestry.form.Upload} component needs).
+
+ *
+ * @author Joe Panico
+ * @version $Id$
+ * @since 2.0.1
+ *
+ **/
+public class DefaultMultipartDecoder implements IMultipartDecoder
+{
+ /**
+ * Request attribute key used to store the part map for this request.
+ * The part map is created in {@link #decode(HttpServletRequest)}. By storing
+ * the part map in the request instead of an instance variable, DefaultMultipartDecoder
+ * becomes threadsafe (no client-specific state in instance variables).
+ *
+ **/
+
+ public static final String PART_MAP_ATTRIBUTE_NAME = "org.apache.tapestry.multipart.part-map";
+
+ private int _maxSize = 10000000;
+ private int _thresholdSize = 1024;
+ private String _repositoryPath = System.getProperty("java.io.tmpdir");
+
+ private static DefaultMultipartDecoder _shared;
+
+ public static DefaultMultipartDecoder getSharedInstance()
+ {
+ if (_shared == null)
+ _shared = new DefaultMultipartDecoder();
+
+ return _shared;
+ }
+
+ public void setMaxSize(int maxSize)
+ {
+ _maxSize = maxSize;
+ }
+
+ public int getMaxSize()
+ {
+ return _maxSize;
+ }
+
+ public void setThresholdSize(int thresholdSize)
+ {
+ _thresholdSize = thresholdSize;
+ }
+
+ public int getThresholdSize()
+ {
+ return _thresholdSize;
+ }
+
+ public void setRepositoryPath(String repositoryPath)
+ {
+ _repositoryPath = repositoryPath;
+ }
+
+ public String getRepositoryPath()
+ {
+ return _repositoryPath;
+ }
+
+ public static boolean isMultipartRequest(HttpServletRequest request)
+ {
+ return FileUpload.isMultipartContent(request);
+ }
+
+ /**
+ * Invokes {@link IPart#cleanup()} on each part.
+ *
+ **/
+ public void cleanup(HttpServletRequest request)
+ {
+ Map partMap = getPartMap(request);
+
+ Iterator i = partMap.values().iterator();
+ while (i.hasNext())
+ {
+ IPart part = (IPart) i.next();
+ part.cleanup();
+ }
+ }
+
+ /**
+ * Decodes the request, storing the part map (keyed on query parameter name,
+ * value is {@link IPart} into the request as an attribute.
+ *
+ * @throws ApplicationRuntimeException if decode fails, for instance the
+ * request exceeds getMaxSize()
+ *
+ **/
+
+ public void decode(HttpServletRequest request)
+ {
+ Map partMap = new HashMap();
+
+ request.setAttribute(PART_MAP_ATTRIBUTE_NAME, partMap);
+
+ // The encoding that will be used to decode the string parameters
+ // It should NOT be null at this point, but it may be
+ // if the older Servlet API 2.2 is used
+ String encoding = request.getCharacterEncoding();
+
+ // DiskFileUpload is not quite threadsafe, so we create a new instance
+ // for each request.
+
+ DiskFileUpload upload = new DiskFileUpload();
+
+ List parts = null;
+
+ try
+ {
+ if (encoding != null)
+ upload.setHeaderEncoding(encoding);
+ parts = upload.parseRequest(request, _thresholdSize, _maxSize, _repositoryPath);
+ }
+ catch (FileUploadException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("DefaultMultipartDecoder.unable-to-decode", ex.getMessage()),
+ ex);
+ }
+
+ int count = Tapestry.size(parts);
+
+ for (int i = 0; i < count; i++)
+ {
+ FileItem uploadItem = (FileItem) parts.get(i);
+
+ if (uploadItem.isFormField())
+ {
+ try
+ {
+ String name = uploadItem.getFieldName();
+ String value;
+ if (encoding == null)
+ value = uploadItem.getString();
+ else
+ value = uploadItem.getString(encoding);
+
+ ValuePart valuePart = (ValuePart) partMap.get(name);
+ if (valuePart != null)
+ {
+ valuePart.add(value);
+ }
+ else
+ {
+ valuePart = new ValuePart(value);
+ partMap.put(name, valuePart);
+ }
+ }
+ catch (UnsupportedEncodingException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("illegal-encoding", encoding),
+ ex);
+ }
+ }
+ else
+ {
+ UploadPart uploadPart = new UploadPart(uploadItem);
+
+ partMap.put(uploadItem.getFieldName(), uploadPart);
+ }
+ }
+
+ }
+
+ public String getString(HttpServletRequest request, String name)
+ {
+ Map partMap = getPartMap(request);
+
+ ValuePart part = (ValuePart) partMap.get(name);
+ if (part != null)
+ return part.getValue();
+
+ return null;
+ }
+
+ public String[] getStrings(HttpServletRequest request, String name)
+ {
+ Map partMap = getPartMap(request);
+
+ ValuePart part = (ValuePart) partMap.get(name);
+ if (part != null)
+ return part.getValues();
+
+ return null;
+ }
+
+ public IUploadFile getUploadFile(HttpServletRequest request, String name)
+ {
+ Map partMap = getPartMap(request);
+
+ return (IUploadFile) partMap.get(name);
+ }
+
+ private Map getPartMap(HttpServletRequest request)
+ {
+ return (Map) request.getAttribute(PART_MAP_ATTRIBUTE_NAME);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/IMultipartDecoder.java b/tapestry-framework/src/org/apache/tapestry/multipart/IMultipartDecoder.java
new file mode 100644
index 0000000..b9ebbd5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/IMultipartDecoder.java
@@ -0,0 +1,78 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.multipart;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.tapestry.request.IUploadFile;
+
+/**
+ * Defines how a multipart HTTP request can be broken into individual
+ * elements (including file uploads).
+ *
+ * <p>Multipart decoder implementations must be threadsafe.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ **/
+
+public interface IMultipartDecoder
+{
+ /**
+ * Decodes the incoming request, identifying all
+ * the parts (values and uploaded files) contained
+ * within.
+ *
+ **/
+
+ public void decode(HttpServletRequest request);
+
+ /**
+ * Invoked to release any resources needed by tghe
+ * decoder. In some cases, large incoming parts
+ * are written to temporary files; this method
+ * ensures those temporary files are deleted.
+ *
+ **/
+
+ public void cleanup(HttpServletRequest request);
+
+ /**
+ * Returns the single value (or first value) for the parameter
+ * with the specified name. Returns null if no such parameter
+ * was in the request.
+ *
+ **/
+
+ public String getString(HttpServletRequest request, String name);
+
+ /**
+ * Returns an array of values (possibly a single element array).
+ * Returns null if no such parameter was in the request.
+ *
+ **/
+
+ public String[] getStrings(HttpServletRequest request, String name);
+
+ /**
+ * Returns the uploaded file with the specified parameter name,
+ * or null if no such parameter was in the request.
+ *
+ **/
+
+ public IUploadFile getUploadFile(HttpServletRequest request, String name);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/IPart.java b/tapestry-framework/src/org/apache/tapestry/multipart/IPart.java
new file mode 100644
index 0000000..12c0bc1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/IPart.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.multipart;
+
+/**
+ * Common interface for data parts from multipart form submissions.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.1
+ *
+ **/
+
+public interface IPart
+{
+ /**
+ * Invoked at the end of a request cycle to delete any resources held by
+ * the part.
+ *
+ * @see UploadPart#cleanup()
+ *
+ **/
+
+ public void cleanup();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/UploadPart.java b/tapestry-framework/src/org/apache/tapestry/multipart/UploadPart.java
new file mode 100644
index 0000000..d7089e8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/UploadPart.java
@@ -0,0 +1,139 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.multipart;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.IUploadFile;
+
+/**
+ * Portion of a multi-part request representing an uploaded file.
+ *
+ * @author Joe Panico
+ * @version $Id$
+ * @since 2.0.1
+ *
+ **/
+public class UploadPart extends Object implements IUploadFile, IPart
+{
+ private static final Log LOG = LogFactory.getLog(UploadPart.class);
+
+ private FileItem _fileItem;
+
+ public UploadPart(FileItem fileItem)
+ {
+ if (fileItem == null)
+ throw new IllegalArgumentException(
+ Tapestry.format("invalid-null-parameter", "fileItem"));
+
+ _fileItem = fileItem;
+ }
+
+ public String getContentType()
+ {
+ return _fileItem.getContentType();
+ }
+
+ /**
+ * Leverages {@link File} to convert the full file path and extract
+ * the name.
+ *
+ **/
+ public String getFileName()
+ {
+ File file = new File(this.getFilePath());
+
+ return file.getName();
+ }
+
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ public String getFilePath()
+ {
+ return _fileItem.getName();
+ }
+
+ public InputStream getStream()
+ {
+ try
+ {
+ return _fileItem.getInputStream();
+ }
+ catch (IOException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("UploadPart.unable-to-open-content-file", _fileItem.getName()),
+ ex);
+ }
+ }
+
+ /**
+ * Deletes the external content file, if one exists.
+ *
+ **/
+
+ public void cleanup()
+ {
+ _fileItem.delete();
+ }
+
+ /**
+ * Writes the uploaded content to a file. This should be invoked at most once
+ * (perhaps we should add a check for this). This will often
+ * be a simple file rename.
+ *
+ * @since 3.0
+ */
+ public void write(File file)
+ {
+ try
+ {
+ _fileItem.write(file);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("UploadPart.write-failure", file, ex.getMessage()),
+ ex);
+ }
+ }
+
+ /**
+ * @since 3.0
+ */
+ public long getSize()
+ {
+ return _fileItem.getSize();
+ }
+
+ /**
+ * @since 3.0
+ */
+ public boolean isInMemory()
+ {
+ return _fileItem.isInMemory();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/ValuePart.java b/tapestry-framework/src/org/apache/tapestry/multipart/ValuePart.java
new file mode 100644
index 0000000..efe27e1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/ValuePart.java
@@ -0,0 +1,104 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.multipart;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A portion of a multipart request that stores a value, or values, for
+ * a parameter.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.1
+ *
+ **/
+
+public class ValuePart implements IPart
+{
+ private int _count;
+ // Stores either String or List of String
+ private Object _value;
+
+ public ValuePart(String value)
+ {
+ _count = 1;
+ _value = value;
+ }
+
+ public int getCount()
+ {
+ return _count;
+ }
+
+ /**
+ * Returns the value, or the first value (if multi-valued).
+ *
+ **/
+
+ public String getValue()
+ {
+ if (_count == 1)
+ return (String) _value;
+
+ List l = (List) _value;
+
+ return (String) l.get(0);
+ }
+
+ /**
+ * Returns the values as an array of strings. If there is only one value,
+ * it is returned wrapped as a single element array.
+ *
+ **/
+
+ public String[] getValues()
+ {
+ if (_count == 1)
+ return new String[] {(String) _value };
+
+ List l = (List) _value;
+
+ return (String[]) l.toArray(new String[_count]);
+ }
+
+ public void add(String newValue)
+ {
+ if (_count == 1)
+ {
+ List l = new ArrayList();
+ l.add(_value);
+ l.add(newValue);
+
+ _value = l;
+ _count++;
+ return;
+ }
+
+ List l = (List) _value;
+ l.add(newValue);
+ _count++;
+ }
+
+ /**
+ * Does nothing.
+ *
+ **/
+
+ public void cleanup()
+ {
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/multipart/org.apache.tapestry.multipart.patch b/tapestry-framework/src/org/apache/tapestry/multipart/org.apache.tapestry.multipart.patch
new file mode 100644
index 0000000..47ceda0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/multipart/org.apache.tapestry.multipart.patch
@@ -0,0 +1,1156 @@
+Index: DefaultMultipartDecoder.java
+===================================================================
+RCS file: /home/cvspublic/jakarta-tapestry/framework/src/org/apache/tapestry/multipart/DefaultMultipartDecoder.java,v
+retrieving revision 1.1
+diff -c -r1.1 DefaultMultipartDecoder.java
+*** DefaultMultipartDecoder.java 5 Mar 2003 22:59:49 -0000 1.1
+--- DefaultMultipartDecoder.java 21 Mar 2003 22:51:11 -0000
+***************
+*** 1,75 ****
+! /* ====================================================================
+! * The Apache Software License, Version 1.1
+! *
+! * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
+! * reserved.
+! *
+! * Redistribution and use in source and binary forms, with or without
+! * modification, are permitted provided that the following conditions
+! * are met:
+! *
+! * 1. Redistributions of source code must retain the above copyright
+! * notice, this list of conditions and the following disclaimer.
+! *
+! * 2. Redistributions in binary form must reproduce the above copyright
+! * notice, this list of conditions and the following disclaimer in
+! * the documentation and/or other materials provided with the
+! * distribution.
+! *
+! * 3. The end-user documentation included with the redistribution,
+! * if any, must include the following acknowledgment:
+! * "This product includes software developed by the
+! * Apache Software Foundation (http://apache.org/)."
+! * Alternately, this acknowledgment may appear in the software itself,
+! * if and wherever such third-party acknowledgments normally appear.
+! *
+! * 4. The names "Apache" and "Apache Software Foundation", "Tapestry"
+! * must not be used to endorse or promote products derived from this
+! * software without prior written permission. For written
+! * permission, please contact apache@apache.org.
+! *
+! * 5. Products derived from this software may not be called "Apache"
+! * or "Tapestry", nor may "Apache" or "Tapestry" appear in their
+! * name, without prior written permission of the Apache Software Foundation.
+! *
+! * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+! * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+! * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+! * DISCLAIMED. IN NO EVENT SHALL THE TAPESTRY CONTRIBUTOR COMMUNITY
+! * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+! * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+! * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+! * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+! * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+! * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+! * SUCH DAMAGE.
+! * ====================================================================
+! *
+! * This software consists of voluntary contributions made by many
+! * individuals on behalf of the Apache Software Foundation. For more
+! * information on the Apache Software Foundation, please see
+! * <http://www.apache.org/>.
+! *
+ */
+-
+ package org.apache.tapestry.multipart;
+
+- import java.io.ByteArrayOutputStream;
+- import java.io.File;
+- import java.io.IOException;
+- import java.io.InputStream;
+- import java.io.OutputStream;
+ import java.util.HashMap;
+ import java.util.Iterator;
+ import java.util.Map;
+-
+ import javax.servlet.http.HttpServletRequest;
+
+ import org.apache.tapestry.ApplicationRuntimeException;
+- import org.apache.tapestry.Tapestry;
+ import org.apache.tapestry.request.IUploadFile;
+! import org.apache.tapestry.util.StringSplitter;
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+
+--- 1,72 ----
+! /*
+! * ====================================================================
+! * The Apache Software License, Version 1.1
+! *
+! * Copyright (c) 2002 The Apache Software Foundation. All rights
+! * reserved.
+! *
+! * Redistribution and use in source and binary forms, with or without
+! * modification, are permitted provided that the following conditions
+! * are met:
+! *
+! * 1. Redistributions of source code must retain the above copyright
+! * notice, this list of conditions and the following disclaimer.
+! *
+! * 2. Redistributions in binary form must reproduce the above copyright
+! * notice, this list of conditions and the following disclaimer in
+! * the documentation and/or other materials provided with the
+! * distribution.
+! *
+! * 3. The end-user documentation included with the redistribution,
+! * if any, must include the following acknowledgment:
+! * "This product includes software developed by the
+! * Apache Software Foundation (http://www.apache.org/)."
+! * Alternately, this acknowledgment may appear in the software itself,
+! * if and wherever such third-party acknowledgments normally appear.
+! *
+! * 4. The names "Apache" and "Apache Software Foundation" and
+! * "Apache Tapestry" must not be used to endorse or promote products
+! * derived from this software without prior written permission. For
+! * written permission, please contact apache@apache.org.
+! *
+! * 5. Products derived from this software may not be called "Apache",
+! * "Apache Tapestry", nor may "Apache" appear in their name, without
+! * prior written permission of the Apache Software Foundation.
+! *
+! * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+! * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+! * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+! * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+! * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+! * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+! * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+! * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+! * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+! * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+! * SUCH DAMAGE.
+! * ====================================================================
+! *
+! * This software consists of voluntary contributions made by many
+! * individuals on behalf of the Apache Software Foundation. For more
+! * information on the Apache Software Foundation, please see
+! * <http://www.apache.org/>.
+ */
+ package org.apache.tapestry.multipart;
+
+ import java.util.HashMap;
+ import java.util.Iterator;
++ import java.util.List;
+ import java.util.Map;
+ import javax.servlet.http.HttpServletRequest;
+
+ import org.apache.tapestry.ApplicationRuntimeException;
+ import org.apache.tapestry.request.IUploadFile;
+!
+! import org.apache.commons.fileupload.FileItem;
+! import org.apache.commons.fileupload.FileUpload;
+! import org.apache.commons.fileupload.FileUploadException;
+!
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+
+***************
+*** 78,475 ****
+ * file uploads and multi-valued parameters. After decoding, the class is used
+ * to access the parameter values.
+ *
+! * <p>This implementation is partially based on the MultipartRequest from
+! * <a href="http://sf.net/projects/jetty">Jetty</a> (which is LGPL), and
+! * partly from research on the web, including a discussion of the
+! * <a href="http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc1867.html">RFC</a>.
+ *
+ * <p>Supports single valued parameters, multi-valued parameters and individual
+ * file uploads. That is, for file uploads, each upload must be a unique parameter
+! * (that is all the {@link org.apache.tapestry.form.Upload} component needs).
+
+ *
+! * @author Howard Lewis Ship
+! * @version $Id: DefaultMultipartDecoder.java,v 1.1 2003/03/05 22:59:49 hlship Exp $
+ * @since 2.0.1
+ *
+ **/
+!
+! public class DefaultMultipartDecoder implements IMultipartDecoder
+ {
+! private static final Log LOG = LogFactory.getLog(DefaultMultipartDecoder.class);
+!
+! public static final String MULTIPART_FORM_DATA_CONTENT_TYPE = "multipart/form-data";
+!
+! private static final String QUOTE = "\"";
+!
+! private Map partMap = new HashMap();
+!
+! public static boolean isMultipartRequest(HttpServletRequest request)
+! {
+! String contentType = request.getContentType();
+!
+! if (contentType == null)
+! return false;
+!
+! return contentType.startsWith(MULTIPART_FORM_DATA_CONTENT_TYPE);
+! }
+!
+! private void close(InputStream stream)
+! {
+! try
+! {
+! if (stream != null)
+! stream.close();
+! }
+! catch (IOException ex)
+! {
+! // Ignore.
+! }
+! }
+!
+! private static final String BOUNDARY = "boundary=";
+!
+! public void decode(HttpServletRequest request)
+! {
+! if (!isMultipartRequest(request))
+! throw new ApplicationRuntimeException(
+! Tapestry.getString(
+! "MultipartDecoder.wrong-content-type",
+! request.getContentType()));
+!
+! String contentType = request.getContentType();
+! int pos = contentType.indexOf(BOUNDARY);
+!
+! String boundaryString = "--" + contentType.substring(pos + BOUNDARY.length());
+! byte[] boundary = (boundaryString + "--").getBytes();
+!
+! LineInput input = null;
+!
+! try
+! {
+! input = new LineInput(request.getInputStream());
+!
+! checkForInitialBoundary(input, boundaryString);
+!
+! boolean last = false;
+!
+! while (!last)
+! {
+! last = readNextPart(input, boundary);
+! }
+! }
+! catch (IOException ex)
+! {
+! LOG.error(
+! Tapestry.getString("MultipartDecoder.io-exception-reading-input", ex.getMessage()),
+! ex);
+!
+! // Cleanup any partial upload files.
+!
+! cleanup();
+!
+! throw new ApplicationRuntimeException(ex);
+! }
+! finally
+! {
+! // close(input);
+! }
+!
+! }
+!
+! private void checkForInitialBoundary(LineInput input, String boundary) throws IOException
+! {
+! String line = input.readLine();
+!
+! if (line != null && line.equals(boundary))
+! return;
+!
+! throw new ApplicationRuntimeException(
+! Tapestry.getString("MultipartDecoder.missing-initial-boundary"));
+! }
+!
+! private boolean readNextPart(LineInput input, byte[] boundary) throws IOException
+! {
+! String disposition = null;
+! String contentType = null;
+!
+! // First read the various headers (before the content)
+!
+! while (true)
+! {
+! String line = input.readLine();
+!
+! if (line == null || line.length() == 0)
+! break;
+!
+! int colonx = line.indexOf(':');
+!
+! if (colonx > 0)
+! {
+! String key = line.substring(0, colonx).toLowerCase();
+!
+! if (key.equals("content-disposition"))
+! {
+! disposition = line.substring(colonx + 1).trim();
+! continue;
+! }
+!
+! if (key.equals("content-type"))
+! {
+! contentType = line.substring(colonx + 1).trim();
+! continue;
+! }
+! }
+!
+! }
+!
+! if (disposition == null)
+! throw new ApplicationRuntimeException(
+! Tapestry.getString("MultipartDecoder.missing-content-disposition"));
+!
+! Map dispositionMap = explodeDisposition(disposition);
+! String name = (String) dispositionMap.get("name");
+!
+! if (Tapestry.isNull(name))
+! throw new ApplicationRuntimeException(
+! Tapestry.getString("MultipartDecoder.invalid-content-disposition", disposition));
+!
+! if (!dispositionMap.containsKey("filename"))
+! return readValuePart(input, boundary, name);
+!
+! String fileName = (String) dispositionMap.get("filename");
+!
+! return readFilePart(input, boundary, name, fileName, contentType);
+! }
+!
+! private static StringSplitter splitter = new StringSplitter(';');
+!
+! private Map explodeDisposition(String disposition)
+! {
+! Map result = new HashMap();
+!
+! String[] elements = splitter.splitToArray(disposition);
+!
+! for (int i = 0; i < elements.length; i++)
+! {
+! String element = elements[i];
+! int x = element.indexOf('=');
+!
+! if (x < 0)
+! continue;
+!
+! String key = element.substring(0, x).trim();
+! String rawValue = element.substring(x + 1);
+!
+! if (!(rawValue.startsWith(QUOTE) && rawValue.endsWith(QUOTE)))
+! throw new ApplicationRuntimeException(
+! Tapestry.getString(
+! "MultipartDecoder.invalid-content-disposition",
+! disposition));
+!
+! result.put(key, rawValue.substring(1, rawValue.length() - 1));
+!
+! }
+!
+! return result;
+! }
+!
+! private boolean readFilePart(
+! LineInput input,
+! byte[] boundary,
+! String name,
+! String fileName,
+! String contentType)
+! throws IOException
+! {
+! UploadOutputStream uploadStream = new UploadOutputStream();
+!
+! boolean last = readIntoStream(input, boundary, uploadStream);
+!
+! uploadStream.close();
+!
+! File file = uploadStream.getContentFile();
+!
+! UploadPart p;
+!
+! if (LOG.isDebugEnabled())
+! LOG.debug("Read file part '" + name + "'.");
+!
+! if (file != null)
+! p = new UploadPart(fileName, contentType, file);
+! else
+! p = new UploadPart(fileName, contentType, uploadStream.getContent());
+!
+! partMap.put(name, p);
+!
+! return last;
+! }
+!
+! private boolean readValuePart(LineInput input, byte[] boundary, String name) throws IOException
+! {
+! ByteArrayOutputStream baos = new ByteArrayOutputStream();
+!
+! boolean last = readIntoStream(input, boundary, baos);
+!
+! baos.close();
+!
+! String value = baos.toString();
+!
+! if (LOG.isDebugEnabled())
+! LOG.debug("Read value part '" + name + "' with value: " + value);
+!
+! ValuePart p = (ValuePart) partMap.get(name);
+!
+! if (p == null)
+! {
+! p = new ValuePart(value);
+! partMap.put(name, p);
+! }
+! else
+! p.add(value);
+!
+! return last;
+! }
+!
+! private static final int CR = 13;
+! private static final int LF = 10;
+! private static final int SPECIAL = -2;
+!
+! /**
+! * Copies the input stream into the output stream, stopping once the boundary is seen
+! * (the boundary is not copied). Returns true when the input stream is exhausted,
+! * false otherwise.
+! *
+! * This is an ugly cut and past of ugly code from Jetty. This really needs to be fixed!
+! *
+! **/
+!
+! private boolean readIntoStream(LineInput input, byte[] boundary, OutputStream stream)
+! throws IOException
+! {
+! boolean result = false;
+! int c = 0;
+! boolean cr = false;
+! boolean lf = false;
+! int _char = SPECIAL;
+! boolean more = true;
+!
+! while (true)
+! {
+! int b = 0;
+!
+! while (more)
+! {
+! c = (_char != SPECIAL) ? _char : input.read();
+!
+! if (c == -1)
+! {
+! more = false;
+! continue;
+! }
+!
+! _char = SPECIAL;
+!
+! // look for CR and/or LF
+! if (c == CR || c == LF)
+! {
+! if (c == CR)
+! _char = input.read();
+! break;
+! }
+!
+! // look for boundary
+! if (b >= 0 && b < boundary.length && c == boundary[b])
+! b++;
+! else
+! {
+! // this is not a boundary
+! if (cr)
+! stream.write(CR);
+! if (lf)
+! stream.write(LF);
+! cr = lf = false;
+!
+! if (b > 0)
+! stream.write(boundary, 0, b);
+! b = -1;
+!
+! stream.write(c);
+! }
+! }
+!
+! // check partial boundary
+! if ((b > 0 && b < boundary.length - 2) || (b == boundary.length - 1))
+! {
+! stream.write(boundary, 0, b);
+! b = -1;
+! }
+!
+! // boundary match
+! if (b > 0 || c == -1)
+! {
+! if (b == boundary.length)
+! result = true;
+!
+! if (_char == LF)
+! _char = SPECIAL;
+!
+! break;
+! }
+!
+! // handle CR LF
+! if (cr)
+! stream.write(CR);
+! if (lf)
+! stream.write(LF);
+!
+! cr = (c == CR);
+! lf = (c == LF || _char == LF);
+!
+! if (_char == LF)
+! _char = SPECIAL;
+! }
+!
+! return result;
+! }
+
+ /**
+ * Invokes {@link IPart#cleanup()} on each part.
+ *
+ **/
+-
+ public void cleanup()
+ {
+! Iterator i = partMap.values().iterator();
+ while (i.hasNext())
+ {
+ IPart part = (IPart) i.next();
+ part.cleanup();
+ }
+ }
+
+! public String getString(String name)
+! {
+! ValuePart p = (ValuePart) partMap.get(name);
+!
+! if (p == null)
+! return null;
+!
+! return p.getValue();
+! }
+!
+! public String[] getStrings(String name)
+! {
+! ValuePart p = (ValuePart) partMap.get(name);
+!
+! if (p == null)
+! return null;
+!
+! return p.getValues();
+! }
+!
+! public IUploadFile getUploadFile(String name)
+! {
+! return (UploadPart) partMap.get(name);
+! }
+! }
+\ No newline at end of file
+--- 75,266 ----
+ * file uploads and multi-valued parameters. After decoding, the class is used
+ * to access the parameter values.
+ *
+! * <p>This implementation is a thin wrapper around the Apache Jakarta
+! * <a href="http://jakarta.apache.org/commons/fileupload/">FileUpload</a>.
+ *
+ * <p>Supports single valued parameters, multi-valued parameters and individual
+ * file uploads. That is, for file uploads, each upload must be a unique parameter
+! * (that is all the {@link net.sf.tapestry.form.Upload} component needs).
+
+ *
+! * @author Joe Panico
+! * @version $Id: DefaultMultipartDecoder.java,v 1.1 2003/01/16 00:53:56 hlship Exp $
+ * @since 2.0.1
+ *
+ **/
+! public class DefaultMultipartDecoder
+! extends Object
+! implements IMultipartDecoder
+ {
+! public static final int DEFAULT_MAX_SIZE = 10000000;
+! public static final int DEFAULT_THRESHOLD_SIZE = 1024;
+! public static final String DEFAULT_REPOSITORY_PATH = System.getProperty("java.io.tmpdir");
+!
+! private static final Log LOG =
+! LogFactory.getLog(DefaultMultipartDecoder.class);
+!
+! private static int _maxSize = DEFAULT_MAX_SIZE;
+! private static int _thresholdSize = DEFAULT_THRESHOLD_SIZE;
+! private static String _repositoryPath = DEFAULT_REPOSITORY_PATH;
+!
+! private Map _partMap = new HashMap();
+!
+!
+! public static void setMaxSize(int maxSize)
+! {
+! _maxSize = maxSize;
+! }
+!
+! public static int getMaxSize()
+! {
+! return _maxSize;
+! }
+!
+! public static void setThresholdSize(int thresholdSize)
+! {
+! _thresholdSize = thresholdSize;
+! }
+!
+! public static int getThresholdSize()
+! {
+! return _thresholdSize;
+! }
+!
+! public static void setRepositoryPath(String repositoryPath)
+! {
+! _repositoryPath = repositoryPath;
+! }
+!
+! public static String getRepositoryPath()
+! {
+! return _repositoryPath;
+! }
+!
+! public static boolean isMultipartRequest(HttpServletRequest request)
+! {
+! return FileUpload.isMultipartContent(request);
+! }
+
+ /**
+ * Invokes {@link IPart#cleanup()} on each part.
+ *
+ **/
+ public void cleanup()
+ {
+! Iterator i = _partMap.values().iterator();
+ while (i.hasNext())
+ {
+ IPart part = (IPart) i.next();
+ part.cleanup();
+ }
+ }
++
++ /**
++ * @see net.sf.tapestry.multipart.IMultipartDecoder#decode(HttpServletRequest)
++ *
++ * @throws ApplicationRuntimeException if decode fails, for instance the
++ * request exceeds getMaxSize()
++ */
++ public void decode(HttpServletRequest request)
++ {
++ if (request != null)
++ {
++ try
++ {
++ FileUpload aFileUpload = new FileUpload();
++
++ aFileUpload.setSizeMax(_maxSize);
++ aFileUpload.setSizeThreshold(_thresholdSize);
++ aFileUpload.setRepositoryPath(_repositoryPath);
++
++ List someParts = aFileUpload.parseRequest(request);
++
++ if ((someParts != null) && (someParts.size() > 0))
++ {
++ Iterator aPartIterator = someParts.iterator();
++ while (aPartIterator.hasNext())
++ {
++ FileItem aPart = (FileItem) aPartIterator.next();
++ if (aPart.isFormField())
++ {
++ String aPartName = aPart.getFieldName();
++ ValuePart aValuePart = (ValuePart) _partMap.get(aPartName);
++ if (aValuePart != null)
++ {
++ aValuePart.add(aPart.getString());
++ }
++ else
++ {
++ aValuePart = new ValuePart(aPart.getString());
++ _partMap.put(aPartName, aValuePart);
++ }
++ }
++ else
++ {
++ UploadPart aFile = new UploadPart(aPart);
++
++ _partMap.put(aPart.getFieldName(), aFile);
++ }
++ }
++ }
++ }
++ catch (FileUploadException e)
++ {
++ LOG.warn("decode", e);
++ throw new ApplicationRuntimeException(e);
++ }
++ }
++
++ }
++
++ /**
++ * @see net.sf.tapestry.multipart.IMultipartDecoder#getString(String)
++ */
++ public String getString(String name)
++ {
++ String getString = null;
++
++ if (name != null)
++ {
++ ValuePart aPart = (ValuePart) _partMap.get(name);
++ if (aPart != null)
++ {
++ getString = aPart.getValue();
++ }
++ }
++ return getString;
++ }
++
++ /**
++ * @see net.sf.tapestry.multipart.IMultipartDecoder#getStrings(String)
++ */
++ public String[] getStrings(String name)
++ {
++ String[] getStrings = null;
++
++ if (name != null)
++ {
++ ValuePart aPart = (ValuePart) _partMap.get(name);
++ if (aPart != null)
++ {
++ getStrings = aPart.getValues();
++ }
++ }
++ return getStrings;
++ }
++
++ /**
++ * @see net.sf.tapestry.multipart.IMultipartDecoder#getUploadFile(String)
++ */
++ public IUploadFile getUploadFile(String name)
++ {
++ IUploadFile getUploadFile = null;
++
++ if (name != null)
++ {
++ getUploadFile = (IUploadFile) _partMap.get(name);
++ }
++ return getUploadFile;
++ }
+
+! }
+Index: UploadPart.java
+===================================================================
+RCS file: /home/cvspublic/jakarta-tapestry/framework/src/org/apache/tapestry/multipart/UploadPart.java,v
+retrieving revision 1.1
+diff -c -r1.1 UploadPart.java
+*** UploadPart.java 5 Mar 2003 22:59:49 -0000 1.1
+--- UploadPart.java 21 Mar 2003 22:51:11 -0000
+***************
+*** 1,193 ****
+! /* ====================================================================
+! * The Apache Software License, Version 1.1
+! *
+! * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
+! * reserved.
+! *
+! * Redistribution and use in source and binary forms, with or without
+! * modification, are permitted provided that the following conditions
+! * are met:
+! *
+! * 1. Redistributions of source code must retain the above copyright
+! * notice, this list of conditions and the following disclaimer.
+! *
+! * 2. Redistributions in binary form must reproduce the above copyright
+! * notice, this list of conditions and the following disclaimer in
+! * the documentation and/or other materials provided with the
+! * distribution.
+! *
+! * 3. The end-user documentation included with the redistribution,
+! * if any, must include the following acknowledgment:
+! * "This product includes software developed by the
+! * Apache Software Foundation (http://apache.org/)."
+! * Alternately, this acknowledgment may appear in the software itself,
+! * if and wherever such third-party acknowledgments normally appear.
+! *
+! * 4. The names "Apache" and "Apache Software Foundation", "Tapestry"
+! * must not be used to endorse or promote products derived from this
+! * software without prior written permission. For written
+! * permission, please contact apache@apache.org.
+! *
+! * 5. Products derived from this software may not be called "Apache"
+! * or "Tapestry", nor may "Apache" or "Tapestry" appear in their
+! * name, without prior written permission of the Apache Software Foundation.
+! *
+! * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+! * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+! * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+! * DISCLAIMED. IN NO EVENT SHALL THE TAPESTRY CONTRIBUTOR COMMUNITY
+! * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+! * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+! * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+! * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+! * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+! * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+! * SUCH DAMAGE.
+! * ====================================================================
+! *
+! * This software consists of voluntary contributions made by many
+! * individuals on behalf of the Apache Software Foundation. For more
+! * information on the Apache Software Foundation, please see
+! * <http://www.apache.org/>.
+! *
+ */
+-
+ package org.apache.tapestry.multipart;
+
+- import java.io.ByteArrayInputStream;
+ import java.io.File;
+- import java.io.FileInputStream;
+ import java.io.IOException;
+ import java.io.InputStream;
+
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+
+- import org.apache.tapestry.ApplicationRuntimeException;
+- import org.apache.tapestry.Tapestry;
+- import org.apache.tapestry.request.IUploadFile;
+
+ /**
+ * Portion of a multi-part request representing an uploaded file.
+ *
+! * @author Howard Lewis Ship
+! * @version $Id: UploadPart.java,v 1.1 2003/03/05 22:59:49 hlship Exp $
+ * @since 2.0.1
+ *
+ **/
+!
+! public class UploadPart implements IUploadFile, IPart
+ {
+! private static final Log LOG = LogFactory.getLog(UploadPart.class);
+
+! private byte[] _content;
+! private File _contentFile;
+! private String _filePath;
+! private String _contentType;
+!
+! public UploadPart(String filePath, String contentType, byte[] content)
+! {
+! _filePath = filePath;
+! _contentType = contentType;
+! _content = content;
+! }
+!
+! public UploadPart(String filePath, String contentType, File contentFile)
+! {
+! _filePath = filePath;
+! _contentType = contentType;
+! _contentFile = contentFile;
+! }
+
+ /**
+ * @since 2.0.4
+ *
+ **/
+!
+! public String getFilePath()
+! {
+! return _filePath;
+! }
+
+ /**
+! * Always returns false, at least so far. Future enhancements
+! * may involve truncating the input if it exceeds a certain
+! * size or upload time (such things may be used for denial
+! * of service attacks).
+! *
+! **/
+!
+! public boolean isTruncated()
+! {
+! return false;
+! }
+!
+! public InputStream getStream()
+! {
+! if (_content != null)
+! return new ByteArrayInputStream(_content);
+!
+! try
+! {
+! return new FileInputStream(_contentFile);
+! }
+! catch (IOException ex)
+! {
+! throw new ApplicationRuntimeException(
+! Tapestry.getString(
+! "UploadPart.unable-to-open-content-file",
+! _filePath,
+! _contentFile.getAbsolutePath()),
+! ex);
+! }
+! }
+
+! /**
+! * Deletes the external content file, if one exists.
+! *
+! **/
+
+- public void cleanup()
+- {
+- if (_contentFile != null)
+- {
+- if (LOG.isDebugEnabled())
+- LOG.debug("Deleting upload file " + _contentFile + ".");
+-
+- boolean success = _contentFile.delete();
+-
+- if (!success)
+- LOG.warn(
+- Tapestry.getString(
+- "UploadPart.temporary-file-not-deleted",
+- _contentFile.getAbsolutePath()));
+-
+- // In rare cases (when exceptions are thrown while the request
+- // is decoded), cleanup() may be called multiple times.
+-
+- _contentFile = null;
+- }
+-
+- }
+-
+-
+ /**
+! * Leverages {@link File} to convert the full file path and extract
+! * the name.
+ *
+ **/
+!
+! public String getFileName()
+! {
+! File file = new File(_filePath);
+!
+! return file.getName();
+! }
+!
+! public String getContentType()
+! {
+! return _contentType;
+! }
+
+! }
+\ No newline at end of file
+--- 1,201 ----
+! /*
+! * ====================================================================
+! * The Apache Software License, Version 1.1
+! *
+! * Copyright (c) 2002 The Apache Software Foundation. All rights
+! * reserved.
+! *
+! * Redistribution and use in source and binary forms, with or without
+! * modification, are permitted provided that the following conditions
+! * are met:
+! *
+! * 1. Redistributions of source code must retain the above copyright
+! * notice, this list of conditions and the following disclaimer.
+! *
+! * 2. Redistributions in binary form must reproduce the above copyright
+! * notice, this list of conditions and the following disclaimer in
+! * the documentation and/or other materials provided with the
+! * distribution.
+! *
+! * 3. The end-user documentation included with the redistribution,
+! * if any, must include the following acknowledgment:
+! * "This product includes software developed by the
+! * Apache Software Foundation (http://www.apache.org/)."
+! * Alternately, this acknowledgment may appear in the software itself,
+! * if and wherever such third-party acknowledgments normally appear.
+! *
+! * 4. The names "Apache" and "Apache Software Foundation" and
+! * "Apache Tapestry" must not be used to endorse or promote products
+! * derived from this software without prior written permission. For
+! * written permission, please contact apache@apache.org.
+! *
+! * 5. Products derived from this software may not be called "Apache",
+! * "Apache Tapestry", nor may "Apache" appear in their name, without
+! * prior written permission of the Apache Software Foundation.
+! *
+! * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+! * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+! * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+! * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+! * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+! * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+! * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+! * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+! * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+! * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+! * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+! * SUCH DAMAGE.
+! * ====================================================================
+! *
+! * This software consists of voluntary contributions made by many
+! * individuals on behalf of the Apache Software Foundation. For more
+! * information on the Apache Software Foundation, please see
+! * <http://www.apache.org/>.
+ */
+ package org.apache.tapestry.multipart;
+
+ import java.io.File;
+ import java.io.IOException;
+ import java.io.InputStream;
+
++ import org.apache.tapestry.ApplicationRuntimeException;
++ import org.apache.tapestry.request.IUploadFile;
++
++ import org.apache.commons.fileupload.DefaultFileItem;
++ import org.apache.commons.fileupload.FileItem;
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+
+
+ /**
+ * Portion of a multi-part request representing an uploaded file.
+ *
+! * @author Joe Panico
+! * @version $Id: UploadPart.java,v 1.10 2003/01/13 03:33:23 hlship Exp $
+ * @since 2.0.1
+ *
+ **/
+! public class UploadPart extends Object
+! implements IUploadFile, IPart
+ {
+! private static final Log LOG =
+! LogFactory.getLog(UploadPart.class);
+!
+! FileItem _fileItem;
+!
+! public UploadPart(FileItem fileItem)
+! {
+! if( fileItem != null)
+! {
+! _fileItem = fileItem;
+! }
+! else
+! {
+! String aMessage = "UploadPart(FileItem) does not accept null args";
+! throw new IllegalArgumentException( aMessage);
+! }
+! }
+!
+! public static UploadPart newInstance(
+! String path,
+! String name,
+! String contentType,
+! int requestSize,
+! int threshold)
+! {
+! UploadPart newInstance = null;
+! FileItem aFileItem =
+! DefaultFileItem.newInstance(
+! path,
+! name,
+! contentType,
+! requestSize,
+! threshold);
+!
+! if( aFileItem != null)
+! {
+! newInstance = new UploadPart( aFileItem);
+! }
+! return newInstance;
+! }
+!
+! /**
+! * @see net.sf.tapestry.IUploadFile#getContentType()
+! */
+! public String getContentType()
+! {
+! return _fileItem.getContentType();
+! }
+
+! /**
+! * Leverages {@link File} to convert the full file path and extract
+! * the name.
+! *
+! **/
+! public String getFileName()
+! {
+! File aFile = new File( this.getFilePath() );
+!
+! return aFile.getName();
+! }
+
+ /**
+ * @since 2.0.4
+ *
+ **/
+! public String getFilePath()
+! {
+! return _fileItem.getName();
+! }
+
+ /**
+! * @see net.sf.tapestry.IUploadFile#getStream()
+! */
+! public InputStream getStream()
+! {
+! InputStream getStream = null;
+!
+! try
+! {
+! getStream = _fileItem.getInputStream();
+! }
+! catch (IOException e)
+! {
+! LOG.warn("getStream", e);
+!
+! throw new ApplicationRuntimeException(
+! "Unable to open uploaded file",
+! e);
+! }
+!
+! return getStream;
+! }
+
+! /**
+! * The current implementation does not truncate. Parts that are too
+! * large cause a decode failure
+! */
+! public boolean isTruncated()
+! {
+! return false;
+! }
+
+ /**
+! * Deletes the external content file, if one exists.
+ *
+ **/
+! public void cleanup()
+! {
+! _fileItem.delete();
+! // unfortunately FileItem.delete() does not signal success, so
+! // we rely on a little more internal knowledge than is ideal
+! File aStorageLocation = _fileItem.getStoreLocation();
+! if( (aStorageLocation != null) && (aStorageLocation.exists()) )
+! {
+! String aMessage = "Unable to delete FileItem: " + _fileItem;
+!
+! throw new ApplicationRuntimeException(aMessage);
+! }
+! }
+
+! }
diff --git a/tapestry-framework/src/org/apache/tapestry/package.html b/tapestry-framework/src/org/apache/tapestry/package.html
new file mode 100644
index 0000000..2821b51
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/package.html
@@ -0,0 +1,45 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<b>Tapestry</b> is a comprehensive web application framework, written in Java.
+
+<p>Tapestry is not an application server.
+It is designed to be used inside an application server.
+
+<p>Tapestry is not an application.
+Tapestry is a framework for creating web applications.
+
+<p>Tapestry is not a way of using JavaServer Pages.
+Tapestry is an <i>alternative to</i> using JavaServer Pages.
+
+<p>Tapestry is not a scripting environment.
+Tapestry uses a component object model, not simple scripting,
+to create highly dynamic, interactive web pages.
+
+<p>Tapestry is based on the Java Servlet API version 2.2.
+
+<p>Tapestry uses a sophisticated component model to divide a web application into
+a hierarchy of {@link org.apache.tapestry.IComponent components}.
+Each component has specific responsibilities for rendering web pages
+(that is, generating a portion of an HTML page) and responding to HTML queries
+(such as clicking on a link, or submitting a form).
+
+<p>The Tapestry framework takes on virtually all of the responsibilities
+for managing application flow and server-side client state.
+This allows developers to concentrate on the business and presentation aspects of the application.
+
+<hr>
+
+<p>Visit Tapestry's home page at
+<a href="http://jakarta.apache.org/tapestry">http://jakarta.apache.org/tapestry</a>
+for more details on licensing.
+
+@author Howard Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/ComponentTreeWalker.java b/tapestry-framework/src/org/apache/tapestry/pageload/ComponentTreeWalker.java
new file mode 100644
index 0000000..f309497
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/ComponentTreeWalker.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Walks through the tree of components and invokes the visitors on each of
+ * of the components in the tree.
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class ComponentTreeWalker
+{
+ private IComponentVisitor[] _visitors;
+
+ public ComponentTreeWalker(IComponentVisitor[] visitors)
+ {
+ _visitors = visitors;
+ }
+
+ public void walkComponentTree(IComponent component)
+ {
+ // Invoke visitors
+ for (int i = 0; i < _visitors.length; i++)
+ {
+ IComponentVisitor visitor = _visitors[i];
+ visitor.visitComponent(component);
+ }
+
+ // Recurse into the embedded components
+ Collection components = component.getComponents().values();
+
+ if (Tapestry.size(components) == 0)
+ return;
+
+ for (Iterator it = components.iterator(); it.hasNext();)
+ {
+ IComponent embedded = (IComponent) it.next();
+ walkComponentTree(embedded);
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/EstablishDefaultParameterValuesVisitor.java b/tapestry-framework/src/org/apache/tapestry/pageload/EstablishDefaultParameterValuesVisitor.java
new file mode 100644
index 0000000..11a355b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/EstablishDefaultParameterValuesVisitor.java
@@ -0,0 +1,83 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.binding.ExpressionBinding;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IParameterSpecification;
+
+/**
+ * For all parameters in the examined component that have default values, but are not bound,
+ * automatically add an ExpressionBinding with the default value.
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class EstablishDefaultParameterValuesVisitor implements IComponentVisitor
+{
+ private IResourceResolver _resolver;
+
+ public EstablishDefaultParameterValuesVisitor(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ /**
+ * @see org.apache.tapestry.pageload.IComponentVisitor#visitComponent(org.apache.tapestry.IComponent)
+ */
+ public void visitComponent(IComponent component)
+ {
+ IComponentSpecification spec = component.getSpecification();
+
+ Iterator i = spec.getParameterNames().iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+ IParameterSpecification parameterSpec = spec.getParameter(name);
+
+ String defaultValue = parameterSpec.getDefaultValue();
+ if (defaultValue == null)
+ continue;
+
+ // the parameter has a default value, so it must not be required
+ if (parameterSpec.isRequired())
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "EstablishDefaultParameterValuesVisitor.parameter-must-have-no-default-value",
+ component.getExtendedId(),
+ name),
+ component,
+ parameterSpec.getLocation(),
+ null);
+
+ // if there is no binding for this parameter, bind it to the default value
+ if (component.getBinding(name) == null) {
+ IBinding binding = new ExpressionBinding(_resolver, component, defaultValue, parameterSpec.getLocation());
+ component.setBinding(name, binding);
+ }
+
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/IComponentVisitor.java b/tapestry-framework/src/org/apache/tapestry/pageload/IComponentVisitor.java
new file mode 100644
index 0000000..6ca0365
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/IComponentVisitor.java
@@ -0,0 +1,29 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import org.apache.tapestry.IComponent;
+
+/**
+ * An interface defining an entity that is interested in examining a particular component
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public interface IComponentVisitor
+{
+ void visitComponent(IComponent component);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/PageLoader.java b/tapestry-framework/src/org/apache/tapestry/pageload/PageLoader.java
new file mode 100644
index 0000000..d48d4fa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/PageLoader.java
@@ -0,0 +1,964 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BaseComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.asset.ContextAsset;
+import org.apache.tapestry.asset.ExternalAsset;
+import org.apache.tapestry.asset.PrivateAsset;
+import org.apache.tapestry.binding.ExpressionBinding;
+import org.apache.tapestry.binding.FieldBinding;
+import org.apache.tapestry.binding.ListenerBinding;
+import org.apache.tapestry.binding.StaticBinding;
+import org.apache.tapestry.binding.StringBinding;
+import org.apache.tapestry.engine.IComponentClassEnhancer;
+import org.apache.tapestry.engine.IPageLoader;
+import org.apache.tapestry.engine.ISpecificationSource;
+import org.apache.tapestry.engine.ITemplateSource;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.html.BasePage;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.resolver.ComponentSpecificationResolver;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+import org.apache.tapestry.resource.ContextResourceLocation;
+import org.apache.tapestry.spec.AssetType;
+import org.apache.tapestry.spec.BindingType;
+import org.apache.tapestry.spec.IAssetSpecification;
+import org.apache.tapestry.spec.IBindingSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IContainedComponent;
+import org.apache.tapestry.spec.IListenerBindingSpecification;
+import org.apache.tapestry.spec.IPropertySpecification;
+
+/**
+ * Runs the process of building the component hierarchy for an entire page.
+ *
+ * <p>
+ * This class is not threadsafe; however, {@link org.apache.tapestry.pageload.PageSource}
+ * creates a new instance of it for each page to be loaded, which bypasses
+ * multithreading issues.
+ *
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class PageLoader implements IPageLoader
+{
+ private static final Log LOG = LogFactory.getLog(PageLoader.class);
+
+ private IEngine _engine;
+ private IResourceResolver _resolver;
+ private IComponentClassEnhancer _enhancer;
+ private ISpecificationSource _specificationSource;
+ private ComponentSpecificationResolver _componentResolver;
+ private List _inheritedBindingQueue = new ArrayList();
+ private List _propertyInitializers = new ArrayList();
+ private ComponentTreeWalker _establishDefaultParameterValuesWalker;
+ private ComponentTreeWalker _verifyRequiredParametersWalker;
+
+ /**
+ * The locale of the application, which is also the locale
+ * of the page being loaded.
+ *
+ **/
+
+ private Locale _locale;
+
+ /**
+ * Number of components instantiated, excluding the page itself.
+ *
+ **/
+
+ private int _count;
+
+ /**
+ * The recursion depth. A page with no components is zero. A component on
+ * a page is one.
+ *
+ **/
+
+ private int _depth;
+
+ /**
+ * The maximum depth reached while building the page.
+ *
+ **/
+
+ private int _maxDepth;
+
+ /**
+ * Used to figure relative paths for context assets.
+ *
+ **/
+
+ private IResourceLocation _servletLocation;
+
+ private static interface IQueuedInheritedBinding
+ {
+ void connect();
+ }
+
+ private static class QueuedInheritedBinding implements IQueuedInheritedBinding
+ {
+ private IComponent _component;
+ private String _containerParameterName;
+ private String _parameterName;
+
+ private QueuedInheritedBinding(
+ IComponent component,
+ String containerParameterName,
+ String parameterName)
+ {
+ _component = component;
+ _containerParameterName = containerParameterName;
+ _parameterName = parameterName;
+ }
+
+ public void connect()
+ {
+ IBinding binding = _component.getContainer().getBinding(_containerParameterName);
+
+ if (binding == null)
+ return;
+
+ _component.setBinding(_parameterName, binding);
+ }
+ }
+
+ private static class QueuedInheritInformalBindings implements IQueuedInheritedBinding
+ {
+ private IComponent _component;
+
+ private QueuedInheritInformalBindings(IComponent component)
+ {
+ _component = component;
+ }
+
+ public void connect()
+ {
+
+ IComponent container = _component.getContainer();
+
+ for (Iterator it = container.getBindingNames().iterator(); it.hasNext();)
+ {
+ String bindingName = (String) it.next();
+ connectInformalBinding(container, _component, bindingName);
+ }
+ }
+
+ private void connectInformalBinding(
+ IComponent container,
+ IComponent component,
+ String bindingName)
+ {
+ IComponentSpecification componentSpec = component.getSpecification();
+ IComponentSpecification containerSpec = container.getSpecification();
+
+ // check if binding already exists in the component
+ if (component.getBinding(bindingName) != null)
+ return;
+
+ // check if parameter is informal for the component
+ if (componentSpec.getParameter(bindingName) != null
+ || componentSpec.isReservedParameterName(bindingName))
+ return;
+
+ // check if parameter is informal for the container
+ if (containerSpec.getParameter(bindingName) != null
+ || containerSpec.isReservedParameterName(bindingName))
+ return;
+
+ // if everything passes, establish binding
+ IBinding binding = container.getBinding(bindingName);
+ component.setBinding(bindingName, binding);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ **/
+
+ public PageLoader(IRequestCycle cycle)
+ {
+ IEngine engine = cycle.getEngine();
+
+ _specificationSource = engine.getSpecificationSource();
+ _resolver = engine.getResourceResolver();
+ _enhancer = engine.getComponentClassEnhancer();
+ _componentResolver = new ComponentSpecificationResolver(cycle);
+
+ RequestContext context = cycle.getRequestContext();
+
+ // Need the location of the servlet within the context as the basis
+ // for building relative context asset paths.
+
+ HttpServletRequest request = context.getRequest();
+
+ String servletPath = request.getServletPath();
+
+ _servletLocation =
+ new ContextResourceLocation(context.getServlet().getServletContext(), servletPath);
+
+ // Create the mechanisms for walking the component tree when it is complete
+ IComponentVisitor verifyRequiredParametersVisitor = new VerifyRequiredParametersVisitor();
+ _verifyRequiredParametersWalker = new ComponentTreeWalker(new IComponentVisitor[] { verifyRequiredParametersVisitor });
+
+ IComponentVisitor establishDefaultParameterValuesVisitor =
+ new EstablishDefaultParameterValuesVisitor(_resolver);
+ _establishDefaultParameterValuesWalker = new ComponentTreeWalker(new IComponentVisitor[] { establishDefaultParameterValuesVisitor });
+ }
+
+ /**
+ * Binds properties of the component as defined by the container's specification.
+ *
+ * <p>This implementation is very simple, we will need a lot more
+ * sanity checking and eror checking in the final version.
+ *
+ * @param container The containing component. For a dynamic
+ * binding ({@link ExpressionBinding}) the property name
+ * is evaluated with the container as the root.
+ * @param component The contained component being bound.
+ * @param spec The specification of the contained component.
+ * @param contained The contained component specification (from the container's
+ * {@link IComponentSpecification}).
+ *
+ **/
+
+ private void bind(IComponent container, IComponent component, IContainedComponent contained)
+ {
+ IComponentSpecification spec = component.getSpecification();
+ boolean formalOnly = !spec.getAllowInformalParameters();
+
+ IComponentSpecification containerSpec = container.getSpecification();
+ boolean containerFormalOnly = !containerSpec.getAllowInformalParameters();
+
+ if (contained.getInheritInformalParameters())
+ {
+ if (formalOnly)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.inherit-informal-invalid-component-formal-only",
+ component.getExtendedId()),
+ component,
+ contained.getLocation(),
+ null);
+
+ if (containerFormalOnly)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.inherit-informal-invalid-container-formal-only",
+ container.getExtendedId(),
+ component.getExtendedId()),
+ component,
+ contained.getLocation(),
+ null);
+
+ IQueuedInheritedBinding queued = new QueuedInheritInformalBindings(component);
+ _inheritedBindingQueue.add(queued);
+ }
+
+ Iterator i = contained.getBindingNames().iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+
+ boolean isFormal = spec.getParameter(name) != null;
+
+ IBindingSpecification bspec = contained.getBinding(name);
+
+ // If not allowing informal parameters, check that each binding matches
+ // a formal parameter.
+
+ if (formalOnly && !isFormal)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.formal-parameters-only",
+ component.getExtendedId(),
+ name),
+ component,
+ bspec.getLocation(),
+ null);
+
+ // If an informal parameter that conflicts with a reserved name, then
+ // skip it.
+
+ if (!isFormal && spec.isReservedParameterName(name))
+ continue;
+
+ // The type determines how to interpret the value:
+ // As a simple static String
+ // As a nested property name (relative to the component)
+ // As the name of a binding inherited from the containing component.
+ // As the name of a public field
+ // As a script for a listener
+
+ BindingType type = bspec.getType();
+
+ // For inherited bindings, defer until later. This gives components
+ // a chance to setup bindings from static values and expressions in the
+ // template. The order of operations is tricky, template bindings come
+ // later.
+
+ if (type == BindingType.INHERITED)
+ {
+ QueuedInheritedBinding queued =
+ new QueuedInheritedBinding(component, bspec.getValue(), name);
+ _inheritedBindingQueue.add(queued);
+ continue;
+ }
+
+ if (type == BindingType.LISTENER)
+ {
+ constructListenerBinding(component, name, (IListenerBindingSpecification) bspec);
+ continue;
+ }
+
+ IBinding binding = convert(container, bspec);
+
+ if (binding != null)
+ component.setBinding(name, binding);
+ }
+ }
+
+ private IBinding convert(IComponent container, IBindingSpecification spec)
+ {
+ BindingType type = spec.getType();
+ ILocation location = spec.getLocation();
+ String value = spec.getValue();
+
+ // The most common type.
+ // TODO These bindings should be created somehow using the SpecFactory in SpecificationParser
+ if (type == BindingType.DYNAMIC)
+ return new ExpressionBinding(_resolver, container, value, location);
+
+ // String bindings are new in 2.0.4. For the momement,
+ // we don't even try to cache and share them ... they
+ // are most often unique within a page.
+
+ if (type == BindingType.STRING)
+ return new StringBinding(container, value, location);
+
+ // static and field bindings are pooled. This allows the
+ // same instance to be used with many components.
+
+ if (type == BindingType.STATIC)
+ return new StaticBinding(value, location);
+
+ // BindingType.FIELD is on the way out, it is in the
+ // 1.3 DTD but not the 1.4 DTD.
+
+ if (type == BindingType.FIELD)
+ return new FieldBinding(_resolver, value, location);
+
+ // This code is unreachable, at least until a new type
+ // of binding is created.
+
+ throw new ApplicationRuntimeException("Unexpected type: " + type + ".");
+ }
+
+ /**
+ * Construct a {@link ListenerBinding} for the component, and add it.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private void constructListenerBinding(
+ IComponent component,
+ String bindingName,
+ IListenerBindingSpecification spec)
+ {
+ String language = spec.getLanguage();
+
+ // If not provided in the page or component specification, then
+ // search for a default (factory default is "jython").
+
+ if (Tapestry.isBlank(language))
+ language =
+ _engine.getPropertySource().getPropertyValue(
+ "org.apache.tapestry.default-script-language");
+
+ // Construct the binding. The first parameter is the compononent
+ // (not the DirectLink or Form, but the page or component containing the link or form).
+
+ IBinding binding =
+ new ListenerBinding(
+ component.getContainer(),
+ language,
+ spec.getScript(),
+ spec.getLocation());
+
+ component.setBinding(bindingName, binding);
+ }
+
+ /**
+ * Sets up a component. This involves:
+ * <ul>
+ * <li>Instantiating any contained components.
+ * <li>Add the contained components to the container.
+ * <li>Setting up bindings between container and containees.
+ * <li>Construct the containees recursively.
+ * <li>Invoking {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
+ * </ul>
+ *
+ * @param cycle the request cycle for which the page is being (initially) constructed
+ * @param page The page on which the container exists.
+ * @param container The component to be set up.
+ * @param containerSpec The specification for the container.
+ * @param the namespace of the container
+ *
+ **/
+
+ private void constructComponent(
+ IRequestCycle cycle,
+ IPage page,
+ IComponent container,
+ IComponentSpecification containerSpec,
+ INamespace namespace)
+ {
+ _depth++;
+ if (_depth > _maxDepth)
+ _maxDepth = _depth;
+
+ List ids = new ArrayList(containerSpec.getComponentIds());
+ int count = ids.size();
+
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ String id = (String) ids.get(i);
+
+ // Get the sub-component specification from the
+ // container's specification.
+
+ IContainedComponent contained = containerSpec.getComponent(id);
+
+ String type = contained.getType();
+ ILocation location = contained.getLocation();
+
+ _componentResolver.resolve(cycle, namespace, type, location);
+
+ IComponentSpecification componentSpecification =
+ _componentResolver.getSpecification();
+ INamespace componentNamespace = _componentResolver.getNamespace();
+
+ // Instantiate the contained component.
+
+ IComponent component =
+ instantiateComponent(
+ page,
+ container,
+ id,
+ componentSpecification,
+ componentNamespace,
+ location);
+
+ // Add it, by name, to the container.
+
+ container.addComponent(component);
+
+ // Set up any bindings in the IContainedComponent specification
+
+ bind(container, component, contained);
+
+ // Now construct the component recusively; it gets its chance
+ // to create its subcomponents and set their bindings.
+
+ constructComponent(
+ cycle,
+ page,
+ component,
+ componentSpecification,
+ componentNamespace);
+ }
+
+ addAssets(container, containerSpec);
+
+ // Finish the load of the component; most components (which
+ // subclass BaseComponent) load their templates here.
+ // That may cause yet more components to be created, and more
+ // bindings to be set, so we defer some checking until
+ // later.
+
+ container.finishLoad(cycle, this, containerSpec);
+
+ // Finally, we create an initializer for each
+ // specified property.
+
+ createPropertyInitializers(page, container, containerSpec);
+ }
+ catch (ApplicationRuntimeException ex)
+ {
+ throw ex;
+ }
+ catch (RuntimeException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.unable-to-instantiate-component",
+ container.getExtendedId(),
+ ex.getMessage()),
+ container,
+ null,
+ ex);
+ }
+
+ _depth--;
+ }
+
+ /**
+ * Invoked to create an implicit component (one which is defined in the
+ * containing component's template, rather that in the containing component's
+ * specification).
+ *
+ * @see org.apache.tapestry.BaseComponentTemplateLoader
+ * @since 3.0
+ *
+ **/
+
+ public IComponent createImplicitComponent(
+ IRequestCycle cycle,
+ IComponent container,
+ String componentId,
+ String componentType,
+ ILocation location)
+ {
+ IPage page = container.getPage();
+
+ _componentResolver.resolve(cycle, container.getNamespace(), componentType, location);
+
+ INamespace componentNamespace = _componentResolver.getNamespace();
+ IComponentSpecification spec = _componentResolver.getSpecification();
+
+ IComponent result =
+ instantiateComponent(page, container, componentId, spec, componentNamespace, location);
+
+ container.addComponent(result);
+
+ // Recusively build the component.
+
+ constructComponent(cycle, page, result, spec, componentNamespace);
+
+ return result;
+ }
+
+ /**
+ * Instantiates a component from its specification. We instantiate
+ * the component object, then set its specification, page, container and id.
+ *
+ * @see AbstractComponent
+ *
+ **/
+
+ private IComponent instantiateComponent(
+ IPage page,
+ IComponent container,
+ String id,
+ IComponentSpecification spec,
+ INamespace namespace,
+ ILocation location)
+ {
+ IComponent result = null;
+ String className = spec.getComponentClassName();
+
+ if (Tapestry.isBlank(className))
+ className = BaseComponent.class.getName();
+
+ Class componentClass = _enhancer.getEnhancedClass(spec, className);
+ String enhancedClassName = componentClass.getName();
+
+ try
+ {
+ result = (IComponent) componentClass.newInstance();
+
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageLoader.class-not-component", enhancedClassName),
+ container,
+ spec.getLocation(),
+ ex);
+ }
+ catch (Throwable ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageLoader.unable-to-instantiate", enhancedClassName),
+ container,
+ spec.getLocation(),
+ ex);
+ }
+
+ if (result instanceof IPage)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageLoader.page-not-allowed", result.getExtendedId()),
+ result,
+ null,
+ null);
+
+ result.setNamespace(namespace);
+ result.setSpecification(spec);
+ result.setPage(page);
+ result.setContainer(container);
+ result.setId(id);
+ result.setLocation(location);
+
+ _count++;
+
+ return result;
+ }
+
+ /**
+ * Instantitates a page from its specification.
+ *
+ * @param name the unqualified, simple, name for the page
+ * @param namespace the namespace containing the page's specification
+ * @param spec the page's specification
+ *
+ * We instantiate the page object, then set its specification,
+ * names and locale.
+ *
+ * @see IEngine
+ * @see ChangeObserver
+ **/
+
+ private IPage instantiatePage(String name, INamespace namespace, IComponentSpecification spec)
+ {
+ IPage result = null;
+
+ String pageName = namespace.constructQualifiedName(name);
+ String className = spec.getComponentClassName();
+ ILocation location = spec.getLocation();
+
+ if (Tapestry.isBlank(className))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Page "
+ + namespace.constructQualifiedName(name)
+ + " does not specify a component class.");
+
+ className =
+ _engine.getPropertySource().getPropertyValue(
+ "org.apache.tapestry.default-page-class");
+
+ if (className == null)
+ className = BasePage.class.getName();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Defaulting to class " + className);
+ }
+
+ Class pageClass = _enhancer.getEnhancedClass(spec, className);
+ String enhancedClassName = pageClass.getName();
+
+ try
+ {
+ result = (IPage) pageClass.newInstance();
+
+ result.setNamespace(namespace);
+ result.setSpecification(spec);
+ result.setPageName(pageName);
+ result.setPage(result);
+ result.setLocale(_locale);
+ result.setLocation(location);
+ }
+ catch (ClassCastException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageLoader.class-not-page", enhancedClassName),
+ location,
+ ex);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageLoader.unable-to-instantiate", enhancedClassName),
+ location,
+ ex);
+ }
+
+ return result;
+ }
+
+ /**
+ * Invoked by the {@link PageSource} to load a specific page. This
+ * method is not reentrant ... the PageSource ensures that
+ * any given instance of PageLoader is loading only a single page at a time.
+ * The page is immediately attached to the {@link IEngine engine}.
+ *
+ * @param name the simple (unqualified) name of the page to load
+ * @param namespace from which the page is to be loaded (used
+ * when resolving components embedded by the page)
+ * @param cycle the request cycle the page is
+ * initially loaded for (this is used
+ * to define the locale of the new page, and provide access
+ * to the corect specification source, etc.).
+ * @param specification the specification for the page
+ *
+ **/
+
+ public IPage loadPage(
+ String name,
+ INamespace namespace,
+ IRequestCycle cycle,
+ IComponentSpecification specification)
+ {
+ IPage page = null;
+
+ _engine = cycle.getEngine();
+
+ _locale = _engine.getLocale();
+
+ _count = 0;
+ _depth = 0;
+ _maxDepth = 0;
+
+ try
+ {
+ page = instantiatePage(name, namespace, specification);
+
+ page.attach(_engine);
+
+ // As of 3.0.1, this is done now, rather than after constructing the page and its
+ // components.
+
+ page.setRequestCycle(cycle);
+
+ constructComponent(cycle, page, page, specification, namespace);
+
+ // Walk through the complete component tree to set up the default parameter values.
+ _establishDefaultParameterValuesWalker.walkComponentTree(page);
+
+ establishInheritedBindings();
+
+ // Walk through the complete component tree to ensure that required parameters are bound
+ _verifyRequiredParametersWalker.walkComponentTree(page);
+
+ establishDefaultPropertyValues();
+ }
+ finally
+ {
+ _locale = null;
+ _engine = null;
+ _inheritedBindingQueue.clear();
+ _propertyInitializers.clear();
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Loaded page "
+ + page
+ + " with "
+ + _count
+ + " components (maximum depth "
+ + _maxDepth
+ + ")");
+
+ return page;
+ }
+
+ private void establishInheritedBindings()
+ {
+ LOG.debug("Establishing inherited bindings");
+
+ int count = _inheritedBindingQueue.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ IQueuedInheritedBinding queued =
+ (IQueuedInheritedBinding) _inheritedBindingQueue.get(i);
+
+ queued.connect();
+ }
+ }
+
+ private void establishDefaultPropertyValues()
+ {
+ LOG.debug("Setting default property values");
+
+ int count = _propertyInitializers.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ PageDetachListener initializer =
+ (PageDetachListener) _propertyInitializers.get(i);
+
+ initializer.pageDetached(null);
+ }
+ }
+
+ private void addAssets(IComponent component, IComponentSpecification specification)
+ {
+ List names = specification.getAssetNames();
+
+ if (names.isEmpty())
+ return;
+
+ IResourceLocation specLocation = specification.getSpecificationLocation();
+
+ Iterator i = names.iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+ IAssetSpecification assetSpec = specification.getAsset(name);
+ IAsset asset = convert(name, component, assetSpec, specLocation);
+
+ component.addAsset(name, asset);
+ }
+ }
+
+ /**
+ * Invoked from
+ * {@link #constructComponent(IRequestCycle, IPage, IComponent, IComponentSpecification, INamespace)}
+ * after {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
+ * is invoked. This iterates over any
+ * {@link org.apache.tapestry.spec.IPropertySpecification}s for the component,
+ * create an initializer for each.
+ *
+ **/
+
+ private void createPropertyInitializers(
+ IPage page,
+ IComponent component,
+ IComponentSpecification spec)
+ {
+ List names = spec.getPropertySpecificationNames();
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ String name = (String) names.get(i);
+ IPropertySpecification ps = spec.getPropertySpecification(name);
+ String expression = ps.getInitialValue();
+
+ PageDetachListener initializer =
+ new PropertyInitializer(_resolver, component, name, expression, ps.getLocation());
+
+ _propertyInitializers.add(initializer);
+ page.addPageDetachListener(initializer);
+ }
+
+ }
+
+ /**
+ * Builds an instance of {@link IAsset} from the specification.
+ *
+ **/
+
+ private IAsset convert(
+ String assetName,
+ IComponent component,
+ IAssetSpecification spec,
+ IResourceLocation specificationLocation)
+ {
+ AssetType type = spec.getType();
+ String path = spec.getPath();
+ ILocation location = spec.getLocation();
+
+ if (type == AssetType.EXTERNAL)
+ return new ExternalAsset(path, location);
+
+ if (type == AssetType.PRIVATE)
+ {
+ IResourceLocation baseLocation = specificationLocation;
+
+ // Fudge a special case for private assets with complete paths. The specificationLocation
+ // can't be used because it is often a ContextResourceLocation,
+ // not a ClasspathResourceLocation.
+
+ if (path.startsWith("/"))
+ {
+ baseLocation = new ClasspathResourceLocation(_resolver, "/");
+ path = path.substring(1);
+ }
+
+ return new PrivateAsset(
+ (ClasspathResourceLocation) findAsset(assetName,
+ component,
+ baseLocation,
+ path,
+ location),
+ location);
+ }
+
+ return new ContextAsset(
+ (ContextResourceLocation) findAsset(assetName,
+ component,
+ _servletLocation,
+ path,
+ location),
+ location);
+ }
+
+ private IResourceLocation findAsset(
+ String assetName,
+ IComponent component,
+ IResourceLocation baseLocation,
+ String path,
+ ILocation location)
+ {
+ IResourceLocation assetLocation = baseLocation.getRelativeLocation(path);
+ IResourceLocation localizedLocation = assetLocation.getLocalization(_locale);
+
+ if (localizedLocation == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.missing-asset",
+ assetName,
+ component.getExtendedId(),
+ assetLocation),
+ component,
+ location,
+ null);
+
+ return localizedLocation;
+ }
+
+ public IEngine getEngine()
+ {
+ return _engine;
+ }
+
+ public ITemplateSource getTemplateSource()
+ {
+ return _engine.getTemplateSource();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/PageSource.java b/tapestry-framework/src/org/apache/tapestry/pageload/PageSource.java
new file mode 100644
index 0000000..d72c25d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/PageSource.java
@@ -0,0 +1,280 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IMonitor;
+import org.apache.tapestry.engine.IPageSource;
+import org.apache.tapestry.resolver.PageSpecificationResolver;
+import org.apache.tapestry.util.MultiKey;
+import org.apache.tapestry.util.pool.Pool;
+
+/**
+ * A source for pages for a particular application. Each application
+ * should have its own <code>PageSource</code>, storing it into the
+ * {@link javax.servlet.ServletContext} using a unique key (usually built from
+ * the application name).
+ *
+ * <p>The <code>PageSource</code> acts as a pool for {@link IPage} instances.
+ * Pages are retrieved from the pool using {@link #getPage(IRequestCycle, String, IMonitor)}
+ * and are later returned to the pool using {@link #releasePage(IPage)}.
+ *
+ *
+ * <p>TBD: Pooled pages stay forever. Need a strategy for cleaning up the pool,
+ * tracking which pages have been in the pool the longest, etc. A mechanism
+ * for reporting pool statistics would be useful.
+ *
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class PageSource implements IPageSource
+{
+ /**
+ * Key used to find PageLoader instances in the Pool.
+ *
+ **/
+
+ private static final String PAGE_LOADER_POOL_KEY = "org.apache.tapestry.PageLoader";
+
+ /**
+ * Key used to find {@link PageSpecificationResolver} instance
+ * in the pool.
+ */
+
+ private static final String PAGE_SPECIFICATION_RESOLVER_KEY =
+ "org.apache.tapestry.PageSpecificationResolver";
+
+ private IResourceResolver _resolver;
+
+ /**
+ * The pool of {@link IPage}s. The key is a {@link MultiKey},
+ * built from the page name and the page locale. This is a reference
+ * to a shared pool.
+ *
+ * @see IEngine#getPool()
+ *
+ **/
+
+ private Pool _pool;
+
+ public PageSource(IEngine engine)
+ {
+ _resolver = engine.getResourceResolver();
+ _pool = engine.getPool();
+ }
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ /**
+ * Builds a key for a named page in the application's current locale.
+ *
+ **/
+
+ protected MultiKey buildKey(IEngine engine, String pageName)
+ {
+ Object[] keys;
+
+ keys = new Object[] { pageName, engine.getLocale()};
+
+ // Don't make a copy, this array is just for the MultiKey.
+
+ return new MultiKey(keys, false);
+ }
+
+ /**
+ * Builds a key from an existing page, using the page's name and locale. This is
+ * used when storing a page into the pool.
+ *
+ **/
+
+ protected MultiKey buildKey(IPage page)
+ {
+ Object[] keys;
+
+ keys = new Object[] { page.getPageName(), page.getLocale()};
+
+ // Don't make a copy, this array is just for the MultiKey.
+
+ return new MultiKey(keys, false);
+ }
+
+ /**
+ * Gets the page from a pool, or otherwise loads the page. This operation
+ * is threadsafe.
+ *
+ **/
+
+ public IPage getPage(IRequestCycle cycle, String pageName, IMonitor monitor)
+ {
+ IEngine engine = cycle.getEngine();
+ Object key = buildKey(engine, pageName);
+ IPage result = (IPage) _pool.retrieve(key);
+
+ if (result == null)
+ {
+ monitor.pageCreateBegin(pageName);
+
+ // Resolvers are not threadsafe, so we get one from
+ // the pool or create as needed.
+
+ PageSpecificationResolver pageSpecificationResolver =
+ getPageSpecificationResolver(cycle);
+
+ pageSpecificationResolver.resolve(cycle, pageName);
+
+ // Likewise PageLoader
+
+ PageLoader loader = getPageLoader(cycle);
+
+ try
+ {
+ result =
+ loader.loadPage(
+ pageSpecificationResolver.getSimplePageName(),
+ pageSpecificationResolver.getNamespace(),
+ cycle,
+ pageSpecificationResolver.getSpecification());
+ }
+ finally
+ {
+ discardPageLoader(loader);
+ discardPageSpecificationResolver(pageSpecificationResolver);
+ }
+
+ monitor.pageCreateEnd(pageName);
+ }
+ else
+ {
+ // The page loader attaches the engine, but a page from
+ // the pool needs to be explicitly attached.
+
+ result.attach(engine);
+ result.setRequestCycle(cycle);
+ }
+
+ return result;
+ }
+
+ /**
+ * Invoked to obtain an instance of
+ * {@link PageLoader}. An instance if aquired from the pool or,
+ * if none are available, created fresh.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected PageLoader getPageLoader(IRequestCycle cycle)
+ {
+ PageLoader result = (PageLoader) _pool.retrieve(PAGE_LOADER_POOL_KEY);
+
+ if (result == null)
+ result = new PageLoader(cycle);
+
+ return result;
+ }
+
+ /**
+ * Invoked once the {@link PageLoader} is not
+ * longer needed; it is then returned to the pool.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void discardPageLoader(PageLoader loader)
+ {
+ _pool.store(PAGE_LOADER_POOL_KEY, loader);
+ }
+
+ /**
+ * Invoked to obtain an instance of {@link PageSpecificationResolver}.
+ * An instance is acquired form the pool or, if none are available,
+ * a new one is instantiated.
+ *
+ * @since 3.0
+ */
+
+ protected PageSpecificationResolver getPageSpecificationResolver(IRequestCycle cycle)
+ {
+ PageSpecificationResolver result =
+ (PageSpecificationResolver) _pool.retrieve(PAGE_SPECIFICATION_RESOLVER_KEY);
+
+ if (result == null)
+ result = new PageSpecificationResolver(cycle);
+
+ return result;
+ }
+
+ /**
+ * Invoked once the {@link PageSpecificationResolver} is no longer
+ * needed, it is returned to the pool.
+ *
+ * @since 3.0
+ */
+
+ protected void discardPageSpecificationResolver(PageSpecificationResolver resolver)
+ {
+ _pool.store(PAGE_SPECIFICATION_RESOLVER_KEY, resolver);
+ }
+
+ /**
+ * Returns the page to the appropriate pool. Invokes
+ * {@link IPage#detach()}.
+ *
+ **/
+
+ public void releasePage(IPage page)
+ {
+ Tapestry.clearMethodInvocations();
+
+ page.detach();
+
+ Tapestry.checkMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID, "detach()", page);
+
+ _pool.store(buildKey(page), page);
+ }
+
+ /**
+ * Invoked (during testing primarily) to release the entire pool
+ * of pages, and the caches of bindings and assets.
+ *
+ **/
+
+ public synchronized void reset()
+ {
+ _pool.clear();
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("pool", _pool);
+ builder.append("resolver", _resolver);
+
+ return builder.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/PropertyInitializer.java b/tapestry-framework/src/org/apache/tapestry/pageload/PropertyInitializer.java
new file mode 100644
index 0000000..2d3d9eb
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/PropertyInitializer.java
@@ -0,0 +1,126 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import ognl.Ognl;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.event.PageDetachListener;
+import org.apache.tapestry.event.PageEvent;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Given a component, a property and a value, this object will
+ * reset the property to the value whenever the page
+ * (containing the component) is detached. This is related
+ * to support for {@link org.apache.tapestry.spec.IPropertySpecification}s.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ **/
+
+public class PropertyInitializer implements PageDetachListener
+{
+ private IResourceResolver _resolver;
+ private IComponent _component;
+ private String _propertyName;
+ private String _expression;
+ private boolean _invariant;
+ private Object _value;
+ private ILocation _location;
+
+ public PropertyInitializer(
+ IResourceResolver resolver,
+ IComponent component,
+ String propertyName,
+ String expression,
+ ILocation location)
+ {
+ _resolver = resolver;
+ _component = component;
+ _propertyName = propertyName;
+ _expression = expression;
+ _location = location;
+
+ prepareInvariant();
+ }
+
+ public void prepareInvariant()
+ {
+ _invariant = false;
+
+ try
+ {
+ // If no initial value expression is provided, then read the current
+ // property of the expression. This may be null, or may be
+ // a value set in finishLoad() (via an abstract accessor).
+
+ if (Tapestry.isBlank(_expression))
+ {
+ _invariant = true;
+ _value = OgnlUtils.get(_propertyName, _resolver, _component);
+ }
+ else
+ if (Ognl.isConstant(_expression, Ognl.createDefaultContext(_component, _resolver)))
+ {
+ // If the expression is a constant, evaluate it and remember the value
+ _invariant = true;
+ _value = OgnlUtils.get(_expression, _resolver, _component);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.unable-to-initialize-property",
+ _propertyName,
+ _component,
+ ex.getMessage()),
+ _location,
+ ex);
+ }
+ }
+
+ public void pageDetached(PageEvent event)
+ {
+ try
+ {
+ if (_invariant)
+ OgnlUtils.set(_propertyName, _resolver, _component, _value);
+ else
+ {
+ Object value = OgnlUtils.get(_expression, _resolver, _component);
+ OgnlUtils.set(_propertyName, _resolver, _component, value);
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.unable-to-initialize-property",
+ _propertyName,
+ _component,
+ ex.getMessage()),
+ _location,
+ ex);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/VerifyRequiredParametersVisitor.java b/tapestry-framework/src/org/apache/tapestry/pageload/VerifyRequiredParametersVisitor.java
new file mode 100644
index 0000000..6368cad
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/VerifyRequiredParametersVisitor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pageload;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IParameterSpecification;
+
+/**
+ * Verify whether all required parameters in the examined component are bound,
+ * and if they are not, throw an exception.
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class VerifyRequiredParametersVisitor implements IComponentVisitor
+{
+ /**
+ * @see org.apache.tapestry.pageload.IComponentVisitor#visitComponent(org.apache.tapestry.IComponent)
+ */
+ public void visitComponent(IComponent component)
+ {
+ IComponentSpecification spec = component.getSpecification();
+
+ Iterator i = spec.getParameterNames().iterator();
+
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+ IParameterSpecification parameterSpec = spec.getParameter(name);
+
+ if (parameterSpec.isRequired() && component.getBinding(name) == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageLoader.required-parameter-not-bound",
+ name,
+ component.getExtendedId()),
+ component,
+ component.getLocation(),
+ null);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pageload/package.html b/tapestry-framework/src/org/apache/tapestry/pageload/package.html
new file mode 100644
index 0000000..7e5a4db
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pageload/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Classes used when loading pages (and thier heirarchies of components) from thier
+specifications, as well as organizaing thier templates.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/Exception.css b/tapestry-framework/src/org/apache/tapestry/pages/Exception.css
new file mode 100644
index 0000000..1bcec19
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/Exception.css
@@ -0,0 +1,152 @@
+P {}
+
+H1 {}
+
+H2 {}
+
+H3 {}
+
+A {}
+
+A:Visited {}
+
+A:Active {}
+
+A:Hover {}
+
+BODY {}
+
+TABLE.exception-display TR.even {
+ top : auto;
+}
+
+TABLE.exception-display TR.odd {
+ top : auto;
+ background-color : #C0C0FF;
+}
+
+TABLE.exception-display TH {
+ text-align : right;
+ font-weight : bold;
+}
+
+TABLE.exception-display TD {
+ text-align : left;
+ width : 100%;
+}
+
+TABLE.exception-display TR.stack-trace {
+ font-size : small;
+ font-family : sans-serif;
+ text-align : left;
+}
+
+SPAN.exception-header {
+ font-size : large;
+ font-weight : bold;
+ color : Red;
+}
+
+SPAN.exception-message {
+ font-weight: bold;
+}
+
+TABLE.request-context-border {
+ border-width : 1;
+ border-color : Black;
+}
+
+SPAN.request-context-object {
+ font-size : large;
+ font-family : sans-serif;
+ font-weight : bold;
+ text-align : left;
+}
+
+TR.request-context-section TH {
+ font-size : medium;
+ font-family : sans-serif;
+ font-weight : bold;
+ text-align : center;
+ color : White;
+ background-color : Blue;
+}
+
+TR.request-context-header TH {
+ font-size : small;
+ font-family : sans-serif;
+ font-weight : bold;
+ text-align : center;
+ color : White;
+ background-color : Blue;
+}
+
+TABLE.request-context-object TD
+{
+ width: 100%;
+}
+
+TABLE.request-context-object TR.odd TD {
+ text-align : left;
+ color : Black;
+ background-color : #C0C0FF;
+ width: 100%;
+}
+
+TABLE.request-context-object TR.odd TH {
+ color : Black;
+ background-color : #C0C0FF;
+ text-align : right;
+}
+
+TABLE.request-context-object TR.even TD {
+ text-align : left;
+}
+
+TABLE.request-context-object TR.even TH {
+ text-align : right;
+}
+
+TABLE.request-context-object {
+ width : 100%;
+}
+
+TABLE.request-context-object TR {
+ vertical-align : text-top;
+}
+
+UL {
+ margin-top : 0px;
+ margin-bottom : 0px;
+ margin-left : 20px;
+}
+
+TABLE.exception-display TR.exception-name TD {
+ font-size : larger;
+ font-weight : bold;
+ text-align : center;
+ background-color : Blue;
+ color : White;
+}
+
+TABLE.exception-display {
+ width : 100%;
+}
+
+TABLE.exception-display TR.exception-message TD {
+ border-width : 1;
+ border-color : Black;
+ border-style : solid;
+ padding : 2;
+ text-align : left;
+ font-style : italic;
+}
+
+TABLE.exception-display TR.strack-trace-label TD {
+ margin : 2;
+ border-width : 1;
+ border-color : Black;
+ border-style : solid;
+ text-align : center;
+}
+
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/Exception.html b/tapestry-framework/src/org/apache/tapestry/pages/Exception.html
new file mode 100644
index 0000000..de41efc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/Exception.html
@@ -0,0 +1,19 @@
+<!-- $Id$ -->
+
+<span jwcid="@Shell" title="Exception" stylesheet="ognl:assets.stylesheet">
+<body>
+
+<span class="exception-header">
+An exception has occurred.
+</span>
+
+<p>You may continue by <b><a jwcid="restart">restarting</a></b> the session.
+
+<span jwcid="@ExceptionDisplay" exceptions="ognl:exceptions"/>
+
+<p>
+
+<span jwcid="@Delegator" delegate="ognl:requestCycle.requestContext"/>
+
+</body>
+</span>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/Exception.java b/tapestry-framework/src/org/apache/tapestry/pages/Exception.java
new file mode 100644
index 0000000..d7390c7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/Exception.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pages;
+
+import org.apache.tapestry.html.BasePage;
+import org.apache.tapestry.util.exception.ExceptionAnalyzer;
+import org.apache.tapestry.util.exception.ExceptionDescription;
+
+/**
+ * Default exception reporting page.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class Exception extends BasePage
+{
+ private ExceptionDescription[] _exceptions;
+
+ public void detach()
+ {
+ _exceptions = null;
+
+ super.detach();
+ }
+
+ public ExceptionDescription[] getExceptions()
+ {
+ return _exceptions;
+ }
+
+ public void setException(Throwable value)
+ {
+ ExceptionAnalyzer analyzer;
+
+ analyzer = new ExceptionAnalyzer();
+
+ _exceptions = analyzer.analyze(value);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/Exception.page b/tapestry-framework/src/org/apache/tapestry/pages/Exception.page
new file mode 100644
index 0000000..f4e8f5b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/Exception.page
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.pages.Exception">
+
+ <component id="restart" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
+ </component>
+
+ <private-asset name="stylesheet" resource-path="Exception.css"/>
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.html b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.html
new file mode 100644
index 0000000..e1ced61
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.html
@@ -0,0 +1,25 @@
+<!-- $Id$ -->
+
+<span jwcid="@Shell" stylesheet="ognl:assets.stylesheet" title="Stale Link">
+
+<body>
+
+You have clicked on a <i>stale link</i>.
+
+<p>
+<span jwcid="@Insert" value="ognl:message" class="exception-message">
+Exception message goes here.
+</span>
+
+<p>This is most likely the result of using your
+browser's <b>back</b> button, but can also be an application error.
+
+<p>You may continue by returning to the
+application's
+
+<b>
+<a jwcid="home">home page</a></b>.
+
+</body>
+
+</span>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.java b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.java
new file mode 100644
index 0000000..6357455
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.java
@@ -0,0 +1,32 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.pages;
+
+import org.apache.tapestry.html.BasePage;
+
+/**
+ * Stores a message (taken from the {@link org.apache.tapestry.StaleLinkException})
+ * that is displayed as part of the page.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class StaleLink extends BasePage
+{
+ public abstract void setMessage(String message);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.page b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.page
new file mode 100644
index 0000000..c55a99d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/StaleLink.page
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.pages.StaleLink">
+
+ <property-specification name="message" type="java.lang.String"/>
+
+ <component id="home" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@HOME_SERVICE"/>
+ </component>
+
+ <private-asset name="stylesheet" resource-path="Exception.css"/>
+
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.html b/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.html
new file mode 100644
index 0000000..9fc09ac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.html
@@ -0,0 +1,19 @@
+<!-- $Id$ -->
+
+<span jwcid="@Shell" title="Stale Session" stylesheet="ognl:assets.stylesheet">
+
+<body>
+Your session has timed out.
+
+<p>Web applications store information about what you are doing on the server. This information
+is called the <em>session</em>.
+
+<p>Web servers must track many, many sessions. If you
+are inactive for a long enough time (usually, a few minutes), this information is discarded to
+make room for active users.
+
+<p>At this point you may <b>
+<a jwcid="restart">restart</a></b> the session to continue.
+
+</body>
+</span>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.page b/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.page
new file mode 100644
index 0000000..911b6f2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/StaleSession.page
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification>
+
+ <component id="restart" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
+ </component>
+
+ <private-asset name="stylesheet" resource-path="Exception.css"/>
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/pages/package.html b/tapestry-framework/src/org/apache/tapestry/pages/package.html
new file mode 100644
index 0000000..aeade4d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/pages/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+Basic pages used for errors, stale links and stale sessions. These can all be
+overriden in the application specification.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/param/AbstractParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/AbstractParameterConnector.java
new file mode 100644
index 0000000..7aa1918
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/AbstractParameterConnector.java
@@ -0,0 +1,189 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.form.Form;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.spec.Direction;
+import org.apache.tapestry.spec.IParameterSpecification;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Standard implementation of {@link IParameterConnector}.
+ * Subclasses add in the ability to clear parameters.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public abstract class AbstractParameterConnector implements IParameterConnector
+{
+ private String _parameterName;
+ private String _propertyName;
+ private IBinding _binding;
+ private IComponent _component;
+ private boolean _required;
+ private Object _clearValue;
+ private Direction _direction;
+ private IResourceResolver _resolver;
+
+ /**
+ * Creates a connector. In addition, obtains the current value
+ * of the component property; this value will be used to
+ * restore the component property.
+ *
+ **/
+
+ protected AbstractParameterConnector(IComponent component, String parameterName, IBinding binding)
+ {
+ _component = component;
+ _parameterName = parameterName;
+ _binding = binding;
+
+ _resolver = component.getPage().getEngine().getResourceResolver();
+
+ IParameterSpecification pspec = _component.getSpecification().getParameter(_parameterName);
+ _required = pspec.isRequired();
+ _propertyName = pspec.getPropertyName();
+ _direction = pspec.getDirection();
+
+ _clearValue = readCurrentPropertyValue();
+ }
+
+ /** @since 2.2 **/
+
+ private Object readCurrentPropertyValue()
+ {
+ return OgnlUtils.get(_propertyName, _resolver, _component);
+ }
+
+ /**
+ * Sets the property of the component to the specified value.
+ *
+ **/
+
+ protected void setPropertyValue(Object value)
+ {
+ OgnlUtils.set(_propertyName, _resolver, _component, value);
+ }
+
+ /**
+ * Gets the value of the binding.
+ * @param requiredType if not null, the expected type of the value object.
+ *
+ *
+ * @see IBinding#getObject()
+ * @see IBinding#getObject(String, Class)
+ **/
+
+ protected Object getBindingValue(Class requiredType)
+ {
+ Object result;
+
+ if (requiredType == null)
+ result = _binding.getObject();
+ else
+ result = _binding.getObject(_parameterName, requiredType);
+
+ return result;
+ }
+
+ protected IBinding getBinding()
+ {
+ return _binding;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer(super.toString());
+ buffer.append('[');
+ buffer.append(_component.getExtendedId());
+ buffer.append(' ');
+ buffer.append(_parameterName);
+ buffer.append(' ');
+ buffer.append(_binding);
+
+ buffer.append(' ');
+ buffer.append(_direction.getName());
+
+ if (_required)
+ buffer.append(" required");
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ /**
+ * Restores the property to its default value. For
+ * {@link Direction#FORM} parameters, extracts the
+ * property value and sets the binding form it
+ * (when appropriate).
+ *
+ **/
+
+ public void resetParameter(IRequestCycle cycle)
+ {
+ if (_direction == Direction.FORM && cycle.isRewinding())
+ {
+ IFormComponent component = (IFormComponent) _component;
+
+ if (!component.isDisabled())
+ {
+ IForm form = Form.get(cycle);
+
+ if (form != null && form.isRewinding())
+ {
+ Object value = readCurrentPropertyValue();
+
+ _binding.setObject(value);
+ }
+ }
+ }
+
+ // Either way, clear the value.
+
+ setPropertyValue(_clearValue);
+ }
+
+ /**
+ * Returns true if the connector should update the property value from
+ * the binding. For {@link org.apache.tapestry.spec.Direction#IN}, this
+ * always returns true. For {@link org.apache.tapestry.spec.Direction#FORM},
+ * this returns true only if the request cycle and the active form
+ * are rewinding.
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected boolean shouldSetPropertyValue(IRequestCycle cycle)
+ {
+ if (_direction == Direction.IN)
+ return true;
+
+ // Must be FORM
+
+ return !cycle.isRewinding();
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/param/BooleanParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/BooleanParameterConnector.java
new file mode 100644
index 0000000..bd32e5a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/BooleanParameterConnector.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Connector for boolean parameters.
+ *
+ * @see IBinding#getBoolean()
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class BooleanParameterConnector extends AbstractParameterConnector
+{
+
+ protected BooleanParameterConnector(IComponent component, String parameterName, IBinding binding)
+ {
+ super(component, parameterName, binding);
+ }
+
+ /**
+ * Invokes {@link IBinding#getBoolean()}, which always
+ * returns true or false (there is no concept of a null
+ * value).
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle)
+ {
+ if (shouldSetPropertyValue(cycle))
+ {
+ boolean value = getBinding().getBoolean();
+
+ setPropertyValue(value ? Boolean.TRUE : Boolean.FALSE);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/param/ConnectedParameterException.java b/tapestry-framework/src/org/apache/tapestry/param/ConnectedParameterException.java
new file mode 100644
index 0000000..968189d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/ConnectedParameterException.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+
+/**
+ * Identifies exceptions in connected parameters (parameters that
+ * are automatically assigned to component properties by the framework).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class ConnectedParameterException extends ApplicationRuntimeException
+{
+ private String _parameterName;
+ private String _propertyName;
+
+ public ConnectedParameterException(
+ String message,
+ Object component,
+ String parameterName,
+ String propertyName,
+ Throwable rootCause)
+ {
+ this(message, component, parameterName, propertyName, null, rootCause);
+ }
+
+ /** @since 3.0 **/
+
+ public ConnectedParameterException(
+ String message,
+ Object component,
+ String parameterName,
+ String propertyName,
+ ILocation location,
+ Throwable rootCause)
+ {
+ super(message, location, rootCause);
+
+ _parameterName = parameterName;
+ _propertyName = propertyName;
+ }
+
+ public String getParameterName()
+ {
+ return _parameterName;
+ }
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/param/DoubleParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/DoubleParameterConnector.java
new file mode 100644
index 0000000..ac76e30
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/DoubleParameterConnector.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Connects a parameter to a property of type double.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class DoubleParameterConnector extends AbstractParameterConnector
+{
+
+ protected DoubleParameterConnector(IComponent component, String parameterName, IBinding binding)
+ {
+ super(component, parameterName, binding);
+ }
+
+ /**
+ * Invokes {@link IBinding#getDouble()} to obtain the value
+ * to assign to the property.
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle)
+ {
+ if (shouldSetPropertyValue(cycle))
+ {
+ double scalar = getBinding().getDouble();
+
+ setPropertyValue(new Double(scalar));
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/param/IParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/IParameterConnector.java
new file mode 100644
index 0000000..87be635
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/IParameterConnector.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Define a type of connector between a binding of a component and a JavaBeans
+ * property of the component (with the same name). Allows
+ * for the parameter to be set before the component is rendered,
+ * then cleared after the component is rendered.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public interface IParameterConnector
+{
+ /**
+ * Sets the parameter from the binding.
+ *
+ * @throws RequiredParameterException if the parameter is
+ * required, but the {@link org.apache.tapestry.IBinding}
+ * supplies a null value.
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle);
+
+ /**
+ * Clears the parameters to a null, 0 or false value
+ * (depending on type).
+ *
+ **/
+
+ public void resetParameter(IRequestCycle cycle);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/param/IntParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/IntParameterConnector.java
new file mode 100644
index 0000000..6e9950e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/IntParameterConnector.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Connects a parameter to an int property.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class IntParameterConnector extends AbstractParameterConnector
+{
+
+ protected IntParameterConnector(IComponent component, String parameterName, IBinding binding)
+ {
+ super(component, parameterName, binding);
+ }
+
+ /**
+ * Invokes {@link IBinding#getInt()} to obtain
+ * an int value to assign.
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle)
+ {
+ if (shouldSetPropertyValue(cycle))
+ {
+ int scalar = getBinding().getInt();
+
+ setPropertyValue(new Integer(scalar));
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/param/ObjectParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/ObjectParameterConnector.java
new file mode 100644
index 0000000..1c02003
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/ObjectParameterConnector.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Implements {@link IParameterConnector} for object parameters.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class ObjectParameterConnector extends AbstractParameterConnector
+{
+ private Class _requiredType;
+
+ protected ObjectParameterConnector(
+ IComponent component,
+ String parameterName,
+ IBinding binding,
+ Class requiredType)
+ {
+ super(component, parameterName, binding);
+
+ _requiredType = requiredType;
+ }
+
+ /**
+ * Sets the parameter property to null.
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle)
+ {
+ if (shouldSetPropertyValue(cycle))
+ setPropertyValue(getBindingValue(_requiredType));
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/param/ParameterManager.java b/tapestry-framework/src/org/apache/tapestry/param/ParameterManager.java
new file mode 100644
index 0000000..d7ab3c9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/ParameterManager.java
@@ -0,0 +1,355 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.Direction;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IParameterSpecification;
+import org.apache.tapestry.util.prop.PropertyFinder;
+import org.apache.tapestry.util.prop.PropertyInfo;
+
+/**
+ * Manages a set of {@link IParameterConnector}s for a
+ * {@link IComponent}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class ParameterManager
+{
+ private static final Log LOG = LogFactory.getLog(ParameterManager.class);
+
+ /**
+ * Special types that aren't resolved by class lookups, including
+ * scalars, arrays of scalars, etc.
+ *
+ * <p>
+ * There's some overlap here with ComponentClassFactory.
+ *
+ **/
+
+ private static final Map SPECIAL_TYPE_MAP = new HashMap();
+
+ static {
+ SPECIAL_TYPE_MAP.put("boolean", boolean.class);
+ SPECIAL_TYPE_MAP.put("boolean[]", boolean[].class);
+ SPECIAL_TYPE_MAP.put("byte", byte.class);
+ SPECIAL_TYPE_MAP.put("byte[]", byte[].class);
+ SPECIAL_TYPE_MAP.put("char", char.class);
+ SPECIAL_TYPE_MAP.put("char[]", char[].class);
+ SPECIAL_TYPE_MAP.put("short", short.class);
+ SPECIAL_TYPE_MAP.put("short[]", short[].class);
+ SPECIAL_TYPE_MAP.put("int", int.class);
+ SPECIAL_TYPE_MAP.put("int[]", int[].class);
+ SPECIAL_TYPE_MAP.put("long", long.class);
+ SPECIAL_TYPE_MAP.put("long[]", long[].class);
+ SPECIAL_TYPE_MAP.put("float", float.class);
+ SPECIAL_TYPE_MAP.put("float[]", float[].class);
+ SPECIAL_TYPE_MAP.put("double", double.class);
+ SPECIAL_TYPE_MAP.put("double[]", double[].class);
+
+ SPECIAL_TYPE_MAP.put("java.lang.Object[]", Object[].class);
+ SPECIAL_TYPE_MAP.put("java.lang.String[]", String[].class);
+ }
+
+ private IComponent _component;
+ private IParameterConnector[] _connectors;
+
+ public ParameterManager(IComponent component)
+ {
+ _component = component;
+ }
+
+ /**
+ * Invoked just before a component renders. Converts bindings to values
+ * that are assigned to connected properties.
+ *
+ **/
+
+ public void setParameters(IRequestCycle cycle)
+ {
+ if (_connectors == null)
+ setup(cycle);
+
+ for (int i = 0; i < _connectors.length; i++)
+ _connectors[i].setParameter(cycle);
+ }
+
+ /**
+ * Invoked just after the component renders. Returns component properties
+ * back to initial values (unless the corresponding binding is
+ * {@link IBinding#isInvariant() invariant}). In addition, for
+ * {@link Direction#FORM} parameters, the property is read and the binding
+ * is set from the property value (if the cycle is rewinding and the current
+ * form is rewinding).
+ *
+ **/
+
+ public void resetParameters(IRequestCycle cycle)
+ {
+ if (_connectors == null)
+ return;
+
+ for (int i = 0; i < _connectors.length; i++)
+ _connectors[i].resetParameter(cycle);
+ }
+
+ private void setup(IRequestCycle cycle)
+ {
+ boolean debug = LOG.isDebugEnabled();
+
+ if (debug)
+ LOG.debug(_component + ": connecting parameters and properties");
+
+ List list = new ArrayList();
+ IComponentSpecification spec = _component.getSpecification();
+ IResourceResolver resolver = _component.getPage().getEngine().getResourceResolver();
+
+ IParameterConnector disabledConnector = null;
+
+ Collection names = spec.getParameterNames();
+ Iterator i = names.iterator();
+ while (i.hasNext())
+ {
+ String name = (String) i.next();
+
+ if (debug)
+ LOG.debug("Connecting parameter " + name + ".");
+
+ IBinding binding = _component.getBinding(name);
+ if (binding == null)
+ {
+ if (debug)
+ LOG.debug("Not bound.");
+
+ continue;
+ }
+
+ IParameterSpecification pspec = spec.getParameter(name);
+ Direction direction = pspec.getDirection();
+
+ if (direction != Direction.IN && direction != Direction.FORM)
+ {
+ if (debug)
+ LOG.debug("Parameter is " + pspec.getDirection().getName() + ".");
+
+ continue;
+ }
+
+ if (!direction.getAllowInvariant() && binding.isInvariant())
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.incompatible-direction-and-binding",
+ new Object[] {
+ name,
+ _component.getExtendedId(),
+ direction.getDisplayName(),
+ binding }),
+ _component,
+ name,
+ null,
+ binding.getLocation(),
+ null);
+
+ String propertyName = pspec.getPropertyName();
+
+ if (debug && !name.equals(propertyName))
+ LOG.debug("Connecting to property " + propertyName + ".");
+
+ // Next,verify that there is a writable property with the same
+ // name as the parameter.
+
+ PropertyInfo propertyInfo =
+ PropertyFinder.getPropertyInfo(_component.getClass(), propertyName);
+
+ if (propertyInfo == null)
+ {
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.no-accessor",
+ _component.getExtendedId(),
+ propertyName),
+ _component,
+ name,
+ propertyName,
+ binding.getLocation(),
+ null);
+ }
+
+ if (!propertyInfo.isReadWrite())
+ {
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.property-not-read-write",
+ _component.getExtendedId(),
+ propertyName),
+ _component,
+ name,
+ propertyName,
+ binding.getLocation(),
+ null);
+ }
+
+ // Check if the parameter type matches the property type
+
+ Class propertyType = propertyInfo.getType();
+ Class parameterType = getType(pspec.getType(), resolver);
+
+ if (parameterType == null)
+ {
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.java-type-not-specified",
+ name,
+ _component.getExtendedId()),
+ _component,
+ name,
+ propertyName,
+ binding.getLocation(),
+ null);
+ }
+
+ if (!propertyType.equals(parameterType))
+ {
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.type-mismatch",
+ new String[] {
+ name,
+ _component.getExtendedId(),
+ parameterType.toString(),
+ propertyType.toString()}),
+ _component,
+ name,
+ propertyName,
+ binding.getLocation(),
+ null);
+ }
+
+ // Here's where we will sniff it for type, for the moment
+ // assume its some form of object (not scalar) type.
+
+ IParameterConnector connector =
+ createConnector(_component, name, binding, propertyType, parameterType);
+
+ // Static bindings are set here and then forgotten
+ // about. Dynamic bindings are kept for later.
+
+ if (binding.isInvariant())
+ {
+ if (debug)
+ LOG.debug("Setting invariant value using " + connector + ".");
+
+ try
+ {
+ connector.setParameter(cycle);
+ }
+ catch (BindingException ex)
+ {
+ throw new ConnectedParameterException(
+ Tapestry.format(
+ "ParameterManager.static-initialization-failure",
+ propertyName,
+ _component.getExtendedId(),
+ binding.toString()),
+ _component,
+ name,
+ propertyName,
+ ex);
+ }
+
+ continue;
+ }
+
+ if (debug)
+ LOG.debug("Adding " + connector + ".");
+
+ // To properly support forms elements, the disabled parameter
+ // must always be processed last.
+
+ if (name.equals("disabled"))
+ disabledConnector = connector;
+ else
+ list.add(connector);
+
+ }
+
+ if (disabledConnector != null)
+ list.add(disabledConnector);
+
+ // Convert for List to array
+
+ _connectors = (IParameterConnector[]) list.toArray(new IParameterConnector[list.size()]);
+
+ }
+
+ private IParameterConnector createConnector(
+ IComponent component,
+ String parameterName,
+ IBinding binding,
+ Class propertyType,
+ Class requiredType)
+ {
+ // Could convert this code to use a Decorator, but then I'd need
+ // some kind of factory for these parameter connectors.
+
+ if (propertyType.equals(Boolean.TYPE))
+ return new BooleanParameterConnector(component, parameterName, binding);
+
+ if (propertyType.equals(Integer.TYPE))
+ return new IntParameterConnector(component, parameterName, binding);
+
+ if (propertyType.equals(Double.TYPE))
+ return new DoubleParameterConnector(component, parameterName, binding);
+
+ if (propertyType.equals(String.class))
+ return new StringParameterConnector(component, parameterName, binding);
+
+ // The default is for any kind of object type
+
+ return new ObjectParameterConnector(component, parameterName, binding, requiredType);
+ }
+
+ private Class getType(String name, IResourceResolver resolver)
+ {
+ if (Tapestry.isBlank(name))
+ return null;
+
+ Class result = (Class) SPECIAL_TYPE_MAP.get(name);
+
+ if (result != null)
+ return result;
+
+ return resolver.findClass(name);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/param/StringParameterConnector.java b/tapestry-framework/src/org/apache/tapestry/param/StringParameterConnector.java
new file mode 100644
index 0000000..e636ad1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/StringParameterConnector.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.param;
+
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Creates a connection between a parameter and a property of type {@link String}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ **/
+
+public class StringParameterConnector extends AbstractParameterConnector
+{
+
+ protected StringParameterConnector(
+ IComponent component,
+ String parameterName,
+ IBinding binding)
+ {
+ super(component, parameterName, binding);
+ }
+
+ /**
+ * Invokes {@link IBinding#getString()} to obtain the property
+ * value.
+ *
+ **/
+
+ public void setParameter(IRequestCycle cycle)
+ {
+ if (shouldSetPropertyValue(cycle))
+ setPropertyValue(getBinding().getString());
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/param/package.html b/tapestry-framework/src/org/apache/tapestry/param/package.html
new file mode 100644
index 0000000..1a44a94
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/param/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+Code to assist {@link org.apache.tapestry.IComponent} in setting JavaBeans properties
+from bound parameters.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/AbstractSpecificationRule.java b/tapestry-framework/src/org/apache/tapestry/parse/AbstractSpecificationRule.java
new file mode 100644
index 0000000..89e30e6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/AbstractSpecificationRule.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.digester.Rule;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.xml.sax.Attributes;
+
+/**
+ * Placeholder for utility methods needed by the various
+ * specification-oriented {@link org.apache.commons.digester.Rule}s.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractSpecificationRule extends Rule
+{
+
+ protected String getValue(Attributes attributes, String name)
+ {
+ int count = attributes.getLength();
+
+ for (int i = 0; i < count; i++)
+ {
+ String attributeName = attributes.getLocalName(i);
+
+ if (Tapestry.isBlank(attributeName))
+ attributeName = attributes.getQName(i);
+
+ if (attributeName.equals(name))
+ return attributes.getValue(i);
+ }
+
+ return null;
+ }
+
+ protected void setProperty(String propertyName, Object value)
+ throws Exception
+ {
+ PropertyUtils.setProperty(digester.peek(), propertyName, value);
+ }
+
+ /**
+ * Gets the current location tag. This requires that the
+ * rule's digester be {@link SpecificationDigester}.
+ *
+ **/
+
+ protected ILocation getLocation()
+ {
+ SpecificationDigester locatableDigester = (SpecificationDigester) digester;
+
+ return locatableDigester.getLocationTag();
+ }
+
+ // Temporary, until DocumentParseException is fixed.
+
+ protected IResourceLocation getResourceLocation()
+ {
+ SpecificationDigester locatableDigester = (SpecificationDigester) digester;
+
+ return locatableDigester.getResourceLocation();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/AttributeType.java b/tapestry-framework/src/org/apache/tapestry/parse/AttributeType.java
new file mode 100644
index 0000000..1724447
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/AttributeType.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * The type of an {@link org.apache.tapestry.parse.TemplateAttribute}.
+ * New types can be created by modifying
+ * {@link org.apache.tapestry.parse.TemplateParser} to recognize
+ * the attribute prefix in compnent tags, and
+ * by modifying
+ * {@link org.apache.tapestry.BaseComponentTemplateLoader}
+ * to actually do something with the TemplateAttribute, based on type.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class AttributeType extends Enum
+{
+ /**
+ * Indicates the attribute is simple, literal text. This is
+ * the default for any attributes without a recognized
+ * prefix.
+ *
+ * @see org.apache.tapestry.binding.StaticBinding
+ *
+ **/
+
+ public static final AttributeType LITERAL = new AttributeType("LITERAL");
+
+ /**
+ * Indicates the attribute is a OGNL expression.
+ *
+ * @see org.apache.tapestry.binding.ExpressionBinding
+ *
+ **/
+
+ public static final AttributeType OGNL_EXPRESSION = new AttributeType("OGNL_EXPRESSION");
+
+ /**
+ * Indicates the attribute is a localization key.
+ *
+ * @see org.apache.tapestry.binding.StringBinding
+ *
+ **/
+
+ public static final AttributeType LOCALIZATION_KEY = new AttributeType("LOCALIZATION_KEY");
+
+ private AttributeType(String name)
+ {
+ super(name);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/BaseDocumentRule.java b/tapestry-framework/src/org/apache/tapestry/parse/BaseDocumentRule.java
new file mode 100644
index 0000000..30233b9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/BaseDocumentRule.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Base implementation of {@link org.apache.tapestry.parse.IDocumentRule}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class BaseDocumentRule implements IDocumentRule
+{
+ private SpecificationDigester _digester;
+
+ public SpecificationDigester getDigester()
+ {
+ return _digester;
+ }
+
+ public void setDigester(SpecificationDigester digester)
+ {
+ _digester = digester;
+ }
+
+ public void startDocument(String namespace, String name, Attributes attributes) throws Exception
+ {
+ }
+
+ public void endDocument() throws Exception
+ {
+ }
+
+ public void finish() throws Exception
+ {
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/BodyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/BodyRule.java
new file mode 100644
index 0000000..56f5895
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/BodyRule.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+/**
+ * Variation of {@link org.apache.commons.digester.BeanPropertySetterRule}
+ * that does <em>not</em> trim the body text of leading and trailing
+ * whitespace. This is important for {@link org.apache.tapestry.spec.IListenerBindingSpecification}s,
+ * where the whitespace may be relevant!
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class BodyRule extends AbstractSpecificationRule
+{
+ private String _propertyName;
+
+ public BodyRule(String propertyName)
+ {
+ _propertyName = propertyName;
+ }
+
+ public void body(String namespace, String name, String text) throws Exception
+ {
+ setProperty(_propertyName, text);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/CloseToken.java b/tapestry-framework/src/org/apache/tapestry/parse/CloseToken.java
new file mode 100644
index 0000000..4b1b42b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/CloseToken.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ILocation;
+
+/**
+ * Represents the closing tag of a component element in the template.
+ *
+ * @see TokenType#CLOSE
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class CloseToken extends TemplateToken
+{
+ private String _tag;
+
+ public CloseToken(String tag, ILocation location)
+ {
+ super(TokenType.CLOSE, location);
+
+ _tag = tag;
+ }
+
+ public String getTag()
+ {
+ return _tag;
+ }
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ builder.append("tag", _tag);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ComponentCopyOfRule.java b/tapestry-framework/src/org/apache/tapestry/parse/ComponentCopyOfRule.java
new file mode 100644
index 0000000..0877ec8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ComponentCopyOfRule.java
@@ -0,0 +1,89 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.Iterator;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.IBindingSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IContainedComponent;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+
+/**
+ * A rule for processing the copy-of attribute
+ * of the <component> element (in a page
+ * or component specification).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ComponentCopyOfRule extends AbstractSpecificationRule
+{
+ /**
+ * Validates that the element has either type or copy-of (not both, not neither).
+ * Uses the copy-of attribute to find a previously declared component
+ * and copies its type and bindings into the new component (on top of the stack).
+ *
+ **/
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String id = getValue(attributes, "id");
+ String copyOf = getValue(attributes, "copy-of");
+ String type = getValue(attributes, "type");
+
+ if (Tapestry.isBlank(copyOf))
+ {
+ if (Tapestry.isBlank(type))
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.missing-type-or-copy-of", id),
+ getResourceLocation());
+
+ return;
+ }
+
+ if (Tapestry.isNonBlank(type))
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.both-type-and-copy-of", id),
+ getResourceLocation());
+
+ IComponentSpecification spec = (IComponentSpecification) digester.getRoot();
+
+ IContainedComponent source = spec.getComponent(copyOf);
+ if (source == null)
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.unable-to-copy", copyOf),
+ getResourceLocation());
+
+ IContainedComponent target = (IContainedComponent) digester.peek();
+
+ target.setType(source.getType());
+ target.setCopyOf(copyOf);
+
+ Iterator i = source.getBindingNames().iterator();
+ while (i.hasNext())
+ {
+ String bindingName = (String) i.next();
+ IBindingSpecification binding = source.getBinding(bindingName);
+ target.setBinding(bindingName, binding);
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ComponentTemplate.java b/tapestry-framework/src/org/apache/tapestry/parse/ComponentTemplate.java
new file mode 100644
index 0000000..1837a0a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ComponentTemplate.java
@@ -0,0 +1,74 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+/**
+ * Enapsulates a parsed component template, allowing access to the
+ * tokens parsed.
+ *
+ * <p>TBD: Record the name of the resource (or other location) from which
+ * the template was parsed (useful during debugging).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ComponentTemplate
+{
+ /**
+ * The HTML template from which the tokens were generated. This is a string
+ * read from a resource. The tokens represents offsets and lengths into
+ * this string.
+ *
+ **/
+
+ private char[] _templateData;
+
+ private TemplateToken[] _tokens;
+
+ /**
+ * Creates a new ComponentTemplate.
+ *
+ * @param templateData The template data. This is <em>not</em> copied, so
+ * the array passed in should not be modified further.
+ *
+ * @param tokens The tokens making up the template. This is also
+ * retained (<em>not</em> copied), and so should not
+ * be modified once passed to the constructor.
+ *
+ **/
+
+ public ComponentTemplate(char[] templateData, TemplateToken[] tokens)
+ {
+ _templateData = templateData;
+ _tokens = tokens;
+ }
+
+ public char[] getTemplateData()
+ {
+ return _templateData;
+ }
+
+ public TemplateToken getToken(int index)
+ {
+ return _tokens[index];
+ }
+
+ public int getTokenCount()
+ {
+ return _tokens.length;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ConnectChildRule.java b/tapestry-framework/src/org/apache/tapestry/parse/ConnectChildRule.java
new file mode 100644
index 0000000..cf13890
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ConnectChildRule.java
@@ -0,0 +1,67 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.beanutils.MethodUtils;
+import org.xml.sax.Attributes;
+
+/**
+ * Connects a child object to a parent object using a named method. The method
+ * takes two parameters: the name of the child object and the child object itself.
+ * The child object name is taken from an attribute.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ConnectChildRule extends AbstractSpecificationRule
+{
+ private String _methodName;
+ private String _attributeName;
+
+ private String _attributeValue;
+
+ public ConnectChildRule(String methodName, String attributeName)
+ {
+ _methodName = methodName;
+ _attributeName = attributeName;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ _attributeValue = getValue(attributes, _attributeName);
+
+ // Check for null?
+ }
+
+ /**
+ * Performs the add. This is done in <code>end()</code> to ensure
+ * that the child object (on top of the stack) is fully initialized.
+ *
+ **/
+
+ public void end(String namespace, String name) throws Exception
+ {
+ Object child = digester.peek();
+ Object parent = digester.peek(1);
+
+ MethodUtils.invokeMethod(parent, _methodName, new Object[] { _attributeValue, child });
+
+ _attributeValue = null;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/DisallowFrameworkNamespaceRule.java b/tapestry-framework/src/org/apache/tapestry/parse/DisallowFrameworkNamespaceRule.java
new file mode 100644
index 0000000..b632e5a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/DisallowFrameworkNamespaceRule.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+
+/**
+ * Special purpose rule that simple validates that a library does
+ * not use the reserved framework namespace.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class DisallowFrameworkNamespaceRule extends AbstractSpecificationRule
+{
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String id = getValue(attributes, "id");
+
+ if (id.equals(INamespace.FRAMEWORK_NAMESPACE))
+ throw new DocumentParseException(
+ Tapestry.format(
+ "SpecificationParser.framework-library-id-is-reserved",
+ INamespace.FRAMEWORK_NAMESPACE),
+ getResourceLocation());
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/IDocumentRule.java b/tapestry-framework/src/org/apache/tapestry/parse/IDocumentRule.java
new file mode 100644
index 0000000..b7ebd5f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/IDocumentRule.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.xml.sax.Attributes;
+
+/**
+ * A {@link org.apache.tapestry.parse.SpecificationDigester} rule that executes
+ * at the start and end of the document.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface IDocumentRule
+{
+ public void setDigester(SpecificationDigester digester);
+
+ /**
+ * Invoked at the time the first element in the document is parsed.
+ *
+ * By this time, the publicId will be known.
+ *
+ **/
+
+ public void startDocument(String namespace, String name, Attributes attributes) throws Exception;
+
+ public void endDocument() throws Exception;
+
+ public void finish() throws Exception;
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ITemplateParserDelegate.java b/tapestry-framework/src/org/apache/tapestry/parse/ITemplateParserDelegate.java
new file mode 100644
index 0000000..01cf54c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ITemplateParserDelegate.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * Provides a {@link TemplateParser} with additional information about
+ * dynamic components.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ITemplateParserDelegate
+{
+ /**
+ * Returns true if the component id is valid, false if the
+ * component id is not recognized.
+ *
+ **/
+
+ public boolean getKnownComponent(String componentId);
+
+ /**
+ * Returns true if the specified component allows a body, false
+ * otherwise. The parser uses this information to determine
+ * if it should ignore the body of a tag.
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if no such component exists
+ *
+ **/
+
+ public boolean getAllowBody(String componentId, ILocation location);
+
+ /**
+ * Used with implicit components to determine if the component
+ * allows a body or not.
+ *
+ * @param libraryId the specified library id, possibly null
+ * @param type the component type
+ *
+ * @throws org.apache.tapestry.ApplicationRuntimeException if the specification cannot be found
+ *
+ * @since 3.0
+ *
+ **/
+
+ public boolean getAllowBody(String libraryId, String type, ILocation location);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/InitializePropertyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/InitializePropertyRule.java
new file mode 100644
index 0000000..dc76c82
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/InitializePropertyRule.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Used to initialize a property of an object on the top of the digester stack.
+ * This should come after the {@link org.apache.commons.digester.ObjectCreateRule}
+ * (or variation) and before and property setting for the object. Remember
+ * that rules order matters with the digester.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class InitializePropertyRule extends AbstractSpecificationRule
+{
+ private String _propertyName;
+ private Object _value;
+
+ public InitializePropertyRule(String propertyName, Object value)
+ {
+ _propertyName = propertyName;
+ _value = value;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ setProperty(_propertyName, _value);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/LocalizationToken.java b/tapestry-framework/src/org/apache/tapestry/parse/LocalizationToken.java
new file mode 100644
index 0000000..ba026b9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/LocalizationToken.java
@@ -0,0 +1,87 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.Map;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * Represents localized text from the template.
+ *
+ * @see TokenType#LOCALIZATION
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class LocalizationToken extends TemplateToken
+{
+ private String _tag;
+ private String _key;
+ private boolean _raw;
+ private Map _attributes;
+
+ /**
+ * Creates a new token.
+ *
+ *
+ * @param tag the tag of the element from the template
+ * @param key the localization key specified
+ * @param raw if true, then the localized value contains markup that should not be escaped
+ * @param attributes any additional attributes (beyond those used to define key and raw)
+ * that were specified. This value is retained, not copied.
+ * @param location location of the tag which defines this token
+ *
+ **/
+
+ public LocalizationToken(String tag, String key, boolean raw, Map attributes, ILocation location)
+ {
+ super(TokenType.LOCALIZATION, location);
+
+ _tag = tag;
+ _key = key;
+ _raw = raw;
+ _attributes = attributes;
+ }
+
+ /**
+ * Returns any attributes for the token, which may be null. Do not modify
+ * the return value.
+ *
+ **/
+
+ public Map getAttributes()
+ {
+ return _attributes;
+ }
+
+ public boolean isRaw()
+ {
+ return _raw;
+ }
+
+ public String getTag()
+ {
+ return _tag;
+ }
+
+ public String getKey()
+ {
+ return _key;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/OpenToken.java b/tapestry-framework/src/org/apache/tapestry/parse/OpenToken.java
new file mode 100644
index 0000000..dfec6ff
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/OpenToken.java
@@ -0,0 +1,128 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ILocation;
+
+/**
+ * Token representing the open tag for a component. Components may be either
+ * specified or implicit. Specified components (the traditional type, dating
+ * back to the origin of Tapestry) are matched by an entry in the
+ * containing component's specification. Implicit components specify their
+ * type in the component template and must not have an entry in
+ * the containing component's specification.
+ *
+ * @see TokenType#OPEN
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class OpenToken extends TemplateToken
+{
+ private String _tag;
+ private String _id;
+ private String _componentType;
+ private Map _attributes;
+
+ /**
+ * Creates a new token with the given tag, id and type
+ *
+ * @param tag the template tag which represents the component, typically "span"
+ * @param id the id for the component, which may be assigned by the template
+ * parser for implicit components
+ * @param componentType the type of component, if an implicit component, or null for
+ * a specified component
+ * @param location location of tag represented by this token
+ *
+ **/
+
+ public OpenToken(String tag, String id, String componentType, ILocation location)
+ {
+ super(TokenType.OPEN, location);
+
+ _tag = tag;
+ _id = id;
+ _componentType = componentType;
+ }
+
+ /**
+ * Returns the id for the component.
+ *
+ **/
+
+ public String getId()
+ {
+ return _id;
+ }
+
+ /**
+ * Returns the tag used to represent the component within the template.
+ *
+ **/
+
+ public String getTag()
+ {
+ return _tag;
+ }
+
+ /**
+ * Returns the specified component type, or null for a component where the type
+ * is not defined in the template. The type may include a library id prefix.
+ *
+ **/
+
+ public String getComponentType()
+ {
+ return _componentType;
+ }
+
+ public void addAttribute(String name, AttributeType type, String value)
+ {
+ TemplateAttribute attribute = new TemplateAttribute(type, value);
+
+ if (_attributes == null)
+ _attributes = new HashMap();
+
+ _attributes.put(name, attribute);
+ }
+
+ /**
+ * Returns a Map of attributes. Key is the attribute name, value
+ * is an instance of {@link org.apache.tapestry.parse.TemplateAttribute}.
+ * The caller should not modify the Map. Returns null if
+ * this OpenToken contains no attributes.
+ *
+ **/
+
+ public Map getAttributesMap()
+ {
+ return _attributes;
+ }
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ builder.append("id", _id);
+ builder.append("componentType", _componentType);
+ builder.append("tag", _tag);
+ builder.append("attributes", _attributes);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetBooleanPropertyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetBooleanPropertyRule.java
new file mode 100644
index 0000000..5808b9c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetBooleanPropertyRule.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Sets a boolean property from an attribute of the current element.
+ * The value must be either "yes" or "no" (or not present).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class SetBooleanPropertyRule extends AbstractSpecificationRule
+{
+ private String _attributeName;
+ private String _propertyName;
+
+ public SetBooleanPropertyRule(String attributeName, String propertyName)
+ {
+ _attributeName = attributeName;
+ _propertyName = propertyName;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String attributeValue = getValue(attributes, _attributeName);
+
+ if (attributeValue == null)
+ return;
+
+ Boolean propertyValue = attributeValue.equals("yes") ? Boolean.TRUE : Boolean.FALSE;
+
+ setProperty(_propertyName, propertyValue);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetConvertedPropertyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetConvertedPropertyRule.java
new file mode 100644
index 0000000..6516971
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetConvertedPropertyRule.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.Map;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Rule that applies a conversion of a string value from an attribute into
+ * an object value before assigning it to the property. This is used
+ * to translate values from strings to
+ * {@link org.apache.commons.lang.enum.Enum}s.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class SetConvertedPropertyRule extends AbstractSpecificationRule
+{
+ private Map _map;
+ private String _attributeName;
+ private String _propertyName;
+
+ public SetConvertedPropertyRule(Map map, String attributeName, String propertyName)
+ {
+ _map = map;
+ _attributeName = attributeName;
+ _propertyName = propertyName;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String attributeValue = getValue(attributes, _attributeName);
+ if (attributeValue == null)
+ return;
+
+ Object propertyValue = _map.get(attributeValue);
+
+ // Check for null here?
+
+ setProperty(_propertyName, propertyValue);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetExtendedPropertyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetExtendedPropertyRule.java
new file mode 100644
index 0000000..28964d9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetExtendedPropertyRule.java
@@ -0,0 +1,92 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+
+/**
+ * Sets a property from an extended attribute. An extended attribute
+ * is a value that may either be specified inside an XML attribute or,
+ * if the attribute is not present, in the body of the element.
+ * It is not allowed that the value be specified in both places.
+ * The value may be optional or required.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class SetExtendedPropertyRule extends AbstractSpecificationRule
+{
+ private String _attributeName;
+ private String _propertyName;
+ private boolean _required;
+
+ private boolean _valueSet;
+
+ public SetExtendedPropertyRule(String attributeName, String propertyName, boolean required)
+ {
+ _attributeName = attributeName;
+ _propertyName = propertyName;
+ _required = required;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String value = getValue(attributes, _attributeName);
+
+ if (value != null)
+ {
+ setProperty(_propertyName, value);
+ _valueSet = true;
+ }
+ }
+
+ public void body(String namespace, String name, String text) throws Exception
+ {
+ if (Tapestry.isBlank(text))
+ return;
+
+ if (_valueSet)
+ {
+ throw new DocumentParseException(
+ Tapestry.format(
+ "SpecificationParser.no-attribute-and-body",
+ _attributeName,
+ name),
+ getResourceLocation());
+ }
+
+ setProperty(_propertyName, text.trim());
+ _valueSet = true;
+ }
+
+ public void end(String namespace, String name) throws Exception
+ {
+ if (!_valueSet && _required)
+ throw new DocumentParseException(
+ Tapestry.format(
+ "SpecificationParser.required-extended-attribute",
+ name,
+ _attributeName),
+ getResourceLocation());
+
+ _valueSet = false;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetLimitedPropertiesRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetLimitedPropertiesRule.java
new file mode 100644
index 0000000..4d5ef44
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetLimitedPropertiesRule.java
@@ -0,0 +1,77 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.tapestry.Tapestry;
+import org.xml.sax.Attributes;
+
+/**
+ * Much like {@link org.apache.commons.digester.SetPropertiesRule}, but
+ * only properties that are declared will be copied; other properties
+ * will be ignored.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class SetLimitedPropertiesRule extends AbstractSpecificationRule
+{
+ private String[] _attributeNames;
+ private String[] _propertyNames;
+
+ public SetLimitedPropertiesRule(String attributeName, String propertyName)
+ {
+ this(new String[] { attributeName }, new String[] { propertyName });
+ }
+
+ public SetLimitedPropertiesRule(String[] attributeNames, String[] propertyNames)
+ {
+ _attributeNames = attributeNames;
+ _propertyNames = propertyNames;
+ }
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ Object top = digester.peek();
+
+ int count = attributes.getLength();
+
+ for (int i = 0; i < count; i++)
+ {
+ String attributeName = attributes.getLocalName(i);
+
+ if (Tapestry.isBlank(attributeName))
+ attributeName = attributes.getQName(i);
+
+ for (int x = 0; x < _attributeNames.length; x++)
+ {
+ if (_attributeNames[x].equals(attributeName))
+ {
+ String value = attributes.getValue(i);
+ String propertyName = _propertyNames[x];
+
+ PropertyUtils.setProperty(top, propertyName, value);
+
+ // Terminate inner loop when attribute name is found.
+
+ break;
+ }
+ }
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetMetaPropertyRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetMetaPropertyRule.java
new file mode 100644
index 0000000..47f21e4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetMetaPropertyRule.java
@@ -0,0 +1,78 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.IPropertyHolder;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+
+/**
+ * Handles the <property> element in Tapestry specifications, which is
+ * designed to hold meta-data about specifications.
+ * Expects the top object on the stack to be a {@link org.apache.tapestry.util.IPropertyHolder}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class SetMetaPropertyRule extends AbstractSpecificationRule
+{
+ private String _name;
+ private String _value;
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ _name = getValue(attributes, "name");
+
+ // First, get the value from the attribute, if present
+
+ _value = getValue(attributes, "value");
+
+ }
+
+ public void body(String namespace, String name, String text) throws Exception
+ {
+ if (Tapestry.isBlank(text))
+ return;
+
+ if (_value != null)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.no-attribute-and-body", "value", name),
+ getResourceLocation());
+ }
+
+ _value = text.trim();
+ }
+
+ public void end(String namespace, String name) throws Exception
+ {
+ if (_value == null)
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.required-extended-attribute", name, "value"),
+ getResourceLocation());
+
+ IPropertyHolder holder = (IPropertyHolder) digester.peek();
+
+ holder.setProperty(_name, _value);
+
+ _name = null;
+ _value = null;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SetPublicIdRule.java b/tapestry-framework/src/org/apache/tapestry/parse/SetPublicIdRule.java
new file mode 100644
index 0000000..ae64893
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SetPublicIdRule.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Sets the publicId
+ * property of the parsed specification.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class SetPublicIdRule extends AbstractSpecificationRule
+{
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ setProperty("publicId", digester.getPublicId());
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SpecificationDigester.java b/tapestry-framework/src/org/apache/tapestry/parse/SpecificationDigester.java
new file mode 100644
index 0000000..9847d6a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SpecificationDigester.java
@@ -0,0 +1,300 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.digester.Digester;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.RegexpMatcher;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Extension of {@link org.apache.commons.digester.Digester} with additional rules, hooks
+ * and methods needed when parsing Tapestry specifications.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class SpecificationDigester extends Digester
+{
+ private List _documentRules;
+ private IResourceLocation _resourceLocation;
+ private RegexpMatcher _matcher;
+
+ private ILocation _lastLocation;
+ private int _lastLine;
+ private int _lastColumn;
+
+ /**
+ * Notifications are sent with the very first element.
+ *
+ **/
+
+ private boolean _firstElement = true;
+
+ public void addDocumentRule(IDocumentRule rule)
+ {
+ if (_documentRules == null)
+ _documentRules = new ArrayList();
+
+ rule.setDigester(this);
+
+ _documentRules.add(rule);
+ }
+
+ public void addSetBooleanProperty(String pattern, String attributeName, String propertyName)
+ {
+ addRule(pattern, new SetBooleanPropertyRule(attributeName, propertyName));
+ }
+
+ public void addSetExtendedProperty(
+ String pattern,
+ String attributeName,
+ String propertyName,
+ boolean required)
+ {
+ addRule(pattern, new SetExtendedPropertyRule(attributeName, propertyName, required));
+ }
+
+ public void addValidate(
+ String pattern,
+ String attributeName,
+ String valuePattern,
+ String errorKey)
+ {
+ addRule(pattern, new ValidateRule(getMatcher(), attributeName, valuePattern, errorKey));
+ }
+
+ public void addSetConvertedProperty(
+ String pattern,
+ Map map,
+ String attributeName,
+ String propertyName)
+ {
+ addRule(pattern, new SetConvertedPropertyRule(map, attributeName, propertyName));
+ }
+
+ public void addConnectChild(String pattern, String methodName, String attributeName)
+ {
+ addRule(pattern, new ConnectChildRule(methodName, attributeName));
+ }
+
+ public void addInitializeProperty(String pattern, String propertyName, Object value)
+ {
+ addRule(pattern, new InitializePropertyRule(propertyName, value));
+ }
+
+ public void addSetLimitedProperties(String pattern, String attributeName, String propertyName)
+ {
+ addRule(pattern, new SetLimitedPropertiesRule(attributeName, propertyName));
+ }
+
+ public void addSetLimitedProperties(
+ String pattern,
+ String[] attributeNames,
+ String[] propertyNames)
+ {
+ addRule(pattern, new SetLimitedPropertiesRule(attributeNames, propertyNames));
+ }
+
+ public void addBody(String pattern, String propertyName)
+ {
+ addRule(pattern, new BodyRule(propertyName));
+ }
+
+ /**
+ * Returns the {@link org.xml.sax.Locator} for the Digester. This object
+ * is provided by the underlying SAX parser to identify where in the
+ * document the parse is currently located; this information can be
+ * used to build a {@link org.apache.tapestry.ILocation}.
+ *
+ **/
+
+ public Locator getLocator()
+ {
+ return locator;
+ }
+
+ public ILocation getLocationTag()
+ {
+ int line = -1;
+ int column = -1;
+
+ if (locator != null)
+ {
+ line = locator.getLineNumber();
+ column = locator.getColumnNumber();
+ }
+
+ if (_lastLine != line || _lastColumn != column)
+ _lastLocation = null;
+
+ if (_lastLocation == null)
+ {
+ _lastLine = line;
+ _lastColumn = column;
+ _lastLocation = new Location(_resourceLocation, line, column);
+ }
+
+ return _lastLocation;
+ }
+
+ public IResourceLocation getResourceLocation()
+ {
+ return _resourceLocation;
+ }
+
+ public void setResourceLocation(IResourceLocation resourceLocation)
+ {
+ _resourceLocation = resourceLocation;
+
+ _lastLocation = null;
+ _lastLine = -1;
+ _lastColumn = -1;
+ }
+
+ public RegexpMatcher getMatcher()
+ {
+ if (_matcher == null)
+ _matcher = new RegexpMatcher();
+
+ return _matcher;
+ }
+
+ public void endDocument() throws SAXException
+ {
+ int count = Tapestry.size(_documentRules);
+
+ for (int i = 0; i < count; i++)
+ {
+ IDocumentRule rule = (IDocumentRule) _documentRules.get(i);
+
+ try
+ {
+
+ rule.endDocument();
+ }
+ catch (Exception ex)
+ {
+ throw createSAXException(ex);
+ }
+ }
+
+ super.endDocument();
+
+ for (int i = 0; i < count; i++)
+ {
+ IDocumentRule rule = (IDocumentRule) _documentRules.get(i);
+
+ try
+ {
+ rule.finish();
+ }
+ catch (Exception ex)
+ {
+ throw createSAXException(ex);
+ }
+ }
+ }
+
+ public void startDocument() throws SAXException
+ {
+ _firstElement = true;
+
+ super.startDocument();
+ }
+
+ public void startElement(
+ String namespaceURI,
+ String localName,
+ String qName,
+ Attributes attributes)
+ throws SAXException
+ {
+ if (_firstElement)
+ {
+ sendStartDocumentNotification(namespaceURI, localName, qName, attributes);
+
+ _firstElement = false;
+ }
+
+ super.startElement(namespaceURI, localName, qName, attributes);
+ }
+
+ private void sendStartDocumentNotification(
+ String namespaceURI,
+ String localName,
+ String qName,
+ Attributes attributes)
+ throws SAXException
+ {
+ int count = Tapestry.size(_documentRules);
+
+ String name = Tapestry.isBlank(localName) ? qName : localName;
+
+ for (int i = 0; i < count; i++)
+ {
+ IDocumentRule rule = (IDocumentRule) _documentRules.get(i);
+
+ try
+ {
+ rule.startDocument(namespaceURI, name, attributes);
+ }
+ catch (Exception ex)
+ {
+ throw createSAXException(ex);
+ }
+ }
+
+ }
+
+ /**
+ * Invokes {@link #fatalError(SAXParseException)}.
+ */
+ public void error(SAXParseException exception) throws SAXException
+ {
+ fatalError(exception);
+ }
+
+ /**
+ * Simply re-throws the exception. All exceptions when parsing
+ * documents are fatal.
+ */
+ public void fatalError(SAXParseException exception) throws SAXException
+ {
+ throw exception;
+ }
+
+ /**
+ * Invokes {@link #fatalError(SAXParseException)}.
+ */
+ public void warning(SAXParseException exception) throws SAXException
+ {
+ fatalError(exception);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/SpecificationParser.java b/tapestry-framework/src/org/apache/tapestry/parse/SpecificationParser.java
new file mode 100644
index 0000000..01c9318
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/SpecificationParser.java
@@ -0,0 +1,1295 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.digester.Rule;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.AssetType;
+import org.apache.tapestry.spec.BeanLifecycle;
+import org.apache.tapestry.spec.BindingType;
+import org.apache.tapestry.spec.Direction;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.spec.IExtensionSpecification;
+import org.apache.tapestry.spec.ILibrarySpecification;
+import org.apache.tapestry.spec.SpecFactory;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Used to parse an application or component specification into a
+ * {@link org.apache.tapestry.spec.ApplicationSpecification} or {@link IComponentSpecification}.
+ *
+ *
+ * <table border=1
+ * <tr>
+ * <th>Version</th> <th>PUBLIC ID</th> <th>SYSTEM ID</th> <th>Description</th>
+ * </tr>
+ *
+ *
+ * <tr valign="top">
+ * <td>1.3</td>
+ * <td><code>-//Howard Lewis Ship//Tapestry Specification 1.3//EN</code></td>
+ * <td><code>http://tapestry.sf.net/dtd/Tapestry_1_3.dtd</code></td>
+ * <td>
+ * Version of specification introduced in release 2.2.
+ * </td>
+ * </tr>
+ *
+ * <tr valign="top">
+ * <td>3.0</td>
+ * <td><code>-//Howard Lewis Ship//Tapestry Specification 3.0//EN</code></td>
+ * <td><code>http://tapestry.sf.net/dtd/Tapestry_3_0.dtd</code></td>
+ * <td>
+ * Version of specification introduced in release 3.0.
+ * <br/>
+ * Note: Future DTD versions will track Tapestry release numbers.
+ * </td>
+ * </tr>
+ *
+ *
+ * </table>
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class SpecificationParser
+{
+ private static final Log LOG = LogFactory.getLog(SpecificationParser.class);
+
+ /** @since 2.2 **/
+
+ public static final String TAPESTRY_DTD_1_3_PUBLIC_ID =
+ "-//Howard Lewis Ship//Tapestry Specification 1.3//EN";
+
+ /** @since 3.0 **/
+
+ public static final String TAPESTRY_DTD_3_0_PUBLIC_ID =
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN";
+
+ /**
+ * Like modified property name, but allows periods in the name as
+ * well.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String EXTENDED_PROPERTY_NAME_PATTERN = "^_?[a-zA-Z](\\w|-|\\.)*$";
+
+ /**
+ * Perl5 pattern that parameter names must conform to.
+ * Letter, followed by letter, number or underscore.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String PARAMETER_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern that property names (that can be connected to
+ * parameters) must conform to.
+ * Letter, followed by letter, number or underscore.
+ *
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String PROPERTY_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for page names. Letter
+ * followed by letter, number, dash, underscore or period.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String PAGE_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for component alias.
+ * Letter, followed by letter, number, or underscore.
+ * This is used to validate component types registered
+ * in the application or library specifications.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String COMPONENT_ALIAS_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for helper bean names.
+ * Letter, followed by letter, number or underscore.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String BEAN_NAME_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for component ids. Letter, followed by
+ * letter, number or underscore.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String COMPONENT_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for asset names. Letter, followed by
+ * letter, number or underscore. Also allows
+ * the special "$template" value.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String ASSET_NAME_PATTERN =
+ "(\\$template)|(" + Tapestry.SIMPLE_PROPERTY_NAME_PATTERN + ")";
+
+ /**
+ * Perl5 pattern for service names. Letter
+ * followed by letter, number, dash, underscore or period.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String SERVICE_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for library ids. Letter followed
+ * by letter, number or underscore.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String LIBRARY_ID_PATTERN = Tapestry.SIMPLE_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Per5 pattern for extension names. Letter followed
+ * by letter, number, dash, period or underscore.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String EXTENSION_NAME_PATTERN = EXTENDED_PROPERTY_NAME_PATTERN;
+
+ /**
+ * Perl5 pattern for component types. Component types are an optional
+ * namespace prefix followed by a normal identifier.
+ *
+ * @since 2.2
+ **/
+
+ public static final String COMPONENT_TYPE_PATTERN = "^(_?[a-zA-Z]\\w*:)?[a-zA-Z_](\\w)*$";
+
+ /**
+ * We can share a single map for all the XML attribute to object conversions,
+ * since the keys are unique.
+ *
+ **/
+
+ private static final Map CONVERSION_MAP = new HashMap();
+
+ /** @since 1.0.9 **/
+
+ private SpecFactory _factory;
+
+ /**
+ * Digester used for component specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private SpecificationDigester _componentDigester;
+
+ /**
+ * Digestger used for page specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private SpecificationDigester _pageDigester;
+
+ /**
+ * Digester use for library specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private SpecificationDigester _libraryDigester;
+
+ /**
+ * @since 3.0
+ *
+ **/
+
+ private IResourceResolver _resolver;
+
+ private interface IConverter
+ {
+ public Object convert(String value) throws DocumentParseException;
+ }
+
+ private static class BooleanConverter implements IConverter
+
+ {
+ public Object convert(String value) throws DocumentParseException
+ {
+ Object result = CONVERSION_MAP.get(value.toLowerCase());
+
+ if (result == null || !(result instanceof Boolean))
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.fail-convert-boolean", value));
+
+ return result;
+ }
+ }
+
+ private static class IntConverter implements IConverter
+ {
+ public Object convert(String value) throws DocumentParseException
+ {
+ try
+ {
+ return new Integer(value);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.fail-convert-int", value),
+ ex);
+ }
+ }
+ }
+
+ private static class LongConverter implements IConverter
+ {
+ public Object convert(String value) throws DocumentParseException
+ {
+ try
+ {
+ return new Long(value);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.fail-convert-long", value),
+ ex);
+ }
+ }
+ }
+
+ private static class DoubleConverter implements IConverter
+ {
+ public Object convert(String value) throws DocumentParseException
+ {
+ try
+ {
+ return new Double(value);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.fail-convert-double", value),
+ ex);
+ }
+ }
+ }
+
+ private static class StringConverter implements IConverter
+ {
+ public Object convert(String value)
+ {
+ return value.trim();
+ }
+ }
+
+ /**
+ * Base class for creating locatable objects using the
+ * {@link SpecFactory}.
+ *
+ **/
+
+ private abstract static class SpecFactoryCreateRule extends AbstractSpecificationRule
+ {
+ /**
+ * Implement in subclass to create correct locatable object.
+ *
+ **/
+
+ public abstract ILocationHolder create();
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ ILocationHolder holder = create();
+
+ holder.setLocation(getLocation());
+
+ digester.push(holder);
+ }
+
+ public void end(String namespace, String name) throws Exception
+ {
+ digester.pop();
+ }
+
+ }
+
+ private class CreateExpressionBeanInitializerRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createExpressionBeanInitializer();
+ }
+ }
+
+ private class CreateStringBeanInitializerRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createMessageBeanInitializer();
+ }
+ }
+
+ private class CreateContainedComponentRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createContainedComponent();
+ }
+ }
+
+ private class CreateParameterSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createParameterSpecification();
+ }
+ }
+
+ private class CreateComponentSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createComponentSpecification();
+ }
+ }
+
+ private class CreateBindingSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createBindingSpecification();
+ }
+ }
+
+ private class CreateBeanSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createBeanSpecification();
+ }
+ }
+
+ private class CreateListenerBindingSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createListenerBindingSpecification();
+ }
+ }
+
+ private class CreateAssetSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createAssetSpecification();
+ }
+ }
+
+ private class CreatePropertySpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createPropertySpecification();
+ }
+ }
+
+ private class CreateApplicationSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createApplicationSpecification();
+ }
+ }
+
+ private class CreateLibrarySpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createLibrarySpecification();
+ }
+ }
+
+ private class CreateExtensionSpecificationRule extends SpecFactoryCreateRule
+ {
+ public ILocationHolder create()
+ {
+ return _factory.createExtensionSpecification();
+ }
+ }
+
+ private static class ProcessExtensionConfigurationRule extends AbstractSpecificationRule
+ {
+ private String _value;
+ private String _propertyName;
+ private IConverter _converter;
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ _propertyName = getValue(attributes, "property-name");
+ _value = getValue(attributes, "value");
+
+ String type = getValue(attributes, "type");
+
+ _converter = (IConverter) CONVERSION_MAP.get(type);
+
+ if (_converter == null)
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.unknown-static-value-type", type),
+ getResourceLocation());
+
+ }
+
+ public void body(String namespace, String name, String text) throws Exception
+ {
+ if (Tapestry.isBlank(text))
+ return;
+
+ if (_value != null)
+ throw new DocumentParseException(
+ Tapestry.format("SpecificationParser.no-attribute-and-body", "value", name),
+ getResourceLocation());
+
+ _value = text.trim();
+ }
+
+ public void end(String namespace, String name) throws Exception
+ {
+ if (_value == null)
+ throw new DocumentParseException(
+ Tapestry.format(
+ "SpecificationParser.required-extended-attribute",
+ name,
+ "value"),
+ getResourceLocation());
+
+ Object objectValue = _converter.convert(_value);
+
+ IExtensionSpecification top = (IExtensionSpecification) digester.peek();
+
+ top.addConfiguration(_propertyName, objectValue);
+
+ _converter = null;
+ _value = null;
+ _propertyName = null;
+
+ }
+
+ }
+
+ // Identify all the different acceptible values.
+ // We continue to sneak by with a single map because
+ // there aren't conflicts; when we have 'foo' meaning
+ // different things in different places in the DTD, we'll
+ // need multiple maps.
+
+ static {
+
+ CONVERSION_MAP.put("true", Boolean.TRUE);
+ CONVERSION_MAP.put("t", Boolean.TRUE);
+ CONVERSION_MAP.put("1", Boolean.TRUE);
+ CONVERSION_MAP.put("y", Boolean.TRUE);
+ CONVERSION_MAP.put("yes", Boolean.TRUE);
+ CONVERSION_MAP.put("on", Boolean.TRUE);
+
+ CONVERSION_MAP.put("false", Boolean.FALSE);
+ CONVERSION_MAP.put("f", Boolean.FALSE);
+ CONVERSION_MAP.put("0", Boolean.FALSE);
+ CONVERSION_MAP.put("off", Boolean.FALSE);
+ CONVERSION_MAP.put("no", Boolean.FALSE);
+ CONVERSION_MAP.put("n", Boolean.FALSE);
+
+ CONVERSION_MAP.put("none", BeanLifecycle.NONE);
+ CONVERSION_MAP.put("request", BeanLifecycle.REQUEST);
+ CONVERSION_MAP.put("page", BeanLifecycle.PAGE);
+ CONVERSION_MAP.put("render", BeanLifecycle.RENDER);
+
+ CONVERSION_MAP.put("boolean", new BooleanConverter());
+ CONVERSION_MAP.put("int", new IntConverter());
+ CONVERSION_MAP.put("double", new DoubleConverter());
+ CONVERSION_MAP.put("String", new StringConverter());
+ CONVERSION_MAP.put("long", new LongConverter());
+
+ CONVERSION_MAP.put("in", Direction.IN);
+ CONVERSION_MAP.put("form", Direction.FORM);
+ CONVERSION_MAP.put("custom", Direction.CUSTOM);
+ CONVERSION_MAP.put("auto", Direction.AUTO);
+ }
+
+ public SpecificationParser(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ setFactory(new SpecFactory());
+ }
+
+ /**
+ * Parses an input stream containing a page or component specification and assembles
+ * an {@link IComponentSpecification} from it.
+ *
+ * @throws DocumentParseException if the input stream cannot be fully
+ * parsed or contains invalid data.
+ *
+ **/
+
+ public IComponentSpecification parseComponentSpecification(IResourceLocation resourceLocation)
+ throws DocumentParseException
+ {
+ if (_componentDigester == null)
+ _componentDigester = constructComponentDigester();
+
+ try
+ {
+ IComponentSpecification result =
+ (IComponentSpecification) parse(_componentDigester, resourceLocation);
+
+ result.setSpecificationLocation(resourceLocation);
+
+ return result;
+ }
+ catch (DocumentParseException ex)
+ {
+ _componentDigester = null;
+
+ throw ex;
+ }
+ }
+
+ /**
+ * Parses a resource using a particular digester.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected Object parse(SpecificationDigester digester, IResourceLocation location)
+ throws DocumentParseException
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsing " + location);
+
+ URL url = location.getResourceURL();
+
+ if (url == null)
+ throw new DocumentParseException(
+ Tapestry.format("AbstractDocumentParser.missing-resource", location),
+ location);
+
+ InputSource source = new InputSource(url.toExternalForm());
+
+ digester.setResourceLocation(location);
+
+ Object result = digester.parse(source);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Result: " + result);
+
+ return result;
+ }
+ catch (SAXParseException ex)
+ {
+ throw new DocumentParseException(ex);
+ }
+ catch (DocumentParseException ex)
+ {
+ throw ex;
+ }
+ catch (Exception ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format(
+ "SpecificationParser.error-reading-resource",
+ location,
+ ex.getMessage()),
+ location,
+ ex);
+ }
+ finally
+ {
+ digester.setResourceLocation(null);
+ }
+ }
+
+ /**
+ * Parses an input stream containing a page specification and assembles
+ * an {@link IComponentSpecification} from it.
+ *
+ * @throws DocumentParseException if the input stream cannot be fully
+ * parsed or contains invalid data.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public IComponentSpecification parsePageSpecification(IResourceLocation resourceLocation)
+ throws DocumentParseException
+ {
+ if (_pageDigester == null)
+ _pageDigester = constructPageDigester();
+
+ try
+ {
+ IComponentSpecification result =
+ (IComponentSpecification) parse(_pageDigester, resourceLocation);
+
+ result.setSpecificationLocation(resourceLocation);
+
+ return result;
+ }
+ catch (DocumentParseException ex)
+ {
+ _pageDigester = null;
+
+ throw ex;
+ }
+ }
+
+ /**
+ * Parses an resource containing an application specification and assembles
+ * an {@link org.apache.tapestry.spec.ApplicationSpecification} from it.
+ *
+ * @throws DocumentParseException if the input stream cannot be fully
+ * parsed or contains invalid data.
+ *
+ **/
+
+ public IApplicationSpecification parseApplicationSpecification(IResourceLocation resourceLocation)
+ throws DocumentParseException
+ {
+
+ // Use a one-shot digester, because you only parse the app spec
+ // once.
+
+ IApplicationSpecification result =
+ (IApplicationSpecification) parse(constructApplicationDigester(), resourceLocation);
+
+ result.setResourceResolver(_resolver);
+ result.setSpecificationLocation(resourceLocation);
+ result.instantiateImmediateExtensions();
+
+ return result;
+ }
+
+ /**
+ * Parses an input stream containing a library specification and assembles
+ * a {@link org.apache.tapestry.spec.LibrarySpecification} from it.
+ *
+ * @throws DocumentParseException if the input stream cannot be fully
+ * parsed or contains invalid data.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public ILibrarySpecification parseLibrarySpecification(IResourceLocation resourceLocation)
+ throws DocumentParseException
+ {
+ if (_libraryDigester == null)
+ _libraryDigester = constructLibraryDigester();
+
+ try
+ {
+ ILibrarySpecification result =
+ (ILibrarySpecification) parse(_libraryDigester, resourceLocation);
+
+ result.setResourceResolver(_resolver);
+ result.setSpecificationLocation(resourceLocation);
+ result.instantiateImmediateExtensions();
+
+ return result;
+ }
+ catch (DocumentParseException ex)
+ {
+ _libraryDigester = null;
+
+ throw ex;
+ }
+ }
+
+ /**
+ * Sets the SpecFactory which instantiates Tapestry spec objects.
+ *
+ * @since 1.0.9
+ **/
+
+ public void setFactory(SpecFactory factory)
+ {
+ _factory = factory;
+ }
+
+ /**
+ * Returns the current SpecFactory which instantiates Tapestry spec objects.
+ *
+ * @since 1.0.9
+ *
+ **/
+
+ public SpecFactory getFactory()
+ {
+ return _factory;
+ }
+
+ /**
+ * Constructs a digester, registerring the known DTDs and the
+ * global rules (for <property> and <description>).
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected SpecificationDigester constructBaseDigester(String rootElement)
+ {
+ SpecificationDigester result = new SpecificationDigester();
+
+ // <description>
+
+ result.addBeanPropertySetter("*/description", "description");
+
+ // <property>
+
+ result.addRule("*/property", new SetMetaPropertyRule());
+
+ result.register(TAPESTRY_DTD_1_3_PUBLIC_ID, getURL("Tapestry_1_3.dtd"));
+ result.register(TAPESTRY_DTD_3_0_PUBLIC_ID, getURL("Tapestry_3_0.dtd"));
+
+ result.addDocumentRule(
+ new ValidatePublicIdRule(
+ new String[] { TAPESTRY_DTD_1_3_PUBLIC_ID, TAPESTRY_DTD_3_0_PUBLIC_ID },
+ rootElement));
+
+ result.setValidating(true);
+
+ return result;
+
+ }
+
+ /**
+ * Constructs a digester configued to parse application specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected SpecificationDigester constructApplicationDigester()
+ {
+ SpecificationDigester result = constructBaseDigester("application");
+
+ String pattern = "application";
+
+ result.addRule(pattern, new CreateApplicationSpecificationRule());
+ result.addSetLimitedProperties(
+ pattern,
+ new String[] { "name", "engine-class" },
+ new String[] { "name", "engineClassName" });
+ result.addRule(pattern, new SetPublicIdRule());
+
+ configureLibraryCommon(result, "application");
+
+ return result;
+ }
+
+ /**
+ * Constructs a digester configured to parse library specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected SpecificationDigester constructLibraryDigester()
+ {
+ SpecificationDigester result = constructBaseDigester("library-specification");
+
+ String pattern = "library-specification";
+
+ result.addRule(pattern, new CreateLibrarySpecificationRule());
+ result.addRule(pattern, new SetPublicIdRule());
+
+ // Has no attributes
+
+ configureLibraryCommon(result, "library-specification");
+
+ return result;
+ }
+
+ /**
+ * Configures a digester to parse the common elements of
+ * a <application> or <library-specification>.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void configureLibraryCommon(SpecificationDigester digester, String rootElementName)
+ {
+ String pattern = rootElementName + "/page";
+
+ // <page>
+
+ digester.addValidate(
+ pattern,
+ "name",
+ PAGE_NAME_PATTERN,
+ "SpecificationParser.invalid-page-name");
+ digester.addCallMethod(pattern, "setPageSpecificationPath", 2);
+ digester.addCallParam(pattern, 0, "name");
+ digester.addCallParam(pattern, 1, "specification-path");
+
+ // <component-type>
+
+ pattern = rootElementName + "/component-type";
+ digester.addValidate(
+ pattern,
+ "type",
+ COMPONENT_ALIAS_PATTERN,
+ "SpecificationParser.invalid-component-type");
+ digester.addCallMethod(pattern, "setComponentSpecificationPath", 2);
+ digester.addCallParam(pattern, 0, "type");
+ digester.addCallParam(pattern, 1, "specification-path");
+
+ // <component-alias>
+ // From 1.3 DTD, replaced with <component-type> in 3.0 DTD
+
+ pattern = rootElementName + "/component-alias";
+ digester.addValidate(
+ pattern,
+ "type",
+ COMPONENT_ALIAS_PATTERN,
+ "SpecificationParser.invalid-component-type");
+ digester.addCallMethod(pattern, "setComponentSpecificationPath", 2);
+ digester.addCallParam(pattern, 0, "type");
+ digester.addCallParam(pattern, 1, "specification-path");
+
+ // <service>
+
+ pattern = rootElementName + "/service";
+
+ digester.addValidate(
+ pattern,
+ "name",
+ SERVICE_NAME_PATTERN,
+ "SpecificationParser.invalid-service-name");
+ digester.addCallMethod(pattern, "setServiceClassName", 2);
+ digester.addCallParam(pattern, 0, "name");
+ digester.addCallParam(pattern, 1, "class");
+
+ // <library>
+
+ pattern = rootElementName + "/library";
+
+ digester.addValidate(
+ pattern,
+ "id",
+ LIBRARY_ID_PATTERN,
+ "SpecificationParser.invalid-library-id");
+ digester.addRule(pattern, new DisallowFrameworkNamespaceRule());
+ digester.addCallMethod(pattern, "setLibrarySpecificationPath", 2);
+ digester.addCallParam(pattern, 0, "id");
+ digester.addCallParam(pattern, 1, "specification-path");
+
+ // <extension>
+
+ pattern = rootElementName + "/extension";
+
+ digester.addRule(pattern, new CreateExtensionSpecificationRule());
+ digester.addValidate(
+ pattern,
+ "name",
+ EXTENSION_NAME_PATTERN,
+ "SpecificationParser.invalid-extension-name");
+ digester.addSetBooleanProperty(pattern, "immediate", "immediate");
+ digester.addSetLimitedProperties(pattern, "class", "className");
+ digester.addConnectChild(pattern, "addExtensionSpecification", "name");
+
+ // <configure> within <extension>
+
+ pattern = rootElementName + "/extension/configure";
+ digester.addValidate(
+ pattern,
+ "property-name",
+ PROPERTY_NAME_PATTERN,
+ "SpecificationParser.invalid-property-name");
+ digester.addRule(pattern, new ProcessExtensionConfigurationRule());
+
+ }
+
+ /**
+ * Returns a digester configured to parse page specifications.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected SpecificationDigester constructPageDigester()
+ {
+ SpecificationDigester result = constructBaseDigester("page-specification");
+
+ // <page-specification>
+
+ String pattern = "page-specification";
+
+ result.addRule(pattern, new CreateComponentSpecificationRule());
+ result.addRule(pattern, new SetPublicIdRule());
+ result.addInitializeProperty(pattern, "pageSpecification", Boolean.TRUE);
+ result.addInitializeProperty(pattern, "allowBody", Boolean.TRUE);
+ result.addInitializeProperty(pattern, "allowInformalParameters", Boolean.FALSE);
+ result.addSetLimitedProperties(pattern, "class", "componentClassName");
+
+ configureCommon(result, "page-specification");
+
+ return result;
+ }
+
+ /**
+ * Returns a digester configured to parse component specifications.
+ *
+ **/
+
+ protected SpecificationDigester constructComponentDigester()
+ {
+ SpecificationDigester result = constructBaseDigester("component-specification");
+
+ // <component-specification>
+
+ String pattern = "component-specification";
+
+ result.addRule(pattern, new CreateComponentSpecificationRule());
+ result.addRule(pattern, new SetPublicIdRule());
+
+ result.addSetBooleanProperty(pattern, "allow-body", "allowBody");
+ result.addSetBooleanProperty(
+ pattern,
+ "allow-informal-parameters",
+ "allowInformalParameters");
+ result.addSetLimitedProperties(pattern, "class", "componentClassName");
+
+ // TODO: publicId
+
+ // <parameter>
+
+ pattern = "component-specification/parameter";
+
+ result.addRule(pattern, new CreateParameterSpecificationRule());
+ result.addValidate(
+ pattern,
+ "name",
+ PARAMETER_NAME_PATTERN,
+ "SpecificationParser.invalid-parameter-name");
+
+ result.addValidate(
+ pattern,
+ "property-name",
+ PROPERTY_NAME_PATTERN,
+ "SpecificationParser.invalid-property-name");
+
+ // We use a slight kludge to set the default propertyName from the
+ // name attribute. If the spec includes a property-name attribute, that
+ // will overwrite the default property name).
+ // Remember that digester rule order counts!
+
+ result.addSetLimitedProperties(pattern, "name", "propertyName");
+
+ // java-type is a holdover from the 1.3 DTD and will eventually be removed.
+
+ result.addSetLimitedProperties(
+ pattern,
+ new String[] { "property-name", "type", "java-type", "default-value" },
+ new String[] { "propertyName", "type", "type", "defaultValue" });
+
+ result.addSetBooleanProperty(pattern, "required", "required");
+
+ result.addSetConvertedProperty(pattern, CONVERSION_MAP, "direction", "direction");
+
+ result.addConnectChild(pattern, "addParameter", "name");
+
+ // <reserved-parameter>
+
+ pattern = "component-specification/reserved-parameter";
+
+ result.addCallMethod(pattern, "addReservedParameterName", 1);
+ result.addCallParam(pattern, 0, "name");
+
+ configureCommon(result, "component-specification");
+
+ return result;
+ }
+
+ /**
+ * Configure the common elements shared by both <page-specification>
+ * and <component-specification>.
+ *
+ **/
+
+ protected void configureCommon(SpecificationDigester digester, String rootElementName)
+ {
+ // <bean>
+
+ String pattern = rootElementName + "/bean";
+
+ digester.addRule(pattern, new CreateBeanSpecificationRule());
+ digester.addValidate(
+ pattern,
+ "name",
+ BEAN_NAME_PATTERN,
+ "SpecificationParser.invalid-bean-name");
+ digester.addSetConvertedProperty(pattern, CONVERSION_MAP, "lifecycle", "lifecycle");
+ digester.addSetLimitedProperties(pattern, "class", "className");
+ digester.addConnectChild(pattern, "addBeanSpecification", "name");
+
+ // <set-property> inside <bean>
+
+ pattern = rootElementName + "/bean/set-property";
+
+ digester.addRule(pattern, new CreateExpressionBeanInitializerRule());
+ digester.addSetLimitedProperties(pattern, "name", "propertyName");
+ digester.addSetExtendedProperty(pattern, "expression", "expression", true);
+ digester.addSetNext(pattern, "addInitializer");
+
+ // <set-string-property> inside <bean>
+ // This is for compatibility with the 1.3 DTD
+
+ pattern = rootElementName + "/bean/set-string-property";
+
+ digester.addRule(pattern, new CreateStringBeanInitializerRule());
+ digester.addSetLimitedProperties(
+ pattern,
+ new String[] { "name", "key" },
+ new String[] { "propertyName", "key" });
+
+ digester.addSetNext(pattern, "addInitializer");
+
+ // It's now set-message-property in the 3.0 DTD
+
+ pattern = rootElementName + "/bean/set-message-property";
+
+ digester.addRule(pattern, new CreateStringBeanInitializerRule());
+ digester.addSetLimitedProperties(
+ pattern,
+ new String[] { "name", "key" },
+ new String[] { "propertyName", "key" });
+
+ digester.addSetNext(pattern, "addInitializer");
+
+ // <component>
+
+ pattern = rootElementName + "/component";
+
+ digester.addRule(pattern, new CreateContainedComponentRule());
+ digester.addValidate(
+ pattern,
+ "id",
+ COMPONENT_ID_PATTERN,
+ "SpecificationParser.invalid-component-id");
+ digester.addValidate(
+ pattern,
+ "type",
+ COMPONENT_TYPE_PATTERN,
+ "SpecificationParser.invalid-component-type");
+ digester.addSetLimitedProperties(pattern, "type", "type");
+ digester.addRule(pattern, new ComponentCopyOfRule());
+ digester.addConnectChild(pattern, "addComponent", "id");
+ digester.addSetBooleanProperty(
+ pattern,
+ "inherit-informal-parameters",
+ "inheritInformalParameters");
+
+ // <binding> inside <component>
+
+ pattern = rootElementName + "/component/binding";
+
+ Rule createBindingSpecificationRule = new CreateBindingSpecificationRule();
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.DYNAMIC);
+ digester.addSetExtendedProperty(pattern, "expression", "value", true);
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <field-binding> inside <component>
+ // For compatibility with 1.3 DTD only, removed in 3.0 DTD
+
+ pattern = rootElementName + "/component/field-binding";
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.FIELD);
+ digester.addSetExtendedProperty(pattern, "field-name", "value", true);
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <inherited-binding> inside <component>
+
+ pattern = rootElementName + "/component/inherited-binding";
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.INHERITED);
+ digester.addSetLimitedProperties(pattern, "parameter-name", "value");
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <static-binding> inside <component>
+
+ pattern = rootElementName + "/component/static-binding";
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.STATIC);
+ digester.addSetExtendedProperty(pattern, "value", "value", true);
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <string-binding> inside <component>
+ // Maintained just for 1.3 DTD compatibility
+
+ pattern = rootElementName + "/component/string-binding";
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.STRING);
+ digester.addSetLimitedProperties(pattern, "key", "value");
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // Renamed to <message-binding> in the 3.0 DTD
+
+ pattern = rootElementName + "/component/message-binding";
+
+ digester.addRule(pattern, createBindingSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", BindingType.STRING);
+ digester.addSetLimitedProperties(pattern, "key", "value");
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <listener-binding> inside <component>
+
+ pattern = rootElementName + "/component/listener-binding";
+
+ digester.addRule(pattern, new CreateListenerBindingSpecificationRule());
+ digester.addSetLimitedProperties(pattern, "language", "language");
+ digester.addBody(pattern, "value");
+ digester.addConnectChild(pattern, "setBinding", "name");
+
+ // <external-asset>
+
+ pattern = rootElementName + "/external-asset";
+
+ Rule createAssetSpecificationRule = new CreateAssetSpecificationRule();
+
+ digester.addRule(pattern, createAssetSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", AssetType.EXTERNAL);
+ digester.addValidate(
+ pattern,
+ "name",
+ ASSET_NAME_PATTERN,
+ "SpecificationParser.invalid-asset-name");
+ digester.addSetLimitedProperties(pattern, "URL", "path");
+ digester.addConnectChild(pattern, "addAsset", "name");
+
+ // <context-asset>
+
+ pattern = rootElementName + "/context-asset";
+
+ digester.addRule(pattern, createAssetSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", AssetType.CONTEXT);
+ digester.addValidate(
+ pattern,
+ "name",
+ ASSET_NAME_PATTERN,
+ "SpecificationParser.invalid-asset-name");
+
+ // TODO: $template$
+
+ digester.addSetLimitedProperties(pattern, "path", "path");
+ digester.addConnectChild(pattern, "addAsset", "name");
+
+ // <private-asset>
+
+ pattern = rootElementName + "/private-asset";
+
+ digester.addRule(pattern, createAssetSpecificationRule);
+ digester.addInitializeProperty(pattern, "type", AssetType.PRIVATE);
+ digester.addValidate(
+ pattern,
+ "name",
+ ASSET_NAME_PATTERN,
+ "SpecificationParser.invalid-asset-name");
+
+ // TODO: $template$
+
+ digester.addSetLimitedProperties(pattern, "resource-path", "path");
+ digester.addConnectChild(pattern, "addAsset", "name");
+
+ // <property-specification>
+
+ pattern = rootElementName + "/property-specification";
+
+ digester.addRule(pattern, new CreatePropertySpecificationRule());
+ digester.addValidate(
+ pattern,
+ "name",
+ PROPERTY_NAME_PATTERN,
+ "SpecificationParser.invalid-property-name");
+ digester.addSetLimitedProperties(
+ pattern,
+ new String[] { "name", "type" },
+ new String[] { "name", "type" });
+ digester.addSetBooleanProperty(pattern, "persistent", "persistent");
+ digester.addSetExtendedProperty(pattern, "initial-value", "initialValue", false);
+ digester.addSetNext(pattern, "addPropertySpecification");
+ }
+
+ private String getURL(String resource)
+ {
+ return getClass().getResource(resource).toExternalForm();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_1_3.dtd b/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_1_3.dtd
new file mode 100644
index 0000000..2a2fd14
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_1_3.dtd
@@ -0,0 +1,549 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Tapestry_1_3.dtd,v 1.18 2002/09/19 17:25:28 hship Exp $ -->
+<!--
+
+The DTD for Tapestry application, page and component specifications.
+Associated with the public identifier:
+
+ -//Howard Lewis Ship//Tapestry Specification 1.3//EN
+
+The canonical location for the DTD is:
+
+ http://tapestry.sf.net/dtd/Tapestry_1_3.dtd
+
+For application specifications, the root element is application.
+
+For component specifications, the root element is component-specification.
+
+For page specifications, the root element is page-specification.
+
+For library specifiations, the root element is library-specification.
+
+This DTD is backwards compatible with the 1.2 DTD, with the following exceptions:
+- specification (in 1.1) has been split into page-specification and component-specification
+- added string-value element
+- added library-specification root element
+- added library element
+- added extension element
+- added static value type 'long'
+- allow <property> within <bean>, <component>, <extension>, <private-asset>, <context-asset> and <external-asset>
+- add "render" to bean lifecycle value
+- rename <binding>'s property-path attribute to expression
+- simplify set-property to use OGNL expressions
+- add "form" as parameter direction
+-->
+
+<!-- =======================================================
+Entity: attribute-flag
+
+For entity attributesthat take a boolean value, defines 'yes' and 'no'.
+The default varies, so isn't included here.
+-->
+<!ENTITY % attribute-flag "(yes|no)">
+
+
+<!-- =======================================================
+Entity: static-value-type
+
+For entity attributes that take a string but convert it to a real
+type. Defaults to String.
+
+-->
+<!ENTITY % static-value-type "(boolean|int|long|double|String) 'String'">
+
+<!ENTITY % library-content "(description*, property*, (page|component-alias|service|library|extension)*)">
+
+<!-- =======================================================
+Element: application
+Root element
+
+Defines a Tapestry application.
+
+Attributes:
+ name: A textual name used to uniquely identify the application.
+ engine-class: The Java class to instantiate as the application engine.
+-->
+<!ELEMENT application %library-content;>
+<!ATTLIST application
+ name CDATA #REQUIRED
+ engine-class CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: bean
+Appears in: component-specification, page-specification
+
+Defines a JavaBean that will be used in some way by the component. Beans
+are accessible via the components' beans property (which contains a property
+for each bean). Beans are created as needed, and are discarded based on
+the lifecycle attribute. Beans are typically used to extend the
+implementation of a component via aggregation.
+
+Attributes:
+ name: the name of the bean
+ class: the Java class to instantiate
+ lifecycle: when the reference to the bean should be discard
+ "none" no lifecycle, the bean is created and returned, but not stored
+ "request" the bean is retained until the end of the request cycle
+ "page" the bean is retained for the lifespan of the page
+ "render" the bean is retained until the end of the current page render
+
+Nothing prevents a bean for storing state; however, such state will
+not be associated with any particular session (unlike persistant page
+properties). Further, because of page pooling, subsequent requests
+from the same client may be serviced by different instances of a page and
+(therefore) different bean instances.
+
+Beans that have the "request" lifecycle may be stored into a pool
+for later re-use (in the same or different page).
+
+The bean may have its properties set. Properties are set on both
+newly instantiated beans, and beans that are retrieved from a pool.
+
+-->
+<!ELEMENT bean (description*, property*, (set-property | set-string-property)*)>
+<!ATTLIST bean
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+ lifecycle (none|request|page|render) "request"
+>
+
+<!-- =======================================================
+Element: binding
+Appears in: component
+
+Binds a parameter of the component to a property of its container.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ expression: The OGNL expression.
+-->
+<!ELEMENT binding EMPTY>
+<!ATTLIST binding
+ name CDATA #REQUIRED
+ expression CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: configure
+Appears in: extension
+
+Configures one JavaBean property of an extension.
+
+Attributes:
+ property-name: The name of the property to configure.
+ type: Conversion of property value.
+
+
+-->
+
+<!ELEMENT configure (#PCDATA)>
+<!ATTLIST configure
+ property-name CDATA #REQUIRED
+ type %static-value-type;
+>
+
+<!-- =======================================================
+Element: component
+Contained by: component-specification, page-specification
+
+Defines a component contained by the component being specified.
+
+Attribute:
+ id: A unique identifier for the component within the container.
+ type: The type of component, either a well known logical name, or the complete path
+ to the component's specification.
+ copy-of: The id of a previously defined component; this component will be a copy
+ of the other component.
+
+The Tapestry page loader ensures that either type or copy-of is specified, but not both.
+-->
+<!ELEMENT component (property*, (binding | field-binding | inherited-binding | static-binding | string-binding)*)>
+<!ATTLIST component
+ id ID #REQUIRED
+ type CDATA #IMPLIED
+ copy-of IDREF #IMPLIED
+>
+
+<!-- =======================================================
+Element: component-alias
+Contained by: application
+
+Establishes a short logic name for a particular component that is used
+within the application.
+
+Attributes:
+ type: The logical name for the component.
+ specification-path: The complete path to the component's specification.
+-->
+<!ELEMENT component-alias EMPTY>
+<!ATTLIST component-alias
+ type CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: component-specification
+Root element
+
+A component specification. It's attributes define the Java class to
+instantiate, whether the component may wrap other elements, and whether
+informal (undeclared) parameters are allowed. Very similar to a page-specification,
+except that component-specification allows for parameters (formal and informal).
+
+Attributes:
+ class: The Java class to instantiate for the component.
+ allow-body: If yes (the default), the component may wrap other elements (have a body).
+ allow-informal-parameters: If yes (the default), informal parameters (parameters that are not
+ explictily defined) are allowed.
+-->
+<!ELEMENT component-specification
+ (description*, parameter*, reserved-parameter*, property*,
+ (bean | component | external-asset | context-asset | private-asset)*)>
+<!ATTLIST component-specification
+ class CDATA #REQUIRED
+ allow-body %attribute-flag; "yes"
+ allow-informal-parameters %attribute-flag; "yes"
+>
+
+<!-- =======================================================
+Element: context-asset
+Contained by: component-specification, page-specification
+
+An asset located in the same web application context as the running
+application.
+
+Attributes:
+ name: The name of the asset.
+ path: The path, relative to the web application context, of the resource.
+-->
+<!ELEMENT context-asset (property*)>
+<!ATTLIST context-asset
+ name CDATA #REQUIRED
+ path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: description
+Appears in: many
+
+Several elements may contain descriptions; these descriptions are
+optional. The eventual goal is to provide help in some form of IDE.
+Currently, descriptions are optional and ignored.
+
+Attributes:
+ xml:lang the language that the description is expressed in.
+-->
+<!ELEMENT description (#PCDATA)>
+<!ATTLIST description
+ xml:lang NMTOKEN "en"
+>
+
+<!-- =======================================================
+Element: extension
+Contained by: application, library-specification
+
+Defines an extension, an object that is instantiated and configured
+(like a helper bean) and is then accessible, by name, from the
+containing library (or application).
+
+Attributes:
+ name: Name of the extension.
+ class: Java class to instantiate.
+ immediate: If true, the extension is instantiated early instead of as-needed.
+
+-->
+<!ELEMENT extension (property*, configure*)>
+<!ATTLIST extension
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+ immediate %attribute-flag; "no"
+>
+
+<!-- =======================================================
+Element: external-asset
+Contained by: component-specification, page-specification
+
+Defines an asset at some external source.
+
+Attributes:
+ name: The name of the asset.
+ URL: The URL used to reference the asset.
+-->
+<!ELEMENT external-asset (property*)>
+<!ATTLIST external-asset
+ name CDATA #REQUIRED
+ URL CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: field-binding
+Appears in: component
+
+Binds a parameter of the component to a public static field of
+some Java object.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ field-name: The name of the field, of the form [package.]class.field.
+ The package may be ommitted if it is "java.lang".
+-->
+<!ELEMENT field-binding EMPTY>
+<!ATTLIST field-binding
+ name CDATA #REQUIRED
+ field-name CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: inherited-binding
+Appears in: component
+
+Binds a parameter of the component to a parameter of the container.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ parameter-name: The name of the container parameter to bind the
+ component parameter to.
+-->
+<!ELEMENT inherited-binding EMPTY>
+<!ATTLIST inherited-binding
+ name CDATA #REQUIRED
+ parameter-name CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: library
+Appears in: application-specification, library-specification
+
+Defines a library used in the construction of the container
+(either another library, or the application itself).
+
+Attributes:
+ id: An identifier used to reference pages and components
+ provided by the library.
+ specification-path: The path to the resource that provides
+ the library specification.
+-->
+<!ELEMENT library EMPTY>
+<!ATTLIST library
+ id CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: library-specification
+Root element
+
+Defines a library that may be used in the construction
+of an application (or another library). An application can
+be thought of as a specialized kind of library.
+
+-->
+<!ELEMENT library-specification %library-content;>
+
+<!-- =======================================================
+Element: page
+Contained by: application, library-specification
+
+Defines a single page within the application. Each application will contain
+at least one of these, to define the Home page.
+
+Attributes:
+ name: A unique name for the application.
+ specification-path: The resource classpath of the component specification
+ for the page.
+-->
+<!ELEMENT page EMPTY>
+<!ATTLIST page
+ name CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: page-specification
+Root element
+
+A page specification. It's attributes define the Java class to
+instantiate. Pages are like components, except they always allow
+a body, and never allow parameters (formal or otherwise).
+
+Attributes:
+ class: The Java class to instantiate for the component.
+-->
+<!ELEMENT page-specification (description*, property*,
+ (bean | component | external-asset | context-asset | private-asset)*)>
+<!ATTLIST page-specification
+ class CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: parameter
+Contained by: component-specification
+
+Defines a formal parameter for the component.
+
+Attributes:
+ name: A unqiue name for the parameter.
+ java-type: The name of a Java class or primitive type expected by the parameter.
+ This is for documentation purposes only, it is not enforced.
+ required: If yes, then the parameter must be bound. If no (the default),
+ then the parameter is optional.
+ property-name: The name to use, instead of the parameter name, for the
+ JavaBean property connected to this parameter.
+ direction: The normal flow of data through the component.
+-->
+
+<!ELEMENT parameter (description*)>
+<!ATTLIST parameter
+ name CDATA #REQUIRED
+ java-type CDATA #IMPLIED
+ required %attribute-flag; "no"
+ property-name CDATA #IMPLIED
+ direction (in|form|custom) "custom"
+>
+
+<!-- =======================================================
+Element: private-asset
+Contained by: component-specification, page-specification
+
+An asset available within the Java classpath (i.e., bundled inside a JAR or WAR).
+
+Attributes:
+ name: The name of the asset.
+ resource-path: The complete pathname of the resource.
+-->
+<!ELEMENT private-asset (property*)>
+<!ATTLIST private-asset
+ name CDATA #REQUIRED
+ resource-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: property
+Contained by: (many other elements)
+
+Defines a key/value pair associated with the application or component specification. Properties
+are used to capture information that doesn't fit into the DTD. The value for the property is
+the PCDATA wrapped by the property tag (which is trimmed of leading and trailing whitespace).
+
+This should not be confused with several other tags which are used to set JavaBeans properties
+of various objects. The <property> tag exists to allow meta-data to be stored in the specification.
+
+Attributes:
+ name: The name of the property to set.
+
+-->
+<!ELEMENT property (#PCDATA)>
+<!ATTLIST property
+ name CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: reserved-parameter
+Appears in: component-specification
+
+Identifies a name which may not be used as an informal parameter.
+Informal parameters are typically HTML attribute names; this
+list identifies HTML attributes that are written exclusively
+by the component and may not be affected by informal parameters.
+
+Attributes:
+ name: The parameter name to reserve.
+-->
+
+<!ELEMENT reserved-parameter EMPTY>
+<!ATTLIST reserved-parameter
+ name CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: service
+Appears in: application
+
+Defines an engine service. You may override the default
+set of engine services or provide completely new services.
+
+Attributes:
+ name: The name of the service.
+ class: The Java class to instantiate for the service.
+
+-->
+
+<!ELEMENT service EMPTY>
+<!ATTLIST service
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: set-property
+Appears in: bean
+
+Used to initialize a property of a helper bean.
+
+Attributes:
+ name: The name of the property to be set.
+ expression: The OGNL expression that provides a value.
+-->
+
+<!ELEMENT set-property EMPTY>
+<!ATTLIST set-property
+ name CDATA #REQUIRED
+ expression CDATA #REQUIRED
+>
+
+
+<!-- =======================================================
+Element: set-string-property
+Appears in: bean
+
+A localized string.
+
+Attributes:
+ key: Sets a property of a string from a localized string.
+
+-->
+
+<!ELEMENT set-string-property EMPTY>
+<!ATTLIST set-string-property
+ name CDATA #REQUIRED
+ key CDATA #REQUIRED
+>
+
+
+<!-- =======================================================
+Element: static-binding
+Appears in: component
+
+Binds a parameter of the component to a static value defined directly
+within this specification. The value is the PCDATA wrapped by the element
+(with leading and trailing whitespace removed).
+
+Attributes:
+ name: The name of the component parameter to bind.
+
+-->
+<!ELEMENT static-binding (#PCDATA)>
+<!ATTLIST static-binding
+ name CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: string-binding
+Appears in: component
+
+Binds a parameter of the component to a localized string of
+its container.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ key: The key used to access a localized string.
+
+-->
+
+<!ELEMENT string-binding EMPTY>
+<!ATTLIST string-binding
+ name CDATA #REQUIRED
+ key CDATA #REQUIRED
+>
+
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_3_0.dtd b/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_3_0.dtd
new file mode 100644
index 0000000..3036a5b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/Tapestry_3_0.dtd
@@ -0,0 +1,547 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Tapestry_1_3.dtd,v 1.18 2002/09/19 17:25:28 hship Exp $ -->
+<!--
+
+The DTD for Tapestry application, library, page and component specifications.
+Associated with the public identifier:
+
+ -//Apache Software Foundation//Tapestry Specification 3.0//EN
+
+The canonical location for the DTD is:
+
+ http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd
+
+For application specifications, the root element is application.
+
+For component specifications, the root element is component-specification.
+
+For page specifications, the root element is page-specification.
+
+For library specifiations, the root element is library-specification.
+
+This DTD is backwards compatible with the 1.3 DTD, with the following exceptions:
+- <field-binding> has been removed
+- attribute class of <component-specification> and <page-specification is now optional
+- attributes name and engine-class of <application> are now optional
+- added value attribute to <static-binding>
+- added value attribute to <property>
+- renamed <component-alias> to <component-type>
+- rename <set-string-property> to <set-message-property>
+- added <listener-binding> element
+- added <property-specification> element
+- renamed java-type to type inside <parameter>
+- allow values to be specified as attributes or wrapped character data in many elements
+- allow only a single <description> per element
+-->
+<!-- =======================================================
+Entity: attribute-flag
+
+For entity attributes that take a boolean value, defines 'yes' and 'no'.
+The default varies, so isn't included here.
+-->
+<!ENTITY % attribute-flag "(yes|no)">
+<!-- =======================================================
+Entity: static-value-type
+
+For entity attributes that take a string but convert it to a real
+type. Defaults to String.
+
+-->
+<!ENTITY % static-value-type "(boolean|int|long|double|String) 'String'">
+<!ENTITY % library-content "(description?, property*, (page|component-type|service|library|extension)*)">
+<!-- =======================================================
+Element: application
+Root element
+
+Defines a Tapestry application.
+
+Attributes:
+ name: A textual name used to uniquely identify the application.
+ engine-class: The Java class to instantiate as the application engine.
+-->
+<!ELEMENT application %library-content;>
+<!ATTLIST application
+ name CDATA #IMPLIED
+ engine-class CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: bean
+Appears in: component-specification, page-specification
+
+Defines a JavaBean that will be used in some way by the component. Beans
+are accessible via the components' beans property (which contains a property
+for each bean). Beans are created as needed, and are discarded based on
+the lifecycle attribute. Beans are typically used to extend the
+implementation of a component via aggregation.
+
+Attributes:
+ name: the name of the bean
+ class: the Java class to instantiate
+ lifecycle: when the reference to the bean should be discard
+ "none" no lifecycle, the bean is created and returned, but not stored
+ "request" the bean is retained until the end of the request cycle
+ "page" the bean is retained for the lifespan of the page
+ "render" the bean is retained until the end of the current page render
+
+Nothing prevents a bean for storing state; however, such state will
+not be associated with any particular session (unlike persistant page
+properties). Further, because of page pooling, subsequent requests
+from the same client may be serviced by different instances of a page and
+(therefore) different bean instances.
+
+Beans that have the "request" lifecycle may be stored into a pool
+for later re-use (in the same or different page).
+
+The bean may have its properties set. Properties are set on both
+newly instantiated beans, and beans that are retrieved from a pool.
+
+-->
+<!ELEMENT bean (description?, property*, (set-property | set-message-property)*)>
+<!ATTLIST bean
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+ lifecycle (none | request | page | render) "request"
+>
+<!-- =======================================================
+Element: binding
+Appears in: component
+
+Binds a parameter of the component to a OGNL expression, relative
+to its container. The expression may be provided as an attribute, or
+as the body of the element. The latter case is useful when the
+expression is long, or uses problematic characters (such as a
+mix of single and double quotes).
+
+Attributes:
+ name: The name of the component parameter to bind.
+ expression: The OGNL expression.
+-->
+<!ELEMENT binding (#PCDATA)>
+<!ATTLIST binding
+ name CDATA #REQUIRED
+ expression CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: configure
+Appears in: extension
+
+Configures one JavaBean property of an extension.
+
+Attributes:
+ property-name: The name of the property to configure.
+ type: Conversion of property value.
+ value: The value to be converted and applied. If not
+ specified, the element's character data is
+ used.
+
+-->
+<!ELEMENT configure (#PCDATA)>
+<!ATTLIST configure
+ property-name CDATA #REQUIRED
+ type %static-value-type;
+ value CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: component
+Contained by: component-specification, page-specification
+
+Defines a component contained by the component being specified.
+
+Attribute:
+ id: A unique identifier for the component within the container.
+ type: The type of component, either a well known logical name, or the complete path
+ to the component's specification.
+ copy-of: The id of a previously defined component; this component will be a copy
+ of the other component.
+
+The Tapestry page loader ensures that either type or copy-of is specified, but not both.
+-->
+<!ELEMENT component (property*, (binding | inherited-binding | listener-binding | static-binding | message-binding)*)>
+<!ATTLIST component
+ id ID #REQUIRED
+ type CDATA #IMPLIED
+ copy-of IDREF #IMPLIED
+ inherit-informal-parameters %attribute-flag; "no"
+>
+<!-- =======================================================
+Element: component-type
+Contained by: application
+
+Establishes a short logic name for a particular component that is used
+within the application.
+
+Attributes:
+ type: The logical name for the component.
+ specification-path: The complete path to the component's specification.
+-->
+<!ELEMENT component-type EMPTY>
+<!ATTLIST component-type
+ type CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: component-specification
+Root element
+
+A component specification. It's attributes define the Java class to
+instantiate, whether the component may wrap other elements, and whether
+informal (undeclared) parameters are allowed. Very similar to a page-specification,
+except that component-specification allows for parameters (formal and informal).
+
+Attributes:
+ class: The Java class to instantiate for the component.
+ allow-body: If yes (the default), the component may wrap other elements (have a body).
+ allow-informal-parameters: If yes (the default), informal parameters (parameters that are not
+ explictily defined) are allowed.
+-->
+<!ELEMENT component-specification (description?, parameter*, reserved-parameter*, property*, (bean | component | external-asset | context-asset | private-asset | property-specification)*)>
+<!ATTLIST component-specification
+ class CDATA #IMPLIED
+ allow-body %attribute-flag; "yes"
+ allow-informal-parameters %attribute-flag; "yes"
+>
+<!-- =======================================================
+Element: context-asset
+Contained by: component-specification, page-specification
+
+An asset located in the same web application context as the running
+application.
+
+Attributes:
+ name: The name of the asset.
+ path: The path, relative to the web application context, of the resource.
+-->
+<!ELEMENT context-asset (property*)>
+<!ATTLIST context-asset
+ name CDATA #REQUIRED
+ path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: description
+Appears in: many
+
+Several elements may contain descriptions; these descriptions are
+optional. The eventual goal is to provide help in some form of IDE.
+Currently, descriptions are optional and ignored.
+
+-->
+<!ELEMENT description (#PCDATA)>
+<!-- =======================================================
+Element: extension
+Contained by: application, library-specification
+
+Defines an extension, an object that is instantiated and configured
+(like a helper bean) and is then accessible, by name, from the
+containing library (or application).
+
+Attributes:
+ name: Name of the extension.
+ class: Java class to instantiate.
+ immediate: If true, the extension is instantiated early instead of as-needed.
+
+-->
+<!ELEMENT extension (property*, configure*)>
+<!ATTLIST extension
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+ immediate %attribute-flag; "no"
+>
+<!-- =======================================================
+Element: external-asset
+Contained by: component-specification, page-specification
+
+Defines an asset at some external source.
+
+Attributes:
+ name: The name of the asset.
+ URL: The URL used to reference the asset.
+-->
+<!ELEMENT external-asset (property*)>
+<!ATTLIST external-asset
+ name CDATA #REQUIRED
+ URL CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: inherited-binding
+Appears in: component
+
+Binds a parameter of the component to a parameter of the container.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ parameter-name: The name of the container parameter to bind the
+ component parameter to.
+-->
+<!ELEMENT inherited-binding EMPTY>
+<!ATTLIST inherited-binding
+ name CDATA #REQUIRED
+ parameter-name CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: library
+Appears in: application-specification, library-specification
+
+Defines a library used in the construction of the container
+(either another library, or the application itself).
+
+Attributes:
+ id: An identifier used to reference pages and components
+ provided by the library.
+ specification-path: The path to the resource that provides
+ the library specification.
+-->
+<!ELEMENT library EMPTY>
+<!ATTLIST library
+ id CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: library-specification
+Root element
+
+Defines a library that may be used in the construction
+of an application (or another library). An application can
+be thought of as a specialized kind of library.
+
+-->
+<!ELEMENT library-specification %library-content;>
+<!-- =======================================================
+Element: page
+Contained by: application, library-specification
+
+Defines a single page within the application. Each application will contain
+at least one of these, to define the Home page.
+
+Attributes:
+ name: A unique name for the application.
+ specification-path: The resource classpath of the component specification
+ for the page.
+-->
+<!ELEMENT page EMPTY>
+<!ATTLIST page
+ name CDATA #REQUIRED
+ specification-path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: listener-binding
+Appears in: component
+
+Defines an in-place script using the scripting language
+supported by Bean Scripting Framework (http://jakarta.apache.org/bsf).
+The script itself is the element's character data (often, inside
+a CDATA block).
+
+The default language is jython, though this can be overridden.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ language: The language the script is written in.
+-->
+<!ELEMENT listener-binding (#PCDATA)>
+<!ATTLIST listener-binding
+ name CDATA #REQUIRED
+ language CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: page-specification
+Root element
+
+A page specification. It's attributes define the Java class to
+instantiate. Pages are like components, except they always allow
+a body, and never allow parameters (formal or otherwise).
+
+Attributes:
+ class: The Java class to instantiate for the component.
+-->
+<!ELEMENT page-specification (description?, property*, (bean | component | external-asset | context-asset | private-asset | property-specification)*)>
+<!ATTLIST page-specification
+ class CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: parameter
+Contained by: component-specification
+
+Defines a formal parameter for the component.
+
+Attributes:
+ name: A unique name for the parameter.
+ type: The name of a Java class or primitive type expected by the parameter.
+ required: If yes, then the parameter must be bound. If no (the default),
+ then the parameter is optional.
+ property-name: The name to use, instead of the parameter name, for the
+ JavaBean property connected to this parameter.
+ direction: The normal flow of data through the component
+ (in, form, custom, auto).
+ default-value: Specifies the default value for the parameter,
+ if the parameter is not bound.
+-->
+<!ELEMENT parameter (description?)>
+<!ATTLIST parameter
+ name CDATA #REQUIRED
+ type CDATA #IMPLIED
+ required %attribute-flag; "no"
+ property-name CDATA #IMPLIED
+ default-value CDATA #IMPLIED
+ direction (in | form | custom | auto) "custom"
+>
+<!-- =======================================================
+Element: private-asset
+Contained by: component-specification, page-specification
+
+An asset available within the Java classpath (i.e., bundled inside a JAR or WAR).
+
+Attributes:
+ name: The name of the asset.
+ resource-path: The complete pathname of the resource.
+-->
+<!ELEMENT private-asset (property*)>
+<!ATTLIST private-asset
+ name CDATA #REQUIRED
+ resource-path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: property
+Contained by: (many other elements)
+
+Defines a key/value pair associated with the application or component specification. Properties
+are used to capture information that doesn't fit into the DTD. The value for the property is
+the PCDATA wrapped by the property tag (which is trimmed of leading and trailing whitespace).
+
+This should not be confused with several other tags which are used to set JavaBeans properties
+of various objects. The <property> tag exists to allow meta-data to be stored in the specification.
+
+Attributes:
+ name: The name of the property to set.
+ value: If specified, is the value of the property, otherwise, the PCDATA is used.
+
+-->
+<!ELEMENT property (#PCDATA)>
+<!ATTLIST property
+ name CDATA #REQUIRED
+ value CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: property-specification
+Appears in: page-specification, component-specification
+
+Identifies a transient or persistent property.
+
+Attributes:
+ name: The name of the property.
+ type: The type of the value, either the name of a scalar type,
+ or the fully qualified name of a class. If omitted,
+ java.lang.Object is used.
+ persistent: If "yes", the value will be made persistant. Default
+ is "no".
+ initial-value: If provided, this is an OGNL expression used
+ to initialize the property. If not specified, the
+ body of the element is used as the initial value.
+
+-->
+<!ELEMENT property-specification (#PCDATA)>
+<!ATTLIST property-specification
+ name CDATA #REQUIRED
+ type CDATA #IMPLIED
+ persistent %attribute-flag; "no"
+ initial-value CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: reserved-parameter
+Appears in: component-specification
+
+Identifies a name which may not be used as an informal parameter.
+Informal parameters are typically HTML attribute names; this
+list identifies HTML attributes that are written exclusively
+by the component and may not be affected by informal parameters.
+
+Attributes:
+ name: The parameter name to reserve.
+-->
+<!ELEMENT reserved-parameter EMPTY>
+<!ATTLIST reserved-parameter
+ name CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: service
+Appears in: application
+
+Defines an engine service. You may override the default
+set of engine services or provide completely new services.
+
+Attributes:
+ name: The name of the service.
+ class: The Java class to instantiate for the service.
+
+-->
+<!ELEMENT service EMPTY>
+<!ATTLIST service
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: set-property
+Appears in: bean
+
+Used to initialize a property of a helper bean. An OGNL expression
+is provided as the expression attribute, or as wrapped
+character data.
+
+Attributes:
+ name: The name of the property to be set.
+ expression: The OGNL expression that provides a value.
+-->
+<!ELEMENT set-property (#PCDATA)>
+<!ATTLIST set-property
+ name CDATA #REQUIRED
+ expression CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: set-message-property
+Appears in: bean
+
+A localized string.
+
+Attributes:
+ key: Sets a property of a string from a localized string.
+
+-->
+<!ELEMENT set-message-property EMPTY>
+<!ATTLIST set-message-property
+ name CDATA #REQUIRED
+ key CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: static-binding
+Appears in: component
+
+Binds a parameter of the component to a static value defined directly
+within this specification. The value either the value ttribute, or
+the PCDATA wrapped by the element (with leading and trailing whitespace removed).
+
+Attributes:
+ name: The name of the component parameter to bind.
+ value: The value of the binding. If not specied, the PCDATA wrapped
+ by the element is the binding.
+-->
+<!ELEMENT static-binding (#PCDATA)>
+<!ATTLIST static-binding
+ name CDATA #REQUIRED
+ value CDATA #IMPLIED
+>
+<!-- =======================================================
+Element: message-binding
+Appears in: component
+
+Binds a parameter of the component to a localized message of
+its container.
+
+Attributes:
+ name: The name of the component parameter to bind.
+ key: The key used to access a localized string.
+
+-->
+<!ELEMENT message-binding EMPTY>
+<!ATTLIST message-binding
+ name CDATA #REQUIRED
+ key CDATA #REQUIRED
+>
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TemplateAttribute.java b/tapestry-framework/src/org/apache/tapestry/parse/TemplateAttribute.java
new file mode 100644
index 0000000..3be1a12
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TemplateAttribute.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * An attribute, associated with a {@link org.apache.tapestry.parse.OpenToken}, taken
+ * from a template. Each attribute has a type and a value. The interpretation of the
+ * value is based on the type.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class TemplateAttribute
+{
+ private AttributeType _type;
+ private String _value;
+
+ public TemplateAttribute(AttributeType type, String value)
+ {
+ _type = type;
+ _value = value;
+ }
+
+ public AttributeType getType()
+ {
+ return _type;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+ builder.append("type", _type);
+ builder.append("value", _value);
+
+ return builder.toString();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TemplateParseException.java b/tapestry-framework/src/org/apache/tapestry/parse/TemplateParseException.java
new file mode 100644
index 0000000..5c861fe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TemplateParseException.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocation;
+
+/**
+ * Exception thrown indicating a problem parsing an HTML template.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public class TemplateParseException extends Exception implements ILocatable
+{
+ private ILocation _location;
+ private Throwable _rootCause;
+
+ public TemplateParseException(String message)
+ {
+ this(message, null, null);
+ }
+
+ public TemplateParseException(String message, ILocation location)
+ {
+ this(message, location, null);
+ }
+
+ public TemplateParseException(String message, ILocation location, Throwable rootCause)
+ {
+ super(message);
+
+ _location = location;
+
+ _rootCause = rootCause;
+
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public Throwable getRootCause()
+ {
+ return _rootCause;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TemplateParser.java b/tapestry-framework/src/org/apache/tapestry/parse/TemplateParser.java
new file mode 100644
index 0000000..0e92805
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TemplateParser.java
@@ -0,0 +1,1615 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.MatchResult;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.PatternMatcher;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.IdAllocator;
+
+/**
+ * Parses Tapestry templates, breaking them into a series of
+ * {@link org.apache.tapestry.parse.TemplateToken tokens}.
+ * Although often referred to as an "HTML template", there is no real
+ * requirement that the template be HTML. This parser can handle
+ * any reasonable SGML derived markup (including XML),
+ * but specifically works around the ambiguities
+ * of HTML reasonably.
+ *
+ * <p>Dynamic markup in Tapestry attempts to be invisible.
+ * Components are arbitrary tags containing a <code>jwcid</code> attribute.
+ * Such components must be well balanced (have a matching close tag, or
+ * end the tag with "<code>/></code>".
+ *
+ * <p>Generally, the id specified in the template is matched against
+ * an component defined in the specification. However, implicit
+ * components are also possible. The jwcid attribute uses
+ * the syntax "<code>@Type</code>" for implicit components.
+ * Type is the component type, and may include a library id prefix. Such
+ * a component is anonymous (but is given a unique id).
+ *
+ * <p>
+ * (The unique ids assigned start with a dollar sign, which is normally
+ * no allowed for component ids ... this helps to make them stand out
+ * and assures that they do not conflict with
+ * user-defined component ids. These ids tend to propagate
+ * into URLs and become HTML element names and even JavaScript
+ * variable names ... the dollar sign is acceptible in these contexts as
+ * well).
+ *
+ * <p>Implicit component may also be given a name using the syntax
+ * "<code>componentId:@Type</code>". Such a component should
+ * <b>not</b> be defined in the specification, but may still be
+ * accessed via {@link org.apache.tapestry.IComponent#getComponent(String)}.
+ *
+ * <p>
+ * Both defined and implicit components may have additional attributes
+ * defined, simply by including them in the template. They set formal or
+ * informal parameters of the component to static strings.
+ * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowInformalParameters()},
+ * if false, will cause such attributes to be simply ignored. For defined
+ * components, conflicting values defined in the template are ignored.
+ *
+ * <p>Attributes in component tags will become formal and informal parameters
+ * of the corresponding component. Most attributes will be
+ *
+ * <p>The parser removes
+ * the body of some tags (when the corresponding component doesn't
+ * {@link org.apache.tapestry.spec.IComponentSpecification#getAllowBody() allow a body},
+ * and allows
+ * portions of the template to be completely removed.
+ *
+ * <p>The parser does a pretty thorough lexical analysis of the template,
+ * and reports a great number of errors, including improper nesting
+ * of tags.
+ *
+ * <p>The parser supports <em>invisible localization</em>:
+ * The parser recognizes HTML of the form:
+ * <code><span key="<i>value</i>"> ... </span></code>
+ * and converts them into a {@link TokenType#LOCALIZATION}
+ * token. You may also specifify a <code>raw</code> attribute ... if the value
+ * is <code>true</code>, then the localized value is
+ * sent to the client without filtering, which is appropriate if the
+ * value has any markup that should not be escaped.
+ *
+ * @author Howard Lewis Ship, Geoff Longman
+ * @version $Id$
+ *
+ **/
+
+public class TemplateParser
+{
+ /**
+ * A Factory used by {@link org.apache.tapestry.parse.TemplateParser} to create
+ * {@link org.apache.tapestry.parse.TemplateToken} objects.
+ *
+ * <p>
+ * This class is extended by Spindle - the Eclipse Plugin for Tapestry.
+ * <p>
+ * @author glongman@intelligentworks.com
+ * @since 3.0
+ */
+ protected static class TemplateTokenFactory
+ {
+
+ public OpenToken createOpenToken(String tagName, String jwcId, String type, ILocation location)
+ {
+ return new OpenToken(tagName, jwcId, type, location);
+ }
+
+ public CloseToken createCloseToken(String tagName, ILocation location)
+ {
+ return new CloseToken(tagName, location);
+ }
+
+ public TextToken createTextToken(char[] templateData, int blockStart, int end, ILocation templateLocation)
+ {
+ return new TextToken(templateData, blockStart, end, templateLocation);
+ }
+
+ public LocalizationToken createLocalizationToken(
+ String tagName,
+ String localizationKey,
+ boolean raw,
+ Map attributes,
+ ILocation startLocation)
+ {
+ return new LocalizationToken(tagName, localizationKey, raw, attributes, startLocation);
+ }
+ }
+
+ /**
+ * Attribute value prefix indicating that the attribute is an OGNL expression.
+ *
+ * @since 3.0
+ **/
+
+ public static final String OGNL_EXPRESSION_PREFIX = "ognl:";
+
+ /**
+ * Attribute value prefix indicating that the attribute is a localization
+ * key.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String LOCALIZATION_KEY_PREFIX = "message:";
+
+ /**
+ * A "magic" component id that causes the tag with the id and its entire
+ * body to be ignored during parsing.
+ *
+ **/
+
+ private static final String REMOVE_ID = "$remove$";
+
+ /**
+ * A "magic" component id that causes the tag to represent the true
+ * content of the template. Any content prior to the tag is discarded,
+ * and any content after the tag is ignored. The tag itself is not
+ * included.
+ *
+ **/
+
+ private static final String CONTENT_ID = "$content$";
+
+ /**
+ *
+ * The attribute, checked for in <span> tags, that signfies
+ * that the span is being used as an invisible localization.
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ public static final String LOCALIZATION_KEY_ATTRIBUTE_NAME = "key";
+
+ /**
+ * Used with {@link #LOCALIZATION_KEY_ATTRIBUTE_NAME} to indicate a string
+ * that should be rendered "raw" (without escaping HTML). If not specified,
+ * defaults to "false". The value must equal "true" (caselessly).
+ *
+ * @since 2.3
+ *
+ **/
+
+ public static final String RAW_ATTRIBUTE_NAME = "raw";
+
+ /**
+ * Attribute used to identify components.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public static final String JWCID_ATTRIBUTE_NAME = "jwcid";
+
+ private static final String PROPERTY_NAME_PATTERN = "_?[a-zA-Z]\\w*";
+
+ /**
+ * Pattern used to recognize ordinary components (defined in the specification).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String SIMPLE_ID_PATTERN = "^(" + PROPERTY_NAME_PATTERN + ")$";
+
+ /**
+ * Pattern used to recognize implicit components (whose type is defined in
+ * the template). Subgroup 1 is the id (which may be null) and subgroup 2
+ * is the type (which may be qualified with a library prefix).
+ * Subgroup 4 is the library id, Subgroup 5 is the simple component type.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final String IMPLICIT_ID_PATTERN =
+ "^(" + PROPERTY_NAME_PATTERN + ")?@(((" + PROPERTY_NAME_PATTERN + "):)?(" + PROPERTY_NAME_PATTERN + "))$";
+
+ private static final int IMPLICIT_ID_PATTERN_ID_GROUP = 1;
+ private static final int IMPLICIT_ID_PATTERN_TYPE_GROUP = 2;
+ private static final int IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP = 4;
+ private static final int IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP = 5;
+
+ private Pattern _simpleIdPattern;
+ private Pattern _implicitIdPattern;
+ private PatternMatcher _patternMatcher;
+
+ private IdAllocator _idAllocator = new IdAllocator();
+
+ private ITemplateParserDelegate _delegate;
+
+ /**
+ * Identifies the template being parsed; used with error messages.
+ *
+ **/
+
+ private IResourceLocation _resourceLocation;
+
+ /**
+ * Shared instance of {@link Location} used by
+ * all {@link TextToken} instances in the template.
+ *
+ **/
+
+ private ILocation _templateLocation;
+
+ /**
+ * Location with in the resource for the current line.
+ *
+ **/
+
+ private ILocation _currentLocation;
+
+ /**
+ * Local reference to the template data that is to be parsed.
+ *
+ **/
+
+ private char[] _templateData;
+
+ /**
+ * List of Tag
+ *
+ **/
+
+ private List _stack = new ArrayList();
+
+ private static class Tag
+ {
+ // The element, i.e., <jwc> or virtually any other element (via jwcid attribute)
+ String _tagName;
+ // If true, the tag is a placeholder for a dynamic element
+ boolean _component;
+ // If true, the body of the tag is being ignored, and the
+ // ignore flag is cleared when the close tag is reached
+ boolean _ignoringBody;
+ // If true, then the entire tag (and its body) is being ignored
+ boolean _removeTag;
+ // If true, then the tag must have a balanced closing tag.
+ // This is always true for components.
+ boolean _mustBalance;
+ // The line on which the start tag exists
+ int _line;
+ // If true, then the parse ends when the closing tag is found.
+ boolean _content;
+
+ Tag(String tagName, int line)
+ {
+ _tagName = tagName;
+ _line = line;
+ }
+
+ boolean match(String matchTagName)
+ {
+ return _tagName.equalsIgnoreCase(matchTagName);
+ }
+ }
+
+ /**
+ * List of {@link TemplateToken}, this forms the ultimate response.
+ *
+ **/
+
+ private List _tokens = new ArrayList();
+
+ /**
+ * The location of the 'cursor' within the template data. The
+ * advance() method moves this forward.
+ *
+ **/
+
+ private int _cursor;
+
+ /**
+ * The start of the current block of static text, or -1 if no block
+ * is active.
+ *
+ **/
+
+ private int _blockStart;
+
+ /**
+ * The current line number; tracked by advance(). Starts at 1.
+ *
+ **/
+
+ private int _line;
+
+ /**
+ * Set to true when the body of a tag is being ignored. This is typically
+ * used to skip over the body of a tag when its corresponding
+ * component doesn't allow a body, or whe the special
+ * jwcid of $remove$ is used.
+ *
+ **/
+
+ private boolean _ignoring;
+
+ /**
+ * A {@link Map} of {@link String}s, used to store attributes collected
+ * while parsing a tag.
+ *
+ **/
+
+ private Map _attributes = new HashMap();
+
+ /**
+ * A factory used to create template tokens.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+
+ protected TemplateTokenFactory _factory;
+
+ public TemplateParser()
+ {
+ Perl5Compiler compiler = new Perl5Compiler();
+
+ try
+ {
+ _simpleIdPattern = compiler.compile(SIMPLE_ID_PATTERN);
+ _implicitIdPattern = compiler.compile(IMPLICIT_ID_PATTERN);
+ } catch (MalformedPatternException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+
+ _patternMatcher = new Perl5Matcher();
+
+ _factory = new TemplateTokenFactory();
+ }
+
+ /**
+ * Parses the template data into an array of {@link TemplateToken}s.
+ *
+ * <p>The parser is <i>decidedly</i> not threadsafe, so care should be taken
+ * that only a single thread accesses it.
+ *
+ * @param templateData the HTML template to parse. Some tokens will hold
+ * a reference to this array.
+ * @param delegate object that "knows" about defined components
+ * @param resourceLocation a description of where the template originated from,
+ * used with error messages.
+ *
+ **/
+
+ public TemplateToken[] parse(
+ char[] templateData,
+ ITemplateParserDelegate delegate,
+ IResourceLocation resourceLocation)
+ throws TemplateParseException
+ {
+ TemplateToken[] result = null;
+
+ try
+ {
+ beforeParse(templateData, delegate, resourceLocation);
+
+ parse();
+
+ result = (TemplateToken[]) _tokens.toArray(new TemplateToken[_tokens.size()]);
+ } finally
+ {
+ afterParse();
+ }
+
+ return result;
+ }
+
+ /**
+ * perform default initialization of the parser.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+
+ protected void beforeParse(
+ char[] templateData,
+ ITemplateParserDelegate delegate,
+ IResourceLocation resourceLocation)
+ {
+ _templateData = templateData;
+ _resourceLocation = resourceLocation;
+ _templateLocation = new Location(resourceLocation);
+ _delegate = delegate;
+ _ignoring = false;
+ _line = 1;
+ }
+
+ /**
+ * Perform default cleanup after parsing completes.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+
+ protected void afterParse()
+ {
+ _delegate = null;
+ _templateData = null;
+ _resourceLocation = null;
+ _templateLocation = null;
+ _currentLocation = null;
+ _stack.clear();
+ _tokens.clear();
+ _attributes.clear();
+ _idAllocator.clear();
+ }
+
+ /**
+ * Used by the parser to report problems in the parse.
+ * Parsing <b>must</b> stop when a problem is reported.
+ * <p>
+ * The default implementation simply throws an exception that contains
+ * the message and location parameters.
+ * <p>
+ * Subclasses may override but <b>must</b> ensure they throw the required exception.
+ * <p>
+ *
+ * author glongman@intelligentworks.com
+ *
+ * @param message
+ * @param location
+ * @param line ignored by the default impl
+ * @param cursor ignored by the default impl
+ * @throws TemplateParseException always thrown in order to terminate the parse.
+ */
+
+ protected void templateParseProblem(String message, ILocation location, int line, int cursor)
+ throws TemplateParseException
+ {
+ throw new TemplateParseException(message, location);
+ }
+
+ /**
+ * Used by the parser to report tapestry runtime specific problems in the parse.
+ * Parsing <b>must</b> stop when a problem is reported.
+ * <p>
+ * The default implementation simply rethrows the exception.
+ * <p>
+ * Subclasses may override but <b>must</b> ensure they rethrow the exception.
+ * <p>
+ *
+ * author glongman@intelligentworks.com
+ *
+ * @param exception
+ * @param line ignored by the default impl
+ * @param cursor ignored by the default impl
+ * @throws ApplicationRuntimeException always rethrown in order to terminate the parse.
+ */
+
+ protected void templateParseProblem(ApplicationRuntimeException exception, int line, int cursor)
+ throws ApplicationRuntimeException
+ {
+ throw exception;
+ }
+
+ /**
+ * Give subclasses access to the parse results.
+ * <p>
+ *
+ * author glongman@intelligentworks.com
+ */
+ protected List getTokens()
+ {
+ if (_tokens == null)
+ return Collections.EMPTY_LIST;
+
+ return _tokens;
+ }
+
+ /**
+ * Checks to see if the next few characters match a given pattern.
+ *
+ **/
+
+ private boolean lookahead(char[] match)
+ {
+ try
+ {
+ for (int i = 0; i < match.length; i++)
+ {
+ if (_templateData[_cursor + i] != match[i])
+ return false;
+ }
+
+ // Every character matched.
+
+ return true;
+ } catch (IndexOutOfBoundsException ex)
+ {
+ return false;
+ }
+ }
+
+ private static final char[] COMMENT_START = new char[] { '<', '!', '-', '-' };
+ private static final char[] COMMENT_END = new char[] { '-', '-', '>' };
+ private static final char[] CLOSE_TAG = new char[] { '<', '/' };
+
+ protected void parse() throws TemplateParseException
+ {
+ _cursor = 0;
+ _blockStart = -1;
+ int length = _templateData.length;
+
+ while (_cursor < length)
+ {
+ if (_templateData[_cursor] != '<')
+ {
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = _cursor;
+
+ advance();
+ continue;
+ }
+
+ // OK, start of something.
+
+ if (lookahead(CLOSE_TAG))
+ {
+ closeTag();
+ continue;
+ }
+
+ if (lookahead(COMMENT_START))
+ {
+ skipComment();
+ continue;
+ }
+
+ // The start of some tag.
+
+ startTag();
+ }
+
+ // Usually there's some text at the end of the template (after the last closing tag) that should
+ // be added. Often the last few tags are static tags so we definately
+ // need to end the text block.
+
+ addTextToken(_templateData.length - 1);
+ }
+
+ /**
+ * Advance forward in the document until the end of the comment is reached.
+ * In addition, skip any whitespace following the comment.
+ *
+ **/
+
+ private void skipComment() throws TemplateParseException
+ {
+ int length = _templateData.length;
+ int startLine = _line;
+
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = _cursor;
+
+ while (true)
+ {
+ if (_cursor >= length)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.comment-not-ended", Integer.toString(startLine)),
+ new Location(_resourceLocation, startLine),
+ startLine,
+ _cursor);
+
+ if (lookahead(COMMENT_END))
+ break;
+
+ // Not the end of the comment, advance over it.
+
+ advance();
+ }
+
+ _cursor += COMMENT_END.length;
+ advanceOverWhitespace();
+ }
+
+ private void addTextToken(int end)
+ {
+ // No active block to add to.
+
+ if (_blockStart < 0)
+ return;
+
+ if (_blockStart <= end)
+ {
+ TemplateToken token = _factory.createTextToken(_templateData, _blockStart, end, _templateLocation);
+
+ _tokens.add(token);
+ }
+
+ _blockStart = -1;
+ }
+
+ private static final int WAIT_FOR_ATTRIBUTE_NAME = 0;
+ private static final int COLLECT_ATTRIBUTE_NAME = 1;
+ private static final int ADVANCE_PAST_EQUALS = 2;
+ private static final int WAIT_FOR_ATTRIBUTE_VALUE = 3;
+ private static final int COLLECT_QUOTED_VALUE = 4;
+ private static final int COLLECT_UNQUOTED_VALUE = 5;
+
+ private void startTag() throws TemplateParseException
+ {
+ int cursorStart = _cursor;
+ int length = _templateData.length;
+ String tagName = null;
+ boolean endOfTag = false;
+ boolean emptyTag = false;
+ int startLine = _line;
+ ILocation startLocation = new Location(_resourceLocation, startLine);
+
+ tagBeginEvent(startLine, _cursor);
+
+ advance();
+
+ // Collect the element type
+
+ while (_cursor < length)
+ {
+ char ch = _templateData[_cursor];
+
+ if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
+ {
+ tagName = new String(_templateData, cursorStart + 1, _cursor - cursorStart - 1);
+
+ break;
+ }
+
+ advance();
+ }
+
+ String attributeName = null;
+ int attributeNameStart = -1;
+ int attributeValueStart = -1;
+ int state = WAIT_FOR_ATTRIBUTE_NAME;
+ char quoteChar = 0;
+
+ _attributes.clear();
+
+ // Collect each attribute
+
+ while (!endOfTag)
+ {
+ if (_cursor >= length)
+ {
+ String key = (tagName == null) ? "TemplateParser.unclosed-unknown-tag" : "TemplateParser.unclosed-tag";
+
+ templateParseProblem(
+ Tapestry.format(key, tagName, Integer.toString(startLine)),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ }
+
+ char ch = _templateData[_cursor];
+
+ switch (state)
+ {
+ case WAIT_FOR_ATTRIBUTE_NAME :
+
+ // Ignore whitespace before the next attribute name, while
+ // looking for the end of the current tag.
+
+ if (ch == '/')
+ {
+ emptyTag = true;
+ advance();
+ break;
+ }
+
+ if (ch == '>')
+ {
+ endOfTag = true;
+ break;
+ }
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
+
+ // Found non-whitespace, assume its the attribute name.
+ // Note: could use a check here for non-alpha.
+
+ attributeNameStart = _cursor;
+ state = COLLECT_ATTRIBUTE_NAME;
+ advance();
+ break;
+
+ case COLLECT_ATTRIBUTE_NAME :
+
+ // Looking for end of attribute name.
+
+ if (ch == '=' || ch == '/' || ch == '>' || Character.isWhitespace(ch))
+ {
+ attributeName = new String(_templateData, attributeNameStart, _cursor - attributeNameStart);
+
+ state = ADVANCE_PAST_EQUALS;
+ break;
+ }
+
+ // Part of the attribute name
+
+ advance();
+ break;
+
+ case ADVANCE_PAST_EQUALS :
+
+ // Looking for the '=' sign. May hit the end of the tag, or (for bare attributes),
+ // the next attribute name.
+
+ if (ch == '/' || ch == '>')
+ {
+ // A bare attribute, which is not interesting to
+ // us.
+
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
+
+ if (ch == '=')
+ {
+ state = WAIT_FOR_ATTRIBUTE_VALUE;
+ quoteChar = 0;
+ attributeValueStart = -1;
+ advance();
+ break;
+ }
+
+ // Otherwise, an HTML style "bare" attribute (such as <select multiple>).
+ // We aren't interested in those (we're just looking for the id or jwcid attribute).
+
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+
+ case WAIT_FOR_ATTRIBUTE_VALUE :
+
+ if (ch == '/' || ch == '>')
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.missing-attribute-value",
+ tagName,
+ Integer.toString(_line),
+ attributeName),
+ getCurrentLocation(),
+ _line,
+ _cursor);
+
+ // Ignore whitespace between '=' and the attribute value. Also, look
+ // for initial quote.
+
+ if (Character.isWhitespace(ch))
+ {
+ advance();
+ break;
+ }
+
+ if (ch == '\'' || ch == '"')
+ {
+ quoteChar = ch;
+
+ state = COLLECT_QUOTED_VALUE;
+ advance();
+ attributeValueStart = _cursor;
+ attributeBeginEvent(attributeName, _line, attributeValueStart);
+ break;
+ }
+
+ // Not whitespace or quote, must be start of unquoted attribute.
+
+ state = COLLECT_UNQUOTED_VALUE;
+ attributeValueStart = _cursor;
+ attributeBeginEvent(attributeName, _line, attributeValueStart);
+ break;
+
+ case COLLECT_QUOTED_VALUE :
+
+ // Start collecting the quoted attribute value. Stop at the matching quote character,
+ // unless bare, in which case, stop at the next whitespace.
+
+ if (ch == quoteChar)
+ {
+ String attributeValue =
+ new String(_templateData, attributeValueStart, _cursor - attributeValueStart);
+
+
+ attributeEndEvent(_cursor);
+
+ if (_attributes.containsKey(attributeName))
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.duplicate-tag-attribute",
+ tagName,
+ Integer.toString(_line),
+ attributeName),
+ getCurrentLocation(),
+ _line,
+ _cursor);
+
+ _attributes.put(attributeName, attributeValue);
+
+ // Advance over the quote.
+ advance();
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
+
+ advance();
+ break;
+
+ case COLLECT_UNQUOTED_VALUE :
+
+ // An unquoted attribute value ends with whitespace
+ // or the end of the enclosing tag.
+
+ if (ch == '/' || ch == '>' || Character.isWhitespace(ch))
+ {
+ String attributeValue =
+ new String(_templateData, attributeValueStart, _cursor - attributeValueStart);
+
+ attributeEndEvent(_cursor);
+
+ if (_attributes.containsKey(attributeName))
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.duplicate-tag-attribute",
+ tagName,
+ Integer.toString(_line),
+ attributeName),
+ getCurrentLocation(),
+ _line,
+ _cursor);
+
+ _attributes.put(attributeName, attributeValue);
+
+ state = WAIT_FOR_ATTRIBUTE_NAME;
+ break;
+ }
+
+ advance();
+ break;
+ }
+ }
+
+ tagEndEvent(_cursor);
+
+ // Check for invisible localizations
+
+ String localizationKey = findValueCaselessly(LOCALIZATION_KEY_ATTRIBUTE_NAME, _attributes);
+ String jwcId = findValueCaselessly(JWCID_ATTRIBUTE_NAME, _attributes);
+
+ if (localizationKey != null && tagName.equalsIgnoreCase("span") && jwcId == null)
+ {
+ if (_ignoring)
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.component-may-not-be-ignored",
+ tagName,
+ Integer.toString(startLine)),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ // If the tag isn't empty, then create a Tag instance to ignore the
+ // body of the tag.
+
+ if (!emptyTag)
+ {
+ Tag tag = new Tag(tagName, startLine);
+
+ tag._component = false;
+ tag._removeTag = true;
+ tag._ignoringBody = true;
+ tag._mustBalance = true;
+
+ _stack.add(tag);
+
+ // Start ignoring content until the close tag.
+
+ _ignoring = true;
+ } else
+ {
+ // Cursor is at the closing carat, advance over it and any whitespace.
+ advance();
+ advanceOverWhitespace();
+ }
+
+ // End any open block.
+
+ addTextToken(cursorStart - 1);
+
+ boolean raw = checkBoolean(RAW_ATTRIBUTE_NAME, _attributes);
+
+ Map attributes = filter(_attributes, new String[] { LOCALIZATION_KEY_ATTRIBUTE_NAME, RAW_ATTRIBUTE_NAME });
+
+ TemplateToken token =
+ _factory.createLocalizationToken(tagName, localizationKey, raw, attributes, startLocation);
+
+ _tokens.add(token);
+
+ return;
+ }
+
+ if (jwcId != null)
+ {
+ processComponentStart(tagName, jwcId, emptyTag, startLine, cursorStart, startLocation);
+ return;
+ }
+
+ // A static tag (not a tag without a jwcid attribute).
+ // We need to record this so that we can match close tags later.
+
+ if (!emptyTag)
+ {
+ Tag tag = new Tag(tagName, startLine);
+ _stack.add(tag);
+ }
+
+ // If there wasn't an active block, then start one.
+
+ if (_blockStart < 0 && !_ignoring)
+ _blockStart = cursorStart;
+
+ advance();
+ }
+
+ /**
+ * Processes a tag that is the open tag for a component (but also handles
+ * the $remove$ and $content$ tags).
+ *
+ **/
+
+ /**
+ * Notify that the beginning of a tag has been detected.
+ * <p>
+ * Default implementation does nothing.
+ * <p>
+ *
+ * author glongman@intelligentworks.com
+ */
+ protected void tagBeginEvent(int startLine, int cursorPosition)
+ {
+ }
+
+ /**
+ * Notify that the end of the current tag has been detected.
+ * <p>
+ * Default implementation does nothing.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+ protected void tagEndEvent(int cursorPosition)
+ {
+ }
+
+ /**
+ * Notify that the beginning of an attribute value has been detected.
+ * <p>
+ * Default implementation does nothing.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+ protected void attributeBeginEvent(String attributeName, int startLine, int cursorPosition)
+ {
+ }
+
+ /**
+ * Notify that the end of the current attribute value has been detected.
+ * <p>
+ * Default implementation does nothing.
+ * <p>
+ * author glongman@intelligentworks.com
+ */
+ protected void attributeEndEvent(int cursorPosition)
+ {
+ }
+
+ private void processComponentStart(
+ String tagName,
+ String jwcId,
+ boolean emptyTag,
+ int startLine,
+ int cursorStart,
+ ILocation startLocation)
+ throws TemplateParseException
+ {
+ if (jwcId.equalsIgnoreCase(CONTENT_ID))
+ {
+ processContentTag(tagName, startLine, cursorStart, emptyTag);
+
+ return;
+ }
+
+ boolean isRemoveId = jwcId.equalsIgnoreCase(REMOVE_ID);
+
+ if (_ignoring && !isRemoveId)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.component-may-not-be-ignored", tagName, Integer.toString(startLine)),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ String type = null;
+ boolean allowBody = false;
+
+ if (_patternMatcher.matches(jwcId, _implicitIdPattern))
+ {
+ MatchResult match = _patternMatcher.getMatch();
+
+ jwcId = match.group(IMPLICIT_ID_PATTERN_ID_GROUP);
+ type = match.group(IMPLICIT_ID_PATTERN_TYPE_GROUP);
+
+ String libraryId = match.group(IMPLICIT_ID_PATTERN_LIBRARY_ID_GROUP);
+ String simpleType = match.group(IMPLICIT_ID_PATTERN_SIMPLE_TYPE_GROUP);
+
+ // If (and this is typical) no actual component id was specified,
+ // then generate one on the fly.
+ // The allocated id for anonymous components is
+ // based on the simple (unprefixed) type, but starts
+ // with a leading dollar sign to ensure no conflicts
+ // with user defined component ids (which don't allow dollar signs
+ // in the id).
+
+ if (jwcId == null)
+ jwcId = _idAllocator.allocateId("$" + simpleType);
+
+ try
+ {
+ allowBody = _delegate.getAllowBody(libraryId, simpleType, startLocation);
+ } catch (ApplicationRuntimeException e)
+ {
+ // give subclasses a chance to handle and rethrow
+ templateParseProblem(e, startLine, cursorStart);
+ }
+
+ } else
+ {
+ if (!isRemoveId)
+ {
+ if (!_patternMatcher.matches(jwcId, _simpleIdPattern))
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.component-id-invalid",
+ tagName,
+ Integer.toString(startLine),
+ jwcId),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ if (!_delegate.getKnownComponent(jwcId))
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.unknown-component-id",
+ tagName,
+ Integer.toString(startLine),
+ jwcId),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ try
+ {
+ allowBody = _delegate.getAllowBody(jwcId, startLocation);
+ } catch (ApplicationRuntimeException e)
+ {
+ // give subclasses a chance to handle and rethrow
+ templateParseProblem(e, startLine, cursorStart);
+ }
+ }
+ }
+
+ // Ignore the body if we're removing the entire tag,
+ // of if the corresponding component doesn't allow
+ // a body.
+
+ boolean ignoreBody = !emptyTag && (isRemoveId || !allowBody);
+
+ if (_ignoring && ignoreBody)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.nested-ignore", tagName, Integer.toString(startLine)),
+ new Location(_resourceLocation, startLine),
+ startLine,
+ cursorStart);
+
+ if (!emptyTag)
+ pushNewTag(tagName, startLine, isRemoveId, ignoreBody);
+
+ // End any open block.
+
+ addTextToken(cursorStart - 1);
+
+ if (!isRemoveId)
+ {
+ addOpenToken(tagName, jwcId, type, startLocation);
+
+ if (emptyTag)
+ _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+ }
+
+ advance();
+ }
+
+ private void pushNewTag(String tagName, int startLine, boolean isRemoveId, boolean ignoreBody)
+ {
+ Tag tag = new Tag(tagName, startLine);
+
+ tag._component = !isRemoveId;
+ tag._removeTag = isRemoveId;
+
+ tag._ignoringBody = ignoreBody;
+
+ _ignoring = tag._ignoringBody;
+
+ tag._mustBalance = true;
+
+ _stack.add(tag);
+ }
+
+ private void processContentTag(String tagName, int startLine, int cursorStart, boolean emptyTag)
+ throws TemplateParseException
+ {
+ if (_ignoring)
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.content-block-may-not-be-ignored",
+ tagName,
+ Integer.toString(startLine)),
+ new Location(_resourceLocation, startLine),
+ startLine,
+ cursorStart);
+
+ if (emptyTag)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.content-block-may-not-be-empty", tagName, Integer.toString(startLine)),
+ new Location(_resourceLocation, startLine),
+ startLine,
+ cursorStart);
+
+ _tokens.clear();
+ _blockStart = -1;
+
+ Tag tag = new Tag(tagName, startLine);
+
+ tag._mustBalance = true;
+ tag._content = true;
+
+ _stack.clear();
+ _stack.add(tag);
+
+ advance();
+ }
+
+ private void addOpenToken(String tagName, String jwcId, String type, ILocation location)
+ {
+ OpenToken token = _factory.createOpenToken(tagName, jwcId, type, location);
+ _tokens.add(token);
+
+ if (_attributes.isEmpty())
+ return;
+
+ Iterator i = _attributes.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String key = (String) entry.getKey();
+
+ if (key.equalsIgnoreCase(JWCID_ATTRIBUTE_NAME))
+ continue;
+
+ String value = (String) entry.getValue();
+
+ addAttributeToToken(token, key, value);
+ }
+ }
+
+ /**
+ * Analyzes the attribute value, looking for possible prefixes that indicate
+ * the value is not a literal. Adds the attribute to the
+ * token.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private void addAttributeToToken(OpenToken token, String name, String attributeValue)
+ {
+ int pos = attributeValue.indexOf(":");
+
+ if (pos > 0)
+ {
+
+ String prefix = attributeValue.substring(0, pos + 1);
+
+ if (prefix.equals(OGNL_EXPRESSION_PREFIX))
+ {
+ token.addAttribute(
+ name,
+ AttributeType.OGNL_EXPRESSION,
+ extractExpression(attributeValue.substring(pos + 1)));
+ return;
+ }
+
+ if (prefix.equals(LOCALIZATION_KEY_PREFIX))
+ {
+ token.addAttribute(name, AttributeType.LOCALIZATION_KEY, attributeValue.substring(pos + 1).trim());
+ return;
+
+ }
+ }
+
+ token.addAttribute(name, AttributeType.LITERAL, attributeValue);
+ }
+
+ /**
+ * Invoked to handle a closing tag, i.e., </foo>. When a tag closes, it will match against
+ * a tag on the open tag start. Preferably the top tag on the stack (if everything is well balanced), but this
+ * is HTML, not XML, so many tags won't balance.
+ *
+ * <p>Once the matching tag is located, the question is ... is the tag dynamic or static? If static, then
+ * the current text block is extended to include this close tag. If dynamic, then the current text block
+ * is ended (before the '<' that starts the tag) and a close token is added.
+ *
+ * <p>In either case, the matching static element and anything above it is removed, and the cursor is left
+ * on the character following the '>'.
+ *
+ **/
+
+ private void closeTag() throws TemplateParseException
+ {
+ int cursorStart = _cursor;
+ int length = _templateData.length;
+ int startLine = _line;
+
+ ILocation startLocation = getCurrentLocation();
+
+ _cursor += CLOSE_TAG.length;
+
+ int tagStart = _cursor;
+
+ while (true)
+ {
+ if (_cursor >= length)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.incomplete-close-tag", Integer.toString(startLine)),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ char ch = _templateData[_cursor];
+
+ if (ch == '>')
+ break;
+
+ advance();
+ }
+
+ String tagName = new String(_templateData, tagStart, _cursor - tagStart);
+
+ int stackPos = _stack.size() - 1;
+ Tag tag = null;
+
+ while (stackPos >= 0)
+ {
+ tag = (Tag) _stack.get(stackPos);
+
+ if (tag.match(tagName))
+ break;
+
+ if (tag._mustBalance)
+ templateParseProblem(
+ Tapestry.format(
+ "TemplateParser.improperly-nested-close-tag",
+ new Object[] {
+ tagName,
+ Integer.toString(startLine),
+ tag._tagName,
+ Integer.toString(tag._line)}),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ stackPos--;
+ }
+
+ if (stackPos < 0)
+ templateParseProblem(
+ Tapestry.format("TemplateParser.unmatched-close-tag", tagName, Integer.toString(startLine)),
+ startLocation,
+ startLine,
+ cursorStart);
+
+ // Special case for the content tag
+
+ if (tag._content)
+ {
+ addTextToken(cursorStart - 1);
+
+ // Advance the cursor right to the end.
+
+ _cursor = length;
+ _stack.clear();
+ return;
+ }
+
+ // When a component closes, add a CLOSE tag.
+ if (tag._component)
+ {
+ addTextToken(cursorStart - 1);
+
+ _tokens.add(_factory.createCloseToken(tagName, getCurrentLocation()));
+ } else
+ {
+ // The close of a static tag. Unless removing the tag
+ // entirely, make sure the block tag is part of a text block.
+
+ if (_blockStart < 0 && !tag._removeTag && !_ignoring)
+ _blockStart = cursorStart;
+ }
+
+ // Remove all elements at stackPos or above.
+
+ for (int i = _stack.size() - 1; i >= stackPos; i--)
+ _stack.remove(i);
+
+ // Advance cursor past '>'
+
+ advance();
+
+ // If editting out the tag (i.e., $remove$) then kill any whitespace.
+ // For components that simply don't contain a body, removeTag will
+ // be false.
+
+ if (tag._removeTag)
+ advanceOverWhitespace();
+
+ // If we were ignoring the body of the tag, then clear the ignoring
+ // flag, since we're out of the body.
+
+ if (tag._ignoringBody)
+ _ignoring = false;
+ }
+
+ /**
+ * Advances the cursor to the next character.
+ * If the end-of-line is reached, then increments
+ * the line counter.
+ *
+ **/
+
+ private void advance()
+ {
+ int length = _templateData.length;
+
+ if (_cursor >= length)
+ return;
+
+ char ch = _templateData[_cursor];
+
+ _cursor++;
+
+ if (ch == '\n')
+ {
+ _line++;
+ _currentLocation = null;
+ return;
+ }
+
+ // A \r, or a \r\n also counts as a new line.
+
+ if (ch == '\r')
+ {
+ _line++;
+ _currentLocation = null;
+
+ if (_cursor < length && _templateData[_cursor] == '\n')
+ _cursor++;
+
+ return;
+ }
+
+ // Not an end-of-line character.
+
+ }
+
+ private void advanceOverWhitespace()
+ {
+ int length = _templateData.length;
+
+ while (_cursor < length)
+ {
+ char ch = _templateData[_cursor];
+ if (!Character.isWhitespace(ch))
+ return;
+
+ advance();
+ }
+ }
+
+ /**
+ * Returns a new Map that is a copy of the input Map with some
+ * key/value pairs removed. A list of keys is passed in
+ * and matching keys (caseless comparison) from the input
+ * Map are excluded from the output map. May return null
+ * (rather than return an empty Map).
+ *
+ **/
+
+ private Map filter(Map input, String[] removeKeys)
+ {
+ if (input == null || input.isEmpty())
+ return null;
+
+ Map result = null;
+
+ Iterator i = input.entrySet().iterator();
+
+ nextkey : while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String key = (String) entry.getKey();
+
+ for (int j = 0; j < removeKeys.length; j++)
+ {
+ if (key.equalsIgnoreCase(removeKeys[j]))
+ continue nextkey;
+ }
+
+ if (result == null)
+ result = new HashMap(input.size());
+
+ result.put(key, entry.getValue());
+ }
+
+ return result;
+ }
+
+ /**
+ * Searches a Map for given key, caselessly. The Map is expected to consist of Strings for keys and
+ * values. Returns the value for the first key found that matches (caselessly) the input key. Returns null
+ * if no value found.
+ *
+ **/
+
+ protected String findValueCaselessly(String key, Map map)
+ {
+ String result = (String) map.get(key);
+
+ if (result != null)
+ return result;
+
+ Iterator i = map.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String entryKey = (String) entry.getKey();
+
+ if (entryKey.equalsIgnoreCase(key))
+ return (String) entry.getValue();
+ }
+
+ return null;
+ }
+
+ /**
+ * Conversions needed by {@link #extractExpression(String)}
+ *
+ **/
+
+ private static final String[] CONVERSIONS = { "<", "<", ">", ">", """, "\"", "&", "&" };
+
+ /**
+ * Provided a raw input string that has been recognized to be an expression,
+ * this removes excess white space and converts &amp;, &quot; &lt; and &gt;
+ * to their normal character values (otherwise its impossible to specify
+ * those values in expressions in the template).
+ *
+ **/
+
+ private String extractExpression(String input)
+ {
+ int inputLength = input.length();
+
+ StringBuffer buffer = new StringBuffer(inputLength);
+
+ int cursor = 0;
+
+ outer : while (cursor < inputLength)
+ {
+ for (int i = 0; i < CONVERSIONS.length; i += 2)
+ {
+ String entity = CONVERSIONS[i];
+ int entityLength = entity.length();
+ String value = CONVERSIONS[i + 1];
+
+ if (cursor + entityLength > inputLength)
+ continue;
+
+ if (input.substring(cursor, cursor + entityLength).equals(entity))
+ {
+ buffer.append(value);
+ cursor += entityLength;
+ continue outer;
+ }
+ }
+
+ buffer.append(input.charAt(cursor));
+ cursor++;
+ }
+
+ return buffer.toString().trim();
+ }
+
+ /**
+ * Returns true if the map contains the given key (caseless search) and the value
+ * is "true" (caseless comparison).
+ *
+ **/
+
+ private boolean checkBoolean(String key, Map map)
+ {
+ String value = findValueCaselessly(key, map);
+
+ if (value == null)
+ return false;
+
+ return value.equalsIgnoreCase("true");
+ }
+
+ /**
+ * Gets the current location within the file. This allows the location to be
+ * created only as needed, and multiple objects on the same line can share
+ * the same Location instance.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected ILocation getCurrentLocation()
+ {
+ if (_currentLocation == null)
+ _currentLocation = new Location(_resourceLocation, _line);
+
+ return _currentLocation;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TemplateToken.java b/tapestry-framework/src/org/apache/tapestry/parse/TemplateToken.java
new file mode 100644
index 0000000..cc21928
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TemplateToken.java
@@ -0,0 +1,77 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocation;
+
+/**
+ * Base class for a number of different types of tokens that can be extracted
+ * from a page/component template. This class defines the
+ * type of the token,
+ * subclasses provide interpretations on the token.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class TemplateToken implements ILocatable
+{
+ private TokenType _type;
+ private ILocation _location;
+
+ protected TemplateToken(TokenType type, ILocation location)
+ {
+ _type = type;
+ _location = location;
+ }
+
+ public TokenType getType()
+ {
+ return _type;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("type", _type.getName());
+ builder.append("location", _location);
+
+ extendDescription(builder);
+
+ return builder.toString();
+ }
+
+ /**
+ * Overridden in subclasses to append additional fields (defined in the subclass)
+ * to the description. Subclasses may override this method without invoking
+ * this implementation, which is empty.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TextToken.java b/tapestry-framework/src/org/apache/tapestry/parse/TextToken.java
new file mode 100644
index 0000000..4840891
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TextToken.java
@@ -0,0 +1,183 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Represents static text in the template that may be passed through
+ * to the client unchanged (except, perhaps, for the removal of
+ * some whitespace).
+ *
+ * @see TokenType#TEXT
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class TextToken extends TemplateToken implements IRender
+{
+ private char[] _templateData;
+
+ private int _startIndex = -1;
+ private int _endIndex = -1;
+
+ private int _offset;
+ private int _length;
+ private boolean _needsTrim = true;
+
+ public TextToken(char[] templateData, int startIndex, int endIndex, ILocation location)
+ {
+ super(TokenType.TEXT, location);
+
+ if (startIndex < 0
+ || endIndex < 0
+ || startIndex > templateData.length
+ || endIndex > templateData.length)
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "TextToken.range-error",
+ this,
+ Integer.toString(templateData.length)));
+
+ _templateData = templateData;
+ _startIndex = startIndex;
+ _endIndex = endIndex;
+
+ // Values actually used to render, may be adjusted to remove excess
+ // leading and trailing whitespace.
+
+ _offset = startIndex;
+ _length = endIndex - startIndex + 1;
+ }
+
+ public synchronized void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (_needsTrim)
+ {
+ trim();
+ _needsTrim = false;
+ }
+
+ if (_length == 0)
+ return;
+
+ // At one time, we would check to see if the cycle was rewinding and
+ // only invoke printRaw() if it was. However, that slows down
+ // normal rendering (microscopically) and, with the new
+ // NullResponseWriter class, the "cost" of invoking cycle.isRewinding()
+ // is approximately the same as the "cost" of invoking writer.printRaw().
+
+ writer.printRaw(_templateData, _offset, _length);
+ }
+
+ /**
+ * Strip off all leading and trailing whitespace by adjusting offset and length.
+ *
+ **/
+
+ private void trim()
+ {
+ if (_length == 0)
+ return;
+
+ try
+ {
+ boolean didTrim = false;
+
+ // Shave characters off the end until we hit a non-whitespace
+ // character.
+
+ while (_length > 0)
+ {
+ char ch = _templateData[_offset + _length - 1];
+
+ if (!Character.isWhitespace(ch))
+ break;
+
+ _length--;
+ didTrim = true;
+ }
+
+ // Restore one character of whitespace to the end
+
+ if (didTrim)
+ _length++;
+
+ didTrim = false;
+
+ // Strip characters off the front until we hit a non-whitespace
+ // character.
+
+ while (_length > 0)
+ {
+ char ch = _templateData[_offset];
+
+ if (!Character.isWhitespace(ch))
+ break;
+
+ _offset++;
+ _length--;
+ didTrim = true;
+ }
+
+ // Again, restore one character of whitespace.
+
+ if (didTrim)
+ {
+ _offset--;
+ _length++;
+ }
+
+ }
+ catch (IndexOutOfBoundsException ex)
+ {
+ throw new RuntimeException(Tapestry.format("TextToken.error-trimming", this));
+ }
+
+ // Ok, this isn't perfect. I don't want to write into templateData[] even
+ // though I'd prefer that my single character of whitespace was always a space.
+ // It would also be kind of neat to shave whitespace within the static HTML, rather
+ // than just on the edges.
+ }
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ builder.append("startIndex", _startIndex);
+ builder.append("endIndex", _endIndex);
+ }
+
+ public int getEndIndex()
+ {
+ return _endIndex;
+ }
+
+ public int getStartIndex()
+ {
+ return _startIndex;
+ }
+
+ public char[] getTemplateData()
+ {
+ return _templateData;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/TokenType.java b/tapestry-framework/src/org/apache/tapestry/parse/TokenType.java
new file mode 100644
index 0000000..20c8540
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/TokenType.java
@@ -0,0 +1,74 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * An {@link Enum} of the different possible token types.
+ *
+ * @see TemplateToken
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class TokenType extends Enum
+{
+ /**
+ * Raw HTML text.
+ *
+ * @see TextToken
+ *
+ *
+ **/
+
+ public static final TokenType TEXT = new TokenType("TEXT");
+
+ /**
+ * The opening tag of an element.
+ *
+ * @see OpenToken
+ *
+ **/
+
+ public static final TokenType OPEN = new TokenType("OPEN");
+
+ /**
+ * The closing tag of an element.
+ *
+ * @see CloseToken
+ *
+ **/
+
+ public static final TokenType CLOSE = new TokenType("CLOSE");
+
+ /**
+ *
+ * A reference to a localized string.
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ public static final TokenType LOCALIZATION = new TokenType("LOCALIZATION");
+
+ private TokenType(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ValidatePublicIdRule.java b/tapestry-framework/src/org/apache/tapestry/parse/ValidatePublicIdRule.java
new file mode 100644
index 0000000..254585e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ValidatePublicIdRule.java
@@ -0,0 +1,76 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.xml.sax.Attributes;
+
+/**
+ * Rule used to validate the public id of the document, ensuring that
+ * it is not null, and that it matches an expected value.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ValidatePublicIdRule extends BaseDocumentRule
+{
+ private String[] _publicIds;
+ private String _rootElement;
+
+ public ValidatePublicIdRule(String[] publicIds, String rootElement)
+ {
+ _publicIds = publicIds;
+ _rootElement = rootElement;
+ }
+
+ public void startDocument(String namespace, String name, Attributes attributes)
+ throws Exception
+ {
+ SpecificationDigester digester = getDigester();
+ IResourceLocation location = digester.getResourceLocation();
+
+ String publicId = digester.getPublicId();
+
+ // publicId will never be null because we use a validating parser.
+
+ for (int i = 0; i < _publicIds.length; i++)
+ {
+ if (_publicIds[i].equals(publicId))
+ {
+
+ if (!name.equals(_rootElement))
+ throw new DocumentParseException(
+ Tapestry.format(
+ "AbstractDocumentParser.incorrect-document-type",
+ _rootElement,
+ name),
+ location);
+
+ return;
+ }
+
+ }
+
+ throw new DocumentParseException(
+ Tapestry.format("AbstractDocumentParser.unknown-public-id", location, publicId),
+ location);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/ValidateRule.java b/tapestry-framework/src/org/apache/tapestry/parse/ValidateRule.java
new file mode 100644
index 0000000..ee07e5a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/ValidateRule.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.parse;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.RegexpMatcher;
+import org.apache.tapestry.util.xml.InvalidStringException;
+import org.xml.sax.Attributes;
+
+/**
+ * Validates that an attribute matches a specified pattern.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ValidateRule extends AbstractSpecificationRule
+{
+ private RegexpMatcher _matcher;
+ private String _attributeName;
+ private String _pattern;
+ private String _errorKey;
+
+ public ValidateRule(RegexpMatcher matcher, String attributeName, String pattern, String errorKey)
+ {
+ _matcher = matcher;
+ _attributeName = attributeName;
+ _pattern = pattern;
+ _errorKey = errorKey;
+ }
+
+ /**
+ * Validates that the attribute, if provided, matches the pattern.
+ *
+ * @throws InvalidStringException if the value does not match the pattern.
+ *
+ **/
+
+ public void begin(String namespace, String name, Attributes attributes) throws Exception
+ {
+ String value = getValue(attributes, _attributeName);
+ if (value == null)
+ return;
+
+ if (_matcher.matches(_pattern, value))
+ return;
+
+ throw new InvalidStringException(
+ Tapestry.format(_errorKey, value),
+ value,
+ getResourceLocation());
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/parse/package.html b/tapestry-framework/src/org/apache/tapestry/parse/package.html
new file mode 100644
index 0000000..1f6422c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/parse/package.html
@@ -0,0 +1,14 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Classes used when parsing templates, application and component specifications.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/record/ChangeKey.java b/tapestry-framework/src/org/apache/tapestry/record/ChangeKey.java
new file mode 100644
index 0000000..39e61b7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/ChangeKey.java
@@ -0,0 +1,92 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.record;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * Used to identify a property change.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ChangeKey
+{
+ private int _hashCode = -1;
+ private String _componentPath;
+ private String _propertyName;
+
+ public ChangeKey(String componentPath, String propertyName)
+ {
+ _componentPath = componentPath;
+ _propertyName = propertyName;
+ }
+
+ public boolean equals(Object object)
+ {
+ if (object == null)
+ return false;
+
+ if (this == object)
+ return true;
+
+ if (!(object instanceof ChangeKey))
+ return false;
+
+ ChangeKey other = (ChangeKey) object;
+
+ EqualsBuilder builder = new EqualsBuilder();
+
+ builder.append(_propertyName, other._propertyName);
+ builder.append(_componentPath, other._componentPath);
+
+ return builder.isEquals();
+ }
+
+ public String getComponentPath()
+ {
+ return _componentPath;
+ }
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+ /**
+ *
+ * Returns a hash code computed from the
+ * property name and component path.
+ *
+ **/
+
+ public int hashCode()
+ {
+ if (_hashCode == -1)
+ {
+ HashCodeBuilder builder = new HashCodeBuilder(257, 23); // Random
+
+ builder.append(_propertyName);
+ builder.append(_componentPath);
+
+ _hashCode = builder.toHashCode();
+ }
+
+ return _hashCode;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/record/IPageChange.java b/tapestry-framework/src/org/apache/tapestry/record/IPageChange.java
new file mode 100644
index 0000000..7866125
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/IPageChange.java
@@ -0,0 +1,48 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.record;
+
+/**
+ * Represents a change to a component on a page, this represents
+ * a datum of information stored by a {@link org.apache.tapestry.engine.IPageRecorder}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ **/
+
+public interface IPageChange
+{
+ /**
+ * The path to the component on the page, or null if the property is a property
+ * of the page.
+ *
+ **/
+
+ public String getComponentPath();
+
+ /**
+ * The new value for the property, which may be null.
+ *
+ **/
+
+ public Object getNewValue();
+
+ /**
+ * The name of the property that changed.
+ *
+ **/
+
+ public String getPropertyName();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/record/PageChange.java b/tapestry-framework/src/org/apache/tapestry/record/PageChange.java
new file mode 100644
index 0000000..fe196b0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/PageChange.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.record;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+
+/**
+ * Represents a change to a component on a page.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class PageChange implements IPageChange
+{
+ private String _componentPath;
+ private String _propertyName;
+ private Object _newValue;
+
+ public PageChange(String componentPath, String propertyName, Object newValue)
+ {
+ _componentPath = componentPath;
+ _propertyName = propertyName;
+ _newValue = newValue;
+ }
+
+ /**
+ * The path to the component on the page, or null if the property
+ * is a property of the page.
+ *
+ **/
+
+ public String getComponentPath()
+ {
+ return _componentPath;
+ }
+
+ /**
+ * The new value for the property, which may be null.
+ *
+ **/
+
+ public Object getNewValue()
+ {
+ return _newValue;
+ }
+
+ /**
+ * The name of the property that changed.
+ *
+ **/
+
+ public String getPropertyName()
+ {
+ return _propertyName;
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("componentPath", _componentPath);
+ builder.append("propertyName", _propertyName);
+ builder.append("newValue", _newValue);
+
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/record/PageRecorder.java b/tapestry-framework/src/org/apache/tapestry/record/PageRecorder.java
new file mode 100644
index 0000000..ad03155
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/PageRecorder.java
@@ -0,0 +1,238 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.record;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IPageRecorder;
+import org.apache.tapestry.event.ObservedChangeEvent;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Tracks changes to components on a page, allowing changes to be persisted across
+ * request cycles, and restoring the state of a page and component when needed.
+ *
+ * <p>This is an abstract implementation; specific implementations can choose where
+ * and how to persist the data.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class PageRecorder implements IPageRecorder
+{
+ private boolean _dirty = false;
+ private boolean _locked = false;
+ private boolean _discard = false;
+
+ /**
+ * Invoked to persist all changes that have been accumulated. If the recorder
+ * saves change incrementally, this should ensure that all changes have been persisted.
+ *
+ * <p>Subclasses should check the dirty flag. If the recorder is dirty, changes
+ * should be recorded and the dirty flag cleared.
+ *
+ **/
+
+ public abstract void commit();
+
+ /**
+ * Returns a <code>Collection</code> of
+ * {@link IPageChange} objects
+ * identifying changes to the page and its components.
+ *
+ **/
+
+ public abstract Collection getChanges();
+
+ /**
+ * Returns true if the page has observed a change.
+ * The dirty flag is cleared by
+ * {@link #commit()}.
+ *
+ **/
+
+ public boolean isDirty()
+ {
+ return _dirty;
+ }
+
+ /**
+ * Returns true if the recorder is locked. The locked flag
+ * is set by {@link #commit()}.
+ *
+ **/
+
+ public boolean isLocked()
+ {
+ return _locked;
+ }
+
+ public void setLocked(boolean value)
+ {
+ _locked = value;
+ }
+
+ /**
+ * Observes the change. The object of the event is expected to
+ * be an {@link IComponent}. Ignores the change if not active,
+ * otherwise, sets invokes {@link #recordChange(String, String,
+ * Object)}.
+ *
+ * <p>If the property name in the event is null, then the recorder
+ * is marked dirty (but
+ * {@link #recordChange(String, String,
+ * Object)} is not invoked. This is how a "distant" property changes
+ * are propogated to the page recorder (a distant property change is a change to
+ * a property of an object that is itself a property of the page).
+ *
+ * <p>If the recorder is not active (typically, when a page is
+ * being rewound), then the event is simply ignored.
+ *
+ **/
+
+ public void observeChange(ObservedChangeEvent event)
+ {
+ IComponent component = event.getComponent();
+ String propertyName = event.getPropertyName();
+
+ if (_locked)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageRecorder.change-after-lock",
+ component.getPage().getPageName(),
+ propertyName,
+ component.getExtendedId()));
+
+ if (propertyName == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PageRecorder.null-property-name", component.getExtendedId()));
+
+ Object activeValue = event.getNewValue();
+
+ try
+ {
+ recordChange(component.getIdPath(), propertyName, activeValue);
+ }
+ catch (Throwable t)
+ {
+ t.printStackTrace();
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageRecorder.unable-to-persist",
+ propertyName,
+ component.getExtendedId(),
+ activeValue),
+ t);
+ }
+ }
+
+ /**
+ * Records a change to a particular component. Subclasses may
+ * cache these in memory, or record them externally at this time.
+ *
+ * <p>This method is responsible for setting the dirty flag if
+ * the described change is real.
+ *
+ * @param componentPath the name of the component relative to the
+ * page which contains it. May be null if the change was to a
+ * property of the page itself.
+ *
+ * @param propertyName the name of the property which changed.
+ *
+ * @param newValue the new value for the property, which may also
+ * be null.
+ *
+ * @see IComponent#getIdPath()
+ *
+ **/
+
+ protected abstract void recordChange(
+ String componentPath,
+ String propertyName,
+ Object newValue);
+
+ /**
+ * Rolls back the page to the currently persisted state.
+ *
+ **/
+
+ public void rollback(IPage page)
+ {
+ Collection changes = getChanges();
+
+ if (changes.isEmpty())
+ return;
+
+ IResourceResolver resolver = page.getEngine().getResourceResolver();
+ Iterator i = changes.iterator();
+
+ while (i.hasNext())
+ {
+ PageChange change = (PageChange) i.next();
+
+ String propertyName = change.getPropertyName();
+
+ IComponent component = page.getNestedComponent(change.getComponentPath());
+
+ Object storedValue = change.getNewValue();
+
+ try
+ {
+ OgnlUtils.set(propertyName, resolver, component, storedValue);
+ }
+ catch (Throwable t)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PageRecorder.unable-to-rollback",
+ new Object[] { propertyName, component, storedValue, t.getMessage()}),
+ t);
+ }
+ }
+ }
+
+ /** @since 2.0.2 **/
+
+ public boolean isMarkedForDiscard()
+ {
+ return _discard;
+ }
+
+ /** @since 2.0.2 **/
+
+ public void markForDiscard()
+ {
+ _discard = true;
+ }
+
+ protected void setDirty(boolean dirty)
+ {
+ _dirty = dirty;
+ }
+
+ protected boolean getDirty()
+ {
+ return _dirty;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/record/SessionPageRecorder.java b/tapestry-framework/src/org/apache/tapestry/record/SessionPageRecorder.java
new file mode 100644
index 0000000..a3fc705
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/SessionPageRecorder.java
@@ -0,0 +1,248 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.record;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.request.RequestContext;
+import org.apache.tapestry.util.StringSplitter;
+
+/**
+ * Simple implementation of {@link org.apache.tapestry.engine.IPageRecorder}that stores page
+ * changes as {@link javax.servlet.http.HttpSession}attributes.
+ *
+ * @author Howard Ship
+ * @version $Id$
+ */
+
+public class SessionPageRecorder extends PageRecorder
+{
+ private static final Log LOG = LogFactory.getLog(SessionPageRecorder.class);
+
+ /**
+ * Dictionary of changes, keyed on an instance of {@link ChangeKey}(which enapsulates component
+ * path and property name). The value is the new value for the object. The same information is
+ * stored into the {@link HttpSession}, which is used as a kind of write-behind cache.
+ */
+
+ private Map _changes;
+
+ /**
+ * The session into which changes are recorded.
+ *
+ * @since 3.0
+ */
+
+ private HttpSession _session;
+
+ /**
+ * The fully qualified name of the page being recorded.
+ *
+ * @since 3.0
+ */
+
+ private String _pageName;
+
+ /**
+ * The prefix (for {@link HttpSession}attributes) used by this page recorder.
+ */
+
+ private String _attributePrefix;
+
+ public void initialize(String pageName, IRequestCycle cycle)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Initializing for " + pageName);
+
+ RequestContext context = cycle.getRequestContext();
+
+ _pageName = pageName;
+ _session = context.getSession();
+
+ _attributePrefix = context.getServlet().getServletName() + "/" + _pageName + "/";
+
+ restorePageChanges();
+ }
+
+ public void discard()
+ {
+ if (Tapestry.isEmpty(_changes))
+ return;
+
+ Iterator i = _changes.keySet().iterator();
+
+ while (i.hasNext())
+ {
+ ChangeKey key = (ChangeKey) i.next();
+
+ String attributeKey = constructAttributeKey(key.getComponentPath(), key
+ .getPropertyName());
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Removing session attribute " + attributeKey);
+
+ _session.removeAttribute(attributeKey);
+ }
+ }
+
+ /**
+ * Simply clears the dirty flag, because there is no external place to store changed page
+ * properties. Sets the locked flag to prevent subsequent changes from occuring now.
+ */
+
+ public void commit()
+ {
+ setDirty(false);
+ setLocked(true);
+ }
+
+ /**
+ * Returns true if the recorder has any changes recorded.
+ */
+
+ public boolean getHasChanges()
+ {
+ if (_changes == null)
+ return false;
+
+ return (_changes.size() > 0);
+ }
+
+ public Collection getChanges()
+ {
+ if (_changes == null)
+ return Collections.EMPTY_LIST;
+
+ int count = _changes.size();
+ Collection result = new ArrayList(count);
+
+ Iterator i = _changes.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ ChangeKey key = (ChangeKey) entry.getKey();
+
+ Object value = entry.getValue();
+
+ PageChange change = new PageChange(key.getComponentPath(), key.getPropertyName(), value);
+
+ result.add(change);
+ }
+
+ return result;
+ }
+
+ protected void recordChange(String componentPath, String propertyName, Object newValue)
+ {
+ ChangeKey key = new ChangeKey(componentPath, propertyName);
+
+ if (_changes == null)
+ _changes = new HashMap();
+
+ setDirty(true);
+
+ _changes.put(key, newValue);
+
+ // Now, build a key used to store the new value
+ // in the HttpSession
+
+ String attributeKey = constructAttributeKey(componentPath, propertyName);
+
+ if (newValue == null)
+ _session.removeAttribute(attributeKey);
+ else
+ _session.setAttribute(attributeKey, newValue);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Stored session attribute " + attributeKey + " = " + newValue);
+ }
+
+ private String constructAttributeKey(String componentPath, String propertyName)
+ {
+ StringBuffer buffer = new StringBuffer(_attributePrefix);
+
+ if (componentPath != null)
+ {
+ buffer.append(componentPath);
+ buffer.append('/');
+ }
+
+ buffer.append(propertyName);
+
+ return buffer.toString();
+ }
+
+ private void restorePageChanges()
+ {
+ int count = 0;
+ Enumeration e = _session.getAttributeNames();
+ StringSplitter splitter = null;
+
+ while (e.hasMoreElements())
+ {
+ String key = (String) e.nextElement();
+
+ if (!key.startsWith(_attributePrefix))
+ continue;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Restoring page change from session attribute " + key);
+
+ if (_changes == null)
+ {
+ _changes = new HashMap();
+
+ splitter = new StringSplitter('/');
+ }
+
+ String[] names = splitter.splitToArray(key);
+
+ // The first name is the servlet name, which allows
+ // multiple Tapestry apps to share a HttpSession, even
+ // when they use the same page names. The second name
+ // is the page name, which we already know.
+
+ int i = 2;
+
+ String componentPath = (names.length == 4) ? names[i++] : null;
+ String propertyName = names[i++];
+ Object value = _session.getAttribute(key);
+
+ ChangeKey changeKey = new ChangeKey(componentPath, propertyName);
+
+ _changes.put(changeKey, value);
+
+ count++;
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(count == 0 ? "No recorded changes." : "Restored " + count
+ + " recorded changes.");
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/record/package.html b/tapestry-framework/src/org/apache/tapestry/record/package.html
new file mode 100644
index 0000000..54a0a95
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/record/package.html
@@ -0,0 +1,15 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Abstract and simple (memory-based) implementations of
+{@link org.apache.tapestry.engine.IPageRecorder}.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/request/DecodedRequest.java b/tapestry-framework/src/org/apache/tapestry/request/DecodedRequest.java
new file mode 100644
index 0000000..5c2101b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/request/DecodedRequest.java
@@ -0,0 +1,85 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.request;
+
+/**
+ * Contains properties of an {@link javax.servlet.http.HttpServletRequest}
+ * that have been extracted from the request (or otherwise determined).
+ *
+ * <p>An alternative idea would have been to create a new
+ * {@link javax.servlet.http.HttpServletRequest}
+ * wrapper that overode the various methods. That struck me as causing
+ * more confusion; instead (in the few places it counts), classes will
+ * get the decoded properties from the {@link RequestContext}.
+ *
+ * @see IRequestDecoder
+ * @see RequestContext#getScheme()
+ * @see RequestContext#getServerName()
+ * @see RequestContext#getServerPort()
+ * @see RequestContext#getRequestURI()
+ *
+ * @author Howard Lewis Ship
+ * @version DecodedRequest.java,v 1.1 2002/08/20 21:49:58 hship Exp
+ * @since 2.2
+ *
+ **/
+
+public class DecodedRequest
+{
+ private String _scheme;
+ private String _serverName;
+ private String _requestURI;
+ private int _serverPort;
+
+ public int getServerPort()
+ {
+ return _serverPort;
+ }
+
+ public String getScheme()
+ {
+ return _scheme;
+ }
+
+ public String getServerName()
+ {
+ return _serverName;
+ }
+
+ public String getRequestURI()
+ {
+ return _requestURI;
+ }
+
+ public void setServerPort(int serverPort)
+ {
+ _serverPort = serverPort;
+ }
+
+ public void setScheme(String scheme)
+ {
+ _scheme = scheme;
+ }
+
+ public void setServerName(String serverName)
+ {
+ _serverName = serverName;
+ }
+
+ public void setRequestURI(String URI)
+ {
+ _requestURI = URI;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/request/IRequestDecoder.java b/tapestry-framework/src/org/apache/tapestry/request/IRequestDecoder.java
new file mode 100644
index 0000000..5743019
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/request/IRequestDecoder.java
@@ -0,0 +1,44 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.request;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Given a {@link javax.servlet.http.HttpServletRequest}, identifies
+ * the correct request properties (server, scheme, URI and port).
+ *
+ * <p>An implementation of this class may be necessary when using
+ * Tapestry with specific firewalls which may obscure
+ * the scheme, server, etc. visible to the client web browser
+ * (the request appears to arrive from the firewall server, not the
+ * client web browser).
+ *
+ * @author Howard Lewis Ship
+ * @version IRequestDecoder.java,v 1.1 2002/08/20 21:49:58 hship Exp
+ * @since 2.2
+ *
+ **/
+
+public interface IRequestDecoder
+{
+
+ /**
+ * Invoked to identify the actual properties from the request.
+ *
+ **/
+
+ public DecodedRequest decodeRequest(HttpServletRequest request);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/request/IUploadFile.java b/tapestry-framework/src/org/apache/tapestry/request/IUploadFile.java
new file mode 100644
index 0000000..951271a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/request/IUploadFile.java
@@ -0,0 +1,99 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.request;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * Represents a file uploaded from a client side form.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public interface IUploadFile
+{
+ /**
+ * Returns the name of the file that was uploaded. This
+ * is just the filename portion of the complete path.
+ *
+ **/
+
+ public String getFileName();
+
+ /**
+ * Returns the complete path, as reported by the client
+ * browser. Different browsers report different things
+ * here.
+ *
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ public String getFilePath();
+
+ /**
+ * Returns an input stream of the content of the file. There is no guarantee
+ * that this stream will be valid after the end of the current request cycle,
+ * so it should be processed immediately.
+ *
+ * <p>As of release 1.0.8, this will be a a {@link java.io.ByteArrayInputStream},
+ * but that, too, may change (a future implementation may upload the stream
+ * to a temporary file and return an input stream from that).
+ *
+ **/
+
+ public InputStream getStream();
+
+ /**
+ * Returns the MIME type specified when the file was uploaded. May return null
+ * if the content type is not known.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public String getContentType();
+
+ /**
+ * Writes the content of the file to a known location. This should
+ * be invoked at most once. In a standard
+ * implementation based on Jakarta FileUpload, this will often
+ * be implemented efficiently as a file rename.
+ *
+ * @since 3.0
+ */
+
+ public void write(File file);
+
+ /**
+ * Returns true if the uploaded content is in memory. False generally
+ * means the content is stored in a temporary file.
+ */
+
+ public boolean isInMemory();
+
+ /**
+ * Returns the size, in bytes, of the uploaded content.
+ *
+ * @since 3.0
+ **/
+
+ public long getSize();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/request/RequestContext.java b/tapestry-framework/src/org/apache/tapestry/request/RequestContext.java
new file mode 100644
index 0000000..edffc0c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/request/RequestContext.java
@@ -0,0 +1,1096 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.request;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationServlet;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.multipart.DefaultMultipartDecoder;
+import org.apache.tapestry.multipart.IMultipartDecoder;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.util.IRenderDescription;
+
+/**
+ * This class encapsulates all the relevant data for one request cycle of an
+ * {@link ApplicationServlet}. This includes:
+ * <ul>
+ * <li>{@link HttpServletRequest}
+ * <li>{@link HttpServletResponse}
+ * <li>{@link HttpSession}
+ * <li>{@link javax.servlet.http.HttpServlet}
+ * </ul>
+ * <p>It also provides methods for:
+ * <ul>
+ * <li>Retrieving the request parameters (even if a file upload is involved)
+ * <li>Getting, setting and removing request attributes
+ * <li>Forwarding requests
+ * <li>Redirecting requests
+ * <li>Getting and setting Cookies
+ * <li>Intepreting the request path info
+ * <li>Writing an HTML description of the <code>RequestContext</code> (for debugging).
+ * </ul>
+ *
+ *
+ * <p>
+ * If some cases, it is necesary to provide an implementation of
+ * {@link IRequestDecoder} (often, due to a firewall).
+ * If the application specifification
+ * provides an extension named
+ * <code>org.apache.tapestry.request-decoder</code>
+ * then it will be used, instead of a default decoder.
+ *
+ * <p>This class is not a component, but does implement {@link IRender}. When asked to render
+ * (perhaps as the delegate of a {@link org.apache.tapestry.components.Delegator} component}
+ * it simply invokes {@link #write(IMarkupWriter)} to display all debugging output.
+ *
+ * <p>This class is derived from the original class
+ * <code>com.primix.servlet.RequestContext</code>,
+ * part of the <b>ServletUtils</b> framework available from
+ * <a href="http://www.gjt.org/servlets/JCVSlet/list/gjt/com/primix/servlet">The Giant
+ * Java Tree</a>.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class RequestContext implements IRender
+{
+ /** @since 2.2 **/
+
+ private static class DefaultRequestDecoder implements IRequestDecoder
+ {
+ public DecodedRequest decodeRequest(HttpServletRequest request)
+ {
+ DecodedRequest result = new DecodedRequest();
+
+ result.setRequestURI(request.getRequestURI());
+ result.setScheme(request.getScheme());
+ result.setServerName(request.getServerName());
+ result.setServerPort(request.getServerPort());
+
+ return result;
+ }
+ }
+
+ private static final Log LOG = LogFactory.getLog(RequestContext.class);
+
+ private HttpSession _session;
+ private HttpServletRequest _request;
+ private HttpServletResponse _response;
+ private ApplicationServlet _servlet;
+ private DecodedRequest _decodedRequest;
+ private IMultipartDecoder _decoder;
+ private boolean _decoded;
+
+ /**
+ * A mapping of the cookies available in the request.
+ *
+ **/
+
+ private Map _cookieMap;
+
+ /**
+ * Used during {@link #write(IMarkupWriter)}.
+ *
+ **/
+
+ private boolean _evenRow;
+
+ /**
+ * Creates a <code>RequestContext</code> from its components.
+ *
+ **/
+
+ public RequestContext(
+ ApplicationServlet servlet,
+ HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException
+ {
+ _servlet = servlet;
+ _request = request;
+ _response = response;
+
+ // All three parameters may be null if created from
+ // AbstractEngine.cleanupEngine().
+
+ if (_request != null && DefaultMultipartDecoder.isMultipartRequest(request))
+ {
+ IMultipartDecoder decoder = obtainMultipartDecoder(servlet, request);
+ setDecoder(decoder);
+ }
+ }
+
+ /**
+ * Invoked from the constructor to create a {@link DefaultMultipartDecoder} instance.
+ * Applications with specific upload needs may need to override this to
+ * provide a subclass instance instead. The caller will invoke
+ * {@link IMultipartDecoder#decode(HttpServletRequest)} on the
+ * returned object.
+ *
+ * <p>
+ * This implementation checks for application extension
+ * {@link Tapestry#MULTIPART_DECODER_EXTENSION_NAME}. If that is not
+ * defined, a shared instance of {@link DefaultMultipartDecoder}
+ * is returned.
+ *
+ *
+ * @see ApplicationServlet#createRequestContext(HttpServletRequest, HttpServletResponse)
+ * @since 3.0
+ *
+ **/
+
+ protected IMultipartDecoder obtainMultipartDecoder(
+ ApplicationServlet servlet,
+ HttpServletRequest request)
+ throws IOException
+ {
+ IApplicationSpecification spec = servlet.getApplicationSpecification();
+
+ if (spec.checkExtension(Tapestry.MULTIPART_DECODER_EXTENSION_NAME))
+ return (IMultipartDecoder) spec.getExtension(
+ Tapestry.MULTIPART_DECODER_EXTENSION_NAME,
+ IMultipartDecoder.class);
+
+ return DefaultMultipartDecoder.getSharedInstance();
+ }
+
+ /**
+ * Adds a simple {@link Cookie}. To set a Cookie with attributes,
+ * use {@link #addCookie(Cookie)}.
+ *
+ **/
+
+ public void addCookie(String name, String value)
+ {
+ addCookie(new Cookie(name, value));
+ }
+
+ /**
+ * Adds a {@link Cookie} to the response. Once added, the
+ * Cookie will also be available to {@link #getCookie(String)} method.
+ *
+ * <p>Cookies should only be added <em>before</em> invoking
+ * {@link HttpServletResponse#getWriter()}..
+ *
+ **/
+
+ public void addCookie(Cookie cookie)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Adding cookie " + cookie);
+
+ _response.addCookie(cookie);
+
+ if (_cookieMap == null)
+ readCookieMap();
+
+ _cookieMap.put(cookie.getName(), cookie);
+ }
+
+ private void datePair(IMarkupWriter writer, String name, long value)
+ {
+ pair(writer, name, new Date(value));
+ }
+
+ /** @since 2.2 **/
+
+ private DecodedRequest getDecodedRequest()
+ {
+ if (_decodedRequest != null)
+ return _decodedRequest;
+
+ IApplicationSpecification spec = _servlet.getApplicationSpecification();
+ IRequestDecoder decoder = null;
+
+ if (!spec.checkExtension(Tapestry.REQUEST_DECODER_EXTENSION_NAME))
+ decoder = new DefaultRequestDecoder();
+ else
+ decoder =
+ (IRequestDecoder) spec.getExtension(
+ Tapestry.REQUEST_DECODER_EXTENSION_NAME,
+ IRequestDecoder.class);
+
+ _decodedRequest = decoder.decodeRequest(_request);
+
+ return _decodedRequest;
+ }
+
+ /**
+ *
+ * Returns the actual scheme, possibly decoded from the request.
+ *
+ * @see IRequestDecoder
+ * @see javax.servlet.ServletRequest#getScheme()
+ * @since 2.2
+ *
+ **/
+
+ public String getScheme()
+ {
+ return getDecodedRequest().getScheme();
+ }
+
+ /**
+ *
+ * Returns the actual server name, possibly decoded from the request.
+ *
+ * @see IRequestDecoder
+ * @see javax.servlet.ServletRequest#getServerName()
+ * @since 2.2
+ *
+ **/
+
+ public String getServerName()
+ {
+ return getDecodedRequest().getServerName();
+ }
+
+ /**
+ *
+ * Returns the actual server port, possibly decoded from the request.
+ *
+ * @see IRequestDecoder
+ * @see javax.servlet.ServletRequest#getServerPort()
+ * @since 2.2
+ *
+ **/
+
+ public int getServerPort()
+ {
+ return getDecodedRequest().getServerPort();
+ }
+
+ /**
+ *
+ * Returns the actual request URI, possibly decoded from the request.
+ *
+ * @see IRequestDecoder
+ * @see HttpServletRequest#getRequestURI()
+ * @since 2.2
+ *
+ **/
+
+ public String getRequestURI()
+ {
+ return getDecodedRequest().getRequestURI();
+ }
+
+ /**
+ * Builds an absolute URL from the given URI, using the {@link HttpServletRequest}
+ * as the source for scheme, server name and port.
+ *
+ * @see #getAbsoluteURL(String, String, String, int)
+ *
+ **/
+
+ public String getAbsoluteURL(String URI)
+ {
+ String scheme = getScheme();
+ String server = getServerName();
+ int port = getServerPort();
+
+ // Keep things simple ... port 80 is accepted as the
+ // standard port for http so it can be ommitted.
+ // Some of the Tomcat code indicates that port 443 is the default
+ // for https, and that needs to be researched.
+
+ if (scheme.equals("http") && port == 80)
+ port = 0;
+
+ return getAbsoluteURL(URI, scheme, server, port);
+ }
+
+ /**
+ * Does some easy checks to turn a path (or URI) into an absolute URL. We assume
+ * <ul>
+ * <li>The presense of a colon means the path is complete already (any other colons
+ * in the URI portion should have been converted to %3A).
+ *
+ * <li>A leading pair of forward slashes means the path is simply missing
+ * the scheme.
+ * <li>Otherwise, we assemble the scheme, server, port (if non-zero) and the URI
+ * as given.
+ * </ul>
+ *
+ **/
+
+ public String getAbsoluteURL(String URI, String scheme, String server, int port)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ // Though, really, what does a leading colon with no scheme before it
+ // mean?
+
+ if (URI.indexOf(':') >= 0)
+ return URI;
+
+ // Should check the length here, first.
+
+ if (URI.substring(0, 1).equals("//"))
+ {
+ buffer.append(scheme);
+ buffer.append(':');
+ buffer.append(URI);
+ return buffer.toString();
+ }
+
+ buffer.append(scheme);
+ buffer.append("://");
+ buffer.append(server);
+
+ if (port > 0)
+ {
+ buffer.append(':');
+ buffer.append(port);
+ }
+
+ if (URI.charAt(0) != '/')
+ buffer.append('/');
+
+ buffer.append(URI);
+
+ return buffer.toString();
+ }
+
+ /**
+ * Gets a named {@link Cookie}.
+ *
+ * @param name The name of the Cookie.
+ * @return The Cookie, or null if no Cookie with that
+ * name exists.
+ *
+ **/
+
+ public Cookie getCookie(String name)
+ {
+ if (_cookieMap == null)
+ readCookieMap();
+
+ return (Cookie) _cookieMap.get(name);
+ }
+
+ /**
+ * Reads the named {@link Cookie} and returns its value (if it exists), or
+ * null if it does not exist.
+ **/
+
+ public String getCookieValue(String name)
+ {
+ Cookie cookie;
+
+ cookie = getCookie(name);
+
+ if (cookie == null)
+ return null;
+
+ return cookie.getValue();
+ }
+
+ /**
+ * Returns the named parameter from the {@link HttpServletRequest}.
+ *
+ * <p>Use {@link #getParameters(String)} for parameters that may
+ * include multiple values.
+ *
+ * <p>This is the preferred way to obtain parameter values (rather than
+ * obtaining the {@link HttpServletRequest} itself). For form/multipart-data
+ * encoded requests, this method will still work.
+ *
+ **/
+
+ public String getParameter(String name)
+ {
+ IMultipartDecoder decoder = getDecoder();
+ if (decoder != null)
+ return decoder.getString(_request, name);
+
+ return _request.getParameter(name);
+ }
+
+ /**
+ * Convienience method for getting a {@link HttpServletRequest} attribute.
+ *
+ * @since 2.3
+ *
+ **/
+
+ public Object getAttribute(String name)
+ {
+ return _request.getAttribute(name);
+ }
+
+ /**
+ * For parameters that are, or are possibly, multi-valued, this
+ * method returns all the values as an array of Strings.
+ *
+ * @see #getParameter(String)
+ *
+ **/
+
+ public String[] getParameters(String name)
+ {
+ // Note: this may not be quite how we want it to work; we'll have to see.
+
+ IMultipartDecoder decoder = getDecoder();
+ if (decoder != null)
+ return decoder.getStrings(_request, name);
+
+ return _request.getParameterValues(name);
+ }
+
+ /**
+ * Returns the named {@link IUploadFile}, if it exists, or null if it doesn't.
+ * Uploads require an encoding of <code>multipart/form-data</code>
+ * (this is specified in the
+ * form's enctype attribute). If the encoding type
+ * is not so, or if no upload matches the name, then this method returns null.
+ *
+ **/
+
+ public IUploadFile getUploadFile(String name)
+ {
+ IMultipartDecoder decoder = getDecoder();
+ if (decoder == null)
+ return null;
+
+ return decoder.getUploadFile(_request, name);
+ }
+
+ /**
+ * Invoked at the end of the request cycle to cleanup and temporary resources.
+ * This is chained to the {@link DefaultMultipartDecoder}, if there is one.
+ *
+ * @since 2.0.1
+ **/
+
+ public void cleanup()
+ {
+ if (_decoder != null)
+ _decoder.cleanup(_request);
+ }
+
+ /**
+ * Returns the request which initiated the current request cycle. Note that
+ * the methods {@link #getParameter(String)} and {@link #getParameters(String)}
+ * should be used, rather than obtaining parameters directly from the request
+ * (since the RequestContext handles the differences between normal and multipart/form
+ * requests).
+ *
+ **/
+
+ public HttpServletRequest getRequest()
+ {
+ return _request;
+ }
+
+ public HttpServletResponse getResponse()
+ {
+ return _response;
+ }
+
+ private String getRowClass()
+ {
+ String result;
+
+ result = _evenRow ? "even" : "odd";
+
+ _evenRow = !_evenRow;
+
+ return result;
+ }
+
+ public ApplicationServlet getServlet()
+ {
+ return _servlet;
+ }
+
+ /**
+ * Returns the {@link HttpSession}, if necessary, invoking
+ * {@link HttpServletRequest#getSession(boolean)}. However,
+ * this method will <em>not</em> create a session.
+ *
+ **/
+
+ public HttpSession getSession()
+ {
+ if (_session == null)
+ _session = _request.getSession(false);
+
+ return _session;
+ }
+
+ /**
+ * Like {@link #getSession()}, but forces the creation of
+ * the {@link HttpSession}, if necessary.
+ *
+ **/
+
+ public HttpSession createSession()
+ {
+ if (_session == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Creating HttpSession");
+
+ _session = _request.getSession(true);
+ }
+
+ return _session;
+ }
+
+ private void header(IMarkupWriter writer, String valueName, String dataName)
+ {
+ writer.begin("tr");
+ writer.attribute("class", "request-context-header");
+
+ writer.begin("th");
+ writer.print(valueName);
+ writer.end();
+
+ writer.begin("th");
+ writer.print(dataName);
+ writer.end("tr");
+
+ _evenRow = true;
+ }
+
+ private void object(IMarkupWriter writer, String objectName)
+ {
+ writer.begin("span");
+ writer.attribute("class", "request-context-object");
+ writer.print(objectName);
+ writer.end();
+ }
+
+ private void pair(IMarkupWriter writer, String name, int value)
+ {
+ pair(writer, name, Integer.toString(value));
+ }
+
+ private void pair(IMarkupWriter writer, String name, Object value)
+ {
+ if (value == null)
+ return;
+
+ if (value instanceof IRenderDescription)
+ {
+ IRenderDescription renderValue = (IRenderDescription) value;
+
+ writer.begin("tr");
+ writer.attribute("class", getRowClass());
+
+ writer.begin("th");
+ writer.print(name);
+ writer.end();
+
+ writer.begin("td");
+
+ renderValue.renderDescription(writer);
+
+ writer.end("tr");
+ writer.println();
+ return;
+ }
+
+ pair(writer, name, value.toString());
+ }
+
+ private void pair(IMarkupWriter writer, String name, String value)
+ {
+ if (value == null)
+ return;
+
+ if (value.length() == 0)
+ return;
+
+ writer.begin("tr");
+ writer.attribute("class", getRowClass());
+
+ writer.begin("th");
+ writer.print(name);
+ writer.end();
+
+ writer.begin("td");
+ writer.print(value);
+ writer.end("tr");
+ writer.println();
+ }
+
+ private void pair(IMarkupWriter writer, String name, boolean value)
+ {
+ pair(writer, name, value ? "yes" : "no");
+ }
+
+ private void readCookieMap()
+ {
+ _cookieMap = new HashMap();
+
+ Cookie[] cookies = _request.getCookies();
+
+ if (cookies != null)
+ for (int i = 0; i < cookies.length; i++)
+ _cookieMap.put(cookies[i].getName(), cookies[i]);
+ }
+
+ /**
+ * Invokes {@link HttpServletResponse#sendRedirect(String)}</code>,
+ * but massages <code>path</code>, supplying missing elements to
+ * make it an absolute URL (i.e., specifying scheme, server, port, etc.).
+ *
+ * <p>The 2.2 Servlet API will do this automatically, and a little more,
+ * according to the early documentation.
+ *
+ **/
+
+ public void redirect(String path) throws IOException
+ {
+ // Now a little magic to convert path into a complete URL. The Servlet
+ // 2.2 API does this automatically.
+
+ String absolutePath = getAbsoluteURL(path);
+
+ String encodedURL = _response.encodeRedirectURL(absolutePath);
+
+ _response.sendRedirect(encodedURL);
+ }
+
+ private void section(IMarkupWriter writer, String sectionName)
+ {
+ writer.begin("tr");
+ writer.attribute("class", "request-context-section");
+ writer.begin("th");
+ writer.attribute("colspan", 2);
+
+ writer.print(sectionName);
+ writer.end("tr");
+ }
+
+ private List getSorted(Enumeration e)
+ {
+ List result = new ArrayList();
+
+ // JDK 1.4 includes a helper method in Collections for
+ // this; but we want 1.2 compatibility for the
+ // forseable future.
+
+ while (e.hasMoreElements())
+ result.add(e.nextElement());
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+ /**
+ * Writes the state of the context to the writer, typically for inclusion
+ * in a HTML page returned to the user. This is useful
+ * when debugging. The Inspector uses this as well.
+ *
+ **/
+
+ public void write(IMarkupWriter writer)
+ {
+ // Create a box around all of this stuff ...
+
+ writer.begin("table");
+ writer.attribute("class", "request-context-border");
+ writer.begin("tr");
+ writer.begin("td");
+
+ // Get the session, if it exists, and display it.
+
+ HttpSession session = getSession();
+
+ if (session != null)
+ {
+ object(writer, "Session");
+ writer.begin("table");
+ writer.attribute("class", "request-context-object");
+
+ section(writer, "Properties");
+ header(writer, "Name", "Value");
+
+ pair(writer, "id", session.getId());
+ datePair(writer, "creationTime", session.getCreationTime());
+ datePair(writer, "lastAccessedTime", session.getLastAccessedTime());
+ pair(writer, "maxInactiveInterval", session.getMaxInactiveInterval());
+ pair(writer, "new", session.isNew());
+
+ List names = getSorted(session.getAttributeNames());
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0)
+ {
+ section(writer, "Attributes");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) names.get(i);
+ pair(writer, name, session.getAttribute(name));
+ }
+
+ writer.end(); // Session
+
+ }
+
+ object(writer, "Request");
+ writer.begin("table");
+ writer.attribute("class", "request-context-object");
+
+ // Parameters ...
+
+ List parameters = getSorted(_request.getParameterNames());
+ int count = parameters.size();
+
+ for (int i = 0; i < count; i++)
+ {
+
+ if (i == 0)
+ {
+ section(writer, "Parameters");
+ header(writer, "Name", "Value(s)");
+ }
+
+ String name = (String) parameters.get(i);
+ String[] values = _request.getParameterValues(name);
+
+ writer.begin("tr");
+ writer.attribute("class", getRowClass());
+ writer.begin("th");
+ writer.print(name);
+ writer.end();
+ writer.begin("td");
+
+ if (values.length > 1)
+ writer.begin("ul");
+
+ for (int j = 0; j < values.length; j++)
+ {
+ if (values.length > 1)
+ writer.beginEmpty("li");
+
+ writer.print(values[j]);
+
+ }
+
+ writer.end("tr");
+ }
+
+ section(writer, "Properties");
+ header(writer, "Name", "Value");
+
+ pair(writer, "authType", _request.getAuthType());
+ pair(writer, "characterEncoding", _request.getCharacterEncoding());
+ pair(writer, "contentLength", _request.getContentLength());
+ pair(writer, "contentType", _request.getContentType());
+ pair(writer, "method", _request.getMethod());
+ pair(writer, "pathInfo", _request.getPathInfo());
+ pair(writer, "pathTranslated", _request.getPathTranslated());
+ pair(writer, "protocol", _request.getProtocol());
+ pair(writer, "queryString", _request.getQueryString());
+ pair(writer, "remoteAddr", _request.getRemoteAddr());
+ pair(writer, "remoteHost", _request.getRemoteHost());
+ pair(writer, "remoteUser", _request.getRemoteUser());
+ pair(writer, "requestedSessionId", _request.getRequestedSessionId());
+ pair(writer, "requestedSessionIdFromCookie", _request.isRequestedSessionIdFromCookie());
+ pair(writer, "requestedSessionIdFromURL", _request.isRequestedSessionIdFromURL());
+ pair(writer, "requestedSessionIdValid", _request.isRequestedSessionIdValid());
+ pair(writer, "requestURI", _request.getRequestURI());
+ pair(writer, "scheme", _request.getScheme());
+ pair(writer, "serverName", _request.getServerName());
+ pair(writer, "serverPort", _request.getServerPort());
+ pair(writer, "contextPath", _request.getContextPath());
+ pair(writer, "servletPath", _request.getServletPath());
+
+ // Now deal with any headers
+
+ List headers = getSorted(_request.getHeaderNames());
+ count = headers.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0)
+ {
+ section(writer, "Headers");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) headers.get(i);
+ String value = _request.getHeader(name);
+
+ pair(writer, name, value);
+ }
+
+ // Attributes
+
+ List attributes = getSorted(_request.getAttributeNames());
+ count = attributes.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0)
+ {
+ section(writer, "Attributes");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) attributes.get(i);
+
+ pair(writer, name, _request.getAttribute(name));
+ }
+
+ // Cookies ...
+
+ Cookie[] cookies = _request.getCookies();
+
+ if (cookies != null)
+ {
+ for (int i = 0; i < cookies.length; i++)
+ {
+
+ if (i == 0)
+ {
+ section(writer, "Cookies");
+ header(writer, "Name", "Value");
+ }
+
+ Cookie cookie = cookies[i];
+
+ pair(writer, cookie.getName(), cookie.getValue());
+
+ } // Cookies loop
+ }
+
+ writer.end(); // Request
+
+ object(writer, "Servlet");
+ writer.begin("table");
+ writer.attribute("class", "request-context-object");
+
+ section(writer, "Properties");
+ header(writer, "Name", "Value");
+
+ pair(writer, "servlet", _servlet);
+ pair(writer, "name", _servlet.getServletName());
+ pair(writer, "servletInfo", _servlet.getServletInfo());
+
+ ServletConfig config = _servlet.getServletConfig();
+
+ List names = getSorted(config.getInitParameterNames());
+ count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+
+ if (i == 0)
+ {
+ section(writer, "Init Parameters");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) names.get(i);
+ ;
+ pair(writer, name, config.getInitParameter(name));
+
+ }
+
+ writer.end(); // Servlet
+
+ ServletContext context = config.getServletContext();
+
+ object(writer, "Servlet Context");
+ writer.begin("table");
+ writer.attribute("class", "request-context-object");
+
+ section(writer, "Properties");
+ header(writer, "Name", "Value");
+
+ pair(writer, "majorVersion", context.getMajorVersion());
+ pair(writer, "minorVersion", context.getMinorVersion());
+ pair(writer, "serverInfo", context.getServerInfo());
+
+ names = getSorted(context.getInitParameterNames());
+ count = names.size();
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0)
+ {
+ section(writer, "Initial Parameters");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) names.get(i);
+ pair(writer, name, context.getInitParameter(name));
+ }
+
+ names = getSorted(context.getAttributeNames());
+ count = names.size();
+ for (int i = 0; i < count; i++)
+ {
+ if (i == 0)
+ {
+ section(writer, "Attributes");
+ header(writer, "Name", "Value");
+ }
+
+ String name = (String) names.get(i);
+ pair(writer, name, context.getAttribute(name));
+ }
+
+ writer.end(); // Servlet Context
+
+ writeSystemProperties(writer);
+
+ writer.end("table"); // The enclosing border
+ }
+
+ private void writeSystemProperties(IMarkupWriter writer)
+ {
+ Properties properties = null;
+
+ object(writer, "JVM System Properties");
+
+ try
+ {
+ properties = System.getProperties();
+ }
+ catch (SecurityException se)
+ {
+ writer.print("<p>");
+ writer.print(se.toString());
+ return;
+ }
+
+ String pathSeparator = System.getProperty("path.separator", ";");
+
+ writer.begin("table");
+ writer.attribute("class", "request-context-object");
+
+ List names = new ArrayList(properties.keySet());
+ Collections.sort(names);
+ int count = names.size();
+
+ for (int i = 0; i < count; i++)
+ {
+
+ if (i == 0)
+ header(writer, "Name", "Value");
+
+ String name = (String) names.get(i);
+
+ String property = properties.getProperty(name);
+
+ if (property != null && property.indexOf(pathSeparator) > 0 && name.endsWith(".path"))
+ {
+ writer.begin("tr");
+ writer.attribute("class", getRowClass());
+
+ writer.begin("th");
+ writer.print(name);
+ writer.end();
+
+ writer.begin("td");
+ writer.begin("ul");
+
+ StringTokenizer tokenizer = new StringTokenizer(property, pathSeparator);
+
+ while (tokenizer.hasMoreTokens())
+ {
+ writer.beginEmpty("li");
+ writer.print(tokenizer.nextToken());
+ }
+
+ writer.end("tr");
+ }
+ else
+ {
+ pair(writer, name, property);
+ }
+ }
+
+ writer.end(); // System Properties
+ }
+
+ /**
+ * Invokes {@link #write(IMarkupWriter)}, which is used for debugging.
+ * Does nothing if the cycle is rewinding.
+ *
+ **/
+
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (!cycle.isRewinding())
+ write(writer);
+ }
+
+ /**
+ * Returns the multipart decoder and lazily decodes the request parameters.
+ * This allows both for this operation to be performed only when really needed
+ * and for opening the request for reading much later, so that the Engine can
+ * have a chance to set the encoding that the request needs to use.
+ *
+ * @return the multipart decoder or null if not needed for this request
+ * @since 3.0
+ **/
+ private IMultipartDecoder getDecoder()
+ {
+ if (_decoder != null && !_decoded) {
+ _decoder.decode(_request);
+ _decoded = true;
+ }
+
+ return _decoder;
+ }
+
+ /**
+ * Sets the multipart decoder to be used for the request.
+ *
+ * @param decoder the multipart decoder
+ * @since 3.0
+ **/
+ public void setDecoder(IMultipartDecoder decoder)
+ {
+ _decoder = decoder;
+ _decoded = false;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/request/ResponseOutputStream.java b/tapestry-framework/src/org/apache/tapestry/request/ResponseOutputStream.java
new file mode 100644
index 0000000..d220510
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/request/ResponseOutputStream.java
@@ -0,0 +1,298 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.request;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.SocketException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A special output stream works with a {@link HttpServletResponse}, buffering
+ * data so as to defer opening the response's output stream.
+ *
+ * <p>The buffering is pretty simple because the code
+ * between {@link org.apache.tapestry.IMarkupWriter} and this shows lots of buffering
+ * after the <code>PrintWriter</code> and inside the <code>OutputStreamWriter</code> that
+ * can't be configured.
+ *
+ * <p>This class performs some buffering, but it is not all that
+ * useful because the
+ * {@link org.apache.tapestry.html.Body} component (which will
+ * be used on virtually all Tapestry pages), buffers its wrapped contents
+ * (that is, evertyhing inside the <body> tag in the generated HTML).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ResponseOutputStream extends OutputStream
+{
+ private static final Log LOG = LogFactory.getLog(ResponseOutputStream.class);
+
+ /**
+ * Default size for the buffer (2000 bytes).
+ *
+ **/
+
+ public static final int DEFAULT_SIZE = 2000;
+
+ private int _pos;
+ private int _maxSize;
+ private byte[] _buffer;
+
+ private String _contentType;
+ private HttpServletResponse _response;
+ private OutputStream _out;
+
+ private boolean _discard = false;
+
+ /**
+ * Creates the stream with the default maximum buffer size.
+ *
+ **/
+
+ public ResponseOutputStream(HttpServletResponse response)
+ {
+ this(response, DEFAULT_SIZE);
+ }
+
+ /**
+ * Standard constructor.
+ *
+ **/
+
+ public ResponseOutputStream(HttpServletResponse response, int maxSize)
+ {
+ _response = response;
+ _maxSize = maxSize;
+ }
+
+ /**
+ * Does nothing. This is because of chaining of <code>close()</code> from
+ * {@link org.apache.tapestry.IMarkupWriter#close()} ... see {@link #flush()}.
+ *
+ **/
+
+ public void close() throws IOException
+ {
+ // Does nothing.
+ }
+
+ /**
+ * Flushes the underlying output stream, if is has been opened.
+ *
+ * <p>This method explicitly <em>does not</em> flush the internal buffer ...
+ * that's because when an {@link org.apache.tapestry.IMarkupWriter} is closed (for instance, because
+ * an exception is thrown), that <code>close()</code> spawns <code>flush()</code>es
+ * and <code>close()</code>s throughout the output stream chain, eventually
+ * reaching this method.
+ *
+ * @see #forceFlush()
+ *
+ **/
+
+ public void flush() throws IOException
+ {
+ try
+ {
+ if (_out != null)
+ _out.flush();
+ }
+ catch (SocketException ex)
+ {
+ LOG.debug("Socket exception.");
+ }
+ }
+
+ /**
+ * Writes the internal buffer to the output stream, opening it if necessary, then
+ * flushes the output stream. Future writes will go directly to the output stream.
+ *
+ **/
+
+ public void forceFlush() throws IOException
+ {
+ if (_out == null)
+ {
+
+ // In certain cases (such as when the Tapestry service sends a redirect),
+ // there is no output to send back (and no content type set). In this
+ // case, forceFlush() does nothing.
+
+ if (_buffer == null)
+ return;
+
+ open();
+ }
+
+ try
+ {
+ _out.flush();
+ }
+ catch (SocketException ex)
+ {
+ LOG.debug("Socket exception.");
+ }
+ }
+
+ public String getContentType()
+ {
+ return _contentType;
+ }
+
+ public boolean getDiscard()
+ {
+ return _discard;
+ }
+
+ /**
+ * Sets the response type to from the contentType property (which
+ * defaults to "text/html") and gets an output stream
+ * from the response, then writes the current buffer to it and
+ * releases the buffer.
+ *
+ * @throws IOException if the content type has never been set.
+ *
+ **/
+
+ private void open() throws IOException
+ {
+ if (_contentType == null)
+ throw new IOException(Tapestry.getMessage("ResponseOutputStream.content-type-not-set"));
+
+ _response.setContentType(_contentType);
+
+ _out = _response.getOutputStream();
+
+ innerWrite(_buffer, 0, _pos);
+
+ _pos = 0;
+ _buffer = null;
+ }
+
+ /**
+ * Discards all output in the buffer. This is used after an error to
+ * restart the output (so that the error may be presented).
+ *
+ * <p>Clears the discard flag.
+ *
+ **/
+
+ public void reset() throws IOException
+ {
+ _pos = 0;
+ _discard = false;
+ }
+
+ /**
+ * Changes the maximum buffer size. If the new buffer size is smaller
+ * than the number of
+ * bytes already in the buffer, the buffer is immediately flushed.
+ *
+ **/
+
+ public void setBufferSize(int value) throws IOException
+ {
+ if (value < _pos)
+ {
+ open();
+ return;
+ }
+
+ _maxSize = value;
+ }
+
+ public void setContentType(String value)
+ {
+ _contentType = value;
+ }
+
+ /**
+ * Indicates whether the stream should ignore all data written to it.
+ *
+ **/
+
+ public void setDiscard(boolean value)
+ {
+ _discard = value;
+ }
+
+ private void innerWrite(byte[] b, int off, int len) throws IOException
+ {
+ if (b == null || len == 0 || _discard)
+ return;
+
+ try
+ {
+ _out.write(b, off, len);
+ }
+ catch (SocketException ex)
+ {
+ LOG.debug("Socket exception.");
+ }
+ }
+
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ if (len == 0 || _discard)
+ return;
+
+ if (_out != null)
+ {
+ _out.write(b, off, len);
+ return;
+ }
+
+ // If too large for the maximum size buffer, then open the output stream
+ // write out and free the buffer, and write out the new stuff.
+
+ if (_pos + len >= _maxSize)
+ {
+ open();
+ innerWrite(b, off, len);
+ return;
+ }
+
+ // Allocate the buffer when it is initially needed.
+
+ if (_buffer == null)
+ _buffer = new byte[_maxSize];
+
+ // Copy the new bytes into the buffer and advance the position.
+
+ System.arraycopy(b, off, _buffer, _pos, len);
+ _pos += len;
+ }
+
+ public void write(int b) throws IOException
+ {
+ if (_discard)
+ return;
+
+ // This method is rarely called so this little inefficiency is better than
+ // maintaining that ugly buffer expansion code in two places.
+
+ byte[] tiny = new byte[] {(byte) b };
+
+ write(tiny, 0, 1);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/resolver/AbstractSpecificationResolver.java b/tapestry-framework/src/org/apache/tapestry/resolver/AbstractSpecificationResolver.java
new file mode 100644
index 0000000..ac4cafe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resolver/AbstractSpecificationResolver.java
@@ -0,0 +1,200 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resolver;
+
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ISpecificationSource;
+import org.apache.tapestry.spec.IApplicationSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+import org.apache.tapestry.util.pool.IPoolable;
+
+/**
+ * Base class for resolving a {@link org.apache.tapestry.spec.IComponentSpecification}
+ * for a particular page or component, within a specified
+ * {@link org.apache.tapestry.INamespace}. In some cases, a search is necessary.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class AbstractSpecificationResolver implements IPoolable
+{
+ private ISpecificationSource _specificationSource;
+
+ private INamespace _namespace;
+
+ private IComponentSpecification _specification;
+
+ private IResourceLocation _applicationRootLocation;
+
+ private IResourceLocation _webInfLocation;
+
+ private IResourceLocation _webInfAppLocation;
+
+ private ISpecificationResolverDelegate _delegate;
+
+ public AbstractSpecificationResolver(IRequestCycle cycle)
+ {
+ IEngine engine = cycle.getEngine();
+
+ _specificationSource = engine.getSpecificationSource();
+
+ _applicationRootLocation = Tapestry.getApplicationRootLocation(cycle);
+
+ String servletName =
+ cycle.getRequestContext().getServlet().getServletConfig().getServletName();
+
+ _webInfLocation = _applicationRootLocation.getRelativeLocation("/WEB-INF/");
+
+ _webInfAppLocation = _webInfLocation.getRelativeLocation(servletName + "/");
+
+ IApplicationSpecification specification = engine.getSpecification();
+
+ if (specification.checkExtension(Tapestry.SPECIFICATION_RESOLVER_DELEGATE_EXTENSION_NAME))
+ _delegate =
+ (ISpecificationResolverDelegate) engine.getSpecification().getExtension(
+ Tapestry.SPECIFICATION_RESOLVER_DELEGATE_EXTENSION_NAME,
+ ISpecificationResolverDelegate.class);
+ else
+ _delegate = NullSpecificationResolverDelegate.getSharedInstance();
+ }
+
+ /**
+ * Returns the {@link ISpecificationResolverDelegate} instance registered
+ * in the application specification as extension
+ * {@link Tapestry#SPECIFICATION_RESOLVER_DELEGATE_EXTENSION_NAME},
+ * or null if no such extension exists.
+ *
+ **/
+
+ public ISpecificationResolverDelegate getDelegate()
+ {
+ return _delegate;
+ }
+
+ /**
+ * Returns the location of the servlet, within the
+ * servlet context.
+ *
+ **/
+
+ protected IResourceLocation getApplicationRootLocation()
+ {
+ return _applicationRootLocation;
+ }
+
+ /**
+ * Invoked in subclasses to identify the resolved namespace.
+ *
+ **/
+
+ protected void setNamespace(INamespace namespace)
+ {
+ _namespace = namespace;
+ }
+
+ /**
+ * Returns the resolve namespace.
+ *
+ **/
+
+ public INamespace getNamespace()
+ {
+ return _namespace;
+ }
+
+ /**
+ * Returns the specification source for the running application.
+ *
+ **/
+
+ protected ISpecificationSource getSpecificationSource()
+ {
+ return _specificationSource;
+ }
+
+ /**
+ * Returns the location of /WEB-INF/, in the servlet context.
+ *
+ **/
+
+ protected IResourceLocation getWebInfLocation()
+ {
+ return _webInfLocation;
+ }
+
+ /**
+ * Returns the location of the application-specific subdirectory, under
+ * /WEB-INF/, in the servlet context.
+ *
+ **/
+
+ protected IResourceLocation getWebInfAppLocation()
+ {
+ return _webInfAppLocation;
+ }
+
+ /**
+ * Returns the resolved specification.
+ *
+ **/
+
+ public IComponentSpecification getSpecification()
+ {
+ return _specification;
+ }
+
+ /**
+ * Invoked in subclass to set the final specification the initial
+ * inputs are resolved to.
+ *
+ **/
+
+ protected void setSpecification(IComponentSpecification specification)
+ {
+ _specification = specification;
+ }
+
+ /**
+ * Clears the namespace, specification and simpleName properties.
+ *
+ **/
+
+ protected void reset()
+ {
+ _namespace = null;
+ _specification = null;
+ }
+
+ /** Does nothing. */
+ public void discardFromPool()
+ {
+
+ }
+
+ /** Invokes {@link #reset()} */
+
+ public void resetForPool()
+ {
+ reset();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resolver/ComponentSpecificationResolver.java b/tapestry-framework/src/org/apache/tapestry/resolver/ComponentSpecificationResolver.java
new file mode 100644
index 0000000..e28b3dc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resolver/ComponentSpecificationResolver.java
@@ -0,0 +1,259 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resolver;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Utility class that understands the rules of component types (which
+ * may optionally have a library prefix) and can resolve
+ * the type to a {@link org.apache.tapestry.INamespace} and a
+ * {@link org.apache.tapestry.spec.IComponentSpecification}.
+ *
+ * <p>Like {@link org.apache.tapestry.resolver.PageSpecificationResolver},
+ * if the component is not defined explicitly in the namespace, a search
+ * may occur:
+ *
+ * Performs the tricky work of resolving a page name to a page specification.
+ * The search for pages in the application namespace is the most complicated,
+ * since Tapestry searches for pages that aren't explicitly defined in the
+ * application specification. The search, based on the <i>simple-name</i>
+ * of the page, goes as follows:
+ *
+ * <ul>
+ * <li>As declared in the application specification
+ * <li><i>type</i>.jwc in the same folder as the application specification
+ * <li><i>type</i> jwc in the WEB-INF/<i>servlet-name</i> directory of the context root
+ * <li><i>type</i>.jwc in WEB-INF
+ * <li><i>type</i>.jwc in the application root (within the context root)
+ * <li>By searching the framework namespace
+ * </ul>
+ *
+ * The search for components in library namespaces is more abbreviated:
+ * <li>As declared in the library specification
+ * <li><i>type</i>.jwc in the same folder as the library specification
+ * <li>By searching the framework namespace
+ * </ul>
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ComponentSpecificationResolver extends AbstractSpecificationResolver
+{
+ private static final Log LOG = LogFactory.getLog(ComponentSpecificationResolver.class);
+
+ private String _type;
+
+ public ComponentSpecificationResolver(IRequestCycle cycle)
+ {
+ super(cycle);
+ }
+
+ protected void reset()
+ {
+ _type = null;
+
+ super.reset();
+ }
+
+ /**
+ * Passed the namespace of a container (to resolve the type in)
+ * and the type to resolve, performs the processing. A "bare type"
+ * (without a library prefix) may be in the containerNamespace,
+ * or the framework namespace
+ * (a search occurs in that order).
+ *
+ * @param cycle current request cycle
+ * @param containerNamespace namespace that may contain
+ * a library referenced in the type
+ * @param type the component specification
+ * to find, either a simple name, or prefixed with a library id
+ * (defined for the container namespace)
+ *
+ * @see #getNamespace()
+ * @see #getSpecification()
+ *
+ **/
+
+ public void resolve(
+ IRequestCycle cycle,
+ INamespace containerNamespace,
+ String type,
+ ILocation location)
+ {
+ int colonx = type.indexOf(':');
+
+ if (colonx > 0)
+ {
+ String libraryId = type.substring(0, colonx);
+ String simpleType = type.substring(colonx + 1);
+
+ resolve(cycle, containerNamespace, libraryId, simpleType, location);
+ }
+ else
+ resolve(cycle, containerNamespace, null, type, location);
+ }
+
+ /**
+ * Like {@link #resolve(org.apache.tapestry.IRequestCycle, org.apache.tapestry.INamespace, java.lang.String, org.apache.tapestry.ILocation)},
+ * but used when the type has already been parsed into a library id and a simple type.
+ *
+ * @param cycle current request cycle
+ * @param containerNamespace namespace that may contain
+ * a library referenced in the type
+ * @param libraryId the library id within the container namespace, or null
+ * @param type the component specification
+ * to find as a simple name (without a library prefix)
+ * @param location of reference to be resolved
+ * @throws ApplicationRuntimeException if the type cannot be resolved
+ *
+ **/
+
+ public void resolve(
+ IRequestCycle cycle,
+ INamespace containerNamespace,
+ String libraryId,
+ String type,
+ ILocation location)
+ {
+ reset();
+ _type = type;
+
+ INamespace namespace = null;
+
+ if (libraryId != null)
+ namespace = containerNamespace.getChildNamespace(libraryId);
+ else
+ namespace = containerNamespace;
+
+ setNamespace(namespace);
+
+ if (namespace.containsComponentType(type))
+ setSpecification(namespace.getComponentSpecification(type));
+ else
+ searchForComponent(cycle);
+
+ // If not found after search, check to see if it's in
+ // the framework instead.
+
+ if (getSpecification() == null)
+ {
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "Namespace.no-such-component-type",
+ type,
+ namespace.getNamespaceId()),
+ location,
+ null);
+
+ }
+ }
+
+ private void searchForComponent(IRequestCycle cycle)
+ {
+ INamespace namespace = getNamespace();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resolving unknown component '" + _type + "' in " + namespace);
+
+ String expectedName = _type + ".jwc";
+ IResourceLocation namespaceLocation = namespace.getSpecificationLocation();
+
+ // Look for appropriate file in same folder as the library (or application)
+ // specificaiton.
+
+ if (found(namespaceLocation.getRelativeLocation(expectedName)))
+ return;
+
+ if (namespace.isApplicationNamespace())
+ {
+
+ // The application namespace gets some extra searching.
+
+ if (found(getWebInfAppLocation().getRelativeLocation(expectedName)))
+ return;
+
+ if (found(getWebInfLocation().getRelativeLocation(expectedName)))
+ return;
+
+ if (found(getApplicationRootLocation().getRelativeLocation(expectedName)))
+ return;
+ }
+
+ // Not in the library or app spec; does it match a component
+ // provided by the Framework?
+
+ INamespace framework = getSpecificationSource().getFrameworkNamespace();
+
+ if (framework.containsComponentType(_type))
+ {
+ setSpecification(framework.getComponentSpecification(_type));
+ return;
+ }
+
+ IComponentSpecification specification =
+ getDelegate().findComponentSpecification(cycle, namespace, _type);
+
+ setSpecification(specification);
+
+ // If not found by here, an exception will be thrown.
+ }
+
+ private boolean found(IResourceLocation location)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Checking: " + location);
+
+ if (location.getResourceURL() == null)
+ return false;
+
+ setSpecification(getSpecificationSource().getComponentSpecification(location));
+
+ install();
+
+ return true;
+ }
+
+ private void install()
+ {
+ INamespace namespace = getNamespace();
+ IComponentSpecification specification = getSpecification();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Installing component type "
+ + _type
+ + " into "
+ + namespace
+ + " as "
+ + specification);
+
+ namespace.installComponentSpecification(_type, specification);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resolver/ISpecificationResolverDelegate.java b/tapestry-framework/src/org/apache/tapestry/resolver/ISpecificationResolverDelegate.java
new file mode 100644
index 0000000..1cf1525
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resolver/ISpecificationResolverDelegate.java
@@ -0,0 +1,72 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resolver;
+
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Delegate interface used when a page or component specification
+ * can not be found by the normal means. This allows hooks
+ * to support specifications from unusual locations, or generated
+ * on the fly.
+ *
+ * <p>The delegate must be coded in a threadsafe manner.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface ISpecificationResolverDelegate
+{
+ /**
+ * Invoked by {@link PageSpecificationResolver} to find the indicated
+ * page specification. Returns
+ * the specification, or null. The specification, if returned, is not cached by Tapestry
+ * (it is up to the delegate to cache the specification if desired).
+ *
+ * @param cycle used to gain access to framework and Servlet API objects
+ * @param namespace the namespace containing the page
+ * @param simplePageName the name of the page (without any namespace prefix)
+ *
+ **/
+
+ public IComponentSpecification findPageSpecification(
+ IRequestCycle cycle,
+ INamespace namespace,
+ String simplePageName);
+
+ /**
+ * Invoked by {@link PageSpecificationResolver} to find the indicated
+ * component specification. Returns
+ * the specification, or null. The specification, if returned, is not cached by Tapestry
+ * (it is up to the delegate to cache the specification if desired).
+ *
+ * <p>The delegate must be coded in a threadsafe manner.
+ *
+ * @param cycle used to gain access to framework and Servlet API objects
+ * @param namespace the namespace containing the component
+ * @param type the component type (without any namespace prefix)
+ *
+ **/
+
+ public IComponentSpecification findComponentSpecification(
+ IRequestCycle cycle,
+ INamespace namespace,
+ String type);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resolver/NullSpecificationResolverDelegate.java b/tapestry-framework/src/org/apache/tapestry/resolver/NullSpecificationResolverDelegate.java
new file mode 100644
index 0000000..d4731d6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resolver/NullSpecificationResolverDelegate.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resolver;
+
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Stand-in class used when the application fails to specify an actual delegate.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class NullSpecificationResolverDelegate implements ISpecificationResolverDelegate
+{
+ private static NullSpecificationResolverDelegate _shared;
+
+ public static NullSpecificationResolverDelegate getSharedInstance()
+ {
+ if (_shared == null)
+ _shared = new NullSpecificationResolverDelegate();
+
+ return _shared;
+ }
+
+ /**
+ * Returns null.
+ *
+ **/
+
+ public IComponentSpecification findPageSpecification(
+ IRequestCycle cycle,
+ INamespace namespace,
+ String simplePageName)
+ {
+ return null;
+ }
+
+ /**
+ * Returns null.
+ *
+ **/
+
+ public IComponentSpecification findComponentSpecification(
+ IRequestCycle cycle,
+ INamespace namespace,
+ String type)
+ {
+ return null;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resolver/PageSpecificationResolver.java b/tapestry-framework/src/org/apache/tapestry/resolver/PageSpecificationResolver.java
new file mode 100644
index 0000000..5c3cfa1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resolver/PageSpecificationResolver.java
@@ -0,0 +1,273 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resolver;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.spec.ComponentSpecification;
+import org.apache.tapestry.spec.IComponentSpecification;
+
+/**
+ * Performs the tricky work of resolving a page name to a page specification.
+ * The search for pages in the application namespace is the most complicated,
+ * since Tapestry searches for pages that aren't explicitly defined in the
+ * application specification. The search, based on the <i>simple-name</i>
+ * of the page, goes as follows:
+ *
+ * <ul>
+ * <li>As declared in the application specification
+ * <li><i>simple-name</i>.page in the same folder as the application specification
+ * <li><i>simple-name</i> page in the WEB-INF/<i>servlet-name</i> directory of the context root
+ * <li><i>simple-name</i>.page in WEB-INF
+ * <li><i>simple-name</i>.page in the application root (within the context root)
+ * <li><i>simple-name</i>.html as a template in the application root,
+ * for which an implicit specification is generated
+ * <li>By searching the framework namespace
+ * <li>By invoking {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate#findPageSpecification(IRequestCycle, INamespace, String)}
+ * </ul>
+ *
+ * <p>Pages in a component library are searched for in a more abbreviated fashion:
+ * <ul>
+ * <li>As declared in the library specification
+ * <li><i>simple-name</i>.page in the same folder as the library specification
+ * <li>By searching the framework namespace
+ * <li>By invoking {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate#findPageSpecification(IRequestCycle, INamespace, String)}
+ * </ul>
+ *
+ * @see org.apache.tapestry.engine.IPageSource
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class PageSpecificationResolver extends AbstractSpecificationResolver
+{
+ private static final Log LOG = LogFactory.getLog(PageSpecificationResolver.class);
+
+ private String _simpleName;
+
+ public PageSpecificationResolver(IRequestCycle cycle)
+ {
+ super(cycle);
+ }
+
+ /**
+ * Resolve the name (which may have a library id prefix) to a namespace
+ * (see {@link #getNamespace()}) and a specification (see {@link #getSpecification()}).
+ *
+ * @throws ApplicationRuntimeException if the name cannot be resolved
+ *
+ **/
+
+ public void resolve(IRequestCycle cycle, String prefixedName)
+ {
+ reset();
+
+ INamespace namespace = null;
+
+ int colonx = prefixedName.indexOf(':');
+
+ if (colonx > 0)
+ {
+ _simpleName = prefixedName.substring(colonx + 1);
+ String namespaceId = prefixedName.substring(0, colonx);
+
+ if (namespaceId.equals(INamespace.FRAMEWORK_NAMESPACE))
+ namespace = getSpecificationSource().getFrameworkNamespace();
+ else
+ namespace =
+ getSpecificationSource().getApplicationNamespace().getChildNamespace(
+ namespaceId);
+ }
+ else
+ {
+ _simpleName = prefixedName;
+
+ namespace = getSpecificationSource().getApplicationNamespace();
+ }
+
+ setNamespace(namespace);
+
+ if (namespace.containsPage(_simpleName))
+ {
+ setSpecification(namespace.getPageSpecification(_simpleName));
+ return;
+ }
+
+ // Not defined in the specification, so it's time to hunt it down.
+
+ searchForPage(cycle);
+
+ if (getSpecification() == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "Namespace.no-such-page",
+ _simpleName,
+ namespace.getNamespaceId()));
+
+ }
+
+ public String getSimplePageName()
+ {
+ return _simpleName;
+ }
+
+ private void searchForPage(IRequestCycle cycle)
+ {
+ INamespace namespace = getNamespace();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resolving unknown page '" + _simpleName + "' in " + namespace);
+
+ String expectedName = _simpleName + ".page";
+
+ IResourceLocation namespaceLocation = namespace.getSpecificationLocation();
+
+ // See if there's a specification file in the same folder
+ // as the library or application specification that's
+ // supposed to contain the page.
+
+ if (found(namespaceLocation.getRelativeLocation(expectedName)))
+ return;
+
+ if (namespace.isApplicationNamespace())
+ {
+
+ // The application namespace gets some extra searching.
+
+ if (found(getWebInfAppLocation().getRelativeLocation(expectedName)))
+ return;
+
+ if (found(getWebInfLocation().getRelativeLocation(expectedName)))
+ return;
+
+ if (found(getApplicationRootLocation().getRelativeLocation(expectedName)))
+ return;
+
+ // The wierd one ... where we see if there's a template in the application root location.
+
+ String templateName = _simpleName + "." + getTemplateExtension();
+
+ IResourceLocation templateLocation =
+ getApplicationRootLocation().getRelativeLocation(templateName);
+
+ if (templateLocation.getResourceURL() != null)
+ {
+ setupImplicitPage(templateLocation);
+ return;
+ }
+
+ // Not found in application namespace, so maybe its a framework page.
+
+ INamespace framework = getSpecificationSource().getFrameworkNamespace();
+
+ if (framework.containsPage(_simpleName))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found " + _simpleName + " in framework namespace.");
+
+ setNamespace(framework);
+
+ // Note: This implies that normal lookup rules don't work
+ // for the framework! Framework pages must be
+ // defined in the framework library specification.
+
+ setSpecification(framework.getPageSpecification(_simpleName));
+ return;
+ }
+ }
+
+ // Not found by any normal rule, so its time to
+ // consult the delegate.
+
+ IComponentSpecification specification =
+ getDelegate().findPageSpecification(cycle, namespace, _simpleName);
+
+ setSpecification(specification);
+ }
+
+ private void setupImplicitPage(IResourceLocation location)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found HTML template at " + location);
+ // TODO The SpecFactory in Specification parser should be used in some way to create an IComponentSpecifciation!
+ IComponentSpecification specification = new ComponentSpecification();
+ specification.setPageSpecification(true);
+ specification.setSpecificationLocation(location);
+
+ setSpecification(specification);
+
+ install();
+ }
+
+ private boolean found(IResourceLocation location)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Checking: " + location);
+
+ if (location.getResourceURL() == null)
+ return false;
+
+ setSpecification(getSpecificationSource().getPageSpecification(location));
+
+ install();
+
+ return true;
+ }
+
+ private void install()
+ {
+ INamespace namespace = getNamespace();
+ IComponentSpecification specification = getSpecification();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Installing page " + _simpleName + " into " + namespace + " as " + specification);
+
+ namespace.installPageSpecification(_simpleName, specification);
+ }
+
+ /**
+ * If the namespace defines the template extension (as property
+ * {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY}, then that is used, otherwise
+ * the default is used.
+ *
+ **/
+
+ private String getTemplateExtension()
+ {
+ String extension =
+ getNamespace().getSpecification().getProperty(Tapestry.TEMPLATE_EXTENSION_PROPERTY);
+
+ if (extension == null)
+ extension = Tapestry.DEFAULT_TEMPLATE_EXTENSION;
+
+ return extension;
+ }
+
+ protected void reset()
+ {
+ _simpleName = null;
+
+ super.reset();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resource/AbstractResourceLocation.java b/tapestry-framework/src/org/apache/tapestry/resource/AbstractResourceLocation.java
new file mode 100644
index 0000000..0aff8d5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resource/AbstractResourceLocation.java
@@ -0,0 +1,108 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resource;
+
+import java.util.Locale;
+
+import org.apache.tapestry.IResourceLocation;
+
+public abstract class AbstractResourceLocation implements IResourceLocation
+{
+ private String _path;
+ private String _name;
+ private String _folderPath;
+ private Locale _locale;
+
+ protected AbstractResourceLocation(String path)
+ {
+ this(path, null);
+ }
+
+ protected AbstractResourceLocation(String path, Locale locale)
+ {
+ _path = path;
+ _locale = locale;
+ }
+
+ public String getName()
+ {
+ if (_name == null)
+ split();
+
+ return _name;
+ }
+
+ public IResourceLocation getRelativeLocation(String name)
+ {
+ if (name.startsWith("/"))
+ {
+ if (name.equals(_path))
+ return this;
+
+ return buildNewResourceLocation(name);
+ }
+
+ if (_folderPath == null)
+ split();
+
+ if (name.equals(_name))
+ return this;
+
+ return buildNewResourceLocation(_folderPath + name);
+ }
+
+ public String getPath()
+ {
+ return _path;
+ }
+
+ public Locale getLocale()
+ {
+ return _locale;
+ }
+
+
+ protected abstract IResourceLocation buildNewResourceLocation(String path);
+
+ private void split()
+ {
+ int lastSlashx = _path.lastIndexOf('/');
+
+ _folderPath = _path.substring(0, lastSlashx + 1);
+ _name = _path.substring(lastSlashx + 1);
+ }
+
+
+ /**
+ * Returns true if the other object is an instance of the
+ * same class, and the paths are equal.
+ *
+ **/
+
+ public boolean equals(Object obj)
+ {
+ if (obj == null)
+ return false;
+
+ if (obj.getClass().equals(getClass()))
+ {
+ AbstractResourceLocation otherLocation = (AbstractResourceLocation) obj;
+
+ return _path.equals(otherLocation._path);
+ }
+
+ return false;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resource/ClasspathResourceLocation.java b/tapestry-framework/src/org/apache/tapestry/resource/ClasspathResourceLocation.java
new file mode 100644
index 0000000..5586dc1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resource/ClasspathResourceLocation.java
@@ -0,0 +1,110 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resource;
+
+import java.net.URL;
+import java.util.Locale;
+
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.util.LocalizedResource;
+import org.apache.tapestry.util.LocalizedResourceFinder;
+
+/**
+ * Implementation of {@link org.apache.tapestry.IResourceLocation}
+ * for resources found within the classpath.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ClasspathResourceLocation extends AbstractResourceLocation
+{
+ private IResourceResolver _resolver;
+
+ public ClasspathResourceLocation(IResourceResolver resolver, String path)
+ {
+ this(resolver, path, null);
+ }
+
+ public ClasspathResourceLocation(IResourceResolver resolver, String path, Locale locale)
+ {
+ super(path, locale);
+
+ _resolver = resolver;
+ }
+
+ /**
+ * Locates the localization of the
+ * resource using {@link org.apache.tapestry.util.LocalizedResourceFinder}
+ *
+ **/
+
+ public IResourceLocation getLocalization(Locale locale)
+ {
+ String path = getPath();
+ LocalizedResourceFinder finder = new LocalizedResourceFinder(_resolver);
+
+ LocalizedResource localizedResource = finder.resolve(path, locale);
+
+ if (localizedResource == null)
+ return null;
+
+ String localizedPath = localizedResource.getResourcePath();
+ Locale pathLocale = localizedResource.getResourceLocale();
+
+ if (localizedPath == null)
+ return null;
+
+ if (path.equals(localizedPath))
+ return this;
+
+ return new ClasspathResourceLocation(_resolver, localizedPath, pathLocale);
+ }
+
+ /**
+ * Invokes {@link IResourceResolver#getResource(String)}
+ *
+ **/
+
+ public URL getResourceURL()
+ {
+ return _resolver.getResource(getPath());
+ }
+
+ public String toString()
+ {
+ return "classpath:" + getPath();
+ }
+
+ public int hashCode()
+ {
+ HashCodeBuilder builder = new HashCodeBuilder(4783, 23);
+
+ builder.append(getPath());
+
+ return builder.toHashCode();
+ }
+
+ protected IResourceLocation buildNewResourceLocation(String path)
+ {
+ return new ClasspathResourceLocation(_resolver, path);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/resource/ContextResourceLocation.java b/tapestry-framework/src/org/apache/tapestry/resource/ContextResourceLocation.java
new file mode 100644
index 0000000..4dfbb31
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/resource/ContextResourceLocation.java
@@ -0,0 +1,125 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.resource;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Locale;
+
+import javax.servlet.ServletContext;
+
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.LocalizedContextResourceFinder;
+import org.apache.tapestry.util.LocalizedResource;
+
+/**
+ * Implementation of {@link org.apache.tapestry.IResourceLocation}
+ * for resources found within the web application context.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ContextResourceLocation extends AbstractResourceLocation
+{
+ private static final Log LOG = LogFactory.getLog(ContextResourceLocation.class);
+
+ private ServletContext _context;
+
+ public ContextResourceLocation(ServletContext context, String path)
+ {
+ this(context, path, null);
+ }
+
+ public ContextResourceLocation(ServletContext context, String path, Locale locale)
+ {
+ super(path, locale);
+
+ _context = context;
+ }
+
+ /**
+ * Locates the resource using {@link LocalizedContextResourceFinder}
+ * and {@link ServletContext#getResource(java.lang.String)}.
+ *
+ **/
+
+ public IResourceLocation getLocalization(Locale locale)
+ {
+ LocalizedContextResourceFinder finder = new LocalizedContextResourceFinder(_context);
+
+ String path = getPath();
+ LocalizedResource localizedResource = finder.resolve(path, locale);
+
+ if (localizedResource == null)
+ return null;
+
+ String localizedPath = localizedResource.getResourcePath();
+ Locale pathLocale = localizedResource.getResourceLocale();
+
+ if (localizedPath == null)
+ return null;
+
+ if (path.equals(localizedPath))
+ return this;
+
+ return new ContextResourceLocation(_context, localizedPath, pathLocale);
+ }
+
+ public URL getResourceURL()
+ {
+ try
+ {
+ return _context.getResource(getPath());
+ }
+ catch (MalformedURLException ex)
+ {
+ LOG.warn(
+ Tapestry.format(
+ "ContextResourceLocation.unable-to-reference-context-path",
+ getPath()),
+ ex);
+
+ return null;
+ }
+ }
+
+ public String toString()
+ {
+ return "context:" + getPath();
+ }
+
+ public int hashCode()
+ {
+ HashCodeBuilder builder = new HashCodeBuilder(3265, 143);
+
+ builder.append(getPath());
+
+ return builder.toHashCode();
+ }
+
+ protected IResourceLocation buildNewResourceLocation(String path)
+ {
+ return new ContextResourceLocation(_context, path);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/AbstractToken.java b/tapestry-framework/src/org/apache/tapestry/script/AbstractToken.java
new file mode 100644
index 0000000..77e5570
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/AbstractToken.java
@@ -0,0 +1,99 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Base class for creating tokens which may contain other tokens.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+abstract class AbstractToken implements IScriptToken
+{
+ private List _tokens;
+ private ILocation _location;
+ private IResourceResolver _resolver;
+
+ protected AbstractToken(ILocation location)
+ {
+ _location = location;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public void addToken(IScriptToken token)
+ {
+ if (_tokens == null)
+ _tokens = new ArrayList();
+
+ _tokens.add(token);
+ }
+
+ /**
+ * Invokes {@link IScriptToken#write(StringBuffer,ScriptSession)}
+ * on each child token (if there are any).
+ *
+ **/
+
+ protected void writeChildren(StringBuffer buffer, ScriptSession session)
+ {
+ if (_tokens == null)
+ return;
+
+ Iterator i = _tokens.iterator();
+
+ while (i.hasNext())
+ {
+ IScriptToken token = (IScriptToken) i.next();
+
+ token.write(buffer, session);
+ }
+ }
+
+ /**
+ * Evaluates the expression against the session's symbols, using
+ * {@link OgnlUtils#get(String, ClassResolver, Object)} and
+ * returns the result.
+ */
+ protected Object evaluate(String expression, ScriptSession session)
+ {
+ if (_resolver == null)
+ _resolver = session.getRequestCycle().getEngine().getResourceResolver();
+
+ try
+ {
+ return OgnlUtils.get(expression, _resolver, session.getSymbols());
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), _location, ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/AbstractTokenRule.java b/tapestry-framework/src/org/apache/tapestry/script/AbstractTokenRule.java
new file mode 100644
index 0000000..1ed6139
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/AbstractTokenRule.java
@@ -0,0 +1,192 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.util.xml.BaseRule;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+
+/**
+ * Base class for the rules that build {@link org.apache.tapestry.script.IScriptToken}s.
+ * Used with classes that can contain a mix of text and elements (those that
+ * accept "full content").
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ **/
+
+abstract class AbstractTokenRule extends BaseRule
+{
+
+ /**
+ * Adds a token to its parent, the top object on the stack.
+ */
+ protected void addToParent(RuleDirectedParser parser, IScriptToken token)
+ {
+ IScriptToken parent = (IScriptToken) parser.peek();
+
+ parent.addToken(token);
+ }
+
+ /**
+ * Peeks at the top object on the stack (which must be a {@link IScriptToken}),
+ * and converts the text into a series of {@link org.apache.tapestry.script.StaticToken} and
+ * {@link org.apache.tapestry.script.InsertToken}s.
+ */
+
+ public void content(RuleDirectedParser parser, String content)
+ {
+ IScriptToken token = (IScriptToken) parser.peek();
+
+ addTextTokens(token, content, parser.getLocation());
+ }
+
+ private static final int STATE_START = 0;
+ private static final int STATE_DOLLAR = 1;
+ private static final int STATE_COLLECT_EXPRESSION = 2;
+
+ /**
+ * Parses the provided text and converts it into a series of
+ */
+ protected void addTextTokens(IScriptToken token, String text, ILocation location)
+ {
+ char[] buffer = text.toCharArray();
+ int state = STATE_START;
+ int blockStart = 0;
+ int blockLength = 0;
+ int expressionStart = -1;
+ int expressionLength = 0;
+ int i = 0;
+ int braceDepth = 0;
+
+ while (i < buffer.length)
+ {
+ char ch = buffer[i];
+
+ switch (state)
+ {
+ case STATE_START :
+
+ if (ch == '$')
+ {
+ state = STATE_DOLLAR;
+ i++;
+ continue;
+ }
+
+ blockLength++;
+ i++;
+ continue;
+
+ case STATE_DOLLAR :
+
+ if (ch == '{')
+ {
+ state = STATE_COLLECT_EXPRESSION;
+ i++;
+
+ expressionStart = i;
+ expressionLength = 0;
+ braceDepth = 1;
+
+ continue;
+ }
+
+ // The '$' was just what it was, not the start of a ${} expression
+ // block, so include it as part of the static text block.
+
+ blockLength++;
+
+ state = STATE_START;
+ continue;
+
+ case STATE_COLLECT_EXPRESSION :
+
+ if (ch != '}')
+ {
+ if (ch == '{')
+ braceDepth++;
+
+ i++;
+ expressionLength++;
+ continue;
+ }
+
+ braceDepth--;
+
+ if (braceDepth > 0)
+ {
+ i++;
+ expressionLength++;
+ continue;
+ }
+
+ // Hit the closing brace of an expression.
+
+ // Degenerate case: the string "${}".
+
+ if (expressionLength == 0)
+ blockLength += 3;
+
+ if (blockLength > 0)
+ token.addToken(constructStatic(text, blockStart, blockLength, location));
+
+ if (expressionLength > 0)
+ {
+ String expression =
+ text.substring(expressionStart, expressionStart + expressionLength);
+
+ token.addToken(new InsertToken(expression, location));
+ }
+
+ i++;
+ blockStart = i;
+ blockLength = 0;
+
+ // And drop into state start
+
+ state = STATE_START;
+
+ continue;
+ }
+
+ }
+
+ // OK, to handle the end. Couple of degenerate cases where
+ // a ${...} was incomplete, so we adust the block length.
+
+ if (state == STATE_DOLLAR)
+ blockLength++;
+
+ if (state == STATE_COLLECT_EXPRESSION)
+ blockLength += expressionLength + 2;
+
+ if (blockLength > 0)
+ token.addToken(constructStatic(text, blockStart, blockLength, location));
+ }
+
+ private IScriptToken constructStatic(
+ String text,
+ int blockStart,
+ int blockLength,
+ ILocation location)
+ {
+ String literal = text.substring(blockStart, blockStart + blockLength);
+
+ return new StaticToken(literal, location);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/BodyRule.java b/tapestry-framework/src/org/apache/tapestry/script/BodyRule.java
new file mode 100644
index 0000000..0e0ddfe
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/BodyRule.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs a {@link org.apache.tapestry.script.BodyToken} from
+ * a <body> element, which contains full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class BodyRule extends AbstractTokenRule
+{
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ BodyToken token = new BodyToken(parser.getLocation());
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/BodyToken.java b/tapestry-framework/src/org/apache/tapestry/script/BodyToken.java
new file mode 100644
index 0000000..2bb18d3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/BodyToken.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+
+
+/**
+ * Generates a String from its child tokens, then applies it
+ * to {@link ScriptSession#setBody(String)}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+class BodyToken extends AbstractToken
+{
+ private int _bufferLengthHighwater = 100;
+
+ public BodyToken(ILocation location)
+ {
+ super(location);
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ if (buffer != null)
+ throw new IllegalArgumentException();
+
+ buffer = new StringBuffer(_bufferLengthHighwater);
+
+ writeChildren(buffer, session);
+
+ session.getProcessor().addBodyScript(buffer.toString());
+
+ // Store the buffer length from this run for the next run, since its
+ // going to be approximately the right size.
+
+ _bufferLengthHighwater = Math.max(_bufferLengthHighwater, buffer.length());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ForeachRule.java b/tapestry-framework/src/org/apache/tapestry/script/ForeachRule.java
new file mode 100644
index 0000000..e28c885
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ForeachRule.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs a {@link org.apache.tapestry.script.ForeachToken}
+ * from a <foreach> element, which contains full content.
+ *
+ * <p>As of 3.0, then index attribute has been added to foreach to keep
+ * track of the current index of the iterating collection.</p>
+ *
+ * @author Howard Lewis Ship, Harish Krishnaswamy
+ * @version $Id$
+ * @since 3.0
+ */
+class ForeachRule extends AbstractTokenRule
+{
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String key = getAttribute(attributes, "key");
+ String index = getAttribute(attributes, "index");
+ String expression = getAttribute(attributes, "expression");
+
+ if (expression == null)
+ expression = getAttribute(attributes, "property-path"); // 1.0, 1.1 DTD
+
+ IScriptToken token = new ForeachToken(key, index, expression, parser.getLocation());
+
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ForeachToken.java b/tapestry-framework/src/org/apache/tapestry/script/ForeachToken.java
new file mode 100644
index 0000000..472fcf9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ForeachToken.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A looping operator, modeled after the Foreach component. It takes
+ * as its source as property and iterates through the values, updating
+ * a symbol on each pass.
+ *
+ * <p>As of 3.0, the index attribute has been added to foreach to keep
+ * track of the current index of the iterating collection.</p>
+ *
+ * @author Howard Lewis Ship, Harish Krishnaswamy
+ * @version $Id$
+ * @since 1.0.1
+ *
+ **/
+
+class ForeachToken extends AbstractToken
+{
+ private String _key;
+ private String _index;
+ private String _expression;
+
+ ForeachToken(String key, String index, String expression, ILocation location)
+ {
+ super(location);
+
+ _key = key;
+ _index = index;
+ _expression = expression;
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ Map symbols = session.getSymbols();
+
+ Object rawSource = evaluate(_expression, session);
+
+ Iterator i = Tapestry.coerceToIterator(rawSource);
+
+ if (i == null)
+ return;
+
+ int index = 0;
+
+ while (i.hasNext())
+ {
+ Object newValue = i.next();
+
+ symbols.put(_key, newValue);
+
+ if (_index != null)
+ symbols.put(_index, String.valueOf(index));
+
+ writeChildren(buffer, session);
+
+ index++;
+ }
+
+ // We leave the last value as a symbol; don't know if that's
+ // good or bad.
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/IScriptToken.java b/tapestry-framework/src/org/apache/tapestry/script/IScriptToken.java
new file mode 100644
index 0000000..a181da5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/IScriptToken.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocatable;
+
+
+/**
+ * Defines the responsibilities of a template token used by a
+ * {@link org.apache.tapestry.IScript}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface IScriptToken extends ILocatable
+{
+ /**
+ * Invoked to have the token
+ * add its text to the buffer. A token may need access
+ * to the symbols in order to produce its output.
+ *
+ * <p>Top level tokens (such as BodyToken) can expect that
+ * buffer will be null.
+ *
+ **/
+
+ public void write(StringBuffer buffer, ScriptSession session);
+
+ /**
+ * Invoked during parsing to add the token parameter as a child
+ * of this token.
+ *
+ * @since 0.2.9
+ **/
+
+ public void addToken(IScriptToken token);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/IfRule.java b/tapestry-framework/src/org/apache/tapestry/script/IfRule.java
new file mode 100644
index 0000000..244d202
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/IfRule.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.IfToken}
+ * from an <if> or <if-not> element, which
+ * contains full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class IfRule extends AbstractTokenRule
+{
+ private boolean _condition;
+
+ public IfRule(boolean condition)
+ {
+ _condition = condition;
+ }
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String expression = getAttribute(attributes, "expression");
+
+ if (expression == null)
+ expression = getAttribute(attributes, "property-path"); // 1.0, 1.1 DTD
+
+ IScriptToken token = new IfToken(_condition, expression, parser.getLocation());
+
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/IfToken.java b/tapestry-framework/src/org/apache/tapestry/script/IfToken.java
new file mode 100644
index 0000000..06ec384
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/IfToken.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A conditional portion of the generated script.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.1
+ *
+ **/
+
+class IfToken extends AbstractToken
+{
+ private boolean _condition;
+ private String _expression;
+
+ IfToken(boolean condition, String expression, ILocation location)
+ {
+ super(location);
+
+ _condition = condition;
+ _expression = expression;
+ }
+
+ private boolean evaluate(ScriptSession session)
+ {
+ Object value = evaluate(_expression, session);
+
+ return Tapestry.evaluateBoolean(value);
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ if (evaluate(session) == _condition)
+ writeChildren(buffer, session);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptRule.java b/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptRule.java
new file mode 100644
index 0000000..02c74f2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptRule.java
@@ -0,0 +1,42 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.BaseRule;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.IncludeScriptToken}
+ * from a <include-script> element, which contains no content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class IncludeScriptRule extends BaseRule
+{
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String path = getAttribute(attributes, "resource-path");
+
+ IncludeScriptToken token = new IncludeScriptToken(path, parser.getLocation());
+
+ IScriptToken parent = (IScriptToken) parser.peek();
+ parent.addToken(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptToken.java b/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptToken.java
new file mode 100644
index 0000000..49a694c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/IncludeScriptToken.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+
+/**
+ * A token for included scripts.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+class IncludeScriptToken extends AbstractToken
+{
+ private String _resourcePath;
+
+ public IncludeScriptToken(String resourcePath, ILocation location)
+ {
+ super(location);
+
+ _resourcePath = resourcePath;
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ IResourceLocation includeLocation = null;
+
+ if (_resourcePath.startsWith("/"))
+ {
+ includeLocation =
+ new ClasspathResourceLocation(
+ session.getRequestCycle().getEngine().getResourceResolver(),
+ _resourcePath);
+ }
+ else
+ {
+ IResourceLocation baseLocation = session.getScriptPath();
+ includeLocation = baseLocation.getRelativeLocation(_resourcePath);
+ }
+
+ // TODO: Allow for scripts relative to context resources!
+
+ session.getProcessor().addExternalScript(includeLocation);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InitRule.java b/tapestry-framework/src/org/apache/tapestry/script/InitRule.java
new file mode 100644
index 0000000..8872f99
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InitRule.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.InitToken}
+ * from an <initialization> element, which
+ * contains full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+public class InitRule extends AbstractTokenRule
+{
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ IScriptToken token = new InitToken(parser.getLocation());
+
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InitToken.java b/tapestry-framework/src/org/apache/tapestry/script/InitToken.java
new file mode 100644
index 0000000..882a995
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InitToken.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * Generates a String from its child tokens, then applies it
+ * to {@link ScriptSession#setInitialization(String)}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+class InitToken extends AbstractToken
+{
+ private int _bufferLengthHighwater = 100;
+
+ public InitToken(ILocation location)
+ {
+ super(location);
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ if (buffer != null)
+ throw new IllegalArgumentException();
+
+ buffer = new StringBuffer(_bufferLengthHighwater);
+
+ writeChildren(buffer, session);
+
+ session.getProcessor().addInitializationScript(buffer.toString());
+
+ // Store the buffer length from this run for the next run, since its
+ // going to be approximately the right size.
+
+ _bufferLengthHighwater = Math.max(_bufferLengthHighwater, buffer.length());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InputSymbolRule.java b/tapestry-framework/src/org/apache/tapestry/script/InputSymbolRule.java
new file mode 100644
index 0000000..042f4f3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InputSymbolRule.java
@@ -0,0 +1,77 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.BaseRule;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.InputSymbolToken}
+ * from an <input-symbol> element.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class InputSymbolRule extends BaseRule
+{
+ private IResourceResolver _resolver;
+
+ public InputSymbolRule(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String key = getAttribute(attributes, "key");
+
+ parser.validate(key, Tapestry.SIMPLE_PROPERTY_NAME_PATTERN, "ScriptParser.invalid-key");
+
+ String className = getAttribute(attributes, "class");
+ Class expectedClass = lookupClass(parser, className);
+
+ String required = getAttribute(attributes, "required");
+
+ InputSymbolToken token =
+ new InputSymbolToken(key, expectedClass, required.equals("yes"), parser.getLocation());
+
+ IScriptToken parent = (IScriptToken) parser.peek();
+ parent.addToken(token);
+ }
+
+ private Class lookupClass(RuleDirectedParser parser, String className)
+ {
+ if (Tapestry.isBlank(className))
+ return null;
+
+ try
+ {
+ return _resolver.findClass(className);
+ }
+ catch (Exception ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("ScriptParser.unable-to-resolve-class", className),
+ parser.getDocumentLocation(),
+ parser.getLocation(),
+ ex);
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InputSymbolToken.java b/tapestry-framework/src/org/apache/tapestry/script/InputSymbolToken.java
new file mode 100644
index 0000000..dbecfd6
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InputSymbolToken.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A token that validates that an input symbol exists or is of a
+ * declared type.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+class InputSymbolToken extends AbstractToken
+{
+ private String _key;
+ private Class _class;
+ private boolean _required;
+
+ InputSymbolToken(String key, Class clazz, boolean required, ILocation location)
+ {
+ super(location);
+
+ _key = key;
+ _class = clazz;
+ _required = required;
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ Object value = session.getSymbols().get(_key);
+
+ if (_required && value == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("InputSymbolToken.required", _key),
+ getLocation(),
+ null);
+
+ if (value != null && _class != null && !_class.isAssignableFrom(value.getClass()))
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "InputSymbolToken.wrong-type",
+ _key,
+ value.getClass().getName(),
+ _class.getName()),
+ getLocation(),
+ null);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InsertRule.java b/tapestry-framework/src/org/apache/tapestry/script/InsertRule.java
new file mode 100644
index 0000000..c3b919f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InsertRule.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.InsertToken}
+ * from an <insert> element, which contains full content.
+ * <insert> is a throwback to the 1.0 and 1.1 DTDs.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class InsertRule extends AbstractTokenRule
+{
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ // property-path is really an OGNL expression.
+ String expression = getAttribute(attributes, "property-path");
+
+ // Was called key in the 1.0 DTD
+ if (expression == null)
+ expression = getAttribute(attributes, "key");
+
+ IScriptToken token = new InsertToken(expression, parser.getLocation());
+
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/InsertToken.java b/tapestry-framework/src/org/apache/tapestry/script/InsertToken.java
new file mode 100644
index 0000000..fb34dc7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/InsertToken.java
@@ -0,0 +1,58 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * A token that writes the value of a property using a property path
+ * routed in the symbols..
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class InsertToken extends AbstractToken
+{
+ private String _expression;
+
+ InsertToken(String expression, ILocation location)
+ {
+ super(location);
+
+ _expression = expression;
+ }
+
+ /**
+ * Gets the named symbol from the symbols {@link Map}, verifies that
+ * it is a String, and writes it to the {@link Writer}.
+ *
+ **/
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ Object value = evaluate(_expression, session);
+
+ if (value != null)
+ buffer.append(value);
+ }
+
+ public void addToken(IScriptToken token)
+ {
+ // Should never be invoked.
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/LetRule.java b/tapestry-framework/src/org/apache/tapestry/script/LetRule.java
new file mode 100644
index 0000000..63aef50
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/LetRule.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs an {@link org.apache.tapestry.script.LetToken}
+ * from a <let> element, which may contain full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+class LetRule extends AbstractTokenRule
+{
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String key = getAttribute(attributes, "key");
+
+ String unique = getAttribute(attributes, "unique");
+ boolean uniqueFlag = unique != null && unique.equals("yes");
+
+ parser.validate(key, Tapestry.SIMPLE_PROPERTY_NAME_PATTERN, "ScriptParser.invalid-key");
+
+ LetToken token = new LetToken(key, uniqueFlag, parser.getLocation());
+
+ addToParent(parser, token);
+
+ parser.push(token);
+ }
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/LetToken.java b/tapestry-framework/src/org/apache/tapestry/script/LetToken.java
new file mode 100644
index 0000000..8561ec4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/LetToken.java
@@ -0,0 +1,73 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import java.util.Map;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ * Allows for the creation of new symbols that can be used in the script
+ * or returned to the caller.
+ *
+ * <p>The <let> tag wraps around static text and <insert>
+ * elements. The results are trimmed.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+class LetToken extends AbstractToken
+{
+ private String _key;
+ private boolean _unique;
+ private int _bufferLengthHighwater = 20;
+
+ public LetToken(String key, boolean unique, ILocation location)
+ {
+ super(location);
+
+ _key = key;
+ _unique = unique;
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ if (buffer != null)
+ throw new IllegalArgumentException();
+
+ buffer = new StringBuffer(_bufferLengthHighwater);
+
+ writeChildren(buffer, session);
+
+ // Store the symbol back into the root set of symbols.
+
+ Map symbols = session.getSymbols();
+
+ String value = buffer.toString().trim();
+
+ if (_unique)
+ value = session.getProcessor().getUniqueString(value);
+
+ symbols.put(_key, value);
+
+ // Store the buffer length from this run for the next run, since its
+ // going to be approximately the right size.
+
+ _bufferLengthHighwater = Math.max(_bufferLengthHighwater, buffer.length());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ParsedScript.java b/tapestry-framework/src/org/apache/tapestry/script/ParsedScript.java
new file mode 100644
index 0000000..94e7036
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ParsedScript.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import java.util.Map;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.IScriptProcessor;
+
+/**
+ * A top level container for a number of {@link IScriptToken script tokens}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+public class ParsedScript extends AbstractToken implements IScript
+{
+ private IResourceLocation _scriptLocation;
+
+ public ParsedScript(ILocation location)
+ {
+ super(location);
+
+ _scriptLocation = location.getResourceLocation();
+ }
+
+ public IResourceLocation getScriptLocation()
+ {
+ return _scriptLocation;
+ }
+
+ /**
+ * Creates the {@link ScriptSession} and invokes
+ * {@link org.apache.tapestry.script.AbstractToken#writeChildren(java.lang.StringBuffer, org.apache.tapestry.script.ScriptSession)}.
+ */
+ public void execute(IRequestCycle cycle, IScriptProcessor processor, Map symbols)
+ {
+ ScriptSession session = new ScriptSession(_scriptLocation, cycle, processor, symbols);
+ writeChildren(null, session);
+ }
+
+ /**
+ * Does nothing; never invoked.
+ */
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ScriptParser.java b/tapestry-framework/src/org/apache/tapestry/script/ScriptParser.java
new file mode 100644
index 0000000..5efe0de
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ScriptParser.java
@@ -0,0 +1,118 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.util.xml.DocumentParseException;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+
+/**
+ * Parses a Tapestry Script, an XML file defined by one of the following
+ * public identifiers:
+ * <ul>
+ * <li><code>-//Primix Solutions//Tapestry Script 1.0//EN</code></li>
+ * <li><code>-//Howard Ship//Tapestry Script 1.1//EN</code></li>
+ * <li><code>-//Howard Lewis Ship//Tapestry Script 1.2//EN</code></li>
+ * .
+ *
+ * <p>The version 1.1, is largely backwards compatible to the
+ * old script, but adds a number of new features (if, if-not, foreach
+ * and the use of property paths with insert).
+ *
+ * <p>Version 1.2 removes the <insert> element, using an Ant-like
+ * syntax (<code>${<i>expression</i>}</code>). It also replaces
+ * the attribute name <code>property-path</code> with <code>expression</code>
+ * (because OGNL is used).
+ *
+ * <p>A Tapestry Script is used, in association with the
+ * {@link org.apache.tapestry.html.Body} and/or
+ * {@link org.apache.tapestry.html.Script} components,
+ * to generate JavaScript for use with a Tapestry component. Two seperate pieces
+ * of JavaScript can be generated. The body section (associated with the <code>body</code>
+ * element of the XML document) is typically used to define JavaScript functions
+ * (most often, event handlers). The initialization section
+ * (associated with the <code>initialization</code> element of the XML document)
+ * is used to add JavaScript that will be evaluated when the page finishes loading
+ * (i.e., from the HTML <body> element's onLoad event handler).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ScriptParser
+{
+ private RuleDirectedParser _parser;
+
+ public static final String SCRIPT_DTD_1_0_PUBLIC_ID =
+ "-//Primix Solutions//Tapestry Script 1.0//EN";
+
+ public static final String SCRIPT_DTD_1_1_PUBLIC_ID = "-//Howard Ship//Tapestry Script 1.1//EN";
+
+ public static final String SCRIPT_DTD_1_2_PUBLIC_ID =
+ "-//Howard Lewis Ship//Tapestry Script 1.2//EN";
+
+ /** @since 3.0 */
+ public static final String SCRIPT_DTD_3_0_PUBLIC_ID =
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN";
+
+ public ScriptParser(IResourceResolver resolver)
+ {
+ _parser = new RuleDirectedParser();
+
+ _parser.registerEntity(
+ SCRIPT_DTD_1_0_PUBLIC_ID,
+ "/org/apache/tapestry/script/Script_1_0.dtd");
+ _parser.registerEntity(
+ SCRIPT_DTD_1_1_PUBLIC_ID,
+ "/org/apache/tapestry/script/Script_1_1.dtd");
+ _parser.registerEntity(
+ SCRIPT_DTD_1_2_PUBLIC_ID,
+ "/org/apache/tapestry/script/Script_1_2.dtd");
+ _parser.registerEntity(
+ SCRIPT_DTD_3_0_PUBLIC_ID,
+ "/org/apache/tapestry/script/Script_3_0.dtd");
+
+ _parser.addRule("script", new ScriptRule());
+ _parser.addRule("let", new LetRule());
+ _parser.addRule("set", new SetRule());
+ _parser.addRule("include-script", new IncludeScriptRule());
+ _parser.addRule("input-symbol", new InputSymbolRule(resolver));
+ _parser.addRule("body", new BodyRule());
+ _parser.addRule("initialization", new InitRule());
+ _parser.addRule("if", new IfRule(true));
+ _parser.addRule("if-not", new IfRule(false));
+ _parser.addRule("foreach", new ForeachRule());
+ _parser.addRule("unique", new UniqueRule());
+
+ // This will go away when the 1.1 and earler DTDs are retired.
+ _parser.addRule("insert", new InsertRule());
+
+ }
+
+ /**
+ * Parses the given input stream to produce a parsed script,
+ * ready to execute.
+ *
+ **/
+
+ public IScript parse(IResourceLocation resourceLocation) throws DocumentParseException
+ {
+ return (IScript) _parser.parse(resourceLocation);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ScriptRule.java b/tapestry-framework/src/org/apache/tapestry/script/ScriptRule.java
new file mode 100644
index 0000000..7eccad8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ScriptRule.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.BaseRule;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Rule for <script> element. Creates a {@link org.apache.tapestry.script.ParsedScript}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+public class ScriptRule extends BaseRule
+{
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ ParsedScript script = new ParsedScript(parser.getLocation());
+
+ parser.push(script);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/ScriptSession.java b/tapestry-framework/src/org/apache/tapestry/script/ScriptSession.java
new file mode 100644
index 0000000..a8dae06
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/ScriptSession.java
@@ -0,0 +1,84 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import java.util.Map;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScriptProcessor;
+
+/**
+ * The result of executing a script, the session is used during the parsing
+ * process as well. Following {@link org.apache.tapestry.IScript#execute(org.apache.tapestry.IRequestCycle, org.apache.tapestry.IScriptProcessor, java.util.Map)}, the session
+ * provides access to output symbols as well as the body and initialization
+ * blocks created by the script tokens.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.9
+ *
+ **/
+
+public class ScriptSession
+{
+ private IRequestCycle _cycle;
+ private IScriptProcessor _processor;
+ private IResourceLocation _scriptLocation;
+ private Map _symbols;
+
+ public ScriptSession(
+ IResourceLocation scriptLocation,
+ IRequestCycle cycle,
+ IScriptProcessor processor,
+ Map symbols)
+ {
+ _scriptLocation = scriptLocation;
+ _cycle = cycle;
+ _processor = processor;
+ _symbols = symbols;
+ }
+
+ public IResourceLocation getScriptPath()
+ {
+ return _scriptLocation;
+ }
+
+ public Map getSymbols()
+ {
+ return _symbols;
+ }
+
+ public IRequestCycle getRequestCycle()
+ {
+ return _cycle;
+ }
+
+ public IScriptProcessor getProcessor()
+ {
+ return _processor;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ buffer.append("ScriptSession[");
+ buffer.append(_scriptLocation);
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/Script_1_0.dtd b/tapestry-framework/src/org/apache/tapestry/script/Script_1_0.dtd
new file mode 100644
index 0000000..5c91d5d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/Script_1_0.dtd
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Script_1_0.dtd,v 1.3 2002/05/02 17:52:39 hship Exp $ -->
+<!--DTD for the files used with the ScriptGenerator class and Script component. This is recognized with the public identifier:
+
+ -//Primix Solutions//Tapestry Script 1.0//EN
+
+-->
+
+<!--
+
+Element: script
+
+Root element.
+
+-->
+
+<!ELEMENT script (let*, body?, initialization?)>
+
+<!--
+
+Element: let
+Contained by: script
+
+Used to create a new symbol.
+
+-->
+
+<!ELEMENT let (#PCDATA | insert)*>
+<!ATTLIST let
+ key CDATA #REQUIRED
+>
+
+<!--
+
+Element: body
+Contained by: script
+
+Allows a mix of text and insert elements. This text is added to
+the large scripting block just before the <body> tag.
+-->
+
+<!ELEMENT body (#PCDATA | insert)*>
+
+<!--
+Element: initialization
+Contained by: script
+
+Text in this block is added to the event handler for the <body>
+tag's onLoad event.
+-->
+
+<!ELEMENT initialization (#PCDATA | insert)*>
+
+<!--
+Element: insert
+Contained by: body, initialization
+
+Allows an arbitrary symbol to be inserted.
+-->
+
+<!ELEMENT insert (#PCDATA)>
+<!ATTLIST insert
+ key CDATA #REQUIRED
+>
diff --git a/tapestry-framework/src/org/apache/tapestry/script/Script_1_1.dtd b/tapestry-framework/src/org/apache/tapestry/script/Script_1_1.dtd
new file mode 100644
index 0000000..911624c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/Script_1_1.dtd
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Script_1_1.dtd,v 1.3 2002/05/02 17:52:39 hship Exp $ -->
+<!--
+
+DTD for the files used with the ScriptGenerator class and Script component.
+This is recognized with the public identifier:
+
+ -//Howard Ship//Tapestry Script 1.1//EN
+
+The canonical location for the DTD is:
+
+ http://tapestry.sf.net/dtd/Tapestry_1_1.dtd
+
+The root element is always script.
+
+-->
+<!-- =======================================================
+
+Entity: full-content
+
+Identifies the contents of most of the other elements.
+
+-->
+<!ENTITY % full-content "(#PCDATA | foreach | insert | if | if-not)*">
+<!-- =======================================================
+
+Element: body
+Contained by: script
+
+Allows a mix of text and insert elements. This text is added to
+the large scripting block just before the <body> tag.
+-->
+<!ELEMENT body %full-content;>
+<!-- =======================================================
+
+Element: foreach
+Appears in: %full-content;
+
+Iterates over a list of items; this is modeled after the
+Foreach component. No iteration occurs if the value
+from the property path is null.
+
+Attributes:
+ key: Defines the symbol into which each succesive value is stored.
+ property-path: The source of values.
+-->
+<!ELEMENT foreach %full-content;>
+<!ATTLIST foreach
+ key CDATA #REQUIRED
+ property-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: include-script
+Contained by: script
+
+Inserts a reference to an external, static, JavaScript file.
+
+Attributes:
+ resource-path: The path to the script within the classpath.
+-->
+<!ELEMENT include-script EMPTY>
+<!ATTLIST include-script
+ resource-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+
+Element: if
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the property-path evaulates to true.
+
+-->
+<!ELEMENT if %full-content;>
+<!ATTLIST if
+ property-path CDATA #REQUIRED
+>
+<!-- =======================================================
+
+Element: if-not
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the property-path evaulates to false.
+
+-->
+<!ELEMENT if-not %full-content;>
+<!ATTLIST if-not
+ property-path CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: initialization
+Contained by: script
+
+Text in this block is added to the event handler for the <body>
+tag's onLoad event.
+-->
+<!ELEMENT initialization %full-content;>
+<!-- =======================================================
+Element: insert
+Contained by: body, initialization
+
+Allows an arbitrary symbol to be inserted.
+
+Attributes:
+ property-path: The path to the value to insert.
+-->
+<!ELEMENT insert EMPTY>
+<!ATTLIST insert
+ property-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+
+Element: let
+Contained by: script
+
+Used to create a new symbol.
+
+-->
+<!ELEMENT let %full-content;>
+<!ATTLIST let
+ key CDATA #REQUIRED
+>
+<!-- =======================================================
+
+Element: script
+
+Root element.
+
+Allows zero or more let elements (to establish new symbols),
+followed by a body and/or initialization element.
+
+-->
+<!ELEMENT script (include-script*, let*, body?, initialization?)>
diff --git a/tapestry-framework/src/org/apache/tapestry/script/Script_1_2.dtd b/tapestry-framework/src/org/apache/tapestry/script/Script_1_2.dtd
new file mode 100644
index 0000000..52ef476
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/Script_1_2.dtd
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Script_1_2.dtd,v 1.3 2002/09/16 19:33:02 hship Exp $ -->
+<!--
+
+DTD for the files used with the ScriptParser class and Script component.
+This is recognized with the public identifier:
+
+ -//Howard Lewis Ship//Tapestry Script 1.2//EN
+
+The canonical location for the DTD is:
+
+ http://tapestry.sf.net/dtd/Script_1_2.dtd
+
+The root element is always script.
+
+-->
+<!-- =======================================================
+
+Entity: full-content
+
+Identifies the contents of most of the other elements.
+
+-->
+<!ENTITY % full-content "(#PCDATA | foreach | if | if-not)*">
+<!-- =======================================================
+
+Element: body
+Contained by: script
+
+Allows a mix of text and insert elements. This text is added to
+the large scripting block just before the <body> tag.
+-->
+<!ELEMENT body %full-content;>
+<!-- =======================================================
+
+Element: foreach
+Appears in: %full-content;
+
+Iterates over a list of items; this is modeled after the
+Foreach component. No iteration occurs if the value
+from the property path is null.
+
+Attributes:
+ key: Defines the symbol into which each succesive value is stored.
+ expression: The source of values, as an OGNL expression rooted in the symbols Map.
+-->
+<!ELEMENT foreach %full-content;>
+<!ATTLIST foreach
+ key CDATA #REQUIRED
+ expression CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: include-script
+Contained by: script
+
+Inserts a reference to an external, static, JavaScript file.
+
+Attributes:
+ resource-path: The path to the script within the classpath.
+-->
+<!ELEMENT include-script EMPTY>
+<!ATTLIST include-script
+ resource-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+
+Element: if
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the expression evaulates to true.
+
+Attributes:
+ expression: The trigger expression, as an OGNL expression rooted in
+ the symbols Map.
+
+-->
+<!ELEMENT if %full-content;>
+<!ATTLIST if
+ expression CDATA #REQUIRED
+>
+<!-- =======================================================
+
+Element: if-not
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the property-path evaulates to false.
+
+Attributes:
+ expression: The trigger expression, as an OGNL expression rooted in
+ the symbols Map.
+
+-->
+<!ELEMENT if-not %full-content;>
+<!ATTLIST if-not
+ expression CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: initialization
+Contained by: script
+
+Text in this block is added to the event handler for the <body>
+tag's onLoad event.
+-->
+<!ELEMENT initialization %full-content;>
+
+
+
+<!-- =======================================================
+Element: input-symbol
+Contained by: script
+
+Defines an input symbol used by the script.
+Attributes:
+ key: The name of the symbol.
+ class: If specified, the exected class or interface for the symbol.
+ required: If yes, then the symbol must be non-null.
+-->
+
+<!ELEMENT input-symbol EMPTY>
+<!ATTLIST input-symbol
+ key CDATA #REQUIRED
+ class CDATA #IMPLIED
+ required (yes|no) "no"
+>
+
+<!-- =======================================================
+
+Element: let
+Contained by: script
+
+Used to create a new symbol.
+
+-->
+<!ELEMENT let %full-content;>
+<!ATTLIST let
+ key CDATA #REQUIRED
+>
+<!-- =======================================================
+
+Element: script
+
+Root element.
+
+Allows zero or more let elements (to establish new symbols),
+followed by a body and/or initialization element.
+
+-->
+<!ELEMENT script (include-script*, input-symbol*,
+ (let | set)*, body?, initialization?)>
+
+
+<!-- =======================================================
+
+Element: set
+Contained by: script
+
+Creates a new symbol as the result of evaluating an OGNL expression.
+
+-->
+<!ELEMENT set EMPTY>
+<!ATTLIST set
+ key CDATA #REQUIRED
+ expression CDATA #REQUIRED
+>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/Script_3_0.dtd b/tapestry-framework/src/org/apache/tapestry/script/Script_3_0.dtd
new file mode 100644
index 0000000..e36a857
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/Script_3_0.dtd
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- $Id: Script_1_2.dtd,v 1.3 2002/09/16 19:33:02 hship Exp $ -->
+<!--
+
+DTD for the files used with the ScriptParser class and Script component.
+This is recognized with the public identifier:
+
+ -//Apache Software Foundation//Tapestry Script Specification 1.3//EN
+
+The canonical location for the DTD is:
+
+ http://jakarta.apache.org/tapestry/dtd/Script_1_3.dtd
+
+The root element is always script.
+
+This DTD is backwards compatible with the 1.2 DTD, with the following exceptions:
+- Addition of <unique> element
+- Addition of unique attribute to <let> element
+- Addition of index attribute to <foreach> element
+-->
+<!-- =======================================================
+
+Entity: full-content
+
+Identifies the contents of most of the other elements.
+
+-->
+<!ENTITY % full-content "(#PCDATA | foreach | if | if-not | unique)*">
+<!-- =======================================================
+
+Element: body
+Contained by: script
+
+Allows a mix of text and insert elements. This text is added to
+the large scripting block just before the <body> tag.
+-->
+<!ELEMENT body %full-content;>
+<!-- =======================================================
+
+Element: foreach
+Appears in: %full-content;
+
+Iterates over a list of items; this is modeled after the
+Foreach component. No iteration occurs if the value
+from the property path is null.
+
+Attributes:
+ key: Defines the symbol into which each succesive value is stored.
+ index: Defines the symbol into which the index of the value of the current iteration is stored.
+ expression: The source of values, as an OGNL expression rooted in the symbols Map.
+-->
+<!ELEMENT foreach %full-content;>
+<!ATTLIST foreach
+ key CDATA #IMPLIED
+ index CDATA #IMPLIED
+ expression CDATA #REQUIRED
+>
+
+<!-- =======================================================
+Element: include-script
+Contained by: script
+
+Inserts a reference to an external, static, JavaScript file.
+
+Attributes:
+ resource-path: The path to the script within the classpath.
+-->
+<!ELEMENT include-script EMPTY>
+<!ATTLIST include-script
+ resource-path CDATA #REQUIRED
+>
+
+<!-- =======================================================
+
+Element: if
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the expression evaulates to true.
+
+Attributes:
+ expression: The trigger expression, as an OGNL expression rooted in
+ the symbols Map.
+
+-->
+<!ELEMENT if %full-content;>
+<!ATTLIST if
+ expression CDATA #REQUIRED
+>
+<!-- =======================================================
+
+Element: if-not
+Appears in: %full-content;
+
+Creates a conditional portion of the script; The body of the element
+is only included if the property-path evaulates to false.
+
+Attributes:
+ expression: The trigger expression, as an OGNL expression rooted in
+ the symbols Map.
+
+-->
+<!ELEMENT if-not %full-content;>
+<!ATTLIST if-not
+ expression CDATA #REQUIRED
+>
+<!-- =======================================================
+Element: initialization
+Contained by: script
+
+Text in this block is added to the event handler for the <body>
+tag's onLoad event.
+-->
+<!ELEMENT initialization %full-content;>
+
+
+
+<!-- =======================================================
+Element: input-symbol
+Contained by: script
+
+Defines an input symbol used by the script.
+Attributes:
+ key: The name of the symbol.
+ class: If specified, the exected class or interface for the symbol.
+ required: If yes, then the symbol must be non-null.
+-->
+
+<!ELEMENT input-symbol EMPTY>
+<!ATTLIST input-symbol
+ key CDATA #REQUIRED
+ class CDATA #IMPLIED
+ required (yes|no) "no"
+>
+
+<!-- =======================================================
+
+Element: let
+Contained by: script
+
+Used to create a new symbol. The content of the tag
+is used to create a string that is the name. If the
+unique flag is enabled, the name is ensured to be unique
+(a suffix may be appended to ensure it is unique
+among all names so generated).
+
+Attributes:
+ key: The name of the symbol to create.
+ unique: If yes, the name is ensured to be unique.
+ The default is no.
+
+-->
+<!ELEMENT let %full-content;>
+<!ATTLIST let
+ key CDATA #REQUIRED
+ unique (yes|no) "no"
+>
+<!-- =======================================================
+
+Element: script
+
+Root element.
+
+Allows zero or more let elements (to establish new symbols),
+followed by a body and/or initialization element.
+
+-->
+<!ELEMENT script (include-script*, input-symbol*,
+ (let | set)*, body?, initialization?)>
+
+
+<!-- =======================================================
+
+Element: set
+Contained by: script
+
+Creates a new symbol as the result of evaluating an OGNL expression.
+
+-->
+<!ELEMENT set EMPTY>
+<!ATTLIST set
+ key CDATA #REQUIRED
+ expression CDATA #REQUIRED
+>
+
+<!-- =======================================================
+
+Element: unique
+Appears in: %full-content;
+
+Defines a block that only is rendered once per page.
+This is appropriate to certain kinds of initialization code
+that should not be duplicated, even if the script is
+executed multiple times.
+
+-->
+<!ELEMENT unique %full-content;>
diff --git a/tapestry-framework/src/org/apache/tapestry/script/SetRule.java b/tapestry-framework/src/org/apache/tapestry/script/SetRule.java
new file mode 100644
index 0000000..8de003c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/SetRule.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.xml.BaseRule;
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs at {@link org.apache.tapestry.script.SetToken} from at <set> element,
+ * which contains full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+class SetRule extends BaseRule
+{
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ String key = getAttribute(attributes, "key");
+
+ parser.validate(key, Tapestry.SIMPLE_PROPERTY_NAME_PATTERN, "ScriptParser.invalid-key");
+
+ String expression = getAttribute(attributes, "expression");
+
+ SetToken token = new SetToken(key, expression, parser.getLocation());
+
+ IScriptToken parent = (IScriptToken) parser.peek();
+ parent.addToken(token);
+
+ parser.push(token);
+ }
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/SetToken.java b/tapestry-framework/src/org/apache/tapestry/script/SetToken.java
new file mode 100644
index 0000000..52038ef
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/SetToken.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+
+/**
+ *
+ * Like {@link org.apache.tapestry.script.LetToken}, but sets the value
+ * from an expression attribute, rather than a body of full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+class SetToken extends AbstractToken
+{
+ private String _key;
+ private String _expression;
+
+ SetToken(String key, String expression, ILocation location)
+ {
+ super(location);
+ _key = key;
+ _expression = expression;
+ }
+
+ /**
+ *
+ * Doesn't <em>write</em>, it evaluates the expression and assigns
+ * the result back to the key.
+ *
+ **/
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+
+ Object value = evaluate(_expression, session);
+
+ session.getSymbols().put(_key, value);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/StaticToken.java b/tapestry-framework/src/org/apache/tapestry/script/StaticToken.java
new file mode 100644
index 0000000..9a1b525
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/StaticToken.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+
+
+/**
+ * A token for static portions of the template.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class StaticToken extends AbstractToken
+{
+ private String _text;
+
+ StaticToken(String text, ILocation location )
+ {
+ super(location);
+
+ _text = text;
+ }
+
+ /**
+ * Writes the text to the writer.
+ *
+ **/
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ buffer.append(_text);
+ }
+
+ public void addToken(IScriptToken token)
+ {
+ // Should never be invoked.
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/script/UniqueRule.java b/tapestry-framework/src/org/apache/tapestry/script/UniqueRule.java
new file mode 100644
index 0000000..6294f2e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/UniqueRule.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.util.xml.RuleDirectedParser;
+import org.xml.sax.Attributes;
+
+/**
+ * Constructs a {@link org.apache.tapestry.script.UniqueToken}
+ * from an <unique> element, which contains full content.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+public class UniqueRule extends AbstractTokenRule
+{
+
+ public void endElement(RuleDirectedParser parser)
+ {
+ parser.pop();
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+ IScriptToken token = new UniqueToken(parser.getLocation());
+
+ addToParent(parser, token);
+ parser.push(token);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/UniqueToken.java b/tapestry-framework/src/org/apache/tapestry/script/UniqueToken.java
new file mode 100644
index 0000000..8441991
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/UniqueToken.java
@@ -0,0 +1,53 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.script;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * Writes out its child tokens only the first time it executes
+ * (with a given tag). Uses
+ * {@link org.apache.tapestry.IRequestCycle#setAttribute(String, Object)}
+ * to identify whether a particular block has rendered yet.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+class UniqueToken extends AbstractToken
+{
+ public UniqueToken(ILocation location)
+ {
+ super(location);
+ }
+
+ public void write(StringBuffer buffer, ScriptSession session)
+ {
+ IRequestCycle cycle = session.getRequestCycle();
+
+ ILocation location = getLocation();
+ String tag = "<unique> " + location.toString();
+
+ if (cycle.getAttribute(tag) != null)
+ return;
+
+ cycle.setAttribute(tag, Boolean.TRUE);
+
+ writeChildren(buffer, session);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/script/package.html b/tapestry-framework/src/org/apache/tapestry/script/package.html
new file mode 100644
index 0000000..d27729c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/script/package.html
@@ -0,0 +1,19 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Parser and related classes for dynamically generating JavaScript for
+inclusion in an HTML response. This is used by a number of
+tapestry components, including
+{@link org.apache.tapestry.html.Rollover}, as well
+as {@link org.apache.tapestry.html.Script} (used for
+including arbitrary user-written JavaScript).
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ApplicationSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ApplicationSpecification.java
new file mode 100644
index 0000000..ce9687b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ApplicationSpecification.java
@@ -0,0 +1,62 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * Defines the configuration for a Tapestry application. An ApplicationSpecification
+ * extends {@link LibrarySpecification} by adding new properties
+ * name and engineClassName.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ApplicationSpecification
+ extends LibrarySpecification
+ implements IApplicationSpecification
+{
+ private String _name;
+ private String _engineClassName;
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public void setEngineClassName(String value)
+ {
+ _engineClassName = value;
+ }
+
+ public String getEngineClassName()
+ {
+ return _engineClassName;
+ }
+
+ public void setName(String name)
+ {
+ _name = name;
+ }
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ builder.append("name", _name);
+ builder.append("engineClassName", _engineClassName);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/AssetSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/AssetSpecification.java
new file mode 100644
index 0000000..a069c6f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/AssetSpecification.java
@@ -0,0 +1,60 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+/**
+ * Defines an internal, external or private asset.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class AssetSpecification extends LocatablePropertyHolder implements IAssetSpecification
+{
+ private AssetType type;
+ protected String path;
+
+ /**
+ * Returns the base path for the asset. This may be interpreted as a URL, relative URL
+ * or the path to a resource, depending on the type of asset.
+ *
+ **/
+
+ public String getPath()
+ {
+ return path;
+ }
+
+ public AssetType getType()
+ {
+ return type;
+ }
+
+ /** @since 3.0 **/
+
+ public void setPath(String path)
+ {
+ this.path = path;
+ }
+
+ /** @since 3.0 **/
+
+ public void setType(AssetType type)
+ {
+ this.type = type;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/AssetType.java b/tapestry-framework/src/org/apache/tapestry/spec/AssetType.java
new file mode 100644
index 0000000..1dbd468
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/AssetType.java
@@ -0,0 +1,58 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Defines the types of assets.
+ *
+ * @see IAssetSpecification
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public final class AssetType extends Enum
+{
+ /**
+ * An external resource.
+ *
+ **/
+
+ public static final AssetType EXTERNAL = new AssetType("EXTERNAL");
+
+ /**
+ * A resource visible to the {@link javax.servlet.ServletContext}.
+ *
+ **/
+
+ public static final AssetType CONTEXT = new AssetType("CONTEXT");
+
+ /**
+ * An internal resource visible only on the classpath. Typically,
+ * a resource package in a WAR or JAR file alongside the classes.
+ *
+ **/
+
+ public static final AssetType PRIVATE = new AssetType("PRIVATE");
+
+ private AssetType(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/BaseLocatable.java b/tapestry-framework/src/org/apache/tapestry/spec/BaseLocatable.java
new file mode 100644
index 0000000..e44e6f0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/BaseLocatable.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.ILocationHolder;
+
+/**
+ * Base class for classes which implement
+ * {@link org.apache.tapestry.ILocationHolder}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class BaseLocatable implements ILocationHolder
+{
+ private ILocation _location;
+
+ public void setLocation(ILocation location)
+ {
+ _location = location;
+ }
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/BeanLifecycle.java b/tapestry-framework/src/org/apache/tapestry/spec/BeanLifecycle.java
new file mode 100644
index 0000000..8b617c2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/BeanLifecycle.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.commons.lang.enum.Enum;
+
+
+/**
+ * An {@link Enum} of the different possible lifecycles for a JavaBean.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.4
+ *
+ **/
+
+public class BeanLifecycle extends Enum
+{
+ /**
+ * No lifecycle; the bean is created fresh on each reference and not retained.
+ *
+ **/
+
+ public static final BeanLifecycle NONE = new BeanLifecycle("NONE");
+
+ /**
+ * The standard lifecycle; the bean is retained for the
+ * duration of the request cycle and is discarded at the end of the
+ * request cycle.
+ *
+ **/
+
+ public static final BeanLifecycle REQUEST = new BeanLifecycle("REQUEST");
+
+ /**
+ * The bean is created once and reused for the lifespan of the page
+ * containing the component.
+ *
+ **/
+
+ public static final BeanLifecycle PAGE = new BeanLifecycle("PAGE");
+
+ /**
+ * The bean is create and reused until the end of the current render,
+ * at which point it is discarded.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final BeanLifecycle RENDER = new BeanLifecycle("RENDER");
+
+ private BeanLifecycle(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/BeanSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/BeanSpecification.java
new file mode 100644
index 0000000..6935e44
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/BeanSpecification.java
@@ -0,0 +1,127 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tapestry.bean.IBeanInitializer;
+
+/**
+ * A specification of a helper bean for a component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.4
+ *
+ **/
+
+public class BeanSpecification extends LocatablePropertyHolder implements IBeanSpecification
+{
+ protected String className;
+ protected BeanLifecycle lifecycle;
+
+ /** @since 1.0.9 **/
+ private String description;
+
+ /**
+ * A List of {@link IBeanInitializer}.
+ *
+ **/
+
+ protected List initializers;
+
+ public String getClassName()
+ {
+ return className;
+ }
+
+ public BeanLifecycle getLifecycle()
+ {
+ return lifecycle;
+ }
+
+ /**
+ * @since 1.0.5
+ *
+ **/
+
+ public void addInitializer(IBeanInitializer initializer)
+ {
+ if (initializers == null)
+ initializers = new ArrayList();
+
+ initializers.add(initializer);
+ }
+
+ /**
+ * Returns the {@link List} of {@link IBeanInitializer}s. The caller
+ * should not modify this value!. May return null if there
+ * are no initializers.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public List getInitializers()
+ {
+ return initializers;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("BeanSpecification[");
+
+ buffer.append(className);
+ buffer.append(", lifecycle ");
+ buffer.append(lifecycle.getName());
+
+ if (initializers != null && initializers.size() > 0)
+ {
+ buffer.append(", ");
+ buffer.append(initializers.size());
+ buffer.append(" initializers");
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public void setDescription(String desc)
+ {
+ description = desc;
+ }
+
+ /** @since 3.0 **/
+
+ public void setClassName(String className)
+ {
+ this.className = className;
+ }
+
+ /** @since 3.0 **/
+
+ public void setLifecycle(BeanLifecycle lifecycle)
+ {
+ this.lifecycle = lifecycle;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/BindingSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/BindingSpecification.java
new file mode 100644
index 0000000..01c40e0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/BindingSpecification.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+/**
+ * Stores a binding specification, which identifies the static value
+ * or OGNL expression for the binding. The name of the binding (which
+ * matches a bindable property of the contined component) is implicitly known.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class BindingSpecification extends BaseLocatable implements IBindingSpecification
+{
+ private BindingType _type;
+ private String _value;
+
+ public BindingType getType()
+ {
+ return _type;
+ }
+
+ public String getValue()
+ {
+ return _value;
+ }
+
+ public void setType(BindingType type)
+ {
+ _type = type;
+ }
+
+ public void setValue(String value)
+ {
+ _value = value;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/BindingType.java b/tapestry-framework/src/org/apache/tapestry/spec/BindingType.java
new file mode 100644
index 0000000..190ae4b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/BindingType.java
@@ -0,0 +1,94 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Defines the different types of bindings possible for a component.
+ * These are used in the {@link IBindingSpecification} and ultimately
+ * used to create an instance of {@link org.apache.tapestry.IBinding}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public final class BindingType extends Enum
+{
+ /**
+ * Indicates a {@link org.apache.tapestry.binding.StaticBinding}.
+ *
+ **/
+
+ public static final BindingType STATIC = new BindingType("STATIC");
+
+ /**
+ * Indicates a standard {@link org.apache.tapestry.binding.ExpressionBinding}.
+ *
+ **/
+
+ public static final BindingType DYNAMIC = new BindingType("DYNAMIC");
+
+ /**
+ * Indicates that an existing binding (from the container) will be
+ * re-used.
+ *
+ **/
+
+ public static final BindingType INHERITED = new BindingType("INHERITED");
+
+ /**
+ * Indicates a {@link org.apache.tapestry.binding.FieldBinding}.
+ *
+ * <p>
+ * Field bindings are only available in the 1.3 DTD. The 1.4 DTD
+ * does not support them (since OGNL expressions can do the same thing).
+ *
+ **/
+
+ public static final BindingType FIELD = new BindingType("FIELD");
+
+ /**
+ * Indicates a {@link org.apache.tapestry.binding.ListenerBinding}, a
+ * specialized kind of binding that encapsulates a component listener
+ * as a script. Uses a subclass of {@link BindingSpecification},
+ * {@link ListenerBindingSpecification}.
+ * {@link IListenerBindingSpecification}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final BindingType LISTENER = new BindingType("LISTENER");
+
+ /**
+ * A binding to one of a component's localized strings.
+ *
+ * @see org.apache.tapestry.IComponent#getString(String)
+ *
+ * @since 2.0.4
+ *
+ **/
+
+ public static final BindingType STRING = new BindingType("STRING");
+
+ private BindingType(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ComponentSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ComponentSpecification.java
new file mode 100644
index 0000000..40ee214
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ComponentSpecification.java
@@ -0,0 +1,603 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A specification for a component, as read from an XML specification file.
+ *
+ * <p>A specification consists of
+ * <ul>
+ * <li>An implementing class
+ * <li>An optional template
+ * <li>An optional description
+ * <li>A set of contained components
+ * <li>Bindings for the properties of each contained component
+ * <li>A set of named assets
+ * <li>Definitions for helper beans
+ * <li>Any reserved names (used for HTML attributes)
+ * </ul>
+ *
+ * <p>From this information, an actual component may be instantiated and
+ * initialized. Instantiating a component is usually a recursive process, since
+ * to initialize a container component, it is necessary to instantiate and initialize
+ * its contained components as well.
+ *
+ * @see org.apache.tapestry.IComponent
+ * @see IContainedComponent
+ * @see org.apache.tapestry.engine.IPageLoader
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ComponentSpecification extends LocatablePropertyHolder implements IComponentSpecification
+{
+ private String _componentClassName;
+
+ /** @since 1.0.9 **/
+
+ private String _description;
+
+ /**
+ * Keyed on component id, value is {@link IContainedComponent}.
+ *
+ **/
+
+ protected Map _components;
+
+ /**
+ * Keyed on asset name, value is {@link IAssetSpecification}.
+ *
+ **/
+
+ protected Map _assets;
+
+ /**
+ * Defines all formal parameters. Keyed on parameter name, value is
+ * {@link IParameterSpecification}.
+ *
+ **/
+
+ protected Map _parameters;
+
+ /**
+ * Defines all helper beans. Keyed on name, value is {@link IBeanSpecification}.
+ *
+ * @since 1.0.4
+ **/
+
+ protected Map _beans;
+
+ /**
+ * The names of all reserved informal parameter names (as lower-case). This
+ * allows the page loader to filter out any informal parameters during page load,
+ * rather than during render.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ protected Set _reservedParameterNames;
+
+ /**
+ * Is the component allowed to have a body (that is, wrap other elements?).
+ *
+ **/
+
+ private boolean _allowBody = true;
+
+ /**
+ * Is the component allow to have informal parameter specified.
+ *
+ **/
+
+ private boolean _allowInformalParameters = true;
+
+ /**
+ * The XML Public Id used when the page or component specification was read
+ * (if applicable).
+ *
+ * @since 2.2
+ *
+ **/
+
+ private String _publicId;
+
+ /**
+ * Indicates that the specification is for a page, not a component.
+ *
+ * @since 2.2
+ *
+ **/
+
+ private boolean _pageSpecification;
+
+ /**
+ * The location from which the specification was obtained.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private IResourceLocation _specificationLocation;
+
+ /**
+ * A Map of {@link IPropertySpecification} keyed on the name
+ * of the property.
+ *
+ * @since 3.0
+ *
+ **/
+
+ private Map _propertySpecifications;
+
+ /**
+ * @throws IllegalArgumentException if the name already exists.
+ *
+ **/
+
+ public void addAsset(String name, IAssetSpecification asset)
+ {
+ if (_assets == null)
+ _assets = new HashMap();
+
+ if (_assets.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("ComponentSpecification.duplicate-asset", this, name));
+
+ _assets.put(name, asset);
+ }
+
+ /**
+ * @throws IllegalArgumentException if the id is already defined.
+ *
+ **/
+
+ public void addComponent(String id, IContainedComponent component)
+ {
+ if (_components == null)
+ _components = new HashMap();
+
+ if (_components.containsKey(id))
+ throw new IllegalArgumentException(
+ Tapestry.format("ComponentSpecification.duplicate-component", this, id));
+
+ _components.put(id, component);
+ }
+
+ /**
+ * Adds the parameter. The name is added as a reserved name.
+ *
+ * @throws IllegalArgumentException if the name already exists.
+ **/
+
+ public void addParameter(String name, IParameterSpecification spec)
+ {
+ if (_parameters == null)
+ _parameters = new HashMap();
+
+ if (_parameters.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("ComponentSpecification.duplicate-parameter", this, name));
+
+ _parameters.put(name, spec);
+
+ addReservedParameterName(name);
+ }
+
+ /**
+ * Returns true if the component is allowed to wrap other elements (static HTML
+ * or other components). The default is true.
+ *
+ * @see #setAllowBody(boolean)
+ *
+ **/
+
+ public boolean getAllowBody()
+ {
+ return _allowBody;
+ }
+
+ /**
+ * Returns true if the component allows informal parameters (parameters
+ * not formally defined). Informal parameters are generally used to create
+ * additional HTML attributes for an HTML tag rendered by the
+ * component. This is often used to specify JavaScript event handlers or the class
+ * of the component (for Cascarding Style Sheets).
+ *
+ * <p>The default value is true.
+ *
+ * @see #setAllowInformalParameters(boolean)
+ **/
+
+ public boolean getAllowInformalParameters()
+ {
+ return _allowInformalParameters;
+ }
+
+ /**
+ * Returns the {@link IAssetSpecification} with the given name, or null
+ * if no such specification exists.
+ *
+ * @see #addAsset(String,IAssetSpecification)
+ **/
+
+ public IAssetSpecification getAsset(String name)
+ {
+
+ return (IAssetSpecification) get(_assets, name);
+ }
+
+ /**
+ * Returns a <code>List</code>
+ * of the String names of all assets, in alphabetical
+ * order
+ *
+ **/
+
+ public List getAssetNames()
+ {
+ return sortedKeys(_assets);
+ }
+
+ /**
+ * Returns the specification of a contained component with the given id, or
+ * null if no such contained component exists.
+ *
+ * @see #addComponent(String, IContainedComponent)
+ *
+ **/
+
+ public IContainedComponent getComponent(String id)
+ {
+ return (IContainedComponent) get(_components, id);
+ }
+
+ public String getComponentClassName()
+ {
+ return _componentClassName;
+ }
+
+ /**
+ * Returns an <code>List</code>
+ * of the String names of the {@link IContainedComponent}s
+ * for this component.
+ *
+ * @see #addComponent(String, IContainedComponent)
+ *
+ **/
+
+ public List getComponentIds()
+ {
+ return sortedKeys(_components);
+ }
+
+ /**
+ * Returns the specification of a parameter with the given name, or
+ * null if no such parameter exists.
+ *
+ * @see #addParameter(String, IParameterSpecification)
+ *
+ **/
+
+ public IParameterSpecification getParameter(String name)
+ {
+ return (IParameterSpecification) get(_parameters, name);
+ }
+
+ /**
+ * Returns a List of
+ * of String names of all parameters. This list
+ * is in alphabetical order.
+ *
+ * @see #addParameter(String, IParameterSpecification)
+ *
+ **/
+
+ public List getParameterNames()
+ {
+ return sortedKeys(_parameters);
+ }
+
+ public void setAllowBody(boolean value)
+ {
+ _allowBody = value;
+ }
+
+ public void setAllowInformalParameters(boolean value)
+ {
+ _allowInformalParameters = value;
+ }
+
+ public void setComponentClassName(String value)
+ {
+ _componentClassName = value;
+ }
+
+ /**
+ * @since 1.0.4
+ *
+ * @throws IllegalArgumentException if the bean already has a specification.
+ **/
+
+ public void addBeanSpecification(String name, IBeanSpecification specification)
+ {
+ if (_beans == null)
+ _beans = new HashMap();
+
+ else
+ if (_beans.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("ComponentSpecification.duplicate-bean", this, name));
+
+ _beans.put(name, specification);
+ }
+
+ /**
+ * Returns the {@link IBeanSpecification} for the given name, or null
+ * if not such specification exists.
+ *
+ * @since 1.0.4
+ *
+ **/
+
+ public IBeanSpecification getBeanSpecification(String name)
+ {
+ if (_beans == null)
+ return null;
+
+ return (IBeanSpecification) _beans.get(name);
+ }
+
+ /**
+ * Returns an unmodifiable collection of the names of all beans.
+ *
+ **/
+
+ public Collection getBeanNames()
+ {
+ if (_beans == null)
+ return Collections.EMPTY_LIST;
+
+ return Collections.unmodifiableCollection(_beans.keySet());
+ }
+
+ /**
+ * Adds the value as a reserved name. Reserved names are not allowed
+ * as the names of informal parameters. Since the comparison is
+ * caseless, the value is converted to lowercase before being
+ * stored.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public void addReservedParameterName(String value)
+ {
+ if (_reservedParameterNames == null)
+ _reservedParameterNames = new HashSet();
+
+ _reservedParameterNames.add(value.toLowerCase());
+ }
+
+ /**
+ * Returns true if the value specified is in the reserved name list.
+ * The comparison is caseless. All formal parameters are automatically
+ * in the reserved name list, as well as any additional
+ * reserved names specified in the component specification. The latter
+ * refer to HTML attributes generated directly by the component.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public boolean isReservedParameterName(String value)
+ {
+ if (_reservedParameterNames == null)
+ return false;
+
+ return _reservedParameterNames.contains(value.toLowerCase());
+ }
+
+ public String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("componentClassName", _componentClassName);
+ builder.append("pageSpecification", _pageSpecification);
+ builder.append("specificationLocation", _specificationLocation);
+ builder.append("allowBody", _allowBody);
+ builder.append("allowInformalParameter", _allowInformalParameters);
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns the documentation for this component.
+ *
+ * @since 1.0.9
+ **/
+
+ public String getDescription()
+ {
+ return _description;
+ }
+
+ /**
+ * Sets the documentation for this component.
+ *
+ * @since 1.0.9
+ **/
+
+ public void setDescription(String description)
+ {
+ _description = description;
+ }
+
+ /**
+ * Returns the XML Public Id for the specification file, or null
+ * if not applicable.
+ *
+ * <p>
+ * This method exists as a convienience for the Spindle plugin.
+ * A previous method used an arbitrary version string, the
+ * public id is more useful and less ambiguous.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public String getPublicId()
+ {
+ return _publicId;
+ }
+
+ /** @since 2.2 **/
+
+ public void setPublicId(String publicId)
+ {
+ _publicId = publicId;
+ }
+
+ /**
+ *
+ * Returns true if the specification is known to be a page
+ * specification and not a component specification. Earlier versions
+ * of the framework did not distinguish between the two, but starting
+ * in 2.2, there are seperate XML entities for pages and components.
+ * Pages omit several attributes and entities related
+ * to parameters, as parameters only make sense for components.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public boolean isPageSpecification()
+ {
+ return _pageSpecification;
+ }
+
+ /** @since 2.2 **/
+
+ public void setPageSpecification(boolean pageSpecification)
+ {
+ _pageSpecification = pageSpecification;
+ }
+
+ /** @since 2.2 **/
+
+ private List sortedKeys(Map input)
+ {
+ if (input == null)
+ return Collections.EMPTY_LIST;
+
+ List result = new ArrayList(input.keySet());
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+ /** @since 2.2 **/
+
+ private Object get(Map map, Object key)
+ {
+ if (map == null)
+ return null;
+
+ return map.get(key);
+ }
+
+ /** @since 3.0 **/
+
+ public IResourceLocation getSpecificationLocation()
+ {
+ return _specificationLocation;
+ }
+
+ /** @since 3.0 **/
+
+ public void setSpecificationLocation(IResourceLocation specificationLocation)
+ {
+ _specificationLocation = specificationLocation;
+ }
+
+ /**
+ * Adds a new property specification. The name of the property must
+ * not already be defined (and must not change after being added).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void addPropertySpecification(IPropertySpecification spec)
+ {
+ if (_propertySpecifications == null)
+ _propertySpecifications = new HashMap();
+
+ String name = spec.getName();
+
+ if (_propertySpecifications.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "ComponentSpecification.duplicate-property-specification",
+ this,
+ name));
+
+ _propertySpecifications.put(name, spec);
+ }
+
+ /**
+ * Returns a sorted, immutable list of the names of all
+ * {@link org.apache.tapestry.spec.IPropertySpecification}s.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public List getPropertySpecificationNames()
+ {
+ return sortedKeys(_propertySpecifications);
+ }
+
+ /**
+ * Returns the named {@link org.apache.tapestry.spec.IPropertySpecification},
+ * or null if no such specification exist.
+ *
+ * @since 3.0
+ * @see #addPropertySpecification(IPropertySpecification)
+ *
+ **/
+
+ public IPropertySpecification getPropertySpecification(String name)
+ {
+ return (IPropertySpecification) get(_propertySpecifications, name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ContainedComponent.java b/tapestry-framework/src/org/apache/tapestry/spec/ContainedComponent.java
new file mode 100644
index 0000000..89d6713
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ContainedComponent.java
@@ -0,0 +1,137 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Defines a contained component. This includes the information needed to
+ * get the contained component's specification, as well as any bindings
+ * for the component.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ContainedComponent extends LocatablePropertyHolder implements IContainedComponent
+{
+ private String type;
+
+ private String copyOf;
+
+ private boolean inheritInformalParameters;
+
+ protected Map bindings;
+
+ private static final int MAP_SIZE = 3;
+
+ /**
+ * Returns the named binding, or null if the binding does not
+ * exist.
+ *
+ **/
+
+ public IBindingSpecification getBinding(String name)
+ {
+ if (bindings == null)
+ return null;
+
+ return (IBindingSpecification) bindings.get(name);
+ }
+
+ /**
+ * Returns an umodifiable <code>Collection</code>
+ * of Strings, each the name of one binding
+ * for the component.
+ *
+ **/
+
+ public Collection getBindingNames()
+ {
+ if (bindings == null)
+ return Collections.EMPTY_LIST;
+
+ return Collections.unmodifiableCollection(bindings.keySet());
+ }
+
+ public String getType()
+ {
+ return type;
+ }
+
+ public void setBinding(String name, IBindingSpecification spec)
+ {
+ if (bindings == null)
+ bindings = new HashMap(MAP_SIZE);
+
+ bindings.put(name, spec);
+ }
+
+ public void setType(String value)
+ {
+ type = value;
+ }
+
+ /**
+ * Sets the String Id of the component being copied from.
+ * For use by IDE tools like Spindle.
+ *
+ * @since 1.0.9
+ **/
+
+ public void setCopyOf(String id)
+ {
+ copyOf = id;
+ }
+
+ /**
+ * Returns the id of the component being copied from.
+ * For use by IDE tools like Spindle.
+ *
+ * @since 1.0.9
+ **/
+
+ public String getCopyOf()
+ {
+ return copyOf;
+ }
+
+ /**
+ * Returns whether the contained component will inherit
+ * the informal parameters of its parent.
+ *
+ * @since 3.0
+ **/
+ public boolean getInheritInformalParameters()
+ {
+ return inheritInformalParameters;
+ }
+
+ /**
+ * Sets whether the contained component will inherit
+ * the informal parameters of its parent.
+ *
+ * @since 3.0
+ */
+ public void setInheritInformalParameters(boolean value)
+ {
+ inheritInformalParameters = value;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/Direction.java b/tapestry-framework/src/org/apache/tapestry/spec/Direction.java
new file mode 100644
index 0000000..aa92714
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/Direction.java
@@ -0,0 +1,121 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.commons.lang.enum.Enum;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Represents different types of parameters. Currently only
+ * in and custom are supported, but this will likely change
+ * when Tapestry supports out parameters is some form (that reflects
+ * form style processing).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.0.3
+ *
+ */
+
+public class Direction extends Enum
+{
+ /**
+ * The parameter value is input only; the component property value
+ * is unchanged or not relevant after the component renders.
+ * The property is set from the binding before the component renders,
+ * then reset to initial value after the component renders.
+ *
+ */
+
+ public static final Direction IN = new Direction("IN");
+
+
+ /**
+ * Encapsulates the semantics of a form component's value parameter.
+ *
+ * <p>The parameter is associated with a {@link org.apache.tapestry.form.IFormComponent}.
+ * The property value is set from the binding before the component renders (when renderring,
+ * but not when rewinding).
+ * The binding is updated from the property value
+ * after after the component renders when the
+ * <b>containing form</b> is <b>rewinding</b>, <i>and</i>
+ * the component is not {@link org.apache.tapestry.form.IFormComponent#isDisabled() disabled}.
+ *
+ * @since 2.2
+ *
+ */
+
+ public static final Direction FORM = new Direction("FORM", false);
+
+ /**
+ * Processing of the parameter is entirely the responsibility
+ * of the component, which must obtain an manipulate
+ * the {@link org.apache.tapestry.IBinding} (if any) for the parameter.
+ *
+ **/
+
+ public static final Direction CUSTOM = new Direction("CUSTOM");
+
+
+ /**
+ * Causes a synthetic property to be created that automatically
+ * references and de-references the underlying binding.
+ *
+ * @since 3.0
+ *
+ */
+
+ public static final Direction AUTO = new Direction("AUTO");
+
+ /**
+ * If true, then this direction is allowed with invariant bindings (the usual case).
+ * If false, then {@link org.apache.tapestry.param.ParameterManager} will not allow
+ * an invariant binding.
+ *
+ * @since 3.0
+ */
+
+ private boolean _allowInvariant;
+
+ protected Direction(String name)
+ {
+ this(name, true);
+ }
+
+ protected Direction(String name, boolean allowInvariant)
+ {
+ super(name);
+
+ _allowInvariant = allowInvariant;
+ }
+
+ /**
+ * @since 3.0
+ */
+ public boolean getAllowInvariant()
+ {
+ return _allowInvariant;
+ }
+
+ /**
+ * Returns a user-presentable name for the direction.
+ *
+ */
+
+ public String getDisplayName()
+ {
+ return Tapestry.getMessage("Direction." + getName());
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ExtensionSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ExtensionSpecification.java
new file mode 100644
index 0000000..4fe8e7e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ExtensionSpecification.java
@@ -0,0 +1,182 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.prop.OgnlUtils;
+
+/**
+ * Defines an "extension", which is much like a helper bean, but
+ * is part of a library or application specification (and has the same
+ * lifecycle as the application).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class ExtensionSpecification
+ extends LocatablePropertyHolder
+ implements IExtensionSpecification
+{
+ private static final Log LOG = LogFactory.getLog(ExtensionSpecification.class);
+
+ private String _className;
+ protected Map _configuration = new HashMap();
+ private boolean _immediate;
+
+ public String getClassName()
+ {
+ return _className;
+ }
+
+ public void setClassName(String className)
+ {
+ _className = className;
+ }
+
+ public void addConfiguration(String propertyName, Object value)
+ {
+ if (_configuration.containsKey(propertyName))
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "ExtensionSpecification.duplicate-property",
+ this,
+ propertyName));
+
+ _configuration.put(propertyName, value);
+ }
+
+ /**
+ * Returns an immutable Map of the configuration; keyed on property name,
+ * with values as properties to assign.
+ *
+ **/
+
+ public Map getConfiguration()
+ {
+ return Collections.unmodifiableMap(_configuration);
+ }
+
+ /**
+ * Invoked to instantiate an instance of the extension and return it.
+ * It also configures properties of the extension.
+ *
+ **/
+
+ public Object instantiateExtension(IResourceResolver resolver)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Instantiating extension class " + _className + ".");
+ Class extensionClass = null;
+ Object result = null;
+
+ try
+ {
+ extensionClass = resolver.findClass(_className);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ExtensionSpecification.bad-class", _className),
+ getLocation(),
+ ex);
+ }
+
+ result = instantiateInstance(extensionClass, result);
+
+ initializeProperties(resolver, result);
+
+ return result;
+ }
+
+ private void initializeProperties(IResourceResolver resolver, Object extension)
+ {
+ if (_configuration.isEmpty())
+ return;
+
+ Iterator i = _configuration.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ String propertyName = (String) entry.getKey();
+
+ OgnlUtils.set(propertyName, resolver, extension, entry.getValue());
+ }
+ }
+
+ private Object instantiateInstance(Class extensionClass, Object result)
+ {
+ try
+ {
+ result = extensionClass.newInstance();
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(ex.getMessage(), getLocation(), ex);
+ }
+
+ return result;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("ExtensionSpecification@");
+ buffer.append(Integer.toHexString(hashCode()));
+ buffer.append('[');
+ buffer.append(_className);
+
+ if (_configuration != null)
+ {
+ buffer.append(' ');
+ buffer.append(_configuration);
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns true if the extensions should be instantiated
+ * immediately after the containing
+ * {@link org.apache.tapestry.spec.LibrarySpecification}
+ * if parsed. Non-immediate extensions are instantiated
+ * only as needed.
+ *
+ **/
+
+ public boolean isImmediate()
+ {
+ return _immediate;
+ }
+
+ public void setImmediate(boolean immediate)
+ {
+ _immediate = immediate;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IApplicationSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IApplicationSpecification.java
new file mode 100644
index 0000000..96c679e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IApplicationSpecification.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+
+
+/**
+ * Defines and interface for the configuration for a Tapestry application. An ApplicationSpecification
+ * extends {@link ILibrarySpecification} by adding new properties
+ * name and engineClassName.
+ *
+ * @author Geoffrey Longman
+ * @version $Id$
+ *
+ **/
+
+public interface IApplicationSpecification extends ILibrarySpecification
+{
+ /**
+ * Returns a "user friendly" name for the application (which is optional).
+ *
+ **/
+
+ public String getName();
+
+ public void setEngineClassName(String value);
+
+ /**
+ * Returns the name of the class (which implements {@link org.apache.tapestry.IEngine}).
+ * May return null, in which case a default is used.
+ *
+ **/
+
+ public String getEngineClassName();
+
+ public void setName(String name);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IAssetSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IAssetSpecification.java
new file mode 100644
index 0000000..05d44b8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IAssetSpecification.java
@@ -0,0 +1,40 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * Defines an internal, external or private asset.
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IAssetSpecification extends IPropertyHolder, ILocationHolder, ILocatable
+{
+ /**
+ * Returns the base path for the asset. This may be interpreted as a URL, relative URL
+ * or the path to a resource, depending on the type of asset.
+ *
+ **/
+ public abstract String getPath();
+ public abstract AssetType getType();
+ /** @since 3.0 **/
+ public abstract void setPath(String path);
+ /** @since 3.0 **/
+ public abstract void setType(AssetType type);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IBeanSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IBeanSpecification.java
new file mode 100644
index 0000000..1127b6f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IBeanSpecification.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.List;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.bean.IBeanInitializer;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * A specification of a helper bean for a component.
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IBeanSpecification extends IPropertyHolder, ILocationHolder, ILocatable
+{
+ public abstract String getClassName();
+ public abstract BeanLifecycle getLifecycle();
+ /**
+ * @since 1.0.5
+ *
+ **/
+ public abstract void addInitializer(IBeanInitializer initializer);
+ /**
+ * Returns the {@link List} of {@link IBeanInitializer}s. The caller
+ * should not modify this value!. May return null if there
+ * are no initializers.
+ *
+ * @since 1.0.5
+ *
+ **/
+ public abstract List getInitializers();
+ public abstract String toString();
+ public abstract String getDescription();
+ public abstract void setDescription(String desc);
+ /** @since 3.0 **/
+ public abstract void setClassName(String className);
+ /** @since 3.0 **/
+ public abstract void setLifecycle(BeanLifecycle lifecycle);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IBindingSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IBindingSpecification.java
new file mode 100644
index 0000000..07e427a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IBindingSpecification.java
@@ -0,0 +1,34 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+
+/**
+ * Stores a binding specification, which identifies the static value
+ * or OGNL expression for the binding. The name of the binding (which
+ * matches a bindable property of the contined component) is implicitly known.
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IBindingSpecification extends ILocationHolder, ILocatable
+{
+ public abstract BindingType getType();
+ public abstract String getValue();
+ public abstract void setType(BindingType type);
+ public abstract void setValue(String value);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IComponentSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IComponentSpecification.java
new file mode 100644
index 0000000..28fdf07
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IComponentSpecification.java
@@ -0,0 +1,254 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * A specification for a component, as read from an XML specification file.
+ *
+ * <p>A specification consists of
+ * <ul>
+ * <li>An implementing class
+ * <li>An optional template
+ * <li>An optional description
+ * <li>A set of contained components
+ * <li>Bindings for the properties of each contained component
+ * <li>A set of named assets
+ * <li>Definitions for helper beans
+ * <li>Any reserved names (used for HTML attributes)
+ * </ul>
+ *
+ * <p>From this information, an actual component may be instantiated and
+ * initialized. Instantiating a component is usually a recursive process, since
+ * to initialize a container component, it is necessary to instantiate and initialize
+ * its contained components as well.
+ *
+ * @see org.apache.tapestry.IComponent
+ * @see IContainedComponent
+ * @see IComponentSpecification
+ * @see org.apache.tapestry.engine.IPageLoader
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IComponentSpecification extends IPropertyHolder, ILocationHolder, ILocatable
+{
+ /**
+ * @throws IllegalArgumentException if the name already exists.
+ *
+ **/
+ public abstract void addAsset(String name, IAssetSpecification asset);
+ /**
+ * @throws IllegalArgumentException if the id is already defined.
+ *
+ **/
+ public abstract void addComponent(String id, IContainedComponent component);
+ /**
+ * Adds the parameter. The name is added as a reserved name.
+ *
+ * @throws IllegalArgumentException if the name already exists.
+ **/
+ public abstract void addParameter(String name, IParameterSpecification spec);
+ /**
+ * Returns true if the component is allowed to wrap other elements (static HTML
+ * or other components). The default is true.
+ *
+ * @see #setAllowBody(boolean)
+ *
+ **/
+ public abstract boolean getAllowBody();
+ /**
+ * Returns true if the component allows informal parameters (parameters
+ * not formally defined). Informal parameters are generally used to create
+ * additional HTML attributes for an HTML tag rendered by the
+ * component. This is often used to specify JavaScript event handlers or the class
+ * of the component (for Cascarding Style Sheets).
+ *
+ * <p>The default value is true.
+ *
+ * @see #setAllowInformalParameters(boolean)
+ **/
+ public abstract boolean getAllowInformalParameters();
+ /**
+ * Returns the {@link IAssetSpecification} with the given name, or null
+ * if no such specification exists.
+ *
+ * @see #addAsset(String,IAssetSpecification)
+ **/
+ public abstract IAssetSpecification getAsset(String name);
+ /**
+ * Returns a <code>List</code>
+ * of the String names of all assets, in alphabetical
+ * order
+ *
+ **/
+ public abstract List getAssetNames();
+ /**
+ * Returns the specification of a contained component with the given id, or
+ * null if no such contained component exists.
+ *
+ * @see #addComponent(String, IContainedComponent)
+ *
+ **/
+ public abstract IContainedComponent getComponent(String id);
+ public abstract String getComponentClassName();
+ /**
+ * Returns an <code>List</code>
+ * of the String names of the {@link IContainedComponent}s
+ * for this component.
+ *
+ * @see #addComponent(String, IContainedComponent)
+ *
+ **/
+ public abstract List getComponentIds();
+ /**
+ * Returns the specification of a parameter with the given name, or
+ * null if no such parameter exists.
+ *
+ * @see #addParameter(String, IParameterSpecification)
+ *
+ **/
+ public abstract IParameterSpecification getParameter(String name);
+ /**
+ * Returns a List of
+ * of String names of all parameters. This list
+ * is in alphabetical order.
+ *
+ * @see #addParameter(String, IParameterSpecification)
+ *
+ **/
+ public abstract List getParameterNames();
+ public abstract void setAllowBody(boolean value);
+ public abstract void setAllowInformalParameters(boolean value);
+ public abstract void setComponentClassName(String value);
+ /**
+ * @since 1.0.4
+ *
+ * @throws IllegalArgumentException if the bean already has a specification.
+ **/
+ public abstract void addBeanSpecification(String name, IBeanSpecification specification);
+ /**
+ * Returns the {@link IBeanSpecification} for the given name, or null
+ * if not such specification exists.
+ *
+ * @since 1.0.4
+ *
+ **/
+ public abstract IBeanSpecification getBeanSpecification(String name);
+ /**
+ * Returns an unmodifiable collection of the names of all beans.
+ *
+ **/
+ public abstract Collection getBeanNames();
+ /**
+ * Adds the value as a reserved name. Reserved names are not allowed
+ * as the names of informal parameters. Since the comparison is
+ * caseless, the value is converted to lowercase before being
+ * stored.
+ *
+ * @since 1.0.5
+ *
+ **/
+ public abstract void addReservedParameterName(String value);
+ /**
+ * Returns true if the value specified is in the reserved name list.
+ * The comparison is caseless. All formal parameters are automatically
+ * in the reserved name list, as well as any additional
+ * reserved names specified in the component specification. The latter
+ * refer to HTML attributes generated directly by the component.
+ *
+ * @since 1.0.5
+ *
+ **/
+ public abstract boolean isReservedParameterName(String value);
+ /**
+ * Returns the documentation for this component.
+ *
+ * @since 1.0.9
+ **/
+ public abstract String getDescription();
+ /**
+ * Sets the documentation for this component.
+ *
+ * @since 1.0.9
+ **/
+ public abstract void setDescription(String description);
+ /**
+ * Returns the XML Public Id for the specification file, or null
+ * if not applicable.
+ *
+ * <p>
+ * This method exists as a convienience for the Spindle plugin.
+ * A previous method used an arbitrary version string, the
+ * public id is more useful and less ambiguous.
+ *
+ * @since 2.2
+ *
+ **/
+ public abstract String getPublicId();
+ /** @since 2.2 **/
+ public abstract void setPublicId(String publicId);
+ /**
+ *
+ * Returns true if the specification is known to be a page
+ * specification and not a component specification. Earlier versions
+ * of the framework did not distinguish between the two, but starting
+ * in 2.2, there are seperate XML entities for pages and components.
+ * Pages omit several attributes and entities related
+ * to parameters, as parameters only make sense for components.
+ *
+ * @since 2.2
+ *
+ **/
+ public abstract boolean isPageSpecification();
+ /** @since 2.2 **/
+ public abstract void setPageSpecification(boolean pageSpecification);
+ /** @since 3.0 **/
+ public abstract IResourceLocation getSpecificationLocation();
+ /** @since 3.0 **/
+ public abstract void setSpecificationLocation(IResourceLocation specificationLocation);
+ /**
+ * Adds a new property specification. The name of the property must
+ * not already be defined (and must not change after being added).
+ *
+ * @since 3.0
+ *
+ **/
+ public abstract void addPropertySpecification(IPropertySpecification spec);
+ /**
+ * Returns a sorted, immutable list of the names of all
+ * {@link org.apache.tapestry.spec.IPropertySpecification}s.
+ *
+ * @since 3.0
+ *
+ **/
+ public abstract List getPropertySpecificationNames();
+ /**
+ * Returns the named {@link org.apache.tapestry.spec.IPropertySpecification},
+ * or null if no such specification exist.
+ *
+ * @since 3.0
+ * @see #addPropertySpecification(IPropertySpecification)
+ *
+ **/
+ public abstract IPropertySpecification getPropertySpecification(String name);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IContainedComponent.java b/tapestry-framework/src/org/apache/tapestry/spec/IContainedComponent.java
new file mode 100644
index 0000000..8a30f81
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IContainedComponent.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.Collection;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * Defines a contained component. This includes the information needed to
+ * get the contained component's specification, as well as any bindings
+ * for the component.
+
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IContainedComponent extends IPropertyHolder, ILocationHolder, ILocatable
+{
+ /**
+ * Returns the named binding, or null if the binding does not
+ * exist.
+ *
+ **/
+ public abstract IBindingSpecification getBinding(String name);
+ /**
+ * Returns an umodifiable <code>Collection</code>
+ * of Strings, each the name of one binding
+ * for the component.
+ *
+ **/
+ public abstract Collection getBindingNames();
+ public abstract String getType();
+ public abstract void setBinding(String name, IBindingSpecification spec);
+ public abstract void setType(String value);
+ /**
+ * Sets the String Id of the component being copied from.
+ * For use by IDE tools like Spindle.
+ *
+ * @since 1.0.9
+ **/
+ public abstract void setCopyOf(String id);
+ /**
+ * Returns the id of the component being copied from.
+ * For use by IDE tools like Spindle.
+ *
+ * @since 1.0.9
+ **/
+ public abstract String getCopyOf();
+
+ /**
+ * Returns whether the contained component will inherit
+ * the informal parameters of its parent.
+ *
+ * @since 3.0
+ **/
+ public abstract boolean getInheritInformalParameters();
+
+ /**
+ * Sets whether the contained component will inherit
+ * the informal parameters of its parent.
+ *
+ * @since 3.0
+ */
+ public abstract void setInheritInformalParameters(boolean value);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IExtensionSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IExtensionSpecification.java
new file mode 100644
index 0000000..5326f39
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IExtensionSpecification.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.Map;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * Defines an "extension", which is much like a helper bean, but
+ * is part of a library or application specification (and has the same
+ * lifecycle as the application).
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IExtensionSpecification extends IPropertyHolder, ILocationHolder, ILocatable
+{
+ public abstract String getClassName();
+ public abstract void setClassName(String className);
+ public abstract void addConfiguration(String propertyName, Object value);
+ /**
+ * Returns an immutable Map of the configuration; keyed on property name,
+ * with values as properties to assign.
+ *
+ **/
+ public abstract Map getConfiguration();
+ /**
+ * Invoked to instantiate an instance of the extension and return it.
+ * It also configures properties of the extension.
+ *
+ **/
+ public abstract Object instantiateExtension(IResourceResolver resolver);
+ /**
+ * Returns true if the extensions should be instantiated
+ * immediately after the containing
+ * {@link org.apache.tapestry.spec.LibrarySpecification}
+ * if parsed. Non-immediate extensions are instantiated
+ * only as needed.
+ *
+ **/
+ public abstract boolean isImmediate();
+ public abstract void setImmediate(boolean immediate);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ILibrarySpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ILibrarySpecification.java
new file mode 100644
index 0000000..f92a3d1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ILibrarySpecification.java
@@ -0,0 +1,215 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.util.IPropertyHolder;
+
+/**
+ * Interface for the Specification for a library. {@link org.apache.tapestry.spec.ApplicationSpecification}
+ * is a specialized kind of library.
+ *
+ * @author Geoffrey Longman
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public interface ILibrarySpecification extends IPropertyHolder, ILocationHolder
+{
+
+ /**
+ * Returns the specification path (within the classpath) for
+ * an embedded library, or null if
+ * no such library has been defined.
+ *
+ **/
+
+ public String getLibrarySpecificationPath(String id);
+
+ /**
+ * Sets the specification path for an embedded library.
+ *
+ * @throws IllegalArgumentException if a library with the given
+ * id already exists
+ *
+ **/
+
+ public void setLibrarySpecificationPath(String id, String path);
+
+ /**
+ * Returns a sorted list of library ids (or the empty list, but not null).
+ *
+ **/
+
+ public List getLibraryIds();
+
+ public String getPageSpecificationPath(String name);
+
+ public void setPageSpecificationPath(String name, String path);
+
+ /**
+ * Returns a sorted list of page names explicitly defined by this library,
+ * or an empty list (but not null).
+ *
+ **/
+
+ public List getPageNames();
+
+ public void setComponentSpecificationPath(String type, String path);
+
+ public String getComponentSpecificationPath(String type);
+
+ /**
+ * Returns the simple types of all components defined in
+ * this library. Returns a list of strings in sorted order,
+ * or an empty list (but not null).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public List getComponentTypes();
+
+ public String getServiceClassName(String name);
+
+ /**
+ * Returns a sorted list of service names (or an empty list, but
+ * not null).
+ *
+ **/
+
+ public List getServiceNames();
+
+ public void setServiceClassName(String name, String className);
+
+
+ /**
+ *
+ * Returns the documentation for this library..
+ *
+ *
+ **/
+
+ public String getDescription();
+
+ /**
+ *
+ * Sets the documentation for this library.
+ *
+ *
+ **/
+
+ public void setDescription(String description);
+
+ /**
+ * Returns a Map of extensions; key is extension name, value is
+ * {@link org.apache.tapestry.spec.IExtensionSpecification}.
+ * May return null. The returned Map is immutable.
+ *
+ **/
+
+ public Map getExtensionSpecifications();
+
+ /**
+ * Adds another extension specification.
+ *
+ **/
+
+ public void addExtensionSpecification(String name, IExtensionSpecification extension);
+
+ /**
+ * Returns a sorted List of the names of all extensions. May return the empty list,
+ * but won't return null.
+ *
+ **/
+
+ public List getExtensionNames();
+
+ /**
+ * Returns the named IExtensionSpecification, or null if it doesn't exist.
+ *
+ **/
+
+ public IExtensionSpecification getExtensionSpecification(String name);
+
+
+ /**
+ * Returns an instantiated extension. Extensions are created as needed and
+ * cached for later use.
+ *
+ * @throws IllegalArgumentException if no extension specification exists for the
+ * given name.
+ *
+ **/
+
+ public Object getExtension(String name);
+
+ /**
+ * Returns an instantiated extension, performing a check to ensure
+ * that the extension is a subtype of the given class (or extends the given
+ * interface).
+ *
+ * @throws IllegalArgumentException if no extension specification exists for
+ * the given name, or if the extension fails the type check.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public Object getExtension(String name, Class typeConstraint);
+
+ /**
+ * Returns true if the named extension exists (or can be instantiated),
+ * returns false if the named extension has no specification.
+ *
+ **/
+
+ public boolean checkExtension(String name);
+
+ /**
+ * Invoked after the entire specification has been constructed
+ * to instantiate any extensions marked immediate.
+ *
+ **/
+
+ public void instantiateImmediateExtensions();
+
+ public IResourceResolver getResourceResolver();
+
+ public void setResourceResolver(IResourceResolver resolver);
+
+ public String getPublicId();
+
+ public void setPublicId(String value);
+
+ /**
+ * Returns the location from which the specification was read.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IResourceLocation getSpecificationLocation();
+
+ /** @since 3.0 **/
+
+ public void setSpecificationLocation(IResourceLocation specificationLocation);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IListenerBindingSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IListenerBindingSpecification.java
new file mode 100644
index 0000000..ecd70f4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IListenerBindingSpecification.java
@@ -0,0 +1,35 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+/**
+ * Special interface of {@link org.apache.tapestry.spec.IBindingSpecification} used
+ * to encapsulate additional information the additional information
+ * specific to listener bindings. In an IListenerBindingSpecification, the
+ * value property is the actual script (and is aliased as property script),
+ * but an additional property,
+ * language, (which may be null) is needed. This is the language
+ * the script is written in. *
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ * @since 3.0
+ */
+public interface IListenerBindingSpecification extends IBindingSpecification
+{
+ public abstract String getLanguage();
+ public abstract String getScript();
+ public abstract void setLanguage(String language);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IParameterSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IParameterSpecification.java
new file mode 100644
index 0000000..6f7723b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IParameterSpecification.java
@@ -0,0 +1,93 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocationHolder;
+
+/**
+ * Defines a formal parameter to a component. An <code>IParameterSpecification</code>
+ * is contained by a {@link IComponentSpecification}.
+ *
+ * <p>TBD: Identify arrays in some way.
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IParameterSpecification extends ILocationHolder
+{
+ /**
+ * Returns the class name of the expected type of the parameter. The default value
+ * is <code>java.lang.Object</code> which matches anything.
+ *
+ **/
+ public abstract String getType();
+ /**
+ * Returns true if the parameter is required by the component.
+ * The default is false, meaning the parameter is optional.
+ *
+ **/
+ public abstract boolean isRequired();
+ public abstract void setRequired(boolean value);
+ /**
+ * Sets the type of value expected for the parameter. This can be
+ * left blank to indicate any type.
+ *
+ **/
+ public abstract void setType(String value);
+ /**
+ * Returns the documentation for this parameter.
+ *
+ * @since 1.0.9
+ *
+ **/
+ public abstract String getDescription();
+ /**
+ * Sets the documentation for this parameter.
+ *
+ * @since 1.0.9
+ *
+ **/
+ public abstract void setDescription(String description);
+ /**
+ * Sets the property name (of the component class)
+ * to connect the parameter to.
+ *
+ **/
+ public abstract void setPropertyName(String propertyName);
+ /**
+ * Returns the name of the JavaBeans property to connect the
+ * parameter to.
+ *
+ **/
+ public abstract String getPropertyName();
+ /**
+ * Returns the parameter value direction, defaulting to {@link Direction#CUSTOM}
+ * if not otherwise specified.
+ *
+ **/
+ public abstract Direction getDirection();
+ public abstract void setDirection(Direction direction);
+ /**
+ * Returns the default value of the JavaBeans property if no binding is provided
+ * or null if it has not been specified
+ **/
+ public abstract String getDefaultValue();
+ /**
+ * Sets the default value of the JavaBeans property if no binding is provided
+ *
+ **/
+ public abstract void setDefaultValue(String defaultValue);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/IPropertySpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/IPropertySpecification.java
new file mode 100644
index 0000000..cec6eaa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/IPropertySpecification.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocationHolder;
+
+/**
+ * Defines a transient or persistant property of a component or page.
+ * A {@link org.apache.tapestry.engine.IComponentClassEnhancer} uses this information
+ * to create a subclass with the necessary instance variables and methods.
+ *
+ * @author glongman@intelligentworks.com
+ * @version $Id$
+ */
+public interface IPropertySpecification extends ILocationHolder
+{
+ public abstract String getInitialValue();
+ public abstract String getName();
+ public abstract boolean isPersistent();
+ public abstract String getType();
+ public abstract void setInitialValue(String initialValue);
+ /**
+ * Sets the name of the property. This should not be changed
+ * once this IPropertySpecification is added to
+ * a {@link org.apache.tapestry.spec.IComponentSpecification}.
+ *
+ **/
+ public abstract void setName(String name);
+ public abstract void setPersistent(boolean persistant);
+ public abstract void setType(String type);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/LibrarySpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/LibrarySpecification.java
new file mode 100644
index 0000000..0e60a4a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/LibrarySpecification.java
@@ -0,0 +1,637 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Specification for a library. {@link org.apache.tapestry.spec.ApplicationSpecification}
+ * is a specialized kind of library.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class LibrarySpecification extends LocatablePropertyHolder implements ILibrarySpecification
+{
+ /**
+ * Resource resolver (used to instantiate extensions).
+ *
+ **/
+
+ private IResourceResolver _resolver;
+
+ /**
+ * Map of page name to page specification path.
+ *
+ **/
+
+ private Map _pages;
+
+ /**
+ * Map of component alias to component specification path.
+ *
+ **/
+ private Map _components;
+
+ /**
+ * Map of service name to service class name.
+ *
+ **/
+
+ private Map _services;
+
+ /**
+ * Map of library id to library specification path.
+ *
+ **/
+
+ private Map _libraries;
+
+ private String _description;
+
+ /**
+ * Map of extension name to {@link IExtensionSpecification}.
+ *
+ **/
+
+ private Map _extensions;
+
+ /**
+ * Map of extension name to Object for instantiated extensions.
+ *
+ **/
+
+ private Map _instantiatedExtensions;
+
+ /**
+ * The XML Public Id used when the library specification was read
+ * (if applicable).
+ *
+ * @since 2.2
+ *
+ **/
+
+ private String _publicId;
+
+ /**
+ * The location of the specification.
+ *
+ **/
+
+ private IResourceLocation _specificationLocation;
+
+ public String getLibrarySpecificationPath(String id)
+ {
+ return (String) get(_libraries, id);
+ }
+
+ /**
+ * Sets the specification path for an embedded library.
+ *
+ * @throws IllegalArgumentException if a library with the given
+ * id already exists
+ *
+ **/
+
+ public void setLibrarySpecificationPath(String id, String path)
+ {
+ if (_libraries == null)
+ _libraries = new HashMap();
+
+ if (_libraries.containsKey(id))
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.duplicate-child-namespace-id", id));
+
+ _libraries.put(id, path);
+ }
+
+ public List getLibraryIds()
+ {
+ return sortedKeys(_libraries);
+ }
+
+ public String getPageSpecificationPath(String name)
+ {
+ return (String) get(_pages, name);
+ }
+
+ public void setPageSpecificationPath(String name, String path)
+ {
+ if (_pages == null)
+ _pages = new HashMap();
+
+ if (_pages.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.duplicate-page-name", name));
+
+ _pages.put(name, path);
+ }
+
+ public List getPageNames()
+ {
+ return sortedKeys(_pages);
+ }
+
+ public void setComponentSpecificationPath(String alias, String path)
+ {
+ if (_components == null)
+ _components = new HashMap();
+
+ if (_components.containsKey(alias))
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.duplicate-component-alias", alias));
+
+ _components.put(alias, path);
+ }
+
+ public String getComponentSpecificationPath(String alias)
+ {
+ return (String) get(_components, alias);
+ }
+
+ /**
+ * @since 3.0
+ *
+ **/
+
+ public List getComponentTypes()
+ {
+ return sortedKeys(_components);
+ }
+
+ public String getServiceClassName(String name)
+ {
+ return (String) get(_services, name);
+ }
+
+ public List getServiceNames()
+ {
+ return sortedKeys(_services);
+ }
+
+ public void setServiceClassName(String name, String className)
+ {
+ if (_services == null)
+ _services = new HashMap();
+
+ if (_services.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.duplicate-service-name", name));
+
+ _services.put(name, className);
+ }
+
+ private List sortedKeys(Map map)
+ {
+ if (map == null)
+ return Collections.EMPTY_LIST;
+
+ List result = new ArrayList(map.keySet());
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+ private Object get(Map map, Object key)
+ {
+ if (map == null)
+ return null;
+
+ return map.get(key);
+ }
+
+ /**
+ *
+ * Returns the documentation for this library..
+ *
+ *
+ **/
+
+ public String getDescription()
+ {
+ return _description;
+ }
+
+ /**
+ *
+ * Sets the documentation for this library.
+ *
+ *
+ **/
+
+ public void setDescription(String description)
+ {
+ _description = description;
+ }
+
+ /**
+ * Returns a Map of extensions; key is extension name, value is
+ * {@link org.apache.tapestry.spec.IExtensionSpecification}.
+ * May return null. The returned Map is immutable.
+ *
+ **/
+
+ public Map getExtensionSpecifications()
+ {
+ if (_extensions == null)
+ return null;
+
+ return Collections.unmodifiableMap(_extensions);
+ }
+
+ /**
+ * Adds another extension specification.
+ *
+ * @throws IllegalArgumentException if an extension with the given name already exists.
+ *
+ **/
+
+ public void addExtensionSpecification(String name, IExtensionSpecification extension)
+ {
+ if (_extensions == null)
+ _extensions = new HashMap();
+
+ if (_extensions.containsKey(name))
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.duplicate-extension-name", this, name));
+
+ _extensions.put(name, extension);
+ }
+
+ /**
+ * Returns a sorted List of the names of all extensions. May return the empty list,
+ * but won't return null.
+ *
+ **/
+
+ public synchronized List getExtensionNames()
+ {
+ return sortedKeys(_instantiatedExtensions);
+ }
+
+ /**
+ * Returns the named IExtensionSpecification, or null if it doesn't exist.
+ *
+ **/
+
+ public IExtensionSpecification getExtensionSpecification(String name)
+ {
+ if (_extensions == null)
+ return null;
+
+ return (IExtensionSpecification) _extensions.get(name);
+ }
+
+ /**
+ * Returns true if this library specification has a specification
+ * for the named extension.
+ *
+ **/
+
+ public boolean checkExtension(String name)
+ {
+ if (_extensions == null)
+ return false;
+
+ return _extensions.containsKey(name);
+ }
+
+ /**
+ * Returns an instantiated extension. Extensions are created as needed and
+ * cached for later use.
+ *
+ * @throws IllegalArgumentException if no extension specification exists for the
+ * given name.
+ *
+ **/
+
+ public synchronized Object getExtension(String name)
+ {
+ return getExtension(name, null);
+ }
+
+ /** @since 3.0 **/
+
+ public synchronized Object getExtension(String name, Class typeConstraint)
+ {
+ if (_instantiatedExtensions == null)
+ _instantiatedExtensions = new HashMap();
+
+ Object result = _instantiatedExtensions.get(name);
+ IExtensionSpecification spec = getExtensionSpecification(name);
+
+ if (spec == null)
+ throw new IllegalArgumentException(
+ Tapestry.format("LibrarySpecification.no-such-extension", name));
+
+ if (result == null)
+ {
+
+ result = spec.instantiateExtension(_resolver);
+
+ _instantiatedExtensions.put(name, result);
+ }
+
+ if (typeConstraint != null)
+ applyTypeConstraint(name, result, typeConstraint, spec.getLocation());
+
+ return result;
+ }
+
+ /**
+ * Checks that an extension conforms to the supplied type constraint.
+ *
+ * @throws IllegalArgumentException if the extension fails the check.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void applyTypeConstraint(
+ String name,
+ Object extension,
+ Class typeConstraint,
+ ILocation location)
+ {
+ Class extensionClass = extension.getClass();
+
+ // Can you assign an instance of the extension to a variable
+ // of type typeContraint legally?
+
+ if (typeConstraint.isAssignableFrom(extensionClass))
+ return;
+
+ String key =
+ typeConstraint.isInterface()
+ ? "LibrarySpecification.extension-does-not-implement-interface"
+ : "LibrarySpecification.extension-not-a-subclass";
+
+ throw new ApplicationRuntimeException(
+ Tapestry.format(key, name, extensionClass.getName(), typeConstraint.getName()),
+ location,
+ null);
+ }
+
+ /**
+ * Invoked after the entire specification has been constructed
+ * to instantiate any extensions marked immediate.
+ *
+ **/
+
+ public synchronized void instantiateImmediateExtensions()
+ {
+ if (_extensions == null)
+ return;
+
+ Iterator i = _extensions.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+
+ IExtensionSpecification spec = (IExtensionSpecification) entry.getValue();
+
+ if (!spec.isImmediate())
+ continue;
+
+ String name = (String) entry.getKey();
+
+ getExtension(name);
+ }
+
+ }
+
+ public IResourceResolver getResourceResolver()
+ {
+ return _resolver;
+ }
+
+ public void setResourceResolver(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ /**
+ * Returns the extensions map.
+ * @return Map of objects.
+ *
+ **/
+
+ protected Map getExtensions()
+ {
+ return _extensions;
+ }
+
+ /**
+ * Updates the extension map.
+ * @param extension A Map of extension specification paths
+ * keyed on extension id.
+ *
+ * <p>The map is retained, not copied.
+ *
+ **/
+
+ protected void setExtensions(Map extension)
+ {
+ _extensions = extension;
+ }
+
+ /**
+ * Returns the libraries map.
+ * @return Map of {@link LibrarySpecification}.
+ *
+ **/
+
+ protected Map getLibraries()
+ {
+ return _libraries;
+ }
+
+ /**
+ * Updates the library map.
+ * @param libraries A Map of library specification paths
+ * keyed on library id.
+ *
+ * <p>The map is retained, not copied.
+ *
+ **/
+
+ protected void setLibraries(Map libraries)
+ {
+ _libraries = libraries;
+ }
+
+ /**
+ * Returns the pages map.
+ * @return Map of {@link IComponentSpecification}.
+ *
+ **/
+
+ protected Map getPages()
+ {
+ return _pages;
+ }
+
+ /**
+ * Updates the page map.
+ * @param pages A Map of page specification paths
+ * keyed on page id.
+ *
+ * <p>The map is retained, not copied.
+ *
+ **/
+
+ protected void setPages(Map pages)
+ {
+ _pages = pages;
+ }
+
+ /**
+ * Returns the services.
+ * @return Map of service class names.
+ *
+ **/
+
+ protected Map getServices()
+ {
+ return _services;
+ }
+
+ /**
+ * Updates the services map.
+ * @param services A Map of the fully qualified names of classes
+ * which implement
+ * {@link org.apache.tapestry.engine.IEngineService}
+ * keyed on service id.
+ *
+ * <p>The map is retained, not copied.
+ *
+ **/
+
+ protected void setServices(Map services)
+ {
+ _services = services;
+ }
+
+ /**
+ * Returns the components map.
+ * @return Map of {@link IContainedComponent}.
+ *
+ **/
+
+ protected Map getComponents()
+ {
+ return _components;
+ }
+
+ /**
+ * Updates the components map.
+ * @param components A Map of {@link IContainedComponent} keyed on component id.
+ * The map is retained, not copied.
+ *
+ **/
+
+ protected void setComponents(Map components)
+ {
+ _components = components;
+ }
+
+ /**
+ * Returns the XML Public Id for the library file, or null
+ * if not applicable.
+ *
+ * <p>
+ * This method exists as a convienience for the Spindle plugin.
+ * A previous method used an arbitrary version string, the
+ * public id is more useful and less ambiguous.
+ *
+ *
+ **/
+
+ public String getPublicId()
+ {
+ return _publicId;
+ }
+
+ public void setPublicId(String publicId)
+ {
+ _publicId = publicId;
+ }
+
+ /** @since 3.0 **/
+
+ public IResourceLocation getSpecificationLocation()
+ {
+ return _specificationLocation;
+ }
+
+ /** @since 3.0 **/
+
+ public void setSpecificationLocation(IResourceLocation specificationLocation)
+ {
+ _specificationLocation = specificationLocation;
+ }
+
+ /** @since 3.0 **/
+
+ public synchronized String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("components", _components);
+ builder.append("description", _description);
+ builder.append("instantiatedExtensions", _instantiatedExtensions);
+ builder.append("libraries", _libraries);
+ builder.append("pages", _pages);
+ builder.append("publicId", _publicId);
+ builder.append("resolver", _resolver);
+ builder.append("services", _services);
+ builder.append("specificationLocation", _specificationLocation);
+
+ extendDescription(builder);
+
+ return builder.toString();
+ }
+
+ /**
+ * Does nothing, subclasses may override to add additional
+ * description.
+ *
+ * @see #toString()
+ * @since 3.0
+ *
+ **/
+
+ protected void extendDescription(ToStringBuilder builder)
+ {
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ListenerBindingSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ListenerBindingSpecification.java
new file mode 100644
index 0000000..a5fd468
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ListenerBindingSpecification.java
@@ -0,0 +1,56 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+/**
+ * Special subclass of {@link org.apache.tapestry.spec.BindingSpecification} used
+ * to encapsulate the additional information
+ * specific to listener bindings. In a ListenerBindingSpecification, the
+ * value property is the actual script (and is aliased as property script),
+ * but an additional property,
+ * language, (which may be null) is needed. This is the language
+ * the script is written in.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ListenerBindingSpecification extends BindingSpecification implements IListenerBindingSpecification
+{
+ protected String _language;
+
+ public ListenerBindingSpecification()
+ {
+ setType(BindingType.LISTENER);
+ }
+
+ public String getLanguage()
+ {
+ return _language;
+ }
+
+ public String getScript()
+ {
+ return getValue();
+ }
+
+ public void setLanguage(String language)
+ {
+ _language = language;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/LocatablePropertyHolder.java b/tapestry-framework/src/org/apache/tapestry/spec/LocatablePropertyHolder.java
new file mode 100644
index 0000000..cbf7f5f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/LocatablePropertyHolder.java
@@ -0,0 +1,47 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.ILocationHolder;
+import org.apache.tapestry.util.BasePropertyHolder;
+
+/**
+ * Base class for implementing both
+ * interfaces {@link org.apache.tapestry.util.IPropertyHolder} and
+ * {@link org.apache.tapestry.ILocationHolder}. This is
+ * used by all the specification classes.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class LocatablePropertyHolder extends BasePropertyHolder implements ILocationHolder
+{
+ private ILocation _location;
+
+ public ILocation getLocation()
+ {
+ return _location;
+ }
+
+ public void setLocation(ILocation location)
+ {
+ _location = location;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/ParameterSpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/ParameterSpecification.java
new file mode 100644
index 0000000..0115043
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/ParameterSpecification.java
@@ -0,0 +1,164 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+/**
+ * Defines a formal parameter to a component. A <code>IParameterSpecification</code>
+ * is contained by a {@link IComponentSpecification}.
+ *
+ * <p>TBD: Identify arrays in some way.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ParameterSpecification extends BaseLocatable implements IParameterSpecification
+{
+ private boolean required = false;
+ private String type;
+
+ /** @since 1.0.9 **/
+ private String description;
+
+ /** @since 2.0.3 **/
+ private String propertyName;
+
+ /** @since 3.0 **/
+ private String defaultValue = null;
+
+ private Direction direction = Direction.CUSTOM;
+
+ /**
+ * Returns the class name of the expected type of the parameter. The default value
+ * is <code>java.lang.Object</code> which matches anything.
+ *
+ **/
+
+ public String getType()
+ {
+ return type;
+ }
+
+ /**
+ * Returns true if the parameter is required by the component.
+ * The default is false, meaning the parameter is optional.
+ *
+ **/
+
+ public boolean isRequired()
+ {
+ return required;
+ }
+
+ public void setRequired(boolean value)
+ {
+ required = value;
+ }
+
+ /**
+ * Sets the type of value expected for the parameter. This can be
+ * left blank to indicate any type.
+ *
+ **/
+
+ public void setType(String value)
+ {
+ type = value;
+ }
+
+ /**
+ * Returns the documentation for this parameter.
+ *
+ * @since 1.0.9
+ *
+ **/
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ * Sets the documentation for this parameter.
+ *
+ * @since 1.0.9
+ *
+ **/
+
+ public void setDescription(String description)
+ {
+ this.description = description;
+ }
+
+ /**
+ * Sets the property name (of the component class)
+ * to connect the parameter to.
+ *
+ **/
+
+ public void setPropertyName(String propertyName)
+ {
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * Returns the name of the JavaBeans property to connect the
+ * parameter to.
+ *
+ **/
+
+ public String getPropertyName()
+ {
+ return propertyName;
+ }
+
+ /**
+ * Returns the parameter value direction, defaulting to {@link Direction#CUSTOM}
+ * if not otherwise specified.
+ *
+ **/
+
+ public Direction getDirection()
+ {
+ return direction;
+ }
+
+ public void setDirection(Direction direction)
+ {
+ if (direction == null)
+ throw new IllegalArgumentException("direction may not be null.");
+
+ this.direction = direction;
+ }
+
+
+ /**
+ * @see org.apache.tapestry.spec.IParameterSpecification#getDefaultValue()
+ */
+ public String getDefaultValue()
+ {
+ return defaultValue;
+ }
+
+ /**
+ * @see org.apache.tapestry.spec.IParameterSpecification#setDefaultValue(java.lang.String)
+ */
+ public void setDefaultValue(String defaultValue)
+ {
+ this.defaultValue = defaultValue;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/PropertySpecification.java b/tapestry-framework/src/org/apache/tapestry/spec/PropertySpecification.java
new file mode 100644
index 0000000..566a364
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/PropertySpecification.java
@@ -0,0 +1,81 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+/**
+ * Defines a transient or persistant property of a component or page.
+ * A {@link org.apache.tapestry.engine.IComponentClassEnhancer} uses this information
+ * to create a subclass with the necessary instance variables and methods.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class PropertySpecification extends BaseLocatable implements IPropertySpecification
+{
+ private String _name;
+ private String _type = "java.lang.Object";
+ private boolean _persistent;
+ private String _initialValue;
+
+ public String getInitialValue()
+ {
+ return _initialValue;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public boolean isPersistent()
+ {
+ return _persistent;
+ }
+
+ public String getType()
+ {
+ return _type;
+ }
+
+ public void setInitialValue(String initialValue)
+ {
+ _initialValue = initialValue;
+ }
+
+ /**
+ * Sets the name of the property. This should not be changed
+ * once this IPropertySpecification is added to
+ * a {@link org.apache.tapestry.spec.ComponentSpecification}.
+ *
+ **/
+
+ public void setName(String name)
+ {
+ _name = name;
+ }
+
+ public void setPersistent(boolean persistant)
+ {
+ _persistent = persistant;
+ }
+
+ public void setType(String type)
+ {
+ _type = type;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/SpecFactory.java b/tapestry-framework/src/org/apache/tapestry/spec/SpecFactory.java
new file mode 100644
index 0000000..2210b0a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/SpecFactory.java
@@ -0,0 +1,180 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.spec;
+
+import org.apache.tapestry.bean.ExpressionBeanInitializer;
+import org.apache.tapestry.bean.IBeanInitializer;
+import org.apache.tapestry.bean.MessageBeanInitializer;
+
+/**
+ * A Factory used by {@link org.apache.tapestry.parse.SpecificationParser} to create Tapestry
+ * domain objects.
+ *
+ * <p>
+ * The default implementation here creates the expected runtime
+ * instances of classes in packages:
+ * <ul>
+ * <li>org.apache.tapestry.spec</li>
+ * <li>org.apache.tapestry.bean</li>
+ * </ul>
+ *
+ * <p>
+ * This class is extended by Spindle - the Eclipse Plugin for Tapestry
+ *
+ * @author GWL
+ * @since 1.0.9
+ *
+ **/
+
+public class SpecFactory
+{
+ /**
+ * Creates a concrete instance of {@link ApplicationSpecification}.
+ **/
+
+ public IApplicationSpecification createApplicationSpecification()
+ {
+ return new ApplicationSpecification();
+ }
+
+ /**
+ * Creates an instance of {@link LibrarySpecification}.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public ILibrarySpecification createLibrarySpecification()
+ {
+ return new LibrarySpecification();
+ }
+
+ /**
+ * Returns a new instance of {@link IAssetSpecification}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IAssetSpecification createAssetSpecification()
+ {
+ return new AssetSpecification();
+ }
+
+ /**
+ * Creates a new instance of {@link IBeanSpecification}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IBeanSpecification createBeanSpecification()
+ {
+ return new BeanSpecification();
+ }
+
+ public IBindingSpecification createBindingSpecification()
+ {
+ return new BindingSpecification();
+ }
+
+ /**
+ * Creates a new concrete instance of {@link IListenerBindingSpecification} for the
+ * given language (which is option) and script.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IListenerBindingSpecification createListenerBindingSpecification()
+ {
+ return new ListenerBindingSpecification();
+ }
+
+ /**
+ * Creates a concrete instance of {@link IComponentSpecification}.
+ **/
+
+ public IComponentSpecification createComponentSpecification()
+ {
+ return new ComponentSpecification();
+ }
+
+ /**
+ * Creates a concrete instance of {@link IContainedComponent}.
+ **/
+
+ public IContainedComponent createContainedComponent()
+ {
+ return new ContainedComponent();
+ }
+
+ /**
+ * Creates a concrete instance of {@link ParameterSpecification}.
+ **/
+
+ public IParameterSpecification createParameterSpecification()
+ {
+ return new ParameterSpecification();
+ }
+
+ /**
+ * Creates a new instance of {@link ExpressionBeanInitializer}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IBeanInitializer createExpressionBeanInitializer()
+ {
+ return new ExpressionBeanInitializer();
+ }
+
+ /**
+ * Returns a new instance of {@link MessageBeanInitializer}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IBeanInitializer createMessageBeanInitializer()
+ {
+ return new MessageBeanInitializer();
+ }
+
+ /**
+ * Creates a concrete instance of {@link org.apache.tapestry.spec.IExtensionSpecification}.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public IExtensionSpecification createExtensionSpecification()
+ {
+ return new ExtensionSpecification();
+ }
+
+ /**
+ * Creates a concrete instance of {@link org.apache.tapestry.spec.IPropertySpecification}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public IPropertySpecification createPropertySpecification()
+ {
+ return new PropertySpecification();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/spec/package.html b/tapestry-framework/src/org/apache/tapestry/spec/package.html
new file mode 100644
index 0000000..2dfaa02
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/spec/package.html
@@ -0,0 +1,14 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Classes to represent application and component specifications.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/AdaptorRegistry.java b/tapestry-framework/src/org/apache/tapestry/util/AdaptorRegistry.java
new file mode 100644
index 0000000..01ad478
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/AdaptorRegistry.java
@@ -0,0 +1,319 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * An implementation of the <b>Adaptor</b> pattern. The adaptor
+ * pattern allows new functionality to be assigned to an existing class.
+ * As implemented here, this is a smart lookup between
+ * a particular class (the class to be adapted, called
+ * the <em>subject class</em>) and some object instance
+ * that can provide the extra functionality (called the
+ * <em>adaptor</em>). The implementation of the adaptor is not relevant
+ * to the AdaptorRegistry class.
+ *
+ * <p>Adaptors are registered before they can be used; the registration maps a
+ * particular class to an adaptor instance. The adaptor instance will be used
+ * when the subject class matches the registered class, or the subject class
+ * inherits from the registered class.
+ *
+ * <p>This means that a search must be made that walks the inheritance tree
+ * (upwards from the subject class) to find a registered mapping.
+ *
+ * <p>In addition, adaptors can be registered against <em>interfaces</em>.
+ * Searching of interfaces occurs after searching of classes. The exact order is:
+ *
+ * <ul>
+ * <li>Search for the subject class, then each super-class of the subject class
+ * (excluding java.lang.Object)
+ * <li>Search interfaces, starting with interfaces implemented by the subject class,
+ * continuing with interfaces implemented by the super-classes, then
+ * interfaces extended by earlier interfaces (the exact order is a bit fuzzy)
+ * <li>Search for a match for java.lang.Object, if any
+ * </ul>
+ *
+ * <p>The first match terminates the search.
+ *
+ * <p>The AdaptorRegistry caches the results of search; a subsequent search for the
+ * same subject class will be resolved immediately.
+ *
+ * <p>AdaptorRegistry does a minor tweak of the "natural" inheritance.
+ * Normally, the parent class of an object array (i.e., <code>Foo[]</code>) is
+ * simply <code>Object</code>, even though you may assign
+ * <code>Foo[]</code> to a variable of type <code>Object[]</code>. AdaptorRegistry
+ * "fixes" this by searching for <code>Object[]</code> as if it was the superclass of
+ * any object array. This means that the search path for <code>Foo[]</code> is
+ * <code>Foo[]</code>, <code>Object[]</code>, then a couple of interfaces
+ * {@link java.lang.Cloneable}, {@link java.io.Serializable}, etc. that are\
+ * implicitily implemented by arrarys), and then, finally, <code>Object</code>
+ *
+ * <p>
+ * This tweak doesn't apply to scalar arrays, since scalar arrays may <em>not</em>
+ * be assigned to <code>Object[]</code>.
+ *
+ * <p>This class is thread safe.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class AdaptorRegistry
+{
+ private static final Log LOG = LogFactory.getLog(AdaptorRegistry.class);
+
+ /**
+ * A Map of adaptor objects, keyed on registration Class.
+ *
+ **/
+
+ private Map registrations = new HashMap();
+
+ /**
+ * A Map of adaptor objects, keyed on subject Class.
+ *
+ **/
+
+ private Map cache = new HashMap();
+
+ /**
+ * Registers an adaptor for a registration class.
+ *
+ * @throws IllegalArgumentException if an adaptor has already
+ * been registered for the given class.
+ **/
+
+ public synchronized void register(Class registrationClass, Object adaptor)
+ {
+ if (registrations.containsKey(registrationClass))
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "AdaptorRegistry.duplicate-registration",
+ Tapestry.getClassName(registrationClass)));
+
+ registrations.put(registrationClass, adaptor);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Registered " + adaptor + " for " + Tapestry.getClassName(registrationClass));
+
+ // Can't tell what is and isn't valid in the cache.
+ // Also, normally all registrations occur before any adaptors
+ // are searched for, so this is not a big deal.
+
+ cache.clear();
+ }
+
+ /**
+ * Gets the adaptor for the specified subjectClass.
+ *
+ * @throws IllegalArgumentException if no adaptor could be found.
+ *
+ **/
+
+ public synchronized Object getAdaptor(Class subjectClass)
+ {
+ Object result;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Getting adaptor for class " + Tapestry.getClassName(subjectClass));
+
+ result = cache.get(subjectClass);
+
+ if (result != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found " + result + " in cache");
+
+ return result;
+ }
+
+ result = searchForAdaptor(subjectClass);
+
+ // Record the result in the cache
+
+ cache.put(subjectClass, result);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Found " + result);
+
+ return result;
+ }
+
+ /**
+ * Searches the registration Map for a match, based on inheritance.
+ *
+ * <p>Searches class inheritance first, then interfaces (in a rather vague order).
+ * Really should match the order from the JVM spec.
+ *
+ * <p>There's a degenerate case where we may check the same interface more than once:
+ * <ul>
+ * <li>Two interfaces, I1 and I2
+ * <li>Two classes, C1 and C2
+ * <li>I2 extends I1
+ * <li>C2 extends C1
+ * <li>C1 implements I1
+ * <li>C2 implements I2
+ * <li>The search will be: C2, C1, I2, I1, I1
+ * <li>I1 is searched twice, because C1 implements it, and I2 extends it
+ * <li>There are other such cases, but none of them cause infinite loops
+ * and most are rare (we could guard against it, but its relatively expensive).
+ * <li>Multiple checks only occur if we don't find a registration
+ * </ul>
+ *
+ * <p>
+ * This method is only called from a synchronized block, so it is
+ * implicitly synchronized.
+ *
+ **/
+
+ private Object searchForAdaptor(Class subjectClass)
+ {
+ LinkedList queue = null;
+ Object result = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Searching for adaptor for class " + Tapestry.getClassName(subjectClass));
+
+ // Step one: work up through the class inheritance.
+
+ Class searchClass = subjectClass;
+
+ // Primitive types have null, not Object, as their parent
+ // class.
+
+ while (searchClass != Object.class && searchClass != null)
+ {
+ result = registrations.get(searchClass);
+ if (result != null)
+ return result;
+
+ // Not an exact match. If the search class
+ // implements any interfaces, add them to the queue.
+
+ Class[] interfaces = searchClass.getInterfaces();
+ int length = interfaces.length;
+
+ if (queue == null && length > 0)
+ queue = new LinkedList();
+
+ for (int i = 0; i < length; i++)
+ queue.addLast(interfaces[i]);
+
+ // Advance up to the next superclass
+
+ searchClass = getSuperclass(searchClass);
+
+ }
+
+ // Ok, the easy part failed, lets start searching
+ // interfaces.
+
+ if (queue != null)
+ {
+ while (!queue.isEmpty())
+ {
+ searchClass = (Class) queue.removeFirst();
+
+ result = registrations.get(searchClass);
+ if (result != null)
+ return result;
+
+ // Interfaces can extend other interfaces; add them
+ // to the queue.
+
+ Class[] interfaces = searchClass.getInterfaces();
+ int length = interfaces.length;
+
+ for (int i = 0; i < length; i++)
+ queue.addLast(interfaces[i]);
+ }
+ }
+
+ // Not a match on interface; our last gasp is to check
+ // for a registration for java.lang.Object
+
+ result = registrations.get(Object.class);
+ if (result != null)
+ return result;
+
+ // No match? That's rare ... and an error.
+
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "AdaptorRegistry.adaptor-not-found",
+ Tapestry.getClassName(subjectClass)));
+ }
+
+ /**
+ * Returns the superclass of the given class, with a single tweak: If the
+ * search class is an array class, and the component type is an object class
+ * (but not Object), then the simple Object array class is returned. This reflects
+ * the fact that an array of any class may be assignable to <code>Object[]</code>,
+ * even though the superclass of an array is always simply <code>Object</code>.
+ *
+ **/
+
+ private Class getSuperclass(Class searchClass)
+ {
+ if (searchClass.isArray())
+ {
+ Class componentType = searchClass.getComponentType();
+
+ if (!componentType.isPrimitive() && componentType != Object.class)
+ return Object[].class;
+ }
+
+ return searchClass.getSuperclass();
+ }
+
+ public synchronized String toString()
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("AdaptorRegistry[");
+
+ Iterator i = registrations.entrySet().iterator();
+ boolean showSep = false;
+
+ while (i.hasNext())
+ {
+ if (showSep)
+ buffer.append(' ');
+
+ Map.Entry entry = (Map.Entry) i.next();
+
+ Class registeredClass = (Class) entry.getKey();
+
+ buffer.append(Tapestry.getClassName(registeredClass));
+ buffer.append("=");
+ buffer.append(entry.getValue());
+
+ showSep = true;
+ }
+
+ buffer.append("]");
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/BasePropertyHolder.java b/tapestry-framework/src/org/apache/tapestry/util/BasePropertyHolder.java
new file mode 100644
index 0000000..35746ea
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/BasePropertyHolder.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class implementation for the {@link IPropertyHolder} interface.
+ *
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public class BasePropertyHolder implements IPropertyHolder
+{
+ private static final int MAP_SIZE = 7;
+ private Map properties;
+
+ public String getProperty(String name)
+ {
+ if (properties == null)
+ return null;
+
+ return (String) properties.get(name);
+ }
+
+ public void setProperty(String name, String value)
+ {
+ if (value == null)
+ {
+ removeProperty(name);
+ return;
+ }
+
+ if (properties == null)
+ properties = new HashMap(MAP_SIZE);
+
+ properties.put(name, value);
+ }
+
+ public void removeProperty(String name)
+ {
+ if (properties == null)
+ return;
+
+ properties.remove(name);
+ }
+
+ public List getPropertyNames()
+ {
+ if (properties == null)
+ return Collections.EMPTY_LIST;
+
+ List result = new ArrayList(properties.keySet());
+
+ Collections.sort(result);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ComponentAddress.java b/tapestry-framework/src/org/apache/tapestry/util/ComponentAddress.java
new file mode 100644
index 0000000..38e2818
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ComponentAddress.java
@@ -0,0 +1,145 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.io.Serializable;
+
+import org.apache.tapestry.IComponent;
+import org.apache.tapestry.INamespace;
+import org.apache.tapestry.IPage;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * The ComponentAddress class contains the path to a component, allowing it to
+ * locate an instance of that component in a different
+ * {@link org.apache.tapestry.IRequestCycle}.
+ *
+ * <p>This class needs to be used mostly when working with components
+ * accessed via the {@link org.apache.tapestry.IRender} interface.
+ * It allows those components to serialize and
+ * pass as a service parameter information about what component they have to
+ * talk to if control returns back to them.
+ *
+ * <p>This situation often occurs when the component used via IRender contains
+ * Direct or Action links.
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 2.2
+ *
+ */
+public class ComponentAddress implements Serializable
+{
+ private String _pageName;
+ private String _idPath;
+
+ /**
+ * Creates a new ComponentAddress object that carries the identification
+ * information of the given component (the page name and the ID path).
+ * @param component the component to get the address of
+ */
+ public ComponentAddress(IComponent component)
+ {
+ this(component.getPage().getPageName(), component.getIdPath());
+ }
+
+ /**
+ * Creates a new ComponentAddress using the given Page Name and ID Path
+ * @param pageName the name of the page that contains the component
+ * @param idPath the ID Path of the component
+ */
+ public ComponentAddress(String pageName, String idPath)
+ {
+ _pageName = pageName;
+ _idPath = idPath;
+ }
+
+ /**
+ * Creates a new ComponentAddress using the given Page Name and ID Path
+ * relative on the provided Namespace
+ * @param namespace the namespace of the page that contains the component
+ * @param pageName the name of the page that contains the component
+ * @param idPath the ID Path of the component
+ */
+ public ComponentAddress(
+ INamespace namespace,
+ String pageName,
+ String idPath)
+ {
+ this(namespace.constructQualifiedName(pageName), idPath);
+ }
+
+ /**
+ * Finds a component with the current address using the given RequestCycle.
+ * @param cycle the RequestCycle to use to locate the component
+ * @return IComponent a component that has been initialized for the given RequestCycle
+ */
+ public IComponent findComponent(IRequestCycle cycle)
+ {
+ IPage objPage = cycle.getPage(_pageName);
+ return objPage.getNestedComponent(_idPath);
+ }
+
+ /**
+ * Returns the idPath of the component.
+ * @return String the ID path of the component
+ */
+ public String getIdPath()
+ {
+ return _idPath;
+ }
+
+ /**
+ * Returns the Page Name of the component.
+ * @return String the Page Name of the component
+ */
+ public String getPageName()
+ {
+ return _pageName;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ int hash = _pageName.hashCode() * 31;
+ if (_idPath != null)
+ hash += _idPath.hashCode();
+ return hash;
+ }
+
+ /**
+ * @see java.lang.Object#equals(Object)
+ */
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof ComponentAddress))
+ return false;
+
+ if (obj == this)
+ return true;
+
+ ComponentAddress objAddress = (ComponentAddress) obj;
+ if (!getPageName().equals(objAddress.getPageName()))
+ return false;
+
+ String idPath1 = getIdPath();
+ String idPath2 = objAddress.getIdPath();
+ return (idPath1 == idPath2)
+ || (idPath1 != null && idPath1.equals(idPath2));
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ContentType.java b/tapestry-framework/src/org/apache/tapestry/util/ContentType.java
new file mode 100644
index 0000000..bafb558
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ContentType.java
@@ -0,0 +1,189 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+/**
+ * Represents an HTTP content type. Allows to set various elements like
+ * the mime type, the character set, and other parameters.
+ * This is similar to a number of other implementations of the same concept in JAF, etc.
+ * We have created this simple implementation to avoid including the whole libraries.
+ *
+ * @version $Id$
+ * @author mindbridge
+ * @since 3.0
+ **/
+public class ContentType
+{
+ private String _baseType;
+ private String _subType;
+ private Map _parameters;
+
+ /**
+ * Creates a new empty content type
+ */
+ public ContentType()
+ {
+ initialize();
+ }
+
+ /**
+ * Creates a new content type from the argument.
+ * The format of the argument has to be basetype/subtype(;key=value)*
+ *
+ * @param contentType the content type that needs to be represented
+ */
+ public ContentType(String contentType)
+ {
+ this();
+ parse(contentType);
+ }
+
+ private void initialize()
+ {
+ _baseType = "";
+ _subType = "";
+ _parameters = new HashMap();
+ }
+
+ /**
+ * @return the base type of the content type
+ */
+ public String getBaseType()
+ {
+ return _baseType;
+ }
+
+ /**
+ * @param baseType
+ */
+ public void setBaseType(String baseType)
+ {
+ _baseType = baseType;
+ }
+
+ /**
+ * @return the sub-type of the content type
+ */
+ public String getSubType()
+ {
+ return _subType;
+ }
+
+ /**
+ * @param subType
+ */
+ public void setSubType(String subType)
+ {
+ _subType = subType;
+ }
+
+ /**
+ * @return the MIME type of the content type
+ */
+ public String getMimeType()
+ {
+ return _baseType + "/" + _subType;
+ }
+
+ /**
+ * @return the list of names of parameters in this content type
+ */
+ public String[] getParameterNames()
+ {
+ Set parameterNames = _parameters.keySet();
+ return (String[]) parameterNames.toArray(new String[parameterNames.size()]);
+ }
+
+ /**
+ * @param key the name of the content type parameter
+ * @return the value of the content type parameter
+ */
+ public String getParameter(String key)
+ {
+ return (String) _parameters.get(key);
+ }
+
+ /**
+ * @param key the name of the content type parameter
+ * @param value the value of the content type parameter
+ */
+ public void setParameter(String key, String value)
+ {
+ _parameters.put(key.toLowerCase(), value);
+ }
+
+ /**
+ * Parses the argument and configures the content type accordingly.
+ * The format of the argument has to be type/subtype(;key=value)*
+ *
+ * @param contentType the content type that needs to be represented
+ */
+ public void parse(String contentType)
+ {
+ initialize();
+
+ StringTokenizer tokens = new StringTokenizer(contentType, ";");
+ if (!tokens.hasMoreTokens())
+ return;
+
+ String mimeType = tokens.nextToken();
+ StringTokenizer mimeTokens = new StringTokenizer(mimeType, "/");
+ setBaseType(mimeTokens.hasMoreTokens() ? mimeTokens.nextToken() : "");
+ setSubType(mimeTokens.hasMoreTokens() ? mimeTokens.nextToken() : "");
+
+ while (tokens.hasMoreTokens()) {
+ String parameter = tokens.nextToken();
+
+ StringTokenizer parameterTokens = new StringTokenizer(parameter, "=");
+ String key = parameterTokens.hasMoreTokens() ? parameterTokens.nextToken() : "";
+ String value = parameterTokens.hasMoreTokens() ? parameterTokens.nextToken() : "";
+ setParameter(key, value);
+ }
+ }
+
+
+
+ /**
+ * @return the string representation of this content type
+ */
+ public String unparse()
+ {
+ StringBuffer buf = new StringBuffer(getMimeType());
+
+ String[] parameterNames = getParameterNames();
+ for (int i = 0; i < parameterNames.length; i++)
+ {
+ String key = parameterNames[i];
+ String value = getParameter(key);
+ buf.append(";" + key + "=" + value);
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * @return the string representation of this content type. Same as unparse().
+ */
+ public String toString()
+ {
+ return unparse();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/DefaultResourceResolver.java b/tapestry-framework/src/org/apache/tapestry/util/DefaultResourceResolver.java
new file mode 100644
index 0000000..9f04c7c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/DefaultResourceResolver.java
@@ -0,0 +1,131 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Default implementation of {@link org.apache.tapestry.IResourceResolver} based
+ * around {@link Thread#getContextClassLoader()} (which is set by the
+ * servlet container).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class DefaultResourceResolver implements IResourceResolver
+{
+ private static final Log LOG = LogFactory.getLog(DefaultResourceResolver.class);
+
+ private ClassLoader _loader;
+
+ /**
+ * Constructs a new instance using
+ * {@link Thread#getContextClassLoader()}.
+ *
+ **/
+
+ public DefaultResourceResolver()
+ {
+ this(Thread.currentThread().getContextClassLoader());
+ }
+
+ public DefaultResourceResolver(ClassLoader loader)
+ {
+ _loader = loader;
+ }
+
+ public URL getResource(String name)
+ {
+ boolean debug = LOG.isDebugEnabled();
+
+ if (debug)
+ LOG.debug("getResource(" + name + ")");
+
+ String stripped = removeLeadingSlash(name);
+
+ URL result = _loader.getResource(stripped);
+
+ if (debug)
+ {
+ if (result == null)
+ LOG.debug("Not found.");
+ else
+ LOG.debug("Found as " + result);
+ }
+
+ return result;
+ }
+
+ private String removeLeadingSlash(String name)
+ {
+ if (name.startsWith("/"))
+ return name.substring(1);
+
+ return name;
+ }
+
+ /**
+ * Invokes {@link Class#forName(java.lang.String, boolean, java.lang.ClassLoader)}.
+ *
+ * @param name the complete class name to locate and load
+ * @return The loaded class
+ * @throws ApplicationRuntimeException if loading the class throws an exception
+ * (typically {@link ClassNotFoundException} or a security exception)
+ *
+ **/
+
+ public Class findClass(String name)
+ {
+ try
+ {
+ return Class.forName(name, true, _loader);
+ }
+ catch (Throwable t)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("ResourceResolver.unable-to-load-class", name, _loader, t.getMessage()),
+ t);
+ }
+ }
+
+ /**
+ *
+ * OGNL Support for dynamic class loading. Simply invokes {@link #findClass(String)}.
+ *
+ **/
+
+ public Class classForName(String name, Map map) throws ClassNotFoundException
+ {
+ return findClass(name);
+ }
+
+ /** @since 3.0 **/
+
+ public ClassLoader getClassLoader()
+ {
+ return _loader;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/DelegatingPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/DelegatingPropertySource.java
new file mode 100644
index 0000000..0085007
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/DelegatingPropertySource.java
@@ -0,0 +1,82 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * An implementation of {@link IPropertySource}
+ * that delegates to a list of other implementations. This makes
+ * it possible to create a search path for property values.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class DelegatingPropertySource implements IPropertySource
+{
+ private List _sources = new ArrayList();
+
+ public DelegatingPropertySource()
+ {
+ }
+
+ public DelegatingPropertySource(IPropertySource delegate)
+ {
+ addSource(delegate);
+ }
+
+ /**
+ * Adds another source to the list of delegate property sources.
+ * This is typically only done during initialization
+ * of this DelegatingPropertySource.
+ *
+ **/
+
+ public void addSource(IPropertySource source)
+ {
+ _sources.add(source);
+ }
+
+ /**
+ * Re-invokes the method on each delegate property source,
+ * in order, return the first non-null value found.
+ *
+ **/
+
+ public String getPropertyValue(String propertyName)
+ {
+ String result = null;
+ int count = _sources.size();
+
+ for (int i = 0; i < count; i++)
+ {
+ IPropertySource source = (IPropertySource)_sources.get(i);
+
+ result = source.getPropertyValue(propertyName);
+
+ if (result != null)
+ break;
+ }
+
+ return result;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ICleanable.java b/tapestry-framework/src/org/apache/tapestry/util/ICleanable.java
new file mode 100644
index 0000000..62c0585
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ICleanable.java
@@ -0,0 +1,44 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+/**
+ * An interface implemented by objects that can be
+ * cleaned up, which is to say, can release unneeded
+ * object references. This is useful for many classes which
+ * provide a pooling or caching function. Over time,
+ * some pooled or cached objects may no longer be useful
+ * to keep and can be released.
+ * references to unneeded objects.
+ * This interface is the bridge between
+ * the {@link JanitorThread} class and an object that
+ * wishes to be periodically told to "clean up".
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ *
+ **/
+
+public interface ICleanable
+{
+ /**
+ * Invoked periodically by the {@link JanitorThread}
+ * to perform whatever memory cleanups are reasonable.
+ *
+ **/
+
+ public void executeCleanup();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/IPropertyHolder.java b/tapestry-framework/src/org/apache/tapestry/util/IPropertyHolder.java
new file mode 100644
index 0000000..07df9db
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/IPropertyHolder.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.List;
+
+/**
+ * An interface that defines an object that can store named propertys. The names
+ * and the properties are Strings.
+ *
+ * @version $Id$
+ * @author Howard Lewis Ship
+ *
+ **/
+
+public interface IPropertyHolder
+{
+ /**
+ * Returns a List of Strings, the names of all
+ * properties held by the receiver. May return an empty list.
+ * The List is sorted alphabetically. The List may be modified
+ * without affecting this property holder.
+ *
+ * <p>Prior to release 2.2, this method returned Collection.
+ *
+ **/
+
+ public List getPropertyNames();
+
+ /**
+ * Sets a named property. The new value replaces the existing value, if any.
+ * Setting a property to null is the same as removing the property.
+ *
+ **/
+
+ public void setProperty(String name, String value);
+
+ /**
+ * Removes the named property, if present.
+ *
+ **/
+
+ public void removeProperty(String name);
+
+ /**
+ * Retrieves the named property, or null if the property is not defined.
+ *
+ **/
+
+ public String getProperty(String name);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/IRenderDescription.java b/tapestry-framework/src/org/apache/tapestry/util/IRenderDescription.java
new file mode 100644
index 0000000..6c0211f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/IRenderDescription.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * An object which may render a description of itself, which is used in debugging
+ * (i.e., by the Inspector).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.6
+ *
+ **/
+
+public interface IRenderDescription
+{
+ /**
+ * Object should render a description of itself to the writer.
+ *
+ **/
+
+ public void renderDescription(IMarkupWriter writer);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/IdAllocator.java b/tapestry-framework/src/org/apache/tapestry/util/IdAllocator.java
new file mode 100644
index 0000000..d7bbbe0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/IdAllocator.java
@@ -0,0 +1,90 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Used to "uniquify" names within a given context. A base name
+ * is passed in, and the return value is the base name, or the base name
+ * extended with a suffix to make it unique.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class IdAllocator
+{
+ private Map _generatorMap = new HashMap();
+
+ private static class NameGenerator
+ {
+ private String _baseId;
+ private int _index;
+
+ NameGenerator(String baseId)
+ {
+ _baseId = baseId + "$";
+ }
+
+ public String nextId()
+ {
+ return _baseId + _index++;
+ }
+ }
+
+ /**
+ * Allocates the id. Repeated calls for the same name will return
+ * "name", "name_0", "name_1", etc.
+ *
+ **/
+
+ public String allocateId(String name)
+ {
+ NameGenerator g = (NameGenerator) _generatorMap.get(name);
+ String result = null;
+
+ if (g == null)
+ {
+ g = new NameGenerator(name);
+ result = name;
+ }
+ else
+ result = g.nextId();
+
+ // Handle the degenerate case, where a base name of the form "foo$0" has been
+ // requested. Skip over any duplicates thus formed.
+
+ while (_generatorMap.containsKey(result))
+ result = g.nextId();
+
+ _generatorMap.put(result, g);
+
+ return result;
+ }
+
+ /**
+ * Clears the allocator, resetting it to freshly allocated state.
+ *
+ **/
+
+ public void clear()
+ {
+ _generatorMap.clear();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/JanitorThread.java b/tapestry-framework/src/org/apache/tapestry/util/JanitorThread.java
new file mode 100644
index 0000000..2967472
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/JanitorThread.java
@@ -0,0 +1,216 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A basic kind of janitor, an object that periodically invokes {@link ICleanable#executeCleanup()}
+ * on a set of objects.
+ * <p>
+ * The JanitorThread holds a <em>weak reference</em> to the objects it operates on.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.5
+ */
+
+public class JanitorThread extends Thread
+{
+ /**
+ * Default number of seconds between janitor runs, about 30 seconds.
+ */
+
+ public static final long DEFAULT_INTERVAL_MILLIS = 30 * 1024;
+
+ private long interval = DEFAULT_INTERVAL_MILLIS;
+
+ private boolean lockInterval = false;
+
+ private static JanitorThread shared = null;
+
+ /**
+ * A {@link List}of {@link WeakReference}s to {@link ICleanable}instances.
+ */
+
+ private List references = new ArrayList();
+
+ /**
+ * Creates a new daemon Janitor.
+ */
+
+ public JanitorThread()
+ {
+ this(null);
+ }
+
+ /**
+ * Creates new Janitor with the given name. The thread will have minimum priority and be a
+ * daemon.
+ */
+
+ public JanitorThread(String name)
+ {
+ super(name);
+
+ setDaemon(true);
+ setPriority(MIN_PRIORITY);
+ }
+
+ /**
+ * Returns a shared instance of JanitorThread. In most cases, the shared instance should be
+ * used, rather than creating a new instance; the exception being when particular scheduling is
+ * of concern. It is also bad policy to change the sleep interval on the shared janitor (though
+ * nothing prevents this, either).
+ */
+
+ public synchronized static JanitorThread getSharedJanitorThread()
+ {
+ if (shared == null)
+ {
+ shared = new JanitorThread("Shared-JanitorThread");
+ shared.lockInterval = true;
+
+ shared.start();
+ }
+
+ return shared;
+ }
+
+ public long getInterval()
+ {
+ return interval;
+ }
+
+ /**
+ * Updates the property, which may not take effect until the next time the thread finishes
+ * sleeping.
+ *
+ * @param value
+ * the interval, in milliseconds, between sweeps.
+ * @throws IllegalStateException
+ * always, if the receiver is the shared JanitorThread
+ * @throws IllegalArgumentException
+ * if value is less than 1
+ */
+
+ public void setInterval(long value)
+ {
+ if (lockInterval)
+ throw new IllegalStateException(Tapestry.getMessage("JanitorThread.interval-locked"));
+
+ if (value < 1)
+ throw new IllegalArgumentException(Tapestry
+ .getMessage("JanitorThread.illegal-interval"));
+
+ interval = value;
+ }
+
+ /**
+ * Adds a new cleanable object to the list of references. Care should be taken that objects are
+ * not added multiple times; they will be cleaned too often.
+ */
+
+ public void add(ICleanable cleanable)
+ {
+ WeakReference reference = new WeakReference(cleanable);
+
+ synchronized (references)
+ {
+ references.add(reference);
+ }
+ }
+
+ /**
+ * Runs through the list of targets and invokes {@link ICleanable#executeCleanup()}on each of
+ * them. {@link WeakReference}s that have been invalidated are weeded out.
+ */
+
+ protected void sweep()
+ {
+ synchronized (references)
+ {
+ Iterator i = references.iterator();
+
+ while (i.hasNext())
+ {
+ WeakReference ref = (WeakReference) i.next();
+
+ ICleanable cleanable = (ICleanable) ref.get();
+
+ if (cleanable == null)
+ i.remove();
+ else
+ cleanable.executeCleanup();
+ }
+ }
+ }
+
+ /**
+ * Waits for the next run, by sleeping for the desired period. Returns true if the sleep was
+ * successful, or false if the thread was interrupted (and should shut down).
+ */
+
+ protected void waitForNextPass()
+ {
+ try
+ {
+ sleep(interval);
+ }
+ catch (InterruptedException ex)
+ {
+ interrupt();
+ }
+ }
+
+ /**
+ * Alternates between {@link #waitForNextPass()}and {@link #sweep()}.
+ */
+
+ public void run()
+ {
+ while (!isInterrupted())
+ {
+ waitForNextPass();
+
+ sweep();
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer = new StringBuffer("JanitorThread@");
+ buffer.append(Integer.toHexString(hashCode()));
+
+ buffer.append("[interval=");
+ buffer.append(interval);
+
+ buffer.append(" count=");
+
+ synchronized (references)
+ {
+ buffer.append(references.size());
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/LocalizedContextResourceFinder.java b/tapestry-framework/src/org/apache/tapestry/util/LocalizedContextResourceFinder.java
new file mode 100644
index 0000000..ee11fbf
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/LocalizedContextResourceFinder.java
@@ -0,0 +1,92 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.net.MalformedURLException;
+import java.util.Locale;
+
+import javax.servlet.ServletContext;
+
+/**
+ * Finds localized resources within the web application context.
+ *
+ * @see javax.servlet.ServletContext
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class LocalizedContextResourceFinder
+{
+ private ServletContext _context;
+
+ public LocalizedContextResourceFinder(ServletContext context)
+ {
+ _context = context;
+ }
+
+ /**
+ * Resolves the resource, returning a path representing
+ * the closest match (with respect to the provided locale).
+ * Returns null if no match.
+ *
+ * <p>The provided path is split into a base path
+ * and a suffix (at the last period character). The locale
+ * will provide different suffixes to the base path
+ * and the first match is returned.
+ *
+ **/
+
+ public LocalizedResource resolve(String contextPath, Locale locale)
+ {
+ int dotx = contextPath.lastIndexOf('.');
+ String basePath = null;
+ String suffix = null;
+ // This handles assets without extensions - still allows them to be localized.
+ if (dotx > -1) {
+ basePath = contextPath.substring(0, dotx);
+ suffix = contextPath.substring(dotx);
+ } else {
+ basePath = contextPath;
+ suffix = "";
+ }
+
+ LocalizedNameGenerator generator = new LocalizedNameGenerator(basePath, locale, suffix);
+
+ while (generator.more())
+ {
+ String candidatePath = generator.next();
+
+ if (isExistingResource(candidatePath))
+ return new LocalizedResource(candidatePath, generator.getCurrentLocale());
+ }
+
+ return null;
+ }
+
+ private boolean isExistingResource(String path)
+ {
+ try
+ {
+ return _context.getResource(path) != null;
+ }
+ catch (MalformedURLException ex)
+ {
+ return false;
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/LocalizedNameGenerator.java b/tapestry-framework/src/org/apache/tapestry/util/LocalizedNameGenerator.java
new file mode 100644
index 0000000..7b4fcc5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/LocalizedNameGenerator.java
@@ -0,0 +1,208 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Used in a wide variety of resource searches. Generates
+ * a series of name variations from a base name, a
+ * {@link java.util.Locale} and an optional suffix.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class LocalizedNameGenerator
+{
+ private int _baseNameLength;
+ private String _suffix;
+ private StringBuffer _buffer;
+ private String _language;
+ private String _country;
+ private String _variant;
+ private int _state;
+ private int _prevState;
+
+ private static final int INITIAL = 0;
+ private static final int LCV = 1;
+ private static final int LC = 2;
+ private static final int LV = 3;
+ private static final int L = 4;
+ private static final int BARE = 5;
+ private static final int EXHAUSTED = 6;
+
+ public LocalizedNameGenerator(String baseName, Locale locale, String suffix)
+ {
+ _baseNameLength = baseName.length();
+
+ if (locale != null)
+ {
+ _language = locale.getLanguage();
+ _country = locale.getCountry();
+ _variant = locale.getVariant();
+ }
+
+ _state = INITIAL;
+ _prevState = INITIAL;
+
+ _suffix = suffix;
+
+ _buffer = new StringBuffer(baseName);
+
+ advance();
+ }
+
+ private void advance()
+ {
+ _prevState = _state;
+
+ while (_state != EXHAUSTED)
+ {
+ _state++;
+
+ switch (_state)
+ {
+ case LCV :
+
+ if (Tapestry.isBlank(_variant))
+ continue;
+
+ return;
+
+ case LC :
+
+ if (Tapestry.isBlank(_country))
+ continue;
+
+ return;
+
+ case LV :
+
+ // If _country is null, then we've already generated this string
+ // as state LCV and we can continue directly to state L
+
+ if (Tapestry.isBlank(_variant) || Tapestry.isBlank(_country))
+ continue;
+
+ return;
+
+ case L :
+
+ if (Tapestry.isBlank(_language))
+ continue;
+
+ return;
+
+ default :
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns true if there are more name variants to be
+ * returned, false otherwise.
+ *
+ **/
+
+ public boolean more()
+ {
+ return _state != EXHAUSTED;
+ }
+
+ /**
+ * Returns the next localized variant.
+ *
+ * @throws NoSuchElementException if all variants have been
+ * returned.
+ *
+ **/
+
+ public String next()
+ {
+ if (_state == EXHAUSTED)
+ throw new NoSuchElementException();
+
+ String result = build();
+
+ advance();
+
+ return result;
+ }
+
+ private String build()
+ {
+ _buffer.setLength(_baseNameLength);
+
+ if (_state == LC || _state == LCV || _state == L)
+ {
+ _buffer.append('_');
+ _buffer.append(_language);
+ }
+
+ // For LV, we want two underscores between language
+ // and variant.
+
+ if (_state == LC || _state == LCV || _state == LV)
+ {
+ _buffer.append('_');
+
+ if (_state != LV)
+ _buffer.append(_country);
+ }
+
+ if (_state == LV || _state == LCV)
+ {
+ _buffer.append('_');
+ _buffer.append(_variant);
+ }
+
+ if (_suffix != null)
+ _buffer.append(_suffix);
+
+ return _buffer.toString();
+ }
+
+ public Locale getCurrentLocale()
+ {
+ switch (_prevState)
+ {
+ case LCV :
+
+ return new Locale(_language, _country, _variant);
+
+ case LC :
+
+ return new Locale(_language, _country, "");
+
+ case LV :
+
+ return new Locale(_language, "", _variant);
+
+ case L :
+
+ return new Locale(_language, "", "");
+
+ default :
+ return null;
+ }
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/LocalizedPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/LocalizedPropertySource.java
new file mode 100644
index 0000000..6cf64c5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/LocalizedPropertySource.java
@@ -0,0 +1,107 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.Locale;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * A PropertySource extending the DelegatingPropertySources and adding the
+ * capability of searching for localized versions of the desired property.
+ * Useful when peoperties related to localization are needed.
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class LocalizedPropertySource extends DelegatingPropertySource
+{
+ private Locale _locale;
+
+ /**
+ * Creates a LocalizedPropertySource with the default locale
+ */
+ public LocalizedPropertySource()
+ {
+ this(Locale.getDefault());
+ }
+
+ /**
+ * Creates a LocalizedPropertySource with the provided locale
+ */
+ public LocalizedPropertySource(Locale locale)
+ {
+ super();
+ setLocale(locale);
+ }
+
+ /**
+ * Creates a LocalizedPropertySource with the default locale and the provided delegate
+ */
+ public LocalizedPropertySource(IPropertySource delegate)
+ {
+ this(Locale.getDefault(), delegate);
+ }
+
+ /**
+ * Creates a LocalizedPropertySource with the provided locale and delegate
+ */
+ public LocalizedPropertySource(Locale locale, IPropertySource delegate)
+ {
+ super(delegate);
+ setLocale(locale);
+ }
+
+
+ /**
+ * @return the locale currently used
+ */
+ public Locale getLocale()
+ {
+ return _locale;
+ }
+
+ /**
+ * @param locale the locale currently used
+ */
+ public void setLocale(Locale locale)
+ {
+ _locale = locale;
+ }
+
+
+ /**
+ * Examines the properties localized using the provided locale in the order
+ * of more specific to more general and returns the first that has a value.
+ * @see org.apache.tapestry.util.DelegatingPropertySource#getPropertyValue(java.lang.String)
+ */
+ public String getPropertyValue(String propertyName)
+ {
+ LocalizedNameGenerator generator = new LocalizedNameGenerator(propertyName, getLocale(), "");
+
+ while (generator.more())
+ {
+ String candidateName = generator.next();
+
+ String value = super.getPropertyValue(candidateName);
+ if (value != null)
+ return value;
+ }
+
+ return null;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/LocalizedResource.java b/tapestry-framework/src/org/apache/tapestry/util/LocalizedResource.java
new file mode 100644
index 0000000..d23f10a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/LocalizedResource.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.Locale;
+
+/**
+ * Contains the path to a localized resource and the locale for which it has been localized.
+ * This object is immutable.
+ *
+ * @author Mindbridge
+ * @version $Id$
+ * @since 3.0
+ */
+public class LocalizedResource
+{
+ private String _resourcePath;
+ private Locale _resourceLocale;
+
+
+ public LocalizedResource(String resourcePath, Locale resourceLocale)
+ {
+ _resourcePath = resourcePath;
+ _resourceLocale = resourceLocale;
+ }
+
+ /**
+ * @return the locale for which this resource has been localized or null if it has not been localized at all
+ */
+ public Locale getResourceLocale()
+ {
+ return _resourceLocale;
+ }
+
+ /**
+ * @return the path to the localized resource
+ */
+ public String getResourcePath()
+ {
+ return _resourcePath;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/LocalizedResourceFinder.java b/tapestry-framework/src/org/apache/tapestry/util/LocalizedResourceFinder.java
new file mode 100644
index 0000000..751ce03
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/LocalizedResourceFinder.java
@@ -0,0 +1,73 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.Locale;
+
+import org.apache.tapestry.IResourceResolver;
+
+/**
+ *
+ * Searches for a localization of a
+ * particular resource in the classpath (using
+ * a {@link org.apache.tapestry.IResourceResolver}.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class LocalizedResourceFinder
+{
+ private IResourceResolver _resolver;
+
+ public LocalizedResourceFinder(IResourceResolver resolver)
+ {
+ _resolver = resolver;
+ }
+
+ /**
+ * Resolves the resource, returning a path representing
+ * the closest match (with respect to the provided locale).
+ * Returns null if no match.
+ *
+ * <p>The provided path is split into a base path
+ * and a suffix (at the last period character). The locale
+ * will provide different suffixes to the base path
+ * and the first match is returned.
+ *
+ **/
+
+ public LocalizedResource resolve(String resourcePath, Locale locale)
+ {
+ int dotx = resourcePath.lastIndexOf('.');
+ String basePath = resourcePath.substring(0, dotx);
+ String suffix = resourcePath.substring(dotx);
+
+ LocalizedNameGenerator generator = new LocalizedNameGenerator(basePath, locale, suffix);
+
+ while (generator.more())
+ {
+ String candidatePath = generator.next();
+
+ if (_resolver.getResource(candidatePath) != null)
+ return new LocalizedResource(candidatePath, generator.getCurrentLocale());
+ }
+
+ return null;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/MultiKey.java b/tapestry-framework/src/org/apache/tapestry/util/MultiKey.java
new file mode 100644
index 0000000..df247a0
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/MultiKey.java
@@ -0,0 +1,234 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A complex key that may be used as an alternative to nested
+ * {@link java.util.Map}s.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class MultiKey implements Externalizable
+{
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = 4465448607415788806L;
+
+ private static final int HASH_CODE_UNSET = -1;
+
+ private transient int hashCode = HASH_CODE_UNSET;
+
+ private Object[] keys;
+
+ /**
+ * Public no-arguments constructor needed to be compatible with
+ * {@link Externalizable}; this leaves the new MultiKey in a
+ * non-usable state and shouldn't be used by user code.
+ *
+ **/
+
+ public MultiKey()
+ {
+ }
+
+ /**
+ * Builds a <code>MultiKey</code> from an array of keys. If the array is not
+ * copied, then it must not be modified.
+ *
+ * @param keys The components of the key.
+ * @param makeCopy If true, a copy of the keys is created. If false,
+ * the keys are simple retained by the <code>MultiKey</code>.
+ *
+ * @throws IllegalArgumentException if keys is null, of if the
+ * first element of keys is null.
+ *
+ **/
+
+ public MultiKey(Object[] keys, boolean makeCopy)
+ {
+ super();
+
+ if (keys == null || keys.length == 0)
+ throw new IllegalArgumentException(Tapestry.getMessage("MultiKey.null-keys"));
+
+ if (keys[0] == null)
+ throw new IllegalArgumentException(Tapestry.getMessage("MultiKey.first-element-may-not-be-null"));
+
+ if (makeCopy)
+ {
+ this.keys = new Object[keys.length];
+ System.arraycopy(keys, 0, this.keys, 0, keys.length);
+ }
+ else
+ this.keys = keys;
+ }
+
+ /**
+ * Returns true if:
+ * <ul>
+ * <li>The other object is a <code>MultiKey</code>
+ * <li>They have the same number of key elements
+ * <li>Every element is an exact match or is equal
+ * </ul>
+ *
+ **/
+
+ public boolean equals(Object other)
+ {
+ int i;
+
+ if (other == null)
+ return false;
+
+ if (keys == null)
+ throw new IllegalStateException(Tapestry.getMessage("MultiKey.no-keys"));
+
+ // Would a hashCode check be worthwhile here?
+
+ try
+ {
+ MultiKey otherMulti = (MultiKey) other;
+
+ if (keys.length != otherMulti.keys.length)
+ return false;
+
+ for (i = 0; i < keys.length; i++)
+ {
+ // On an exact match, continue. This means that null matches
+ // null.
+
+ if (keys[i] == otherMulti.keys[i])
+ continue;
+
+ // If either is null, but not both, then
+ // not a match.
+
+ if (keys[i] == null || otherMulti.keys[i] == null)
+ return false;
+
+ if (!keys[i].equals(otherMulti.keys[i]))
+ return false;
+
+ }
+
+ // Every key equal. A match.
+
+ return true;
+ }
+ catch (ClassCastException e)
+ {
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the hash code of the receiver, which is computed from all the
+ * non-null key elements. This value is computed once and
+ * then cached, so elements should not change their hash codes
+ * once created (note that this
+ * is the same constraint that would be used if the individual
+ * key elements were
+ * themselves {@link java.util.Map} keys.
+ *
+ *
+ **/
+
+ public int hashCode()
+ {
+ if (hashCode == HASH_CODE_UNSET)
+ {
+ hashCode = keys[0].hashCode();
+
+ for (int i = 1; i < keys.length; i++)
+ {
+ if (keys[i] != null)
+ hashCode ^= keys[i].hashCode();
+ }
+ }
+
+ return hashCode;
+ }
+
+ /**
+ * Identifies all the keys stored by this <code>MultiKey</code>.
+ *
+ **/
+
+ public String toString()
+ {
+ StringBuffer buffer;
+ int i;
+
+ buffer = new StringBuffer("MultiKey[");
+
+ for (i = 0; i < keys.length; i++)
+ {
+ if (i > 0)
+ buffer.append(", ");
+
+ if (keys[i] == null)
+ buffer.append("<null>");
+ else
+ buffer.append(keys[i]);
+ }
+
+ buffer.append(']');
+
+ return buffer.toString();
+ }
+
+ /**
+ * Writes a count of the keys, then writes each individual key.
+ *
+ **/
+
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ out.writeInt(keys.length);
+
+ for (int i = 0; i < keys.length; i++)
+ out.writeObject(keys[i]);
+ }
+
+ /**
+ * Reads the state previously written by {@link #writeExternal(ObjectOutput)}.
+ *
+ **/
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
+ {
+ int count;
+
+ count = in.readInt();
+ keys = new Object[count];
+
+ for (int i = 0; i < count; i++)
+ keys[i] = in.readObject();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/PropertyHolderPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/PropertyHolderPropertySource.java
new file mode 100644
index 0000000..ec1093f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/PropertyHolderPropertySource.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * Implements the {@link IPropertySource} interface
+ * for instances that implement {@link org.apache.tapestry.util.IPropertyHolder}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class PropertyHolderPropertySource implements IPropertySource
+{
+ private IPropertyHolder _holder;
+
+ public PropertyHolderPropertySource(IPropertyHolder holder)
+ {
+ _holder = holder;
+ }
+
+ public String getPropertyValue(String propertyName)
+ {
+ return _holder.getProperty(propertyName);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/RegexpMatcher.java b/tapestry-framework/src/org/apache/tapestry/util/RegexpMatcher.java
new file mode 100644
index 0000000..1716b9d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/RegexpMatcher.java
@@ -0,0 +1,123 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.PatternCompiler;
+import org.apache.oro.text.regex.PatternMatcher;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+import org.apache.tapestry.ApplicationRuntimeException;
+
+/**
+ * Streamlines the interface to ORO by implicitly constructing the
+ * necessary compilers and matchers, and by
+ * caching compiled patterns.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class RegexpMatcher
+{
+ private PatternCompiler _patternCompiler;
+
+ private PatternMatcher _matcher;
+
+ private Map _compiledPatterns = new HashMap();
+
+ private Map _escapedPatternStrings = new HashMap();
+
+ protected Pattern compilePattern(String pattern)
+ {
+ if (_patternCompiler == null)
+ _patternCompiler = new Perl5Compiler();
+
+ try
+ {
+ return _patternCompiler.compile(pattern, Perl5Compiler.SINGLELINE_MASK);
+ }
+ catch (MalformedPatternException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+ }
+
+ protected Pattern getCompiledPattern(String pattern)
+ {
+ Pattern result = (Pattern) _compiledPatterns.get(pattern);
+
+ if (result == null)
+ {
+ result = compilePattern(pattern);
+ _compiledPatterns.put(pattern, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Clears any previously compiled patterns.
+ *
+ **/
+
+ public void clear()
+ {
+ _compiledPatterns.clear();
+ }
+
+ protected PatternMatcher getPatternMatcher()
+ {
+ if (_matcher == null)
+ _matcher = new Perl5Matcher();
+
+ return _matcher;
+ }
+
+ public boolean matches(String pattern, String input)
+ {
+ Pattern compiledPattern = getCompiledPattern(pattern);
+
+ return getPatternMatcher().matches(input, compiledPattern);
+ }
+
+ public boolean contains(String pattern, String input)
+ {
+ Pattern compiledPattern = getCompiledPattern(pattern);
+
+ return getPatternMatcher().contains(input, compiledPattern);
+ }
+
+ public String getEscapedPatternString(String pattern)
+ {
+ String result = (String) _escapedPatternStrings.get(pattern);
+
+ if (result == null)
+ {
+ result = Perl5Compiler.quotemeta(pattern);
+
+ _escapedPatternStrings.put(pattern, result);
+ }
+
+ return result;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ResourceBundlePropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/ResourceBundlePropertySource.java
new file mode 100644
index 0000000..b98b637
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ResourceBundlePropertySource.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * A property source that is based on a {@link java.util.ResourceBundle}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ResourceBundlePropertySource implements IPropertySource
+{
+ private ResourceBundle _bundle;
+
+ public ResourceBundlePropertySource(ResourceBundle bundle)
+ {
+ _bundle = bundle;
+ }
+
+ /**
+ * Gets the value from the bundle by invoking
+ * {@link ResourceBundle#getString(java.lang.String)}. If
+ * the bundle does not contain the key (that is, it it
+ * throws {@link java.util.MissingResourceException}), then
+ * null is returned.
+ *
+ **/
+
+ public String getPropertyValue(String propertyName)
+ {
+ try
+ {
+ return _bundle.getString(propertyName);
+ }
+ catch (MissingResourceException ex)
+ {
+ return null;
+ }
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ServletContextPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/ServletContextPropertySource.java
new file mode 100644
index 0000000..1c6c768
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ServletContextPropertySource.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+/**
+ * Implementation of {@link IPropertySource}
+ * that returns values defined as ServletContext initialization parameters
+ * (defined as <code><init-param></code> in the
+ * <code>web.xml</code> deployment descriptor.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+import javax.servlet.ServletContext;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+public class ServletContextPropertySource implements IPropertySource
+{
+ private ServletContext _context;
+
+ public ServletContextPropertySource(ServletContext context)
+ {
+ _context = context;
+ }
+
+ /**
+ * Invokes {@link ServletContext#getInitParameter(java.lang.String)}.
+ *
+ **/
+
+ public String getPropertyValue(String propertyName)
+ {
+ return _context.getInitParameter(propertyName);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/ServletPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/ServletPropertySource.java
new file mode 100644
index 0000000..d5dca28
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/ServletPropertySource.java
@@ -0,0 +1,52 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import javax.servlet.ServletConfig;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * Implementation of {@link IPropertySource}
+ * that returns values defined as Servlet initialization parameters
+ * (defined as <code><init-param></code> in the
+ * <code>web.xml</code> deployment descriptor.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class ServletPropertySource implements IPropertySource
+{
+ private ServletConfig _config;
+
+ public ServletPropertySource(ServletConfig config)
+ {
+ _config = config;
+ }
+
+ /**
+ * Invokes {@link ServletConfig#getInitParameter(java.lang.String)}.
+ *
+ **/
+
+ public String getPropertyValue(String propertyName)
+ {
+ return _config.getInitParameter(propertyName);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/StringSplitter.java b/tapestry-framework/src/org/apache/tapestry/util/StringSplitter.java
new file mode 100644
index 0000000..7e725a8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/StringSplitter.java
@@ -0,0 +1,124 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+/**
+ * Used to split a string into substrings based on a single character
+ * delimiter. A fast, simple version of
+ * {@link java.util.StringTokenizer}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class StringSplitter
+{
+ private char delimiter;
+
+ public StringSplitter(char delimiter)
+ {
+ this.delimiter = delimiter;
+ }
+
+ public char getDelimiter()
+ {
+ return delimiter;
+ }
+
+ /**
+ * Splits a string on the delimter into an array of String
+ * tokens. The delimiters are not included in the tokens. Null
+ * tokens (caused by two consecutive delimiter) are reduced to an
+ * empty string. Leading delimiters are ignored.
+ *
+ **/
+
+ public String[] splitToArray(String value)
+ {
+ char[] buffer;
+ int i;
+ String[] result;
+ int resultCount = 0;
+ int start;
+ int length;
+ String token;
+ String[] newResult;
+ boolean first = true;
+
+ buffer = value.toCharArray();
+
+ result = new String[3];
+
+ start = 0;
+ length = 0;
+
+ for (i = 0; i < buffer.length; i++)
+ {
+ if (buffer[i] != delimiter)
+ {
+ length++;
+ continue;
+ }
+
+ // This is used to ignore leading delimiter(s).
+
+ if (length > 0 || !first)
+ {
+ token = new String(buffer, start, length);
+
+ if (resultCount == result.length)
+ {
+ newResult = new String[result.length * 2];
+
+ System.arraycopy(result, 0, newResult, 0, result.length);
+
+ result = newResult;
+ }
+
+ result[resultCount++] = token;
+
+ first = false;
+ }
+
+ start = i + 1;
+ length = 0;
+ }
+
+ // Special case: if the string contains no delimiters
+ // then it isn't really split. Wrap the input string
+ // in an array and return. This is a little optimization
+ // to prevent a new String instance from being
+ // created unnecessarily.
+
+ if (start == 0 && length == buffer.length)
+ {
+ result = new String[1];
+ result[0] = value;
+ return result;
+ }
+
+ // If the string is all delimiters, then this
+ // will result in a single empty token.
+
+ token = new String(buffer, start, length);
+
+ newResult = new String[resultCount + 1];
+ System.arraycopy(result, 0, newResult, 0, resultCount);
+ newResult[resultCount] = token;
+
+ return newResult;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/SystemPropertiesPropertySource.java b/tapestry-framework/src/org/apache/tapestry/util/SystemPropertiesPropertySource.java
new file mode 100644
index 0000000..315d271
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/SystemPropertiesPropertySource.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util;
+
+import org.apache.tapestry.engine.IPropertySource;
+
+/**
+ * Obtain properties from JVM system properties.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class SystemPropertiesPropertySource implements IPropertySource
+{
+ private static IPropertySource _shared;
+
+ public static synchronized IPropertySource getInstance()
+ {
+ if (_shared == null)
+ _shared = new SystemPropertiesPropertySource();
+
+ return _shared;
+ }
+
+ /**
+ * Delegates to {@link System#getProperty(java.lang.String)}.
+ *
+ **/
+
+ public String getPropertyValue(String propertyName)
+ {
+ return System.getProperty(propertyName);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionAnalyzer.java b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionAnalyzer.java
new file mode 100644
index 0000000..4056dc5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionAnalyzer.java
@@ -0,0 +1,435 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.exception;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Analyzes an exception, creating one or more
+ * {@link ExceptionDescription}s
+ * from it.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ExceptionAnalyzer
+{
+ private List exceptionDescriptions;
+ private List propertyDescriptions;
+ private CharArrayWriter writer;
+
+ private static final int LIST_SIZE = 3;
+
+ private boolean exhaustive = false;
+
+ /**
+ * If true, then stack trace is extracted for each exception. If false,
+ * the default, then stack trace is extracted for only the deepest exception.
+ *
+ **/
+
+ public boolean isExhaustive()
+ {
+ return exhaustive;
+ }
+
+ public void setExhaustive(boolean value)
+ {
+ exhaustive = value;
+ }
+
+ /**
+ * Analyzes the exceptions. This builds an {@link ExceptionDescription} for the
+ * exception. It also looks for a non-null {@link Throwable}
+ * property. If one exists, then a second {@link ExceptionDescription}
+ * is created. This continues until no more nested exceptions can be found.
+ *
+ * <p>The description includes a set of name/value properties
+ * (as {@link ExceptionProperty}) object. This list contains all
+ * non-null properties that are not, themselves, {@link Throwable}.
+ *
+ * <p>The name is the display name (not the logical name) of the property. The value
+ * is the <code>toString()</code> value of the property.
+ *
+ * Only properties defined in subclasses of {@link Throwable} are included.
+ *
+ * <p>A future enhancement will be to alphabetically sort the properties by name.
+ **/
+
+ public ExceptionDescription[] analyze(Throwable exception)
+ {
+ ExceptionDescription[] result;
+
+ if (writer == null)
+ writer = new CharArrayWriter();
+
+ if (propertyDescriptions == null)
+ propertyDescriptions = new ArrayList(LIST_SIZE);
+
+ if (exceptionDescriptions == null)
+ exceptionDescriptions = new ArrayList(LIST_SIZE);
+
+ while (exception != null)
+ {
+ exception = buildDescription(exception);
+ }
+
+ result = new ExceptionDescription[exceptionDescriptions.size()];
+ result = (ExceptionDescription[]) exceptionDescriptions.toArray(result);
+
+ exceptionDescriptions.clear();
+ propertyDescriptions.clear();
+
+ writer.reset();
+
+ // We never actually close() the writer which is bad ... I'm expecting that
+ // the finalize() method will close them, or that they don't need to
+ // close.
+
+ return result;
+ }
+
+ protected Throwable buildDescription(Throwable exception)
+ {
+ BeanInfo info;
+ Class exceptionClass;
+ ExceptionProperty property;
+ PropertyDescriptor[] descriptors;
+ PropertyDescriptor descriptor;
+ Throwable next = null;
+ int i;
+ Object value;
+ Method method;
+ ExceptionProperty[] properties;
+ ExceptionDescription description;
+ String stringValue;
+ String message;
+ String[] stackTrace = null;
+
+ propertyDescriptions.clear();
+
+ message = exception.getMessage();
+ exceptionClass = exception.getClass();
+
+ // Get properties, ignoring those in Throwable and higher
+ // (including the 'message' property).
+
+ try
+ {
+ info = Introspector.getBeanInfo(exceptionClass, Throwable.class);
+ }
+ catch (IntrospectionException e)
+ {
+ return null;
+ }
+
+ descriptors = info.getPropertyDescriptors();
+
+ for (i = 0; i < descriptors.length; i++)
+ {
+ descriptor = descriptors[i];
+
+ method = descriptor.getReadMethod();
+ if (method == null)
+ continue;
+
+ try
+ {
+ value = method.invoke(exception, null);
+ }
+ catch (Exception e)
+ {
+ continue;
+ }
+
+ if (value == null)
+ continue;
+
+ // Some annoying exceptions duplicate the message property
+ // (I'm talking to YOU SAXParseException), so just edit that out.
+
+ if (message != null && message.equals(value))
+ continue;
+
+ // Skip Throwables ... but the first non-null
+ // found is the next exception. We kind of count
+ // on there being no more than one Throwable
+ // property per Exception.
+
+ if (value instanceof Throwable)
+ {
+ if (next == null)
+ next = (Throwable) value;
+
+ continue;
+ }
+
+ stringValue = value.toString().trim();
+
+ if (stringValue.length() == 0)
+ continue;
+
+ property = new ExceptionProperty(descriptor.getDisplayName(), value.toString());
+
+ propertyDescriptions.add(property);
+ }
+
+ // If exhaustive, or in the deepest exception (where there's no next)
+ // the extract the stack trace.
+
+ if (next == null || exhaustive)
+ stackTrace = getStackTrace(exception);
+
+ // Would be nice to sort the properties here.
+
+ properties = new ExceptionProperty[propertyDescriptions.size()];
+
+ ExceptionProperty[] propArray =
+ (ExceptionProperty[]) propertyDescriptions.toArray(properties);
+
+ description =
+ new ExceptionDescription(
+ exceptionClass.getName(),
+ message,
+ propArray,
+ stackTrace);
+
+ exceptionDescriptions.add(description);
+
+ return next;
+ }
+
+ /**
+ * Gets the stack trace for the exception, and converts it into an array of strings.
+ *
+ * <p>This involves parsing the
+ * string generated indirectly from
+ * <code>Throwable.printStackTrace(PrintWriter)</code>. This method can get confused
+ * if the message (presumably, the first line emitted by printStackTrace())
+ * spans multiple lines.
+ *
+ * <p>Different JVMs format the exception in different ways.
+ *
+ * <p>A possible expansion would be more flexibility in defining the pattern
+ * used. Hopefully all 'mainstream' JVMs are close enough for this to continue
+ * working.
+ *
+ **/
+
+ protected String[] getStackTrace(Throwable exception)
+ {
+ writer.reset();
+
+ PrintWriter printWriter = new PrintWriter(writer);
+
+ exception.printStackTrace(printWriter);
+
+ printWriter.close();
+
+ String fullTrace = writer.toString();
+
+ writer.reset();
+
+ // OK, the trick is to convert the full trace into an array of stack frames.
+
+ StringReader stringReader = new StringReader(fullTrace);
+ LineNumberReader lineReader = new LineNumberReader(stringReader);
+ int lineNumber = 0;
+ List frames = new ArrayList();
+
+ try
+ {
+ while (true)
+ {
+ String line = lineReader.readLine();
+
+ if (line == null)
+ break;
+
+ // Always ignore the first line.
+
+ if (++lineNumber == 1)
+ continue;
+
+ frames.add(stripFrame(line));
+ }
+
+ lineReader.close();
+ }
+ catch (IOException ex)
+ {
+ // Not likely to happen with this particular set
+ // of readers.
+ }
+
+ String result[] = new String[frames.size()];
+
+ return (String[]) frames.toArray(result);
+ }
+
+ private static final int SKIP_LEADING_WHITESPACE = 0;
+ private static final int SKIP_T = 1;
+ private static final int SKIP_OTHER_WHITESPACE = 2;
+
+ /**
+ * Sun's JVM prefixes each line in the stack trace
+ * with "<tab>at ", other JVMs don't. This method
+ * looks for and strips such stuff.
+ *
+ **/
+
+ private String stripFrame(String frame)
+ {
+ char array[] = frame.toCharArray();
+
+ int i = 0;
+ int state = SKIP_LEADING_WHITESPACE;
+ boolean more = true;
+
+ while (more)
+ {
+ // Ran out of characters to skip? Return the empty string.
+
+ if (i == array.length)
+ return "";
+
+ char ch = array[i];
+
+ switch (state)
+ {
+ // Ignore whitespace at the start of the line.
+
+ case SKIP_LEADING_WHITESPACE :
+
+ if (Character.isWhitespace(ch))
+ {
+ i++;
+ continue;
+ }
+
+ if (ch == 'a')
+ {
+ state = SKIP_T;
+ i++;
+ continue;
+ }
+
+ // Found non-whitespace, not 'a'
+ more = false;
+ break;
+
+ // Skip over the 't' after an 'a'
+
+ case SKIP_T :
+
+ if (ch == 't')
+ {
+ state = SKIP_OTHER_WHITESPACE;
+ i++;
+ continue;
+ }
+
+ // Back out the skipped-over 'a'
+
+ i--;
+ more = false;
+ break;
+
+ // Skip whitespace between 'at' and the name of the class
+
+ case SKIP_OTHER_WHITESPACE :
+
+ if (Character.isWhitespace(ch))
+ {
+ i++;
+ continue;
+ }
+
+ // Not whitespace
+ more = false;
+ break;
+ }
+
+ }
+
+ // Found nothing to strip out.
+
+ if (i == 0)
+ return frame;
+
+ return frame.substring(i);
+ }
+
+ /**
+ * Produces a text based exception report to the provided stream.
+ *
+ **/
+
+ public void reportException(Throwable exception, PrintStream stream)
+ {
+ int i;
+ int j;
+ ExceptionDescription[] descriptions;
+ ExceptionProperty[] properties;
+ String[] stackTrace;
+ String message;
+
+ descriptions = analyze(exception);
+
+ for (i = 0; i < descriptions.length; i++)
+ {
+ message = descriptions[i].getMessage();
+
+ if (message == null)
+ stream.println(descriptions[i].getExceptionClassName());
+ else
+ stream.println(
+ descriptions[i].getExceptionClassName() + ": " + descriptions[i].getMessage());
+
+ properties = descriptions[i].getProperties();
+
+ for (j = 0; j < properties.length; j++)
+ stream.println(
+ " " + properties[j].getName() + ": " + properties[j].getValue());
+
+ // Just show the stack trace on the deepest exception.
+
+ if (i + 1 == descriptions.length)
+ {
+ stackTrace = descriptions[i].getStackTrace();
+
+ for (j = 0; j < stackTrace.length; j++)
+ stream.println(stackTrace[j]);
+ }
+ else
+ stream.println();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionDescription.java b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionDescription.java
new file mode 100644
index 0000000..d39f3b3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionDescription.java
@@ -0,0 +1,76 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.exception;
+
+import java.io.Serializable;
+
+/**
+ * A description of an <code>Exception</code>. This is useful when presenting an
+ * exception (in output or on a web page).
+ *
+ * <p>We capture all the information about an exception as
+ * Strings.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ExceptionDescription implements Serializable
+{
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = -4874930784340781514L;
+
+ private String exceptionClassName;
+ private String message;
+ private ExceptionProperty[] properties;
+ private String[] stackTrace;
+
+ public ExceptionDescription(
+ String exceptionClassName,
+ String message,
+ ExceptionProperty[] properties,
+ String[] stackTrace)
+ {
+ this.exceptionClassName = exceptionClassName;
+ this.message = message;
+ this.properties = properties;
+ this.stackTrace = stackTrace;
+ }
+
+ public String getExceptionClassName()
+ {
+ return exceptionClassName;
+ }
+
+ public String getMessage()
+ {
+ return message;
+ }
+
+ public ExceptionProperty[] getProperties()
+ {
+ return properties;
+ }
+
+ public String[] getStackTrace()
+ {
+ return stackTrace;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionProperty.java b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionProperty.java
new file mode 100644
index 0000000..7bcc484
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/exception/ExceptionProperty.java
@@ -0,0 +1,54 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.exception;
+
+import java.io.Serializable;
+
+/**
+ * Captures a name/value property pair from an exception. Part of
+ * an {@link ExceptionDescription}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ExceptionProperty implements Serializable
+{
+ /**
+ * @since 2.0.4
+ *
+ **/
+
+ private static final long serialVersionUID = -4598312382467505134L;
+ private String name;
+ private String value;
+
+ public ExceptionProperty(String name, String value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/exception/package.html b/tapestry-framework/src/org/apache/tapestry/util/exception/package.html
new file mode 100644
index 0000000..d40c47b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/exception/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>A basic framework for analyzing a reporting exceptions. The
+{@link org.apache.tapestry.util.exception.ExceptionAnalyzer} class will identify
+the type, message and other properties of an exception, and understands about nested
+exceptions.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/BinaryDumpOutputStream.java b/tapestry-framework/src/org/apache/tapestry/util/io/BinaryDumpOutputStream.java
new file mode 100644
index 0000000..8fbd5a4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/BinaryDumpOutputStream.java
@@ -0,0 +1,308 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * A kind of super-formatter. It is sent a stream of binary data and
+ * formats it in a human-readable dump format which is forwarded to
+ * its output stream.
+ *
+ * <p>Currently, output is in hex though options to change that may
+ * be introduced.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class BinaryDumpOutputStream extends OutputStream
+{
+ private PrintWriter out;
+
+ private boolean locked = false;
+
+ private boolean showOffset = true;
+ private int bytesPerLine = 16;
+ private int spacingInterval = 4;
+ private char substituteChar = '.';
+ private String offsetSeperator = ": ";
+ private int offset = 0;
+ private int lineCount = 0;
+ private int bytesSinceSpace = 0;
+ private char[] ascii = null;
+ private boolean showAscii = true;
+ private String asciiBegin = " |";
+ private String asciiEnd = "|";
+
+ private static final char[] HEX =
+ {
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f' };
+
+ /**
+ * Creates a <code>PrintWriter</code> for <code>System.out</code>.
+ *
+ **/
+
+ public BinaryDumpOutputStream()
+ {
+ this(new PrintWriter(System.out, true));
+ }
+
+ public BinaryDumpOutputStream(PrintWriter out)
+ {
+ this.out = out;
+ }
+
+ public BinaryDumpOutputStream(Writer out)
+ {
+ this.out = new PrintWriter(out);
+ }
+
+ public void close() throws IOException
+ {
+ if (out != null)
+ {
+ if (lineCount > 0)
+ finishFinalLine();
+
+ out.close();
+ }
+
+ out = null;
+ }
+
+ private void finishFinalLine()
+ {
+ // Since we only finish the final line after at least one byte has
+ // been written to it, we don't need to worry about
+ // the offset.
+
+ while (lineCount < bytesPerLine)
+ {
+ // After every <n> bytes, emit a space.
+
+ if (spacingInterval > 0 && bytesSinceSpace == spacingInterval)
+ {
+ out.print(' ');
+ bytesSinceSpace = 0;
+ }
+
+ // Two spaces to substitute for the two hex digits.
+
+ out.print(" ");
+
+ if (showAscii)
+ ascii[lineCount] = ' ';
+
+ lineCount++;
+ bytesSinceSpace++;
+ }
+
+ if (showAscii)
+ {
+ out.print(asciiBegin);
+ out.print(ascii);
+ out.print(asciiEnd);
+ }
+
+ out.println();
+ }
+
+ /**
+ * Forwards the <code>flush()</code> to the <code>PrintWriter</code>.
+ *
+ **/
+
+ public void flush() throws IOException
+ {
+ out.flush();
+ }
+
+ public String getAsciiBegin()
+ {
+ return asciiBegin;
+ }
+
+ public String getAsciiEnd()
+ {
+ return asciiEnd;
+ }
+
+ public int getBytesPerLine()
+ {
+ return bytesPerLine;
+ }
+
+ public String getOffsetSeperator()
+ {
+ return offsetSeperator;
+ }
+
+ public boolean getShowAscii()
+ {
+ return showAscii;
+ }
+
+ public char getSubstituteChar()
+ {
+ return substituteChar;
+ }
+
+ public void setAsciiBegin(String value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ asciiBegin = value;
+ }
+
+ public void setAsciiEnd(String value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ asciiEnd = value;
+ }
+
+ public void setBytesPerLine(int value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ bytesPerLine = value;
+
+ ascii = null;
+ }
+
+ public void setOffsetSeperator(String value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ offsetSeperator = value;
+ }
+
+ public void setShowAscii(boolean value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ showAscii = value;
+ }
+
+ /**
+ * Sets the character used in the ASCII dump that substitutes for characters
+ * outside the range of 32..126.
+ *
+ **/
+
+ public void setSubstituteChar(char value)
+ {
+ if (locked)
+ throw new IllegalStateException();
+
+ substituteChar = value;
+ }
+
+ public void write(int b) throws IOException
+ {
+ char letter;
+
+ if (showAscii && ascii == null)
+ ascii = new char[bytesPerLine];
+
+ // Prevent further customization after output starts being written.
+
+ locked = true;
+
+ if (lineCount == bytesPerLine)
+ {
+ if (showAscii)
+ {
+ out.print(asciiBegin);
+ out.print(ascii);
+ out.print(asciiEnd);
+ }
+
+ out.println();
+
+ bytesSinceSpace = 0;
+ lineCount = 0;
+ offset += bytesPerLine;
+ }
+
+ if (lineCount == 0 && showOffset)
+ {
+ writeHex(offset, 4);
+ out.print(offsetSeperator);
+ }
+
+ // After every <n> bytes, emit a space.
+
+ if (spacingInterval > 0 && bytesSinceSpace == spacingInterval)
+ {
+ out.print(' ');
+ bytesSinceSpace = 0;
+ }
+
+ writeHex(b, 2);
+
+ if (showAscii)
+ {
+ if (b < 32 | b > 127)
+ letter = substituteChar;
+ else
+ letter = (char) b;
+
+ ascii[lineCount] = letter;
+ }
+
+ lineCount++;
+ bytesSinceSpace++;
+ }
+
+ private void writeHex(int value, int digits)
+ {
+ int i;
+ int nybble;
+
+ for (i = 0; i < digits; i++)
+ {
+ nybble = (value >> 4 * (digits - i - 1)) & 0x0f;
+
+ out.print(HEX[nybble]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/BooleanAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/BooleanAdaptor.java
new file mode 100644
index 0000000..ba397f1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/BooleanAdaptor.java
@@ -0,0 +1,70 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Boolean}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class BooleanAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "TF";
+
+ /**
+ * Registers using the prefixes 'T' and 'F' (for TRUE and FALSE).
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Boolean.class, this);
+ }
+
+ /**
+ * Squeezes the {@link Boolean} data to either 'T' or 'F'.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ Boolean bool = (Boolean) data;
+
+ if (bool.booleanValue())
+ return "T";
+ else
+ return "F";
+ }
+
+ /**
+ * Unsqueezes the string to either {@link Boolean#TRUE} or {@link Boolean#FALSE},
+ * depending on the prefix character.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ char ch = string.charAt(0);
+
+ if (ch == 'T')
+ return Boolean.TRUE;
+
+ return Boolean.FALSE;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/ByteAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/ByteAdaptor.java
new file mode 100644
index 0000000..d6cf466
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/ByteAdaptor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Byte}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class ByteAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "b";
+
+ /**
+ * Registers using the prefix 'b'.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Byte.class, this);
+ }
+
+ /**
+ * Invoked <code>toString()</code> on data (which is type {@link Byte}),
+ * and prefixs the result.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return PREFIX + data.toString();
+ }
+
+ /**
+ * Constructs an {@link Byte} from the string, after stripping
+ * the prefix.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Byte(string.substring(1));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/CharacterAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/CharacterAdaptor.java
new file mode 100644
index 0000000..0703a0f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/CharacterAdaptor.java
@@ -0,0 +1,56 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+
+/**
+ * Squeezes a Character.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class CharacterAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "c";
+ private static final char PREFIX_CH = 'c';
+
+ public String squeeze(DataSqueezer squeezer, Object data) throws IOException
+ {
+ Character charData = (Character)data;
+ char value = charData.charValue();
+
+ char[] buffer = new char[]
+ {
+ PREFIX_CH, value
+ };
+
+ return new String(buffer);
+ }
+
+ public Object unsqueeze(DataSqueezer squeezer, String string) throws IOException
+ {
+ return new Character(string.charAt(1));
+ }
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Character.class, this);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/ComponentAddressAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/ComponentAddressAdaptor.java
new file mode 100644
index 0000000..8b0ac52
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/ComponentAddressAdaptor.java
@@ -0,0 +1,67 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.ComponentAddress;
+
+/**
+ * Squeezes a org.apache.tapestry.ComponentAddress.
+ *
+ * @author mindbridge
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class ComponentAddressAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "A";
+ private static final char SEPARATOR = '/';
+
+ public String squeeze(DataSqueezer squeezer, Object data) throws IOException
+ {
+ ComponentAddress address = (ComponentAddress) data;
+
+ // a 'null' id path is encoded as an empty string
+ String idPath = address.getIdPath();
+ if (idPath == null)
+ idPath = "";
+
+ return PREFIX + address.getPageName() + SEPARATOR + idPath;
+ }
+
+ public Object unsqueeze(DataSqueezer squeezer, String string) throws IOException
+ {
+ int separator = string.indexOf(SEPARATOR);
+ if (separator < 0)
+ throw new IOException(Tapestry.getMessage("ComponentAddressAdaptor.no-separator"));
+
+ String pageName = string.substring(1, separator);
+ String idPath = string.substring(separator + 1);
+ if (idPath.equals(""))
+ idPath = null;
+
+ return new ComponentAddress(pageName, idPath);
+ }
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, ComponentAddress.class, this);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/DataSqueezer.java b/tapestry-framework/src/org/apache/tapestry/util/io/DataSqueezer.java
new file mode 100644
index 0000000..1bab14b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/DataSqueezer.java
@@ -0,0 +1,299 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+
+import org.apache.tapestry.IResourceResolver;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.AdaptorRegistry;
+
+/**
+ * A class used to convert arbitrary objects to Strings and back.
+ * This has particular uses involving HTTP URLs and Cookies.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class DataSqueezer
+{
+ private static final String NULL_PREFIX = "X";
+ private static final char NULL_PREFIX_CH = 'X';
+
+ private static final int ARRAY_SIZE = 90;
+ private static final int FIRST_ADAPTOR_OFFSET = 33;
+
+ /**
+ * An array of adaptors; this is used as a cheap lookup-table when unsqueezing.
+ * Each adaptor is identified by a single ASCII character, in the range of
+ * 33 ('!') to 122 (the letter 'z'). The offset into this table
+ * is the character minus 33.
+ *
+ **/
+
+ private ISqueezeAdaptor[] _adaptorByPrefix = new ISqueezeAdaptor[ARRAY_SIZE];
+
+ /**
+ * AdaptorRegistry cache of adaptors.
+ *
+ **/
+
+ private AdaptorRegistry _adaptors = new AdaptorRegistry();
+
+ /**
+ * Resource resolver used to deserialize classes.
+ *
+ **/
+
+ private IResourceResolver _resolver;
+
+ /**
+ * Creates a new squeezer with the default set of adaptors.
+ *
+ **/
+
+ public DataSqueezer(IResourceResolver resolver)
+ {
+ this(resolver, null);
+ }
+
+ /**
+ * Creates a new data squeezer, which will have the default set of
+ * adaptors, and may add additional adaptors.
+ *
+ * @param adaptors an optional list of adaptors that will be registered to
+ * the data squeezer (it may be null or empty)
+ *
+ **/
+
+ public DataSqueezer(IResourceResolver resolver, ISqueezeAdaptor[] adaptors)
+ {
+ _resolver = resolver;
+
+ registerDefaultAdaptors();
+
+ if (adaptors != null)
+ for (int i = 0; i < adaptors.length; i++)
+ adaptors[i].register(this);
+ }
+
+ private void registerDefaultAdaptors()
+ {
+ new CharacterAdaptor().register(this);
+ new StringAdaptor().register(this);
+ new IntegerAdaptor().register(this);
+ new DoubleAdaptor().register(this);
+ new ByteAdaptor().register(this);
+ new FloatAdaptor().register(this);
+ new LongAdaptor().register(this);
+ new ShortAdaptor().register(this);
+ new BooleanAdaptor().register(this);
+ new SerializableAdaptor().register(this);
+ new ComponentAddressAdaptor().register(this);
+ new EnumAdaptor().register(this);
+ }
+
+ /**
+ * Registers the adaptor with one or more single-character prefixes.
+ *
+ * @param prefix one or more characters, each of which will be a prefix for
+ * the adaptor.
+ * @param dataClass the class (or interface) which can be encoded by the adaptor.
+ * @param adaptor the adaptor which to be registered.
+ *
+ **/
+
+ public synchronized void register(String prefix, Class dataClass, ISqueezeAdaptor adaptor)
+ {
+ int prefixLength = prefix.length();
+ int offset;
+
+ if (prefixLength < 1)
+ throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.short-prefix"));
+
+ if (dataClass == null)
+ throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.null-class"));
+
+ if (adaptor == null)
+ throw new IllegalArgumentException(Tapestry.getMessage("DataSqueezer.null-adaptor"));
+
+ for (int i = 0; i < prefixLength; i++)
+ {
+ char ch = prefix.charAt(i);
+
+ if (ch < '!' | ch > 'z')
+ throw new IllegalArgumentException(
+ Tapestry.getMessage("DataSqueezer.prefix-out-of-range"));
+
+ offset = ch - FIRST_ADAPTOR_OFFSET;
+
+ if (_adaptorByPrefix[offset] != null)
+ throw new IllegalArgumentException(
+ Tapestry.format(
+ "DataSqueezer.adaptor-prefix-taken",
+ prefix.substring(i, i)));
+
+ _adaptorByPrefix[offset] = adaptor;
+
+ }
+
+ _adaptors.register(dataClass, adaptor);
+ }
+
+ /**
+ * Squeezes the data object into a String by locating an appropriate
+ * adaptor that can perform the conversion. data may be null.
+ *
+ **/
+
+ public String squeeze(Object data) throws IOException
+ {
+ ISqueezeAdaptor adaptor;
+
+ if (data == null)
+ return NULL_PREFIX;
+
+ adaptor = (ISqueezeAdaptor) _adaptors.getAdaptor(data.getClass());
+
+ return adaptor.squeeze(this, data);
+ }
+
+ /**
+ * A convience; invokes {@link #squeeze(Object)} for each element in the
+ * data array. If data is null, returns null.
+ *
+ **/
+
+ public String[] squeeze(Object[] data) throws IOException
+ {
+ if (data == null)
+ return null;
+
+ int length = data.length;
+ String[] result;
+
+ result = new String[length];
+
+ for (int i = 0; i < length; i++)
+ result[i] = squeeze(data[i]);
+
+ return result;
+ }
+
+ /**
+ * Unsqueezes the string. Note that in a special case, where the first
+ * character of the string is not a recognized prefix, it is assumed
+ * that the string is simply a string, and return with no
+ * change.
+ *
+ **/
+
+ public Object unsqueeze(String string) throws IOException
+ {
+ ISqueezeAdaptor adaptor = null;
+
+ if (string.equals(NULL_PREFIX))
+ return null;
+
+ int offset = string.charAt(0) - FIRST_ADAPTOR_OFFSET;
+
+ if (offset >= 0 && offset < _adaptorByPrefix.length)
+ adaptor = _adaptorByPrefix[offset];
+
+ // If the adaptor is not otherwise recognized, the it is simply
+ // an encoded String (the StringAdaptor may not have added
+ // a prefix).
+
+ if (adaptor == null)
+ return string;
+
+ // Adaptor should never be null, because we always supply
+ // an adaptor for String
+
+ return adaptor.unsqueeze(this, string);
+ }
+
+ /**
+ * Convienience method for unsqueezing many strings (back into objects).
+ *
+ * <p>If strings is null, returns null.
+ *
+ **/
+
+ public Object[] unsqueeze(String[] strings) throws IOException
+ {
+ if (strings == null)
+ return null;
+
+ int length = strings.length;
+ Object[] result;
+
+ result = new Object[length];
+
+ for (int i = 0; i < length; i++)
+ result[i] = unsqueeze(strings[i]);
+
+ return result;
+ }
+
+ /**
+ * Checks to see if a given prefix character has a registered
+ * adaptor. This is used by the String adaptor to
+ * determine whether it needs to put a prefix on its String.
+ *
+ **/
+
+ public boolean isPrefixRegistered(char prefix)
+ {
+ int offset = prefix - FIRST_ADAPTOR_OFFSET;
+
+ // Special case for handling nulls.
+
+ if (prefix == NULL_PREFIX_CH)
+ return true;
+
+ if (offset < 0 || offset >= _adaptorByPrefix.length)
+ return false;
+
+ return _adaptorByPrefix[offset] != null;
+ }
+
+ public String toString()
+ {
+ StringBuffer buffer;
+
+ buffer = new StringBuffer();
+ buffer.append("DataSqueezer[adaptors=<");
+ buffer.append(_adaptors.toString());
+ buffer.append(">]");
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the resource resolver used with this squeezer.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public IResourceResolver getResolver()
+ {
+ return _resolver;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/DoubleAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/DoubleAdaptor.java
new file mode 100644
index 0000000..fe55923
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/DoubleAdaptor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Double}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class DoubleAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "d";
+
+ /**
+ * Registers using the prefix 'd'.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Double.class, this);
+ }
+
+ /**
+ * Invoked <code>toString()</code> on data (which is type {@link Double}),
+ * and prefixs the result.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return PREFIX + data.toString();
+ }
+
+ /**
+ * Constructs an {@link Double} from the string, after stripping
+ * the prefix.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Double(string.substring(1));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/EnumAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/EnumAdaptor.java
new file mode 100644
index 0000000..63bbbeb
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/EnumAdaptor.java
@@ -0,0 +1,65 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+
+import org.apache.commons.lang.enum.Enum;
+import org.apache.commons.lang.enum.EnumUtils;
+
+/**
+ * Adaptor for {@link Enum} classes.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class EnumAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "E";
+ private static final char SEPARATOR = '@';
+
+ public String squeeze(DataSqueezer squeezer, Object o) throws IOException
+ {
+ Enum e = (Enum) o;
+ return PREFIX + e.getClass().getName() + SEPARATOR + e.getName();
+ }
+
+ public Object unsqueeze(DataSqueezer squeezer, String str) throws IOException
+ {
+ int pos = str.indexOf(SEPARATOR);
+
+ String className = str.substring(1, pos);
+ String name = str.substring(pos + 1, str.length());
+
+ Class enumClass = squeezer.getResolver().findClass(className);
+
+ try
+ {
+ return EnumUtils.getEnum(enumClass, name);
+ }
+ catch (IllegalArgumentException ex)
+ {
+ throw new IOException(ex.getMessage());
+ }
+ }
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Enum.class, this);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/FloatAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/FloatAdaptor.java
new file mode 100644
index 0000000..420e629
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/FloatAdaptor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Float}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class FloatAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "f";
+
+ /**
+ * Registers using the prefix 'f'.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Float.class, this);
+ }
+
+ /**
+ * Invoked <code>toString()</code> on data (which is type {@link Float}),
+ * and prefixs the result.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return PREFIX + data.toString();
+ }
+
+ /**
+ * Constructs a {@link Float} from the string, after stripping
+ * the prefix.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Float(string.substring(1));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/ISqueezeAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/ISqueezeAdaptor.java
new file mode 100644
index 0000000..3ef6014
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/ISqueezeAdaptor.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+
+/**
+ * Interface which defines a class used to convert data for a specific
+ * Java type into a String format (squeeze it),
+ * or convert from a String back into a Java type (unsqueeze).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public interface ISqueezeAdaptor
+{
+ /**
+ * Converts the data object into a String.
+ *
+ * @throws IOException if the object can't be converted.
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data) throws IOException;
+
+ /**
+ * Converts a String back into an appropriate object.
+ *
+ * @throws IOException if the String can't be converted.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ throws IOException;
+
+ /**
+ * Invoked to ask an adaptor to register itself to the squeezer.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/IntegerAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/IntegerAdaptor.java
new file mode 100644
index 0000000..1b1fe82
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/IntegerAdaptor.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Integer}. This adaptor claims all the digits as prefix
+ * characters, so its the very simplest conversion of all!
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class IntegerAdaptor implements ISqueezeAdaptor
+{
+ /**
+ * Registers this adaptor using all nine digits and the minus sign.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register("-0123456789", Integer.class, this);
+ }
+
+ /**
+ * Simply invokes <code>toString()</code> on the data,
+ * which is actually type {@link Integer}.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return data.toString();
+ }
+
+ /**
+ * Constructs an {@link Integer} from the string.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Integer(string);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/LongAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/LongAdaptor.java
new file mode 100644
index 0000000..7e116a7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/LongAdaptor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Long}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class LongAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "l";
+
+ /**
+ * Registers using the prefix 'l'.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Long.class, this);
+ }
+
+ /**
+ * Invoked <code>toString()</code> on data (which is type {@link Long}),
+ * and prefixs the result.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return PREFIX + data.toString();
+ }
+
+ /**
+ * Constructs a {@link Long} from the string, after stripping
+ * the prefix.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Long(string.substring(1));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/ResolvingObjectInputStream.java b/tapestry-framework/src/org/apache/tapestry/util/io/ResolvingObjectInputStream.java
new file mode 100644
index 0000000..4c8f20e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/ResolvingObjectInputStream.java
@@ -0,0 +1,57 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+import org.apache.tapestry.IResourceResolver;
+
+/**
+ * Specialized subclass of {@link java.io.ObjectInputStream}
+ * that knows how to resolve classes with a non-default
+ * class loader (represented by an instance of
+ * {@link org.apache.tapestry.IResourceResolver}).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class ResolvingObjectInputStream extends ObjectInputStream
+{
+ private IResourceResolver _resolver;
+
+ public ResolvingObjectInputStream(IResourceResolver resolver, InputStream input) throws IOException
+ {
+ super(input);
+
+ _resolver = resolver;
+ }
+
+ /**
+ * Overrides the default implementation to
+ * have the resource resolver find the class.
+ *
+ **/
+
+ protected Class resolveClass(ObjectStreamClass v) throws IOException, ClassNotFoundException
+ {
+ return _resolver.findClass(v.getName());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/SerializableAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/SerializableAdaptor.java
new file mode 100644
index 0000000..986596e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/SerializableAdaptor.java
@@ -0,0 +1,283 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The most complicated of the adaptors, this one takes an arbitrary serializable
+ * object, serializes it to binary, and encodes it in a Base64 encoding.
+ *
+ * <p>Encoding and decoding of Base64 strings uses code adapted from work in the public
+ * domain originally written by Jonathan Knudsen and published in
+ * O'reilly's "Java Cryptography". Note that we use a <em>modified</em> form of Base64 encoding,
+ * with URL-safe characters to encode the 62 and 63 values and the pad character.
+ *
+ * <p>TBD: Work out some class loader issues involved in deserializing.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class SerializableAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "O";
+
+ /**
+ * The PAD character, appended to the end of the string to make things
+ * line up. In normal Base64, this is the character '='.
+ *
+ **/
+
+ private static final char PAD = '.';
+
+ /**
+ * Representation for the 6-bit code 63, normally '+' in Base64.
+ *
+ **/
+
+ private static final char CH_62 = '-';
+
+ /**
+ * Representation for the 6-bit code 64, normally '/' in Base64.
+ *
+ **/
+
+ private static final char CH_63 = '_';
+
+ public String squeeze(DataSqueezer squeezer, Object data) throws IOException
+ {
+ ByteArrayOutputStream bos = null;
+ GZIPOutputStream gos = null;
+ ObjectOutputStream oos = null;
+ byte[] byteData = null;
+
+ try
+ {
+ bos = new ByteArrayOutputStream();
+ gos = new GZIPOutputStream(bos);
+ oos = new ObjectOutputStream(gos);
+
+ oos.writeObject(data);
+ oos.close();
+ }
+ finally
+ {
+ close(oos);
+ close(gos);
+ close(bos);
+ }
+
+ byteData = bos.toByteArray();
+
+ StringBuffer encoded = new StringBuffer(2 * byteData.length);
+ char[] base64 = new char[4];
+
+ encoded.append(PREFIX);
+
+ for (int i = 0; i < byteData.length; i += 3)
+ {
+ encodeBlock(byteData, i, base64);
+ encoded.append(base64);
+ }
+
+ return encoded.toString();
+ }
+
+ private void close(OutputStream stream)
+ {
+ if (stream != null)
+ {
+ try
+ {
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+
+ private void close(InputStream stream)
+ {
+ if (stream != null)
+ {
+ try
+ {
+ stream.close();
+ }
+ catch (IOException ex)
+ {
+ // Ignore.
+ }
+ }
+ }
+ public Object unsqueeze(DataSqueezer squeezer, String string) throws IOException
+ {
+ ByteArrayInputStream bis = null;
+ GZIPInputStream gis = null;
+ ObjectInputStream ois = null;
+ byte[] byteData;
+
+ // Strip off the first character and decode the rest.
+
+ byteData = decode(string.substring(1));
+
+ try
+ {
+ bis = new ByteArrayInputStream(byteData);
+ gis = new GZIPInputStream(bis);
+ ois = new ResolvingObjectInputStream(squeezer.getResolver(), gis);
+
+ return ois.readObject();
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // The message is the name of the class.
+
+ throw new IOException(
+ Tapestry.format("SerializableAdaptor.class-not-found", ex.getMessage()));
+ }
+ finally
+ {
+ close(ois);
+ close(gis);
+ close(bis);
+ }
+ }
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Serializable.class, this);
+ }
+
+ private static void encodeBlock(byte[] raw, int offset, char[] base64) throws IOException
+ {
+ int block = 0;
+ int slack = raw.length - offset - 1;
+ int end = (slack >= 2) ? 2 : slack;
+
+ for (int i = 0; i <= end; i++)
+ {
+ byte b = raw[offset + i];
+ int neuter = (b < 0) ? b + 256 : b;
+ block += neuter << (8 * (2 - i));
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ int sixbit = (block >>> (6 * (3 - i))) & 0x3f;
+ base64[i] = getChar(sixbit);
+ }
+
+ if (slack < 1)
+ base64[2] = PAD;
+
+ if (slack < 2)
+ base64[3] = PAD;
+ }
+
+ protected static char getChar(int sixBit) throws IOException
+ {
+ if (sixBit >= 0 && sixBit <= 25)
+ return (char) ('A' + sixBit);
+
+ if (sixBit >= 26 && sixBit <= 51)
+ return (char) ('a' + (sixBit - 26));
+
+ if (sixBit >= 52 && sixBit <= 61)
+ return (char) ('0' + (sixBit - 52));
+
+ if (sixBit == 62)
+ return CH_62;
+
+ if (sixBit == 63)
+ return CH_63;
+
+ throw new IOException(
+ Tapestry.format("SerializableAdaptor.unable-to-convert", Integer.toString(sixBit)));
+ }
+
+ public static byte[] decode(String string) throws IOException
+ {
+ int pad = 0;
+ char[] base64 = string.toCharArray();
+
+ for (int i = base64.length - 1; base64[i] == PAD; i--)
+ pad++;
+
+ int length = base64.length * 6 / 8 - pad;
+ byte[] raw = new byte[length];
+ int rawIndex = 0;
+
+ for (int i = 0; i < base64.length; i += 4)
+ {
+ int block =
+ (getValue(base64[i]) << 18)
+ + (getValue(base64[i + 1]) << 12)
+ + (getValue(base64[i + 2]) << 6)
+ + (getValue(base64[i + 3]));
+
+ for (int j = 0; j < 3 && rawIndex + j < raw.length; j++)
+ raw[rawIndex + j] = (byte) ((block >> (8 * (2 - j))) & 0xff);
+
+ rawIndex += 3;
+ }
+
+ return raw;
+ }
+
+ private static int getValue(char c) throws IOException
+ {
+ if (c >= 'A' && c <= 'Z')
+ return c - 'A';
+
+ if (c >= 'a' && c <= 'z')
+ return c - 'a' + 26;
+
+ if (c >= '0' && c <= '9')
+ return c - '0' + 52;
+
+ if (c == CH_62)
+ return 62;
+
+ if (c == CH_63)
+ return 63;
+
+ // Pad character
+
+ if (c == PAD)
+ return 0;
+
+ throw new IOException(
+ Tapestry.format(
+ "SerializableAdaptor.unable-to-interpret-char",
+ new String(new char[] { c })));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/ShortAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/ShortAdaptor.java
new file mode 100644
index 0000000..8f7a1b4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/ShortAdaptor.java
@@ -0,0 +1,61 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a {@link Short}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class ShortAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "s";
+
+ /**
+ * Registers using the prefix 's'.
+ *
+ **/
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, Short.class, this);
+ }
+
+ /**
+ * Invoked <code>toString()</code> on data (which is type {@link Short}),
+ * and prefixs the result.
+ *
+ **/
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ return PREFIX + data.toString();
+ }
+
+ /**
+ * Constructs a {@link Short} from the string, after stripping
+ * the prefix.
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ return new Short(string.substring(1));
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/StringAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/io/StringAdaptor.java
new file mode 100644
index 0000000..c7e6f03
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/StringAdaptor.java
@@ -0,0 +1,55 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.io;
+
+/**
+ * Squeezes a String (which is pretty simple, most of the time).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class StringAdaptor implements ISqueezeAdaptor
+{
+ private static final String PREFIX = "S";
+
+ public void register(DataSqueezer squeezer)
+ {
+ squeezer.register(PREFIX, String.class, this);
+ }
+
+ public String squeeze(DataSqueezer squeezer, Object data)
+ {
+ String string = (String) data;
+
+ return PREFIX + string;
+ }
+
+ /**
+ * Strips the prefix from the string. This method is only
+ * invoked by the {@link DataSqueezer} if the string leads
+ * with its normal prefix (an 'S').
+ *
+ **/
+
+ public Object unsqueeze(DataSqueezer squeezer, String string)
+ {
+ if (string.length() == 1)
+ return "";
+
+ return string.substring(1);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/io/package.html b/tapestry-framework/src/org/apache/tapestry/util/io/package.html
new file mode 100644
index 0000000..2f4e8be
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/io/package.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+
+<body>
+
+<p>Some interesting I/O classes. {@link org.apache.tapestry.util.io.BinaryDumpOutputStream}
+formats a stream of bytes into a human readable presentation, much like
+the Unix command line tool <code>od</code>.
+
+<p>{@link org.apache.tapestry.util.io.DataSqueezer} is used to squeeze and unsqueeze
+basic scalar types, Strings and serializable objects into a String format. The eventual
+purpose is to safely encode information into URLs or as HTTP Cookies.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/package.html b/tapestry-framework/src/org/apache/tapestry/util/package.html
new file mode 100644
index 0000000..a533dac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/package.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+<body>
+
+<p>A general set of resuable classes and utilities for creating Internet and XML applications.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/DefaultPoolableAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/pool/DefaultPoolableAdaptor.java
new file mode 100644
index 0000000..66ee7c4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/DefaultPoolableAdaptor.java
@@ -0,0 +1,44 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * Implementation for objects that implement
+ * the {@link org.apache.tapestry.util.pool.IPoolable} interface.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class DefaultPoolableAdaptor implements IPoolableAdaptor
+{
+
+ public void resetForPool(Object object)
+ {
+ IPoolable poolable = (IPoolable)object;
+
+ poolable.resetForPool();
+ }
+
+ public void discardFromPool(Object object)
+ {
+ IPoolable poolable = (IPoolable)object;
+
+ poolable.discardFromPool();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolable.java b/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolable.java
new file mode 100644
index 0000000..cfd1da4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolable.java
@@ -0,0 +1,48 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * Marks an object as being aware that is to be stored into a {@link Pool}.
+ * This gives the object a last chance to reset any state.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.4
+ *
+ **/
+
+public interface IPoolable
+{
+ /**
+ * Invoked by a {@link org.apache.tapestry.util.pool.Pool}
+ * just before the object is added to the pool.
+ * The object should return its state to how it was when freshly instantiated
+ * (or at least, its state should be indistinguishable from a freshly
+ * instantiated instance).
+ *
+ **/
+
+ public void resetForPool();
+
+ /**
+ * Invoked just as a Pool discards an object (for lack of use).
+ * This allows a last chance to perform final cleanup
+ * on the object while it is still referencable.
+ *
+ **/
+
+ public void discardFromPool();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolableAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolableAdaptor.java
new file mode 100644
index 0000000..e93ee70
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/IPoolableAdaptor.java
@@ -0,0 +1,44 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * Defines methods that define an adaptor to provide
+ * {@link org.apache.tapestry.util.pool.IPoolable}
+ * type behavior to arbitrary objects.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public interface IPoolableAdaptor
+{
+ /**
+ * Invoked just as an object is returned to the pool; this
+ * allows it to reset any state back to newly initialized.
+ *
+ **/
+
+ public void resetForPool(Object object);
+
+ /**
+ * Invoked when a pooled object is discarded from the pool.
+ *
+ **/
+
+ public void discardFromPool(Object object);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/NullPoolableAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/pool/NullPoolableAdaptor.java
new file mode 100644
index 0000000..ae439e2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/NullPoolableAdaptor.java
@@ -0,0 +1,38 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * A default, empty implementation, for objects that
+ * have no special behavior related to being pooled.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class NullPoolableAdaptor implements IPoolableAdaptor
+{
+
+ public void resetForPool(Object object)
+ {
+ }
+
+ public void discardFromPool(Object object)
+ {
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/Pool.java b/tapestry-framework/src/org/apache/tapestry/util/pool/Pool.java
new file mode 100644
index 0000000..7d96ea2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/Pool.java
@@ -0,0 +1,516 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.AdaptorRegistry;
+import org.apache.tapestry.util.ICleanable;
+import org.apache.tapestry.util.IRenderDescription;
+import org.apache.tapestry.util.JanitorThread;
+
+/**
+ * A Pool is used to pool instances of a useful class. It uses
+ * keys, much like a {@link Map}, to identify a list of pooled objects.
+ * Retrieving an object from the Pool atomically removes it from the
+ * pool. It can then be stored again later. In this way, a single
+ * Pool instance can manage many different types of pooled objects,
+ * filed under different keys.
+ *
+ * <p>
+ * Unlike traditional Pools, this class does not create new instances of
+ * the objects it stores (with the exception of simple Java Beans,
+ * via {@link #retrieve(Class)}. The usage pattern is to retrieve an instance
+ * from the Pool, and if the instance is null, create a new instance.
+ *
+ * <p>The implementation of Pool is threadsafe.
+ *
+ * <p>Pool implements {@link ICleanable}, with a goal of
+ * only keeping pooled objects that have been needed within
+ * a recent time frame. A generational system is used, where each
+ * pooled object is assigned a generation count. {@link #executeCleanup}
+ * discards objects whose generation count is too old (outside of a
+ * {@link #getWindow() window}).
+ *
+ * <p>
+ * Objects in the pool can receive two notifications: one notification
+ * when they are {@link #store(Object, Object) stored} into the pool,
+ * and one when they are discarded from the pool.
+ *
+ * <p>
+ * Classes that implement {@link org.apache.tapestry.util.pool.IPoolable}
+ * receive notifications directly, as per the two methods
+ * of that interface.
+ *
+ * <p>
+ * Alternately, an adaptor for the other classes can be
+ * registerered (using {@link #registerAdaptor(Class, IPoolableAdaptor)}.
+ * The adaptor will be invoked to handle the notification when a
+ * pooled object is stored or discarded.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class Pool implements ICleanable, IRenderDescription
+{
+ private static final Log LOG = LogFactory.getLog(Pool.class);
+
+ private AdaptorRegistry _adaptors = new AdaptorRegistry();
+
+ /**
+ * The generation, used to cull unused pooled items.
+ *
+ * @since 1.0.5
+ **/
+
+ private int _generation;
+
+ /**
+ * The generation window, used to identify which
+ * items should be culled.
+ *
+ * @since 1.0.5
+ **/
+
+ private int _window = 10;
+
+ /**
+ * The number of objects pooled.
+ *
+ **/
+
+ private int _pooledCount;
+
+ /**
+ * A map of PoolLists, keyed on an arbitrary object.
+ *
+ **/
+
+ private Map _map;
+
+ /**
+ * Creates a new Pool using the default map size. Creation of the map is deferred.
+ *
+ *
+ **/
+
+ public Pool()
+ {
+ this(true);
+ }
+
+ /**
+ * Creates a new Pool using the specified map size. The map is created immediately.
+ *
+ * @deprecated Use {@link #Pool()} instead.
+ *
+ **/
+
+ public Pool(int mapSize)
+ {
+ this(mapSize, true);
+ }
+
+ /**
+ * @param useSharedJanitor if true, then the Pool is added to
+ * the {@link JanitorThread#getSharedJanitorThread() shared janitor}.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public Pool(boolean useSharedJanitor)
+ {
+ if (useSharedJanitor)
+ JanitorThread.getSharedJanitorThread().add(this);
+
+ registerAdaptors();
+ }
+
+ /**
+ * Standard constructor.
+ *
+ * @param mapSize initial size of the map.
+ * @param useSharedJanitor if true, then the Pool is added to
+ * the {@link JanitorThread#getSharedJanitorThread() shared janitor}.
+ *
+ * @since 1.0.5
+ * @deprecated Use {@link #Pool(boolean)} instead.
+ *
+ **/
+
+ public Pool(int mapSize, boolean useSharedJanitor)
+ {
+ this(useSharedJanitor);
+
+ _map = new HashMap(mapSize);
+ }
+
+ /**
+ * Returns the window used to cull pooled objects during a cleanup.
+ * The default is 10, which works out to about five minutes with
+ * a standard janitor (on a 30 second cycle).
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public int getWindow()
+ {
+ return _window;
+ }
+
+ /**
+ * Sets the window, or number of generations that an object may stay
+ * in the pool before being culled.
+ *
+ * @throws IllegalArgumentException if value is less than 1.
+ *
+ * @since 1.0.5
+ **/
+
+ public void setWindow(int value)
+ {
+ if (value < 1)
+ throw new IllegalArgumentException("Pool window may not be less than 1.");
+
+ _window = value;
+ }
+
+ /**
+ * Returns a previously pooled object with the given key, or null if no
+ * such object exists. Getting an object from a Pool removes it from the Pool,
+ * but it can later be re-added with {@link #store(Object,Object)}.
+ *
+ **/
+
+ public synchronized Object retrieve(Object key)
+ {
+ Object result = null;
+
+ if (_map == null)
+ _map = new HashMap();
+
+ PoolList list = (PoolList) _map.get(key);
+
+ if (list != null)
+ result = list.retrieve();
+
+ if (result != null)
+ _pooledCount--;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Retrieved " + result + " from " + key);
+
+ return result;
+ }
+
+ /**
+ * Retrieves an instance of the named class. If no pooled
+ * instance is available, a new instance is created
+ * (using the no arguments constructor). Objects are
+ * pooled using their actual class as a key.
+ *
+ * <p>
+ * However, don't be fooled by false economies. Unless
+ * an object is very expensive to create, pooling is
+ * <em>more</em> expensive than simply instantiating temporary
+ * instances and letting the garbage collector deal with it
+ * (this is counter intuitive, but true). For example,
+ * this method was originally created to allow pooling
+ * of {@link StringBuffer}, but testing showed that it
+ * was a net defecit.
+ *
+ **/
+
+ public Object retrieve(Class objectClass)
+ {
+ Object result = retrieve((Object) objectClass);
+
+ if (result == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("No instance of " + objectClass.getName() + " is available, instantiating one.");
+
+ try
+ {
+ result = objectClass.newInstance();
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("Pool.unable-to-instantiate-instance", objectClass.getName()),
+ ex);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Stores an object using its class as a key.
+ *
+ * @see #retrieve(Class)
+ *
+ **/
+
+ public void store(Object object)
+ {
+ store(object.getClass(), object);
+ }
+
+ /**
+ * Stores an object in the pool for later retrieval, resetting
+ * the object for storage within the pool.
+ *
+ **/
+
+ public synchronized void store(Object key, Object object)
+ {
+ getAdaptor(object).resetForPool(object);
+
+ if (_map == null)
+ _map = new HashMap();
+
+ PoolList list = (PoolList) _map.get(key);
+
+ if (list == null)
+ {
+ list = new PoolList(this);
+ _map.put(key, list);
+ }
+
+ int count = list.store(_generation, object);
+
+ _pooledCount++;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Stored " + object + " into " + key + " (" + count + " pooled)");
+ }
+
+ /**
+ * Removes all previously pooled objects from this Pool.
+ *
+ **/
+
+ public synchronized void clear()
+ {
+ if (_map != null)
+ {
+ Iterator i = _map.values().iterator();
+
+ while (i.hasNext())
+ {
+ PoolList list = (PoolList) i.next();
+
+ list.discardAll();
+ }
+
+ _map.clear();
+ }
+
+ _pooledCount = 0;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Cleared");
+ }
+
+ /**
+ * Returns the number of object pooled, the sum of the number
+ * of objects in pooled under each key.
+ *
+ * @since 1.0.2
+ **/
+
+ public synchronized int getPooledCount()
+ {
+ return _pooledCount;
+ }
+
+ /**
+ * Returns the number of keys within the pool.
+ *
+ * @since 1.0.2
+ **/
+
+ public synchronized int getKeyCount()
+ {
+ if (_map == null)
+ return 0;
+
+ return _map.size();
+ }
+
+ /**
+ * Peforms culling of unneeded pooled objects.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ public synchronized void executeCleanup()
+ {
+ if (_map == null)
+ return;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Executing cleanup of " + this);
+
+ _generation++;
+
+ int oldestGeneration = _generation - _window;
+
+ if (oldestGeneration < 0)
+ return;
+
+ int oldCount = _pooledCount;
+ int culledKeys = 0;
+
+ // During the cleanup, we keep the entire instance synchronized
+ // (meaning other threads will block when trying to store
+ // or retrieved pooled objects). Fortunately, this
+ // should be pretty darn quick!
+
+ int newCount = 0;
+
+ Iterator i = _map.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry e = (Map.Entry) i.next();
+
+ PoolList list = (PoolList) e.getValue();
+
+ int count = list.cleanup(oldestGeneration);
+
+ if (count == 0)
+ {
+ i.remove();
+ culledKeys++;
+ }
+ else
+ newCount += count;
+ }
+
+ _pooledCount = newCount;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Culled " + (oldCount - _pooledCount) + " pooled objects and " + culledKeys + " keys.");
+ }
+
+ public synchronized String toString()
+ {
+ ToStringBuilder builder = new ToStringBuilder(this);
+
+ builder.append("generation", _generation);
+ builder.append("pooledCount", _pooledCount);
+
+ return builder.toString();
+ }
+
+ /** @since 1.0.6 **/
+
+ public synchronized void renderDescription(IMarkupWriter writer)
+ {
+ writer.begin("table");
+ writer.attribute("border", "1");
+ writer.println();
+
+ writer.begin("tr");
+ writer.begin("th");
+ writer.attribute("colspan", "2");
+ writer.print(toString());
+ writer.end();
+ writer.end();
+ writer.println();
+
+ if (_map != null)
+ {
+ Iterator i = _map.entrySet().iterator();
+
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ PoolList list = (PoolList) entry.getValue();
+
+ writer.begin("tr");
+ writer.begin("td");
+ writer.print(entry.getKey().toString());
+ writer.end();
+ writer.begin("td");
+ writer.print(list.getPooledCount());
+ writer.end();
+ writer.end();
+
+ writer.println();
+ }
+ }
+ }
+
+ /**
+ * Invoked from the constructor to register the default set of
+ * {@link org.apache.tapestry.util.pool.IPoolableAdaptor}. Subclasses
+ * may override this to register a different set.
+ *
+ * <p>
+ * Registers:
+ * <ul>
+ * <li>{@link NullPoolableAdaptor} for class Object
+ * <li>{@link DefaultPoolableAdaptor} for interface {@link IPoolable}
+ * <li>{@link StringBufferAdaptor} for {@link StringBuffer}
+ * </ul>
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected void registerAdaptors()
+ {
+ registerAdaptor(Object.class, new NullPoolableAdaptor());
+ registerAdaptor(IPoolable.class, new DefaultPoolableAdaptor());
+ registerAdaptor(StringBuffer.class, new StringBufferAdaptor());
+ }
+
+ /**
+ * Registers an adaptor for a particular class (or interface).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void registerAdaptor(Class registrationClass, IPoolableAdaptor adaptor)
+ {
+ _adaptors.register(registrationClass, adaptor);
+ }
+
+ /**
+ * Returns an adaptor appropriate to the object.
+ *
+ **/
+
+ public IPoolableAdaptor getAdaptor(Object object)
+ {
+ return (IPoolableAdaptor) _adaptors.getAdaptor(object.getClass());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/PoolList.java b/tapestry-framework/src/org/apache/tapestry/util/pool/PoolList.java
new file mode 100644
index 0000000..26a50e3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/PoolList.java
@@ -0,0 +1,245 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * A wrapper around a list of objects for a given key in a {@link Pool}.
+ * The current implementation of this is FIFO. This class is closely
+ * tied to {@link Pool}, which controls synchronization for it.
+ *
+ * <p>This class, and {@link Pool}, were heavily revised in 1.0.5
+ * to support generational cleaning. The PoolList acts like a first-in
+ * first-out queue and each pooled object is tagged with a "generation
+ * count", provided by the {@link Pool}. The generation count is
+ * incremented periodically. This allows us to track, roughly,
+ * how often a pooled object has been accessed; unused objects will
+ * be buried with relatively low generation counts.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+class PoolList
+{
+ /** @since 3.0 **/
+
+ private Pool _pool;
+
+ /**
+ * Linked list of pooled objects.
+ *
+ * @since 1.0.5
+ **/
+
+ private Entry _first;
+
+ /**
+ * Linked list of "spare" Entries, ready to be re-used.
+ *
+ * @since 1.0.5
+ **/
+
+ private Entry _spare;
+
+ /**
+ * Overall count of items pooled.
+ *
+ **/
+
+ private int _count;
+
+ /**
+ * A simple linked-list entry for items stored in the PoolList.
+ *
+ * @since 1.0.5
+ *
+ **/
+
+ private static class Entry
+ {
+ int generation;
+ Object pooled;
+ Entry next;
+ }
+
+ /**
+ * @since 3.0
+ *
+ **/
+
+ PoolList(Pool pool)
+ {
+ _pool = pool;
+ }
+
+ /**
+ * Returns the number of pooled objects currently stored.
+ *
+ * @since 1.0.5
+ **/
+
+ public int getPooledCount()
+ {
+ return _count;
+ }
+
+ /**
+ * Returns an object previously stored into the list, or null if the list
+ * is empty. The returned object is removed from the list.
+ *
+ **/
+
+ public Object retrieve()
+ {
+ if (_count == 0)
+ return null;
+
+ _count--;
+
+ Entry e = _first;
+ Object result = e.pooled;
+
+ // Okay, store e into the list of spare entries.
+
+ _first = e.next;
+
+ e.next = _spare;
+ _spare = e;
+ e.generation = 0;
+ e.pooled = null;
+
+ return result;
+ }
+
+ /**
+ * Adds the object to this PoolList. An arbitrary number of objects can be
+ * stored. The objects can later be retrieved using {@link #get()}.
+ * The list requires that generation never decrease. On each subsequent
+ * invocation, it should be the same as, or greater, than the previous value.
+ *
+ * @return The number of objects stored in the list (after adding the new object).
+ **/
+
+ public int store(int generation, Object object)
+ {
+ Entry e;
+
+ if (_spare == null)
+ {
+ e = new Entry();
+ }
+ else
+ {
+ e = _spare;
+ _spare = _spare.next;
+ }
+
+ e.generation = generation;
+ e.pooled = object;
+ e.next = _first;
+ _first = e;
+
+ return ++_count;
+ }
+
+ /**
+ * Invoked to cleanup the list, freeing unneeded objects.
+ *
+ * @param generation pooled objects stored in this generation or
+ * earlier are released.
+ *
+ * @since 1.0.5
+ **/
+
+ public int cleanup(int generation)
+ {
+ _spare = null;
+
+ _count = 0;
+
+ Entry prev = null;
+
+ // Walk through the list. They'll be sorted by generation.
+
+ Entry e = _first;
+ while (true)
+ {
+ if (e == null)
+ break;
+
+ // If found a too-old entry then we want to
+ // delete it.
+
+ if (e.generation <= generation)
+ {
+ Object pooled = e.pooled;
+
+ // Notify the object that it is being dropped
+ // through the cracks!
+
+ _pool.getAdaptor(pooled).discardFromPool(pooled);
+
+ // Set the next pointer of the previous node to null.
+ // If the very first node inspected was too old,
+ // set the first pointer to null.
+
+ if (prev == null)
+ _first = null;
+ else
+ prev.next = null;
+ }
+ else
+ _count++;
+
+ prev = e;
+ e = e.next;
+ }
+
+ return _count;
+ }
+
+ public String toString()
+ {
+ return "PoolList[" + _count + "]";
+ }
+
+ /**
+ * Much like {@link #cleanup(int)}, but discards all
+ * pooled objects.
+ *
+ * @since 3.0
+ *
+ **/
+
+ void discardAll()
+ {
+ Entry e = _first;
+
+ while (e != null)
+ {
+ Object pooled = e.pooled;
+
+ _pool.getAdaptor(pooled).discardFromPool(pooled);
+
+ e = e.next;
+ }
+
+ _first = null;
+ _spare = null;
+ _count = 0;
+
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/StringBufferAdaptor.java b/tapestry-framework/src/org/apache/tapestry/util/pool/StringBufferAdaptor.java
new file mode 100644
index 0000000..61a8754
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/StringBufferAdaptor.java
@@ -0,0 +1,43 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.pool;
+
+/**
+ * Adaptor for {@link java.lang.StringBuffer}, that clears
+ * the buffer as it is returned to the pool.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class StringBufferAdaptor extends NullPoolableAdaptor
+{
+
+ /**
+ * Sets the length of the {@link java.lang.StringBuffer}
+ * to zero.
+ *
+ **/
+
+ public void resetForPool(Object object)
+ {
+ StringBuffer buffer = (StringBuffer)object;
+
+ buffer.setLength(0);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/pool/package.html b/tapestry-framework/src/org/apache/tapestry/util/pool/package.html
new file mode 100644
index 0000000..1cde499
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/pool/package.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+<body>
+
+<p>Classes for managing a pool of reusable objects. Rather than creating
+expensive objects as needed, they are obtained from a
+{@link org.apache.tapestry.util.pool.Pool}. Instead of discarding them, they
+are returned to the Pool. The Pool class is threadsafe and reasonably efficient.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/prop/OgnlUtils.java b/tapestry-framework/src/org/apache/tapestry/util/prop/OgnlUtils.java
new file mode 100644
index 0000000..c6852fc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/prop/OgnlUtils.java
@@ -0,0 +1,161 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.prop;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ognl.ClassResolver;
+import ognl.Ognl;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * Utilities wrappers around <a href="http://www.ognl.org">OGNL</a>.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class OgnlUtils
+{
+ private static final Map _cache = new HashMap();
+
+ private OgnlUtils()
+ {
+ }
+
+ /**
+ * Gets a parsed OGNL expression from the input string.
+ *
+ * @throws ApplicationRuntimeException if the expression can not be parsed.
+ *
+ **/
+
+ public static synchronized Object getParsedExpression(String expression)
+ {
+ Object result = _cache.get(expression);
+
+ if (result == null)
+ {
+ try
+ {
+ result = Ognl.parseExpression(expression);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("OgnlUtils.unable-to-parse-expression", expression),
+ ex);
+ }
+
+ _cache.put(expression, result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses and caches the expression and uses it to update
+ * the target object with the provided value.
+ *
+ * @throws ApplicationRuntimeException if the expression
+ * can not be parsed, or the target can not be updated.
+ *
+ **/
+
+ public static void set(String expression, ClassResolver resolver, Object target, Object value)
+ {
+ set(getParsedExpression(expression), resolver, target, value);
+ }
+
+ /**
+ * Updates the target object with the provided value.
+ *
+ * @param expression a parsed OGNL expression
+ * @throws ApplicationRuntimeException if the target can not be updated.
+ *
+ **/
+
+ public static void set(Object expression, ClassResolver resolver, Object target, Object value)
+ {
+ try
+ {
+ Map context = Ognl.createDefaultContext(target, resolver);
+
+ Ognl.setValue(expression, context, target, value);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "OgnlUtils.unable-to-update-expression",
+ "<parsed expression>",
+ target,
+ value),
+ ex);
+ }
+ }
+
+ /**
+ * Returns the value of the expression evaluated against
+ * the object.
+ *
+ * @param expression a parsed OGNL expression
+ * @param object the root object
+ *
+ * @throws ApplicationRuntimeException
+ * if the value can not be obtained from the object.
+ *
+ **/
+
+ public static Object get(Object expression, ClassResolver resolver, Object object)
+ {
+ try
+ {
+ Map context = Ognl.createDefaultContext(object, resolver);
+
+ return Ognl.getValue(expression, context, object);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "OgnlUtils.unable-to-read-expression",
+ "<parsed expression>",
+ object),
+ ex);
+ }
+ }
+
+ /**
+ * Returns the value of the expression evaluated against
+ * the object.
+ *
+ *
+ * @throws ApplicationRuntimeException if the
+ * expression can not be parsed, or the value
+ * not obtained from the object.
+ **/
+
+ public static Object get(String expression, ClassResolver resolver, Object object)
+ {
+ return get(getParsedExpression(expression), resolver, object);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyFinder.java b/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyFinder.java
new file mode 100644
index 0000000..75977c3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyFinder.java
@@ -0,0 +1,104 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.prop;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.Tapestry;
+
+/**
+ *
+ * Uses {@link java.beans.Introspector} to get bean information
+ * and analyze properties for those beans.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class PropertyFinder
+{
+ /**
+ * Keyed on bean class value is also a Map. Inner Map is
+ * keyed on property name, value is a
+ * {@link PropertyInfo}.
+ *
+ **/
+
+ private static Map _cache = new HashMap();
+
+ /**
+ * Finds the {@link PropertyInfo} for the specified class and
+ * property. Returns null if the class does not implement
+ * such a property.
+ *
+ **/
+
+ public synchronized static PropertyInfo getPropertyInfo(Class beanClass, String propertyName)
+ {
+ Map beanClassMap = (Map) _cache.get(beanClass);
+
+ if (beanClassMap == null)
+ {
+ beanClassMap = buildBeanClassMap(beanClass);
+
+ _cache.put(beanClass, beanClassMap);
+ }
+
+ return (PropertyInfo) beanClassMap.get(propertyName);
+ }
+
+ private static Map buildBeanClassMap(Class beanClass)
+ {
+ Map result = new HashMap();
+ BeanInfo bi = null;
+
+ try
+ {
+ bi = Introspector.getBeanInfo(beanClass);
+ }
+ catch (IntrospectionException ex)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("PropertyFinder.unable-to-introspect-class", beanClass.getName()),
+ ex);
+ }
+
+ PropertyDescriptor[] pd = bi.getPropertyDescriptors();
+
+ for (int i = 0; i < pd.length; i++)
+ {
+ PropertyDescriptor d = pd[i];
+
+ PropertyInfo info =
+ new PropertyInfo(
+ d.getName(),
+ d.getPropertyType(),
+ d.getReadMethod() != null,
+ d.getWriteMethod() != null);
+
+ result.put(d.getName(), info);
+ }
+
+ return result;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyInfo.java b/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyInfo.java
new file mode 100644
index 0000000..3fe2ad5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/prop/PropertyInfo.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.prop;
+
+/**
+ * Used by {@link org.apache.tapestry.util.prop.PropertyFinder}
+ * to identify information about a property.
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class PropertyInfo
+{
+ private String _name;
+ private Class _type;
+ private boolean _read;
+ private boolean _write;
+
+ PropertyInfo(String name, Class type, boolean read, boolean write)
+ {
+ _name = name;
+ _type = type;
+ _read = read;
+ _write = write;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public Class getType()
+ {
+ return _type;
+ }
+
+ public boolean isRead()
+ {
+ return _read;
+ }
+
+ public boolean isWrite()
+ {
+ return _write;
+ }
+
+ public boolean isReadWrite()
+ {
+ return _read && _write;
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/prop/package.html b/tapestry-framework/src/org/apache/tapestry/util/prop/package.html
new file mode 100644
index 0000000..1d864ac
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/prop/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+
+<body>
+
+<p>Classes for operating on Java Beans as collections of named properties.
+Prior to release 2.2, there was much more here, but with 2.2
+Tapestry switch to use the <a href="http://www.ognl.org">Object Graph
+Navigation Library</a> which is much more powerful.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/BaseRule.java b/tapestry-framework/src/org/apache/tapestry/util/xml/BaseRule.java
new file mode 100644
index 0000000..e6d766a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/BaseRule.java
@@ -0,0 +1,63 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.xml;
+
+import org.apache.tapestry.Tapestry;
+import org.xml.sax.Attributes;
+
+/**
+ * Base implementation of {@link org.apache.tapestry.util.xml.IRule} that
+ * does nothing.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ **/
+public class BaseRule implements IRule
+{
+ protected String getAttribute(Attributes attributes, String name)
+ {
+ int count = attributes.getLength();
+
+ for (int i = 0; i < count; i++)
+ {
+ String attributeName = attributes.getLocalName(i);
+
+ if (Tapestry.isBlank(attributeName))
+ attributeName = attributes.getQName(i);
+
+ if (attributeName.equals(name))
+ return attributes.getValue(i);
+ }
+
+ return null;
+ }
+
+ public void startElement(RuleDirectedParser parser, Attributes attributes)
+ {
+
+ }
+
+ public void endElement(RuleDirectedParser parser)
+ {
+
+ }
+
+ public void content(RuleDirectedParser parser, String content)
+ {
+
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/DocumentParseException.java b/tapestry-framework/src/org/apache/tapestry/util/xml/DocumentParseException.java
new file mode 100644
index 0000000..c56043f
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/DocumentParseException.java
@@ -0,0 +1,105 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.xml;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Location;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Exception thrown if there is any kind of error parsing the
+ * an XML document.
+ *
+ * @see org.apache.tapestry.parse.SpecificationParser
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 0.2.10
+ *
+ **/
+
+public class DocumentParseException extends ApplicationRuntimeException
+{
+ private IResourceLocation _documentLocation;
+
+ public DocumentParseException(String message, Throwable rootCause)
+ {
+ this(message, null, null, rootCause);
+ }
+
+ public DocumentParseException(String message, IResourceLocation documentLocation)
+ {
+ this(message, documentLocation, null);
+ }
+
+ public DocumentParseException(
+ String message,
+ IResourceLocation documentLocation,
+ Throwable rootCause)
+ {
+ this(message, documentLocation, null, rootCause);
+ }
+
+ public DocumentParseException(
+ String message,
+ IResourceLocation documentLocation,
+ ILocation location,
+ Throwable rootCause)
+ {
+ super(message, null, location, rootCause);
+
+ _documentLocation = documentLocation;
+ }
+
+ public DocumentParseException(
+ String message,
+ IResourceLocation documentLocation,
+ SAXParseException rootCause)
+ {
+ this(
+ message,
+ documentLocation,
+ rootCause == null
+ || documentLocation == null
+ ? null
+ : new Location(
+ documentLocation,
+ rootCause.getLineNumber(),
+ rootCause.getColumnNumber()),
+ rootCause);
+ }
+
+ public DocumentParseException(String message)
+ {
+ this(message, null, null, null);
+ }
+
+ public DocumentParseException(Throwable rootCause)
+ {
+ this(rootCause.getMessage(), rootCause);
+ }
+
+ public DocumentParseException(SAXParseException rootCause)
+ {
+ this(rootCause.getMessage(), (Throwable) rootCause);
+ }
+
+ public IResourceLocation getDocumentLocation()
+ {
+ return _documentLocation;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/IRule.java b/tapestry-framework/src/org/apache/tapestry/util/xml/IRule.java
new file mode 100644
index 0000000..dd0ea44
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/IRule.java
@@ -0,0 +1,56 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.xml;
+
+import org.xml.sax.Attributes;
+
+/**
+ * A rule that may be pushed onto the {@link org.apache.tapestry.util.xml.RuleDirectedParser}'s
+ * rule stack. A rule is associated with an XML element. It is pushed onto the stack when the
+ * open tag for the rule is encountered. It is is popped off the stack after the end-tag is
+ * encountered. It is notified about any text it directly wraps around.
+ *
+ * <p>Rules should be stateless, because a rule instance may appear multiple times in the
+ * rule stack (if elements can be recusively nested).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ **/
+
+public interface IRule
+{
+ /**
+ * Invoked just after the rule is pushed onto the rule stack. Typically, a Rule will
+ * use the information to create a new object and push it onto the object stack.
+ * If the rule needs to know about the element (rather than the attributes), it
+ * may obtain the URI, localName and qName from the parser.
+ *
+ */
+ public void startElement(RuleDirectedParser parser, Attributes attributes);
+
+ /**
+ * Invoked just after the rule is popped off the rule stack.
+ */
+ public void endElement(RuleDirectedParser parser);
+
+
+ /**
+ * Invoked when real content is found. The parser is responsible for aggregating
+ * all content provided by the underlying SAX parser into a single string.
+ */
+
+ public void content(RuleDirectedParser parser, String content);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/InvalidStringException.java b/tapestry-framework/src/org/apache/tapestry/util/xml/InvalidStringException.java
new file mode 100644
index 0000000..429b793
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/InvalidStringException.java
@@ -0,0 +1,56 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.xml;
+
+import org.apache.tapestry.ILocatable;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+
+/**
+ * Exception thrown if there is any kind of error validating a string
+ * during document parsing
+ *
+ * @author Geoffrey Longman
+ * @version $Id$
+ * @since 2.2
+ *
+ **/
+
+public class InvalidStringException extends DocumentParseException implements ILocatable
+{
+ private String _invalidString;
+
+ public InvalidStringException(
+ String message,
+ String invalidString,
+ IResourceLocation resourceLocation)
+ {
+ super(message, resourceLocation);
+
+ _invalidString = invalidString;
+ }
+
+ public InvalidStringException(String message, String invalidString, ILocation location)
+ {
+ super(message, location == null ? null : location.getResourceLocation(), location, null);
+
+ _invalidString = invalidString;
+ }
+
+ public String getInvalidString()
+ {
+ return _invalidString;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/RuleDirectedParser.java b/tapestry-framework/src/org/apache/tapestry/util/xml/RuleDirectedParser.java
new file mode 100644
index 0000000..1b79172
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/RuleDirectedParser.java
@@ -0,0 +1,584 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.util.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.ILocation;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.util.RegexpMatcher;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A simplified version of {@link org.apache.commons.digester.Digester}.
+ * This version is without as many bells and whistles but has some key features needed when parsing
+ * a document (rather than a configuration file):
+ * <br>
+ * <ul>
+ * <li>Notifications for each bit of text</ul>
+ * <li>Tracking of exact location within the document.</li>
+ * </ul>
+ *
+ * <p>
+ * Like Digester, there's an object stack and a rule stack. The rules are much
+ * simpler (more coding), in that there's a one-to-one relationship between
+ * an element and a rule.
+ *
+ * <p>
+ * Based on SAX2.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 3.0
+ */
+
+public class RuleDirectedParser extends DefaultHandler
+{
+ private static final Log LOG = LogFactory.getLog(RuleDirectedParser.class);
+
+ private IResourceLocation _documentLocation;
+ private List _ruleStack = new ArrayList();
+ private List _objectStack = new ArrayList();
+ private Object _documentObject;
+
+ private Locator _locator;
+ private int _line = -1;
+ private int _column = -1;
+ private ILocation _location;
+
+ private static SAXParserFactory _parserFactory;
+ private SAXParser _parser;
+
+ private RegexpMatcher _matcher;
+
+ private String _uri;
+ private String _localName;
+ private String _qName;
+
+ /**
+ * Map of {@link IRule} keyed on the local name
+ * of the element.
+ */
+ private Map _ruleMap = new HashMap();
+
+ /**
+ * Used to accumlate content provided by
+ * {@link org.xml.sax.ContentHandler#characters(char[], int, int)}.
+ */
+
+ private StringBuffer _contentBuffer = new StringBuffer();
+
+ /**
+ * Map of paths to external entities (such as the DTD) keyed on public id.
+ */
+
+ private Map _entities = new HashMap();
+
+ public Object parse(IResourceLocation documentLocation)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsing: " + documentLocation);
+
+ try
+ {
+ _documentLocation = documentLocation;
+
+ URL url = documentLocation.getResourceURL();
+
+ if (url == null)
+ throw new DocumentParseException(
+ Tapestry.format("RuleDrivenParser.resource-missing", documentLocation),
+ documentLocation,
+ null,
+ null);
+
+ return parse(url);
+ }
+ finally
+ {
+ _documentLocation = null;
+ _ruleStack.clear();
+ _objectStack.clear();
+ _documentObject = null;
+
+ _uri = null;
+ _localName = null;
+ _qName = null;
+
+ _line = -1;
+ _column = -1;
+ _location = null;
+ _locator = null;
+
+ _contentBuffer.setLength(0);
+ }
+ }
+
+ protected Object parse(URL url)
+ {
+ if (_parser == null)
+ _parser = constructParser();
+
+ InputStream stream = null;
+
+ try
+ {
+ stream = url.openStream();
+ }
+ catch (IOException ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("RuleDrivenParser.unable-to-open-resource", url),
+ _documentLocation,
+ null,
+ ex);
+ }
+
+ InputSource source = new InputSource(stream);
+
+ try
+ {
+ _parser.parse(source, this);
+
+ stream.close();
+ }
+ catch (Exception ex)
+ {
+ throw new DocumentParseException(
+ Tapestry.format("RuleDrivenParser.parse-error", url, ex.getMessage()),
+ _documentLocation,
+ getLocation(),
+ ex);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Document parsed as: " + _documentObject);
+
+ return _documentObject;
+ }
+
+ /**
+ * Returns an {@link ILocation} representing the
+ * current position within the document (depending
+ * on the parser, this may be accurate to
+ * column number level).
+ */
+
+ public ILocation getLocation()
+ {
+ if (_locator == null)
+ return null;
+
+ int line = _locator.getLineNumber();
+ int column = _locator.getColumnNumber();
+
+ if (_line != line || _column != column)
+ {
+ _location = null;
+ _line = line;
+ _column = column;
+ }
+
+ if (_location == null)
+ _location = new Location(_documentLocation, _line, _column);
+
+ return _location;
+ }
+
+ /**
+ * Pushes an object onto the object stack. The first object
+ * pushed is the "document object", the root object returned
+ * by the parse.
+ */
+ public void push(Object object)
+ {
+ if (_documentObject == null)
+ _documentObject = object;
+
+ push(_objectStack, object, "object stack");
+ }
+
+ /**
+ * Returns the top object on the object stack.
+ */
+ public Object peek()
+ {
+ return peek(_objectStack, 0);
+ }
+
+ /**
+ * Returns an object within the object stack, at depth.
+ * Depth 0 is the top object, depth 1 is the next-to-top object,
+ * etc.
+ */
+
+ public Object peek(int depth)
+ {
+ return peek(_objectStack, depth);
+ }
+
+ /**
+ * Removes and returns the top object on the object stack.
+ */
+ public Object pop()
+ {
+ return pop(_objectStack, "object stack");
+ }
+
+ private Object pop(List list, String name)
+ {
+ Object result = list.remove(list.size() - 1);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Popped " + result + " off " + name + " (at " + getLocation() + ")");
+
+ return result;
+ }
+
+ private Object peek(List list, int depth)
+ {
+ return list.get(list.size() - 1 - depth);
+ }
+
+ private void push(List list, Object object, String name)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Pushing " + object + " onto " + name + " (at " + getLocation() + ")");
+
+ list.add(object);
+ }
+
+ /**
+ * Pushes a new rule onto the rule stack.
+ */
+
+ protected void pushRule(IRule rule)
+ {
+ push(_ruleStack, rule, "rule stack");
+ }
+
+ /**
+ * Returns the top rule on the stack.
+ */
+
+ protected IRule peekRule()
+ {
+ return (IRule) peek(_ruleStack, 0);
+ }
+
+ protected IRule popRule()
+ {
+ return (IRule) pop(_ruleStack, "rule stack");
+ }
+
+ public void addRule(String localElementName, IRule rule)
+ {
+ _ruleMap.put(localElementName, rule);
+ }
+
+ /**
+ * Registers
+ * a public id and corresponding input source. Generally, the source
+ * is a wrapper around an input stream to a package resource.
+ *
+ * @param publicId the public identifier to be registerred, generally
+ * the publicId of a DTD related to the document being parsed
+ * @param entityPath the resource path of the entity, typically a DTD
+ * file. Relative files names are expected to be stored in the same package
+ * as the class file, otherwise a leading slash is an absolute pathname
+ * within the classpath.
+ *
+ **/
+
+ public void registerEntity(String publicId, String entityPath)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Registering " + publicId + " as " + entityPath);
+
+ if (_entities == null)
+ _entities = new HashMap();
+
+ _entities.put(publicId, entityPath);
+ }
+
+ protected IRule selectRule(String localName, Attributes attributes)
+ {
+ IRule rule = (IRule) _ruleMap.get(localName);
+
+ if (rule == null)
+ throw new DocumentParseException(
+ Tapestry.format("RuleDrivenParser.no-rule-for-element", localName),
+ _documentLocation,
+ getLocation(),
+ null);
+
+ return rule;
+ }
+
+ /**
+ * Uses the {@link Locator} to track the position
+ * in the document as a {@link ILocation}. This is invoked
+ * once (before the initial element is parsed) and
+ * the Locator is retained and queried as to
+ * the current file location.
+ *
+ * @see #getLocation()
+ */
+ public void setDocumentLocator(Locator locator)
+ {
+ _locator = locator;
+ }
+
+ /**
+ * Accumulates the content in a buffer; the concatinated content
+ * is provided to the top rule just before any start or end tag.
+ */
+ public void characters(char[] ch, int start, int length) throws SAXException
+ {
+ _contentBuffer.append(ch, start, length);
+ }
+
+ /**
+ * Pops the top rule off the stack and
+ * invokes {@link IRule#endElement(RuleDirectedParser)}.
+ */
+ public void endElement(String uri, String localName, String qName) throws SAXException
+ {
+ fireContentRule();
+
+ _uri = uri;
+ _localName = localName;
+ _qName = qName;
+
+ popRule().endElement(this);
+ }
+
+ /**
+ * Ignorable content is ignored.
+ */
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException
+ {
+ }
+
+ /**
+ * Invokes {@link #selectRule(String, Attributes)} to choose a new rule,
+ * which is pushed onto the rule stack, then invokes
+ * {@link IRule#startElement(RuleDirectedParser, Attributes)}.
+ */
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException
+ {
+ fireContentRule();
+
+ _uri = uri;
+ _localName = localName;
+ _qName = qName;
+
+ String name = extractName(uri, localName, qName);
+
+ IRule newRule = selectRule(name, attributes);
+
+ pushRule(newRule);
+
+ newRule.startElement(this, attributes);
+ }
+
+ private String extractName(String uri, String localName, String qName)
+ {
+ return Tapestry.isBlank(localName) ? qName : localName;
+ }
+
+ /**
+ * Uses {@link javax.xml.parsers.SAXParserFactory} to create a instance
+ * of a validation SAX2 parser.
+ */
+ protected synchronized SAXParser constructParser()
+ {
+ if (_parserFactory == null)
+ {
+ _parserFactory = SAXParserFactory.newInstance();
+ configureParserFactory(_parserFactory);
+ }
+
+ try
+ {
+ return _parserFactory.newSAXParser();
+ }
+ catch (SAXException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+ catch (ParserConfigurationException ex)
+ {
+ throw new ApplicationRuntimeException(ex);
+ }
+
+ }
+
+ /**
+ * Configures a {@link SAXParserFactory} before
+ * {@link SAXParserFactory#newSAXParser()} is invoked.
+ * The default implementation sets validating to true
+ * and namespaceAware to false,
+ */
+
+ protected void configureParserFactory(SAXParserFactory factory)
+ {
+ factory.setValidating(true);
+ factory.setNamespaceAware(false);
+ }
+
+ /**
+ * Throws the exception.
+ */
+ public void error(SAXParseException ex) throws SAXException
+ {
+ fatalError(ex);
+ }
+
+ /**
+ * Throws the exception.
+ */
+ public void fatalError(SAXParseException ex) throws SAXException
+ {
+ // Sometimes, a bad parse "corrupts" a parser so that it doesn't
+ // work properly for future parses (of valid documents),
+ // so discard it here.
+
+ _parser = null;
+
+ throw ex;
+ }
+
+ /**
+ * Throws the exception.
+ */
+ public void warning(SAXParseException ex) throws SAXException
+ {
+ fatalError(ex);
+ }
+
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException
+ {
+ String entityPath = null;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(
+ "Attempting to resolve entity; publicId = " + publicId + " systemId = " + systemId);
+
+ if (_entities != null)
+ entityPath = (String) _entities.get(publicId);
+
+ if (entityPath == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Entity not found, using " + systemId);
+
+ return null;
+ }
+
+ InputStream stream = getClass().getResourceAsStream(entityPath);
+
+ InputSource result = new InputSource(stream);
+
+ if (result != null && LOG.isDebugEnabled())
+ LOG.debug("Resolved " + publicId + " as " + result + " (for " + entityPath + ")");
+
+ return result;
+ }
+
+ /**
+ * Validates that the input value matches against the specified
+ * Perl5 pattern. If valid, the method simply returns.
+ * If not a match, then an error message is generated (using the
+ * errorKey and the input value) and a
+ * {@link InvalidStringException} is thrown.
+ *
+ **/
+
+ public void validate(String value, String pattern, String errorKey)
+ throws DocumentParseException
+ {
+ if (_matcher == null)
+ _matcher = new RegexpMatcher();
+
+ if (_matcher.matches(pattern, value))
+ return;
+
+ throw new InvalidStringException(Tapestry.format(errorKey, value), value, getLocation());
+ }
+
+ public IResourceLocation getDocumentLocation()
+ {
+ return _documentLocation;
+ }
+
+ /**
+ * Returns the localName for the current element.
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public String getLocalName()
+ {
+ return _localName;
+ }
+
+ /**
+ * Returns the qualified name for the current element.
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public String getQName()
+ {
+ return _qName;
+ }
+
+ /**
+ * Returns the URI for the current element.
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public String getUri()
+ {
+ return _uri;
+ }
+
+ private void fireContentRule()
+ {
+ String content = _contentBuffer.toString();
+ _contentBuffer.setLength(0);
+
+ if (!_ruleStack.isEmpty())
+ peekRule().content(this, content);
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/util/xml/package.html b/tapestry-framework/src/org/apache/tapestry/util/xml/package.html
new file mode 100644
index 0000000..16de827
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/util/xml/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+
+<body>
+
+<p>Base classes for streamlining the process of parsing an XML document. This is primarily
+used with a validating parser, where the DTD is stored within the classpath.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/BaseValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/BaseValidator.java
new file mode 100644
index 0000000..b8f6bbf
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/BaseValidator.java
@@ -0,0 +1,350 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IEngine;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.IResourceLocation;
+import org.apache.tapestry.IScript;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.IScriptSource;
+import org.apache.tapestry.form.FormEventType;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.html.Body;
+import org.apache.tapestry.resource.ClasspathResourceLocation;
+
+/**
+ * Abstract base class for {@link IValidator}. Supports a required and locale property.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public abstract class BaseValidator implements IValidator
+{
+ /**
+ * Input Symbol used to represent the field being validated.
+ *
+ * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String FIELD_SYMBOL = "field";
+
+ /**
+ * Input symbol used to represent the validator itself to the script.
+ *
+ * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String VALIDATOR_SYMBOL = "validator";
+
+ /**
+ * Input symbol used to represent the {@link IForm} containing the field
+ * to the script.
+ *
+ * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
+ *
+ * @since 2.2
+ **/
+
+ public static final String FORM_SYMBOL = "form";
+
+ /**
+ * Output symbol set by the script asthe name of the validator
+ * JavaScript function.
+ * The function implemented must return true or false (true
+ * if the field is valid, false otherwise).
+ * After the script is executed, the function is added
+ * to the {@link IForm} as a {@link org.apache.tapestry.form.FormEventType#SUBMIT}.
+ *
+ * @see #processValidatorScript(String, IRequestCycle, IFormComponent, Map)
+ *
+ * @since 2.2
+ *
+ **/
+
+ public static final String FUNCTION_SYMBOL = "function";
+
+ private boolean _required;
+
+ /** @since 3.0 */
+
+ private String _requiredMessage;
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ private boolean _clientScriptingEnabled = false;
+
+ /**
+ * Standard constructor. Leaves locale as system default and required as false.
+ *
+ **/
+
+ public BaseValidator()
+ {
+ }
+
+ protected BaseValidator(boolean required)
+ {
+ _required = required;
+ }
+
+ public boolean isRequired()
+ {
+ return _required;
+ }
+
+ public void setRequired(boolean required)
+ {
+ _required = required;
+ }
+
+ /**
+ * Gets a pattern, either as the default value, or as a localized key.
+ * If override is null, then the key from the
+ * <code>org.apache.tapestry.valid.ValidationStrings</code>
+ * {@link ResourceBundle} (in the specified locale) is used.
+ * The pattern can then be used with {@link #formatString(String, Object[])}.
+ *
+ * <p>Why do we not just lump these strings into TapestryStrings.properties?
+ * because TapestryStrings.properties is localized to the server's locale, which is fine
+ * for the logging, debugging and error messages it contains. For field validation, whose errors
+ * are visible to the end user normally, we want to localize to the page's locale.
+ *
+ * @param override The override value for the localized string from the bundle.
+ * @param key used to lookup pattern from bundle, if override is null.
+ * @param locale used to get right localization of bundle.
+ * @since 3.0
+ */
+
+ protected String getPattern(String override, String key, Locale locale)
+ {
+ if (override != null)
+ return override;
+
+ ResourceBundle strings =
+ ResourceBundle.getBundle("org.apache.tapestry.valid.ValidationStrings", locale);
+
+ return strings.getString(key);
+ }
+
+ /**
+ * Gets a string from the standard resource bundle. The string in the bundle
+ * is treated as a pattern for {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
+ *
+ * @param pattern string the input pattern to be used with
+ * {@link MessageFormat#format(java.lang.String, java.lang.Object[])}.
+ * It may contain replaceable parameters, {0}, {1}, etc.
+ * @param args the arguments used to fill replaceable parameters {0}, {1}, etc.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected String formatString(String pattern, Object[] args)
+ {
+ return MessageFormat.format(pattern, args);
+ }
+
+ /**
+ * Convienience method for invoking {@link #formatString(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected String formatString(String pattern, Object arg)
+ {
+ return formatString(pattern, new Object[] { arg });
+ }
+
+ /**
+ * Convienience method for invoking {@link #formatString(String, Object[])}.
+ *
+ * @since 3.0
+ *
+ **/
+
+ protected String formatString(String pattern, Object arg1, Object arg2)
+ {
+ return formatString(pattern, new Object[] { arg1, arg2 });
+ }
+
+ /**
+ * Invoked to check if the value is null. If the value is null (or empty),
+ * but the required flag is set, then this method throws a {@link ValidatorException}.
+ * Otherwise, returns true if the value is null.
+ *
+ **/
+
+ protected boolean checkRequired(IFormComponent field, String value) throws ValidatorException
+ {
+ boolean isEmpty = Tapestry.isBlank(value);
+
+ if (_required && isEmpty)
+ throw new ValidatorException(
+ buildRequiredMessage(field),
+ ValidationConstraint.REQUIRED);
+
+ return isEmpty;
+ }
+
+ /**
+ * Builds an error message indicating a value for a required
+ * field was not supplied.
+ *
+ * @since 3.0
+ */
+
+ protected String buildRequiredMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_requiredMessage, "field-is-required", field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName());
+ }
+
+ /**
+ * This implementation does nothing. Subclasses may supply their own implementation.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ }
+
+ /**
+ * Invoked (from sub-class
+ * implementations of {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)}
+ * to process a standard validation script. This expects that:
+ * <ul>
+ * <li>The {@link IFormComponent} is (ultimately) wrapped by a {@link Body}
+ * <li>The script generates a symbol named "function" (as per {@link #FUNCTION_SYMBOL})
+ * </ul>
+ *
+ * @param scriptPath the resource path of the script to execute
+ * @param cycle The active request cycle
+ * @param field The field to be validated
+ * @param symbols a set of input symbols needed by the script. These symbols
+ * are augmented with symbols for the field, form and validator. symbols may be
+ * null, but will be modified if not null.
+ * @throws ApplicationRuntimeException if there's an error processing the script.
+ *
+ * @since 2.2
+ *
+ **/
+
+ protected void processValidatorScript(
+ String scriptPath,
+ IRequestCycle cycle,
+ IFormComponent field,
+ Map symbols)
+ {
+ IEngine engine = field.getPage().getEngine();
+ IScriptSource source = engine.getScriptSource();
+ IForm form = field.getForm();
+
+ Map finalSymbols = (symbols == null) ? new HashMap() : symbols;
+
+ finalSymbols.put(FIELD_SYMBOL, field);
+ finalSymbols.put(FORM_SYMBOL, form);
+ finalSymbols.put(VALIDATOR_SYMBOL, this);
+
+ IResourceLocation location =
+ new ClasspathResourceLocation(engine.getResourceResolver(), scriptPath);
+
+ IScript script = source.getScript(location);
+
+ Body body = Body.get(cycle);
+
+ if (body == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("ValidField.must-be-contained-by-body"),
+ field,
+ null,
+ null);
+
+ script.execute(cycle, body, finalSymbols);
+
+ String functionName = (String) finalSymbols.get(FUNCTION_SYMBOL);
+
+ form.addEventHandler(FormEventType.SUBMIT, functionName);
+ }
+
+ /**
+ * Returns true if client scripting is enabled. Some validators are
+ * capable of generating client-side scripting to perform validation
+ * when the form is submitted. By default, this flag is false and
+ * subclasses should check it
+ * (in {@link #renderValidatorContribution(IFormComponent, IMarkupWriter, IRequestCycle)})
+ * before generating client side script.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public boolean isClientScriptingEnabled()
+ {
+ return _clientScriptingEnabled;
+ }
+
+ public void setClientScriptingEnabled(boolean clientScriptingEnabled)
+ {
+ _clientScriptingEnabled = clientScriptingEnabled;
+ }
+
+ public String getRequiredMessage()
+ {
+ return _requiredMessage;
+ }
+
+ /**
+ * Overrides the <code>field-is-required</code> bundle key.
+ * Parameter {0} is the display name of the field.
+ *
+ * @since 3.0
+ */
+
+ public void setRequiredMessage(String string)
+ {
+ _requiredMessage = string;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.java
new file mode 100644
index 0000000..74e17e9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.java
@@ -0,0 +1,351 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Provides input validation for strings treated as dates. In addition,
+ * allows a minimum and maximum date to be set.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class DateValidator extends BaseValidator
+{
+ private DateFormat _format;
+ private String _displayFormat;
+ private Date _minimum;
+ private Date _maximum;
+ private Calendar _calendar;
+ private String _scriptPath = "/org/apache/tapestry/valid/DateValidator.script";
+
+ private static DateFormat defaultDateFormat = new SimpleDateFormat("MM/dd/yyyy");
+ private static final String defaultDateDisplayFormat = "MM/DD/YYYY";
+
+ private String _dateTooEarlyMessage;
+ private String _dateTooLateMessage;
+ private String _invalidDateFormatMessage;
+
+ public void setFormat(DateFormat value)
+ {
+ _format = value;
+ }
+
+ public DateFormat getFormat()
+ {
+ return _format;
+ }
+
+ /**
+ * @return the {@link DateFormat} the validator will use, returning the default if no
+ * other date format is specified via {@link #setFormat(DateFormat)}
+ *
+ * @since 3.0
+ */
+ public DateFormat getEffectiveFormat()
+ {
+ if (_format == null)
+ return defaultDateFormat;
+
+ return _format;
+ }
+
+ public String getDisplayFormat()
+ {
+ return _displayFormat;
+ }
+
+ public void setDisplayFormat(String value)
+ {
+ _displayFormat = value;
+ }
+
+ /**
+ * @return the display format message the validator will use, returning the default if no
+ * other display format message is specified. The default is the {@link SimpleDateFormat#toPattern()}
+ * for {@link SimpleDateFormat}s, or "MM/DD/YYYY" for unknown {@link DateFormat} subclasses.
+ *
+ * @since 3.0
+ */
+ public String getEffectiveDisplayFormat()
+ {
+ if (_displayFormat == null)
+ {
+ DateFormat format = getEffectiveFormat();
+ if (format instanceof SimpleDateFormat)
+ return ((SimpleDateFormat)format).toPattern();
+ else
+ return defaultDateDisplayFormat;
+ }
+
+ return _displayFormat;
+ }
+
+ public String toString(IFormComponent file, Object value)
+ {
+ if (value == null)
+ return null;
+
+ Date date = (Date) value;
+
+ DateFormat format = getEffectiveFormat();
+
+ // DateFormat is not threadsafe, so guard access to it.
+
+ synchronized (format)
+ {
+ return format.format(date);
+ }
+ }
+
+ public Object toObject(IFormComponent field, String value) throws ValidatorException
+ {
+ if (checkRequired(field, value))
+ return null;
+
+ DateFormat format = getEffectiveFormat();
+
+ Date result;
+
+ try
+ {
+ // DateFormat is not threadsafe, so guard access
+ // to it.
+
+ synchronized (format)
+ {
+ result = format.parse(value);
+ }
+
+ if (_calendar == null)
+ _calendar = new GregorianCalendar();
+
+ _calendar.setTime(result);
+
+ // SimpleDateFormat allows two-digit dates to be
+ // entered, i.e., 12/24/66 is Dec 24 0066 ... that's
+ // probably not what is really wanted, so treat
+ // it as an invalid date.
+
+ if (_calendar.get(Calendar.YEAR) < 1000)
+ result = null;
+
+ }
+ catch (ParseException ex)
+ {
+ // ParseException does not include a useful error message
+ // about what's wrong.
+ result = null;
+ }
+
+ if (result == null)
+ throw new ValidatorException(
+ buildInvalidDateFormatMessage(field),
+ ValidationConstraint.DATE_FORMAT);
+
+ // OK, check that the date is in range.
+
+ if (_minimum != null && _minimum.compareTo(result) > 0)
+ throw new ValidatorException(
+ buildDateTooEarlyMessage(field, format.format(_minimum)),
+ ValidationConstraint.TOO_SMALL);
+
+ if (_maximum != null && _maximum.compareTo(result) < 0)
+ throw new ValidatorException(
+ buildDateTooLateMessage(field, format.format(_maximum)),
+ ValidationConstraint.TOO_LARGE);
+
+ return result;
+
+ }
+
+ public Date getMaximum()
+ {
+ return _maximum;
+ }
+
+ public void setMaximum(Date maximum)
+ {
+ _maximum = maximum;
+ }
+
+ public Date getMinimum()
+ {
+ return _minimum;
+ }
+
+ public void setMinimum(Date minimum)
+ {
+ _minimum = minimum;
+ }
+
+ /**
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ if (!(isClientScriptingEnabled() && isRequired()))
+ return;
+
+ Map symbols = new HashMap();
+
+ symbols.put("requiredMessage", buildRequiredMessage(field));
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ public String getScriptPath()
+ {
+ return _scriptPath;
+ }
+
+ /**
+ * Allows a developer to use the existing validation logic with a different client-side
+ * script. This is often sufficient to allow application-specific error presentation
+ * (perhaps by using DHTML to update the content of a <span> tag, or to use
+ * a more sophisticated pop-up window than <code>window.alert()</code>).
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void setScriptPath(String scriptPath)
+ {
+ _scriptPath = scriptPath;
+ }
+
+ /** @since 3.0 */
+
+ public String getDateTooEarlyMessage()
+ {
+ return _dateTooEarlyMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getDateTooLateMessage()
+ {
+ return _dateTooLateMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getInvalidDateFormatMessage()
+ {
+ return _invalidDateFormatMessage;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildInvalidDateFormatMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(
+ _invalidDateFormatMessage,
+ "invalid-date-format",
+ field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName(), getEffectiveDisplayFormat());
+ }
+
+ /** @since 3.0 **/
+
+ protected String buildDateTooEarlyMessage(IFormComponent field, String earliestDate)
+ {
+ String pattern =
+ getPattern(_dateTooEarlyMessage, "date-too-early", field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName(), earliestDate);
+ }
+
+ /** @since 3.0 */
+
+ protected String buildDateTooLateMessage(IFormComponent field, String latestDate)
+ {
+ String pattern =
+ getPattern(_dateTooLateMessage, "date-too-late", field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName(), latestDate);
+ }
+
+ /**
+ * Overrides the bundle key
+ * <code>date-too-early</code>.
+ * Parameter {0} is the display name of the field.
+ * Parameter {1} is the earliest allowed date.
+ *
+ * @since 3.0
+ */
+
+ public void setDateTooEarlyMessage(String string)
+ {
+ _dateTooEarlyMessage = string;
+ }
+
+ /**
+ * Overrides the bundle key
+ * <code>date-too-late</code>.
+ * Parameter {0} is the display name of the field.
+ * Parameter {1} is the latest allowed date.
+ *
+ * @since 3.0
+ */
+
+ public void setDateTooLateMessage(String string)
+ {
+ _dateTooLateMessage = string;
+ }
+
+ /**
+ * Overrides the bundle key
+ * <code>invalid-date-format</code>.
+ * Parameter {0} is the display name of the field.
+ * Parameter {1} is the allowed format.
+ *
+ * @since 3.0
+ */
+
+ public void setInvalidDateFormatMessage(String string)
+ {
+ _invalidDateFormatMessage = string;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.script
new file mode 100644
index 0000000..6ec5098
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/DateValidator.script
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+
+<!--
+
+ Creates a script for validating that a date field is required. Eventually,
+ this will also do client-side input validation.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ requiredMessage: Message to display if the field is required yet blank.
+
+-->
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/valid/Validator.js"/>
+
+<input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes"/>
+<input-symbol key="form" class="org.apache.tapestry.IForm" required="yes"/>
+<input-symbol key="validator" class="org.apache.tapestry.valid.DateValidator" required="yes"/>
+<input-symbol key="requiredMessage" class="java.lang.String"/>
+
+<let key="function" unique="yes">
+validate_${field.name}
+</let>
+
+<body>
+function ${function}()
+{
+ var field = document.${form.name}.${field.name};
+
+ if (field.value.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+
+ return true;
+}
+</body>
+
+</script>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.java
new file mode 100644
index 0000000..303e3cc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.java
@@ -0,0 +1,218 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Simple validation of email strings, to enforce required, and minimum length
+ * (maximum length is enforced in the client browser, by setting a maximum input
+ * length on the text field).
+ *
+ *
+ * @author Malcolm Edgar
+ * @version $Id$
+ * @since 2.3
+ *
+ **/
+
+public class EmailValidator extends BaseValidator
+{
+ private int _minimumLength;
+ private String _minimumLengthMessage;
+ private String _invalidEmailFormatMessage;
+
+ private String _scriptPath = "/org/apache/tapestry/valid/EmailValidator.script";
+
+ public EmailValidator()
+ {
+ }
+
+ private EmailValidator(boolean required)
+ {
+ super(required);
+ }
+
+ public String toString(IFormComponent field, Object value)
+ {
+ if (value == null)
+ return null;
+
+ return value.toString();
+ }
+
+ public Object toObject(IFormComponent field, String input) throws ValidatorException
+ {
+ if (checkRequired(field, input))
+ return null;
+
+ if (_minimumLength > 0 && input.length() < _minimumLength)
+ throw new ValidatorException(
+ buildMinimumLengthMessage(field),
+ ValidationConstraint.MINIMUM_WIDTH);
+
+ if (!isValidEmail(input))
+ throw new ValidatorException(
+ buildInvalidEmailFormatMessage(field),
+ ValidationConstraint.EMAIL_FORMAT);
+
+ return input;
+ }
+
+ public int getMinimumLength()
+ {
+ return _minimumLength;
+ }
+
+ public void setMinimumLength(int minimumLength)
+ {
+ _minimumLength = minimumLength;
+ }
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ if (!isClientScriptingEnabled())
+ return;
+
+ Map symbols = new HashMap();
+
+ Locale locale = field.getPage().getLocale();
+ String displayName = field.getDisplayName();
+
+ if (isRequired())
+ symbols.put("requiredMessage", buildRequiredMessage(field));
+
+ if (_minimumLength > 0)
+ symbols.put("minimumLengthMessage", buildMinimumLengthMessage(field));
+
+ String pattern = getPattern(getInvalidEmailFormatMessage(), "invalid-email-format", locale);
+
+ symbols.put("emailFormatMessage", formatString(pattern, displayName));
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ public String getScriptPath()
+ {
+ return _scriptPath;
+ }
+
+ /**
+ * Allows a developer to use the existing validation logic with a different client-side
+ * script. This is often sufficient to allow application-specific error presentation
+ * (perhaps by using DHTML to update the content of a <span> tag, or to use
+ * a more sophisticated pop-up window than <code>window.alert()</code>).
+ *
+ **/
+
+ public void setScriptPath(String scriptPath)
+ {
+ _scriptPath = scriptPath;
+ }
+
+ /**
+ * Return true if the email format is valid.
+ *
+ * @param email the email string to validate
+ * @return true if the email format is valid
+ */
+
+ protected boolean isValidEmail(String email)
+ {
+ int atIndex = email.indexOf('@');
+
+ if ((atIndex == -1) || (atIndex == 0) || (atIndex == email.length() - 1))
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /** @since 3.0 */
+
+ public String getInvalidEmailFormatMessage()
+ {
+ return _invalidEmailFormatMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getMinimumLengthMessage()
+ {
+ return _minimumLengthMessage;
+ }
+
+ /**
+ * Overrides the <code>invalid-email-format</code>
+ * bundle key.
+ * Parameter {0} is the display name of the field.
+ *
+ * @since 3.0
+ *
+ */
+
+ public void setInvalidEmailFormatMessage(String string)
+ {
+ _invalidEmailFormatMessage = string;
+ }
+
+ /**
+ * Overrides the <code>field-too-short</code> bundle key.
+ * Parameter {0} is the minimum length.
+ * Parameter {1} is the display name of the field.
+ *
+ * @since 3.0
+ *
+ */
+ public void setMinimumLengthMessage(String string)
+ {
+ _minimumLengthMessage = string;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildMinimumLengthMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_minimumLengthMessage, "field-too-short", field.getPage().getLocale());
+
+ return formatString(pattern, Integer.toString(_minimumLength), field.getDisplayName());
+ }
+
+ /** @since 3.0 */
+
+ protected String buildInvalidEmailFormatMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(
+ _invalidEmailFormatMessage,
+ "invalid-email-format",
+ field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName());
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.script
new file mode 100644
index 0000000..4f724b3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/EmailValidator.script
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+
+<!--
+
+ Creates a script for validating that a field is required and/or has a minimum
+ field length.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ requiredMessage: Message to display if the field is required yet blank.
+ minimumLengthMessage: Message to display if the field length is too short.
+
+-->
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/valid/Validator.js"/>
+
+<input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes"/>
+<input-symbol key="form" class="org.apache.tapestry.IForm" required="yes"/>
+<input-symbol key="validator" class="org.apache.tapestry.valid.EmailValidator" required="yes"/>
+<input-symbol key="requiredMessage" class="java.lang.String"/>
+<input-symbol key="minimumLengthMessage" class="java.lang.String"/>
+<input-symbol key="emailFormatMessage" class="java.lang.String"/>
+
+<let key="function" unique="yes">
+validate_${field.name}
+</let>
+
+<body>
+function ${function}()
+{
+ var field = document.${form.name}.${field.name};
+
+ strValue = field.value.replace(/ /g,"");
+
+ field.value = strValue;
+
+<if expression="validator.required">
+ if (strValue.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+</if>
+
+<if-not expression="validator.required">
+ if (strValue.length == 0)
+ return true;
+</if-not>
+
+<if expression="validator.minimumLength">
+ if (strValue.length < ${validator.minimumLength})
+ return validator_invalid_field(field, "${minimumLengthMessage}");
+</if>
+
+ atIndex = strValue.indexOf("@");
+ if ((atIndex == -1) || (atIndex == 0) || (atIndex == strValue.length -1))
+ return validator_invalid_field(field, "${emailFormatMessage}");
+
+ return true;
+}
+</body>
+
+</script>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.java b/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.java
new file mode 100644
index 0000000..b8c3426
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.java
@@ -0,0 +1,109 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.BindingException;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.Form;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Used to label an {@link IFormComponent}. Because such fields
+ * know their displayName (user-presentable name), there's no reason
+ * to hard code the label in a page's HTML template (this also helps
+ * with localization).
+ *
+ * [<a href="../../../../../ComponentReference/FieldLabel.html">Component Reference</a>]
+
+ *
+ * @author Howard Lewis Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class FieldLabel extends AbstractComponent
+{
+ /**
+ * Gets the {@link IFormComponent}
+ * and {@link IValidationDelegate delegate},
+ * then renders the label obtained from the field. Does nothing
+ * when rewinding.
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.isRewinding())
+ return;
+
+ IFormComponent field = getField();
+ String displayName = getDisplayName();
+
+ if (displayName == null)
+ {
+ if (field == null)
+ throw Tapestry.createRequiredParameterException(this, "field");
+
+ displayName = field.getDisplayName();
+
+ if (displayName == null)
+ {
+ String msg = Tapestry.format("FieldLabel.no-display-name", field.getExtendedId());
+
+ throw new BindingException(msg, this, null, getBinding("field"), null);
+ }
+ }
+
+ IForm form = Form.get(cycle);
+
+ if (form == null)
+ {
+ String msg = Tapestry.getMessage("FieldLabel.must-be-contained-by-form");
+
+ throw new ApplicationRuntimeException(msg, this, null, null);
+ }
+
+ IValidationDelegate delegate = form.getDelegate();
+
+ if (delegate == null)
+ {
+ String msg =
+ Tapestry.format("FieldLabel.no-delegate", getExtendedId(), form.getExtendedId());
+
+ throw new ApplicationRuntimeException(msg, this, null, null);
+ }
+
+ delegate.writeLabelPrefix(field, writer, cycle);
+
+ if (getRaw()) {
+ writer.printRaw(displayName);
+ } else {
+ writer.print(displayName);
+ }
+
+ delegate.writeLabelSuffix(field, writer, cycle);
+ }
+
+ public abstract String getDisplayName();
+
+ public abstract IFormComponent getField();
+
+ public abstract boolean getRaw();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.jwc b/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.jwc
new file mode 100644
index 0000000..a79043b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/FieldLabel.jwc
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.valid.FieldLabel"
+ allow-body="no"
+ allow-informal-parameters="no">
+
+ <description>
+ Labels a ValidField.
+ </description>
+
+ <parameter name="field" type="org.apache.tapestry.form.IFormComponent" required="yes" direction="in"/>
+
+ <parameter name="displayName" type="java.lang.String" direction="in">
+ <description>
+ Optional. Defaults to the displayName of the associated field.
+ </description>
+ </parameter>
+
+ <parameter name="raw" type="boolean" direction="in">
+ <description>
+ If false (the default), then HTML characters in the value are escaped. If
+ true, then value is emitted exactly as is.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/FieldTracking.java b/tapestry-framework/src/org/apache/tapestry/valid/FieldTracking.java
new file mode 100644
index 0000000..6471979
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/FieldTracking.java
@@ -0,0 +1,106 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Default implementation of {@link IFieldTracking}.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class FieldTracking implements IFieldTracking
+{
+ private IFormComponent _component;
+ private String _input;
+ private IRender _renderer;
+ private String _fieldName;
+ private ValidationConstraint _constraint;
+
+ /**
+ * Constructor used for unassociated errors; errors that are not about any particular
+ * field within the form.
+ *
+ **/
+
+ FieldTracking()
+ {
+ }
+
+ /**
+ * Standard constructor for a field (with the given name), rendered
+ * by the specified component.
+ *
+ **/
+
+ FieldTracking(String fieldName, IFormComponent component)
+ {
+ _fieldName = fieldName;
+ _component = component;
+ }
+
+ public IFormComponent getComponent()
+ {
+ return _component;
+ }
+
+ public IRender getErrorRenderer()
+ {
+ return _renderer;
+ }
+
+ public void setErrorRenderer(IRender value)
+ {
+ _renderer = value;
+ }
+
+ public String getInput()
+ {
+ return _input;
+ }
+
+ public void setInput(String value)
+ {
+ _input = value;
+ }
+
+ public String getFieldName()
+ {
+ return _fieldName;
+ }
+
+ public ValidationConstraint getConstraint()
+ {
+ return _constraint;
+ }
+
+ public void setConstraint(ValidationConstraint constraint)
+ {
+ _constraint = constraint;
+ }
+
+ /** @since 3.0 **/
+
+ public boolean isInError()
+ {
+ return _renderer != null;
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/IFieldTracking.java b/tapestry-framework/src/org/apache/tapestry/valid/IFieldTracking.java
new file mode 100644
index 0000000..cdf509b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/IFieldTracking.java
@@ -0,0 +1,80 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Defines the interface for an object that tracks input fields. This interface is now poorly named,
+ * in that it tracks errors that may <em>not</em> be associated with a specific field.
+ * <p>
+ * For each field, a flag is stored indicating if the field is, in fact, in error. The input
+ * supplied by the client is stored so that if the form is re-rendered (as is typically done when
+ * there are input errors), the value entered by the user is displayed back to the user. An error
+ * message renderer is stored; this is an object that can render the error message (it is usually a
+ * {@link org.apache.tapestry.valid.RenderString}wrapper around a simple string).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ */
+
+public interface IFieldTracking
+{
+ /**
+ * Returns true if the field is in error (that is, if it has an error message
+ * {@link #getErrorRenderer() renderer}.
+ */
+
+ public boolean isInError();
+
+ /**
+ * Returns the field component. This may return null if the error is not associated with any
+ * particular field.
+ */
+
+ public IFormComponent getComponent();
+
+ /**
+ * Returns an object that will render the error message. Alternately, the
+ * <code>toString()</code> of the renderer can be used as a simple error message.
+ *
+ * @since 1.0.9
+ */
+
+ public IRender getErrorRenderer();
+
+ /**
+ * Returns the invalid input recorded for the field. This is stored so that, on a subsequent
+ * render, the smae invalid input can be presented to the client to be corrected.
+ */
+
+ public String getInput();
+
+ /**
+ * Returns the name of the field, that is, the name assigned by the form (this will differ from
+ * the component's id when any kind of looping operation is in effect).
+ */
+
+ public String getFieldName();
+
+ /**
+ * Returns the validation constraint that was violated by the input. This may be null if the
+ * constraint isn't known.
+ */
+
+ public ValidationConstraint getConstraint();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/IValidationDelegate.java b/tapestry-framework/src/org/apache/tapestry/valid/IValidationDelegate.java
new file mode 100644
index 0000000..c869537
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/IValidationDelegate.java
@@ -0,0 +1,250 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.util.List;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Interface used to track validation errors in forms and {@link IFormComponent}s (including
+ * {@link org.apache.tapestry.form.AbstractTextField}and its subclasses).
+ * <p>
+ * In addition, controls how fields that are in error are presented (they can be marked in various
+ * ways by the delegate; the default implementation adds two red asterisks to the right of the
+ * field).
+ * <p>
+ * The interface is designed so that a single instance can be shared with many instances of
+ * {@link IFormComponent}.
+ * <p>
+ * Starting with release 1.0.8, this interface was extensively revised (in a non-backwards
+ * compatible way) to move the tracking of errors and invalid values (during a request cycle) to the
+ * delegate. It has evolved from a largely stateless conduit for error messages into a very stateful
+ * tracker of field state.
+ * <p>
+ * Starting with release 1.0.9, this interface was <em>again</em> reworked, to allow tracking of
+ * errors in {@link IFormComponent form components}, and to allow unassociated (with any field)
+ * errors to be tracked.
+ * <p>
+ * <b>Fields vs. Form Components </b> <br>
+ * For most simple forms, these terms are pretty much synonymous. Your form will render normally,
+ * and each form component will render only once. Some of your form components will be
+ * {@link ValidField}components and handle most of their validation internally (with the help of
+ * {@link IValidator}objects). In addition, your form listener may do additional validation and
+ * notify the validation delegate of additional errors, some of which are associated with a
+ * particular field, some of which are unassociated with any particular field.
+ * <p>
+ * But what happens if you use a {@link org.apache.tapestry.components.Foreach}or
+ * {@link org.apache.tapestry.form.ListEdit}inside your form? Some of your components will render
+ * multiple times. In this case you will have multiple <em>fields</em>. Each field will have a
+ * unique field name (you can see this in the generated HTML). It is this field name that the
+ * delegate keys off of, which means that some fields generated by a component may have errors and
+ * some may not, it all works fine (with one exception).
+ * <p>
+ * <b>The Exception </b> <br>
+ * The problem is that a component doesn't know its field name until its <code>render()</code>
+ * method is invoked (at which point, it allocates a unique field name from the
+ * {@link org.apache.tapestry.IForm#getElementId(org.apache.tapestry.form.IFormComponent)}. This is
+ * not a problem for the field or its {@link IValidator}, but screws things up for the
+ * {@link FieldLabel}.
+ * <p>
+ * Typically, the label is rendered <em>before</em> the corresponding form component. Form
+ * components leave their last assigned field name in their
+ * {@link IFormComponent#getName() name property}. So if the form component is in any kind of loop,
+ * the {@link FieldLabel}will key its name, {@link IFormComponent#getDisplayName() display name}
+ * and error status off of its last renderred value. So the moral of the story is don't use
+ * {@link FieldLabel}in this situation.
+ *
+ * @author Howard Lewis Ship
+ */
+
+public interface IValidationDelegate
+{
+ /**
+ * Invoked before other methods to configure the delegate for the given form component. Sets the
+ * current field based on the {@link IFormComponent#getName() name}of the form component (which
+ * is almost always a {@link ValidField}).
+ * <p>
+ * The caller should invoke this with a parameter of null to record unassociated global errors
+ * (errors not associated with any particular field).
+ *
+ * @since 1.0.8
+ */
+
+ public void setFormComponent(IFormComponent component);
+
+ /**
+ * Returns true if the current component is in error (that is, had bad input submitted by the
+ * end user).
+ *
+ * @since 1.0.8
+ */
+
+ public boolean isInError();
+
+ /**
+ * Returns the string submitted by the client as the value for the current field.
+ *
+ * @since 1.0.8
+ */
+
+ public String getFieldInputValue();
+
+ /**
+ * Returns a {@link List}of {@link IFieldTracking}, in default order (the order in which
+ * fields are renderred). A caller should not change the values (the List is immutable). May
+ * return null if no fields are in error.
+ *
+ * @since 1.0.8
+ */
+
+ public List getFieldTracking();
+
+ /**
+ * Resets any tracking information for the current field. This will clear the field's inError
+ * flag, and set its error message and invalid input value to null.
+ *
+ * @since 1.0.8
+ */
+
+ public void reset();
+
+ /**
+ * Clears all tracking information.
+ *
+ * @since 1.0.10
+ */
+
+ public void clear();
+
+ /**
+ * Clears all errors, but maintains user input. This is useful when a form has been submitted
+ * for a semantic other than "process this data". A common example of this is a dependent drop
+ * down list; selecting an option in one drop down list forces a submit to repopulate the
+ * options in a second, dependent drop down list.
+ * <p>
+ * In these cases, the user input provided in the request is maintained, but any errors should
+ * be cleared out (to prevent unwanted error messages and decorations).
+ *
+ * @since 3.0.1
+ */
+
+ public void clearErrors();
+
+ /**
+ * Records the user's input for the current form component. Input should be recorded even if
+ * there isn't an explicit error, since later form-wide validations may discover an error in the
+ * field.
+ *
+ * @since 3.0
+ */
+
+ public void recordFieldInputValue(String input);
+
+ /**
+ * The error notification method, invoked during the rewind phase (that is, while HTTP
+ * parameters are being extracted from the request and assigned to various object properties).
+ * <p>
+ * Typically, the delegate simply invokes {@link #record(String, ValidationConstraint)}or
+ * {@link #record(IRender, ValidationConstraint)}, but special delegates may override this
+ * behavior to provide (in some cases) different error messages or more complicated error
+ * renderers.
+ */
+
+ public void record(ValidatorException ex);
+
+ /**
+ * Records an error in the current field, or an unassociated error if there is no current field.
+ *
+ * @param message
+ * message to display (@see RenderString}
+ * @param constraint
+ * the constraint that was violated, or null if not known
+ * @since 1.0.9
+ */
+
+ public void record(String message, ValidationConstraint constraint);
+
+ /**
+ * Records an error in the current component, or an unassociated error. The maximum flexibility
+ * recorder.
+ *
+ * @param errorRenderer
+ * object that will render the error message (@see RenderString}. The object should
+ * implement a reasonable <code>toString()</code> as well, to allow the error
+ * message to be rendered using an Insert component, or used where full markup is not
+ * allowed.
+ * @param constraint
+ * the constraint that was violated, or null if not known
+ */
+
+ public void record(IRender errorRenderer, ValidationConstraint constraint);
+
+ /**
+ * Invoked before the field is rendered. If the field is in error, the delegate may decorate the
+ * field in some way (to highlight its error state).
+ */
+
+ public void writePrefix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
+ IValidator validator);
+
+ /**
+ * Invoked just before the <input> element is closed. The delegate can write additional
+ * attributes. This is often used to set the CSS class of the field so that it can be displayed
+ * differently, if in error (or required).
+ *
+ * @since 1.0.5
+ */
+
+ public void writeAttributes(IMarkupWriter writer, IRequestCycle cycle,
+ IFormComponent component, IValidator validator);
+
+ /**
+ * Invoked after the form component is rendered, so that the delegate may decorate the form
+ * component (if it is in error).
+ */
+
+ public void writeSuffix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
+ IValidator validator);
+
+ /**
+ * Invoked by a {@link FieldLabel}just before writing the name of the form component.
+ */
+
+ public void writeLabelPrefix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Invoked by a {@link FieldLabel}just after writing the name of the form component.
+ */
+
+ public void writeLabelSuffix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle);
+
+ /**
+ * Returns true if any form component has errors.
+ */
+
+ public boolean getHasErrors();
+
+ /**
+ * Returns the {@link IFieldTracking}for the current component, if any. Useful when displaying
+ * error messages for individual fields.
+ *
+ * @since 3.0.2
+ */
+ public IFieldTracking getCurrentFieldTracking();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/IValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/IValidator.java
new file mode 100644
index 0000000..5d636d4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/IValidator.java
@@ -0,0 +1,79 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * An object that works with an {@link IFormComponent} to format output
+ * (convert object values to strings values) and to process input
+ * (convert strings to object values and validate them).
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public interface IValidator
+{
+ /**
+ * All validators must implement a required property. If true,
+ * the client must supply a non-null value.
+ *
+ **/
+
+ public boolean isRequired();
+
+ /**
+ * Invoked during rendering to convert an object value (which may be null)
+ * to a String. It is acceptible to return null. The string will be the
+ * VALUE attribute of the HTML text field.
+ *
+ **/
+
+ public String toString(IFormComponent field, Object value);
+
+ /**
+ * Converts input, submitted by the client, into an object value.
+ * May return null if the input is null (and the required flag is false).
+ *
+ * <p>The input string will already have been trimmed. It may be null.
+ *
+ * @throws ValidatorException if the string cannot be converted into
+ * an object, or the object is
+ * not valid (due to other constraints).
+ **/
+
+ public Object toObject(IFormComponent field, String input) throws ValidatorException;
+
+ /**
+ * Invoked by the field after it finishes rendering its tag (but before
+ * the tag is closed) to allow the validator to provide a contribution to the
+ * rendering process. Validators typically generated client-side JavaScript
+ * to peform validation.
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle);
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.java
new file mode 100644
index 0000000..c48fb45
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.java
@@ -0,0 +1,682 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.util.AdaptorRegistry;
+
+/**
+ * Simple validation for standard number classes. This is probably insufficient
+ * for anything tricky and application specific, such as parsing currency.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class NumberValidator extends BaseValidator
+{
+ private static final Map TYPES = new HashMap();
+
+ static {
+ TYPES.put("boolean", boolean.class);
+ TYPES.put("Boolean", Boolean.class);
+ TYPES.put("java.lang.Boolean", Boolean.class);
+ TYPES.put("char", char.class);
+ TYPES.put("Character", Character.class);
+ TYPES.put("java.lang.Character", Character.class);
+ TYPES.put("short", short.class);
+ TYPES.put("Short", Short.class);
+ TYPES.put("java.lang.Short", Short.class);
+ TYPES.put("int", int.class);
+ TYPES.put("Integer", Integer.class);
+ TYPES.put("java.lang.Integer", Integer.class);
+ TYPES.put("long", long.class);
+ TYPES.put("Long", Long.class);
+ TYPES.put("java.lang.Long", Long.class);
+ TYPES.put("float", float.class);
+ TYPES.put("Float", Float.class);
+ TYPES.put("java.lang.Float", Float.class);
+ TYPES.put("byte", byte.class);
+ TYPES.put("Byte", Byte.class);
+ TYPES.put("java.lang.Byte", Byte.class);
+ TYPES.put("double", double.class);
+ TYPES.put("Double", Double.class);
+ TYPES.put("java.lang.Double", Double.class);
+ TYPES.put("java.math.BigInteger", BigInteger.class);
+ TYPES.put("java.math.BigDecimal", BigDecimal.class);
+ }
+
+ private static final Set INT_TYPES = new HashSet();
+
+ private Class _valueTypeClass = int.class;
+
+ private boolean _zeroIsNull;
+ private Number _minimum;
+ private Number _maximum;
+
+ private String _scriptPath = "/org/apache/tapestry/valid/NumberValidator.script";
+
+ private String _invalidNumericFormatMessage;
+ private String _invalidIntegerFormatMessage;
+ private String _numberTooSmallMessage;
+ private String _numberTooLargeMessage;
+ private String _numberRangeMessage;
+
+ private static AdaptorRegistry _numberAdaptors = new AdaptorRegistry();
+
+ public final static int NUMBER_TYPE_INTEGER = 0;
+ public final static int NUMBER_TYPE_REAL = 1;
+
+ /**
+ * This class is not meant for use outside of NumberValidator; it
+ * is public only to fascilitate some unit testing.
+ *
+ */
+ public static abstract class NumberAdaptor
+ {
+ /**
+ * Parses a non-empty {@link String} into the correct subclass of
+ * {@link Number}.
+ *
+ * @throws NumberFormatException if the String can not be parsed.
+ **/
+
+ abstract public Number parse(String value);
+
+ /**
+ * Indicates the type of the number represented -- integer or real.
+ * The information is used to build the client-side validator.
+ * This method could return a boolean, but returns an int to allow
+ * future extensions of the validator.
+ *
+ * @return one of the predefined number types
+ **/
+ abstract public int getNumberType();
+
+ public int compare(Number left, Number right)
+ {
+ if (!left.getClass().equals(right.getClass()))
+ right = coerce(right);
+
+ Comparable lc = (Comparable) left;
+
+ return lc.compareTo(right);
+ }
+
+ /**
+ * Invoked when comparing two Numbers of different types.
+ * The number is cooerced from its ordinary type to
+ * the correct type for comparison.
+ *
+ * @since 3.0
+ */
+ protected abstract Number coerce(Number number);
+ }
+
+ private static abstract class IntegerNumberAdaptor extends NumberAdaptor
+ {
+ public int getNumberType()
+ {
+ return NUMBER_TYPE_INTEGER;
+ }
+ }
+
+ private static abstract class RealNumberAdaptor extends NumberAdaptor
+ {
+ public int getNumberType()
+ {
+ return NUMBER_TYPE_REAL;
+ }
+ }
+
+ private static class ByteAdaptor extends IntegerNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Byte(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Byte(number.byteValue());
+ }
+ }
+
+ private static class ShortAdaptor extends IntegerNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Short(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Short(number.shortValue());
+ }
+ }
+
+ private static class IntAdaptor extends IntegerNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Integer(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Integer(number.intValue());
+ }
+ }
+
+ private static class LongAdaptor extends IntegerNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Long(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Long(number.longValue());
+ }
+ }
+
+ private static class FloatAdaptor extends RealNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Float(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Float(number.floatValue());
+ }
+ }
+
+ private static class DoubleAdaptor extends RealNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new Double(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new Double(number.doubleValue());
+ }
+ }
+
+ private static class BigDecimalAdaptor extends RealNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new BigDecimal(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new BigDecimal(number.doubleValue());
+ }
+ }
+
+ private static class BigIntegerAdaptor extends IntegerNumberAdaptor
+ {
+ public Number parse(String value)
+ {
+ return new BigInteger(value);
+ }
+
+ protected Number coerce(Number number)
+ {
+ return new BigInteger(number.toString());
+ }
+ }
+
+ static {
+ NumberAdaptor byteAdaptor = new ByteAdaptor();
+ NumberAdaptor shortAdaptor = new ShortAdaptor();
+ NumberAdaptor intAdaptor = new IntAdaptor();
+ NumberAdaptor longAdaptor = new LongAdaptor();
+ NumberAdaptor floatAdaptor = new FloatAdaptor();
+ NumberAdaptor doubleAdaptor = new DoubleAdaptor();
+
+ _numberAdaptors.register(Byte.class, byteAdaptor);
+ _numberAdaptors.register(byte.class, byteAdaptor);
+ _numberAdaptors.register(Short.class, shortAdaptor);
+ _numberAdaptors.register(short.class, shortAdaptor);
+ _numberAdaptors.register(Integer.class, intAdaptor);
+ _numberAdaptors.register(int.class, intAdaptor);
+ _numberAdaptors.register(Long.class, longAdaptor);
+ _numberAdaptors.register(long.class, longAdaptor);
+ _numberAdaptors.register(Float.class, floatAdaptor);
+ _numberAdaptors.register(float.class, floatAdaptor);
+ _numberAdaptors.register(Double.class, doubleAdaptor);
+ _numberAdaptors.register(double.class, doubleAdaptor);
+
+ _numberAdaptors.register(BigDecimal.class, new BigDecimalAdaptor());
+ _numberAdaptors.register(BigInteger.class, new BigIntegerAdaptor());
+ }
+
+ public String toString(IFormComponent field, Object value)
+ {
+ if (value == null)
+ return null;
+
+ if (_zeroIsNull)
+ {
+ Number number = (Number) value;
+
+ if (number.doubleValue() == 0.0)
+ return null;
+ }
+
+ return value.toString();
+ }
+
+ private NumberAdaptor getAdaptor(IFormComponent field)
+ {
+ NumberAdaptor result = getAdaptor(_valueTypeClass);
+
+ if (result == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "NumberValidator.no-adaptor-for-field",
+ field,
+ _valueTypeClass.getName()));
+
+ return result;
+ }
+
+ /**
+ * Returns an adaptor for the given type.
+ *
+ * <p>
+ * Note: this method exists only for testing purposes. It is not meant to
+ * be invoked by user code and is subject to change at any time.
+ *
+ * @param type the type (a Number subclass) for which to return an adaptor
+ * @return the adaptor, or null if no such adaptor may be found
+ * @since 3.0
+ */
+ public static NumberAdaptor getAdaptor(Class type)
+ {
+ return (NumberAdaptor) _numberAdaptors.getAdaptor(type);
+ }
+
+ public Object toObject(IFormComponent field, String value) throws ValidatorException
+ {
+ if (checkRequired(field, value))
+ return null;
+
+ NumberAdaptor adaptor = getAdaptor(field);
+ Number result = null;
+
+ try
+ {
+ result = adaptor.parse(value);
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new ValidatorException(
+ buildInvalidNumericFormatMessage(field),
+ ValidationConstraint.NUMBER_FORMAT);
+ }
+
+ if (_minimum != null && adaptor.compare(result, _minimum) < 0)
+ throw new ValidatorException(
+ buildNumberTooSmallMessage(field),
+ ValidationConstraint.TOO_SMALL);
+
+ if (_maximum != null && adaptor.compare(result, _maximum) > 0)
+ throw new ValidatorException(
+ buildNumberTooLargeMessage(field),
+ ValidationConstraint.TOO_LARGE);
+
+ return result;
+ }
+
+ public Number getMaximum()
+ {
+ return _maximum;
+ }
+
+ public boolean getHasMaximum()
+ {
+ return _maximum != null;
+ }
+
+ public void setMaximum(Number maximum)
+ {
+ _maximum = maximum;
+ }
+
+ public Number getMinimum()
+ {
+ return _minimum;
+ }
+
+ public boolean getHasMinimum()
+ {
+ return _minimum != null;
+ }
+
+ public void setMinimum(Number minimum)
+ {
+ _minimum = minimum;
+ }
+
+ /**
+ * If true, then when rendering, a zero is treated as a non-value, and null is returned.
+ * If false, the default, then zero is rendered as zero.
+ *
+ **/
+
+ public boolean getZeroIsNull()
+ {
+ return _zeroIsNull;
+ }
+
+ public void setZeroIsNull(boolean zeroIsNull)
+ {
+ _zeroIsNull = zeroIsNull;
+ }
+
+ /**
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ if (!isClientScriptingEnabled())
+ return;
+
+ if (!(isRequired() || _minimum != null || _maximum != null))
+ return;
+
+ Map symbols = new HashMap();
+
+ if (isRequired())
+ symbols.put("requiredMessage", buildRequiredMessage(field));
+
+ if (isIntegerNumber())
+ symbols.put("formatMessage", buildInvalidIntegerFormatMessage(field));
+ else
+ symbols.put("formatMessage", buildInvalidNumericFormatMessage(field));
+
+ if (_minimum != null || _maximum != null)
+ symbols.put("rangeMessage", buildRangeMessage(field));
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ private String buildRangeMessage(IFormComponent field)
+ {
+ if (_minimum != null && _maximum != null)
+ return buildNumberRangeMessage(field);
+
+ if (_maximum != null)
+ return buildNumberTooLargeMessage(field);
+
+ return buildNumberTooSmallMessage(field);
+ }
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ public String getScriptPath()
+ {
+ return _scriptPath;
+ }
+
+ /**
+ * Allows a developer to use the existing validation logic with a different client-side
+ * script. This is often sufficient to allow application-specific error presentation
+ * (perhaps by using DHTML to update the content of a <span> tag, or to use
+ * a more sophisticated pop-up window than <code>window.alert()</code>).
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void setScriptPath(String scriptPath)
+ {
+ _scriptPath = scriptPath;
+ }
+
+ /** Sets the value type from a string type name. The name may be
+ * a scalar numeric type, a fully qualified class name, or the name
+ * of a numeric wrapper type from java.lang (with the package name omitted).
+ *
+ * @since 3.0
+ *
+ **/
+
+ public void setValueType(String typeName)
+ {
+ Class typeClass = (Class) TYPES.get(typeName);
+
+ if (typeClass == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format("NumberValidator.unknown-type", typeName));
+
+ _valueTypeClass = typeClass;
+ }
+
+ /** @since 3.0 **/
+
+ public void setValueTypeClass(Class valueTypeClass)
+ {
+ _valueTypeClass = valueTypeClass;
+ }
+
+ /**
+ *
+ * Returns the value type to convert strings back into. The default is int.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public Class getValueTypeClass()
+ {
+ return _valueTypeClass;
+ }
+
+ /** @since 3.0 */
+
+ public String getInvalidNumericFormatMessage()
+ {
+ return _invalidNumericFormatMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getInvalidIntegerFormatMessage()
+ {
+ return _invalidIntegerFormatMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getNumberRangeMessage()
+ {
+ return _numberRangeMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getNumberTooLargeMessage()
+ {
+ return _numberTooLargeMessage;
+ }
+
+ /** @since 3.0 */
+
+ public String getNumberTooSmallMessage()
+ {
+ return _numberTooSmallMessage;
+ }
+
+ /**
+ * Overrides the <code>invalid-numeric-format</code> bundle key.
+ * Parameter {0} is the display name of the field.
+ *
+ * @since 3.0
+ */
+
+ public void setInvalidNumericFormatMessage(String string)
+ {
+ _invalidNumericFormatMessage = string;
+ }
+
+ /**
+ * Overrides the <code>invalid-int-format</code> bundle key.
+ * Parameter {0} is the display name of the field.
+ *
+ * @since 3.0
+ */
+
+ public void setInvalidIntegerFormatMessage(String string)
+ {
+ _invalidIntegerFormatMessage = string;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildInvalidNumericFormatMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(
+ getInvalidNumericFormatMessage(),
+ "invalid-numeric-format",
+ field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName());
+ }
+
+ /** @since 3.0 */
+
+ protected String buildInvalidIntegerFormatMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(
+ getInvalidIntegerFormatMessage(),
+ "invalid-int-format",
+ field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName());
+ }
+
+ /**
+ * Overrides the <code>number-range</code> bundle key.
+ * Parameter [0} is the display name of the field.
+ * Parameter {1} is the minimum value.
+ * Parameter {2} is the maximum value.
+ *
+ * @since 3.0
+ */
+
+ public void setNumberRangeMessage(String string)
+ {
+ _numberRangeMessage = string;
+ }
+
+ protected String buildNumberRangeMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_numberRangeMessage, "number-range", field.getPage().getLocale());
+
+ return formatString(pattern, new Object[] { field.getDisplayName(), _minimum, _maximum });
+ }
+
+ /**
+ * Overrides the <code>number-too-large</code> bundle key.
+ * Parameter {0} is the display name of the field.
+ * Parameter {1} is the maximum allowed value.
+ *
+ * @since 3.0
+ */
+
+ public void setNumberTooLargeMessage(String string)
+ {
+ _numberTooLargeMessage = string;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildNumberTooLargeMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_numberTooLargeMessage, "number-too-large", field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName(), _maximum);
+ }
+
+ /**
+ * Overrides the <code>number-too-small</code> bundle key.
+ * Parameter {0} is the display name of the field.
+ * Parameter {1} is the minimum allowed value.
+ *
+ * @since 3.0
+ *
+ */
+
+ public void setNumberTooSmallMessage(String string)
+ {
+ _numberTooSmallMessage = string;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildNumberTooSmallMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_numberTooSmallMessage, "number-too-small", field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName(), _minimum);
+ }
+
+ /** @since 3.0 */
+
+ public boolean isIntegerNumber()
+ {
+ NumberAdaptor result = (NumberAdaptor) _numberAdaptors.getAdaptor(_valueTypeClass);
+ if (result == null)
+ return false;
+
+ return result.getNumberType() == NUMBER_TYPE_INTEGER;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.script
new file mode 100644
index 0000000..78924da
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/NumberValidator.script
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+
+<!--
+
+ Creates a script for validating that a field is required and/or has a minimum
+ field length.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ formatMessage: Message displayed if the input is not valid.
+ requiredMessage: Message to display if the field is required yet blank.
+ rangeMessage: Message to display if the field is not in the expected range.
+ formatExpression: Regular expression for the field.
+-->
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/valid/Validator.js"/>
+
+<input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes"/>
+<input-symbol key="form" class="org.apache.tapestry.IForm" required="yes"/>
+<input-symbol key="validator" class="org.apache.tapestry.valid.NumberValidator" required="yes"/>
+<input-symbol key="formatMessage" class="java.lang.String" required="yes"/>
+<input-symbol key="requiredMessage" class="java.lang.String"/>
+<input-symbol key="rangeMessage" class="java.lang.String"/>
+
+<let key="function" unique="yes">
+validate_${field.name}
+</let>
+
+<body>
+function ${function}()
+{
+ var field = document.${form.name}.${field.name};
+ var stringValue = field.value;
+<if expression="validator.required">
+ if (stringValue.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+</if>
+<if-not expression="validator.required">
+ if (stringValue.length == 0)
+ return true;
+</if-not>
+ var value = stringValue * 1;
+ if (isNaN(value))
+ return validator_invalid_field(field, "${formatMessage}");
+<if expression="validator.integerNumber">
+ var regex = /\./;
+ if (stringValue.search(regex) != -1)
+ return validator_invalid_field(field, "${formatMessage}");
+</if>
+<if expression="validator.minimum != null">
+ if (value < ${validator.minimum})
+ return validator_invalid_field(field, "${rangeMessage}");
+</if>
+<if expression="validator.maximum != null">
+ if (value > ${validator.maximum})
+ return validator_invalid_field(field, "${rangeMessage}");
+</if>
+ return true;
+}
+</body>
+
+</script>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/PatternDelegate.java b/tapestry-framework/src/org/apache/tapestry/valid/PatternDelegate.java
new file mode 100644
index 0000000..399eeff
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/PatternDelegate.java
@@ -0,0 +1,42 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+/**
+ * Implementations of this interface will provide pattern utility services.
+ *
+ * @author Harish Krishnaswamy
+ * @version $Id$
+ * @since 3.0
+ */
+public interface PatternDelegate
+{
+ /**
+ * Answers the question whether the input string fulfills the pattern string provided.
+ *
+ * @param patternString The pattern that the input string is compared against.
+ * @param input The string under test.
+ * @return Returns true if the pattern exists in the input string; returns false otherwise.
+ */
+ public boolean contains(String patternString, String input);
+
+ /**
+ * Returns the escaped sequence of characters representing the pattern string provided.
+ *
+ * @param patternString The raw sequence of characters that represent the pattern.
+ * @return The escaped sequence of characters that represent the pattern.
+ */
+ public String getEscapedPatternString(String patternString);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.java
new file mode 100644
index 0000000..275aaca
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.java
@@ -0,0 +1,255 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.util.RegexpMatcher;
+
+/**
+ * <p>The validator bean that provides a pattern validation service.
+ *
+ * <p>The actual pattern matching algorithm is provided by the
+ * {@link org.apache.tapestry.valid.PatternDelegate}. This enables the user to provide
+ * custom pattern matching implementations. In the event a custom implementation is not
+ * provided, this validator will use the {@link org.apache.tapestry.util.RegexpMatcher}.
+ *
+ * <p>This validator has the ability to provide client side validation on demand.
+ * To enable client side validation simply set the <code>clientScriptingEnabled</code>
+ * property to <code>true</code>.
+ * The default implementation of the script will be in JavaScript and allows the user to
+ * override this with a custom implementation by setting the path to the custom
+ * script via {@link #setScriptPath(String)}.
+ *
+ * @author Harish Krishnaswamy
+ * @version $Id$
+ * @since 3.0
+ */
+public class PatternValidator extends BaseValidator
+{
+ /**
+ * The pattern that this validator will use to validate the input. The default
+ * pattern is an empty string.
+ */
+ private String _patternString = "";
+
+ /**
+ * A custom message in the event of a validation failure.
+ */
+ private String _patternNotMatchedMessage;
+
+ /**
+ * The object that handles pattern matching.
+ */
+ private PatternDelegate _patternDelegate;
+
+ /**
+ * The location of the script specification for client side validation.
+ */
+ private String _scriptPath = "/org/apache/tapestry/valid/PatternValidator.script";
+
+ /**
+ * Returns custom validation failure message. The default message comes from
+ * <code>ValidationStrings.properties</code> file for key
+ * <code>pattern-not-matched</code>.
+ */
+ public String getPatternNotMatchedMessage()
+ {
+ return _patternNotMatchedMessage;
+ }
+
+ /**
+ * Returns the pattern that this validator uses for validation.
+ */
+ public String getPatternString()
+ {
+ return _patternString;
+ }
+
+ /**
+ * Allows for a custom message to be set typically via the bean specification.
+ */
+ public void setPatternNotMatchedMessage(String message)
+ {
+ _patternNotMatchedMessage = message;
+ }
+
+ /**
+ * Allows the user to change the validation pattern.
+ */
+ public void setPatternString(String pattern)
+ {
+ _patternString = pattern;
+ }
+
+ /**
+ * Static inner class that acts as a delegate to RegexpMatcher and conforms to the
+ * PatternDelegate contract.
+ */
+ private static class RegExpDelegate implements PatternDelegate
+ {
+ private RegexpMatcher _matcher;
+
+ private RegexpMatcher getPatternMatcher()
+ {
+ if (_matcher == null)
+ _matcher = new RegexpMatcher();
+
+ return _matcher;
+ }
+
+ public boolean contains(String patternString, String input)
+ {
+ return getPatternMatcher().contains(patternString, input);
+ }
+
+ public String getEscapedPatternString(String patternString)
+ {
+ return getPatternMatcher().getEscapedPatternString(patternString);
+ }
+ }
+
+ /**
+ * Allows for a custom implementation to do the pattern matching. The default pattern
+ * matching is done with {@link org.apache.tapestry.util.RegexpMatcher}.
+ */
+ public void setPatternDelegate(PatternDelegate patternDelegate)
+ {
+ _patternDelegate = patternDelegate;
+ }
+
+ /**
+ * Returns the custom pattern matcher if one is provided or creates and returns the
+ * default matcher laziliy.
+ */
+ public PatternDelegate getPatternDelegate()
+ {
+ if (_patternDelegate == null)
+ _patternDelegate = new RegExpDelegate();
+
+ return _patternDelegate;
+ }
+
+ /**
+ * @see org.apache.tapestry.valid.IValidator#toString(org.apache.tapestry.form.IFormComponent, java.lang.Object)
+ */
+ public String toString(IFormComponent field, Object value)
+ {
+ if (value == null)
+ return null;
+
+ return value.toString();
+ }
+
+ private String buildPatternNotMatchedMessage(IFormComponent field, String patternString)
+ {
+ String templateMessage =
+ getPattern(
+ _patternNotMatchedMessage,
+ "pattern-not-matched",
+ field.getPage().getLocale());
+
+ return formatString(templateMessage, field.getDisplayName(), patternString);
+ }
+
+ /**
+ * @see org.apache.tapestry.valid.IValidator#toObject(org.apache.tapestry.form.IFormComponent, java.lang.String)
+ */
+ public Object toObject(IFormComponent field, String input) throws ValidatorException
+ {
+ if (checkRequired(field, input))
+ return null;
+
+ boolean matched = false;
+
+ try
+ {
+ matched = getPatternDelegate().contains(_patternString, input);
+ }
+ catch (Throwable t)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "PatternValidator.pattern-match-error",
+ _patternString,
+ field.getDisplayName()),
+ field,
+ field.getLocation(),
+ t);
+ }
+
+ if (!matched)
+ throw new ValidatorException(
+ buildPatternNotMatchedMessage(field, _patternString),
+ ValidationConstraint.PATTERN_MISMATCH);
+
+ return input;
+ }
+
+ /**
+ * Allows for a custom implementation of the client side validation.
+ */
+ public void setScriptPath(String scriptPath)
+ {
+ _scriptPath = scriptPath;
+ }
+
+ /**
+ * @see org.apache.tapestry.valid.IValidator#renderValidatorContribution(org.apache.tapestry.form.IFormComponent, org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ */
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ if (!isClientScriptingEnabled())
+ return;
+
+ Map symbols = new HashMap();
+
+ if (isRequired())
+ symbols.put("requiredMessage", buildRequiredMessage(field));
+
+ symbols.put(
+ "patternNotMatchedMessage",
+ buildPatternNotMatchedMessage(field, getEscapedPatternString()));
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ /**
+ * Returns the escaped sequence of the pattern string for rendering in the error message.
+ */
+ public String getEscapedPatternString()
+ {
+ return getPatternDelegate().getEscapedPatternString(_patternString);
+ }
+
+ public String toString()
+ {
+ return "Pattern: "
+ + _patternString
+ + "; Script Path: "
+ + _scriptPath
+ + "; Pattern Delegate: "
+ + _patternDelegate;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.script
new file mode 100644
index 0000000..fdaef23
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/PatternValidator.script
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!-- $Id: $ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+<!--
+
+ Creates a script for validating that a field matches a required pattern.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ requiredMessage: Message to display if the field is required yet blank.
+ patternNotMatchedMessage: Message to display if the field does not fulfill the required pattern.
+
+-->
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/valid/Validator.js"/>
+
+<input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes"/>
+<input-symbol key="form" class="org.apache.tapestry.IForm" required="yes"/>
+<input-symbol key="validator" class="org.apache.tapestry.valid.PatternValidator" required="yes"/>
+<input-symbol key="requiredMessage" class="java.lang.String"/>
+<input-symbol key="patternNotMatchedMessage" class="java.lang.String" required="yes"/>
+
+<let key="function" unique="yes">
+ validate_${field.name}
+</let>
+
+<let key="pattern" unique="yes">
+ pattern_${field.name}
+</let>
+
+<body>
+var ${pattern} = new RegExp("${validator.escapedPatternString}");
+
+function ${function}()
+{
+ var field = document.${form.name}.${field.name};
+<if expression="validator.required">
+ if (field.value.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+</if>
+ if (field.value.length > 0 && !${pattern}.test(field.value))
+ return validator_invalid_field(field, "${patternNotMatchedMessage}");
+
+ return true;
+}
+</body>
+
+</script>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/RenderString.java b/tapestry-framework/src/org/apache/tapestry/valid/RenderString.java
new file mode 100644
index 0000000..402aa63
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/RenderString.java
@@ -0,0 +1,83 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * A wrapper around {@link String}that allows the String to be renderred. This is primarily used to
+ * present error messages.
+ *
+ * @author Howard Lewis Ship
+ */
+
+public class RenderString implements IRender
+{
+ private String _string;
+
+ private boolean _raw = false;
+
+ public RenderString(String string)
+ {
+ _string = string;
+ }
+
+ /**
+ * @param string
+ * the string to render
+ * @param raw
+ * if true, the String is rendered as-is, with no filtering. If false (the default),
+ * the String is filtered.
+ */
+
+ public RenderString(String string, boolean raw)
+ {
+ _string = string;
+ _raw = raw;
+ }
+
+ /**
+ * Renders the String to the writer. Does nothing if the string is null. If raw is true, uses
+ * {@link IMarkupWriter#printRaw(String)}, otherwise {@link IMarkupWriter#print(String)}.
+ */
+
+ public void render(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (_string == null)
+ return;
+
+ if (_raw)
+ writer.printRaw(_string);
+ else
+ writer.print(_string);
+ }
+
+ public String getString()
+ {
+ return _string;
+ }
+
+ public boolean isRaw()
+ {
+ return _raw;
+ }
+
+ public String toString()
+ {
+ return _string;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.java
new file mode 100644
index 0000000..a61bdb5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.java
@@ -0,0 +1,168 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * Simple validation of strings, to enforce required, and minimum length
+ * (maximum length is enforced in the client browser, by setting a maximum input
+ * length on the text field).
+ *
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class StringValidator extends BaseValidator
+{
+ private int _minimumLength;
+
+ private String _minimumLengthMessage;
+
+ /** @since 2.2 **/
+
+ private String _scriptPath = "/org/apache/tapestry/valid/StringValidator.script";
+
+ public StringValidator()
+ {
+ }
+
+ private StringValidator(boolean required)
+ {
+ super(required);
+ }
+
+ public String toString(IFormComponent field, Object value)
+ {
+ if (value == null)
+ return null;
+
+ return value.toString();
+ }
+
+ public Object toObject(IFormComponent field, String input) throws ValidatorException
+ {
+ if (checkRequired(field, input))
+ return null;
+
+ if (_minimumLength > 0 && input.length() < _minimumLength)
+ throw new ValidatorException(
+ buildMinimumLengthMessage(field),
+ ValidationConstraint.MINIMUM_WIDTH);
+
+ return input;
+ }
+
+ public int getMinimumLength()
+ {
+ return _minimumLength;
+ }
+
+ public void setMinimumLength(int minimumLength)
+ {
+ _minimumLength = minimumLength;
+ }
+
+ /**
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle)
+ {
+ if (!isClientScriptingEnabled())
+ return;
+
+ if (!(isRequired() || _minimumLength > 0))
+ return;
+
+ Map symbols = new HashMap();
+
+ if (isRequired())
+ symbols.put("requiredMessage", buildRequiredMessage(field));
+
+ if (_minimumLength > 0)
+ symbols.put("minimumLengthMessage", buildMinimumLengthMessage(field));
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ /**
+ * @since 2.2
+ *
+ **/
+
+ public String getScriptPath()
+ {
+ return _scriptPath;
+ }
+
+ /**
+ * Allows a developer to use the existing validation logic with a different client-side
+ * script. This is often sufficient to allow application-specific error presentation
+ * (perhaps by using DHTML to update the content of a <span> tag, or to use
+ * a more sophisticated pop-up window than <code>window.alert()</code>).
+ *
+ * @since 2.2
+ *
+ **/
+
+ public void setScriptPath(String scriptPath)
+ {
+ _scriptPath = scriptPath;
+ }
+
+ /** @since 3.0 */
+ public String getMinimumLengthMessage()
+ {
+ return _minimumLengthMessage;
+ }
+
+ /**
+ * Overrides the <code>field-too-short</code> bundle key.
+ * Parameter {0} is the minimum length.
+ * Parameter {1} is the display name of the field.
+ *
+ * @since 3.0
+ */
+
+ public void setMinimumLengthMessage(String string)
+ {
+ _minimumLengthMessage = string;
+ }
+
+ /** @since 3.0 */
+
+ protected String buildMinimumLengthMessage(IFormComponent field)
+ {
+ String pattern =
+ getPattern(_minimumLengthMessage, "field-too-short", field.getPage().getLocale());
+
+ return formatString(pattern, Integer.toString(_minimumLength), field.getDisplayName());
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.script
new file mode 100644
index 0000000..ff508ed
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/StringValidator.script
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- $Id$ -->
+<!DOCTYPE script PUBLIC
+ "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">
+<!--
+
+ Creates a script for validating that a field is required and/or has a minimum
+ field length.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ requiredMessage: Message to display if the field is required yet blank.
+ minimumLengthMessage: Message to display if the field length is too short.
+
+-->
+
+<script>
+
+<include-script resource-path="/org/apache/tapestry/valid/Validator.js"/>
+
+<input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes"/>
+<input-symbol key="form" class="org.apache.tapestry.IForm" required="yes"/>
+<input-symbol key="validator" class="org.apache.tapestry.valid.StringValidator" required="yes"/>
+<input-symbol key="requiredMessage" class="java.lang.String"/>
+<input-symbol key="minimumLengthMessage" class="java.lang.String"/>
+
+<let key="function" unique="yes">
+validate_${field.name}
+</let>
+
+<body>
+function ${function}()
+{
+ var field = document.${form.name}.${field.name};
+<if expression="validator.required">
+ if (field.value.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+</if>
+
+<if-not expression="validator.required">
+ if (field.value.length == 0)
+ return true;
+</if-not>
+
+<if expression="validator.minimumLength">
+ if (field.value.length < ${validator.minimumLength})
+ return validator_invalid_field(field, "${minimumLengthMessage}");
+</if>
+ return true;
+}
+</body>
+
+</script>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.java b/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.java
new file mode 100644
index 0000000..280a900
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.java
@@ -0,0 +1,276 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Vector;
+
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.util.StringSplitter;
+
+/**
+ *
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class UrlValidator extends BaseValidator {
+ private int _minimumLength;
+ private String _minimumLengthMessage;
+ private String _invalidUrlFormatMessage;
+ private String _disallowedProtocolMessage;
+ private Collection _allowedProtocols;
+
+ private String _scriptPath = "/org/apache/tapestry/valid/UrlValidator.script"; //$NON-NLS-1$
+
+ public UrlValidator() {
+ }
+
+ private UrlValidator(boolean required) {
+ super(required);
+ }
+
+ public String toString(IFormComponent field, Object value) {
+ if (value == null)
+ return null;
+
+ return value.toString();
+ }
+
+ public Object toObject(IFormComponent field, String input)
+ throws ValidatorException {
+ if (checkRequired(field, input))
+ return null;
+
+ if (_minimumLength > 0 && input.length() < _minimumLength)
+ throw new ValidatorException(
+ buildMinimumLengthMessage(field),
+ ValidationConstraint.MINIMUM_WIDTH);
+
+ if (!isValidUrl(input))
+ throw new ValidatorException(
+ buildInvalidUrlFormatMessage(field),
+ ValidationConstraint.URL_FORMAT);
+
+ if (!isAllowedProtocol(input)) {
+ throw new ValidatorException(
+ buildDisallowedProtocolMessage(field),
+ ValidationConstraint.DISALLOWED_PROTOCOL);
+ }
+
+ return input;
+ }
+
+ public int getMinimumLength() {
+ return _minimumLength;
+ }
+
+ public void setMinimumLength(int minimumLength) {
+ _minimumLength = minimumLength;
+ }
+
+ public void renderValidatorContribution(
+ IFormComponent field,
+ IMarkupWriter writer,
+ IRequestCycle cycle) {
+ if (!isClientScriptingEnabled())
+ return;
+
+ Map symbols = new HashMap();
+
+ if (isRequired())
+ symbols.put("requiredMessage", buildRequiredMessage(field)); //$NON-NLS-1$
+
+ if (_minimumLength > 0)
+ symbols.put("minimumLengthMessage", //$NON-NLS-1$
+ buildMinimumLengthMessage(field));
+
+ symbols.put("urlFormatMessage", buildInvalidUrlFormatMessage(field)); //$NON-NLS-1$
+
+ symbols.put("urlDisallowedProtocolMessage", //$NON-NLS-1$
+ buildDisallowedProtocolMessage(field));
+
+ symbols.put("urlRegexpProtocols", buildUrlRegexpProtocols()); //$NON-NLS-1$
+
+ processValidatorScript(_scriptPath, cycle, field, symbols);
+ }
+
+ private String buildUrlRegexpProtocols() {
+ if(_allowedProtocols == null) {
+ return null;
+ }
+ String regexp = "/("; //$NON-NLS-1$
+ Iterator iter = _allowedProtocols.iterator();
+ while (iter.hasNext()) {
+ String protocol = (String) iter.next();
+ regexp += protocol;
+ if (iter.hasNext()) {
+ regexp += "|"; //$NON-NLS-1$
+ }
+ }
+ regexp += "):///"; //$NON-NLS-1$
+ return regexp;
+ }
+
+ public String getScriptPath() {
+ return _scriptPath;
+ }
+
+ public void setScriptPath(String scriptPath) {
+ _scriptPath = scriptPath;
+ }
+
+ protected boolean isValidUrl(String url) {
+ boolean bIsValid;
+ try {
+ new URL(url);
+ bIsValid = true;
+ } catch (MalformedURLException mue) {
+ bIsValid = false;
+ }
+ return bIsValid;
+ }
+
+ protected boolean isAllowedProtocol(String url) {
+ boolean bIsAllowed = false;
+ if (_allowedProtocols != null) {
+ URL oUrl;
+ try {
+ oUrl = new URL(url);
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ String actualProtocol = oUrl.getProtocol();
+ Iterator iter = _allowedProtocols.iterator();
+ while (iter.hasNext()) {
+ String protocol = (String) iter.next();
+ if (protocol.equals(actualProtocol)) {
+ bIsAllowed = true;
+ break;
+ }
+ }
+ } else {
+ bIsAllowed = true;
+ }
+ return bIsAllowed;
+ }
+
+ public String getInvalidUrlFormatMessage() {
+ return _invalidUrlFormatMessage;
+ }
+
+ public String getMinimumLengthMessage() {
+ return _minimumLengthMessage;
+ }
+
+ public void setInvalidUrlFormatMessage(String string) {
+ _invalidUrlFormatMessage = string;
+ }
+
+ public String getDisallowedProtocolMessage() {
+ return _disallowedProtocolMessage;
+ }
+
+ public void setDisallowedProtocolMessage(String string) {
+ _disallowedProtocolMessage = string;
+ }
+
+ public void setMinimumLengthMessage(String string) {
+ _minimumLengthMessage = string;
+ }
+
+ protected String buildMinimumLengthMessage(IFormComponent field) {
+ String pattern = getPattern(_minimumLengthMessage, "field-too-short", //$NON-NLS-1$
+ field.getPage().getLocale());
+
+ return formatString(
+ pattern,
+ Integer.toString(_minimumLength),
+ field.getDisplayName());
+ }
+
+ protected String buildInvalidUrlFormatMessage(IFormComponent field) {
+ String pattern = getPattern(_invalidUrlFormatMessage, "invalid-url-format", //$NON-NLS-1$
+ field.getPage().getLocale());
+
+ return formatString(pattern, field.getDisplayName());
+ }
+
+ protected String buildDisallowedProtocolMessage(IFormComponent field) {
+ if(_allowedProtocols == null) {
+ return null;
+ }
+ String pattern = getPattern(_disallowedProtocolMessage, "disallowed-protocol", //$NON-NLS-1$
+ field.getPage().getLocale());
+
+ String allowedProtocols = ""; //$NON-NLS-1$
+ Iterator iter = _allowedProtocols.iterator();
+ while (iter.hasNext()) {
+ String protocol = (String) iter.next();
+ if (!allowedProtocols.equals("")) { //$NON-NLS-1$
+ if(iter.hasNext()) {
+ allowedProtocols += ", "; //$NON-NLS-1$
+ } else {
+ allowedProtocols += " or "; //$NON-NLS-1$
+ }
+ }
+ allowedProtocols += protocol;
+ }
+
+ return formatString(pattern, allowedProtocols);
+ }
+
+ protected String getPattern(String override, String key, Locale locale) {
+ if (override != null)
+ return override;
+
+ ResourceBundle strings;
+ String string;
+ try {
+ strings = ResourceBundle.getBundle("net.sf.cendil.tapestry.valid.ValidationStrings", //$NON-NLS-1$
+ locale);
+ string = strings.getString(key);
+ } catch (Exception exc) {
+ strings = ResourceBundle.getBundle("org.apache.tapestry.valid.ValidationStrings", //$NON-NLS-1$
+ locale);
+ string = strings.getString(key);
+ }
+
+ return string;
+ }
+
+ /**
+ * @param protocols comma separated list of allowed protocols
+ */
+ public void setAllowedProtocols(String protocols) {
+ StringSplitter spliter = new StringSplitter(',');
+ //String[] aProtocols = protocols.split(","); //$NON-NLS-1$
+ String[] aProtocols = spliter.splitToArray(protocols); //$NON-NLS-1$
+ _allowedProtocols = new Vector();
+ for (int i = 0; i < aProtocols.length; i++) {
+ _allowedProtocols.add(aProtocols[i]);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.script b/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.script
new file mode 100644
index 0000000..1795d3d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/UrlValidator.script
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<!DOCTYPE script
+ PUBLIC "-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd"
+>
+
+
+<!--
+ Creates a script for validating that a field is required and/or has a minimum
+ field length.
+
+ Input symbols:
+ field, form, validator: As normal for a validation script.
+ requiredMessage: Message to display if the field is required yet blank.
+ minimumLengthMessage: Message to display if the field length is too short.
+ urlFormatMessage: Message to display if the field value is not a valid URL.
+ urlRegexpProtocols: The regexp to check that the protocol is one of the allowed protocols.
+ urlDisallowedProtocolMessage: Message to display if the field value does not use an allowed protocol.
+
+-->
+<script>
+
+ <include-script resource-path="/org/apache/tapestry/valid/Validator.js" />
+
+ <input-symbol key="field" class="org.apache.tapestry.valid.ValidField" required="yes" />
+ <input-symbol key="form" class="org.apache.tapestry.IForm" required="yes" />
+ <input-symbol key="validator" class="org.apache.tapestry.valid.UrlValidator" required="yes" />
+ <input-symbol key="requiredMessage" class="java.lang.String" />
+ <input-symbol key="minimumLengthMessage" class="java.lang.String" />
+ <input-symbol key="urlFormatMessage" class="java.lang.String" />
+ <input-symbol key="urlRegexpProtocols" class="java.lang.String" />
+ <input-symbol key="urlDisallowedProtocolMessage" class="java.lang.String" />
+
+ <let key="function" unique="yes">validate_${field.name}</let>
+
+ <body>
+ function ${function}() {
+ var field = document.${form.name}.${field.name};
+ strValue = field.value.replace(/ /g,"");
+ field.value = strValue;
+
+ <if expression="validator.required">
+ if (strValue.length == 0)
+ return validator_invalid_field(field, "${requiredMessage}");
+ </if>
+
+ <if-not expression="validator.required">
+ if (strValue.length == 0)
+ return true;
+ </if-not>
+
+ <if expression="validator.minimumLength">
+ if (strValue.length < ${validator.minimumLength})
+ return validator_invalid_field(field, "${minimumLengthMessage}");
+ </if>
+
+ if(!regexpTestUrl(strValue))
+ return validator_invalid_field(field, "${urlFormatMessage}");
+
+ <if expression="null != urlRegexpProtocols">
+ var protoRegExp = ${urlRegexpProtocols};
+ if(!protoRegExp.test(strValue))
+ return validator_invalid_field(field, "${urlDisallowedProtocolMessage}");
+ </if>
+
+ return true;
+ }
+ </body>
+
+</script>
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidField.java b/tapestry-framework/src/org/apache/tapestry/valid/ValidField.java
new file mode 100644
index 0000000..ea85b64
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidField.java
@@ -0,0 +1,197 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.AbstractTextField;
+import org.apache.tapestry.form.Form;
+import org.apache.tapestry.form.IFormComponent;
+import org.apache.tapestry.html.Body;
+
+/**
+ *
+ * A {@link Form} component that creates a text field that
+ * allows for validation of user input and conversion between string and object
+ * values.
+ *
+ * [<a href="../../../../../ComponentReference/ValidField.html">Component Reference</a>]
+ *
+ * <p> A ValidatingTextField uses an {@link IValidationDelegate} to
+ * track errors and an {@link IValidator} to convert between strings and objects
+ * (as well as perform validations). The validation delegate is shared by all validating
+ * text fields in a form, the validator may be shared my multiple elements as desired.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public abstract class ValidField extends AbstractTextField implements IFormComponent
+{
+ public abstract Object getValue();
+ public abstract void setValue(Object value);
+
+ public abstract String getDisplayName();
+
+ /**
+ *
+ * Renders the component, which involves the {@link IValidationDelegate delegate}.
+ *
+ * <p>During a render, the <em>first</em> field rendered that is either
+ * in error, or required but null gets special treatment. JavaScript is added
+ * to select that field (such that the cursor jumps right to the field when the
+ * page loads).
+ *
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+ IValidationDelegate delegate = form.getDelegate();
+
+ if (delegate == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.format(
+ "ValidField.no-delegate",
+ getExtendedId(),
+ getForm().getExtendedId()),
+ this,
+ null,
+ null);
+
+ IValidator validator = getValidator();
+
+ if (validator == null)
+ throw Tapestry.createRequiredParameterException(this, "validator");
+
+ boolean rendering = !cycle.isRewinding();
+
+ if (rendering)
+ delegate.writePrefix(writer, cycle, this, validator);
+
+ super.renderComponent(writer, cycle);
+
+ if (rendering)
+ delegate.writeSuffix(writer, cycle, this, validator);
+
+ // If rendering and there's either an error in the field,
+ // then we may have identified the default field (which will
+ // automatically receive focus).
+
+ if (rendering && delegate.isInError())
+ addSelect(cycle);
+
+ // That's OK, but an ideal situation would know about non-validating
+ // text fields, and also be able to put the cursor in the
+ // first field, period (even if there are no required or error fields).
+ // Still, this pretty much rocks!
+
+ }
+
+ /**
+ * Invokes {@link IValidationDelegate#writeAttributes(IMarkupWriter,IRequestCycle, IFormComponent,IValidator)}.
+ *
+ **/
+
+ protected void beforeCloseTag(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IValidator validator = getValidator();
+
+ validator.renderValidatorContribution(this, writer, cycle);
+
+ getForm().getDelegate().writeAttributes(writer, cycle, this, validator);
+ }
+
+ private static final String SELECTED_ATTRIBUTE_NAME =
+ "org.apache.tapestry.component.html.valid.SelectedFieldSet";
+
+ /**
+ * Creates JavaScript to set the cursor on the first required or error
+ * field encountered while rendering. This only works if the text field
+ * is wrapped by a {@link Body} component (which is almost always true).
+ *
+ **/
+
+ protected void addSelect(IRequestCycle cycle)
+ {
+ // If some other field has taken the honors, then let it.
+
+ if (cycle.getAttribute(SELECTED_ATTRIBUTE_NAME) != null)
+ return;
+
+ Body body = Body.get(cycle);
+
+ // If not wrapped by a Body, then do nothing.
+
+ if (body == null)
+ return;
+
+ IForm form = Form.get(cycle);
+
+ String formName = form.getName();
+ String textFieldName = getName();
+
+ String fullName = "document." + formName + "." + textFieldName;
+
+ body.addInitializationScript(fullName + ".focus();");
+ body.addInitializationScript(fullName + ".select();");
+
+ // Put a marker in, indicating that the selected field is known.
+
+ cycle.setAttribute(SELECTED_ATTRIBUTE_NAME, Boolean.TRUE);
+ }
+
+ protected String readValue()
+ {
+ IValidationDelegate delegate = getForm().getDelegate();
+
+ if (delegate.isInError())
+ return delegate.getFieldInputValue();
+
+ Object value = getValue();
+ String result = getValidator().toString(this, value);
+
+ if (Tapestry.isBlank(result) && getValidator().isRequired())
+ addSelect(getPage().getRequestCycle());
+
+ return result;
+ }
+
+ protected void updateValue(String value)
+ {
+ Object objectValue = null;
+ IValidationDelegate delegate = getForm().getDelegate();
+
+ delegate.recordFieldInputValue(value);
+
+ try
+ {
+ objectValue = getValidator().toObject(this, value);
+ }
+ catch (ValidatorException ex)
+ {
+ delegate.record(ex);
+ return;
+ }
+
+ setValue(objectValue);
+ }
+
+ public abstract IValidator getValidator();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidField.jwc b/tapestry-framework/src/org/apache/tapestry/valid/ValidField.jwc
new file mode 100644
index 0000000..0011476
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidField.jwc
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.valid.ValidField" allow-body="no">
+
+ <description>
+ A text input field that can validate input.
+ </description>
+
+ <parameter name="value" type="java.lang.Object" required="yes" direction="auto"/>
+
+ <parameter name="disabled" type="boolean" direction="in"/>
+
+ <parameter name="hidden" type="boolean" direction="in"/>
+
+ <parameter name="validator" type="org.apache.tapestry.valid.IValidator" required="yes" direction="auto">
+ <description>
+ Converts value to a string and parses strings back into object values.
+ </description>
+ </parameter>
+
+ <parameter name="displayName" type="java.lang.String" required="yes" direction="auto">
+ <description>
+ Name used by FieldLabel and when generating validation error messages.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="type"/>
+ <reserved-parameter name="value"/>
+ <reserved-parameter name="name"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationConstraint.java b/tapestry-framework/src/org/apache/tapestry/valid/ValidationConstraint.java
new file mode 100644
index 0000000..ddc0146
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationConstraint.java
@@ -0,0 +1,134 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.commons.lang.enum.Enum;
+
+/**
+ * Defines an enumeration of different types of validation constraints
+ * that may be violated.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ *
+ **/
+
+public class ValidationConstraint extends Enum
+{
+ /**
+ * Indicates that no value (or a value consisting only of white space) was
+ * provided for a field that requires a non-null value.
+ *
+ **/
+
+ public static final ValidationConstraint REQUIRED = new ValidationConstraint("REQUIRED");
+
+ /**
+ * Indicates that a non-null value was provided, but that (after removing
+ * leading and trailing whitespace), the value was not long enough.
+ *
+ **/
+
+ public static final ValidationConstraint MINIMUM_WIDTH =
+ new ValidationConstraint("MINUMUM_WIDTH");
+
+ /**
+ * Indicates a general error in converting a String into a Date.
+ *
+ **/
+
+ public static final ValidationConstraint DATE_FORMAT = new ValidationConstraint("DATE_FORMAT");
+
+ /**
+ * Indicates a general error in the format of a string that is
+ * to be interpreted as a email.
+ *
+ **/
+
+ public static final ValidationConstraint EMAIL_FORMAT =
+ new ValidationConstraint("EMAIL_FORMAT");
+
+ /**
+ * Indicates a general error in the format of a string that is
+ * to be interpreted as a number.
+ *
+ **/
+
+ public static final ValidationConstraint NUMBER_FORMAT =
+ new ValidationConstraint("NUMBER_FORMAT");
+
+ /**
+ * Indicates that the value was too small (for a Date, too early).
+ *
+ **/
+
+ public static final ValidationConstraint TOO_SMALL = new ValidationConstraint("TOO_SMALL");
+
+ /**
+ * Indicates that the value was too large (for a Date, too late).
+ *
+ **/
+
+ public static final ValidationConstraint TOO_LARGE = new ValidationConstraint("TOO_LARGE");
+
+ /**
+ * Indicates an error in a string that does not fulfill a pattern.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final ValidationConstraint PATTERN_MISMATCH =
+ new ValidationConstraint("PATTERN_MISMATCH");
+
+ /**
+ * Indicates a consistency error, usually between too different fields.
+ *
+ * @since 3.0
+ *
+ **/
+
+ public static final ValidationConstraint CONSISTENCY = new ValidationConstraint("CONSISTENCY");
+
+ /**
+ * Indicates that a URL is not of the correct format
+ *
+ * @since 3.0
+ */
+
+ public static final ValidationConstraint URL_FORMAT = new ValidationConstraint("URL_FORMAT");
+
+ /**
+ * Indicates that the URL does not use one of the specified protocols
+ *
+ * @since 3.0
+ */
+
+ public static final ValidationConstraint DISALLOWED_PROTOCOL = new ValidationConstraint("DISALLOWED_PROTOCOL");
+
+
+
+ /**
+ * Protected constructor, which allows new constraints to be created
+ * as subclasses.
+ *
+ **/
+
+ protected ValidationConstraint(String name)
+ {
+ super(name);
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationDelegate.java b/tapestry-framework/src/org/apache/tapestry/valid/ValidationDelegate.java
new file mode 100644
index 0000000..6b3c03e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationDelegate.java
@@ -0,0 +1,460 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRender;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.IFormComponent;
+
+/**
+ * A base implementation of {@link IValidationDelegate}that can be used as a helper bean. This
+ * class is often subclassed, typically to override presentation details.
+ *
+ * @author Howard Lewis Ship
+ * @since 1.0.5
+ */
+
+public class ValidationDelegate implements IValidationDelegate
+{
+ private IFormComponent _currentComponent;
+
+ /**
+ * List of {@link FieldTracking}.
+ */
+ private List _trackings;
+
+ /**
+ * A Map of Maps, keyed on the name of the Form. Each inner map contains the trackings for one
+ * form, keyed on component name. Care must be taken, because the inner Map is not always
+ * present.
+ * <p>
+ * Each ultimate {@link FieldTracking}object is also in the _trackings list.
+ */
+
+ private Map _trackingMap;
+
+ public void clear()
+ {
+ _currentComponent = null;
+ _trackings = null;
+ _trackingMap = null;
+ }
+
+ public void clearErrors()
+ {
+ if (_trackings == null)
+ return;
+
+ Iterator i = (Iterator) _trackings.iterator();
+ while (i.hasNext())
+ {
+ FieldTracking ft = (FieldTracking) i.next();
+ ft.setErrorRenderer(null);
+ }
+ }
+
+ /**
+ * If the form component is in error, places a <font color="red"< around it. Note: this
+ * will only work on the render phase after a rewind, and will be confused if components are
+ * inside any kind of loop.
+ */
+
+ public void writeLabelPrefix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (isInError(component))
+ {
+ writer.begin("font");
+ writer.attribute("color", "red");
+ }
+ }
+
+ /**
+ * Closes the <font> element,started by
+ * {@link #writeLabelPrefix(IFormComponent,IMarkupWriter,IRequestCycle)}, if the form component
+ * is in error.
+ */
+
+ public void writeLabelSuffix(IFormComponent component, IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (isInError(component))
+ {
+ writer.end();
+ }
+ }
+
+ /**
+ * Returns the {@link IFieldTracking}for the current component, if any. The
+ * {@link IFieldTracking}is usually created in {@link #record(String, ValidationConstraint)}or
+ * in {@link #record(IRender, ValidationConstraint)}.
+ * <p>
+ * Components may be rendered multiple times, with multiple names (provided by the
+ * {@link org.apache.tapestry.form.Form}, care must be taken that this method is invoked
+ * <em>after</em> the Form has provided a unique {@link IFormComponent#getName()}for the
+ * component.
+ *
+ * @see #setFormComponent(IFormComponent)
+ * @return the {@link FieldTracking}, or null if the field has no tracking.
+ */
+
+ protected FieldTracking getComponentTracking()
+ {
+ if (_trackingMap == null)
+ return null;
+
+ String formName = _currentComponent.getForm().getName();
+
+ Map formMap = (Map) _trackingMap.get(formName);
+
+ if (formMap == null)
+ return null;
+
+ return (FieldTracking) formMap.get(_currentComponent.getName());
+ }
+
+ public void setFormComponent(IFormComponent component)
+ {
+ _currentComponent = component;
+ }
+
+ public boolean isInError()
+ {
+ IFieldTracking tracking = getComponentTracking();
+
+ return tracking != null && tracking.isInError();
+ }
+
+ public String getFieldInputValue()
+ {
+ IFieldTracking tracking = getComponentTracking();
+
+ return tracking == null ? null : tracking.getInput();
+ }
+
+ /**
+ * Returns all the field trackings as an unmodifiable List.
+ */
+
+ public List getFieldTracking()
+ {
+ if (Tapestry.size(_trackings) == 0)
+ return null;
+
+ return Collections.unmodifiableList(_trackings);
+ }
+
+ public void reset()
+ {
+ IFieldTracking tracking = getComponentTracking();
+
+ if (tracking != null)
+ {
+ _trackings.remove(tracking);
+
+ String formName = tracking.getComponent().getForm().getName();
+
+ Map formMap = (Map) _trackingMap.get(formName);
+
+ if (formMap != null)
+ formMap.remove(tracking.getFieldName());
+ }
+ }
+
+ /**
+ * Invokes {@link #record(String, ValidationConstraint)}, or
+ * {@link #record(IRender, ValidationConstraint)}if the
+ * {@link ValidatorException#getErrorRenderer() error renderer property}is not null.
+ */
+
+ public void record(ValidatorException ex)
+ {
+ IRender errorRenderer = ex.getErrorRenderer();
+
+ if (errorRenderer == null)
+ record(ex.getMessage(), ex.getConstraint());
+ else
+ record(errorRenderer, ex.getConstraint());
+ }
+
+ /**
+ * Invokes {@link #record(IRender, ValidationConstraint)}, after wrapping the message parameter
+ * in a {@link RenderString}.
+ */
+
+ public void record(String message, ValidationConstraint constraint)
+ {
+ record(new RenderString(message), constraint);
+ }
+
+ /**
+ * Records error information about the currently selected component, or records unassociated
+ * (with any field) errors.
+ * <p>
+ * Currently, you may have at most one error per <em>field</em> (note the difference between
+ * field and component), but any number of unassociated errors.
+ * <p>
+ * Subclasses may override the default error message (based on other factors, such as the field
+ * and constraint) before invoking this implementation.
+ *
+ * @since 1.0.9
+ */
+
+ public void record(IRender errorRenderer, ValidationConstraint constraint)
+ {
+ FieldTracking tracking = findCurrentTracking();
+
+ // Note that recording two errors for the same field is not advised; the
+ // second will override the first.
+
+ tracking.setErrorRenderer(errorRenderer);
+ tracking.setConstraint(constraint);
+ }
+
+ public void recordFieldInputValue(String input)
+ {
+ FieldTracking tracking = findCurrentTracking();
+
+ tracking.setInput(input);
+ }
+
+ /**
+ * Finds or creates the field tracking for the {@link #setFormComponent(IFormComponent)}current
+ * component. If no current component, an unassociated error is created and returned.
+ *
+ * @since 3.0
+ */
+
+ protected FieldTracking findCurrentTracking()
+ {
+ FieldTracking result = null;
+
+ if (_trackings == null)
+ _trackings = new ArrayList();
+
+ if (_trackingMap == null)
+ _trackingMap = new HashMap();
+
+ if (_currentComponent == null || _currentComponent.getName() == null)
+ {
+ result = new FieldTracking();
+
+ // Add it to the field trackings, but not to the
+ // map.
+
+ _trackings.add(result);
+ }
+ else
+ {
+ result = getComponentTracking();
+
+ if (result == null)
+ {
+ String formName = _currentComponent.getForm().getName();
+
+ Map formMap = (Map) _trackingMap.get(formName);
+
+ if (formMap == null)
+ {
+ formMap = new HashMap();
+ _trackingMap.put(formName, formMap);
+ }
+
+ String fieldName = _currentComponent.getName();
+
+ result = new FieldTracking(fieldName, _currentComponent);
+
+ _trackings.add(result);
+ formMap.put(fieldName, result);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Does nothing. Override in a subclass to decoreate fields.
+ */
+
+ public void writePrefix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
+ IValidator validator)
+ {
+ }
+
+ /**
+ * Does nothing. Override in a subclass to decorate fields.
+ */
+
+ public void writeAttributes(IMarkupWriter writer, IRequestCycle cycle,
+ IFormComponent component, IValidator validator)
+ {
+ }
+
+ /**
+ * Default implementation; if the current field is in error, then a suffix is written. The
+ * suffix is: <code>&nbsp;<font color="red">**</font></code>.
+ */
+
+ public void writeSuffix(IMarkupWriter writer, IRequestCycle cycle, IFormComponent component,
+ IValidator validator)
+ {
+ if (isInError())
+ {
+ writer.printRaw(" ");
+ writer.begin("font");
+ writer.attribute("color", "red");
+ writer.print("**");
+ writer.end();
+ }
+ }
+
+ public boolean getHasErrors()
+ {
+ return getFirstError() != null;
+ }
+
+ /**
+ * A convienience, as most pages just show the first error on the page.
+ * <p>
+ * As of release 1.0.9, this returns an instance of {@link IRender}, not a {@link String}.
+ */
+
+ public IRender getFirstError()
+ {
+ if (Tapestry.size(_trackings) == 0)
+ return null;
+
+ Iterator i = _trackings.iterator();
+
+ while (i.hasNext())
+ {
+ IFieldTracking tracking = (IFieldTracking) i.next();
+
+ if (tracking.isInError())
+ return tracking.getErrorRenderer();
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks to see if the field is in error. This will <em>not</em> work properly in a loop, but
+ * is only used by {@link FieldLabel}. Therefore, using {@link FieldLabel}in a loop (where the
+ * {@link IFormComponent}is renderred more than once) will not provide correct results.
+ */
+
+ protected boolean isInError(IFormComponent component)
+ {
+ if (_trackingMap == null)
+ return false;
+
+ IForm form = component.getForm();
+ // if there is no form, the component cannot have been rewound or rendered into a form yet
+ // so assume it cannot have errors.
+ if (form == null)
+ return false;
+
+ String formName = form.getName();
+ Map formMap = (Map) _trackingMap.get(formName);
+
+ if (formMap == null)
+ return false;
+
+ IFieldTracking tracking = (IFieldTracking) formMap.get(component.getName());
+
+ return tracking != null && tracking.isInError();
+ }
+
+ /**
+ * Returns a {@link List}of {@link IFieldTracking}s. This is the master list of trackings,
+ * except that it omits and trackings that are not associated with a particular field. May
+ * return an empty list, or null.
+ * <p>
+ * Order is not determined, though it is likely the order in which components are laid out on in
+ * the template (this is subject to change).
+ */
+
+ public List getAssociatedTrackings()
+ {
+ int count = Tapestry.size(_trackings);
+
+ if (count == 0)
+ return null;
+
+ List result = new ArrayList(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
+
+ if (tracking.getFieldName() == null)
+ continue;
+
+ result.add(tracking);
+ }
+
+ return result;
+ }
+
+ /**
+ * Like {@link #getAssociatedTrackings()}, but returns only the unassociated trackings.
+ * Unassociated trackings are new (in release 1.0.9), and are why interface
+ * {@link IFieldTracking}is not very well named.
+ * <p>
+ * The trackings are returned in an unspecified order, which (for the moment, anyway) is the
+ * order in which they were added (this could change in the future, or become more concrete).
+ */
+
+ public List getUnassociatedTrackings()
+ {
+ int count = Tapestry.size(_trackings);
+
+ if (count == 0)
+ return null;
+
+ List result = new ArrayList(count);
+
+ for (int i = 0; i < count; i++)
+ {
+ IFieldTracking tracking = (IFieldTracking) _trackings.get(i);
+
+ if (tracking.getFieldName() != null)
+ continue;
+
+ result.add(tracking);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the {@link IFieldTracking}for the current component, if any. Useful
+ * when displaying error messages for individual fields.
+ *
+ * @since 3.0.2
+ */
+ public IFieldTracking getCurrentFieldTracking()
+ {
+ return getComponentTracking();
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings.properties
new file mode 100644
index 0000000..83007bd
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings.properties
@@ -0,0 +1,24 @@
+# $Id$
+
+field-is-required=You must enter a value for {0}.
+field-too-short=You must enter at least {0} characters for {1}.
+
+invalid-date-format=Invalid date format for {0}. Format is {1}.
+invalid-int-format={0} must be an integer value.
+invalid-format={0} is not in a recognized format.
+invalid-numeric-format={0} must be a numeric value.
+
+date-too-early={0} must be on or after {1}.
+date-too-late={0} must be on or before {1}.
+
+number-too-small={0} must not be smaller than {1}.
+number-too-large={0} must not be larger than {1}.
+
+number-range={0} must be between {1} and {2}.
+
+invalid-email-format=Invalid email format for {0}. Format is user@hostname.
+
+pattern-not-matched={0} does not fulfill the required pattern {1}.
+
+invalid-url-format = Invalid URL.
+disallowed-protocol = Disallowed protocol - protocol must be {0}.
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_de.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_de.properties
new file mode 100644
index 0000000..00a4688
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_de.properties
@@ -0,0 +1,17 @@
+# $Id$
+
+field-is-required=Das Eingabefeld ''{0}'' ist ein Pflichtfeld.
+field-too-short=Sie müssen mindestens {0} Zeichen für das Eingabefeld ''{1}'' eingeben.
+
+invalid-date-format=Das Eingabefeld ''{0}'' hat ein falsches Datumsformat (Eingabeformat ist {1}).
+invalid-int-format=Das Eingabefeld ''{0}'' erwartet einen ganzzahligen Wert.
+invalid-format=Das Eingabefeld ''{0}'' hat nicht das gewünschte Format.
+invalid-numeric-format=Das Eingabefeld ''{0}'' erwartet einen numerischen Wert.
+
+date-too-early=Das Datum für das Eingabefeld ''{0}'' kann nur der {1} oder später sein.
+date-too-late=Das Datum für das Eingabefeld ''{0}'' kann nur der {1} oder früher sein.
+
+number-too-small=Der Wert für das Eingabefeld ''{0}'' darf nicht kleiner als {1} sein.
+number-too-large=Der Wert für das Eingabefeld ''{0}'' darf nicht größer als {1} sein.
+
+number-range=Der Wert für das Eingabefeld ''{0}'' darf nur zwischen {1} und {2} liegen.
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_es.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_es.properties
new file mode 100644
index 0000000..a8e9c6d
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_es.properties
@@ -0,0 +1,19 @@
+# $Id$
+
+field-is-required=Tiene que ingresar un valor para {0}.
+field-too-short=Tiene que ingresar al menos {0} caracteres para {1}.
+
+invalid-date-format=Formato de fecha no válido para {0}. El formato es {1}.
+invalid-int-format={0} tiene que ser un valor entero.
+invalid-format={0} no se encuentra en un formato reconocido.
+invalid-numeric-format={0} tiene que ser un valor numérico.
+
+date-too-early={0} tiene que ser actual o después de {1}.
+date-too-late={0} tiene que ser actual o antes de {1}.
+
+number-too-small={0} no puede ser menor que {1}.
+number-too-large={0} no puede ser mayor que {1}.
+
+number-range={0} tiene que estar entre {1} y {2}.
+
+invalid-email-format=Formato de email inválido para {0}. El formato es usuario@servidor.
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_fi.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_fi.properties
new file mode 100644
index 0000000..e40e8cc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_fi.properties
@@ -0,0 +1,24 @@
+# $Id$
+
+field-is-required=Anna syöte kenttään: {0}.
+field-too-short=Kentän {1} arvon minimipituus on {0} merkkiä.
+
+invalid-date-format=Kentän {0} päivämäärä on väärää muotoa. Muoto on {1}.
+invalid-int-format=Kentän {0} arvon pitää olla kokonaisluku.
+invalid-format=Kentän {0} syöte on väärää muotoa.
+invalid-numeric-format=Kentän {0} arvon pitää olla luku.
+
+date-too-early=Kentän {0} päivämäärä ei saa olla ennen {1}.
+date-too-late=Kentän {0} päivämäärä ei saa olla jälkeen {1}.
+
+number-too-small=Kentän {0} arvo ei saa olla pienempi {1}.
+number-too-large=Kentän {0} arvo ei saa olla suurempi kuin {1}.
+
+number-range=Kentän {0} arvon tulee olla välillä {1}-{2}.
+
+invalid-email-format=Sähköpostiosoite kentässä {0} on väärää muotoa. Muoto on tunnus@kone.fi.
+
+pattern-not-matched=Kentän {0} arvo ei ole vaaditussa muodossa {1}.
+
+invalid-url-format = URL on väärää muotoa.
+disallowed-protocol = Protokolla ei kelpaa - protokollan pitää olla {0}.
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_sv.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_sv.properties
new file mode 100644
index 0000000..83059f3
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_sv.properties
@@ -0,0 +1,24 @@
+# $Id$
+
+field-is-required=Du måste skriva in ett värde för {0}.
+field-too-short=Du måste skriva in minst {0} tecken för {1}.
+
+invalid-date-format=Ogilitigt datumformat för {0}. Formatet är {1}.
+invalid-int-format={0} måste vara ett heltalsvärde.
+invalid-format={0} är inte i ett känt format.
+invalid-numeric-format={0} måste vara ett numeriskt värde.
+
+date-too-early={0} måste vara på eller efter {1}.
+date-too-late={0} måste vara på eller före {1}.
+
+number-too-small={0} måste vara mindre än {1}.
+number-too-large={0} måste vara större än {1}.
+
+number-range={0} måste vara mellan {1} och {2}.
+
+invalid-email-format=Ogiltigt e-post address format för {0}. Korrekt format är användare@domän.
+
+pattern-not-matched={0} uppfyller inte det önskade mönstret. {1}.
+
+invalid-url-format=Ogiltig URL.
+disallowed-protocol=Protokollet är inte tillåtet - det måste vara {0}.
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_CN.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_CN.properties
new file mode 100644
index 0000000..b2a8239
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_CN.properties
@@ -0,0 +1,36 @@
+# Copyright 2005 The Apache Software Foundation
+#
+# Licensed 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.
+
+field-is-required=\u8bf7\u8f93\u5165{0}\u7684\u5185\u5bb9\u3002
+field-too-short={1}\u7684\u5185\u5bb9\u4e0d\u80fd\u5c11\u4e8e{0}\u5b57\u7b26\u3002
+
+invalid-date-format={0}\u7684\u65e5\u671f\u683c\u5f0f\u4e0d\u6b63\u786e\uff0c\u6b63\u786e\u683c\u5f0f\u662f{1}\u3002
+invalid-int-format={0}\u7684\u5185\u5bb9\u5fc5\u987b\u662f\u6574\u6570\u3002
+invalid-format=\u65e0\u6cd5\u8bc6\u522b{0}\u7684\u683c\u5f0f\u3002
+invalid-numeric-format={0}\u7684\u5185\u5bb9\u5fc5\u987b\u662f\u6570\u5b57\u3002
+
+date-too-early={0}\u7684\u65e5\u671f\u5fc5\u987b\u5728{1}\u4e4b\u540e\u3002
+date-too-late={0}\u7684\u65e5\u671f\u5fc5\u987b\u5728{1}\u4e4b\u524d\u3002
+
+number-too-small={0}\u7684\u6570\u503c\u4e0d\u80fd\u5c0f\u4e8e{1}\u3002
+number-too-large={0}\u7684\u6570\u503c\u4e0d\u80fd\u5927\u4e8e{1}\u3002
+
+number-range={0}\u7684\u6570\u503c\u5fc5\u987b\u5728{1}\u548c{2}\u4e4b\u95f4\u3002
+
+invalid-email-format=\u7535\u5b50\u90ae\u4ef6\u5730\u5740{0}\u683c\u5f0f\u4e0d\u6b63\u786e\uff0c\u6b63\u786e\u7684\u683c\u5f0f\u662fuser@hostname\u3002
+
+pattern-not-matched={0}\u4e0d\u7b26\u5408\u6240\u8981\u6c42\u7684\u683c\u5f0f{1}\u3002
+
+invalid-url-format = \u9519\u8bef\u7684URL\u3002
+disallowed-protocol = \u4e0d\u88ab\u5141\u8bb8\u7684\u534f\u8bae\uff0c\u5fc5\u987b\u662f{0}\u3002
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_TW.properties b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_TW.properties
new file mode 100644
index 0000000..fde9e7a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidationStrings_zh_TW.properties
@@ -0,0 +1,36 @@
+# Copyright 2005 The Apache Software Foundation
+#
+# Licensed 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.
+
+field-is-required=\u8acb\u8f38\u5165{0}\u6b04\u4f4d\u7684\u5167\u5bb9\u3002
+field-too-short={1}\u6b04\u4f4d\u7684\u5167\u5bb9\u4e0d\u80fd\u5c11\u65bc{0}\u5b57\u5143\u3002
+
+invalid-date-format={0}\u6b04\u4f4d\u7684\u65e5\u671f\u683c\u5f0f\u4e0d\u6b63\u78ba\uff0c\u6b63\u78ba\u683c\u5f0f\u70ba{1}\u3002
+invalid-int-format={0}\u6b04\u4f4d\u7684\u5167\u5bb9\u5fc5\u9808\u662f\u6574\u6578\u3002
+invalid-format=\u7121\u6cd5\u8fa8\u8a8d{0}\u6b04\u4f4d\u7684\u683c\u5f0f\u3002
+invalid-numeric-format={0}\u6b04\u4f4d\u7684\u5167\u5bb9\u5fc5\u9808\u662f\u6578\u5b57\u3002
+
+date-too-early={0}\u6b04\u4f4d\u7684\u65e5\u671f\u5fc5\u9808\u5728{1}\u4e4b\u5f8c\u3002
+date-too-late={0}\u6b04\u4f4d\u7684\u65e5\u671f\u5fc5\u9808\u5728{1}\u4e4b\u524d\u3002
+
+number-too-small={0}\u6b04\u4f4d\u7684\u6578\u5b57\u4e0d\u80fd\u5c0f\u65bc{1}\u3002
+number-too-large={0}\u6b04\u4f4d\u7684\u6578\u5b57\u4e0d\u80fd\u5927\u65bc{1}\u3002
+
+number-range={0}\u6b04\u4f4d\u7684\u6578\u5b57\u5fc5\u9808\u5728{1}\u548c{2}\u4e4b\u9593\u3002
+
+invalid-email-format=\u96fb\u5b50\u90f5\u4ef6\u5730\u5740{0}\u683c\u5f0f\u932f\u8aa4\uff0c\u6b63\u78ba\u7684\u683c\u5f0f\u662fuser@hostname\u3002
+
+pattern-not-matched={0}\u4e0d\u7b26\u5408\u6240\u8981\u6c42\u7684\u6a23\u5f0f{1}\u3002
+
+invalid-url-format = \u932f\u8aa4\u7684URL\u3002
+disallowed-protocol = \u4e0d\u88ab\u5141\u8a31\u7684\u5354\u5b9a\uff0c\u5fc5\u9808\u662f{0}\u3002
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/Validator.js b/tapestry-framework/src/org/apache/tapestry/valid/Validator.js
new file mode 100644
index 0000000..6508436
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/Validator.js
@@ -0,0 +1,18 @@
+// $Id: Validator.js,v 1.1 2002/09/07 13:03:24 hship Exp $
+//
+// Simple functions to support input field validation in Tapestry.
+
+function validator_invalid_field(field, message)
+{
+ field.focus();
+ field.select();
+
+ window.alert(message);
+
+ return false;
+}
+
+function regexpTestUrl(sUrl) {
+ var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/
+ return regexp.test(sUrl);
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/ValidatorException.java b/tapestry-framework/src/org/apache/tapestry/valid/ValidatorException.java
new file mode 100644
index 0000000..c9f0643
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/ValidatorException.java
@@ -0,0 +1,75 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.valid;
+
+import org.apache.tapestry.IRender;
+
+/**
+ * Thrown by a {@link IValidator} when submitted input is not valid.
+ *
+ * @author Howard Lewis Ship
+ * @version $Id$
+ * @since 1.0.8
+ *
+ **/
+
+public class ValidatorException extends Exception
+{
+ private IRender _errorRenderer;
+ private ValidationConstraint _constraint;
+
+ public ValidatorException(String errorMessage)
+ {
+ this(errorMessage, null, null);
+ }
+
+ public ValidatorException(String errorMessage, ValidationConstraint constraint)
+ {
+ this(errorMessage, null, constraint);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param errorMessage the default error message to be used (this may be
+ * overriden by the {@link IValidationDelegate})
+ * @param errorRenderer to use to render the error message (may be null)
+ * @param constraint a validation constraint that has been compromised, or
+ * null if no constraint is applicable
+ *
+ **/
+
+ public ValidatorException(
+ String errorMessage,
+ IRender errorRenderer,
+ ValidationConstraint constraint)
+ {
+ super(errorMessage);
+
+ _errorRenderer = errorRenderer;
+ _constraint = constraint;
+ }
+
+ public ValidationConstraint getConstraint()
+ {
+ return _constraint;
+ }
+
+ /** @since 3.0 **/
+
+ public IRender getErrorRenderer()
+ {
+ return _errorRenderer;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/valid/package.html b/tapestry-framework/src/org/apache/tapestry/valid/package.html
new file mode 100644
index 0000000..f858edf
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/valid/package.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<body>
+
+Components and classes that provide specialized, validating text fields.
+The component {@link org.apache.tapestry.valid.ValidField}
+does most of the work, and is paired with an implementation of
+{@link org.apache.tapestry.valid.IValidator} (often as a helper bean)
+which provides the rules of translation (between object value and string) and validation.
+
+<p>
+Fields can all be set as required or not; most IValidator implementations add additional
+validations, such as fitting the input value between a minimum and maximum value.
+
+
+<p>Fields can also have a {@link org.apache.tapestry.valid.FieldLabel} that reflects the state (normal or error)
+of the field.
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+
+</body>
+</html>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/AbstractPostfield.java b/tapestry-framework/src/org/apache/tapestry/wml/AbstractPostfield.java
new file mode 100644
index 0000000..f1d661a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/AbstractPostfield.java
@@ -0,0 +1,121 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IForm;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.form.AbstractFormComponent;
+
+/**
+ * A base class for building components that correspond to WML postfield elements.
+ * All such components must be wrapped (directly or indirectly) by
+ * a {@link Go} component.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class AbstractPostfield extends AbstractFormComponent
+{
+
+ /**
+ * Returns the {@link org.apache.tapestry.wml.Go} wrapping this component.
+ *
+ * @throws ApplicationRuntimeException if the component is not wrapped by a
+ * {@link org.apache.tapestry.wml.Go}.
+ *
+ **/
+
+ public IForm getForm(IRequestCycle cycle)
+ {
+ IForm result = Go.get(cycle);
+
+ if (result == null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Postfield.must-be-contained-by-go"),
+ this,
+ null,
+ null);
+
+ setForm(result);
+
+ return result;
+ }
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ IForm form = getForm(cycle);
+
+ boolean rewinding = form.isRewinding();
+
+ if (!rewinding && cycle.isRewinding())
+ return;
+
+ String name = form.getElementId(this);
+
+ if (rewinding)
+ {
+ rewind(cycle);
+ return;
+ }
+
+ writer.beginEmpty("postfield");
+
+ writer.attribute("name", name);
+ String varName = getVarName();
+ writer.attributeRaw("value", varName != null ? getEncodedVarName(varName) : "");
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+
+ protected abstract void rewind(IRequestCycle cycle);
+
+ private String getEncodedVarName(String varName)
+ {
+ return "$(" + varName + ")";
+ }
+
+ public boolean isDisabled()
+ {
+ return false;
+ }
+
+ public abstract String getVarName();
+
+ public abstract IBinding getValueBinding();
+
+ public void updateValue(Object value)
+ {
+ getValueBinding().setObject(value);
+ }
+
+ public abstract IForm getForm();
+ public abstract void setForm(IForm form);
+
+ public abstract String getName();
+ public abstract void setName(String name);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Card.java b/tapestry-framework/src/org/apache/tapestry/wml/Card.java
new file mode 100644
index 0000000..9e733fa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Card.java
@@ -0,0 +1,72 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * A deck contains a collection of cards. There is a variety of card types, each specifying a different mode of
+ * user interaction.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Card extends AbstractComponent
+{
+ private static final String ATTRIBUTE_NAME = "org.apache.tapestry.wml.Card";
+
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Card.cards-may-not-nest"),
+ this,
+ null,
+ null);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ writer.begin("card");
+
+ String title = getTitle();
+ if (Tapestry.isNonBlank(title))
+ writer.attribute("title", title);
+
+ renderInformalParameters(writer, cycle);
+
+ IMarkupWriter nestedWriter = writer.getNestedWriter();
+
+ renderBody(nestedWriter, cycle);
+
+ nestedWriter.close();
+
+ writer.end();
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+ }
+
+ public abstract String getTitle();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Card.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Card.jwc
new file mode 100644
index 0000000..4d1101a
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Card.jwc
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Card">
+
+ <description>
+ An WML deck contains a collection of cards. There is a variety of card types, each specifying a different mode
+ of user interaction.
+ </description>
+
+ <parameter name="title" type="java.lang.String" direction="in">
+ <description>
+ The title attribute specifies advisory information about the element. The title may be rendered in a
+ variety of ways by the user agent (eg, suggested bookmark name, pop-up tooltip, etc.).
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Deck.java b/tapestry-framework/src/org/apache/tapestry/wml/Deck.java
new file mode 100644
index 0000000..369dd6b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Deck.java
@@ -0,0 +1,45 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import java.io.OutputStream;
+
+import org.apache.tapestry.AbstractPage;
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * Concrete class for WML decks. Most decks
+ * should be able to simply subclass this, adding new properties and
+ * methods. An unlikely exception would be a deck that was not based
+ * on a template.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 0.2.9
+ *
+ **/
+
+public class Deck extends AbstractPage
+{
+ /**
+ * Returns a new {@link WMLWriter}.
+ *
+ **/
+ public IMarkupWriter getResponseWriter(OutputStream out)
+ {
+ return new WMLWriter(out, getOutputEncoding());
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Do.java b/tapestry-framework/src/org/apache/tapestry/wml/Do.java
new file mode 100644
index 0000000..7bfbed1
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Do.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The do element provides a general mechanism for the user to act upon the current card,
+ * in other words a card-level user interface element.
+ * The representation of the do element is user agent dependent and the author must only assume
+ * that the element is mapped to a unique user interface widget that the user can activate.
+ * For example, the widget mapping may be to a graphically rendered button, a soft or function key, a voice-activated command sequence, or any other interface that has a simple "activate" operation with no inter-operation persistent state.
+ * The do element may appear at both the card and deck-level.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Do extends AbstractComponent
+{
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.begin("do");
+
+ writer.attribute("type", getType());
+
+ String label = getLabel();
+ if (Tapestry.isNonBlank(label))
+ writer.attribute("label", label);
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (render)
+ writer.end();
+ }
+
+ public abstract String getType();
+
+ public abstract String getLabel();
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Do.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Do.jwc
new file mode 100644
index 0000000..f792b03
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Do.jwc
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Do">
+
+ <description>
+ The do element provides a general mechanism for the user to act upon the current card, i.e. a card-level user
+ interface element.
+ </description>
+
+ <parameter name="type" type="java.lang.String" direction="in" required="yes" >
+ <description>
+ The do element type. This attribute provides a hint to the user agent about the author's intended use of
+ the element and how the element should be mapped to a physical user interface construct.
+ Predefined DO types are accept, prev, help, reset, options, delete and unkown.
+ </description>
+ </parameter>
+
+ <parameter name="label" type="java.lang.String" direction="in">
+ <description>
+ If the user agent is able to dynamically label the user interface widget, this attribute specifies a textual
+ string suitable for such labelling.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Go.java b/tapestry-framework/src/org/apache/tapestry/wml/Go.java
new file mode 100644
index 0000000..fa1c0d8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Go.java
@@ -0,0 +1,92 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.ILink;
+import org.apache.tapestry.form.Form;
+import org.apache.tapestry.valid.IValidationDelegate;
+
+/**
+ * The go element declares a go task, indicating navigation to a URI. If the URI
+ * names a WML card or deck, it is displayed.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Go extends Form
+{
+
+ /** @since 3.0 **/
+
+ protected void writeAttributes(IMarkupWriter writer, ILink link)
+ {
+ String method = getMethod();
+
+ writer.begin(getTag());
+ writer.attribute("method", (method == null) ? "post" : method);
+ writer.attribute("href", link.getURL(null, false));
+ }
+
+ /** @since 3.0 **/
+
+ protected void writeHiddenField(IMarkupWriter writer, String name, String value)
+ {
+ writer.beginEmpty("postfield");
+ writer.attribute("name", name);
+ writer.attribute("value", value);
+ writer.println();
+ }
+
+ /**
+ * This component doesn't support event handlers.
+ *
+ **/
+ protected void emitEventHandlers(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ }
+
+ /**
+ * This component doesn't support delegate.
+ *
+ **/
+ public IValidationDelegate getDelegate()
+ {
+ return null;
+ }
+
+ public void setDelegate(IValidationDelegate delegate)
+ {
+ throw new ApplicationRuntimeException(
+ Tapestry.format("unsupported-property", this, "delegate"));
+ }
+
+ protected String getTag()
+ {
+ return "go";
+ }
+
+
+ protected String getDisplayName()
+ {
+ return "Go";
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Go.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Go.jwc
new file mode 100644
index 0000000..d06bf96
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Go.jwc
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Go">
+
+ <description>
+ The go element declares a go task, indicating navigation to a URI.
+ </description>
+
+ <parameter name="method" type="java.lang.String" direction="in">
+ <description>
+ The method used by the form when it is submitted, defaults to POST.
+ </description>
+ </parameter>
+
+ <parameter name="listener" type="org.apache.tapestry.IActionListener" required="no" direction="in">
+ <description>
+ Object invoked when the form is submitted, after all form components have responded
+ to the submission.
+ </description>
+ </parameter>
+
+ <parameter name="stateful" type="boolean" direction="custom">
+ <description>
+ If true (the default), then an active HttpSession is required.
+ </description>
+ </parameter>
+
+ <parameter name="direct" type="boolean" direction="in">
+ <description>
+ If true (the default), then the more efficient direct service is used.
+ If false, then the action service is used.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="href"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/GoLinkRenderer.java b/tapestry-framework/src/org/apache/tapestry/wml/GoLinkRenderer.java
new file mode 100644
index 0000000..ca3f83b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/GoLinkRenderer.java
@@ -0,0 +1,46 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.link.DefaultLinkRenderer;
+import org.apache.tapestry.link.ILinkRenderer;
+
+/**
+ * A subclass of {@link org.apache.tapestry.link.DefaultLinkRenderer} for
+ * the WML Go element.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0
+ **/
+public class GoLinkRenderer extends DefaultLinkRenderer
+{
+
+ /**
+ * A singleton for the go link.
+ **/
+
+ public static final ILinkRenderer SHARED_INSTANCE = new GoLinkRenderer();
+
+ public String getElement()
+ {
+ return "go";
+ }
+
+ public boolean getHasBody()
+ {
+ return false;
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Image.java b/tapestry-framework/src/org/apache/tapestry/wml/Image.java
new file mode 100644
index 0000000..86e6dd9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Image.java
@@ -0,0 +1,59 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IAsset;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * The Image component indicates that an image is to be included in the text flow. Image layout is done within the
+ * context of normal text layout.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Image extends AbstractComponent
+{
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.beginEmpty("img");
+
+ writer.attribute("src", getImage().buildURL(cycle));
+
+ writer.attribute("alt", getAlt());
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+ }
+
+ public abstract IAsset getImage();
+
+ public abstract String getAlt();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Image.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Image.jwc
new file mode 100644
index 0000000..797e67c
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Image.jwc
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Image" allow-body="no">
+
+ <description>
+ Displays a wml image, deriving the source URL for the image from an asset.
+ </description>
+
+ <parameter name="image" type="org.apache.tapestry.IAsset" direction="in" required="yes">
+ <description>
+ The asset to display.
+ </description>
+ </parameter>
+
+ <parameter name="alt" type="java.lang.String" direction="in" required="yes">
+ <description>
+ This attribute specifies an alternative textual representation for the image. This representation is used
+ when the image can not be displayed using any other method
+ </description>
+ </parameter>
+
+ <reserved-parameter name="src"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Input.java b/tapestry-framework/src/org/apache/tapestry/wml/Input.java
new file mode 100644
index 0000000..92a83c5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Input.java
@@ -0,0 +1,90 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The Input element specifies a text entry object.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Input extends AbstractComponent
+{
+
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.beginEmpty("input");
+
+ writer.attribute("type", isHidden() ? "password" : "text");
+
+ writer.attribute("name", getName());
+
+ String title = getTitle();
+ if (Tapestry.isNonBlank(title))
+ writer.attribute("title", title);
+
+ String format = getFormat();
+ if (Tapestry.isNonBlank(format))
+ writer.attribute("format", format);
+
+ boolean emptyok = isEmptyok();
+ if (emptyok != false)
+ writer.attribute("emptyok", emptyok);
+
+ renderInformalParameters(writer, cycle);
+
+ String value = readValue();
+ if (Tapestry.isNonBlank(value))
+ writer.attribute("value", value);
+
+ writer.closeTag();
+ }
+ }
+
+ public abstract String getTitle();
+
+ public abstract String getName();
+
+ public abstract String getFormat();
+
+ public abstract boolean isHidden();
+
+ public abstract boolean isEmptyok();
+
+ public abstract IBinding getValueBinding();
+
+ public String readValue()
+ {
+ return getValueBinding().getString();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Input.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Input.jwc
new file mode 100644
index 0000000..5fc4c88
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Input.jwc
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Input" allow-body="no">
+
+ <description>
+ The Input element specifies a text entry object.
+ </description>
+
+ <parameter name="title" type="java.lang.String" direction="in">
+ <description>
+ The title attribute specifies advisory information about the element. The title may be rendered in a
+ variety of ways by the user agent (eg, suggested bookmark name, pop-up tooltip, etc.).
+ </description>
+ </parameter>
+
+ <parameter name="hidden" type="boolean" direction="in"/>
+
+ <parameter name="name" type="java.lang.String" direction="in" required="yes" >
+ <description>
+ The name attribute specifies a variable name.
+ </description>
+ </parameter>
+
+ <parameter name="format" type="java.lang.String" direction="in">
+ <description>
+ The format attribute specifies an input mask for user input entries. The string consists of mask control
+ characters and static text that is displayed in the input area. The user agent may use the format mask to
+ facilitate accelerated data input. An input mask is only valid when it contains only legal format codes.
+ User agents must ignore invalid masks.
+ The format control characters specify the data format expected to be entered by the user.
+ See the WML specification for valid format control characters.
+ </description>
+ </parameter>
+
+ <parameter name="emptyok" type="boolean" direction="in">
+ <description>
+ The emptyok attribute indicates that this input element accepts empty input although a non-empty format
+ string has been specified.
+ </description>
+ </parameter>
+
+ <parameter name="value" type="java.lang.String" required="yes">
+ <description>
+ Bind value to the variable that should recieve the user input string.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="type"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/NestedWMLWriter.java b/tapestry-framework/src/org/apache/tapestry/wml/NestedWMLWriter.java
new file mode 100644
index 0000000..414526e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/NestedWMLWriter.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import java.io.CharArrayWriter;
+import java.io.PrintWriter;
+
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * Subclass of {@link org.apache.tapestry.wml.WMLWriter} that is nested. A nested writer
+ * buffers its output, then inserts it into its parent writer when it is
+ * closed.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 0.2.9
+ *
+ **/
+
+public class NestedWMLWriter extends WMLWriter
+{
+ private IMarkupWriter _parent;
+ private CharArrayWriter _internalBuffer;
+
+ public NestedWMLWriter(IMarkupWriter parent)
+ {
+ super(parent.getContentType());
+
+ _parent = parent;
+
+ _internalBuffer = new CharArrayWriter();
+
+ setWriter(new PrintWriter(_internalBuffer));
+ }
+
+ /**
+ * Invokes the {@link WMLWriter#close() super-class
+ * implementation}, then gets the data accumulated in the
+ * internal buffer and provides it to the containing writer using
+ * {@link IMarkupWriter#printRaw(char[], int, int)}.
+ *
+ **/
+
+ public void close()
+ {
+ super.close();
+
+ char[] data = _internalBuffer.toCharArray();
+
+ _parent.printRaw(data, 0, data.length);
+
+ _internalBuffer = null;
+ _parent = null;
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.java b/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.java
new file mode 100644
index 0000000..2fc91fa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.java
@@ -0,0 +1,62 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * The onevent element binds a task to a particular intrinsic event for the immediately enclosing element, ie,
+ * specifying an onevent element inside an "XYZ" element associates an intrinsic event binding with the "XYZ" element.
+ * The user agent must ignore any onevent element specifying a type that does not correspond to a legal intrinsic event
+ * for the immediately enclosing element.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class OnEvent extends AbstractComponent
+{
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.begin("onevent");
+
+ writer.attribute("type", getType());
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (render)
+ {
+ writer.end();
+ }
+ }
+
+ public abstract String getType();
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.jwc b/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.jwc
new file mode 100644
index 0000000..576e48b
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/OnEvent.jwc
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.OnEvent">
+
+ <description>
+ The onevent element binds a task to a particular intrinsic event for the immediately enclosing element, ie,
+ specifying an onevent element inside an "XYZ" element associates an intrinsic event binding with the "XYZ"
+ element.
+ The user agent must ignore any onevent element specifying a type that does not correspond to a legal
+ intrinsic event for the immediately enclosing element.
+ </description>
+
+ <parameter name="type" type="java.lang.String" direction="in" required="yes">
+ <description>
+ The type attribute indicates the name of the intrinsic event
+ (ontimer, onenterforward, onenterbackward, onpick).
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Option.java b/tapestry-framework/src/org/apache/tapestry/wml/Option.java
new file mode 100644
index 0000000..e18d7f7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Option.java
@@ -0,0 +1,64 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * This component serves as a container for one item that is listed as a choice in a {@link Select}. A {@link Select}
+ * offers a selection of choices from which usersu may choose one or more items. The select list is created using a
+ * select element which contains a collection of option elements. A string or text describing the item appears between
+ * the opening and closing option tags.
+ *
+ * In order to have a dynamic onpick attribute it is better to use a concrete class of
+ * {@link org.apache.tapestry.link.ILinkRenderer} with the {@link OptionRenderer}.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Option extends AbstractComponent {
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.begin("option");
+
+ String value = getValue();
+ if (Tapestry.isNonBlank(value))
+ writer.attribute("value", value);
+
+ renderInformalParameters(writer, cycle);
+
+ renderBody(writer, cycle);
+
+ writer.end();
+ }
+ }
+
+ public abstract String getValue();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Option.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Option.jwc
new file mode 100644
index 0000000..fdf5cc4
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Option.jwc
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Option">
+
+ <description>
+ This component serves as a container for one item that is listed as a choice in a Select. A Select
+ offers a selection of choices from which usersu may choose one or more items. The select list is created using a
+ select element which contains a collection of option elements. A string or text describing the item appears between
+ the opening and closing option tags.
+ </description>
+
+ <parameter name="value" type="java.lang.String" direction="in">
+ <description>
+ The value attribute specifies the value to be used when setting the name variable. When the user selects
+ this option, the resulting value specified in the value attribute is used to set the Select element's
+ name wml variable.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/OptionRenderer.java b/tapestry-framework/src/org/apache/tapestry/wml/OptionRenderer.java
new file mode 100644
index 0000000..6f41dfc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/OptionRenderer.java
@@ -0,0 +1,49 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.link.DefaultLinkRenderer;
+import org.apache.tapestry.link.ILinkRenderer;
+
+/**
+ * Implementation of {@link org.apache.tapestry.link.ILinkRenderer} for
+ * the WML Option element.
+ *
+ * The value attribute is reserved.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public class OptionRenderer extends DefaultLinkRenderer
+{
+ /**
+ * A singleton for the option link.
+ **/
+
+ public static final ILinkRenderer SHARED_INSTANCE = new OptionRenderer();
+
+ protected String getElement()
+ {
+ return "option";
+ }
+
+ protected String getUrlAttribute()
+ {
+ return "onpick";
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Postfield.java b/tapestry-framework/src/org/apache/tapestry/wml/Postfield.java
new file mode 100644
index 0000000..c24db09
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Postfield.java
@@ -0,0 +1,37 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.IRequestCycle;
+
+/**
+ * The postfield element specifies a field name and value for transmission to an origin server during a URL request.
+ * @see Go
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+
+public abstract class Postfield extends AbstractPostfield
+{
+
+ protected void rewind(IRequestCycle cycle)
+ {
+ String value = cycle.getRequestContext().getParameter(getName());
+ updateValue(value);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Postfield.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Postfield.jwc
new file mode 100644
index 0000000..318e1b8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Postfield.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Postfield" allow-body="no">
+
+ <description>
+ The postfield element specifies a field name and value for transmission to an origin server during an
+ URL request.
+ </description>
+
+ <parameter name="value" type="java.lang.String" required="yes">
+ <description>
+ Bind value to the variable that should recieve the user input string.
+ </description>
+ </parameter>
+
+ <parameter name="name" property-name="varName" type="java.lang.String" direction="in" required="yes">
+ <description>
+ The name attribute specifies an WML variable name.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="value"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.java b/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.java
new file mode 100644
index 0000000..c20cd84
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.java
@@ -0,0 +1,76 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IPropertySelectionModel;
+
+/**
+ * A high level component used to render a drop-down list of options that the user may select.
+ *
+ * Informal parameters are applied to the <select> tag. To have greater control over the <option> tags, you must use
+ * a Select and Option or a concrete class of {@link org.apache.tapestry.link.ILinkRenderer} with the
+ * {@link OptionRenderer}.
+ *
+ * @version $Id$
+ * @author David Solis
+ */
+
+public abstract class PropertySelection extends AbstractComponent
+{
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ IPropertySelectionModel model = getModel();
+
+ writer.begin("select");
+
+ writer.attribute("name", getName());
+
+ renderInformalParameters(writer, cycle);
+
+ writer.println();
+
+ int count = model.getOptionCount();
+
+ for (int i = 0; i < count; i++)
+ {
+
+ writer.begin("option");
+ writer.attribute("value", model.getValue(i));
+
+ writer.print(model.getLabel(i));
+
+ writer.end();
+ writer.println();
+ }
+
+ writer.end();
+ }
+ }
+
+ public abstract IPropertySelectionModel getModel();
+
+ public abstract String getName();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.jwc b/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.jwc
new file mode 100644
index 0000000..8a38fbc
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/PropertySelection.jwc
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.PropertySelection"
+ allow-body="no"
+ allow-informal-parameters="yes">
+
+ <description>
+ Creates an WML select to choose a single property from a list of options.
+ </description>
+
+ <parameter name="name" required="yes" type="java.lang.String" direction="in"/>
+
+ <parameter name="model"
+ type="org.apache.tapestry.form.IPropertySelectionModel"
+ required="yes"
+ direction="auto"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Select.java b/tapestry-framework/src/org/apache/tapestry/wml/Select.java
new file mode 100644
index 0000000..0c092c5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Select.java
@@ -0,0 +1,102 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.ApplicationRuntimeException;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The Select element lets users pick from a list of options. Each option
+ * is specified by an Option element. Each Option element may have one
+ * line of formatted text (which may be wrapped or truncated by the user
+ * agent if too long).
+ *
+ * Unless multiple selections are required it is generally easier to use the {@link PropertySelection} component.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+public abstract class Select extends AbstractComponent
+{
+ /**
+ * Used by the <code>Select</code> to record itself as a
+ * {@link IRequestCycle} attribute, so that the
+ * {@link Option} components it wraps can have access to it.
+ *
+ **/
+
+ private final static String ATTRIBUTE_NAME = "org.apache.tapestry.active.Select";
+
+ /**
+ * @see org.apache.tapestry.AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ if (cycle.getAttribute(ATTRIBUTE_NAME) != null)
+ throw new ApplicationRuntimeException(
+ Tapestry.getMessage("Select.may-not-nest"),
+ this,
+ null,
+ null);
+
+ cycle.setAttribute(ATTRIBUTE_NAME, this);
+
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.begin("select");
+
+ writer.attribute("name", getName());
+
+ String value = getValue();
+ if (Tapestry.isNonBlank(value))
+ writer.attribute("value", value);
+
+ String title = getTitle();
+ if (Tapestry.isNonBlank(title))
+ writer.attribute("title", title);
+
+ boolean multiple = isMultiple();
+ if (multiple)
+ writer.attribute("multiple", multiple);
+
+ renderInformalParameters(writer, cycle);
+ }
+
+ renderBody(writer, cycle);
+
+ if (render)
+ {
+ writer.end();
+ }
+
+ cycle.removeAttribute(ATTRIBUTE_NAME);
+ }
+
+ public abstract boolean isMultiple();
+
+ public abstract String getName();
+
+ public abstract String getValue();
+
+ public abstract String getTitle();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Select.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Select.jwc
new file mode 100644
index 0000000..c9f6041
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Select.jwc
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Select">
+
+ <description>
+ The WMLSelect element lets users pick from a list of options. Each option is specified
+ by an Option element. Each Option element may have one line of formatted text
+ (which may be wrapped or truncated by the user agent if too long).
+ Option elements may be organised into hierarchical groups using the OptionGroup
+ element.
+ </description>
+
+ <parameter name="multiple" type="boolean" direction="in">
+ <description>
+ This attribute indicates that the select list should accept multiple selections.
+ When not set, the select list should only accept a single selected option.
+ </description>
+ </parameter>
+
+ <parameter name="title" type="java.lang.String" direction="in">
+ <description>
+ This attribute specifies a title for this element, which may be used in the
+ presentation of this object.
+ This attribute specifies a title for this element, which may be used in the
+ presentation of this object.
+ </description>
+ </parameter>
+
+ <parameter name="name" type="java.lang.String" direction="in">
+ <description>
+ The name attribute indicates the name of the variable to set with the result
+ of the selection. The variable is set to the string value of the chosen option
+ element, which is specified with the value attribute. The name variable's value
+ is used to pre-select options in the select list.
+ </description>
+ </parameter>
+
+ <parameter name="value" type="java.lang.String" direction="in">
+ <description>
+ The value attribute indicates the default value of the variable named in the
+ name attribute. When the element is displayed, and the variable named in the
+ name attribute is not set, the name variable may be assigned the value
+ specified in the value attribute, depending on the values defined in iname and
+ ivalue. If the name variable already contains a value, the value attribute is
+ ignored. Any application of the default value is done before the list is
+ pre-selected with the value of the name variable.
+ If this element allows the selection of multiple options, the result of the
+ user's choice is a list of all selected values, separated by the semicolon
+ character. The name variable is set with this result. In addition, the value
+ attribute is interpreted as a semicolon-separated list of pre-selected options.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.java b/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.java
new file mode 100644
index 0000000..d2fc1c9
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.java
@@ -0,0 +1,40 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.form.IPropertySelectionModel;
+
+/**
+ * SelectionField specifies a postfield element and it is used to complement the {@link PropertySelection} component.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ **/
+public abstract class SelectionField extends AbstractPostfield
+{
+ protected void rewind(IRequestCycle cycle)
+ {
+ String optionValue = cycle.getRequestContext().getParameter(getName());
+ IPropertySelectionModel model = getModel();
+ Object value = (optionValue == null) ? null : model.translateValue(optionValue);
+
+ updateValue(value);
+ }
+
+ public abstract IPropertySelectionModel getModel();
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.jwc b/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.jwc
new file mode 100644
index 0000000..0f35faa
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/SelectionField.jwc
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.SelectionField" allow-body="no">
+
+ <description>
+ SelectionField specifies a postfield element and it is used to complement the PropertySelection component.
+ </description>
+
+ <parameter name="value" required="yes" type="java.lang.Object"/>
+
+ <parameter name="model"
+ type="org.apache.tapestry.form.IPropertySelectionModel"
+ required="yes"
+ direction="auto"/>
+
+ <parameter name="name" property-name="varName" type="java.lang.String" direction="in" required="yes">
+ <description>
+ The name attribute specifies an WML variable name.
+ </description>
+ </parameter>
+
+ <reserved-parameter name="value"/>
+
+ <property-specification name="name" type="java.lang.String"/>
+ <property-specification name="form" type="org.apache.tapestry.IForm"/>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Setvar.java b/tapestry-framework/src/org/apache/tapestry/wml/Setvar.java
new file mode 100644
index 0000000..57b300e
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Setvar.java
@@ -0,0 +1,69 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The setvar element specifies the variable to set in the current browser context as a side effect of executing a task.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ *
+ */
+
+public abstract class Setvar extends AbstractComponent
+{
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.beginEmpty("setvar");
+
+ writer.attribute("name", getName());
+
+ renderInformalParameters(writer, cycle);
+
+ String value = readValue();
+ if (Tapestry.isNonBlank(value))
+ writer.attribute("value", value);
+ else
+ writer.attribute("value", "");
+
+ writer.closeTag();
+ }
+ }
+
+ public abstract String getName();
+
+ public abstract IBinding getValueBinding();
+
+ public String readValue()
+ {
+ return getValueBinding().getString();
+ }
+
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Setvar.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Setvar.jwc
new file mode 100644
index 0000000..eb17b20
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Setvar.jwc
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Setvar" allow-body="no">
+
+ <description>
+ The setvar element specifies the variable to set in the current browser context as a side effect of executing
+ a task.
+ </description>
+ <parameter name="name" type="java.lang.String" direction="in" required="yes">
+ <description>
+ The name attribute specifies a WML variable name.
+ </description>
+ </parameter>
+
+ <parameter name="value" type="java.lang.String" required="yes">
+ <description>
+ Bind value to the variable that should recieve the user input string.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Timer.java b/tapestry-framework/src/org/apache/tapestry/wml/Timer.java
new file mode 100644
index 0000000..0bba5a7
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Timer.java
@@ -0,0 +1,68 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import org.apache.tapestry.AbstractComponent;
+import org.apache.tapestry.IBinding;
+import org.apache.tapestry.IMarkupWriter;
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+
+/**
+ * The Timer element declares a card timer, which exposes a means of processing inactivity or idle time.
+ * The timer is initialised and started at card entry and is stopped when the card is exited.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 3.0
+ */
+
+public abstract class Timer extends AbstractComponent
+{
+ /**
+ * @see AbstractComponent#renderComponent(IMarkupWriter, IRequestCycle)
+ **/
+
+ protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
+ {
+ boolean render = !cycle.isRewinding();
+
+ if (render)
+ {
+ writer.beginEmpty("timer");
+
+ writer.attribute("name", getName());
+
+ String value = readValue();
+ if (Tapestry.isNonBlank(value))
+ writer.attribute("value", value);
+ else
+ writer.attribute("value", "0");
+
+ renderInformalParameters(writer, cycle);
+
+ writer.closeTag();
+ }
+ }
+
+ public abstract String getName();
+
+ public abstract IBinding getValueBinding();
+
+ public String readValue()
+ {
+ return getValueBinding().getString();
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/Timer.jwc b/tapestry-framework/src/org/apache/tapestry/wml/Timer.jwc
new file mode 100644
index 0000000..3a67805
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/Timer.jwc
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE component-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<component-specification class="org.apache.tapestry.wml.Timer" allow-body="no">
+
+ <description>
+ The Timer element declares a card timer, which exposes a means of processing inactivity or idle time.
+ The timer is initialised and started at card entry and is stopped when the card is exited.
+ </description>
+
+ <parameter name="name" type="java.lang.String" direction="in">
+ <description>
+ The name attribute specifies the name of the variable to be set with the value of the timer. The name
+ variable's value is used to set the timeout period upon timer initialisation. The variable named by the
+ name attribute will be set with the current timer value when the card is exited or when the timer expires.
+ For example, if the timer expires, the name variable is set to a value of "0".
+ </description>
+ </parameter>
+
+ <parameter name="value" type="java.lang.String">
+ <description>
+ The value attribute indicates the default value of the variable named in the name attribute. When the
+ timer is initialised and the variable named in the name attribute is not set, the name variable is assigned
+ the value specified in the value attribute. If the name variable already contains a value, the value
+ attribute is ignored. If the name attribute is not specified, the timeout is always initialised to the
+ value specified in the value attribute.
+ </description>
+ </parameter>
+
+</component-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/WML.library b/tapestry-framework/src/org/apache/tapestry/wml/WML.library
new file mode 100644
index 0000000..4acc557
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/WML.library
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE library-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<library-specification>
+ <property name="org.apache.tapestry.template-extension" value="wml"/>
+
+ <component-type type="Card" specification-path="/org/apache/tapestry/wml/Card.jwc"/>
+ <component-type type="Do" specification-path="/org/apache/tapestry/wml/Do.jwc"/>
+ <component-type type="Go" specification-path="/org/apache/tapestry/wml/Go.jwc"/>
+ <component-type type="Input" specification-path="/org/apache/tapestry/wml/Input.jwc"/>
+ <component-type type="OnEvent" specification-path="/org/apache/tapestry/wml/OnEvent.jwc"/>
+ <component-type type="Postfield" specification-path="/org/apache/tapestry/wml/Postfield.jwc"/>
+ <component-type type="Setvar" specification-path="/org/apache/tapestry/wml/Setvar.jwc"/>
+ <component-type type="Timer" specification-path="/org/apache/tapestry/wml/Timer.jwc"/>
+ <component-type type="Image" specification-path="/org/apache/tapestry/wml/Image.jwc"/>
+ <component-type type="Option" specification-path="/org/apache/tapestry/wml/Option.jwc"/>
+ <component-type type="PropertySelection" specification-path="/org/apache/tapestry/wml/PropertySelection.jwc"/>
+ <component-type type="Select" specification-path="/org/apache/tapestry/wml/Select.jwc"/>
+ <component-type type="SelectionField" specification-path="/org/apache/tapestry/wml/SelectionField.jwc"/>
+
+</library-specification>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/WMLEngine.java b/tapestry-framework/src/org/apache/tapestry/wml/WMLEngine.java
new file mode 100644
index 0000000..5e51101
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/WMLEngine.java
@@ -0,0 +1,89 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import javax.servlet.ServletException;
+
+import org.apache.tapestry.IRequestCycle;
+import org.apache.tapestry.Tapestry;
+import org.apache.tapestry.engine.BaseEngine;
+
+/**
+ * Subclass of {@link BaseEngine} used for WML applications to change the
+ * Exception, StaleLink and StaleSession pages.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public class WMLEngine extends BaseEngine
+{
+ protected void activateExceptionPage(
+ IRequestCycle cycle,
+ org.apache.tapestry.request.ResponseOutputStream output,
+ Throwable cause)
+ throws ServletException
+ {
+ super.activateExceptionPage(cycle, output, cause);
+ // Sometimes the exception page isn't enough
+ reportException(
+ Tapestry.getMessage("AbstractEngine.unable-to-process-client-request"),
+ cause);
+ }
+
+
+ /** @since 3.0 **/
+
+ protected String getExceptionPageName()
+ {
+ return EXCEPTION_PAGE;
+ }
+
+ /** @since 3.0 **/
+
+ protected String getStaleLinkPageName()
+ {
+ return STALE_LINK_PAGE;
+ }
+
+ /** @since 3.0 **/
+
+ protected String getStaleSessionPageName()
+ {
+ return STALE_SESSION_PAGE;
+ }
+
+ /**
+ * The name of the page used for reporting exceptions.
+ *
+ **/
+ private static final String EXCEPTION_PAGE = "WMLException";
+
+ /**
+ * The name of the page used for reporting stale links.
+ *
+ * */
+
+ private static final String STALE_LINK_PAGE = "WMLStaleLink";
+
+ /**
+ * The name of the page used for reporting state sessions.
+ *
+ **/
+
+ private static final String STALE_SESSION_PAGE = "WMLStaleSession";
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/WMLWriter.java b/tapestry-framework/src/org/apache/tapestry/wml/WMLWriter.java
new file mode 100644
index 0000000..53e0398
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/WMLWriter.java
@@ -0,0 +1,116 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml;
+
+import java.io.OutputStream;
+
+import org.apache.tapestry.AbstractMarkupWriter;
+import org.apache.tapestry.IMarkupWriter;
+
+/**
+ * This class is used to create WML output.
+ *
+ * <p>The <code>WMLResponseWriter</code> handles the necessary escaping
+ * of invalid characters.
+ * Specifically, the '$', '<', '>' and '&' characters are properly
+ * converted to their WML entities by the <code>print()</code> methods.
+ * Similar measures are taken by the {@link #attribute(String, String)} method.
+ * Other invalid characters are converted to their numeric entity equivalent.
+ *
+ * <p>This class makes it easy to generate trivial and non-trivial WML pages.
+ * It is also useful to generate WML snippets. It's ability to do simple
+ * formatting is very useful. A JSP may create an instance of the class
+ * and use it as an alternative to the simple-minded <b><%= ... %></b>
+ * construct, espcially because it can handle null more cleanly.
+ *
+ * @version $Id$
+ * @author David Solis
+ * @since 0.2.9
+ *
+ **/
+
+public class WMLWriter extends AbstractMarkupWriter
+{
+
+ private static final String[] entities = new String[64];
+ private static final boolean[] safe = new boolean[128];
+ private static final String SAFE_CHARACTERS =
+ "01234567890"
+ + "abcdefghijklmnopqrstuvwxyz"
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "\t\n\r !\"#%'()*+,-./:;=?@[\\]^_`{|}~";
+
+ static {
+ entities['"'] = """;
+ entities['<'] = "<";
+ entities['>'] = ">";
+ entities['&'] = "&";
+ entities['$'] = "$$";
+
+ int length = SAFE_CHARACTERS.length();
+ for (int i = 0; i < length; i++)
+ safe[SAFE_CHARACTERS.charAt(i)] = true;
+ }
+
+ /**
+ * Creates a response writer for content type "text/vnd.wap.wml".
+ *
+ **/
+
+ public WMLWriter(OutputStream stream)
+ {
+ this(stream, "UTF-8");
+ }
+
+ /**
+ *
+ * @param stream the output stream where to write the text
+ * @param encoding the encoding to be used to generate the output
+ * @since 3.0
+ *
+ **/
+ public WMLWriter(OutputStream stream, String encoding)
+ {
+ this("text/vnd.wap.wml", encoding, stream);
+ }
+
+ public WMLWriter(String contentType, OutputStream stream)
+ {
+ super(safe, entities, contentType, stream);
+ }
+
+ /**
+ *
+ * @param mimeType the MIME type to be used to generate the content type
+ * @param encoding the encoding to be used to generate the output
+ * @param stream the output stream where to write the text
+ * @since 3.0
+ *
+ **/
+ public WMLWriter(String mimeType, String encoding, OutputStream stream)
+ {
+ super(safe, entities, mimeType, encoding, stream);
+ }
+
+ protected WMLWriter(String contentType)
+ {
+ super(safe, entities, contentType);
+ }
+
+ public IMarkupWriter getNestedWriter()
+ {
+ return new NestedWMLWriter(this);
+ }
+}
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/package.html b/tapestry-framework/src/org/apache/tapestry/wml/package.html
new file mode 100644
index 0000000..3575716
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/package.html
@@ -0,0 +1,23 @@
+<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
+<!-- $Id$ -->
+<html>
+<head>
+<title>Tapestry: Web Application Framework</title>
+</head>
+<body>
+
+<p>Classes and components for main elements of the Wireless Markup Language (WML 1.2).
+
+<ul>
+<li>{@link org.apache.tapestry.wml.Deck} is an alternative to
+{@link org.apache.tapestry.html.BasePage} that provides a <code>text/wml</code> response.</li>
+<li>Basic support for WMLScript.</li>
+<li><b>No support for WML style tables.</li>
+</ul>
+
+@author Howard Lewis Ship <a href="mailto:hlship@apache.org">hlship@apache.org</a>
+@author David Solis <a href="mailto:dsolis@apache.org">dsolis@apache.org</a>
+
+</body>
+</html>
+
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.java b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.java
new file mode 100644
index 0000000..28418ab
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.java
@@ -0,0 +1,51 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml.pages;
+
+import org.apache.tapestry.util.exception.ExceptionAnalyzer;
+import org.apache.tapestry.util.exception.ExceptionDescription;
+import org.apache.tapestry.wml.Deck;
+
+/**
+ * Default exception reporting page for WML applications.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+public class WMLException extends Deck
+{
+ private ExceptionDescription[] _exceptions;
+
+ public void initialize()
+ {
+ _exceptions = null;
+ }
+
+ public ExceptionDescription[] getExceptions()
+ {
+ return _exceptions;
+ }
+
+ public void setException(Throwable value)
+ {
+ ExceptionAnalyzer analyzer;
+
+ analyzer = new ExceptionAnalyzer();
+
+ _exceptions = analyzer.analyze(value);
+ }
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.page b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.page
new file mode 100644
index 0000000..964fdd8
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.page
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.wml.pages.WMLException">
+
+ <property name="org.apache.tapestry.template-extension" value="wml"/>
+
+ <property-specification name="current" type="org.apache.tapestry.util.exception.ExceptionDescription"/>
+
+ <component id="restart" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
+ </component>
+
+ <component id="foreachProperty" type="Foreach">
+ <static-binding name="element" value="tr"/>
+ <binding name="source" expression="current.properties"/>
+ </component>
+
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.wml b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.wml
new file mode 100644
index 0000000..2d4a7b2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLException.wml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!-- $Id -->
+ <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.2//EN"
+ "http://www.wapforum.org/DTD/wml12.dtd">
+<wml>
+ <head>
+ <meta http-equiv="Cache-Control" content="must-revalidate" forua="true"/>
+ <meta http-equiv="Cache-Control" content="no-cache" forua="true"/>
+ </head>
+
+ <card id="Exception" title="Exception">
+ <p>
+ An exception has occured.
+ You may continue by <b><a jwcid="restart">restarting</a></b> the session.<br/>
+ <table columns="1">
+ <p jwcid="@Foreach" source="ognl:exceptions" value="ognl:current">
+ <tr><td><b jwcid="@Insert" value="ognl:current.exceptionClassName">some.exception.Class</b></td></tr>
+ <tr><td><b jwcid="@Insert" value="ognl:current.message">A message describing the exception.</b></td></tr>
+ <tr jwcid="foreachProperty">
+ <td><b jwcid="@Insert" value="ognl:components.foreachProperty.value.name">Property Name</b>:<b jwcid="@Insert" value="ognl:components.foreachProperty.value.value">Property Value</b></td>
+ </tr>
+ </p>
+ </table>
+ </p>
+
+ </card>
+</wml>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.java b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.java
new file mode 100644
index 0000000..61b8ebb
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.java
@@ -0,0 +1,33 @@
+// Copyright 2004 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.wml.pages;
+
+import org.apache.tapestry.wml.Deck;
+
+
+/**
+ * Stores a message (taken from the {@link org.apache.tapestry.StaleLinkException})
+ * that is displayed as part of the page.
+ *
+ * @author David Solis
+ * @version $Id$
+ * @since 3.0
+ *
+ **/
+
+public abstract class WMLStaleLink extends Deck
+{
+ public abstract void setMessage(String message);
+}
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.page b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.page
new file mode 100644
index 0000000..0ee9d63
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.page
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.wml.pages.WMLStaleLink">
+
+ <property name="org.apache.tapestry.template-extension" value="wml"/>
+
+ <property-specification name="message" type="java.lang.String"/>
+
+ <component id="home" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@HOME_SERVICE"/>
+ </component>
+
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.wml b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.wml
new file mode 100644
index 0000000..9adb5e2
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleLink.wml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!-- $Id -->
+ <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.2//EN"
+ "http://www.wapforum.org/DTD/wml12.dtd">
+<wml>
+ <head>
+ <meta http-equiv="Cache-Control" content="must-revalidate" forua="true"/>
+ <meta http-equiv="Cache-Control" content="no-cache" forua="true"/>
+ </head>
+
+ <card id="StaleLink" title="StaleLink">
+ <p>You have clicked on a <i>stale link</i>.</p>
+ <p>
+ <b jwcid="@Insert" value="ognl:message">Exception message goes here.</b>
+ </p>
+ <p>
+ This is most likely the result of using your browser's <b>back</b> button, but can also be an application error.
+ </p>
+ <p>
+ You may continue by returning to the application's <b><a jwcid="home">home page</a></b>.
+ </p>
+ </card>
+</wml>
\ No newline at end of file
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.page b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.page
new file mode 100644
index 0000000..718feb5
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.page
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2004 The Apache Software Foundation
+
+ Licensed 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.
+-->
+<!-- $Id$ -->
+<!DOCTYPE page-specification PUBLIC
+ "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
+ "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
+
+<page-specification class="org.apache.tapestry.wml.Deck">
+
+ <property name="org.apache.tapestry.template-extension" value="wml"/>
+
+ <component id="restart" type="ServiceLink">
+ <binding name="service" expression="@org.apache.tapestry.Tapestry@RESTART_SERVICE"/>
+ </component>
+
+</page-specification>
diff --git a/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.wml b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.wml
new file mode 100644
index 0000000..6869804
--- /dev/null
+++ b/tapestry-framework/src/org/apache/tapestry/wml/pages/WMLStaleSession.wml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- $Id -->
+ <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.2//EN"
+ "http://www.wapforum.org/DTD/wml12.dtd">
+<wml>
+ <head>
+ <meta http-equiv="Cache-Control" content="must-revalidate" forua="true"/>
+ <meta http-equiv="Cache-Control" content="no-cache" forua="true"/>
+ </head>
+
+ <card id="StaleSession" title="StaleSession">
+ <p>Your session has timed out.</p>
+ <p>Web applications store information about what you are doing on the server. This information
+ is called the <em>session</em>.</p>
+
+ <p>Web servers must track many, many sessions. If you
+ are inactive for a long enough time (usually, a few minutes), this information is discarded to
+ make room for active users.</p>
+
+ <p>At this point you may <b><a jwcid="restart">restart</a></b> the session to continue.</p>
+ </card>
+</wml>
\ No newline at end of file