blob: 6e88b754c9420641ccdec639bc89503b2e721a5d [file] [log] [blame]
package com.primix.tapestry;
import java.awt.Color;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.*;
import java.io.Writer;
import com.primix.foundation.*;
/*
* Tapestry Web Application Framework
* Copyright (c) 2000 by Howard Ship and Primix Solutions
*
* Primix Solutions
* One Arsenal Marketplace
* Watertown, MA 02472
* http://www.primix.com
* mailto:hship@primix.com
*
* This library is free software.
*
* You may redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation.
*
* Version 2.1 of the license should be included with this distribution in
* the file LICENSE, as well as License.html. If the license is not
* included with this distribution, you may find a copy at the FSF web
* site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
* Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
*/
/**
* This class encapsulates all the relevant data for one request cycle of an
* <code>HttpServlet</code>, that is:
* <ul>
* <li>HttpServletRequest
* <li>HttpServletResponse
* <li>HttpSession
* <li>HttpServlet
* </ul>
* <p>It also provides methods for:
* <ul>
* <li>Retrieving the request parameters
* <li>Getting and setting and removing request attributes
* <li>Getting, setting and removing Session attributes
* <li>Forwarding requests (typically to a JSP for rendering)
* <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>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 Ship
*/
public class RequestContext
{
private HttpSession session;
private HttpServletRequest request;
private HttpServletResponse response;
private HttpServlet servlet;
/**
* A mapping of the cookies available in the request.
*
*/
private Map cookieMap;
private static final int MAP_SIZE = 5;
/**
* Used to contain the parsed, decoded pathInfo.
*
*/
private String[] pathInfo;
private boolean evenRow;
/**
* Identifies which characters are safe in a URL, and do not need any encoding.
*
*/
private static BitSet safe;
static
{
int i;
safe = new BitSet(256);
for (i = 'a'; i <= 'z'; i++)
safe.set(i);
for (i = 'A'; i <= 'Z'; i++)
safe.set(i);
for (i = '0'; i <= '9'; i++)
safe.set(i);
safe.set('.');
safe.set('-');
safe.set('_');
safe.set('*');
}
/**
* Used to quickly convert a 8 bit character value to a hex string.
*/
private static final char HEX[] = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* Creates a <code>RequestContext</code> from its components.
*
*/
public RequestContext(HttpServlet servlet, HttpServletRequest request,
HttpServletResponse response)
{
this.servlet = servlet;
this.request = request;
this.response = response;
}
/**
* Adds a simple <code>Cookie</code>. To set a Cookie with attributes,
* use {@link #addCookie(Cookie)}.
*
*/
public void addCookie(String name, String value)
{
addCookie(new Cookie(name, value));
}
/**
* Adds a <code>Cookie</code> 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
* <code>HttpServletResponse.getWriter()</code>.
*
*/
public void addCookie(Cookie cookie)
{
response.addCookie(cookie);
if (cookieMap == null)
readCookieMap();
cookieMap.put(cookie.getName(), cookie);
}
private void buildPathInfo()
{
String raw;
StringSplitter splitter;
int i;
raw = request.getPathInfo();
if (raw == null)
{
pathInfo = new String[]{};
return;
}
splitter = new StringSplitter('/');
pathInfo = splitter.splitToArray(raw);
// Decode any URL encoded values (such as bad punctuation,
// etc.) in the path info.
for (i = 0; i < pathInfo.length; i++)
{
try
{
pathInfo[i] = decode(pathInfo[i]);
}
catch (Exception e)
{
// Ignore
}
}
}
/**
* Builds a URL from a base name, adding query elements as
* necessary. It performs escaping of the values in the query
* string, such as converting spaces to '+', much like
* <code>URLEncoder.encode()</code>. <p>If there's any doubt about
* the client's ability to use cookies, this should be passed
* through <code>HttpServletResponse.encodeURL()</code> before
* inclusion on the generated page.
*
*
* @param basePath The main component of the URL, such as
* "/servlets/SomeClass". This is often obtained from
* <code>HttpRequest.getServletPath()</code>.
*
* @param pathInfo An array of values. Each value is appended to
* the basePath and preceded with a forward slash. Each element is
* encoded. The array may be empty or null.
*
* @param query An array of values used to form the query portion
* of the URL, which comes after the base path and any pathInfo
* segments. The even numbered elements (starting with element
* zero) are the names of query parameters the odd numbered
* elements are the parameter values. The array must have an even
* number of elements, be empty, or be null.
*
* @return The final URL with proper URL encoding of all query
* parameters.
*/
public static String buildURL(String basePath, String[] pathInfo, String[] query)
{
StringBuffer buffer;
char sep = '?';
boolean encodeFirst = false;
String parameterName;
String value;
int i;
// Start with the base path
buffer = new StringBuffer(basePath);
// If there's any path info, add them, seperated
// by slashes.
if (pathInfo != null)
{
for (i = 0; i < pathInfo.length; i++)
{
buffer.append('/');
encode(buffer, pathInfo[i], false);
}
}
// If there's any query strings, pair them off as parameters
// and values.
if (query != null)
{
for (i = 0; i < query.length;)
{
parameterName = query[i++];
// Will throw an exception if there isn't an even number of
// elements in the query array.
value = query[i++];
// Append a seperator from the path or the previous parameter/value
// pair.
buffer.append(sep);
encode(buffer, parameterName, encodeFirst);
buffer.append('=');
encode(buffer, value, false);
// Becuase the next parameter name will follow an '&' we want to encode
// the first character as a hex value to prevent it from resembling
// an HTML entity.
sep = '&';
encodeFirst = true;
}
}
return buffer.toString();
}
private static int convertHex(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'a' && ch <= 'f')
return (ch - 'a') + 10;
if (ch >= 'A' && ch <= 'F')
return (ch - 'A') + 10;
throw new IllegalArgumentException("Invalid hex character '" + ch + "'.");
}
private void datePair(IResponseWriter writer, String name, long value)
{
pair(writer, name, new Date(value));
}
/**
* Decodes an x-www-form-urlencoded string.
*
* @param raw The encoded string.
* @return The decoded string. Returns the empty string if raw is null.
* @throws IllegalArgumentException if the string is not properly encoded.
*
*/
public static String decode(String raw)
{
StringBuffer buffer;
int i, length;
char ch;
int digit;
char value;
boolean invalid = false;
boolean dirty = false;
if (raw == null)
return "";
length = raw.length();
buffer = new StringBuffer(length);
try
{
for (i = 0; i < length; i++)
{
ch = raw.charAt(i);
// A '+' is the encoding of a space.
if (ch == '+')
{
dirty = true;
buffer.append(' ');
continue;
}
// These characters are not encoded
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9'))
{
buffer.append(ch);
continue;
}
// Otherwise, the only legit character is the '%', which leads
// off two hex digits for the character in question.
if (ch != '%')
{
invalid = true;
break;
}
// Advance and get the high order digit.
ch = raw.charAt(++i);
digit = convertHex(ch);
value = (char) (digit << 4);
// Advance and get the low order digit
ch = raw.charAt(++i);
digit = convertHex(ch);
value += digit;
// Append the character
buffer.append(value);
dirty = true;
}
}
catch (IllegalArgumentException e)
{
// Thrown by convertHex() if the character following a '%'
// is not a hex digit.
invalid = true;
}
// Catch exceptions when string too short after '%'
catch (IndexOutOfBoundsException e)
{
invalid = true;
}
if (invalid)
throw new IllegalArgumentException("'" + raw +
"' is not a valid x-www-form-urlencoded String.");
// If not 'dirty', meaning there were no encoded characters in
// the string, then return the raw input value. If
// subsitutions did take place, return the decoded buffer.
if (dirty)
return buffer.toString();
else
return raw;
}
/**
* Applies URL encoding to the value, appending the result to the
* <code>StringBuffer</code>.
*
* <p>Why would you want to encode the first character of a
* string? Well, <em>IE 4.0</em> has a bug or feature where it
* would convert HTML entities <i>inside</i> a URL. If a query
* parameter happened to have a name that looked like an HTML
* entity, it got compressed down to a single character (even if
* it <em>didn't end in a semicolon</em>). The end result is that
* the URL is invalid. This is addressed by encoding the first
* character after each ampersand so that it is impossible to
* generate an entity. Any ampersands in a query parameter name or
* value will be encoded as a hex value so the only real
* ampersands will be those seperating query parameters.
*
*/
protected static void encode(StringBuffer buffer, String value, boolean encodeFirst)
{
int i, length;
char ch;
boolean encode;
length = value.length();
for (i = 0; i < length; i++)
{
// Reduce any Unicode characters to just the low byte.
ch = (char) (value.charAt(i) & 0xff);
// The space is a special case.
if (ch == ' ')
{
buffer.append('+');
continue;
}
// Encode if not a safe character, or if the first and
// encodeFirst is set.
if (i == 0 && encodeFirst)
encode = true;
else
encode = !safe.get(ch);
if (encode)
{
// Express the character as a two digit hex constant.
buffer.append('%');
buffer.append(HEX[ch >> 4]);
buffer.append(HEX[ch & 0x0F]);
}
else
buffer.append(ch);
} // for i
}
/**
* Encodes a <code>java.awt.Color</code> in the standard HTML
* format: a pound sign ('#'), followed by six hex digits for
* specifying the red, green and blue components of the color.
*
*/
public static String encodeColor(Color color)
{
char[] buffer;
int component;
buffer = new char[7];
buffer[0] = '#';
// Red
component = color.getRed();
buffer[1] = HEX[component >> 4];
buffer[2] = HEX[component & 0x0F];
// Green
component = color.getGreen();
buffer[3] = HEX[component >> 4];
buffer[4] = HEX[component & 0x0F];
// Blue
component = color.getBlue();
buffer[5] = HEX[component >> 4];
buffer[6] = HEX[component & 0x0F];
return new String(buffer);
}
/**
* Forwards the request to a new resource, typically a JSP.
*/
public void forward(String path) throws ServletException, IOException
{
RequestDispatcher dispatcher;
dispatcher = servlet.getServletContext().getRequestDispatcher(path);
dispatcher.forward(request, response);
}
public String getAbsoluteURL(String path)
{
String server;
int port;
String scheme;
scheme = request.getScheme();
server = request.getServerName();
port = request.getServerPort();
// Keep things simple ... port 80 is accepted as the
// standard port for http so it can be ommitted.
if (scheme.equals("http") && port == 80)
port = 0;
return getAbsoluteURL(path, scheme, server, port);
}
/**
* Does some easy checks to turn a path 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 path
* as given.
* </ul>
*
* <p>The Servlet 2.2 API claims to do this, and do it right. This is just
* a stopgap for Servlet 2.1 API.
*
*/
public String getAbsoluteURL(String path, String scheme, String server, int port)
{
StringBuffer buffer = new StringBuffer();
// Though, really, what does a leading colon with no scheme before it
// mean?
if (path.indexOf(':') >= 0)
return path;
// Should check the length here, first.
if (path.substring(0, 1).equals("//"))
{
buffer.append(scheme);
buffer.append(':');
buffer.append(path);
return buffer.toString();
}
buffer.append(scheme);
buffer.append("://");
buffer.append(server);
if (port > 0)
{
buffer.append(':');
buffer.append(port);
}
if (path.charAt(0) != '/')
buffer.append('/');
buffer.append(path);
return buffer.toString();
}
/**
* Returns a named attribute of the <code>HttpServletRequest</code>,
* or null if not defined.
*
*/
public Object getAttribute(String name)
{
Object result;
result = request.getAttribute(name);
return result;
}
/**
* Gets a named <code>Cookie</code>.
*
* @param name The name of the <code>Cookie</code>.
* @return The <code>Cookie</code>, 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 <CODE>Cookie</CODE> 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 <code>HttpServletRequest</code>.
*
* <p>Use {@link #getParameterValues(String)} for parameters that may
* invlude multiple values.
*
*/
public String getParameter(String name)
{
return request.getParameter(name);
}
/**
* For parameters that are, or are possibly, multi-valued, this
* method returns all the values as an array of Strings.
*
*/
public String[] getParameterValues(String name)
{
return request.getParameterValues(name);
}
/**
* Returns the pathInfo string at the given index. If the index
* is out of range, this returns null.
*
*/
public String getPathInfo(int index)
{
if (pathInfo == null)
buildPathInfo();
try
{
return pathInfo[index];
}
catch(ArrayIndexOutOfBoundsException e)
{
return null;
}
}
/**
* Returns the number of items in the pathInfo.
*/
public int getPathInfoCount()
{
if (pathInfo == null)
buildPathInfo();
return pathInfo.length;
}
public HttpServletRequest getRequest()
{
return request;
}
public HttpServletResponse getResponse()
{
return response;
}
private String getRowClass()
{
String result;
result = evenRow ? "even" : "odd";
evenRow = !evenRow;
return result;
}
public HttpServlet getServlet()
{
return servlet;
}
/**
* Returns the session. The session is created if it doesn't already
* exist.
*
*/
public HttpSession getSession()
{
if (session == null)
session = request.getSession(true);
return session;
}
/**
* Gets an attribute from the HttpSession. Attributes are named
* "values" through the 2.1 version of the API. In 2.2, the
* <code>getValue()</code> method will be deprecated in favor
* of <code>getAttribute</code>.
*
* @return The named value, or null if not found.
*
* @see #setSessionAttribute(String,Object)
*/
public Object getSessionAttribute(String name)
{
Object result;
result = getSession().getValue(name);
return result;
}
private void header(IResponseWriter 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(IResponseWriter writer, String objectName)
{
writer.begin("span");
writer.attribute("class", "request-context-object");
writer.print(objectName);
writer.end();
}
private void pair(IResponseWriter writer, String name, int value)
{
pair(writer, name, Integer.toString(value));
}
private void pair(IResponseWriter writer, String name, Object value)
{
if (value != null)
pair(writer, name, value.toString());
}
private void pair(IResponseWriter 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");
}
private void pair(IResponseWriter writer, String name, boolean value)
{
pair(writer, name, value ? "yes" : "no");
}
private void readCookieMap()
{
Cookie[] cookies;
int i;
cookieMap = new HashMap(MAP_SIZE);
cookies = request.getCookies();
for (i = 0; i < cookies.length; i++)
cookieMap.put(cookies[i].getName(), cookies[i]);
}
/**
* Invokes <code>HttpResponse.sendRedirect()</code>, but
* massages <code>path</code>, supplying missing elements to
* make it a full 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
{
String absolutePath;
String encodedURL;
// Now a little magic to convert path into a complete URL. The Servlet
// 2.2 API does this automatically.
absolutePath = getAbsoluteURL(path);
encodedURL = response.encodeRedirectURL(absolutePath);
response.sendRedirect(encodedURL);
}
/**
* Removes an attribute previously stored in the
* <code>HttpSession</code>.
*
* @see #setSessionAttribute(String,Object)
*
*/
public void removeSessionAttribute(String name)
{
getSession().removeValue(name);
}
private void section(IResponseWriter 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");
}
/**
* Sets an attribute of the <code>HttpServletRequest</code>.
*/
public void setAttribute(String name, Object value)
{
request.setAttribute(name, value);
}
/**
* Stores an attribute into the <code>HttpSession</code>.
*
* @see #getSessionAttribute(String)
*
*/
public void setSessionAttribute(String name, Object value)
{
getSession().putValue(name, value);
}
/**
* 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, and is typically invoked from a JSP.
*
*/
public void write(IResponseWriter writer)
{
int i;
String names[];
String values[];
Enumeration e;
String name;
String value;
boolean first = true;
Cookie[] cookies;
Cookie cookie;
ServletConfig config;
ServletContext context;
// Create a box around all of this stuff ...
writer.begin("table");
writer.attribute("class", "request-context-border");
writer.begin("tr");
writer.begin("td");
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());
// In 2.2, getValue...() will be deprecated in favor of
// getAttribute...().
names = session.getValueNames();
for (i = 0; i < names.length; i++)
{
if (i == 0)
{
section(writer, "Attributes");
header(writer, "Name", "Value");
}
pair(writer, names[i], session.getValue(names[i]));
}
writer.end(); // Session
object(writer, "Request");
writer.begin("table");
writer.attribute("class", "request-context-object");
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, "servletPath", request.getServletPath());
// Now deal with any headers
e = request.getHeaderNames();
while (e.hasMoreElements())
{
if (first)
{
section(writer, "Headers");
header(writer, "Name", "Value");
first = false;
}
name = (String)e.nextElement();
value = request.getHeader(name);
pair(writer, name, value);
}
// Parameters ...
first = true;
e = request.getParameterNames();
while (e.hasMoreElements())
{
if (first)
{
section(writer, "Parameters");
header(writer, "Name", "Value(s)");
first = false;
}
name = (String)e.nextElement();
values = request.getParameterValues(name);
for (i = 0; i < values.length; i++)
{
writer.begin("tr");
writer.attribute("class", getRowClass());
writer.begin("td");
if (i == 0)
writer.print(name);
writer.end();
writer.begin("td");
writer.print(values[i]);
writer.end("tr");
}
}
// Attributes
first = true;
e = request.getAttributeNames();
while (e.hasMoreElements())
{
if (first)
{
section(writer, "Attributes");
header(writer, "Name", "Value");
first = false;
}
name = (String)e.nextElement();
pair(writer, name, request.getAttribute(name));
}
if (pathInfo == null)
buildPathInfo();
for (i = 0; i < pathInfo.length; i++)
{
if (i == 0)
{
section(writer, "Path Info");
header(writer, "Index", "Value");
}
pair(writer, Integer.toString(i), pathInfo[i]);
}
// Cookies ...
cookies = request.getCookies();
for (i = 0; i < cookies.length; i++)
{
if (i == 0)
{
section(writer, "Cookies");
header(writer, "Name", "Value");
}
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, "servletInfo", servlet.getServletInfo());
config = servlet.getServletConfig();
first = true;
e = config.getInitParameterNames();
while (e.hasMoreElements())
{
if (first)
{
section(writer, "Init Parameters");
header(writer, "Name", "Value");
first = false;
}
name = (String)e.nextElement();
pair(writer, name, config.getInitParameter(name));
}
writer.end(); // Servlet
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());
first = true;
e = context.getAttributeNames();
while (e.hasMoreElements())
{
if (first)
{
section(writer, "Attributes");
header(writer, "Name", "Value");
first = false;
}
name = (String)e.nextElement();
pair(writer, name, context.getAttribute(name));
}
writer.end(); // Servlet Context
writeSystemProperties(writer);
writer.end("table"); // The enclosing border
}
private void writeSystemProperties(IResponseWriter writer)
{
boolean first = true;
Properties properties;
List names;
Iterator i;
String name;
String property;
String pathSeparator;
StringTokenizer tokenizer;
object(writer, "JVM System Properties");
try
{
properties = System.getProperties();
}
catch (SecurityException se)
{
writer.print("<p>");
writer.print(se.toString());
return;
}
pathSeparator = System.getProperty("path.separator", ";");
writer.begin("table");
writer.attribute("class", "request-context-object");
names = new ArrayList(properties.keySet());
Collections.sort(names);
i = names.iterator();
while (i.hasNext())
{
name = (String)i.next();
property = properties.getProperty(name);
if (first)
{
header(writer, "Name", "Value");
first = false;
first = false;
}
if (property.indexOf(pathSeparator) < 0)
pair(writer, name, property);
else
{
tokenizer = new StringTokenizer(property, pathSeparator);
while (tokenizer.hasMoreTokens())
{
pair(writer, name, tokenizer.nextToken());
name = "";
}
}
}
}
}