Migrated samskivert into its own, sensibly organized, repository.
git-svn-id: https://samskivert.googlecode.com/svn/trunk@1774 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id: Log.java,v 1.3 2001/08/12 01:34:30 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert;
|
||||
|
||||
/**
|
||||
* A placeholder class that contains a reference to the log object used by
|
||||
* the samskivert package. This is a useful pattern to use when using the
|
||||
* samskivert logging facilities. One creates a top-level class like this
|
||||
* one that instantiates a log object with an name that identifies log
|
||||
* messages from that package and then provides static methods that
|
||||
* generate log messages using that instance. Then, classes in that
|
||||
* package need only import the log wrapper class and can easily use it to
|
||||
* generate log messages. For example:
|
||||
*
|
||||
* <pre>
|
||||
* import com.samskivert.Log;
|
||||
* // ...
|
||||
* Log.warning("All hell is breaking loose!");
|
||||
* // ...
|
||||
* </pre>
|
||||
*
|
||||
* @see com.samskivert.util.Log
|
||||
*/
|
||||
public class Log
|
||||
{
|
||||
/** The static log instance configured for use by this package. */
|
||||
public static com.samskivert.util.Log log =
|
||||
new com.samskivert.util.Log("samskivert");
|
||||
|
||||
/** Convenience function. */
|
||||
public static void debug (String message)
|
||||
{
|
||||
log.debug(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void info (String message)
|
||||
{
|
||||
log.info(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void warning (String message)
|
||||
{
|
||||
log.warning(message);
|
||||
}
|
||||
|
||||
/** Convenience function. */
|
||||
public static void logStackTrace (Throwable t)
|
||||
{
|
||||
log.logStackTrace(com.samskivert.util.Log.WARNING, t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// $Id: ByteArrayOutInputStream.java,v 1.1 2002/02/01 18:19:43 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* The byte array out/input stream is used for writing data to a byte
|
||||
* array output stream and then obtaining an input stream that can read
|
||||
* back the data written to the output stream without first having to copy
|
||||
* it to a separate buffer.
|
||||
*/
|
||||
public class ByteArrayOutInputStream extends ByteArrayOutputStream
|
||||
{
|
||||
/**
|
||||
* Creates a new byte array out/input stream. The buffer capacity is
|
||||
* initially 32 bytes, though its size increases if necessary.
|
||||
*/
|
||||
public ByteArrayOutInputStream ()
|
||||
{
|
||||
this(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new byte array out/input stream, with a buffer capacity
|
||||
* of the specified size, in bytes.
|
||||
*
|
||||
* @param size the initial size.
|
||||
*
|
||||
* @exception IllegalArgumentException if size is negative.
|
||||
*/
|
||||
public ByteArrayOutInputStream (int size)
|
||||
{
|
||||
super(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input stream configured to read only the bytes that have
|
||||
* been written this far to this byte array out/input stream.
|
||||
*/
|
||||
public InputStream getInputStream ()
|
||||
{
|
||||
return new ByteArrayInputStream(buf, 0, count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// $Id: ExtensiblePrintStream.java,v 1.2 2002/10/16 19:12:53 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Walter Korman
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Wraps a supplied {@link PrintStream} to allow capturing all data
|
||||
* written to the stream via the various <code>print()</code> and
|
||||
* <code>println()</code> variants. Derived classes must implement the
|
||||
* {@link #handlePrinted} and {@link #handleNewLine} methods.
|
||||
*/
|
||||
public abstract class ExtensiblePrintStream extends PrintStream
|
||||
{
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (PrintStream out)
|
||||
{
|
||||
super(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (PrintStream out, boolean autoFlush)
|
||||
{
|
||||
super(out, autoFlush);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an extensible print stream.
|
||||
*/
|
||||
public ExtensiblePrintStream (
|
||||
PrintStream out, boolean autoFlush, String encoding)
|
||||
throws UnsupportedEncodingException
|
||||
{
|
||||
super(out, autoFlush, encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with any text printed to the stream, excepting newlines
|
||||
* resulting from calls to the various <code>println()</code> methods,
|
||||
* which are reported via {@link #handleNewLine}.
|
||||
*/
|
||||
public abstract void handlePrinted (String s);
|
||||
|
||||
/**
|
||||
* Called whenever a newline is printed to the stream by one of the
|
||||
* various <code>println()</code> methods.
|
||||
*/
|
||||
public abstract void handleNewLine ();
|
||||
|
||||
// documentation inherited
|
||||
public void print (boolean b)
|
||||
{
|
||||
super.print(b);
|
||||
handlePrinted(b ? "true" : "false");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (char c)
|
||||
{
|
||||
super.print(c);
|
||||
handlePrinted(String.valueOf(c));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (int i)
|
||||
{
|
||||
super.print(i);
|
||||
handlePrinted(String.valueOf(i));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (long l)
|
||||
{
|
||||
super.print(l);
|
||||
handlePrinted(String.valueOf(l));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (float f)
|
||||
{
|
||||
super.print(f);
|
||||
handlePrinted(String.valueOf(f));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (double d)
|
||||
{
|
||||
super.print(d);
|
||||
handlePrinted(String.valueOf(d));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (char[] s)
|
||||
{
|
||||
super.print(s);
|
||||
handlePrinted(String.valueOf(s));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (String s)
|
||||
{
|
||||
super.print(s);
|
||||
handlePrinted((s == null) ? "null" : s);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void print (Object obj)
|
||||
{
|
||||
super.print(obj);
|
||||
handlePrinted(String.valueOf(obj));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println ()
|
||||
{
|
||||
super.println();
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (boolean x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (char x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (int x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (long x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (float x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (double x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (char[] x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (String x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void println (Object x)
|
||||
{
|
||||
super.println(x);
|
||||
handleNewLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// $Id: PersistenceException.java,v 1.4 2003/03/17 23:01:37 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
/**
|
||||
* A persistence exception can be thrown when an error occurs in
|
||||
* underlying persistence code. By encapsulating errors, one retains the
|
||||
* ability to make changes to the implementation structure without
|
||||
* affecting the interface to persistence services presented to the
|
||||
* application.
|
||||
*/
|
||||
public class PersistenceException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructs a persistence exception with the specified error
|
||||
* message.
|
||||
*/
|
||||
public PersistenceException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a persistence exception with the specified error message
|
||||
* and the chained causing event.
|
||||
*/
|
||||
public PersistenceException (String message, Exception cause)
|
||||
{
|
||||
super(message);
|
||||
initCause(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a persistence exception with the specified chained
|
||||
* causing event.
|
||||
*/
|
||||
public PersistenceException (Exception cause)
|
||||
{
|
||||
initCause(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// $Id: StreamUtil.java,v 1.1 2003/05/14 21:30:29 ray Exp $
|
||||
|
||||
package com.samskivert.io;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Convenience methods for streams.
|
||||
*/
|
||||
public class StreamUtil
|
||||
{
|
||||
/**
|
||||
* Convenient close for a stream. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (InputStream in)
|
||||
{
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error closing input stream [stream=" + in +
|
||||
", cause=" + ioe + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient close for a stream. Use in a finally clause and love life.
|
||||
*/
|
||||
public static void close (OutputStream out)
|
||||
{
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error closing output stream [stream=" + out +
|
||||
", cause=" + ioe + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001-2005 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Various I/O related utilities.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// $Id: ConnectionProvider.java,v 1.2 2001/09/21 03:01:46 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* As the repository aims to interface with whatever database pooling
|
||||
* services a project cares to use, it obtains all of its database
|
||||
* connections through a connection provider. The connection provider
|
||||
* provides connections based on a database identifier (a string) which
|
||||
* identifies a particular connection to a particular database. A user of
|
||||
* these services would then coordinate the database identifier (or
|
||||
* identifiers) used to invoke a database operation with the database
|
||||
* connections that are returned by the connection provider that they
|
||||
* provided to the repository at construct time.
|
||||
*/
|
||||
public interface ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Obtains a database connection based on the supplied database
|
||||
* identifier. The repository expects to have exclusive use of this
|
||||
* connection instance until it releases it. This connection will be
|
||||
* released subsequently with a call to {@link #releaseConnection} or
|
||||
* {@link #connectionFailed} depending on the circumstances of the
|
||||
* release. <code>close()</code> <em>will not</em> be called on the
|
||||
* connection. It is up to the connection provider to close the
|
||||
* connection when it is released if appropriate.
|
||||
*
|
||||
* @param ident the database connection identifier.
|
||||
*
|
||||
* @return an active JDBC connection (which may have come from a
|
||||
* connection pool).
|
||||
*
|
||||
* @exception PersistenceException thrown if a problem occurs trying
|
||||
* to open the requested connection.
|
||||
*/
|
||||
public Connection getConnection (String ident)
|
||||
throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Releases a database connection when it is no longer needed by the
|
||||
* repository.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this
|
||||
* connection.
|
||||
* @param conn the connection to release (back into the pool or to be
|
||||
* closed if pooling is not going on).
|
||||
*/
|
||||
public void releaseConnection (String ident, Connection conn);
|
||||
|
||||
/**
|
||||
* Called by the repository if a failure occurred on the connection.
|
||||
* It is expected that the connection will be disposed of and
|
||||
* subsequent calls to <code>getConnection</code> will return a
|
||||
* freshly established connection to the database.
|
||||
*
|
||||
* @param ident the database identifier used when obtaining this
|
||||
* connection.
|
||||
* @param conn the connection that failed.
|
||||
* @param error the error thrown by the connection (which may be used
|
||||
* to determine if the connection should be closed or can be reused).
|
||||
*/
|
||||
public void connectionFailed (String ident, Connection conn,
|
||||
SQLException error);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// $Id: DatabaseLiaison.java,v 1.2 2001/09/20 02:09:09 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Despite good intentions, JDBC and SQL do not provide a unified
|
||||
* interface to all databases. There remain idiosyncrasies that must be
|
||||
* worked around when making code interact with different database
|
||||
* servers. The database liaison encapsulates the code needed to
|
||||
* straighten out the curves and curve out the straights.
|
||||
*/
|
||||
public interface DatabaseLiaison
|
||||
{
|
||||
/**
|
||||
* Indicates whether or not this database liaison is the proper
|
||||
* liaison for the specified database URL.
|
||||
*
|
||||
* @return true if we should use this liaison for connections created
|
||||
* with the supplied URL, false if we should not.
|
||||
*/
|
||||
public boolean matchesURL (String url);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception was caused by
|
||||
* a duplicate row being inserted into a table with a unique key.
|
||||
*
|
||||
* @return true if the exception was caused by the insertion of a
|
||||
* duplicate row, false if not.
|
||||
*/
|
||||
public boolean isDuplicateRowException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Determines whether or not the supplied SQL exception is a transient
|
||||
* failure, meaning one that is not related to the SQL being executed,
|
||||
* but instead to the environment at the time of execution, like the
|
||||
* connection to the database having been lost.
|
||||
*
|
||||
* @return true if the exception was thrown due to a transient
|
||||
* failure, false if not.
|
||||
*/
|
||||
public boolean isTransientException (SQLException sqe);
|
||||
|
||||
/**
|
||||
* Returns the value of an <code>AUTO_INCREMENT</code> column for the
|
||||
* last row insertion. This is MySQL specific, but exists here for now
|
||||
* until we sort out a general purpose mechanism for assigning unique
|
||||
* ids.
|
||||
*
|
||||
* @return the most recent ID generated by an insert into an
|
||||
* <code>AUTO_INCREMENT</code> table or -1 if it could not be
|
||||
* obtained.
|
||||
*/
|
||||
public int lastInsertedId (Connection conn) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id: DefaultLiaison.java,v 1.1 2001/09/20 02:09:09 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* The default liaison is used if no other liaison could be matched for a
|
||||
* particular database connection. It isn't very smart or useful but we
|
||||
* need something.
|
||||
*/
|
||||
public class DefaultLiaison implements DatabaseLiaison
|
||||
{
|
||||
// documentation inherited
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int lastInsertedId (Connection conn) throws SQLException
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
//
|
||||
// $Id: JDBCUtil.java,v 1.10 2004/05/28 01:54:47 eric Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A repository for JDBC related utility functions.
|
||||
*/
|
||||
public class JDBCUtil
|
||||
{
|
||||
/**
|
||||
* Closes the supplied JDBC statement and gracefully handles being
|
||||
* passed null (by doing nothing).
|
||||
*/
|
||||
public static void close (Statement stmt)
|
||||
throws SQLException
|
||||
{
|
||||
if (stmt != null) {
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the supplied JDBC connection and gracefully handles being
|
||||
* passed null (by doing nothing).
|
||||
*/
|
||||
public static void close (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement,
|
||||
* checking to see that it returns the expected update count and
|
||||
* throwing a persistence exception if it does not.
|
||||
*/
|
||||
public static void checkedUpdate (
|
||||
PreparedStatement stmt, int expectedCount)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
int modified = stmt.executeUpdate();
|
||||
if (modified != expectedCount) {
|
||||
String err = "Statement did not modify expected number of rows " +
|
||||
"[stmt=" + stmt + ", expected=" + expectedCount +
|
||||
", modified=" + modified + "]";
|
||||
throw new PersistenceException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement
|
||||
* with the supplied query, checking to see that it returns the
|
||||
* expected update count and throwing a persistence exception if it
|
||||
* does not.
|
||||
*/
|
||||
public static void checkedUpdate (
|
||||
Statement stmt, String query, int expectedCount)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
int modified = stmt.executeUpdate(query);
|
||||
if (modified != expectedCount) {
|
||||
String err = "Statement did not modify expected number of rows " +
|
||||
"[stmt=" + stmt + ", expected=" + expectedCount +
|
||||
", modified=" + modified + "]";
|
||||
throw new PersistenceException(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement,
|
||||
* checking to see that it returns the expected update count and
|
||||
* logging a warning if it does not.
|
||||
*/
|
||||
public static void warnedUpdate (
|
||||
PreparedStatement stmt, int expectedCount)
|
||||
throws SQLException
|
||||
{
|
||||
int modified = stmt.executeUpdate();
|
||||
if (modified != expectedCount) {
|
||||
Log.warning("Statement did not modify expected number of rows " +
|
||||
"[stmt=" + stmt + ", expected=" + expectedCount +
|
||||
", modified=" + modified + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls <code>stmt.executeUpdate()</code> on the supplied statement
|
||||
* with the supplied query, checking to see that it returns the
|
||||
* expected update count and logging a warning if it does not.
|
||||
*/
|
||||
public static void warnedUpdate (
|
||||
Statement stmt, String query, int expectedCount)
|
||||
throws SQLException
|
||||
{
|
||||
int modified = stmt.executeUpdate(query);
|
||||
if (modified != expectedCount) {
|
||||
Log.warning("Statement did not modify expected number of rows " +
|
||||
"[stmt=" + stmt + ", expected=" + expectedCount +
|
||||
", modified=" + modified + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes any single quotes in the supplied text and wraps it in
|
||||
* single quotes to make it safe for embedding into a database query.
|
||||
*/
|
||||
public static String escape (String text)
|
||||
{
|
||||
text = StringUtil.replace(text, "\\", "\\\\");
|
||||
return "'" + StringUtil.replace(text, "'", "\\'") + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a list of values, separating the escaped values by
|
||||
* commas. See {@link #escape(String)}.
|
||||
*/
|
||||
public static String escape (Object[] values)
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(escape(String.valueOf(values[ii])));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Many databases simply fail to handle Unicode text properly and this
|
||||
* routine provides a common workaround which is to represent a UTF-8
|
||||
* string as an ISO-8895-1 string. If you don't need to use the
|
||||
* database's collation routines, this allows you to do pretty much
|
||||
* exactly what you want at the expense of having to jigger and
|
||||
* dejigger every goddamned string that might contain multibyte
|
||||
* characters every time you access the database. Three cheers for
|
||||
* progress!
|
||||
*/
|
||||
public static String jigger (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new String(text.getBytes("UTF8"), "8859_1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
Log.logStackTrace(uee);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses {@link #jigger}.
|
||||
*/
|
||||
public static String unjigger (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new String(text.getBytes("8859_1"), "UTF8");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
Log.logStackTrace(uee);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to jigger the specified string so that it's safe
|
||||
* to use in a regular Statement.
|
||||
*/
|
||||
public static String safeJigger (String text)
|
||||
{
|
||||
return StringUtil.replace(jigger(text), "'", "\\'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table with the specified name exists, false if
|
||||
* it does not. <em>Note:</em> the table name is case sensitive.
|
||||
*/
|
||||
public static boolean tableExists (Connection conn, String name)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getTables("", "", name, null);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
if (name.equals(tname)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the table with the specified name exists and
|
||||
* contains a column with the specified name, false if either
|
||||
* condition does not hold true. <em>Note:</em> the names are case
|
||||
* sensitive.
|
||||
*/
|
||||
public static boolean tableContainsColumn (
|
||||
Connection conn, String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the index on the specified column exists for the
|
||||
* specified table, false if it does not. Optionally you can specifiy
|
||||
* a non null index name, and the table will be checked to see if it
|
||||
* contains that specifically named index. <em>Note:</em> the names
|
||||
* are case sensitive.
|
||||
*/
|
||||
public static boolean tableContainsIndex (Connection conn, String table,
|
||||
String column, String index)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false,
|
||||
true);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
String iname = rs.getString("INDEX_NAME");
|
||||
|
||||
if (index == null) {
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (index.equals(iname)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified table contains a primary key on the
|
||||
* specified column.
|
||||
*/
|
||||
public static boolean tableContainsPrimaryKey (Connection conn,
|
||||
String table, String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getPrimaryKeys("", "", table);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the index for the specified column in the
|
||||
* specified table.
|
||||
*/
|
||||
public static String getIndexName (Connection conn, String table,
|
||||
String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getIndexInfo("", "", table, false,
|
||||
true);
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
String iname = rs.getString("INDEX_NAME");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
return iname;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type (as specified in {@link java.sql.Types} for the
|
||||
* specified column in the specified table.
|
||||
*/
|
||||
public static int getColumnType (Connection conn, String table,
|
||||
String column)
|
||||
throws SQLException
|
||||
{
|
||||
boolean matched = false;
|
||||
ResultSet rs = conn.getMetaData().getColumns("", "", table, column);
|
||||
|
||||
while (rs.next()) {
|
||||
String tname = rs.getString("TABLE_NAME");
|
||||
String cname = rs.getString("COLUMN_NAME");
|
||||
int type = rs.getInt("DATA_TYPE");
|
||||
if (tname.equals(table) && cname.equals(column)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
throw new SQLException("Table or Column not defined. [table=" + table +
|
||||
", col=" + column + "].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a column (with name 'cname' and definition 'cdef') to the
|
||||
* specified table.
|
||||
*
|
||||
* @param afterCname (optional) the name of the column after which to
|
||||
* add the new column.
|
||||
*/
|
||||
public static void addColumn (Connection conn, String table,
|
||||
String cname, String cdef, String afterCname)
|
||||
throws SQLException
|
||||
{
|
||||
if (JDBCUtil.tableContainsColumn(conn, table, cname)) {
|
||||
// Log.info("Database table '" + table + "' already has column '" +
|
||||
// cname + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
String update = "ALTER TABLE " + table + " ADD COLUMN " +
|
||||
cname + " " + cdef;
|
||||
if (afterCname != null) {
|
||||
update += " AFTER " + afterCname;
|
||||
}
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
|
||||
Log.info("Database column '" + cname + "' added to table '" + table +
|
||||
"'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a column's definition. Takes a full column definition
|
||||
* 'cdef' (including the name of the column) with which to replace the
|
||||
* specified column 'cname'.
|
||||
*
|
||||
* NOTE: A handy thing you can do with this is to rename a column by
|
||||
* providing a column definition that has a different name, but the
|
||||
* same column type.
|
||||
*/
|
||||
public static void changeColumn (Connection conn, String table,
|
||||
String cname, String cdef)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " CHANGE " + cname + " " +
|
||||
cdef;
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
|
||||
Log.info("Database column '" + cname + "' of table '" + table +
|
||||
"' modified to have this def '" + cdef + "'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a column from the specified table.
|
||||
*/
|
||||
public static void dropColumn (Connection conn, String table, String cname)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " DROP COLUMN " + cname;
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
Log.info("Database index '" + cname + "' removed from " +
|
||||
"table '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a named index from the specified table.
|
||||
*/
|
||||
public static void dropIndex (Connection conn, String table, String iname)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " DROP INDEX " + iname;
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
Log.info("Database index '" + iname + "' removed from " +
|
||||
"table '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the primary key from the specified table.
|
||||
*/
|
||||
public static void dropPrimaryKey (Connection conn, String table)
|
||||
throws SQLException
|
||||
{
|
||||
String update = "ALTER TABLE " + table + " DROP PRIMARY KEY";
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
if (stmt.executeUpdate() == 1) {
|
||||
Log.info("Database primary key removed from '" + table + "'.");
|
||||
}
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an index on the specified column (cname) to the specified
|
||||
* table. Optionally supply an index name, otherwise the index is
|
||||
* named after the column.
|
||||
*/
|
||||
public static void addIndexToTable (Connection conn, String table,
|
||||
String cname, String iname)
|
||||
throws SQLException
|
||||
{
|
||||
if (JDBCUtil.tableContainsIndex(conn, table, cname, iname)) {
|
||||
// Log.info("Database table '" + table + "' already has an index " +
|
||||
// "on column '" + cname + "'" +
|
||||
// (iname != null ? " named '" + iname + "'." : "."));
|
||||
return;
|
||||
}
|
||||
|
||||
String idx_name = (iname != null ? iname : cname);
|
||||
String update = "CREATE INDEX " + idx_name + " on " + table + "(" +
|
||||
cname + ")";
|
||||
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(update);
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
close(stmt);
|
||||
}
|
||||
|
||||
Log.info("Database index '" + idx_name + "' added to table '" + table +
|
||||
"'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
//
|
||||
// $Id: JORARepository.java,v 1.2 2004/02/25 13:16:05 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.jora.*;
|
||||
|
||||
/**
|
||||
* The JORA repository simplifies the process of building persistence
|
||||
* services that make use of the JORA object relational mapping package.
|
||||
*
|
||||
* @see com.samskivert.jdbc.jora.Session
|
||||
*/
|
||||
public abstract class JORARepository extends SimpleRepository
|
||||
{
|
||||
/**
|
||||
* Creates and initializes a JORA repository which will access the
|
||||
* database identified by the supplied database identifier.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
* @param dbident the identifier of the database that will be accessed
|
||||
* by this repository.
|
||||
*/
|
||||
public JORARepository (ConnectionProvider provider, String dbident)
|
||||
{
|
||||
super(provider, dbident);
|
||||
|
||||
// our parent class will have already obtained a database connection
|
||||
// and therefore ended up calling getConnection() which will create our
|
||||
// session, so we can make use of it straight away
|
||||
|
||||
// create our tables
|
||||
createTables(_session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the supplied object into the specified table. The table
|
||||
* must be configured to store items of the supplied type.
|
||||
*/
|
||||
protected int insert (final Table table, final Object object)
|
||||
throws PersistenceException
|
||||
{
|
||||
Integer iid = (Integer)execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.insert(object);
|
||||
return new Integer(liaison.lastInsertedId(conn));
|
||||
}
|
||||
});
|
||||
return iid.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the supplied object in the specified table. The table must
|
||||
* be configured to store items of the supplied type.
|
||||
*/
|
||||
protected void update (final Table table, final Object object)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.update(object);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied query. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected Object load (final Table table, final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.select(query).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied example. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected Object loadByExample (final Table table, final Object example)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(example).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single object from the specified table that matches the
|
||||
* supplied example. <em>Note:</em> the query should match one or zero
|
||||
* records, not more.
|
||||
*/
|
||||
protected Object loadByExample (
|
||||
final Table table, final FieldMask mask, final Object example)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
return table.queryByExample(example, mask).get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* First attempts to update the supplied object and if that modifies
|
||||
* zero rows, inserts the object into the specified table. The table
|
||||
* must be configured to store items of the supplied type.
|
||||
*/
|
||||
protected void store (final Table table, final Object object)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
if (table.update(object) == 0) {
|
||||
table.insert(object);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified field in the supplied object (which must
|
||||
* correspond to the supplied table).
|
||||
*/
|
||||
protected void updateField (
|
||||
final Table table, final Object object, String field)
|
||||
throws PersistenceException
|
||||
{
|
||||
final FieldMask mask = table.getFieldMask();
|
||||
mask.setModified(field);
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.update(object, mask);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified fields in the supplied object (which must
|
||||
* correspond to the supplied table).
|
||||
*/
|
||||
protected void updateFields (
|
||||
final Table table, final Object object, String[] fields)
|
||||
throws PersistenceException
|
||||
{
|
||||
final FieldMask mask = table.getFieldMask();
|
||||
for (int ii = 0; ii < fields.length; ii++) {
|
||||
mask.setModified(fields[ii]);
|
||||
}
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.update(object, mask);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void delete (final Table table, final Object object)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
table.delete(object);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After the database session is begun, this function will be called
|
||||
* to give the repository implementation the opportunity to create its
|
||||
* table objects.
|
||||
*
|
||||
* @param session the session instance to use when creating your table
|
||||
* instances.
|
||||
*/
|
||||
protected abstract void createTables (Session session);
|
||||
|
||||
protected void gotConnection (Connection conn)
|
||||
{
|
||||
// create or update our JORA session
|
||||
if (_session == null) {
|
||||
_session = new Session(conn);
|
||||
} else {
|
||||
_session.setConnection(conn);
|
||||
}
|
||||
}
|
||||
|
||||
protected Session _session;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// $Id: LiaisonRegistry.java,v 1.4 2001/09/20 20:41:10 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* The liaison registry provides access to the appropriate database
|
||||
* liaison implementation for a particular database connection.
|
||||
*/
|
||||
public class LiaisonRegistry
|
||||
{
|
||||
/**
|
||||
* Fetch the appropriate database liaison for the supplied database
|
||||
* connection.
|
||||
*/
|
||||
public static DatabaseLiaison getLiaison (Connection conn)
|
||||
throws SQLException
|
||||
{
|
||||
DatabaseMetaData dmd = conn.getMetaData();
|
||||
String url = dmd.getURL();
|
||||
|
||||
// see if we already have a liaison mapped for this connection
|
||||
DatabaseLiaison liaison = (DatabaseLiaison)_mappings.get(url);
|
||||
|
||||
if (liaison == null) {
|
||||
// scan the list looking for a matching liaison
|
||||
Iterator iter = _liaisons.iterator();
|
||||
while (iter.hasNext()) {
|
||||
DatabaseLiaison candidate = (DatabaseLiaison)iter.next();
|
||||
if (candidate.matchesURL(url)) {
|
||||
liaison = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find a matching liaison, use the default
|
||||
if (liaison == null) {
|
||||
Log.warning("Unable to match liaison for database " +
|
||||
"[url=" + url + "]. Using default.");
|
||||
liaison = new DefaultLiaison();
|
||||
}
|
||||
|
||||
// map this URL to this liaison
|
||||
_mappings.put(url, liaison);
|
||||
}
|
||||
|
||||
return liaison;
|
||||
}
|
||||
|
||||
protected static void registerLiaisonClass (Class lclass)
|
||||
{
|
||||
// create a new instance and stick it on our list
|
||||
try {
|
||||
_liaisons.add(lclass.newInstance());
|
||||
} catch (Exception e) {
|
||||
Log.warning("Unable to instantiate liaison " +
|
||||
"[class=" + lclass.getName() + ", error=" + e + "].");
|
||||
}
|
||||
}
|
||||
|
||||
protected static ArrayList _liaisons = new ArrayList();
|
||||
protected static HashMap _mappings = new HashMap();
|
||||
|
||||
// register our liaison classes
|
||||
static {
|
||||
registerLiaisonClass(MySQLLiaison.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// $Id: MySQLLiaison.java,v 1.5 2002/08/19 21:18:58 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* A database liaison for the MySQL database.
|
||||
*/
|
||||
public class MySQLLiaison implements DatabaseLiaison
|
||||
{
|
||||
// documentation inherited
|
||||
public boolean matchesURL (String url)
|
||||
{
|
||||
return url.startsWith("jdbc:mysql");
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isDuplicateRowException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null && msg.indexOf("Duplicate entry") != -1);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isTransientException (SQLException sqe)
|
||||
{
|
||||
String msg = sqe.getMessage();
|
||||
return (msg != null &&
|
||||
(msg.indexOf("Lost connection") != -1 ||
|
||||
msg.indexOf("Communication link failure") != -1 ||
|
||||
msg.indexOf("Broken pipe") != -1));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int lastInsertedId (Connection conn) throws SQLException
|
||||
{
|
||||
Statement stmt = null;
|
||||
|
||||
// we have to do this by hand. alas all is not roses.
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("select LAST_INSERT_ID()");
|
||||
if (rs.next()) {
|
||||
return rs.getInt(1);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// $Id: Repository.java,v 1.12 2001/11/01 02:08:17 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* The repository class provides basic functionality upon which to build
|
||||
* an interface to a repository of information stored in a database (a
|
||||
* table or set of tables) that is accessed via JDBC.
|
||||
*
|
||||
* <p> It is expected that the repository class will encapsulate all
|
||||
* database access for a particular table or set of tables. The interface
|
||||
* provided to the rest of the application will involve only the
|
||||
* application object model. For example:
|
||||
*
|
||||
* <pre>
|
||||
* public class PeopleRepository extends SimpleRepository
|
||||
* {
|
||||
* public Person getPerson (int personid);
|
||||
* public Person[] getPeopleByFirstName (String firstName);
|
||||
* public void updatePerson (Person person);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p> It is probably also desirable to catch SQL exceptions and wrap them
|
||||
* in the <code>PersistenceException</code> class that is provided by this
|
||||
* package.
|
||||
*
|
||||
* <p> The repository comes in a few flavors depending on the needs of the
|
||||
* persistence services being developed:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link SimpleRepository}: The simple repository is used by services
|
||||
* that need access to a single JDBC connection to perform their database
|
||||
* operations.</li>
|
||||
*
|
||||
* <li>{@link JORARepository}: JORA repository is used by services that
|
||||
* wish to make use of the JORA Java/RDBMS interoperability package.</li>
|
||||
*
|
||||
* <li> Because the repository provides a unified interface to a
|
||||
* particular persistence service, it is conceivable that it would need to
|
||||
* talk to multiple databases to provide those services. Presently, the
|
||||
* repository only supports a single connection, but if the need arose,
|
||||
* implementing a repository flavor that supported multiple connections
|
||||
* would be the proper solution.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class Repository
|
||||
{
|
||||
/**
|
||||
* Creates and initializes the repository.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
*/
|
||||
public Repository (ConnectionProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database operations should be encapsulated in instances of this
|
||||
* class and then provided to the repository for invocation. This
|
||||
* allows the repository to manage transaction commits for you as well
|
||||
* as for it to automatically retry an operation if the connection
|
||||
* failed for some transient reason.
|
||||
*/
|
||||
public interface Operation
|
||||
{
|
||||
/**
|
||||
* Invokes code that performs one or more database operations, all
|
||||
* of which will be encapsulated in a single transaction (which
|
||||
* can be retried in the event of a transient failure).
|
||||
*
|
||||
* @param conn the database connection on which the operations
|
||||
* will be performed.
|
||||
* @param liaison a database liaison for the supplied connection
|
||||
* which can be used to determine things for which there is no
|
||||
* standard way to determine via JDBC.
|
||||
*
|
||||
* @exception SQLException if thrown, this will be wrapped in a
|
||||
* {@link PersistenceException} before being passed up to the
|
||||
* operation invoker.
|
||||
* @exception PersistenceException can be thrown if something goes
|
||||
* awry when executing the operation. Note that the operation will
|
||||
* not be retried if a persistence exception is thrown. Such
|
||||
* exceptions are assumed to be application specific and not
|
||||
* indicative of a basic JDBC failure. The transaction
|
||||
* <em>will</em> be rolled back in such cases, however.
|
||||
*/
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException;
|
||||
}
|
||||
|
||||
/** Our database connection provider. */
|
||||
protected ConnectionProvider _provider;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// $Id: SimpleRepository.java,v 1.9 2004/05/11 05:43:15 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The simple repository should be used for a repository that only needs
|
||||
* access to a single JDBC connection instance to perform its persistence
|
||||
* services.
|
||||
*/
|
||||
public class SimpleRepository extends Repository
|
||||
{
|
||||
/** See {@link #setExecutePreCondition}. */
|
||||
public static interface PreCondition
|
||||
{
|
||||
/** See {@link #setExecutePreCondition}. */
|
||||
public boolean validate (String dbident, Operation op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an operation that will be invoked prior to the execution
|
||||
* of every database operation to validate whether some pre-condition
|
||||
* is met. This mainly exists for systems that wish to ensure that all
|
||||
* database operations take place on a particular thread (or not on a
|
||||
* particular thread) as database operations are generally slow and
|
||||
* blocking.
|
||||
*/
|
||||
public static void setExecutePreCondition (PreCondition condition)
|
||||
{
|
||||
_precond = condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a simple repository which will access the
|
||||
* database identified by the supplied database identifier.
|
||||
*
|
||||
* @param provider the connection provider which will be used to
|
||||
* obtain our database connection.
|
||||
* @param dbident the identifier of the database that will be accessed
|
||||
* by this repository.
|
||||
*/
|
||||
public SimpleRepository (ConnectionProvider provider, String dbident)
|
||||
{
|
||||
super(provider);
|
||||
_dbident = dbident;
|
||||
|
||||
// give the repository a chance to do any schema migration before
|
||||
// things get further underway
|
||||
try {
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
migrateSchema(conn, liaison);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (PersistenceException pe) {
|
||||
Log.warning("Failure migrating schema [dbident=" + _dbident + "].");
|
||||
Log.logStackTrace(pe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied operation. In the event of a transient
|
||||
* failure, the repository will attempt to reestablish the database
|
||||
* connection and try the operation again.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected Object execute (Operation op)
|
||||
throws PersistenceException
|
||||
{
|
||||
return execute(op, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied operation followed by a call to
|
||||
* <code>commit()</code> on the connection unless a
|
||||
* <code>PersistenceException</code> or runtime error occurs, in which
|
||||
* case a call to <code>rollback()</code> is executed on the
|
||||
* connection.
|
||||
*
|
||||
* @param retryOnTransientFailure if true and the operation fails due
|
||||
* to a transient failure (like losing the connection to the database
|
||||
* or deadlock detection), the connection to the database will be
|
||||
* reestablished (if necessary) and the operation attempted once more.
|
||||
*
|
||||
* @return whatever value is returned by the invoked operation.
|
||||
*/
|
||||
protected Object execute (Operation op, boolean retryOnTransientFailure)
|
||||
throws PersistenceException
|
||||
{
|
||||
Connection conn = null;
|
||||
DatabaseLiaison liaison = null;
|
||||
Object rv = null;
|
||||
boolean supportsTransactions = false;
|
||||
boolean attemptedOperation = false;
|
||||
|
||||
// check our pre-condition
|
||||
if (_precond != null && !_precond.validate(_dbident, op)) {
|
||||
Log.warning("Repository operation failed pre-condition check! " +
|
||||
"[dbident=" + _dbident + ", op=" + op + "].");
|
||||
Thread.dumpStack();
|
||||
}
|
||||
|
||||
try {
|
||||
// obtain our database connection and associated goodies
|
||||
conn = _provider.getConnection(_dbident);
|
||||
liaison = LiaisonRegistry.getLiaison(conn);
|
||||
|
||||
// find out if we support transactions
|
||||
DatabaseMetaData dmd = conn.getMetaData();
|
||||
if (dmd != null) {
|
||||
supportsTransactions = dmd.supportsTransactions();
|
||||
}
|
||||
|
||||
// turn off auto-commit
|
||||
conn.setAutoCommit(false);
|
||||
|
||||
// let derived classes do any got-connection processing
|
||||
gotConnection(conn);
|
||||
|
||||
// invoke the operation
|
||||
attemptedOperation = true;
|
||||
rv = op.invoke(conn, liaison);
|
||||
|
||||
// commit the transaction
|
||||
if (supportsTransactions) {
|
||||
conn.commit();
|
||||
}
|
||||
|
||||
// return the operation result
|
||||
return rv;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (attemptedOperation) {
|
||||
// back out our changes if something got hosed (but not if
|
||||
// the hosage was a result of losing our connection)
|
||||
try {
|
||||
if (supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[origerr=" + sqe + ", rberr=" + rbe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
if (conn != null) {
|
||||
// let the connection provider know that the connection failed
|
||||
_provider.connectionFailed(_dbident, conn, sqe);
|
||||
|
||||
// clear out the reference so that we don't release it later
|
||||
conn = null;
|
||||
}
|
||||
|
||||
// if this is a transient failure and we've been requested to
|
||||
// retry such failures, try one more time
|
||||
if (retryOnTransientFailure &&
|
||||
liaison != null && liaison.isTransientException(sqe)) {
|
||||
// the MySQL JDBC driver has the annoying habit of
|
||||
// including the embedded exception stack trace in the
|
||||
// message of their outer exception; if I want a fucking
|
||||
// stack trace, I'll call printStackTrace() thanksverymuch
|
||||
String msg = StringUtil.split("" + sqe, "\n")[0];
|
||||
Log.info("Transient failure executing operation, " +
|
||||
"retrying [error=" + msg + "].");
|
||||
return execute(op, false);
|
||||
}
|
||||
|
||||
String err = "Operation invocation failed";
|
||||
throw new PersistenceException(err, sqe);
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
// back out our changes if something got hosed
|
||||
try {
|
||||
if (supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[origerr=" + pe + ", rberr=" + rbe + "].");
|
||||
}
|
||||
throw pe;
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
// back out our changes if something got hosed
|
||||
try {
|
||||
if (conn != null && supportsTransactions && !conn.isClosed()) {
|
||||
conn.rollback();
|
||||
}
|
||||
} catch (SQLException rbe) {
|
||||
Log.warning("Unable to roll back operation " +
|
||||
"[origerr=" + rte + ", rberr=" + rbe + "].");
|
||||
}
|
||||
throw rte;
|
||||
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
// release the database connection
|
||||
_provider.releaseConnection(_dbident, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, returning
|
||||
* the number of rows modified.
|
||||
*/
|
||||
protected int update (final String query)
|
||||
throws PersistenceException
|
||||
{
|
||||
Integer rv = (Integer)execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
return new Integer(stmt.executeUpdate(query));
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
return rv.intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the supplied update query in this repository, throwing an
|
||||
* exception if the modification count is not equal to the specified
|
||||
* count.
|
||||
*/
|
||||
protected void checkedUpdate (final String query, final int count)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
JDBCUtil.checkedUpdate(stmt, query, 1);
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs MySQL to perform table maintenance on the specified
|
||||
* table.
|
||||
*
|
||||
* @param action <code>analyze</code> recomputes the distribution of
|
||||
* the keys for the specified table. This can help certain joins to be
|
||||
* performed more efficiently. <code>optimize</code> instructs MySQL
|
||||
* to coalesce fragmented records and reclaim space left by deleted
|
||||
* records. This can improve a tables efficiency but can take a long
|
||||
* time to run on large tables.
|
||||
*/
|
||||
protected void maintenance (final String action, final String table)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(
|
||||
action + " table " + table);
|
||||
while (rs.next()) {
|
||||
String result = rs.getString("Msg_text");
|
||||
if (result == null ||
|
||||
result.indexOf("up to date") == -1 &&
|
||||
!result.equals("OK")) {
|
||||
Log.info("Table maintenance [" +
|
||||
SimpleRepository.toString(rs) + "].");
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes can override this method and perform any schema
|
||||
* migration they might need (using the idempotent {@link JDBCUtil} schema
|
||||
* migration methods). This is called during the repository's constructor
|
||||
* and will thus take place before derived classes (like the {@link
|
||||
* JORARepository} introspect on the schema to match it up to associated
|
||||
* Java classes).
|
||||
*/
|
||||
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
|
||||
throws SQLException, PersistenceException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we fetch a connection from the provider. This gives
|
||||
* derived classes an opportunity to configure whatever internals they
|
||||
* might be using with the connection that was fetched.
|
||||
*/
|
||||
protected void gotConnection (Connection conn)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a row of a result set to a string, prepending each column
|
||||
* with the column name from the result set metadata.
|
||||
*/
|
||||
protected static String toString (ResultSet rs)
|
||||
throws SQLException
|
||||
{
|
||||
ResultSetMetaData md = rs.getMetaData();
|
||||
int ccount = md.getColumnCount();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int ii = 1; ii <= ccount; ii++) {
|
||||
if (buf.length() > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(md.getColumnName(ii)).append("=");
|
||||
buf.append(rs.getObject(ii));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected String _dbident;
|
||||
protected static PreCondition _precond;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// $Id: StaticConnectionProvider.java,v 1.4 2002/03/03 03:14:15 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.PropertiesUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The static connection provider generates JDBC connections based on
|
||||
* configuration information provided via a properties file. It does no
|
||||
* connection pooling and always returns the same connection for a
|
||||
* particular identifier (unless that connection need be closed because of
|
||||
* a connection failure, in which case it opens a new one the next time
|
||||
* the connection is requested).
|
||||
*
|
||||
* <p> The configuration properties file should contain the following
|
||||
* information:
|
||||
*
|
||||
* <pre>
|
||||
* IDENT.driver=[jdbc driver class]
|
||||
* IDENT.url=[jdbc driver url]
|
||||
* IDENT.username=[jdbc username]
|
||||
* IDENT.password=[jdbc password]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*
|
||||
* Where <code>IDENT</code> is the database identifier for a particular
|
||||
* database connection. When a particular database identifier is
|
||||
* requested, the configuration information will be fetched from the
|
||||
* properties.
|
||||
*
|
||||
* <p> Additionally, a default set of properties can be provided using the
|
||||
* identifier <code>default</code>. Values not provided for a specific
|
||||
* identifier will be sought from the defaults. For example:
|
||||
*
|
||||
* <pre>
|
||||
* default.driver=[jdbc driver class]
|
||||
* default.url=[jdbc driver class]
|
||||
*
|
||||
* IDENT1.username=[jdbc username]
|
||||
* IDENT1.password=[jdbc password]
|
||||
*
|
||||
* IDENT2.username=[jdbc username]
|
||||
* IDENT2.password=[jdbc password]
|
||||
*
|
||||
* [...]
|
||||
* </pre>
|
||||
*/
|
||||
public class StaticConnectionProvider implements ConnectionProvider
|
||||
{
|
||||
/**
|
||||
* Constructs a static connection provider which will load its
|
||||
* configuration from a properties file accessible via the classpath
|
||||
* of the running application and identified by the specified path.
|
||||
*
|
||||
* @param propPath the path (relative to the classpath) to the
|
||||
* properties file that will be used for configuration information.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs locating or
|
||||
* loading the specified properties file.
|
||||
*/
|
||||
public StaticConnectionProvider (String propPath)
|
||||
throws IOException
|
||||
{
|
||||
this(ConfigUtil.loadProperties(propPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a static connection provider which will fetch its
|
||||
* configuration information from the specified properties object.
|
||||
*
|
||||
* @param props the configuration for this connection provider.
|
||||
*/
|
||||
public StaticConnectionProvider (Properties props)
|
||||
{
|
||||
_props = props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all of the open database connections in preparation for
|
||||
* shutting down.
|
||||
*/
|
||||
public void shutdown ()
|
||||
{
|
||||
// close all of the connections
|
||||
Iterator iter = _keys.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
String key = (String)iter.next();
|
||||
Mapping conmap = (Mapping)_keys.get(key);
|
||||
try {
|
||||
conmap.connection.close();
|
||||
} catch (SQLException sqe) {
|
||||
Log.warning("Error shutting down connection " +
|
||||
"[key=" + key + ", err=" + sqe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
// clear out our mapping tables
|
||||
_keys.clear();
|
||||
_idents.clear();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Connection getConnection (String ident)
|
||||
throws PersistenceException
|
||||
{
|
||||
Mapping conmap = (Mapping)_idents.get(ident);
|
||||
|
||||
// open the connection if we haven't already
|
||||
if (conmap == null) {
|
||||
Properties props =
|
||||
PropertiesUtil.getSubProperties(_props, ident, DEFAULTS_KEY);
|
||||
|
||||
// get the JDBC configuration info
|
||||
String err = "No driver class specified [ident=" + ident + "].";
|
||||
String driver = requireProp(props, "driver", err);
|
||||
err = "No driver URL specified [ident=" + ident + "].";
|
||||
String url = requireProp(props, "url", err);
|
||||
err = "No driver username specified [ident=" + ident + "].";
|
||||
String username = requireProp(props, "username", err);
|
||||
err = "No driver password specified [ident=" + ident + "].";
|
||||
String password = requireProp(props, "password", err);
|
||||
|
||||
// we cache connections by username+url to avoid making more
|
||||
// that one connection to a particular database server
|
||||
String key = username + "@" + url;
|
||||
conmap = (Mapping)_keys.get(key);
|
||||
if (conmap == null) {
|
||||
Log.debug("Creating " + key + " for " + ident + ".");
|
||||
conmap = new Mapping();
|
||||
conmap.key = key;
|
||||
conmap.connection =
|
||||
openConnection(driver, url, username, password);
|
||||
_keys.put(key, conmap);
|
||||
} else {
|
||||
Log.debug("Reusing " + key + " for " + ident + ".");
|
||||
}
|
||||
|
||||
// cache the connection
|
||||
conmap.idents.add(ident);
|
||||
_idents.put(ident, conmap);
|
||||
}
|
||||
|
||||
return conmap.connection;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void releaseConnection (String ident, Connection conn)
|
||||
{
|
||||
// nothing to do here, all is well
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void connectionFailed (String ident, Connection conn,
|
||||
SQLException error)
|
||||
{
|
||||
Mapping conmap = (Mapping)_idents.get(ident);
|
||||
if (conmap == null) {
|
||||
Log.warning("Unknown connection failed!? [ident=" + ident + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// attempt to close the connection
|
||||
try {
|
||||
conmap.connection.close();
|
||||
} catch (SQLException sqe) {
|
||||
Log.warning("Error closing failed connection [ident=" + ident +
|
||||
", error=" + sqe + "].");
|
||||
}
|
||||
|
||||
// clear it from our mapping tables
|
||||
for (int ii = 0; ii < conmap.idents.size(); ii++) {
|
||||
_idents.remove(conmap.idents.get(ii));
|
||||
}
|
||||
_keys.remove(conmap.key);
|
||||
}
|
||||
|
||||
protected Connection openConnection (
|
||||
String driver, String url, String username, String password)
|
||||
throws PersistenceException
|
||||
{
|
||||
// load up the driver class
|
||||
try {
|
||||
Class.forName(driver);
|
||||
} catch (Exception e) {
|
||||
String err = "Error loading driver [class=" + driver + "].";
|
||||
throw new PersistenceException(err, e);
|
||||
}
|
||||
|
||||
// create the connection
|
||||
try {
|
||||
return DriverManager.getConnection(url, username, password);
|
||||
} catch (SQLException sqe) {
|
||||
String err = "Error creating database connection " +
|
||||
"[driver=" + driver + ", url=" + url +
|
||||
", username=" + username + "].";
|
||||
throw new PersistenceException(err, sqe);
|
||||
}
|
||||
}
|
||||
|
||||
protected static String requireProp (
|
||||
Properties props, String name, String errmsg)
|
||||
throws PersistenceException
|
||||
{
|
||||
String value = props.getProperty(name);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
// augment the error message
|
||||
errmsg = "Unable to get connection. " + errmsg;
|
||||
throw new PersistenceException(errmsg);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Contains information on a particular connection to which any
|
||||
* number of database identifiers can be mapped. */
|
||||
protected static class Mapping
|
||||
{
|
||||
/** The combination of username and JDBC url that uniquely
|
||||
* identifies our database connection. */
|
||||
public String key;
|
||||
|
||||
/** The connection itself. */
|
||||
public Connection connection;
|
||||
|
||||
/** The database identifiers that are mapped to this connection. */
|
||||
public ArrayList idents = new ArrayList();
|
||||
}
|
||||
|
||||
/** Our configuration in the form of a properties object. */
|
||||
protected Properties _props;
|
||||
|
||||
/** A mapping from database identifier to connection records. */
|
||||
protected HashMap _idents = new HashMap();
|
||||
|
||||
/** A mapping from connection key to connection records. */
|
||||
protected HashMap _keys = new HashMap();
|
||||
|
||||
/** The key used as defaults for the database definitions. */
|
||||
protected static final String DEFAULTS_KEY = "default";
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
//-< Cursor.java >---------------------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 20-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Cursor for navigation thru result of SELECT statement
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.util.*;
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/** Cursor is used for successive access to records fetched by SELECT
|
||||
* statement. As far as records can be retrived from several derived tables
|
||||
* (polymorphic form of select), this class can issue several requests
|
||||
* to database. Cursor also provides methods for updating/deleting current
|
||||
* record.
|
||||
*/
|
||||
public class Cursor {
|
||||
/**
|
||||
* A cursor is initially positioned before its first row; the
|
||||
* first call to next makes the first row the current row; the
|
||||
* second call makes the second row the current row, etc.
|
||||
*
|
||||
* <P>If an input stream from the previous row is open, it is
|
||||
* implicitly closed. The ResultSet's warning chain is cleared
|
||||
* when a new row is read.
|
||||
*
|
||||
* @return object constructed from fetched record or null if there
|
||||
* are no more rows
|
||||
*/
|
||||
public Object next()
|
||||
throws SQLException
|
||||
{
|
||||
// if we closed everything up after the last call to next(),
|
||||
// nTables will be zero here and we should bail immediately
|
||||
if (nTables == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// try {
|
||||
do {
|
||||
if (result == null) {
|
||||
if (table.isAbstract) {
|
||||
table = table.derived;
|
||||
continue;
|
||||
}
|
||||
if (qbeObject != null) {
|
||||
PreparedStatement qbeStmt;
|
||||
synchronized(session.preparedStmtHash) {
|
||||
Object s = session.preparedStmtHash.get(query);
|
||||
if (s == null) {
|
||||
qbeStmt=
|
||||
session.connection.prepareStatement(query);
|
||||
session.preparedStmtHash.put(query, qbeStmt);
|
||||
} else {
|
||||
qbeStmt = (PreparedStatement)s;
|
||||
}
|
||||
}
|
||||
synchronized(qbeStmt) {
|
||||
table.bindQueryVariables(
|
||||
qbeStmt, qbeObject, qbeMask);
|
||||
result = qbeStmt.executeQuery();
|
||||
qbeStmt.clearParameters();
|
||||
}
|
||||
} else {
|
||||
if (stmt == null) {
|
||||
stmt = session.connection.createStatement();
|
||||
}
|
||||
result = stmt.executeQuery(query);
|
||||
}
|
||||
}
|
||||
if (result.next()) {
|
||||
return currObject = table.load(result);
|
||||
}
|
||||
result.close();
|
||||
result = null;
|
||||
currObject = null;
|
||||
table = table.derived;
|
||||
} while (--nTables != 0);
|
||||
|
||||
if (stmt != null) {
|
||||
stmt.close();
|
||||
}
|
||||
// }
|
||||
// catch (SQLException ex) { session.handleSQLException(ex); }
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first element matched by this cursor or null if no elements
|
||||
* were matched. Checks to ensure that no subsequent elements were matched
|
||||
* by the query, logs a warning if there were spurious additional matches.
|
||||
*/
|
||||
public Object get()
|
||||
throws SQLException
|
||||
{
|
||||
Object result = next();
|
||||
if (result != null) {
|
||||
int spurious = 0;
|
||||
while (next() != null) {
|
||||
spurious++;
|
||||
}
|
||||
if (spurious > 0) {
|
||||
Log.warning("Cursor.get() quietly tossed " + spurious +
|
||||
" spurious additional records. " +
|
||||
"[query=" + query + "].");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Update current record pointed by cursor. This method can be called
|
||||
* only after next() method, which returns non-null object. This objects
|
||||
* is used to update current record fields.<P>
|
||||
*
|
||||
* If you are going to update or delete selected records, you should add
|
||||
* "for update" clause to select statement. So parameter of
|
||||
* <CODE>jora.Table.select()</CODE> statement should contain "for update"
|
||||
* clause:
|
||||
* <CODE>record.table.Select("where name='xyz' for update");</CODE><P>
|
||||
*
|
||||
* <I><B>Attention!</I></B>
|
||||
* Not all database drivers support update operation with
|
||||
* cursor. This method will not work with such database drivers.
|
||||
*/
|
||||
public void update()
|
||||
throws SQLException
|
||||
{
|
||||
if (currObject == null) {
|
||||
throw new NoCurrentObjectError();
|
||||
}
|
||||
// try {
|
||||
table.updateVariables(result, currObject);
|
||||
// }
|
||||
// catch (SQLException ex) { session.handleSQLException(ex); }
|
||||
}
|
||||
|
||||
/** Delete current record pointed by cursor. This method can be called
|
||||
* only after next() method, which returns non-null object.<P>
|
||||
*
|
||||
* If you are going to update or delete selected records, you should add
|
||||
* "for update" clause to select statement. So parameter of
|
||||
* <CODE>jora.Table.select()</CODE> statement should contain "for update"
|
||||
* clause:
|
||||
* <CODE>record.table.Select("where name='xyz' for update");</CODE><P>
|
||||
*
|
||||
* <I><B>Attention!</I></B>
|
||||
* Not all database drivers support delete operation with cursor.
|
||||
* This method will not work with such database drivers.
|
||||
*/
|
||||
public void delete()
|
||||
throws SQLException
|
||||
{
|
||||
if (currObject == null) {
|
||||
throw new NoCurrentObjectError();
|
||||
}
|
||||
// try {
|
||||
result.deleteRow();
|
||||
// }
|
||||
// catch (SQLException ex) { session.handleSQLException(ex); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Cursor, even if we haven't read all the possible
|
||||
* objects.
|
||||
*/
|
||||
public void close ()
|
||||
throws SQLException
|
||||
{
|
||||
if (stmt != null) {
|
||||
stmt.close(); // also automatically closes result
|
||||
result = null;
|
||||
stmt = null;
|
||||
}
|
||||
nTables = 0;
|
||||
}
|
||||
|
||||
/** Extracts no more than <I>maxElements</I> records from database and
|
||||
* store them into array. It is possible to extract rest records
|
||||
* by successive next() or toArray() calls. Selected objects should
|
||||
* have now components of InputStream, Blob or Clob type, because
|
||||
* their data will be not available after fetching next record.
|
||||
*
|
||||
* @param maxElements limitation for result array size (and also for number
|
||||
* of fetched records)
|
||||
* @return Array with objects constructed from fetched records.
|
||||
*/
|
||||
public Object[] toArray(int maxElements)
|
||||
throws SQLException
|
||||
{
|
||||
Vector v = new Vector(maxElements < 100 ? maxElements : 100);
|
||||
Object o;
|
||||
while (--maxElements >= 0 && (o = next()) != null) {
|
||||
v.addElement(o);
|
||||
}
|
||||
Object[] a = new Object[v.size()];
|
||||
v.copyInto(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
/** Store all objects returned by SELECT query into array of Object.
|
||||
* Selected objects should have now components of InputStream, Blob or
|
||||
* Clob type, because their data will be not available after fetching
|
||||
* next record.
|
||||
*
|
||||
* @return Array with objects constructed from fetched records.
|
||||
*/
|
||||
public Object[] toArray()
|
||||
throws SQLException
|
||||
{ return toArray(Integer.MAX_VALUE); }
|
||||
|
||||
/** Extracts no more than <I>maxElements</I> records from database and
|
||||
* store them into array. It is possible to extract rest records
|
||||
* by successive next() or toArray() calls. Selected objects should
|
||||
* have now components of InputStream, Blob or Clob type, because
|
||||
* their data will be not available after fetching next record.
|
||||
*
|
||||
* @param maxElements limitation for result array size (and also for number
|
||||
* of fetched records)
|
||||
* @return List with objects constructed from fetched records.
|
||||
*/
|
||||
public List toArrayList(int maxElements)
|
||||
throws SQLException
|
||||
{
|
||||
ArrayList al = new ArrayList(maxElements < 100 ? maxElements : 100);
|
||||
Object o;
|
||||
while (--maxElements >= 0 && (o = next()) != null) {
|
||||
al.add(o);
|
||||
}
|
||||
return al;
|
||||
}
|
||||
|
||||
/** Store all objects returned by SELECT query into a list of Object.
|
||||
* Selected objects should have now components of InputStream, Blob or
|
||||
* Clob type, because their data will be not available after fetching
|
||||
* next record.
|
||||
*
|
||||
* @return Array with objects constructed from fetched records.
|
||||
*/
|
||||
public List toArrayList()
|
||||
throws SQLException
|
||||
{ return toArrayList(Integer.MAX_VALUE); }
|
||||
|
||||
// Internals
|
||||
|
||||
protected Cursor(Table table, Session session, int nTables, String query) {
|
||||
if (session == null) {
|
||||
session = ((SessionThread)Thread.currentThread()).session;
|
||||
}
|
||||
this.table = table;
|
||||
this.session = session;
|
||||
this.nTables = nTables;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
protected Cursor(Table table, Session session, int nTables,
|
||||
Object obj, FieldMask mask, boolean like) {
|
||||
if (session == null) {
|
||||
session = ((SessionThread)Thread.currentThread()).session;
|
||||
}
|
||||
this.table = table;
|
||||
this.session = session;
|
||||
this.nTables = nTables;
|
||||
this.like = like;
|
||||
qbeObject = obj;
|
||||
qbeMask = mask;
|
||||
query = table.buildQueryList(obj, mask, like);
|
||||
stmt = null;
|
||||
}
|
||||
|
||||
private Table table;
|
||||
private Session session;
|
||||
private int nTables;
|
||||
private ResultSet result;
|
||||
private String query;
|
||||
private Statement stmt;
|
||||
private Object currObject;
|
||||
private Object qbeObject;
|
||||
private FieldMask qbeMask;
|
||||
private boolean like;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//-< DataTransferError.java >----------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 19-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Exception raised when error is happed during data transfer between
|
||||
// program and database server
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/** This error is raised when error is happened during data transfer
|
||||
* between program and database server (for example IOException was thrown
|
||||
* while operation with InputStream field)
|
||||
*/
|
||||
public class DataTransferError extends java.lang.Error {
|
||||
DataTransferError() {
|
||||
super("Database data transfer error");
|
||||
}
|
||||
DataTransferError(Exception ex) {
|
||||
super(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
//-< FieldDescriptor.java >------------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 25-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Table field descriptor
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.sql.*;
|
||||
import java.math.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
class FieldDescriptor {
|
||||
protected int inType; // type tag for field input (see constants below)
|
||||
protected int outType; // type tag for field output
|
||||
protected int scale; // scale for tDecimal type,
|
||||
protected String name; // full (compound) name of component
|
||||
protected Field field; // field info from java.lang.reflect
|
||||
|
||||
protected Constructor constructor; // constructor of object component
|
||||
|
||||
protected static final int t_byte = 0;
|
||||
protected static final int t_short = 1;
|
||||
protected static final int t_int = 2;
|
||||
protected static final int t_long = 3;
|
||||
protected static final int t_float = 4;
|
||||
protected static final int t_double = 5;
|
||||
protected static final int t_boolean = 6;
|
||||
protected static final int tByte = 7;
|
||||
protected static final int tShort = 8;
|
||||
protected static final int tInteger = 9;
|
||||
protected static final int tLong = 10;
|
||||
protected static final int tFloat = 11;
|
||||
protected static final int tDouble = 12;
|
||||
protected static final int tBoolean = 13;
|
||||
protected static final int tDecimal = 14;
|
||||
protected static final int tString = 15;
|
||||
protected static final int tBytes = 16;
|
||||
protected static final int tDate = 17;
|
||||
protected static final int tTime = 18;
|
||||
protected static final int tTimestamp = 19;
|
||||
protected static final int tStream = 20;
|
||||
protected static final int tBlob = 21;
|
||||
protected static final int tClob = 22;
|
||||
protected static final int tAsString = 23;
|
||||
|
||||
protected static final int tClosure = 24;
|
||||
protected static final int tCompound = 25;
|
||||
|
||||
protected static final int[] sqlTypeMapping = {
|
||||
Types.INTEGER, // t_byte
|
||||
Types.INTEGER, // t_short
|
||||
Types.INTEGER, // t_int
|
||||
Types.BIGINT, // t_long
|
||||
Types.FLOAT, // t_float
|
||||
Types.DOUBLE, // t_double
|
||||
Types.BIT, // t_boolean
|
||||
Types.INTEGER, // tByte
|
||||
Types.INTEGER, // tShort
|
||||
Types.INTEGER, // tInteger
|
||||
Types.BIGINT, // tLong
|
||||
Types.FLOAT, // tFloat
|
||||
Types.DOUBLE, // tDouble
|
||||
Types.BIT, // tBoolean
|
||||
Types.NUMERIC, // tDecimal
|
||||
Types.VARCHAR, // tString
|
||||
Types.VARBINARY,// tBytes
|
||||
Types.DATE, // tDate
|
||||
Types.TIME, // tTime
|
||||
Types.TIMESTAMP, // tTimestamp
|
||||
Types.LONGVARBINARY, // tStream
|
||||
Types.LONGVARBINARY, // tBlob
|
||||
Types.LONGVARCHAR, // tClob
|
||||
Types.VARCHAR, // tAsString
|
||||
Types.LONGVARBINARY // tClosure
|
||||
};
|
||||
|
||||
protected FieldDescriptor(Field field, String name) {
|
||||
this.name = name;
|
||||
this.field = field;
|
||||
this.scale = -1;
|
||||
}
|
||||
|
||||
protected final boolean isAtomic() { return inType < tClosure; }
|
||||
|
||||
protected final boolean isCompound() { return inType >= tCompound; }
|
||||
|
||||
protected final boolean isBuiltin() { return inType <= t_boolean; }
|
||||
|
||||
|
||||
protected final boolean bindVariable(PreparedStatement pstmt,
|
||||
Object obj, int column)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
switch (outType) {
|
||||
case t_byte:
|
||||
pstmt.setByte(column, field.getByte(obj));
|
||||
break;
|
||||
case t_short:
|
||||
pstmt.setShort(column, field.getShort(obj));
|
||||
break;
|
||||
case t_int:
|
||||
pstmt.setInt(column, field.getInt(obj));
|
||||
break;
|
||||
case t_long:
|
||||
pstmt.setLong(column, field.getLong(obj));
|
||||
break;
|
||||
case t_float:
|
||||
pstmt.setFloat(column, field.getFloat(obj));
|
||||
break;
|
||||
case t_double:
|
||||
pstmt.setDouble(column, field.getDouble(obj));
|
||||
break;
|
||||
case t_boolean:
|
||||
pstmt.setBoolean(column, field.getBoolean(obj));
|
||||
break;
|
||||
case tByte:
|
||||
pstmt.setByte(column, ((Byte)field.get(obj)).byteValue());
|
||||
break;
|
||||
case tShort:
|
||||
pstmt.setShort(column, ((Short)field.get(obj)).shortValue());
|
||||
break;
|
||||
case tInteger:
|
||||
pstmt.setInt(column, ((Integer)field.get(obj)).intValue());
|
||||
break;
|
||||
case tLong:
|
||||
pstmt.setLong(column, ((Long)field.get(obj)).longValue());
|
||||
break;
|
||||
case tFloat:
|
||||
pstmt.setFloat(column, ((Float)field.get(obj)).floatValue());
|
||||
break;
|
||||
case tDouble:
|
||||
pstmt.setDouble(column,((Double)field.get(obj)).doubleValue());
|
||||
break;
|
||||
case tBoolean:
|
||||
pstmt.setBoolean(column,
|
||||
((Boolean)field.get(obj)).booleanValue());
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
pstmt.setBigDecimal(column, (BigDecimal)field.get(obj));
|
||||
break;
|
||||
case tString:
|
||||
pstmt.setString(column, (String)field.get(obj));
|
||||
break;
|
||||
case tBytes:
|
||||
pstmt.setBytes(column, (byte[])field.get(obj));
|
||||
break;
|
||||
case tDate:
|
||||
pstmt.setDate(column, (java.sql.Date)field.get(obj));
|
||||
break;
|
||||
case tTime:
|
||||
pstmt.setTime(column, (java.sql.Time)field.get(obj));
|
||||
break;
|
||||
case tTimestamp:
|
||||
pstmt.setTimestamp(column, (java.sql.Timestamp)field.get(obj));
|
||||
break;
|
||||
case tStream:
|
||||
java.io.InputStream in = (java.io.InputStream)field.get(obj);
|
||||
pstmt.setBinaryStream(column, in, in.available());
|
||||
break;
|
||||
case tBlob:
|
||||
pstmt.setBlob(column, (Blob)field.get(obj));
|
||||
break;
|
||||
case tClob:
|
||||
pstmt.setClob(column, (Clob)field.get(obj));
|
||||
break;
|
||||
case tAsString:
|
||||
pstmt.setString(column, field.get(obj).toString());
|
||||
break;
|
||||
case tClosure:
|
||||
// There is no reason to use piped streams because
|
||||
// we need to pass total number of bytes to JDBC driver
|
||||
java.io.ByteArrayOutputStream out =
|
||||
new java.io.ByteArrayOutputStream();
|
||||
java.io.ObjectOutputStream clu =
|
||||
new java.io.ObjectOutputStream(out);
|
||||
clu.writeObject(field.get(obj));
|
||||
clu.close();
|
||||
pstmt.setBytes(column, out.toByteArray());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch(SQLException ex) {
|
||||
if (outType != tClosure && outType != tAsString) {
|
||||
outType = tAsString;
|
||||
return bindVariable(pstmt, obj, column);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
} catch(IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new IllegalAccessError();
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final boolean updateVariable(ResultSet result,
|
||||
Object obj, int column)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
switch (outType) {
|
||||
case t_byte:
|
||||
result.updateByte(column, field.getByte(obj));
|
||||
break;
|
||||
case t_short:
|
||||
result.updateShort(column, field.getShort(obj));
|
||||
break;
|
||||
case t_int:
|
||||
result.updateInt(column, field.getInt(obj));
|
||||
break;
|
||||
case t_long:
|
||||
result.updateLong(column, field.getLong(obj));
|
||||
break;
|
||||
case t_float:
|
||||
result.updateFloat(column, field.getFloat(obj));
|
||||
break;
|
||||
case t_double:
|
||||
result.updateDouble(column, field.getDouble(obj));
|
||||
break;
|
||||
case t_boolean:
|
||||
result.updateBoolean(column, field.getBoolean(obj));
|
||||
break;
|
||||
case tByte:
|
||||
result.updateByte(column, ((Byte)field.get(obj)).byteValue());
|
||||
break;
|
||||
case tShort:
|
||||
result.updateShort(column,
|
||||
((Short)field.get(obj)).shortValue());
|
||||
break;
|
||||
case tInteger:
|
||||
result.updateInt(column, ((Integer)field.get(obj)).intValue());
|
||||
break;
|
||||
case tLong:
|
||||
result.updateLong(column, ((Long)field.get(obj)).longValue());
|
||||
break;
|
||||
case tFloat:
|
||||
result.updateFloat(column,
|
||||
((Float)field.get(obj)).floatValue());
|
||||
break;
|
||||
case tDouble:
|
||||
result.updateDouble(column,
|
||||
((Double)field.get(obj)).doubleValue());
|
||||
break;
|
||||
case tBoolean:
|
||||
result.updateBoolean(column,
|
||||
((Boolean)field.get(obj)).booleanValue());
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
result.updateBigDecimal(column, (BigDecimal)field.get(obj));
|
||||
break;
|
||||
case tString:
|
||||
result.updateString(column, (String)field.get(obj));
|
||||
break;
|
||||
case tBytes:
|
||||
result.updateBytes(column, (byte[])field.get(obj));
|
||||
break;
|
||||
case tDate:
|
||||
result.updateDate(column, (java.sql.Date)field.get(obj));
|
||||
break;
|
||||
case tTime:
|
||||
result.updateTime(column, (java.sql.Time)field.get(obj));
|
||||
break;
|
||||
case tTimestamp:
|
||||
result.updateTimestamp(column,
|
||||
(java.sql.Timestamp)field.get(obj));
|
||||
break;
|
||||
case tStream:
|
||||
java.io.InputStream in = (java.io.InputStream)field.get(obj);
|
||||
result.updateBinaryStream(column, in, in.available());
|
||||
break;
|
||||
case tBlob:
|
||||
Blob blob = (Blob)field.get(obj);
|
||||
result.updateBinaryStream(column,
|
||||
blob.getBinaryStream(),
|
||||
(int)blob.length());
|
||||
break;
|
||||
case tClob:
|
||||
Clob clob = (Clob)field.get(obj);
|
||||
result.updateCharacterStream(column,
|
||||
clob.getCharacterStream(),
|
||||
(int)clob.length());
|
||||
break;
|
||||
case tAsString:
|
||||
result.updateString(column, field.get(obj).toString());
|
||||
break;
|
||||
case tClosure:
|
||||
// There is no reason to use piped streams because
|
||||
// we need to pass total number of bytes to JDBC driver
|
||||
java.io.ByteArrayOutputStream out =
|
||||
new java.io.ByteArrayOutputStream();
|
||||
java.io.ObjectOutputStream clu =
|
||||
new java.io.ObjectOutputStream(out);
|
||||
clu.writeObject(field.get(obj));
|
||||
clu.close();
|
||||
result.updateBytes(column, out.toByteArray());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch(SQLException ex) {
|
||||
if (outType != tClosure && outType != tAsString) {
|
||||
outType = tAsString;
|
||||
return updateVariable(result, obj, column);
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
} catch(IllegalAccessException ex) {
|
||||
ex.printStackTrace();
|
||||
throw new IllegalAccessError();
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final boolean loadVariable(ResultSet result,
|
||||
Object obj, int column)
|
||||
throws SQLException, IllegalAccessException
|
||||
{
|
||||
switch (inType) {
|
||||
case t_byte:
|
||||
field.setByte(obj, result.getByte(column));
|
||||
break;
|
||||
case t_short:
|
||||
field.setShort(obj, result.getShort(column));
|
||||
break;
|
||||
case t_int:
|
||||
field.setInt(obj, result.getInt(column));
|
||||
break;
|
||||
case t_long:
|
||||
field.setLong(obj, result.getLong(column));
|
||||
break;
|
||||
case t_float:
|
||||
field.setFloat(obj, result.getFloat(column));
|
||||
break;
|
||||
case t_double:
|
||||
field.setDouble(obj, result.getDouble(column));
|
||||
break;
|
||||
case t_boolean:
|
||||
field.setBoolean(obj, result.getBoolean(column));
|
||||
break;
|
||||
|
||||
case tByte:
|
||||
byte b = result.getByte(column);
|
||||
field.set(obj, result.wasNull() ? null : new Byte(b));
|
||||
break;
|
||||
case tShort:
|
||||
short s = result.getShort(column);
|
||||
field.set(obj, result.wasNull() ? null : new Short(s));
|
||||
break;
|
||||
case tInteger:
|
||||
int i = result.getInt(column);
|
||||
field.set(obj, result.wasNull() ? null : new Integer(i));
|
||||
break;
|
||||
case tLong:
|
||||
long l = result.getLong(column);
|
||||
field.set(obj, result.wasNull() ? null : new Long(l));
|
||||
break;
|
||||
case tFloat:
|
||||
float f = result.getFloat(column);
|
||||
field.set(obj, result.wasNull() ? null : new Float(f));
|
||||
field.setFloat(obj, result.getFloat(column));
|
||||
break;
|
||||
case tDouble:
|
||||
double d = result.getDouble(column);
|
||||
field.set(obj, result.wasNull() ? null : new Double(d));
|
||||
break;
|
||||
case tBoolean:
|
||||
boolean bl = result.getBoolean(column);
|
||||
field.set(obj, result.wasNull() ? null : new Boolean(bl));
|
||||
break;
|
||||
|
||||
case tDecimal:
|
||||
field.set(obj, result.getBigDecimal(column));
|
||||
break;
|
||||
case tString:
|
||||
field.set(obj, result.getString(column));
|
||||
break;
|
||||
case tBytes:
|
||||
field.set(obj, result.getBytes(column));
|
||||
break;
|
||||
case tDate:
|
||||
field.set(obj, result.getDate(column));
|
||||
break;
|
||||
case tTime:
|
||||
field.set(obj, result.getTime(column));
|
||||
break;
|
||||
case tTimestamp:
|
||||
field.set(obj, result.getTimestamp(column));
|
||||
break;
|
||||
case tStream:
|
||||
field.set(obj, result.getBinaryStream(column));
|
||||
break;
|
||||
case tBlob:
|
||||
field.set(obj, result.getBlob(column));
|
||||
break;
|
||||
case tClob:
|
||||
field.set(obj, result.getClob(column));
|
||||
break;
|
||||
case tClosure:
|
||||
try {
|
||||
java.io.InputStream input = result.getBinaryStream(column);
|
||||
java.io.ObjectInputStream in =
|
||||
new java.io.ObjectInputStream(input);
|
||||
field.set(obj, in.readObject());
|
||||
in.close();
|
||||
} catch(ClassNotFoundException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
} catch(java.io.IOException ex) {
|
||||
throw new DataTransferError(ex);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// $Id: FieldMask.java,v 1.6 2003/09/20 00:04:34 ray Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides support for doing partial updates to objects in a JORA table.
|
||||
* A field mask can be obtained for a particular table, fields marked as
|
||||
* modified and the object subsequently updated in a fairly
|
||||
* straightforward manner:
|
||||
*
|
||||
* <pre>
|
||||
* // updating parts of a table that contains User objects
|
||||
* User user = // load user object from table
|
||||
* FieldMask mask = table.getFieldMask();
|
||||
* user.firstName = newFirstName;
|
||||
* mask.setModified("firstName");
|
||||
* user.lastName = newLastName;
|
||||
* mask.setModified("lastName");
|
||||
* table.update(user, mask);
|
||||
* </pre>
|
||||
*/
|
||||
public class FieldMask
|
||||
implements Cloneable
|
||||
{
|
||||
/**
|
||||
* Creates a field mask for a {@link Table} that uses the supplied
|
||||
* field descriptors.
|
||||
*/
|
||||
public FieldMask (FieldDescriptor[] descrips)
|
||||
{
|
||||
// create a mapping from field name to descriptor index
|
||||
_descripMap = new HashMap();
|
||||
int dcount = descrips.length;
|
||||
for (int i = 0; i < dcount; i++) {
|
||||
_descripMap.put(descrips[i].field.getName(), new Integer(i));
|
||||
}
|
||||
// create our modified flags
|
||||
_modified = new boolean[dcount];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the fields in this mask are modified.
|
||||
*/
|
||||
public final boolean isModified ()
|
||||
{
|
||||
int mcount = _modified.length;
|
||||
for (int ii = 0; ii < mcount; ii++) {
|
||||
if (_modified[ii]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the field with the specified index is modified.
|
||||
*/
|
||||
public final boolean isModified (int index)
|
||||
{
|
||||
return _modified[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the field with the specified name is modifed.
|
||||
*/
|
||||
public final boolean isModified (String fieldName)
|
||||
{
|
||||
Integer index = (Integer)_descripMap.get(fieldName);
|
||||
if (index == null) {
|
||||
String errmsg = "Passed in field not in mask.";
|
||||
throw new IllegalArgumentException(errmsg);
|
||||
}
|
||||
return _modified[index.intValue()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only if the set of modified fields is a subset of the
|
||||
* fields specified.
|
||||
*/
|
||||
public final boolean onlySubsetModified (Set fieldSet)
|
||||
{
|
||||
Iterator itr = _descripMap.keySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
String field = (String)itr.next();
|
||||
if (isModified(field) && (!fieldSet.contains(field))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the specified field as modified.
|
||||
*/
|
||||
public void setModified (String fieldName)
|
||||
{
|
||||
Integer index = (Integer)_descripMap.get(fieldName);
|
||||
if (index == null) {
|
||||
String errmsg = "";
|
||||
throw new IllegalArgumentException(errmsg);
|
||||
}
|
||||
_modified[index.intValue()] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out the modification state of the fields in this mask.
|
||||
*/
|
||||
public void clear ()
|
||||
{
|
||||
Arrays.fill(_modified, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this field mask, with all fields set to
|
||||
* not-modified.
|
||||
*/
|
||||
public Object clone ()
|
||||
{
|
||||
try {
|
||||
FieldMask mask = (FieldMask)super.clone();
|
||||
mask._modified = new boolean[_modified.length];
|
||||
return mask;
|
||||
} catch (CloneNotSupportedException cnse) {
|
||||
throw new RuntimeException("Oh god, the clones!");
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public String toString ()
|
||||
{
|
||||
// return a list of the modified fields
|
||||
StringBuffer buf = new StringBuffer("FieldMask [modified={");
|
||||
boolean added = false;
|
||||
for (Iterator itr=_descripMap.entrySet().iterator(); itr.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry) itr.next();
|
||||
if (_modified[((Integer) entry.getValue()).intValue()]) {
|
||||
if (added) {
|
||||
buf.append(", ");
|
||||
} else {
|
||||
added = true;
|
||||
}
|
||||
buf.append(entry.getKey());
|
||||
}
|
||||
}
|
||||
buf.append("}]");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** Modified flags for each field of an object in this table. */
|
||||
protected boolean[] _modified;
|
||||
|
||||
/** A mapping from field names to field descriptor index. */
|
||||
protected HashMap _descripMap;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//-< NoCurrentObjectError.java >-------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 16-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Exception raised when UPDATE/REMOVE operation is applied to Cursor
|
||||
// with no current object
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/** Error raised when update/remove operation is applied to
|
||||
* cursor with no current object (before first call of <TT>next()</TT>
|
||||
* method).
|
||||
*/
|
||||
public class NoCurrentObjectError extends java.lang.Error {
|
||||
NoCurrentObjectError() {
|
||||
super("Cursor doesn't specify current object");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//-< NoPrimaryKeyError.java >----------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 16-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Exception raised when UPDATE/REMOVE operation is appllied to Table with
|
||||
// no primary key
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/** Error raised when unpdate/remove operation is invoked for the table
|
||||
* with no correct primary key defined (key was not specified or
|
||||
* type of the key component is not atomic).
|
||||
*/
|
||||
public class NoPrimaryKeyError extends java.lang.Error {
|
||||
NoPrimaryKeyError(Table table) {
|
||||
super("Table " + table.name + " has no atomic primary key");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
What have we here?
|
||||
------------------
|
||||
|
||||
This is a hacked version of JORA, a library for automatically mapping
|
||||
RDBMS database rows to Java objects and vice versa. It's simple, it uses
|
||||
reflection and doesn't require any classfile post-processing or external
|
||||
configuration information.
|
||||
|
||||
"That's all great but why is it hacked?" wonders the astute reader. Well,
|
||||
I couldn't quite cope with the error handling paradigm that it used (catch
|
||||
all SQL exceptions internally and pass them to an application-wide error
|
||||
handler method) and a few seconds thought on the matter led me to believe
|
||||
that there is not a simple way to allow a choice of error handling
|
||||
paradigms (the methods have to throw SQLException or not). So I took the
|
||||
easy way out and forked the codebase.
|
||||
|
||||
The code is pretty stable, so I don't expect to be merging in updates with
|
||||
any frequency. I promise to be a good monkey and submit any useful
|
||||
modifications that I make back to the original maintainer. It's a good
|
||||
thing that the license was unrestrictive enough to allow me to do this,
|
||||
otherwise I'd probably be stuck reinventing the wheel or using something
|
||||
that does less of what I want. Three cheers for free software.
|
||||
|
||||
The original version can be found here: http://www.ispras.ru/~knizhnik/
|
||||
(or at least it could when I wrote this README).
|
||||
|
||||
- mdb (2/12/2001)
|
||||
|
||||
$Id: README,v 1.2 2001/08/11 22:53:13 mdb Exp $
|
||||
@@ -0,0 +1,21 @@
|
||||
//-< SQLError.java >-------------------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 16-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Database error
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/** Database error. Exception SQLException was catched by JORA.
|
||||
*/
|
||||
public class SQLError extends java.lang.RuntimeException {
|
||||
java.sql.SQLException ex;
|
||||
SQLError(java.sql.SQLException x) {
|
||||
super("Database session aborted due to critical error");
|
||||
ex = x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
//-< Session.java >--------------------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 10-Jun-98 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 20-Jun-98 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Database session abstraction
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
import java.util.*;
|
||||
import java.sql.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* This class is reposnsible for establishing connection with database
|
||||
* and handling database errors.
|
||||
*/
|
||||
public class Session {
|
||||
public Connection connection;
|
||||
|
||||
/** Session consructor
|
||||
*
|
||||
* @param driverClass class of database driver
|
||||
*/
|
||||
public Session(String driverClass)
|
||||
{
|
||||
driver = driverClass;
|
||||
preparedStmtHash = new Hashtable();
|
||||
connectionID = 0;
|
||||
}
|
||||
|
||||
/** Construct a session with a pre-existing connection instance. In
|
||||
* this case, {@link #open} should not be called on this session.
|
||||
*
|
||||
* @param connection the connection to use.
|
||||
*/
|
||||
public Session(Connection connection)
|
||||
{
|
||||
connectionID = 0;
|
||||
preparedStmtHash = new Hashtable();
|
||||
setConnection(connection);
|
||||
}
|
||||
|
||||
/** Session consructor for ODBC bridge driver
|
||||
*/
|
||||
public Session() { this("sun.jdbc.odbc.JdbcOdbcDriver"); }
|
||||
|
||||
/** Sets the connection that should be used by this session.
|
||||
*/
|
||||
public void setConnection(Connection conn)
|
||||
{
|
||||
// only up the connection id if this is a new connection
|
||||
if (connection != conn) {
|
||||
// clear out our prepared statement hash because we've got a
|
||||
// new connection
|
||||
Enumeration items = preparedStmtHash.elements();
|
||||
while (items.hasMoreElements()) {
|
||||
try {
|
||||
((PreparedStatement)items.nextElement()).close();
|
||||
} catch (SQLException sqe) {
|
||||
Log.warning("Error closing cached prepared statement " +
|
||||
"[error=" + sqe + "].");
|
||||
}
|
||||
}
|
||||
preparedStmtHash.clear();
|
||||
|
||||
// switch to our new connection
|
||||
connection = conn;
|
||||
connectionID += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Handler of database session errors. Programmer should override
|
||||
* this method in derived class in order to provide application
|
||||
* dependent error handling.
|
||||
*
|
||||
* @param ex exception thrown by some of JDBC methods
|
||||
*/
|
||||
public void handleSQLException(SQLException ex) {
|
||||
// A SQLException was generated. Catch it and
|
||||
// display the error information. Note that there
|
||||
// could be multiple error objects chained
|
||||
// together
|
||||
System.out.println ("*** SQLException caught ***");
|
||||
SQLException x = ex;
|
||||
while (ex != null) {
|
||||
System.out.println("SQLState: " + ex.getSQLState ());
|
||||
System.out.println("Message: " + ex.getMessage ());
|
||||
System.out.println("Vendor: " + ex.getErrorCode ());
|
||||
ex = ex.getNextException();
|
||||
System.out.println ("");
|
||||
}
|
||||
throw new SQLError(x); // terminate program execution
|
||||
}
|
||||
|
||||
/** Open database session.
|
||||
* Attempt to establish a connection to the given database URL.
|
||||
* The DriverManager attempts to select an appropriate driver from
|
||||
* the set of registered JDBC drivers.
|
||||
*
|
||||
* @param dataSource a database url of the form
|
||||
* jdbc:<em>subprotocol</em>:<em>subname</em>
|
||||
* @param user the database user on whose behalf the Connection is
|
||||
* being made
|
||||
* @param password the user's password
|
||||
* @return true if session is succesfully openned, false otherwise
|
||||
*/
|
||||
public boolean open(String dataSource, String user, String password)
|
||||
throws SQLException
|
||||
{
|
||||
try {
|
||||
Class.forName(driver);
|
||||
|
||||
connection =
|
||||
DriverManager.getConnection(dataSource, user, password);
|
||||
connectionID += 1;
|
||||
}
|
||||
// catch(SQLException ex) {
|
||||
// handleSQLException(ex);
|
||||
// return false;
|
||||
// }
|
||||
catch(ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Close database session and release all resources holded by session.
|
||||
*/
|
||||
public void close()
|
||||
throws SQLException
|
||||
{
|
||||
// try {
|
||||
Enumeration items = preparedStmtHash.elements();
|
||||
while (items.hasMoreElements()) {
|
||||
((PreparedStatement)items.nextElement()).close();
|
||||
}
|
||||
preparedStmtHash.clear();
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
// }
|
||||
// catch (SQLException ex) { handleSQLException(ex); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL INSERT, UPDATE or DELETE statement. In addition,
|
||||
* SQL statements that return nothing such as SQL DDL statements
|
||||
* can be executed.
|
||||
*
|
||||
* @param sql a SQL INSERT, UPDATE or DELETE statement or a SQL
|
||||
* statement that returns nothing
|
||||
* @return either the row count for INSERT, UPDATE or DELETE or 0
|
||||
* for SQL statements that return nothing
|
||||
*/
|
||||
public int execute(String sql)
|
||||
throws SQLException
|
||||
{
|
||||
// try {
|
||||
Statement stmt = connection.createStatement();
|
||||
int result = stmt.executeUpdate(sql);
|
||||
stmt.close();
|
||||
return result;
|
||||
// } catch(SQLException ex) { handleSQLException(ex); }
|
||||
// return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit makes all changes made since the previous
|
||||
* commit/rollback permanent and releases any database locks
|
||||
* currently held by the Connection. This method should only be
|
||||
* used when auto commit has been disabled.
|
||||
*/
|
||||
public void commit()
|
||||
throws SQLException
|
||||
{
|
||||
// try {
|
||||
connection.commit();
|
||||
// } catch(SQLException ex) { handleSQLException(ex); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback drops all changes made since the previous
|
||||
* commit/rollback and releases any database locks currently held
|
||||
* by the Connection. This method should only be used when auto
|
||||
* commit has been disabled.
|
||||
*/
|
||||
public void rollback()
|
||||
throws SQLException
|
||||
{
|
||||
// try {
|
||||
connection.rollback();
|
||||
// } catch(SQLException ex) { handleSQLException(ex); }
|
||||
}
|
||||
|
||||
protected String driver; // driver class name
|
||||
protected Hashtable preparedStmtHash;
|
||||
protected int connectionID;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
//-< SessionThread.java >--------------------------------------------*--------*
|
||||
// JORA Version 2.0 (c) 1998 GARRET * ? *
|
||||
// (Java Object Relational Adapter) * /\| *
|
||||
// * / \ *
|
||||
// Created: 18-Jun-99 K.A. Knizhnik * / [] \ *
|
||||
// Last update: 18-Jun-99 K.A. Knizhnik * GARRET *
|
||||
//-------------------------------------------------------------------*--------*
|
||||
// Thread associated with database session
|
||||
//-------------------------------------------------------------------*--------*
|
||||
|
||||
package com.samskivert.jdbc.jora;
|
||||
|
||||
/**
|
||||
* Class representing thread associated with users database session.
|
||||
* If there is single database session opened by application, that it is
|
||||
* possible to associate it with Table object statically. Otherwise it is
|
||||
* needed either to explicitly specify session object in each insert,select
|
||||
* or update statement or associate session with thread by means of
|
||||
* SessionThread class.
|
||||
*/
|
||||
public class SessionThread extends Thread {
|
||||
Session session;
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object and associate it with
|
||||
* the specified session. This constructor has the same effect as
|
||||
* <code>SessionThread(session, null, null,</code>
|
||||
* <i>gname</i><code>)</code>, where <b><i>gname</i></b> is
|
||||
* a newly generated name. Automatically generated names are of the
|
||||
* form <code>"Thread-"+</code><i>n</i>, where <i>n</i> is an integer.
|
||||
* <p>
|
||||
* Threads created this way must have overridden their
|
||||
* <code>run()</code> method to actually do anything.
|
||||
*
|
||||
* @param session user database session associated with this thread
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object and associate it with
|
||||
* the specified session. This constructor has the same effect as
|
||||
* <code>SessionThread(session, target, null,</code>
|
||||
* <i>gname</i><code>)</code>, where <b><i>gname</i></b> is
|
||||
* a newly generated name. Automatically generated names are of the
|
||||
* form <code>"Thread-"+</code><i>n</i>, where <i>n</i> is an integer.
|
||||
* <p>
|
||||
* Threads created this way must have overridden their
|
||||
* <code>run()</code> method to actually do anything.
|
||||
*
|
||||
* @param session user database session associated with this thread
|
||||
* @param target the object whose <code>run</code> method is called.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, Runnable target) {
|
||||
super(target);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object and associate it with
|
||||
* the specified session. This constructor has the same effect as
|
||||
* <code>SessionThread(session, target, group,</code>
|
||||
* <i>gname</i><code>)</code>, where <b><i>gname</i></b> is
|
||||
* a newly generated name. Automatically generated names are of the
|
||||
* form <code>"Thread-"+</code><i>n</i>, where <i>n</i> is an integer.
|
||||
* <p>
|
||||
* Threads created this way must have overridden their
|
||||
* <code>run()</code> method to actually do anything.
|
||||
*
|
||||
* @param session user database session associated with this thread
|
||||
* @param group the thread group.
|
||||
* @param target the object whose <code>run</code> method is called.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, ThreadGroup group, Runnable target) {
|
||||
super(target);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object. This constructor has
|
||||
* the same effect as <code>SessionThread(session, null, null, name)</code>.
|
||||
*
|
||||
* @param session user database session associated with this thread
|
||||
* @param name the name of the new thread.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, String name) {
|
||||
super(name);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object. This constructor has
|
||||
* the same effect as <code>SessionThread(session, group, null, name)</code>.
|
||||
*
|
||||
* @param session user database session associated with this thread
|
||||
* @param group the thread group.
|
||||
* @param name the name of the new thread.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, ThreadGroup group, String name) {
|
||||
super(group, name);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object. This constructor has
|
||||
* the same effect as <code>SessionThread(session, null, target, name)</code> *
|
||||
* @param session user database session associated with this thread
|
||||
* @param target the object whose <code>run</code> method is called.
|
||||
* @param name the name of the new thread.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, Runnable target, String name) {
|
||||
super(target, name);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new <code>SessionThread</code> object so that it has
|
||||
* <code>target</code> as its run object, has the specified
|
||||
* <code>name</code> as its name, and belongs to the thread group
|
||||
* referred to by <code>group</code>.
|
||||
* <p>
|
||||
* @param session user database session associated with this thread
|
||||
* @param group the thread group.
|
||||
* @param target the object whose <code>run</code> method is called.
|
||||
* @param name the name of the new thread.
|
||||
* @see java.lang.Thread#Thread(java.lang.ThreadGroup,
|
||||
* java.lang.Runnable, java.lang.String)
|
||||
*/
|
||||
public SessionThread(Session session, ThreadGroup group, Runnable target,
|
||||
String name)
|
||||
{
|
||||
super(group, target, name);
|
||||
this.session = session;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides a mapping between JDBC tables and Java objects.
|
||||
|
||||
<p> This is a hacked version of JORA 2.06, a library for automatically
|
||||
mapping RDBMS database rows to Java objects and vice versa. It's
|
||||
simple, it uses reflection and doesn't require any classfile
|
||||
post-processing or external configuration information.
|
||||
|
||||
<p> "That's all great but why is it hacked?" wonders the astute
|
||||
reader. Well, I couldn't quite cope with the error handling paradigm
|
||||
that it used (catch all SQL exceptions internally and pass them to
|
||||
an application-wide error handler method) and a few seconds thought
|
||||
on the matter led me to believe that there is not a simple way to
|
||||
allow a choice of error handling paradigms (the methods have to
|
||||
throw SQLException or not). So I took the easy way out and forked
|
||||
the codebase.
|
||||
|
||||
<p> The code is pretty stable, so I don't expect to be merging in
|
||||
updates with any frequency. I promise to be a good monkey and submit
|
||||
any useful modifications that I make back to the original
|
||||
maintainer. Fortunately the license was unrestrictive enough to
|
||||
allow me to do this, otherwise I'd probably be stuck reinventing the
|
||||
wheel or using something that does less of what I want. Three cheers
|
||||
for free software.
|
||||
|
||||
<p> The JORA package was written by Konstantin Knizhnik and the
|
||||
original is available from
|
||||
<a href="http://www.ispras.ru/~knizhnik/">his web site.</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides support services to applications that access relational
|
||||
databases via JDBC.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// $Id: AttachableURLFactory.java,v 1.1 2003/07/09 18:44:11 ray Exp $
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Allows other entities in an application to register URLStreamHandler
|
||||
* classes for protocols of their own making.
|
||||
*/
|
||||
public class AttachableURLFactory implements URLStreamHandlerFactory
|
||||
{
|
||||
/**
|
||||
* Register a URL handler.
|
||||
*
|
||||
* @param protocol the protocol to register.
|
||||
* @param handlerClass a Class of type java.net.URLStreamHandler
|
||||
*/
|
||||
public static void attachHandler (String protocol, Class handlerClass)
|
||||
{
|
||||
if (!URLStreamHandler.class.isAssignableFrom(handlerClass)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Specified class is not a java.net.URLStreamHandler.");
|
||||
}
|
||||
|
||||
// set up the factory.
|
||||
if (_handlers == null) {
|
||||
_handlers = new HashMap();
|
||||
|
||||
// There are two ways to do this.
|
||||
|
||||
// Method 1, which is the only one that seems to work under
|
||||
// Java Web Start, is to register a factory. This can throw an
|
||||
// Error if another factory is already registered. We let that
|
||||
// error bubble on back.
|
||||
URL.setURLStreamHandlerFactory(new AttachableURLFactory());
|
||||
|
||||
// Method 2 seems like a better idea but doesn't work under
|
||||
// Java Web Start. We add on a property that registers this
|
||||
// very class as the handler for the resource property. It
|
||||
// would be instantiated with Class.forName().
|
||||
// (And I did check, it's not dasho that is preventing this
|
||||
// from working under JWS, it's something else.)
|
||||
/*
|
||||
// dug up from java.net.URL
|
||||
String HANDLER_PROP = "java.protocol.handler.pkgs";
|
||||
|
||||
String prop = System.getProperty(HANDLER_PROP, "");
|
||||
if (!"".equals(prop)) {
|
||||
prop += "|";
|
||||
}
|
||||
prop += "com.threerings";
|
||||
System.setProperty(HANDLER_PROP, prop);
|
||||
*/
|
||||
}
|
||||
|
||||
_handlers.put(protocol.toLowerCase(), handlerClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not let others instantiate us.
|
||||
*/
|
||||
private AttachableURLFactory ()
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface URLStreamHandlerFactory
|
||||
public URLStreamHandler createURLStreamHandler (String protocol)
|
||||
{
|
||||
Class handler = (Class) _handlers.get(protocol.toLowerCase());
|
||||
if (handler != null) {
|
||||
try {
|
||||
return (URLStreamHandler) handler.newInstance();
|
||||
} catch (Exception e) {
|
||||
Log.warning("Unable to instantiate URLStreamHandler" +
|
||||
" [protocol=" + protocol + ", cause=" + e + "].");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** A mapping of protocol name to handler classes. */
|
||||
protected static HashMap _handlers;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $Id: HttpPostUtil.java,v 1.2 2003/10/08 23:52:49 ray Exp $
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import com.samskivert.util.ServiceWaiter;
|
||||
|
||||
/**
|
||||
* Contains utility methods for doing a form post.
|
||||
*/
|
||||
public class HttpPostUtil
|
||||
{
|
||||
/**
|
||||
* Return the results of a form post. Note that the http request takes
|
||||
* place on another thread, but this thread blocks until the results
|
||||
* are returned or it times out.
|
||||
*
|
||||
* @param url from which to make the request.
|
||||
* @param submission the entire submission eg "foo=bar&baz=boo&futz=foo".
|
||||
* @param timeout time to wait for the response, in seconds, or -1
|
||||
* for forever.
|
||||
*/
|
||||
public static String httpPost (final URL url, final String submission,
|
||||
int timeout)
|
||||
throws IOException, ServiceWaiter.TimeoutException
|
||||
{
|
||||
final ServiceWaiter waiter = new ServiceWaiter(
|
||||
(timeout < 0) ? ServiceWaiter.NO_TIMEOUT : timeout);
|
||||
Thread tt = new Thread() {
|
||||
public void run () {
|
||||
try {
|
||||
HttpURLConnection conn =
|
||||
(HttpURLConnection) url.openConnection();
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
conn.setRequestProperty(
|
||||
"Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
DataOutputStream out = new DataOutputStream(
|
||||
conn.getOutputStream());
|
||||
out.writeBytes(submission);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream()));
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (String s; null != (s = reader.readLine()); ) {
|
||||
buf.append(s);
|
||||
}
|
||||
reader.close();
|
||||
|
||||
waiter.postSuccess(buf.toString()); // yay
|
||||
|
||||
} catch (IOException e) {
|
||||
waiter.postFailure(e); // boo
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tt.start();
|
||||
|
||||
if (waiter.waitForResponse()) {
|
||||
return (String) waiter.getArgument();
|
||||
|
||||
} else {
|
||||
throw (IOException) waiter.getArgument();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// $Id: MACUtil.java,v 1.12 2004/07/02 19:53:26 eric Exp $
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Attempts to find all the MAC addresses on the machine.
|
||||
* This is accomplished by calling external programs specific
|
||||
* to the platform we are on, and parsing the results.
|
||||
*/
|
||||
public class MACUtil
|
||||
{
|
||||
/**
|
||||
public static void main (String[] args)
|
||||
{
|
||||
String testOutput =
|
||||
"YOUR TEST STRING GOES HERE";
|
||||
|
||||
String[] macs = parseMACs(testOutput);
|
||||
for (int ii = 0; ii < macs.length; ii++) {
|
||||
System.err.println("mac: " + macs[ii]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get all the MAC addresses of the hardware we are running on that we
|
||||
* can find.
|
||||
*/
|
||||
public static String[] getMACAddresses ()
|
||||
{
|
||||
String [] cmds;
|
||||
if (RunAnywhere.isWindows()) {
|
||||
cmds = WINDOWS_CMDS;
|
||||
} else {
|
||||
cmds = UNIX_CMDS;
|
||||
}
|
||||
|
||||
return parseMACs(tryCommands(cmds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look through the text for all the MAC addresses we can find.
|
||||
*/
|
||||
protected static String[] parseMACs (String text)
|
||||
{
|
||||
Matcher m = MACRegex.matcher(text);
|
||||
|
||||
ArrayList list = new ArrayList();
|
||||
while (m.find()) {
|
||||
String mac = m.group(1).toUpperCase();
|
||||
mac = mac.replace(':', '-');
|
||||
|
||||
// "Didn't you get that memo?" Apparently some people are not
|
||||
// up on MAC addresses actually being unique, so we will
|
||||
// ignore those.
|
||||
//
|
||||
// 44-45-53-XX-XX-XX - PPP Adaptor
|
||||
// 00-53-45-XX-XX-XX - PPP Adaptor
|
||||
// 00-E0-06-09-55-66 - Some bogus run of ASUS motherboards
|
||||
// 00-04-4B-80-80-03 - Some nvidia built-in lan issues
|
||||
// 00-03-8A-XX-XX-XX - MiniWAN or AOL software
|
||||
// 02-03-8A-00-00-11 - Westell Dual (USB/Ethernet) modem
|
||||
// FF-FF-FF-FF-FF-FF - Tunnel adapter Teredo
|
||||
// 02-00-4C-4F-4F-50 - MSFT thinger, loopback of some sort
|
||||
if (mac.startsWith("44-45-53")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-53-45-00")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-E0-06-09-55-66")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-04-4B-80-80-03")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("00-03-8A")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("02-03-8A-00-00-11")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("FF-FF-FF-FF-FF-FF")) {
|
||||
continue;
|
||||
} else if (mac.startsWith("02-00-4C-4F-4F-50")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list.add(mac);
|
||||
}
|
||||
|
||||
return (String[])list.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a lists of commands and trys to run each one till one works.
|
||||
* The idea being to beable to 'gracefully' cope with not knowing
|
||||
* where a command is installed on different installations.
|
||||
*/
|
||||
protected static String tryCommands (String[] cmds)
|
||||
{
|
||||
if (cmds == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String output;
|
||||
|
||||
for (int ii = 0; ii < cmds.length; ii++) {
|
||||
output = runCommand(cmds[ii]);
|
||||
if (output != null) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the specificed command and return the output as a string.
|
||||
*/
|
||||
protected static String runCommand (String cmd)
|
||||
{
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec(cmd);
|
||||
BufferedReader cin = new BufferedReader(
|
||||
new InputStreamReader(p.getInputStream()));
|
||||
StringBuffer buffer= new StringBuffer();
|
||||
|
||||
String line = "";
|
||||
while (line != null)
|
||||
{
|
||||
buffer.append(line);
|
||||
line = cin.readLine();
|
||||
}
|
||||
cin.close();
|
||||
|
||||
return buffer.toString();
|
||||
} catch (IOException e) {
|
||||
// don't want to log anything for the client to know what we
|
||||
// are doing. This will almost always be thrown/caught when
|
||||
// the cmd isn't there.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Look for 2 hex values in a row followed by a ':' or '-' repeated 5
|
||||
times, followed by 2 hex values. */
|
||||
protected static Pattern MACRegex =
|
||||
Pattern.compile("((?:\\p{XDigit}{2}+[:\\-]){5}+\\p{XDigit}{2}+)",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// TODO maybe we should obfuscate these with rot13 or something so
|
||||
// that people can't run 'strings' on us and instantly see what we try
|
||||
protected static String[] WINDOWS_CMDS = {"ipconfig /all"};
|
||||
protected static String[] UNIX_CMDS = {"/sbin/ifconfig", "/etc/ifconfig"};
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// $Id: MailUtil.java,v 1.9 2004/01/15 21:41:00 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The mail util class encapsulates some utility functions related to
|
||||
* sending emails.
|
||||
*/
|
||||
public class MailUtil
|
||||
{
|
||||
/**
|
||||
* Checks the supplied address against a regular expression that (for
|
||||
* the most part) matches only valid email addresses. It's not
|
||||
* perfect, but the omissions and inclusions are so obscure that we
|
||||
* can't be bothered to worry about them.
|
||||
*/
|
||||
public static boolean isValidAddress (String address)
|
||||
{
|
||||
return _emailre.matcher(address).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail message using the machine's local mail
|
||||
* SMTP server which must be listening on port 25.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs delivering the
|
||||
* email. See {@link Transport#send} for exceptions that can be thrown
|
||||
* due to response from the SMTP server.
|
||||
*/
|
||||
public static void deliverMail (String recipient, String sender,
|
||||
String subject, String body)
|
||||
throws IOException
|
||||
{
|
||||
deliverMail(new String[] { recipient }, sender, subject, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail message using the machine's local mail
|
||||
* SMTP server which must be listening on port 25. If the
|
||||
* <code>mail.smtp.host</code> system property is set, that will be
|
||||
* used instead of localhost.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs delivering the
|
||||
* email. See {@link Transport#send} for exceptions that can be thrown
|
||||
* due to response from the SMTP server.
|
||||
*/
|
||||
public static void deliverMail (String[] recipients, String sender,
|
||||
String subject, String body)
|
||||
throws IOException
|
||||
{
|
||||
deliverMail(recipients, sender, subject, body, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers the supplied mail, with the specified additional headers.
|
||||
*/
|
||||
public static void deliverMail (String[] recipients, String sender,
|
||||
String subject, String body,
|
||||
String[] headers, String[] values)
|
||||
throws IOException
|
||||
{
|
||||
if (recipients == null || recipients.length < 1) {
|
||||
throw new IOException("Must specify one or more recipients.");
|
||||
}
|
||||
|
||||
try {
|
||||
Properties props = System.getProperties();
|
||||
if (props.getProperty("mail.smtp.host") == null) {
|
||||
props.put("mail.smtp.host", "localhost");
|
||||
}
|
||||
Session session = Session.getDefaultInstance(props, null);
|
||||
MimeMessage message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(sender));
|
||||
for (int ii = 0; ii < recipients.length; ii++) {
|
||||
message.addRecipient(Message.RecipientType.TO,
|
||||
new InternetAddress(recipients[ii]));
|
||||
}
|
||||
int hcount = (headers == null) ? 0 : headers.length;
|
||||
for (int ii = 0; ii < hcount; ii++) {
|
||||
message.addHeader(headers[ii], values[ii]);
|
||||
}
|
||||
message.setSubject(subject);
|
||||
message.setText(body);
|
||||
Transport.send(message);
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failure sending mail [from=" + sender +
|
||||
", to=" + StringUtil.toString(recipients) +
|
||||
", subject=" + subject + "]";
|
||||
IOException ioe = new IOException(errmsg);
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
/** Originally formulated by lambert@nas.nasa.gov. */
|
||||
protected static final String EMAIL_REGEX = "^([-A-Za-z0-9_.!%+]+@" +
|
||||
"[-a-zA-Z0-9]+(\\.[-a-zA-Z0-9]+)*\\.[-a-zA-Z0-9]+)$";
|
||||
|
||||
/** A compiled version of our email regular expression. */
|
||||
protected static Pattern _emailre;
|
||||
static {
|
||||
try {
|
||||
_emailre = Pattern.compile(EMAIL_REGEX);
|
||||
} catch (PatternSyntaxException pse) {
|
||||
Log.warning("Unable to initialize email regexp?!");
|
||||
Log.logStackTrace(pse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id: PathUtil.java,v 1.3 2001/08/13 23:24:24 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net;
|
||||
|
||||
/**
|
||||
* Path related utility functions.
|
||||
*/
|
||||
public class PathUtil
|
||||
{
|
||||
/**
|
||||
* Replaces the final component in the supplied path with the
|
||||
* specified new component. For example, if <code>/foo/bar/baz</code>
|
||||
* was provided as the source path, <code>baz</code> would be replaced
|
||||
* with the supplied new path component. If no slashes occur in the
|
||||
* path, the entire path will be replaced. Note that this function is
|
||||
* intended for use on URLs rather than filesystem paths and thus
|
||||
* always uses forward slash rather than the platform defined path
|
||||
* separator.
|
||||
*/
|
||||
public static String replaceFinalComponent (String source,
|
||||
String newComponent)
|
||||
{
|
||||
int sidx = source.lastIndexOf("/");
|
||||
if (sidx != -1) {
|
||||
return source.substring(0, sidx+1) + newComponent;
|
||||
} else {
|
||||
return newComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the supplied affix to the specified source path, ensuring
|
||||
* that exactly one path separator (<code>/</code> as we are dealing
|
||||
* with URLs here not platform specific file-system paths) is used
|
||||
* between the two. <em>Note:</em> this means that the affix will be
|
||||
* made into a relative path regardless of whether or not it starts
|
||||
* with a <code>/</code>.
|
||||
*/
|
||||
public static String appendPath (String source, String affix)
|
||||
{
|
||||
if (source.endsWith("/")) {
|
||||
if (affix.startsWith("/")) {
|
||||
return source + affix.substring(1);
|
||||
} else {
|
||||
return source + affix;
|
||||
}
|
||||
} else if (affix.startsWith("/")) {
|
||||
return source + affix;
|
||||
} else {
|
||||
return source + "/" + affix;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
//
|
||||
// $Id: CDDB.java,v 1.8 2001/08/12 01:34:31 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* The CDDB class provides access to the information provided by servers
|
||||
* compliant with the
|
||||
* <a href="http://www.freedb.org/software/old/CDDBPROTO">CDDB
|
||||
* protocol</a>.
|
||||
*/
|
||||
public class CDDB
|
||||
{
|
||||
/**
|
||||
* The port on which CDDB servers are generally run.
|
||||
*/
|
||||
public static final int STANDARD_PORT = 888;
|
||||
|
||||
/**
|
||||
* The client name reported to the CDDB server.
|
||||
*/
|
||||
public static final String CLIENT_NAME = "TSP/CDDB_client";
|
||||
|
||||
/**
|
||||
* The client version reported to the CDDB server.
|
||||
*/
|
||||
public static String CLIENT_VERSION; // assigned during static init
|
||||
|
||||
/**
|
||||
* This class encapsulates the information needed to look up a full
|
||||
* CDDB record for a particular disc. An array of them are returned in
|
||||
* response to a CDDB query.
|
||||
*/
|
||||
public static class Entry
|
||||
{
|
||||
/** The category to which this entry belongs. */
|
||||
public String category;
|
||||
|
||||
/** The unique identifier for this entry. */
|
||||
public String cdid;
|
||||
|
||||
/** The human readable title of this entry. */
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* Parses values for this entry from the supplied source
|
||||
* string. The source string should contain an entry description
|
||||
* as formatted by the CDDB server.
|
||||
*
|
||||
* @exception CDDBException Thrown if the entry is not properly
|
||||
* formatted.
|
||||
*/
|
||||
public void parse (String source)
|
||||
throws CDDBException
|
||||
{
|
||||
int sidx1 = source.indexOf(" ");
|
||||
int sidx2 = source.indexOf(" ", sidx1+1);
|
||||
|
||||
if (sidx1 == -1 || sidx2 == -1) {
|
||||
throw new CDDBException(499, "Malformed entry '" +
|
||||
source + "'");
|
||||
}
|
||||
|
||||
category = source.substring(0, sidx1);
|
||||
cdid = source.substring(sidx1+1, sidx2);
|
||||
title = source.substring(sidx2+1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this CDDB instance to the CDDB server running on the
|
||||
* supplied host using the standard port.
|
||||
*
|
||||
* <p> <b>Note well:</b> you must close a CDDB connection once you are
|
||||
* done with it, otherwise the socket connection will remain open and
|
||||
* pointlessly consume machine resources.
|
||||
*
|
||||
* @param hostname The host to which to connect.
|
||||
*
|
||||
* @exception IOException Thrown if a network error occurs attempting
|
||||
* to connect to the host.
|
||||
* @exception CDDBException Thrown if an error occurs after
|
||||
* identifying ourselves to the host.
|
||||
*
|
||||
* @return The message supplied with the succesful connection
|
||||
* response.
|
||||
*
|
||||
* @see #STANDARD_PORT
|
||||
* @see #close
|
||||
*/
|
||||
public String connect (String hostname)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
return connect(hostname, STANDARD_PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this CDDB instance to the CDDB server running on the
|
||||
* supplied host using the specified port.
|
||||
*
|
||||
* <p> <b>Note well:</b> you must close a CDDB connection once you are
|
||||
* done with it, otherwise the socket connection will remain open and
|
||||
* pointlessly consume machine resources.
|
||||
*
|
||||
* @param hostname The host to which to connect.
|
||||
* @param port The port number on which to connect to the host.
|
||||
*
|
||||
* @exception IOException Thrown if a network error occurs attempting
|
||||
* to connect to the host.
|
||||
* @exception CDDBException Thrown if an error occurs after
|
||||
* identifying ourselves to the host.
|
||||
*
|
||||
* @return The message supplied with the succesful connection
|
||||
* response.
|
||||
*
|
||||
* @see #close
|
||||
*/
|
||||
public String connect (String hostname, int port)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// obtain the necessary information we'll need to identify
|
||||
// ourselves to the CDDB server
|
||||
String localhost = InetAddress.getLocalHost().getHostName();
|
||||
String username = System.getProperty("user.name");
|
||||
if (username == null) {
|
||||
username = "anonymous";
|
||||
}
|
||||
|
||||
// establish our socket connection and IO streams
|
||||
InetAddress addr = InetAddress.getByName(hostname);
|
||||
_sock = new Socket(addr, port);
|
||||
_in = new BufferedReader(
|
||||
new InputStreamReader(_sock.getInputStream()));
|
||||
_out = new PrintStream(
|
||||
new BufferedOutputStream(_sock.getOutputStream()));
|
||||
|
||||
// first read (and discard) the banner string
|
||||
_in.readLine();
|
||||
|
||||
// send a hello request
|
||||
StringBuffer req = new StringBuffer("cddb hello ");
|
||||
req.append(username).append(" ");
|
||||
req.append(localhost).append(" ");
|
||||
req.append(CLIENT_NAME).append(" ");
|
||||
req.append(CLIENT_VERSION);
|
||||
|
||||
Response rsp = request(req.toString());
|
||||
|
||||
// confirm that the response was a successful one
|
||||
if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK &&
|
||||
rsp.code != 402 /* already shook hands */) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
return rsp.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the number of milliseconds that the client should wait
|
||||
* for a response from the server before aborting. This must be called
|
||||
* <em>after</em> a successful call to connect, otherwise the timeout
|
||||
* will not be set.
|
||||
*/
|
||||
public void setTimeout (int timeout)
|
||||
throws SocketException
|
||||
{
|
||||
if (_sock != null) {
|
||||
_sock.setSoTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this CDDB instance is connected to a CDDB server.
|
||||
*/
|
||||
public boolean connected ()
|
||||
{
|
||||
return _sock != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the CDDB server previously opened with a
|
||||
* call to <code>connect()</code>. If the connection was not
|
||||
* previously established, this member function does nothing.
|
||||
*
|
||||
* @see #connect
|
||||
*/
|
||||
public void close ()
|
||||
throws IOException
|
||||
{
|
||||
if (_sock != null) {
|
||||
_sock.close();
|
||||
|
||||
// clear out our references
|
||||
_sock = null;
|
||||
_in = null;
|
||||
_out = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a query to the CDDB server using the supplied CD identifying
|
||||
* information.
|
||||
*
|
||||
* @param discid The disc identifier (information on how to compute
|
||||
* the disc ID is available
|
||||
* <a href="http://www.freedb.org/sections.php?op=viewarticle&artid=6">
|
||||
* here</a>.
|
||||
* @param frameOffsets The frame offset of each track. The length of
|
||||
* this array is assumed to be the number of tracks on the CD and is
|
||||
* used in the query.
|
||||
* @param length The length (in seconds) of the CD.
|
||||
*
|
||||
* @return If no entry matches the query, null is returned. Otherwise
|
||||
* one or more entries is returned that matched the query parameters.
|
||||
*/
|
||||
public Entry[] query (String discid, int[] frameOffsets, int length)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// sanity check
|
||||
if (_sock == null) {
|
||||
throw new CDDBException(500, "Not connected");
|
||||
}
|
||||
|
||||
// construct the query parameter
|
||||
StringBuffer req = new StringBuffer("cddb query ");
|
||||
req.append(discid).append(" ");
|
||||
req.append(frameOffsets.length).append(" ");
|
||||
for (int i = 0; i < frameOffsets.length; i++) {
|
||||
req.append(frameOffsets[i]).append(" ");
|
||||
}
|
||||
req.append(length);
|
||||
|
||||
// make the request
|
||||
Response rsp = request(req.toString());
|
||||
Entry[] entries = null;
|
||||
|
||||
// if this is an exact match, parse the entry and return it
|
||||
if (rsp.code == 200 /* exact match */) {
|
||||
entries = new Entry[1];
|
||||
entries[0] = new Entry();
|
||||
entries[0].parse(rsp.message);
|
||||
|
||||
} else if (rsp.code == 211 /* inexact matches */) {
|
||||
// read the matches from the server
|
||||
ArrayList list = new ArrayList();
|
||||
String input = _in.readLine();
|
||||
System.out.println("...: " + input);
|
||||
while (!input.equals(CDDBProtocol.TERMINATOR)) {
|
||||
Entry e = new Entry();
|
||||
e.parse(input);
|
||||
list.add(e);
|
||||
input = _in.readLine();
|
||||
System.out.println("...: " + input);
|
||||
}
|
||||
entries = new Entry[list.size()];
|
||||
list.toArray(entries);
|
||||
|
||||
} else if (CDDBProtocol.codeFamily(rsp.code) != CDDBProtocol.OK) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* A detail object contains all of the detailed information about a
|
||||
* particular CD as retrieved from the CDDB server.
|
||||
*/
|
||||
public static class Detail
|
||||
{
|
||||
/** The unique identifier for this CD. */
|
||||
public String discid;
|
||||
|
||||
/** The category to which this CD belongs. */
|
||||
public String category;
|
||||
|
||||
/** The title of this CD. */
|
||||
public String title;
|
||||
|
||||
/** The track names. */
|
||||
public String[] trackNames;
|
||||
|
||||
/** The extended data for the CD. */
|
||||
public String extendedData;
|
||||
|
||||
/** The extended data for each track. */
|
||||
public String[] extendedTrackData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the detail information for a particular disc in a
|
||||
* particular category from the CDDB server.
|
||||
*
|
||||
* @return A detail instance filled with the information for the
|
||||
* requested CD.
|
||||
*/
|
||||
public Detail read (String category, String discid)
|
||||
throws IOException, CDDBException
|
||||
{
|
||||
// sanity check
|
||||
if (_sock == null) {
|
||||
throw new CDDBException(500, "Not connected");
|
||||
}
|
||||
|
||||
// construct the query
|
||||
StringBuffer req = new StringBuffer("cddb read ");
|
||||
req.append(category).append(" ");
|
||||
req.append(discid);
|
||||
|
||||
// make the request
|
||||
Response rsp = request(req.toString());
|
||||
// anything other than an OK response earns an exception
|
||||
if (rsp.code != 210 /* OK, entry follows */) {
|
||||
throw new CDDBException(rsp.code, rsp.message);
|
||||
}
|
||||
|
||||
Detail detail = new Detail();
|
||||
|
||||
// parse the category and discid from the response string
|
||||
int sidx = rsp.message.indexOf(" ");
|
||||
if (sidx == -1) {
|
||||
throw new CDDBException(500, "Malformed read response: " +
|
||||
rsp.message);
|
||||
}
|
||||
detail.category = rsp.message.substring(0, sidx);
|
||||
detail.discid = rsp.message.substring(sidx+1);
|
||||
|
||||
ArrayList tnames = new ArrayList();
|
||||
ArrayList texts = new ArrayList();
|
||||
|
||||
// now parse the contents
|
||||
String input = _in.readLine();
|
||||
for (int lno = 0; !input.equals(CDDBProtocol.TERMINATOR); lno++) {
|
||||
|
||||
if (input.startsWith("#")) {
|
||||
// skip comments
|
||||
|
||||
} else if (input.startsWith("DTITLE")) {
|
||||
if (detail.title == null) {
|
||||
detail.title = contents(input, lno);
|
||||
} else {
|
||||
detail.title += contents(input, lno);
|
||||
}
|
||||
|
||||
} else if (input.startsWith("EXTD")) {
|
||||
if (detail.extendedData == null) {
|
||||
detail.extendedData = contents(input, lno);
|
||||
} else {
|
||||
detail.extendedData += contents(input, lno);
|
||||
}
|
||||
|
||||
} else if (input.startsWith("TTITLE")) {
|
||||
append(tnames, index(input, "TTITLE", lno),
|
||||
contents(input, lno));
|
||||
|
||||
} else if (input.startsWith("EXTT")) {
|
||||
append(texts, index(input, "EXTT", lno),
|
||||
contents(input, lno));
|
||||
}
|
||||
|
||||
// read in the next line of input
|
||||
input = _in.readLine();
|
||||
}
|
||||
|
||||
// convert the lists into arrays
|
||||
detail.trackNames = new String[tnames.size()];
|
||||
tnames.toArray(detail.trackNames);
|
||||
detail.extendedTrackData = new String[texts.size()];
|
||||
texts.toArray(detail.extendedTrackData);
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the track index of the supplied line (the number
|
||||
* immediately following the supplied prefix and preceding the equals
|
||||
* sign) or throws a CDDBException if the line contains no equals sign
|
||||
* or track index.
|
||||
*/
|
||||
protected final int index (String source, String prefix, int lineno)
|
||||
throws CDDBException
|
||||
{
|
||||
int eidx = source.indexOf("=", prefix.length());
|
||||
if (eidx == -1) {
|
||||
throw new CDDBException(500, "Malformed line '" + source +
|
||||
"' number " + lineno);
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(source.substring(prefix.length(), eidx));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new CDDBException(500, "Malformed line '" + source +
|
||||
"' number " + lineno);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the contents of the supplied line (everything after the
|
||||
* equals sign) or throws a CDDBException if the line contains no
|
||||
* equals sign.
|
||||
*/
|
||||
protected final String contents (String source, int lineno)
|
||||
throws CDDBException
|
||||
{
|
||||
int eidx = source.indexOf("=");
|
||||
if (eidx == -1) {
|
||||
throw new CDDBException(500, "Malformed line '" + source +
|
||||
"' number " + lineno);
|
||||
}
|
||||
return source.substring(eidx+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the supplied string to the contents of the list at the
|
||||
* supplied index. If the list has no contents at the supplied
|
||||
* index, the supplied value becomes the contents at that index.
|
||||
*/
|
||||
protected final void append (ArrayList list, int index, String value)
|
||||
{
|
||||
// expand the list as necessary
|
||||
while (list.size() <= index) {
|
||||
list.add("");
|
||||
}
|
||||
list.set(index, list.get(index) + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class to encapsulate the response from the CDDB server.
|
||||
*/
|
||||
protected class Response
|
||||
{
|
||||
public int code;
|
||||
public String message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a request to the CDDB server and parses the response.
|
||||
*/
|
||||
protected Response request (String req)
|
||||
throws IOException
|
||||
{
|
||||
System.err.println("REQ:" + req);
|
||||
|
||||
// send the request to the server
|
||||
_out.println(req);
|
||||
_out.flush();
|
||||
|
||||
// now read the response
|
||||
String rspstr = _in.readLine();
|
||||
// if they closed the connection on us, we should deal
|
||||
if (rspstr == null) {
|
||||
throw new EOFException();
|
||||
}
|
||||
System.err.println("RSP:" + rspstr);
|
||||
|
||||
// parse the response
|
||||
Response rsp = new Response();
|
||||
String codestr = rspstr;
|
||||
|
||||
// assume the response is just a code unless we see a space
|
||||
int sidx = rspstr.indexOf(" ");
|
||||
if (sidx != -1) {
|
||||
codestr = rspstr.substring(0, sidx);
|
||||
rsp.message = rspstr.substring(sidx+1);
|
||||
}
|
||||
|
||||
try {
|
||||
rsp.code = Integer.parseInt(codestr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
rsp.code = 599;
|
||||
rsp.message = "Unparseable response";
|
||||
}
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The client version number is extracted from the version control
|
||||
* revision of this file from a string that is managed by the version
|
||||
* control system.
|
||||
*/
|
||||
static
|
||||
{
|
||||
StringTokenizer tok = new StringTokenizer("$Revision: 1.8 $");
|
||||
tok.nextToken();
|
||||
CLIENT_VERSION = tok.nextToken();
|
||||
}
|
||||
|
||||
protected Socket _sock;
|
||||
protected BufferedReader _in;
|
||||
protected PrintStream _out;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// $Id: CDDBException.java,v 1.2 2001/08/11 22:43:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* This exception class encapsulates errors that may occur while
|
||||
* communicating to a CDDB server. It is not used to communicate IO errors
|
||||
* (an IOException is used for that), but it is used to communicate
|
||||
* failures communicated within the scope of the CDDB protocol.
|
||||
*
|
||||
* @see CDDB
|
||||
*/
|
||||
public class CDDBException extends Exception
|
||||
{
|
||||
public CDDBException (int code, String message)
|
||||
{
|
||||
super(message);
|
||||
_code = code;
|
||||
}
|
||||
|
||||
public int getCode ()
|
||||
{
|
||||
return _code;
|
||||
}
|
||||
|
||||
protected int _code;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id: CDDBProtocol.java,v 1.2 2001/08/11 22:43:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* The CDDB protocol class provides constants related to the CDDB protocol
|
||||
* as well as utility routines useful in dealing with the protocol.
|
||||
*/
|
||||
public class CDDBProtocol
|
||||
{
|
||||
/**
|
||||
* The terminating marker which occurs alone on it's own line to
|
||||
* indicate the end of an extended command.
|
||||
*/
|
||||
public static final String TERMINATOR = ".";
|
||||
|
||||
/* Class constant offsets */
|
||||
public static final int INFORMATIONAL = 100;
|
||||
public static final int OK = 200;
|
||||
public static final int OK_WITH_CONTINUATION = 300;
|
||||
public static final int UNABLE_TO_PERFORM = 400;
|
||||
public static final int SERVER_ERROR = 500;
|
||||
|
||||
/**
|
||||
* @return The family to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeFamily (int code)
|
||||
{
|
||||
return (code / 100) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The genus to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeGenus (int code)
|
||||
{
|
||||
return ((code % 100) / 10) * 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The species to which the supplied specific response code
|
||||
* belongs.
|
||||
*/
|
||||
public static int codeSpecies (int code)
|
||||
{
|
||||
return code % 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// $Id: CDDBTest.java,v 1.3 2001/08/11 22:43:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.net.cddb;
|
||||
|
||||
/**
|
||||
* Tests the CDDB code by connecting to a CDDB server and making some
|
||||
* requests.
|
||||
*/
|
||||
public class CDDBTest
|
||||
{
|
||||
public static void test (String hostname, String cdid)
|
||||
throws Exception
|
||||
{
|
||||
CDDB cddb = new CDDB();
|
||||
|
||||
try {
|
||||
String rsp = cddb.connect(hostname);
|
||||
|
||||
// set the timeout to 30 seconds
|
||||
cddb.setTimeout(30*1000);
|
||||
|
||||
// try a test query
|
||||
int[] offsets = { 150, 18130, 48615 };
|
||||
int length = 893;
|
||||
CDDB.Entry[] entries = cddb.query(cdid, offsets, length);
|
||||
|
||||
if (entries == null || entries.length == 0) {
|
||||
System.out.println("No match for " + cdid + ".");
|
||||
|
||||
} else {
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
System.out.println("Match " + entries[i].category + "/" +
|
||||
entries[i].cdid + "/" +
|
||||
entries[i].title);
|
||||
}
|
||||
|
||||
CDDB.Detail detail = cddb.read(entries[0].category,
|
||||
entries[0].cdid);
|
||||
System.out.println("Title: " + detail.title);
|
||||
for (int i = 0; i < detail.trackNames.length; i++) {
|
||||
System.out.println(pad(i) + ": " + detail.trackNames[i]);
|
||||
}
|
||||
System.out.println("Extended data: " + detail.extendedData);
|
||||
for (int i = 0; i < detail.extendedTrackData.length; i++) {
|
||||
System.out.println(pad(i) + ": " +
|
||||
detail.extendedTrackData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
cddb.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected static String pad (int value)
|
||||
{
|
||||
return ((value > 9) ? "" : " ") + value;
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
System.err.println("Usage: CDDBTest cddbhost");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
try {
|
||||
test(args[0], "1b037b03");
|
||||
|
||||
} catch (CDDBException ce) {
|
||||
System.err.println("Protocol exception: " + ce.getCode() +
|
||||
": " + ce.getMessage());
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Routines for accessing a CDDB server.
|
||||
|
||||
<p> The {@link com.samskivert.net.cddb.CDDB} class provides the
|
||||
primary interface to the CDDB services. This service does not
|
||||
provide a means to obtain the CD identification information that
|
||||
you'll need to perform a CDDB lookup. In the application that
|
||||
motivated the creation of these services, a <code>cdparanoia</code>
|
||||
process was spawned to read the disc info. Other mechanisms surely
|
||||
exist.
|
||||
|
||||
<p> Use of the CDDB class is fairly straightforward:
|
||||
|
||||
<pre>
|
||||
CDDB cddb = new CDDB();
|
||||
cddb.connect("www.freedb.org");
|
||||
CDDB.Entry[] entries = cddb.query(discid, frameOffsets, length);
|
||||
for (int i = 0; i < entries.length; i++) {
|
||||
CDDB.Detail detail = cddb.read(entries[i].category,
|
||||
entries[i].discid);
|
||||
// do your thang now...
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p> These services presently do not provide a mechanism for submitting
|
||||
a CDDB entry (which involves sending an email). It surely will in
|
||||
the future.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides network related services and patterns.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: overview.html,v 1.1 2001/08/12 01:45:46 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
The samskivert library (SL) aims to provide useful reusable Java
|
||||
routines that do things for which I've been unable to find useful
|
||||
reusable implementations on the net.
|
||||
|
||||
<p> Given the emphasis on reusability, SL attempts to closely adhere
|
||||
to the following principles:
|
||||
|
||||
<ul>
|
||||
<li><p> Each individual module should depend as little as possible
|
||||
on other SL modules. Obvious exceptions include modules that are
|
||||
a logical extension of other modules and modules that clearly
|
||||
require a service that is implemented by another SL module and
|
||||
would have to implement that service themselves in the absence
|
||||
of dependence on the other module.
|
||||
|
||||
<li><p> Modules should be both simple to use and as general purpose
|
||||
as possible. To meet these two competing requirements, a balance
|
||||
must be struck at that sweet spot where reusability is
|
||||
maximized.
|
||||
|
||||
<li><p> Code included in SL will freely depend on JDK packages
|
||||
available in the Java 2 platform and beyond. SL is initially a
|
||||
repository of software useful for server-side or stand alone
|
||||
applications and therefore need not make compromises to function
|
||||
in the jungle of JVMs in commonly available web browsers.
|
||||
|
||||
<li><p> We are not here to reinvent the wheel, nor to provide a
|
||||
uniform interface to every software service under the sun. If
|
||||
something is available in a freely redistributable and reusable
|
||||
form from someone else, it won't be found in SL. If SL depends
|
||||
on such software from another source, it will provide clear
|
||||
documentation on how to get that software and make use of it
|
||||
within the scope of SL's particular needs. Again a balance of
|
||||
reusability will be struck here and software that is
|
||||
sufficiently difficult to make usable in an arbitrary
|
||||
environment will not be used by SL and may be "reinvented".
|
||||
</ul>
|
||||
|
||||
<p> I and others who have contributed to the library hope that you
|
||||
find it useful, easy to use and time saving. Comments and
|
||||
contributions are always welcome.
|
||||
|
||||
<p> -- <a href="mailto:mdb@samskivert.com">mdb@samskivert.com</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:30 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Nothing lives here except the {@link com.samskivert.util.Log} wrapper
|
||||
that is used by the samskivert services to generate log messages.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001-2004 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* An http error exception is thrown by servlet services when they wish to fail
|
||||
* the request in some particular way (ie. {@link
|
||||
* HttpServletResponse#SC_FORBIDDEN}, etc.). It is expected that error handling
|
||||
* can be implemented in a single place such that servlets can simply allow
|
||||
* this exception to propagate up to the proper handler which will then issue
|
||||
* the appropriate failure header.
|
||||
*/
|
||||
public class HttpErrorException extends Exception
|
||||
{
|
||||
public HttpErrorException (int errorCode)
|
||||
{
|
||||
super(String.valueOf(errorCode));
|
||||
_errorCode = errorCode;
|
||||
}
|
||||
|
||||
public HttpErrorException (int errorCode, String errorMessage)
|
||||
{
|
||||
super(errorMessage);
|
||||
_errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP error code supplied with this exception.
|
||||
*/
|
||||
public int getErrorCode ()
|
||||
{
|
||||
return _errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual error message supplied with this exception or null
|
||||
* if only an error code was supplied.
|
||||
*/
|
||||
public String getErrorMessage ()
|
||||
{
|
||||
String msg = getMessage();
|
||||
return String.valueOf(_errorCode).equals(msg) ? null : msg;
|
||||
}
|
||||
|
||||
protected int _errorCode;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id: IndiscriminateSiteIdentifier.java,v 1.3 2003/11/13 00:53:00 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Used by default and for systems that have no need to discriminate
|
||||
* between different sites in their web applications.
|
||||
*/
|
||||
public class IndiscriminateSiteIdentifier implements SiteIdentifier
|
||||
{
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_ID} regardless of the
|
||||
* information in the request.
|
||||
*/
|
||||
public int identifySite (HttpServletRequest req)
|
||||
{
|
||||
return DEFAULT_SITE_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_STRING} regardless of the value
|
||||
* of the supplied identifer.
|
||||
*/
|
||||
public String getSiteString (int siteId)
|
||||
{
|
||||
return DEFAULT_SITE_STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns {@link #DEFAULT_SITE_ID} regardless of the value of
|
||||
* the supplied string.
|
||||
*/
|
||||
public int getSiteId (String siteString)
|
||||
{
|
||||
return DEFAULT_SITE_ID;
|
||||
}
|
||||
|
||||
// documented inherited from interface
|
||||
public Iterator enumerateSites ()
|
||||
{
|
||||
return _sites.iterator();
|
||||
}
|
||||
|
||||
protected static ArrayList _sites = new ArrayList();
|
||||
static {
|
||||
_sites.add(new Site(DEFAULT_SITE_ID, DEFAULT_SITE_STRING));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
//
|
||||
// $Id: JDBCTableSiteIdentifier.java,v 1.12 2004/02/25 13:17:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.SimpleRepository;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Accomplishes the process of site identification based on a mapping from
|
||||
* domains (e.g. samskivert.com) to site identifiers that is maintained in
|
||||
* a database table, accessible via JDBC (hence the name).
|
||||
*
|
||||
* <p> There are two tables, one that maps domains to site identifiers and
|
||||
* another that maps site identifiers to site strings. These are both
|
||||
* loaded at construct time and refreshed periodically in the course of
|
||||
* normal operation.
|
||||
*
|
||||
* <p> Note that any of the calls to identify, lookup or enumerate site
|
||||
* information can result in the sites table being refreshed from the
|
||||
* database which will take relatively much longer than the simple
|
||||
* hashtable lookup that the operations normally require. However, this
|
||||
* happens only once every 15 minutes and the circumstances in which the
|
||||
* site identifier are normally used can generally accomodate the extra
|
||||
* 100 milliseconds or so that it is likely to take to reload the (tiny)
|
||||
* sites and domains tables from the database.
|
||||
*/
|
||||
public class JDBCTableSiteIdentifier implements SiteIdentifier
|
||||
{
|
||||
/** The database identifier used to obtain a connection from our
|
||||
* connection provider. The value is <code>sitedb</code> which you'll
|
||||
* probably need to know to provide the proper configuration to your
|
||||
* connection provider. */
|
||||
public static final String SITE_IDENTIFIER_IDENT = "sitedb";
|
||||
|
||||
/**
|
||||
* Constructs a JDBC table site identifier with the supplied
|
||||
* connection provider from which to obtain its database connection.
|
||||
*
|
||||
* @see #SITE_IDENTIFIER_IDENT
|
||||
*/
|
||||
public JDBCTableSiteIdentifier (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
_repo = new SiteIdentifierRepository(conprov);
|
||||
// load up our site data
|
||||
_repo.refreshSiteData();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int identifySite (HttpServletRequest req)
|
||||
{
|
||||
checkReloadSites();
|
||||
String serverName = req.getServerName();
|
||||
|
||||
// scan for the mapping that matches the specified domain
|
||||
int msize = _mappings.size();
|
||||
for (int i = 0; i < msize; i++) {
|
||||
SiteMapping mapping = (SiteMapping)_mappings.get(i);
|
||||
if (serverName.endsWith(mapping.domain)) {
|
||||
return mapping.siteId;
|
||||
}
|
||||
}
|
||||
|
||||
// if we matched nothing, return the default id
|
||||
return DEFAULT_SITE_ID;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public String getSiteString (int siteId)
|
||||
{
|
||||
checkReloadSites();
|
||||
Site site = (Site)_sitesById.get(siteId);
|
||||
return (site == null) ? DEFAULT_SITE_STRING : site.siteString;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getSiteId (String siteString)
|
||||
{
|
||||
checkReloadSites();
|
||||
Site site = (Site)_sitesByString.get(siteString);
|
||||
return (site == null) ? DEFAULT_SITE_ID : site.siteId;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Iterator enumerateSites ()
|
||||
{
|
||||
checkReloadSites();
|
||||
return _sitesById.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new site into the site table and into this mapping.
|
||||
*/
|
||||
public Site insertNewSite (String siteString)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (_sitesByString.containsKey(siteString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// add it to the db
|
||||
Site site = new Site();
|
||||
site.siteString = siteString;
|
||||
_repo.insertNewSite(site);
|
||||
|
||||
// add it to our two mapping tables,
|
||||
// avoiding causing enumerateSites() to choke
|
||||
HashMap newStrings = (HashMap) _sitesByString.clone();
|
||||
HashIntMap newIds = (HashIntMap) _sitesById.clone();
|
||||
newIds.put(site.siteId, site);
|
||||
newStrings.put(site.siteString, site);
|
||||
_sitesByString = newStrings;
|
||||
_sitesById = newIds;
|
||||
|
||||
return site;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if we should reload our sites information from the
|
||||
* sites table.
|
||||
*/
|
||||
protected void checkReloadSites ()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
boolean reload = false;
|
||||
synchronized (this) {
|
||||
if (reload = (now - _lastReload > RELOAD_INTERVAL)) {
|
||||
_lastReload = now;
|
||||
}
|
||||
}
|
||||
if (reload) {
|
||||
try {
|
||||
_repo.refreshSiteData();
|
||||
} catch (PersistenceException pe) {
|
||||
Log.warning("Error refreshing site data.");
|
||||
Log.logStackTrace(pe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to load information from the site database.
|
||||
*/
|
||||
protected class SiteIdentifierRepository extends SimpleRepository
|
||||
implements SimpleRepository.Operation
|
||||
{
|
||||
public SiteIdentifierRepository (ConnectionProvider conprov)
|
||||
{
|
||||
super(conprov, SITE_IDENTIFIER_IDENT);
|
||||
}
|
||||
|
||||
public void refreshSiteData ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// we are the operation!
|
||||
execute(this);
|
||||
}
|
||||
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = conn.createStatement();
|
||||
try {
|
||||
// first load up the list of sites
|
||||
String query = "select siteId, siteString from sites";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
HashIntMap sites = new HashIntMap();
|
||||
HashMap strings = new HashMap();
|
||||
while (rs.next()) {
|
||||
Site site = new Site(rs.getInt(1), rs.getString(2));
|
||||
sites.put(site.siteId, site);
|
||||
strings.put(site.siteString, site);
|
||||
}
|
||||
_sitesById = sites;
|
||||
_sitesByString = strings;
|
||||
|
||||
// now load up the domain mappings
|
||||
query = "select domain, siteId from domains";
|
||||
rs = stmt.executeQuery(query);
|
||||
ArrayList mappings = new ArrayList();
|
||||
while (rs.next()) {
|
||||
mappings.add(new SiteMapping(rs.getInt(2),
|
||||
rs.getString(1)));
|
||||
}
|
||||
|
||||
// sort the mappings in order of specificity
|
||||
Collections.sort(mappings);
|
||||
_mappings = mappings;
|
||||
// Log.info("Loaded site mappings " +
|
||||
// StringUtil.toString(_mappings) + ".");
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new site to the database.
|
||||
*/
|
||||
public void insertNewSite (final Site site)
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
PreparedStatement stmt = null;
|
||||
try {
|
||||
stmt = conn.prepareStatement(
|
||||
"insert into sites (siteString) VALUES (?)");
|
||||
stmt.setString(1, site.siteString);
|
||||
if (1 != stmt.executeUpdate()) {
|
||||
throw new PersistenceException(
|
||||
"Not inserted " + site);
|
||||
}
|
||||
site.siteId = liaison.lastInsertedId(conn);
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to track domain to site identifier mappings.
|
||||
*/
|
||||
protected static class SiteMapping implements Comparable
|
||||
{
|
||||
/** The domain to match. */
|
||||
public String domain;
|
||||
|
||||
/** The site identifier for the associated domain. */
|
||||
public int siteId;
|
||||
|
||||
public SiteMapping (int siteId, String domain)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
this.domain = domain;
|
||||
byte[] bytes = domain.getBytes();
|
||||
ArrayUtil.reverse(bytes);
|
||||
_rdomain = new String(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Site mappings sort from most specific (www.yahoo.com) to least
|
||||
* specific (yahoo.com).
|
||||
*/
|
||||
public int compareTo (Object other)
|
||||
{
|
||||
if (other instanceof SiteMapping) {
|
||||
SiteMapping orec = (SiteMapping)other;
|
||||
return orec._rdomain.compareTo(_rdomain);
|
||||
} else {
|
||||
// no comparablo
|
||||
return getClass().getName().compareTo(
|
||||
other.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a string representation of this site mapping. */
|
||||
public String toString ()
|
||||
{
|
||||
return "[" + domain + " => " + siteId + "]";
|
||||
}
|
||||
|
||||
protected String _rdomain;
|
||||
}
|
||||
|
||||
/** The repository through which we load up site identifier
|
||||
* information. */
|
||||
protected SiteIdentifierRepository _repo;
|
||||
|
||||
/** The list of domain to site identifier mappings ordered from most
|
||||
* specific domain to least specific. */
|
||||
protected volatile ArrayList _mappings = new ArrayList();
|
||||
|
||||
/** The mapping from integer site identifiers to string site
|
||||
* identifiers. */
|
||||
protected volatile HashIntMap _sitesById = new HashIntMap();
|
||||
|
||||
/** The mapping from string site identifiers to integer site
|
||||
* identifiers. */
|
||||
protected volatile HashMap _sitesByString = new HashMap();
|
||||
|
||||
/** Used to periodically reload our site data. */
|
||||
protected long _lastReload;
|
||||
|
||||
/** Reload our site data every 15 minutes. */
|
||||
protected static final long RELOAD_INTERVAL = 15 * 60 * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
//
|
||||
// $Id: MessageManager.java,v 1.11 2004/05/11 03:14:03 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.text.MessageUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The message manager handles the translation messages for a web
|
||||
* application. The webapp should construct the message manager with the
|
||||
* name of its message properties file and it can then make use of the
|
||||
* message manager to generate locale specific messages for a request.
|
||||
*/
|
||||
public class MessageManager
|
||||
{
|
||||
/**
|
||||
* Constructs a message manager with the specified bundle path. The
|
||||
* message manager will be instantiating a <code>ResourceBundle</code>
|
||||
* with the supplied path, so it should conform to the naming
|
||||
* conventions defined by that class.
|
||||
*
|
||||
* @see java.util.ResourceBundle
|
||||
*/
|
||||
public MessageManager (String bundlePath, Locale deflocale)
|
||||
{
|
||||
// keep these for later
|
||||
_bundlePath = bundlePath;
|
||||
_deflocale = deflocale;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the message manager is to be used in a multi-site environment,
|
||||
* it can be configured to load site-specific message resources in
|
||||
* addition to the default application message resources. It must be
|
||||
* configured with the facilities to load site-specific resources by a
|
||||
* call to this function.
|
||||
*
|
||||
* @param siteBundlePath the path to the site-specific message
|
||||
* resources.
|
||||
* @param siteLoader a site-specific resource loader, properly
|
||||
* configured with a site identifier.
|
||||
* @param siteIdent a site identifier that can be used to identify the
|
||||
* site via which an http request was made.
|
||||
*/
|
||||
public void activateSiteSpecificMessages (String siteBundlePath,
|
||||
SiteResourceLoader siteLoader,
|
||||
SiteIdentifier siteIdent)
|
||||
{
|
||||
_siteBundlePath = siteBundlePath;
|
||||
_siteLoader = siteLoader;
|
||||
_siteIdent = siteIdent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the specifed path exists in the resource bundle.
|
||||
*/
|
||||
public boolean exists (HttpServletRequest req, String path)
|
||||
{
|
||||
return (getMessage(req, path, false) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle
|
||||
* most appropriate for the locales described as preferred by the
|
||||
* request. Always reports missing paths.
|
||||
*/
|
||||
public String getMessage (HttpServletRequest req, String path)
|
||||
{
|
||||
return getMessage(req, path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle
|
||||
* most appropriate for the locales described as preferred by the
|
||||
* request, then substitutes the supplied arguments into that message
|
||||
* using a <code>MessageFormat</code> object.
|
||||
*
|
||||
* @see java.text.MessageFormat
|
||||
*/
|
||||
public String getMessage (HttpServletRequest req, String path,
|
||||
Object[] args)
|
||||
{
|
||||
String msg = getMessage(req, path, true);
|
||||
// we may cache message formatters later, but for now just use the
|
||||
// static convenience function
|
||||
return MessageFormat.format(MessageUtil.escape(msg), args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the message with the specified path in the resource bundle
|
||||
* most appropriate for the locales described as preferred by the
|
||||
* request. If requested it will log a missing path and return the
|
||||
* path as the translation (which should make it obvious in the
|
||||
* servlet that the translation is missing) otherwise it returns null.
|
||||
*/
|
||||
protected String getMessage (HttpServletRequest req, String path,
|
||||
boolean reportMissing)
|
||||
{
|
||||
if (path == null) {
|
||||
return "[null message key]";
|
||||
}
|
||||
|
||||
// if the key is tainted, just strip the taint character
|
||||
if (path.startsWith(MessageUtil.TAINT_CHAR)) {
|
||||
return path.substring(1);
|
||||
}
|
||||
|
||||
// attempt to determine whether or not this is a compound key
|
||||
int tidx = path.indexOf("|");
|
||||
if (tidx != -1) {
|
||||
String key = path.substring(0, tidx);
|
||||
String argstr = path.substring(tidx+1);
|
||||
String[] args = StringUtil.split(argstr, "|");
|
||||
// unescape and translate the arguments
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
// if the argument is tainted, do no further translation
|
||||
// (it might contain |s or other fun stuff)
|
||||
if (args[i].startsWith(MessageUtil.TAINT_CHAR)) {
|
||||
args[i] = MessageUtil.unescape(args[i].substring(1));
|
||||
} else {
|
||||
args[i] = getMessage(req, MessageUtil.unescape(args[i]));
|
||||
}
|
||||
}
|
||||
return getMessage(req, key, args);
|
||||
}
|
||||
|
||||
// load up the matching resource bundles (the array will contain
|
||||
// the site-specific resources first and the application resources
|
||||
// second); use the locale preferred by the client if possible
|
||||
ResourceBundle[] bundles = resolveBundles(req);
|
||||
if (bundles != null) {
|
||||
int blength = bundles.length;
|
||||
for (int i = 0; i < blength; i++) {
|
||||
try {
|
||||
if (bundles[i] != null) {
|
||||
return bundles[i].getString(path);
|
||||
}
|
||||
} catch (MissingResourceException mre) {
|
||||
// no complaints, just try the bundle in the enclosing
|
||||
// scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reportMissing) {
|
||||
// if there's no translation for this path, complain about it
|
||||
Log.warning("Missing translation message [path=" + path +
|
||||
", url=" + getURL(req) + "].");
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest matching resource bundle for the locales
|
||||
* specified as preferred by the client in the supplied http request.
|
||||
*/
|
||||
protected ResourceBundle[] resolveBundles (HttpServletRequest req)
|
||||
{
|
||||
// first look to see if we've cached the bundles for this request
|
||||
// in the request object
|
||||
ResourceBundle[] bundles = null;
|
||||
if (req != null) {
|
||||
bundles = (ResourceBundle[])req.getAttribute(BUNDLE_CACHE_NAME);
|
||||
}
|
||||
if (bundles != null) {
|
||||
return bundles;
|
||||
}
|
||||
|
||||
// grab our site-specific class loader if we have one
|
||||
ClassLoader siteLoader = null;
|
||||
if (_siteLoader != null && _siteIdent != null) {
|
||||
int siteId = _siteIdent.identifySite(req);
|
||||
try {
|
||||
siteLoader = _siteLoader.getSiteClassLoader(siteId);
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Unable to fetch site-specific classloader " +
|
||||
"[siteId=" + siteId + ", error=" + ioe + "].");
|
||||
}
|
||||
}
|
||||
|
||||
// try looking up the appropriate bundles
|
||||
bundles = new ResourceBundle[2];
|
||||
|
||||
// first from the site-specific classloader
|
||||
if (siteLoader != null) {
|
||||
bundles[0] = resolveBundle(req, _siteBundlePath, siteLoader);
|
||||
}
|
||||
|
||||
// then from the default classloader
|
||||
bundles[1] = resolveBundle(req, _bundlePath,
|
||||
getClass().getClassLoader());
|
||||
|
||||
// if we found either or both bundles, cache 'em
|
||||
if (bundles[0] != null || bundles[1] != null && req != null) {
|
||||
req.setAttribute(BUNDLE_CACHE_NAME, bundles);
|
||||
}
|
||||
|
||||
return bundles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the default resource bundle based on the locale
|
||||
* information provided in the supplied http request object.
|
||||
*/
|
||||
protected ResourceBundle resolveBundle (
|
||||
HttpServletRequest req, String bundlePath, ClassLoader loader)
|
||||
{
|
||||
ResourceBundle bundle = null;
|
||||
|
||||
if (req != null) {
|
||||
Enumeration locales = req.getLocales();
|
||||
while (locales.hasMoreElements()) {
|
||||
Locale locale = (Locale)locales.nextElement();
|
||||
|
||||
try {
|
||||
// java caches resource bundles, so we don't need to
|
||||
// reinvent the wheel here. however, java also falls back
|
||||
// from a specific bundle to a more general one if it
|
||||
// can't find a specific bundle. that's real nice of it,
|
||||
// but we want first to see whether or not we have exact
|
||||
// matches on any of the preferred locales specified by
|
||||
// the client. if we don't, then we can rely on java's
|
||||
// fallback mechanisms
|
||||
bundle = ResourceBundle.getBundle(
|
||||
bundlePath, locale, loader);
|
||||
|
||||
// if it's an exact match, off we go
|
||||
if (bundle.getLocale().equals(locale)) {
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (MissingResourceException mre) {
|
||||
// no need to freak out quite yet, see if we have
|
||||
// something for one of the other preferred locales
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we were unable to find an exact match for any of the user's
|
||||
// preferred locales, take their most preferred and let java
|
||||
// perform it's fallback logic on that one
|
||||
if (bundle == null) {
|
||||
Locale locale = (req == null) ? _deflocale : req.getLocale();
|
||||
try {
|
||||
bundle = ResourceBundle.getBundle(bundlePath, locale, loader);
|
||||
} catch (MissingResourceException mre) {
|
||||
// if we were unable even to find a default bundle, we've
|
||||
// got real problems. time to freak out
|
||||
Log.warning("Unable to resolve any message bundle " +
|
||||
"[req=" + getURL(req) + ", locale=" + locale +
|
||||
", bundlePath=" + bundlePath +
|
||||
", classLoader=" + loader +
|
||||
", siteBundlePath=" + _siteBundlePath +
|
||||
", siteLoader=" + _siteLoader + "].");
|
||||
}
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected String getURL (HttpServletRequest req)
|
||||
{
|
||||
return (req == null) ? "<none>" : req.getRequestURL().toString();
|
||||
}
|
||||
|
||||
/** The path, relative to the classpath, to our resource bundles. */
|
||||
protected String _bundlePath;
|
||||
|
||||
/** The path to the site-specific message bundles, fetched via the
|
||||
* site-specific resource loader. */
|
||||
protected String _siteBundlePath;
|
||||
|
||||
/** The resource loader with which to fetch our site-specific message
|
||||
* bundles. */
|
||||
protected SiteResourceLoader _siteLoader;
|
||||
|
||||
/** The site identifier we use to determine through which site a
|
||||
* request was made. */
|
||||
protected SiteIdentifier _siteIdent;
|
||||
|
||||
/** The locale to use if we are accessed without an HTTP request. */
|
||||
protected Locale _deflocale;
|
||||
|
||||
/** The attribute name that we use for caching resource bundles in
|
||||
* request objects. */
|
||||
protected static final String BUNDLE_CACHE_NAME =
|
||||
"com.samskivert.servlet.MessageManager:CachedResourceBundle";
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id: RedirectException.java,v 1.2 2001/08/11 22:43:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
/**
|
||||
* A redirect exception is thrown by servlet services when they require
|
||||
* that the user be redirected to a different URL rather than continue
|
||||
* processing this request. It is expected that redirect handling can be
|
||||
* implemented in a single place such that servlets can simply allow this
|
||||
* exception to propagate up to the proper handler which will then issue
|
||||
* the appropriate redirect header.
|
||||
*/
|
||||
public class RedirectException extends Exception
|
||||
{
|
||||
public RedirectException (String url)
|
||||
{
|
||||
super(url);
|
||||
}
|
||||
|
||||
public String getRedirectURL ()
|
||||
{
|
||||
return getMessage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id: Site.java,v 1.2 2003/11/13 00:53:00 mdb Exp $
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Represents a site mapping known to a {@link SiteIdentifier}.
|
||||
*
|
||||
* @see SiteIdentifier#enumerateSites
|
||||
*/
|
||||
public class Site
|
||||
{
|
||||
/** The site's unqiue identifier. */
|
||||
public int siteId;
|
||||
|
||||
/** The site's human readable identifier (i.e., "monkeybutter"). */
|
||||
public String siteString;
|
||||
|
||||
/** Constructs a site record with the specified id and string. */
|
||||
public Site (int siteId, String siteString)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
this.siteString = siteString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a blank record for unserialization from the repository.
|
||||
*/
|
||||
public Site ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of this instance.
|
||||
*/
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// $Id: SiteIdentifier.java,v 1.3 2003/11/13 00:53:00 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.util.Iterator;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Responsible for determining the unique site identifier based on
|
||||
* information available in the HTTP request. Site identifiers are
|
||||
* integers ranging from 1 to {@link Integer#MAX_VALUE}. Because the site
|
||||
* identifier implementation is likely to have access to the site
|
||||
* classification metadata, this interface is also used to map integer
|
||||
* site identifiers to string site identifiers.
|
||||
*/
|
||||
public interface SiteIdentifier
|
||||
{
|
||||
/** The default site identifier, to be used when a site cannot be
|
||||
* identified or for site identifiers that don't wish to distinguish
|
||||
* between sites. */
|
||||
public static final int DEFAULT_SITE_ID = -1;
|
||||
|
||||
/** The string identifier for the default site. */
|
||||
public static final String DEFAULT_SITE_STRING = "default";
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for the site on which this request
|
||||
* originated. That may be divined by looking at the server name, or
|
||||
* perhaps a request parameter, or part of the path info. The
|
||||
* mechanism (or mechanisms) are up to the implementation.
|
||||
*
|
||||
* @param req the http servlet request the site for which we are
|
||||
* trying to identify.
|
||||
*
|
||||
* @return the unique site identifier requestsed or {@link
|
||||
* #DEFAULT_SITE_ID} if the site could not be identified. No site
|
||||
* should ever have a site id value of 0.
|
||||
*/
|
||||
public int identifySite (HttpServletRequest req);
|
||||
|
||||
/**
|
||||
* Returns a string representation of the site identifier. The
|
||||
* SiteIdentifier in use can map the site ids to strings however it
|
||||
* likes as long as it consistently maps the same identifier to the
|
||||
* same string and vice versa. Presumably these strings would be human
|
||||
* readable and meaningful.
|
||||
*
|
||||
* @param siteId the unique integer identifier for the site that we
|
||||
* wish to be identified by a string.
|
||||
*
|
||||
* @return the string identifier for this site.
|
||||
*/
|
||||
public String getSiteString (int siteId);
|
||||
|
||||
/**
|
||||
* Returns the site identifier for the site associated with the
|
||||
* supplied site string. The SiteIdentifier in use can map the site
|
||||
* ids to strings however it likes as long as it consistently maps the
|
||||
* same string to the same identifier and vice versa.
|
||||
*
|
||||
* @param siteString the string to be converted into a site identifer.
|
||||
*
|
||||
* @return the integer identifier for this site.
|
||||
*/
|
||||
public int getSiteId (String siteString);
|
||||
|
||||
/**
|
||||
* Returns an enumerator over all {@link Site} mappings known to this
|
||||
* SiteIdentifier.
|
||||
*/
|
||||
public Iterator enumerateSites ();
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
//
|
||||
// $Id: SiteResourceLoader.java,v 1.7 2004/05/11 03:14:03 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
/**
|
||||
* Web applications may wish to load resources in such a way that the site
|
||||
* on which they are running is allowed to override a resource with a
|
||||
* site-specific version (a header, footer or navigation template for
|
||||
* example). The site resource loader provides this capability by loading
|
||||
* resources from a site-specific jar file.
|
||||
*
|
||||
* <p> The site resource loader must be configured with the path to the
|
||||
* site-specific jar files, the names of which are dictated by the string
|
||||
* identifiers returned by the {@link SiteIdentifier} provided to the
|
||||
* resource loader at construct time. For example, if the configuration
|
||||
* dictates that site-specific jar files are located in
|
||||
* <code>/usr/share/java/webapps/site-data</code> and the site identifier
|
||||
* returns <code>samskivert</code> as the site identifier for a particular
|
||||
* request, site-specific resources will be loaded from
|
||||
* <code>/usr/share/java/webapps/site-data/samskivert.jar</code>.
|
||||
*/
|
||||
public class SiteResourceLoader
|
||||
{
|
||||
/**
|
||||
* Constructs a new resource loader.
|
||||
*
|
||||
* @param siteIdent the site identifier to be used to identify which
|
||||
* site through which a request was made when loading resources.
|
||||
* @param siteJarPath the path to the site-specific jar files.
|
||||
*/
|
||||
public SiteResourceLoader (
|
||||
SiteIdentifier siteIdent, String siteJarPath)
|
||||
{
|
||||
// keep this stuff around
|
||||
_siteIdent = siteIdent;
|
||||
_jarPath = siteJarPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specific resource, from the site-specific jar file if one
|
||||
* exists and contains the specified resource. If no resource exists
|
||||
* with that path, null will be returned.
|
||||
*
|
||||
* @param req the http request for which we are loading a resource
|
||||
* (this will be used to determine for which site the resource will be
|
||||
* loaded).
|
||||
* @param path the path to the desired resource.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or null
|
||||
* if no resource could be located with the specified path.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while loading
|
||||
* a resource.
|
||||
*/
|
||||
public InputStream getResourceAsStream (
|
||||
HttpServletRequest req, String path)
|
||||
throws IOException
|
||||
{
|
||||
return getResourceAsStream(_siteIdent.identifySite(req), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the specific resource, from the site-specific jar file if one
|
||||
* exists and contains the specified resource. If no resource exists
|
||||
* with that path, null will be returned.
|
||||
*
|
||||
* @param siteId the unique identifer for the site for which we are
|
||||
* loading the resource.
|
||||
* @param path the path to the desired resource.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or null
|
||||
* if no resource could be located with the specified path.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while loading
|
||||
* a resource.
|
||||
*/
|
||||
public InputStream getResourceAsStream (int siteId, String path)
|
||||
throws IOException
|
||||
{
|
||||
// Log.info("Loading site resource [siteId=" + siteId +
|
||||
// ", path=" + path + "].");
|
||||
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
SiteResourceBundle bundle = getBundle(siteId);
|
||||
|
||||
// make sure the path has no leading slash
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// obtain our resource from the bundle
|
||||
return bundle.getResourceAsStream(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modification time of the site-specific jar file
|
||||
* for the specified site.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs accessing the
|
||||
* site-specific jar file (like it doesn't exist).
|
||||
*/
|
||||
public long getLastModified (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
return getBundle(siteId).getLastModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a class loader that loads resources from the site-specific
|
||||
* jar file for the specified site. If no site-specific jar file
|
||||
* exists for the specified site, null will be returned.
|
||||
*/
|
||||
public ClassLoader getSiteClassLoader (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// synchronize on the lock to ensure that only one thread per site
|
||||
// is concurrently executing
|
||||
synchronized (getLock(siteId)) {
|
||||
// see if we've already got one
|
||||
ClassLoader loader = (ClassLoader)_loaders.get(siteId);
|
||||
|
||||
// create one if we've not
|
||||
if (loader == null) {
|
||||
SiteResourceBundle bundle = getBundle(siteId);
|
||||
if (bundle == null) {
|
||||
// no bundle... no classloader.
|
||||
return null;
|
||||
}
|
||||
|
||||
loader = new SiteClassLoader(bundle);
|
||||
_loaders.put(siteId, loader);
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this instance.
|
||||
*/
|
||||
public String toString ()
|
||||
{
|
||||
return "[jarPath=" + _jarPath + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* We synchronize on a per-site basis, but we use a separate lock
|
||||
* object for each site so that the process of loading a bundle for
|
||||
* the first time does not require blocking access to resources from
|
||||
* other sites.
|
||||
*/
|
||||
protected Object getLock (int siteId)
|
||||
{
|
||||
Object lock = null;
|
||||
|
||||
synchronized (_locks) {
|
||||
lock = _locks.get(siteId);
|
||||
|
||||
// create a lock object if we haven't one already
|
||||
if (lock == null) {
|
||||
_locks.put(siteId, lock = new Object());
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the site-specific jar file for the specified site. This
|
||||
* should only be called when the lock for this site is held.
|
||||
*/
|
||||
protected SiteResourceBundle getBundle (int siteId)
|
||||
throws IOException
|
||||
{
|
||||
// look up the site resource bundle for this site
|
||||
SiteResourceBundle bundle = (SiteResourceBundle)
|
||||
_bundles.get(siteId);
|
||||
|
||||
// if we haven't got one, create one
|
||||
if (bundle == null) {
|
||||
// obtain the string identifier for this site
|
||||
String ident = _siteIdent.getSiteString(siteId);
|
||||
// compose that with the jar file directory to obtain the
|
||||
// path to the site-specific jar file
|
||||
File file = new File(_jarPath, ident + JAR_EXTENSION);
|
||||
// create a handle for this site-specific jar file
|
||||
bundle = new SiteResourceBundle(file);
|
||||
// cache our new bundle
|
||||
_bundles.put(siteId, bundle);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the information we need to load data from a site
|
||||
* resource bundle as well as to determine whether the loaded bundle
|
||||
* is up to date.
|
||||
*/
|
||||
public static class SiteResourceBundle
|
||||
{
|
||||
/** The object through which we load resources from the
|
||||
* site-specific jar file. */
|
||||
public JarFile jarFile;
|
||||
|
||||
/** A handle on the site-specific jar file. */
|
||||
public File file;
|
||||
|
||||
/**
|
||||
* Constructs a new site resource bundle. The associated jar file
|
||||
* will be opened the first time a resource is read.
|
||||
*/
|
||||
public SiteResourceBundle (File file)
|
||||
throws IOException
|
||||
{
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the specified resource from our site-specific jar file.
|
||||
* The last modified time of the underlying jar file may be
|
||||
* checked to determine whether or not it needs to be reloaded.
|
||||
*
|
||||
* @return an input stream via which the resource can be read or
|
||||
* null if no resource exists with the specified path.
|
||||
*/
|
||||
public InputStream getResourceAsStream (String path)
|
||||
throws IOException
|
||||
{
|
||||
// open or reopen our underlying jar file as necessary
|
||||
refreshJarFile();
|
||||
|
||||
// now load up the resource
|
||||
JarEntry entry = jarFile.getJarEntry(path);
|
||||
return (entry == null) ? null : jarFile.getInputStream(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified time of the underlying jar file.
|
||||
*/
|
||||
public long getLastModified ()
|
||||
throws IOException
|
||||
{
|
||||
// open or reopen our underlying jar file as necessary
|
||||
refreshJarFile();
|
||||
|
||||
return _lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this instance.
|
||||
*/
|
||||
public String toString ()
|
||||
{
|
||||
return "[bundle=" + file + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopens our site-specific jar file if it has been modified
|
||||
* since it was last opened.
|
||||
*/
|
||||
protected void refreshJarFile ()
|
||||
throws IOException
|
||||
{
|
||||
// ensure that the file exists
|
||||
if (!file.exists()) {
|
||||
String errmsg = "No site-specific jar file " +
|
||||
"[path=" + file.getPath() + "].";
|
||||
throw new FileNotFoundException(errmsg);
|
||||
}
|
||||
|
||||
// determine whether or not we need to create a new jarfile
|
||||
// instance
|
||||
if (file.lastModified() > _lastModified) {
|
||||
// make a note of the last modified time
|
||||
_lastModified = file.lastModified();
|
||||
|
||||
// close our old jar file if we've got one
|
||||
if (jarFile != null) {
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
Log.info("Opened site bundle [path=" + file.getPath() + "].");
|
||||
|
||||
// and open a new one
|
||||
jarFile = new JarFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
/** The last modified time of the jar file at the time that we
|
||||
* opened it for reading. */
|
||||
protected long _lastModified;
|
||||
}
|
||||
|
||||
protected static class SiteClassLoader extends ClassLoader
|
||||
{
|
||||
public SiteClassLoader (SiteResourceBundle bundle)
|
||||
{
|
||||
_bundle = bundle;
|
||||
}
|
||||
|
||||
public InputStream getResourceAsStream (String path)
|
||||
{
|
||||
try {
|
||||
return _bundle.getResourceAsStream(path);
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error loading resource from jarfile " +
|
||||
"[bundle=" + _bundle + ", path=" + path +
|
||||
", error=" + ioe + "].");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
return _bundle.toString();
|
||||
}
|
||||
|
||||
protected SiteResourceBundle _bundle;
|
||||
}
|
||||
|
||||
/** The site identifier we use to identify requests. */
|
||||
protected SiteIdentifier _siteIdent;
|
||||
|
||||
/** The path to our site-specific jar files. */
|
||||
protected String _jarPath;
|
||||
|
||||
/** We synchronize on a per-site basis. */
|
||||
protected HashIntMap _locks = new HashIntMap();
|
||||
|
||||
/** The table of site-specific jar file information. */
|
||||
protected HashIntMap _bundles = new HashIntMap();
|
||||
|
||||
/** The table of site-specific class loaders. */
|
||||
protected HashIntMap _loaders = new HashIntMap();
|
||||
|
||||
/** The default path to the site-specific jar files. This won't be
|
||||
* used without logging a complaint first. */
|
||||
protected static final String DEFAULT_SITE_JAR_PATH =
|
||||
"/usr/share/java/webapps/site-data";
|
||||
|
||||
/** The file extension to be appended to the string site identifier to
|
||||
* obtain the file name of the site-specific jar file. */
|
||||
protected static final String JAR_EXTENSION = ".jar";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides services and patterns that are useful in developing web
|
||||
applications.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id: AuthenticationFailedException.java,v 1.1 2002/05/02 19:10:34 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Walter Korman
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown when a user authentication attempt failed.
|
||||
*/
|
||||
public class AuthenticationFailedException extends Exception
|
||||
{
|
||||
public AuthenticationFailedException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $Id: Authenticator.java,v 1.1 2002/05/02 19:10:34 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Walter Korman
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Provides a means for applications to use their own application-specific
|
||||
* authentication schemes for validating a user by constructing their own
|
||||
* authenticator and passing it to {@link UserManager#login}.
|
||||
*/
|
||||
public interface Authenticator
|
||||
{
|
||||
/**
|
||||
* Checks whether the user should be authenticated based on the
|
||||
* supplied user record and the specified user information. Throws an
|
||||
* {@link AuthenticationFailedException} if the user fails to pass the
|
||||
* authentication check for some reason.
|
||||
*
|
||||
* @param user the definitive user record loaded from the persistent
|
||||
* repository against which the user-supplied data is to be checked.
|
||||
* @param username the username supplied by the user.
|
||||
* @param password the plaintext password supplied by the user.
|
||||
* @param persist if true, the cookie will expire in one month, if
|
||||
* false, the cookie will expire in 24 hours.
|
||||
*
|
||||
* @throws AuthenticationFailedException if the user failed to pass
|
||||
* the authentication check.
|
||||
*/
|
||||
public void authenticateUser (
|
||||
User user, String username, Password password, boolean persist)
|
||||
throws AuthenticationFailedException;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id: InvalidPasswordException.java,v 1.4 2002/05/02 19:10:34 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown during authentication when an invalid password is supplied.
|
||||
*/
|
||||
public class InvalidPasswordException extends AuthenticationFailedException
|
||||
{
|
||||
public InvalidPasswordException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id: InvalidUsernameException.java,v 1.3 2001/08/12 01:34:31 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown during user account creation if an invalid username is supplied.
|
||||
*/
|
||||
public class InvalidUsernameException extends Exception
|
||||
{
|
||||
public InvalidUsernameException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// $Id: NoSuchUserException.java,v 1.4 2002/05/02 19:10:34 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Thrown when a user cannot be located in the user database.
|
||||
*/
|
||||
public class NoSuchUserException extends AuthenticationFailedException
|
||||
{
|
||||
public NoSuchUserException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// $Id: Password.java,v 1.1 2004/01/31 06:24:01 mdb Exp $
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Represents an encrypted password. Currently only used when creating
|
||||
* user accounts.
|
||||
*/
|
||||
public class Password
|
||||
{
|
||||
/**
|
||||
* Returns the clear password text. This method may return null if
|
||||
* none was provided when creating the password.
|
||||
*/
|
||||
public String getCleartext ()
|
||||
{
|
||||
return _cleartext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encrypted password text.
|
||||
*/
|
||||
public String getEncrypted ()
|
||||
{
|
||||
return _encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a password instance from the supplied plaintext.
|
||||
*/
|
||||
public static Password makeFromClear (String password)
|
||||
{
|
||||
return new Password(password, UserUtil.encryptPassword(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a password instance from the supplied already encrypted
|
||||
* text. <em>Note:</em> the encrypted text must be obtained from
|
||||
* {@link UserUtil#encryptPassword}.
|
||||
*/
|
||||
public static Password makeFromCrypto (String encrypted)
|
||||
{
|
||||
return new Password(null, encrypted);
|
||||
}
|
||||
|
||||
/** Creates a password instance. */
|
||||
protected Password (String cleartext, String encrypted)
|
||||
{
|
||||
_cleartext = cleartext;
|
||||
_encrypted = encrypted;
|
||||
}
|
||||
|
||||
protected String _cleartext;
|
||||
protected String _encrypted;
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// $Id: User.java,v 1.10 2004/01/13 23:02:04 ray Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
import com.samskivert.jdbc.jora.FieldMask;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A user object contains information about a registered user in our web
|
||||
* application environment. Users are stored in the user repository (a
|
||||
* database table) and loaded during a request based on authentication
|
||||
* information provided with the request headers.
|
||||
*
|
||||
* <p><b>Note:</b> Do not modify any of the fields of this object
|
||||
* directly. Use the <code>set</code> methods to make updates. If no set
|
||||
* methods exist, you shouldn't be modifying that field.
|
||||
*/
|
||||
public class User
|
||||
{
|
||||
/** The user's assigned integer userid. */
|
||||
public int userId;
|
||||
|
||||
/** The user's chosen username. */
|
||||
public String username;
|
||||
|
||||
/** The date this record was created. */
|
||||
public Date created;
|
||||
|
||||
/** The user's real name (first, last and whatever else they opt to
|
||||
* provide). */
|
||||
public String realname;
|
||||
|
||||
/** The user's chosen password (encrypted). */
|
||||
public String password;
|
||||
|
||||
/** The user's email address. */
|
||||
public String email;
|
||||
|
||||
/** The site identifier of the site through which the user created
|
||||
* their account. (Their affiliation, if you will.) */
|
||||
public int siteId;
|
||||
|
||||
/**
|
||||
* Updates the user's real name.
|
||||
*/
|
||||
public void setRealName (String realname)
|
||||
{
|
||||
this.realname = realname;
|
||||
_dirty.setModified("realname");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's password.
|
||||
*
|
||||
* @param password The user's new (unencrypted) password.
|
||||
*/
|
||||
public void setPassword (String password)
|
||||
{
|
||||
setPassword(Password.makeFromClear(password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's password.
|
||||
*
|
||||
* @param password The user's new password.
|
||||
*/
|
||||
public void setPassword (Password password)
|
||||
{
|
||||
this.password = password.getEncrypted();
|
||||
_dirty.setModified("password");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's email address.
|
||||
*/
|
||||
public void setEmail (String email)
|
||||
{
|
||||
this.email = email;
|
||||
_dirty.setModified("email");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the user's site id.
|
||||
*/
|
||||
public void setSiteId (int siteId)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
_dirty.setModified("siteId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a legacy password.
|
||||
*
|
||||
* @return true if a conversion took place, false if not.
|
||||
*/
|
||||
public boolean updateLegacyPassword (String password)
|
||||
{
|
||||
// we may have an old crypt() encrypted password
|
||||
if (this.password.length() == 13) {
|
||||
// check both the case sensitive and insensitive versions for
|
||||
// further legacy reasons
|
||||
if (this.password.equals(
|
||||
UserUtil.legacyEncrypt(username, password, false)) ||
|
||||
this.password.equals(
|
||||
UserUtil.legacyEncrypt(username, password, true))) {
|
||||
// update our password with the new encryption method
|
||||
setPassword(password);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the supplied password with the password associated with
|
||||
* this user record.
|
||||
*
|
||||
* @return true if the passwords match, false if they do not.
|
||||
*/
|
||||
public boolean passwordsMatch (Password password)
|
||||
{
|
||||
return this.password.equals(password.getEncrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this User has been "deleted".
|
||||
*/
|
||||
public boolean isDeleted ()
|
||||
{
|
||||
// a deleted account has an "=" in the username
|
||||
return (-1 != username.indexOf('='));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the repository to find out which fields have been
|
||||
* modified since the object was loaded.
|
||||
*/
|
||||
protected FieldMask getDirtyMask ()
|
||||
{
|
||||
return _dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the repository to configure this user record with a field
|
||||
* mask that it can use to track modified fields.
|
||||
*/
|
||||
protected void setDirtyMask (FieldMask dirty)
|
||||
{
|
||||
_dirty = dirty;
|
||||
}
|
||||
|
||||
/** Returns a string representation of this instance. */
|
||||
public String toString ()
|
||||
{
|
||||
return StringUtil.fieldsToString(this);
|
||||
}
|
||||
|
||||
/** Our dirty field mask. */
|
||||
protected transient FieldMask _dirty;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id: UserExistsException.java,v 1.5 2001/09/21 03:01:46 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
|
||||
/**
|
||||
* Thrown during user account creation when a user with the requested
|
||||
* username already exists.
|
||||
*/
|
||||
public class UserExistsException extends PersistenceException
|
||||
{
|
||||
public UserExistsException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
//
|
||||
// $Id: UserManager.java,v 1.28 2003/12/05 18:20:30 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.util.Properties;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.servlet.RedirectException;
|
||||
import com.samskivert.servlet.util.CookieUtil;
|
||||
import com.samskivert.servlet.util.RequestUtils;
|
||||
import com.samskivert.util.Interval;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The user manager provides easy access to user objects for servlets. It
|
||||
* takes care of cookie management involved in login, logout and loading a
|
||||
* user record during an authenticated session.
|
||||
*/
|
||||
public class UserManager
|
||||
{
|
||||
/** An instance of the insecure authenticator for general-purpose use. */
|
||||
public static final Authenticator AUTH_INSECURE =
|
||||
new InsecureAuthenticator();
|
||||
|
||||
/** An instance of the password authenticator for general-purpose use. */
|
||||
public static final Authenticator AUTH_PASSWORD =
|
||||
new PasswordAuthenticator();
|
||||
|
||||
/**
|
||||
* A totally insecure authenticator that authenticates any user.
|
||||
* <em>Note:</em> Applications that make use of this authenticator
|
||||
* should make sure the user has already been authenticated through
|
||||
* some other means.
|
||||
*/
|
||||
public static class InsecureAuthenticator implements Authenticator
|
||||
{
|
||||
// documentation inherited
|
||||
public void authenticateUser (
|
||||
User user, String username, Password password, boolean persist)
|
||||
throws InvalidPasswordException
|
||||
{
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An authenticator that requires that the user-supplied password
|
||||
* match the actual user password.
|
||||
*/
|
||||
public static class PasswordAuthenticator implements Authenticator
|
||||
{
|
||||
// documentation inherited
|
||||
public void authenticateUser (
|
||||
User user, String username, Password password, boolean persist)
|
||||
throws AuthenticationFailedException
|
||||
{
|
||||
if (!user.passwordsMatch(password)) {
|
||||
throw new InvalidPasswordException("error.invalid_password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a user manager and prepares it for operation. Presently
|
||||
* the user manager requires the following configuration information:
|
||||
*
|
||||
* <ul>
|
||||
* <li><code>login_url</code>: Should be set to the URL to which to
|
||||
* redirect a requester if they are required to login before accessing
|
||||
* the requested page. For example:
|
||||
*
|
||||
* <pre>
|
||||
* login_url = /usermgmt/login.ajsp?return=%R
|
||||
* </pre>
|
||||
*
|
||||
* The <code>%R</code> will be replaced with the URL encoded URL the
|
||||
* user is currently requesting (complete with query parameters) so
|
||||
* that the login code can redirect the user back to this request once
|
||||
* they are authenticated.
|
||||
* </ul>
|
||||
*
|
||||
* @param config the user manager configuration properties.
|
||||
* @param conprov the database connection provider that will be used
|
||||
* to obtain a connection to the user database.
|
||||
*/
|
||||
public UserManager (Properties config, ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
// save this for later
|
||||
_config = config;
|
||||
|
||||
// create the user repository
|
||||
_repository = createRepository(conprov);
|
||||
|
||||
// fetch the login URL from the properties
|
||||
_loginURL = config.getProperty("login_url");
|
||||
if (_loginURL == null) {
|
||||
Log.warning("No login_url supplied in user manager config. " +
|
||||
"Authentication won't work.");
|
||||
}
|
||||
|
||||
// look up any override to our user auth cookie
|
||||
String authCook = config.getProperty("auth_cookie.name");
|
||||
if (!StringUtil.isBlank(authCook)) {
|
||||
_userAuthCookie = authCook;
|
||||
}
|
||||
|
||||
// register a cron job to prune the session table every hour
|
||||
_pruner = new Interval() {
|
||||
public void expired ()
|
||||
{
|
||||
try {
|
||||
_repository.pruneSessions();
|
||||
} catch (PersistenceException pe) {
|
||||
Log.warning("Error pruning session table.");
|
||||
Log.logStackTrace(pe);
|
||||
}
|
||||
}
|
||||
};
|
||||
_pruner.schedule(SESSION_PRUNE_INTERVAL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the user manager to create the user repository. Derived
|
||||
* classes can override this and create a specialized repository if
|
||||
* they so desire.
|
||||
*/
|
||||
protected UserRepository createRepository (ConnectionProvider conprov)
|
||||
throws PersistenceException
|
||||
{
|
||||
return new UserRepository(conprov);
|
||||
}
|
||||
|
||||
public void shutdown ()
|
||||
{
|
||||
// cancel our session table pruning thread
|
||||
_pruner.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the repository in use by this user manager.
|
||||
*/
|
||||
public UserRepository getRepository ()
|
||||
{
|
||||
return _repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the necessary authentication information from the http
|
||||
* request and loads the user identified by that information.
|
||||
*
|
||||
* @return the user associated with the request or null if no user was
|
||||
* associated with the request or if the authentication information is
|
||||
* bogus.
|
||||
*/
|
||||
public User loadUser (HttpServletRequest req)
|
||||
throws PersistenceException
|
||||
{
|
||||
String authcode = CookieUtil.getCookieValue(req, _userAuthCookie);
|
||||
if (authcode != null) {
|
||||
return _repository.loadUserBySession(authcode);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the necessary authentication information from the http
|
||||
* request and loads the user identified by that information. If no
|
||||
* user could be loaded (because the requester is not authenticated),
|
||||
* a redirect exception will be thrown to redirect the user to the
|
||||
* login page specified in the user manager configuration.
|
||||
*
|
||||
* @return the user associated with the request.
|
||||
*/
|
||||
public User requireUser (HttpServletRequest req)
|
||||
throws PersistenceException, RedirectException
|
||||
{
|
||||
User user = loadUser(req);
|
||||
// if no user was loaded, we need to redirect these fine people to
|
||||
// the login page
|
||||
if (user == null) {
|
||||
// first construct the redirect URL
|
||||
String eurl = RequestUtils.getLocationEncoded(req);
|
||||
String target = StringUtil.replace(_loginURL, "%R", eurl);
|
||||
throw new RedirectException(target);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to authenticate the requester and initiate an
|
||||
* authenticated session for them. An authenticated session involves
|
||||
* their receiving a cookie that proves them to be authenticated and
|
||||
* an entry in the session database being created that maps their
|
||||
* information to their userid. If this call completes, the session
|
||||
* was established and the proper cookies were set in the supplied
|
||||
* response object. If invalid authentication information is provided
|
||||
* or some other error occurs, an exception will be thrown.
|
||||
*
|
||||
* @param username The username supplied by the user.
|
||||
* @param password The password supplied by the user.
|
||||
* @param persist If true, the cookie will expire in one month, if
|
||||
* false, the cookie will expire at the end of the user's browser
|
||||
* session.
|
||||
* @param req The request via which the login page was loaded.
|
||||
* @param rsp The response in which the cookie is to be set.
|
||||
* @param auth The authenticator used to check whether the user should
|
||||
* be authenticated.
|
||||
*
|
||||
* @return the user object of the authenticated user.
|
||||
*/
|
||||
public User login (String username, Password password, boolean persist,
|
||||
HttpServletRequest req, HttpServletResponse rsp,
|
||||
Authenticator auth)
|
||||
throws PersistenceException, AuthenticationFailedException
|
||||
{
|
||||
// load up the requested user
|
||||
User user = _repository.loadUser(username);
|
||||
if (user == null) {
|
||||
throw new NoSuchUserException("error.no_such_user");
|
||||
}
|
||||
|
||||
// potentially convert the user's legacy password
|
||||
if (password != null && password.getCleartext() != null &&
|
||||
user.updateLegacyPassword(password.getCleartext())) {
|
||||
Log.info("Updated legacy password " + user.username + ".");
|
||||
_repository.updateUser(user);
|
||||
}
|
||||
|
||||
// run the user through the authentication gamut
|
||||
auth.authenticateUser(user, username, password, persist);
|
||||
|
||||
// give them the necessary cookies and business
|
||||
effectLogin(user, persist, req, rsp);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a user is already known to be authenticated for one reason or
|
||||
* other, this method can be used to give them the appropriate
|
||||
* authentication cookies to effect their login.
|
||||
*/
|
||||
public void effectLogin (User user, boolean persist,
|
||||
HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws PersistenceException
|
||||
{
|
||||
// generate a new session for this user
|
||||
String authcode = _repository.createNewSession(user, persist);
|
||||
// stick it into a cookie for their browsing convenience
|
||||
Cookie acookie = new Cookie(_userAuthCookie, authcode);
|
||||
// strip the hostname from the server and use that as the domain
|
||||
// unless configured not to
|
||||
if (!"false".equalsIgnoreCase(
|
||||
_config.getProperty("auth_cookie.strip_hostname"))) {
|
||||
CookieUtil.widenDomain(req, acookie);
|
||||
}
|
||||
acookie.setPath("/");
|
||||
// expire in one month if persistent, else at the end of the
|
||||
// session
|
||||
acookie.setMaxAge((persist) ? (30*24*60*60) : -1);
|
||||
rsp.addCookie(acookie);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the user out.
|
||||
*/
|
||||
public void logout (HttpServletRequest req, HttpServletResponse rsp)
|
||||
{
|
||||
String authcode = CookieUtil.getCookieValue(req, _userAuthCookie);
|
||||
|
||||
// nothing to do if they don't already have an auth cookie
|
||||
if (authcode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set them up the bomb
|
||||
Cookie c = new Cookie(_userAuthCookie, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
CookieUtil.widenDomain(req, c);
|
||||
rsp.addCookie(c);
|
||||
|
||||
// we need an unwidened one to ensure that old-style cookies are
|
||||
// wiped as well
|
||||
c = new Cookie(_userAuthCookie, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
rsp.addCookie(c);
|
||||
}
|
||||
|
||||
/** Our user manager configuration. */
|
||||
protected Properties _config;
|
||||
|
||||
/** The user repository. */
|
||||
protected UserRepository _repository;
|
||||
|
||||
/** The interval for user session pruning. */
|
||||
protected Interval _pruner;
|
||||
|
||||
/** The URL for the user login page. */
|
||||
protected String _loginURL;
|
||||
|
||||
/** The name of our user authentication cookie. */
|
||||
protected String _userAuthCookie = USERAUTH_COOKIE;
|
||||
|
||||
/** The user authentication cookie name. */
|
||||
protected static final String USERAUTH_COOKIE = "id_";
|
||||
|
||||
/** Prune the session table every hour. */
|
||||
protected static final long SESSION_PRUNE_INTERVAL = 60L * 60L * 1000L;
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
//
|
||||
// $Id: UserRepository.java,v 1.40 2004/02/25 13:17:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.jdbc.ConnectionProvider;
|
||||
import com.samskivert.jdbc.DatabaseLiaison;
|
||||
import com.samskivert.jdbc.JDBCUtil;
|
||||
import com.samskivert.jdbc.JORARepository;
|
||||
import com.samskivert.jdbc.StaticConnectionProvider;
|
||||
import com.samskivert.jdbc.jora.Cursor;
|
||||
import com.samskivert.jdbc.jora.FieldMask;
|
||||
import com.samskivert.jdbc.jora.Session;
|
||||
import com.samskivert.jdbc.jora.Table;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.samskivert.servlet.SiteIdentifier;
|
||||
|
||||
/**
|
||||
* Interfaces with the RDBMS in which the user information is stored. The
|
||||
* user repository encapsulates the creating, loading and management of
|
||||
* users and sessions.
|
||||
*/
|
||||
public class UserRepository extends JORARepository
|
||||
{
|
||||
/**
|
||||
* The database identifier used to obtain a connection from our
|
||||
* connection provider. The value is <code>userdb</code> which you'll
|
||||
* probably need to know to provide the proper configuration to your
|
||||
* connection provider.
|
||||
*/
|
||||
public static final String USER_REPOSITORY_IDENT = "userdb";
|
||||
|
||||
/**
|
||||
* Creates the repository and opens the user database. The database
|
||||
* identifier used to fetch our database connection is documented by
|
||||
* {@link #USER_REPOSITORY_IDENT}.
|
||||
*
|
||||
* @param provider the database connection provider.
|
||||
*/
|
||||
public UserRepository (ConnectionProvider provider)
|
||||
throws PersistenceException
|
||||
{
|
||||
super(provider, USER_REPOSITORY_IDENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived classes that extend the user record with their own
|
||||
* additional information will want to override this method and return
|
||||
* their desired {@link User} derivation.
|
||||
*/
|
||||
protected Class getUserClass ()
|
||||
{
|
||||
return User.class;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void createTables (Session session)
|
||||
{
|
||||
// create our table object
|
||||
_utable = new Table(
|
||||
getUserClass().getName(), "users", session, "userId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that a new user be created in the repository.
|
||||
*
|
||||
* @param username the username of the new user to create.
|
||||
* @param password the password for the new user.
|
||||
* @param realname the user's real name.
|
||||
* @param email the user's email address.
|
||||
* @param siteId the unique identifier of the site through which this
|
||||
* account is being created. The resulting user will be tracked as
|
||||
* originating from this site for accounting purposes ({@link
|
||||
* SiteIdentifier#DEFAULT_SITE_ID} can be used by systems that don't
|
||||
* desire to perform site tracking.
|
||||
*
|
||||
* @return The userid of the newly created user.
|
||||
*/
|
||||
public int createUser (Username username, Password password,
|
||||
String realname, String email, int siteId)
|
||||
throws UserExistsException, PersistenceException
|
||||
{
|
||||
// create a new user object...
|
||||
User user = new User();
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
|
||||
// ...configure it...
|
||||
populateUser(user, username, password, realname, email, siteId);
|
||||
// ...and stick it into the database
|
||||
return insertUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the supplied user record with the provided information
|
||||
* in preparation for inserting the record into the database for the
|
||||
* first time.
|
||||
*/
|
||||
protected void populateUser (User user, Username name, Password pass,
|
||||
String realname, String email, int siteId)
|
||||
{
|
||||
user.username = name.getUsername();
|
||||
user.setPassword(pass);
|
||||
user.setRealName(realname);
|
||||
user.setEmail(email);
|
||||
user.created = new Date(System.currentTimeMillis());
|
||||
user.setSiteId(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the supplied user record into the user database, assigning
|
||||
* it a userid in the process, which is returned.
|
||||
*/
|
||||
protected int insertUser (final User user)
|
||||
throws UserExistsException, PersistenceException
|
||||
{
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
try {
|
||||
_utable.insert(user);
|
||||
// update the userid now that it's known
|
||||
user.userId = liaison.lastInsertedId(conn);
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} catch (SQLException sqe) {
|
||||
if (liaison.isDuplicateRowException(sqe)) {
|
||||
throw new UserExistsException("error.user_exists");
|
||||
} else {
|
||||
throw sqe;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return user.userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by username.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with
|
||||
* that id exists.
|
||||
*/
|
||||
public User loadUser (String username)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadUserWhere("where username = " +
|
||||
JDBCUtil.escape(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by userid.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with
|
||||
* that id exists.
|
||||
*/
|
||||
public User loadUser (int userId)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadUserWhere("where userId = " + userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a user by a session identifier.
|
||||
*
|
||||
* @return the user associated with the specified session or null of
|
||||
* no session exists with the supplied identifier.
|
||||
*/
|
||||
public User loadUserBySession (final String sessionKey)
|
||||
throws PersistenceException
|
||||
{
|
||||
return (User)execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
String query = "where authcode = '" + sessionKey +
|
||||
"' AND sessions.userId = users.userId";
|
||||
// look up the user
|
||||
Cursor ec = _utable.select("sessions", query);
|
||||
|
||||
// fetch the user from the cursor
|
||||
User user = (User)ec.next();
|
||||
if (user != null) {
|
||||
// call next() again to cause the cursor to close itself
|
||||
ec.next();
|
||||
// configure the user record with its field mask
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up users by userid
|
||||
*
|
||||
* @return the users whom have a user id in the userIds array.
|
||||
*/
|
||||
public HashIntMap loadUsersFromId (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
// make sure we actually have something to do
|
||||
if (userIds.length == 0) {
|
||||
return new HashIntMap();
|
||||
}
|
||||
|
||||
final String ids = genIdString(userIds);
|
||||
|
||||
return (HashIntMap)execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
// look up the users
|
||||
Cursor ec = _utable.select("where userid in (" + ids + ")");
|
||||
|
||||
User user;
|
||||
HashIntMap data = new HashIntMap();
|
||||
while ((user = (User)ec.next()) != null) {
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
data.put(user.userId, user);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a user by email address, something that is not efficient and
|
||||
* should really only be done by site admins attempting to look up a
|
||||
* user record.
|
||||
*
|
||||
* @return the user with the specified user id or null if no user with
|
||||
* that id exists.
|
||||
*/
|
||||
public ArrayList lookupUsersByEmail (String email)
|
||||
throws PersistenceException
|
||||
{
|
||||
final String where = "where email = " +
|
||||
JDBCUtil.escape(email);
|
||||
return (ArrayList) execute(new Operation() {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
return _utable.select(where).toArrayList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads up a user record that matches the specified where clause.
|
||||
* Returns null if no record matches.
|
||||
*/
|
||||
protected User loadUserWhere (final String whereClause)
|
||||
throws PersistenceException
|
||||
{
|
||||
return (User)execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
// look up the user
|
||||
Cursor ec = _utable.select(whereClause);
|
||||
|
||||
// fetch the user from the cursor
|
||||
User user = (User)ec.next();
|
||||
if (user != null) {
|
||||
// call next() again to cause the cursor to close itself
|
||||
ec.next();
|
||||
// configure the user record with its field mask
|
||||
user.setDirtyMask(_utable.getFieldMask());
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a user that was previously fetched from the repository.
|
||||
* Only fields that have been modified since it was loaded will be
|
||||
* written to the database and those fields will subsequently be
|
||||
* marked clean once again.
|
||||
*
|
||||
* @return true if the record was updated, false if the update was
|
||||
* skipped because no fields in the user record were modified.
|
||||
*/
|
||||
public boolean updateUser (final User user)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (!user.getDirtyMask().isModified()) {
|
||||
// nothing doing!
|
||||
return false;
|
||||
}
|
||||
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
_utable.update(user, user.getDirtyMask());
|
||||
// nothing to return
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Delete' the users account such that they can no longer access it,
|
||||
* however we do not delete the record from the db. The name is
|
||||
* changed such that the original name has XX=FOO if the name were FOO
|
||||
* originally. If we have to lop off any of the name to get our
|
||||
* prefix to fit we use a minus sign instead of a equals side. The
|
||||
* password field is set to be the empty string so that no one can log
|
||||
* in (since nothing hashes to the empty string. We also make sure
|
||||
* their email address no longer works, so in case we don't ignore
|
||||
* 'deleted' users when we do the sql to get emailaddresses for the mass
|
||||
* mailings we still won't spam delete folk. We leave the emailaddress
|
||||
* intact exect for the @ sign which gets turned to a #, so that we can
|
||||
* see what their email was incase it was an accidently deletion and we
|
||||
* have to verify through email.
|
||||
*/
|
||||
public void deleteUser (final User user)
|
||||
throws PersistenceException
|
||||
{
|
||||
if (user.isDeleted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
// create our modified fields mask
|
||||
FieldMask mask = _utable.getFieldMask();
|
||||
mask.setModified("username");
|
||||
mask.setModified("password");
|
||||
mask.setModified("email");
|
||||
|
||||
// set the password to unusable
|
||||
user.password = "";
|
||||
|
||||
// 'disable' their email address
|
||||
String newEmail = user.email.replace('@','#');
|
||||
user.email = newEmail;
|
||||
|
||||
String oldName = user.username;
|
||||
for (int ii = 0; ii < 100; ii++) {
|
||||
try {
|
||||
user.username = StringUtil.truncate(
|
||||
ii + "=" + oldName, 24);
|
||||
_utable.update(user, mask);
|
||||
return null; // nothing to return
|
||||
} catch (SQLException se) {
|
||||
if (!liaison.isDuplicateRowException(se)) {
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ok we failed to rename the user, lets bust an error
|
||||
throw new PersistenceException("Failed to 'delete' the user");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session for the specified user and returns the
|
||||
* randomly generated session identifier for that session. Temporary
|
||||
* sessions are set to expire at the end of the user's browser
|
||||
* session. Persistent sessions expire after one month.
|
||||
*/
|
||||
public String createNewSession (final User user, boolean persist)
|
||||
throws PersistenceException
|
||||
{
|
||||
// generate a random session identifier
|
||||
final String authcode = UserUtil.genAuthCode(user);
|
||||
|
||||
// figure out when to expire the session
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DATE, persist ? 30 : 2);
|
||||
final Date expires = new Date(cal.getTime().getTime());
|
||||
|
||||
// insert the session into the database
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
_session.execute("insert into sessions " +
|
||||
"(authcode, userId, expires) values('" +
|
||||
authcode + "', " + user.userId + ", '" +
|
||||
expires + "')");
|
||||
// nothing to return
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// and let the user know what the session identifier is
|
||||
return authcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prunes any expired sessions from the sessions table.
|
||||
*/
|
||||
public void pruneSessions ()
|
||||
throws PersistenceException
|
||||
{
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
_session.execute("delete from sessions where " +
|
||||
"expires <= CURRENT_DATE()");
|
||||
// nothing to return
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the usernames of the users identified by the supplied user
|
||||
* ids and returns them in an array. If any users do not exist, their
|
||||
* slot in the array will contain a null.
|
||||
*/
|
||||
public String[] loadUserNames (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadNames(userIds, "username");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the real names of the users identified by the supplied user
|
||||
* ids and returns them in an array. If any users do not exist, their
|
||||
* slot in the array will contain a null.
|
||||
*/
|
||||
public String[] loadRealNames (int[] userIds)
|
||||
throws PersistenceException
|
||||
{
|
||||
return loadNames(userIds, "realname");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the real names of every user in the system.
|
||||
* This is for Paul who whined about not knowing who was using Who,
|
||||
* Where, When because he didn't feel like emailing anyone that wasn't
|
||||
* already using it to link up.
|
||||
*/
|
||||
public String[] loadAllRealNames ()
|
||||
throws PersistenceException
|
||||
{
|
||||
final ArrayList names = new ArrayList();
|
||||
|
||||
// do the query
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = _session.connection.createStatement();
|
||||
try {
|
||||
String query = "select realname from users";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
names.add(rs.getString(1));
|
||||
}
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// finally construct our result
|
||||
String[] result = new String[names.size()];
|
||||
names.toArray(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String[] loadNames (int[] userIds, final String column)
|
||||
throws PersistenceException
|
||||
{
|
||||
// if userids is zero length, we've got no work to do
|
||||
if (userIds.length == 0) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
final String ids = genIdString(userIds);
|
||||
|
||||
final HashIntMap map = new HashIntMap();
|
||||
|
||||
// do the query
|
||||
execute(new Operation () {
|
||||
public Object invoke (Connection conn, DatabaseLiaison liaison)
|
||||
throws PersistenceException, SQLException
|
||||
{
|
||||
Statement stmt = _session.connection.createStatement();
|
||||
try {
|
||||
String query = "select userId, " + column +
|
||||
" from users " +
|
||||
"where userId in (" + ids + ")";
|
||||
ResultSet rs = stmt.executeQuery(query);
|
||||
while (rs.next()) {
|
||||
int userId = rs.getInt(1);
|
||||
String name = rs.getString(2);
|
||||
map.put(userId, name);
|
||||
}
|
||||
|
||||
// nothing to return
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
JDBCUtil.close(stmt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// finally construct our result
|
||||
String[] result = new String[userIds.length];
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
result[i] = (String)map.get(userIds[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take the passed in int array and create the a string suitable for
|
||||
* using in a SQL set query (I.e., "select foo, from bar where userid
|
||||
* in (genIdString(userIds))"; )
|
||||
*/
|
||||
protected String genIdString (int[] userIds)
|
||||
{
|
||||
// build up the string we need for the query
|
||||
StringBuffer ids = new StringBuffer();
|
||||
for (int i = 0; i < userIds.length; i++) {
|
||||
if (ids.length() > 0) {
|
||||
ids.append(",");
|
||||
}
|
||||
ids.append(userIds[i]);
|
||||
}
|
||||
|
||||
return ids.toString();
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
Properties props = new Properties();
|
||||
props.put(USER_REPOSITORY_IDENT + ".driver",
|
||||
"org.gjt.mm.mysql.Driver");
|
||||
props.put(USER_REPOSITORY_IDENT + ".url",
|
||||
"jdbc:mysql://localhost:3306/samskivert");
|
||||
props.put(USER_REPOSITORY_IDENT + ".username", "www");
|
||||
props.put(USER_REPOSITORY_IDENT + ".password", "Il0ve2PL@Y");
|
||||
|
||||
try {
|
||||
StaticConnectionProvider scp =
|
||||
new StaticConnectionProvider(props);
|
||||
UserRepository rep = new UserRepository(scp);
|
||||
|
||||
System.out.println(rep.loadUser("mdb"));
|
||||
System.out.println(rep.loadUserBySession("auth"));
|
||||
|
||||
rep.createUser(new Username("samskivert"),
|
||||
Password.makeFromClear("foobar"),
|
||||
"Michael Bayne", "mdb@samskivert.com",
|
||||
SiteIdentifier.DEFAULT_SITE_ID);
|
||||
rep.createUser(new Username("mdb"),
|
||||
Password.makeFromClear("foobar"), "Michael Bayne",
|
||||
"mdb@samskivert.com",
|
||||
SiteIdentifier.DEFAULT_SITE_ID);
|
||||
|
||||
scp.shutdown();
|
||||
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
/** A wrapper that provides access to the userstable. */
|
||||
protected Table _utable;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// $Id: UserUtil.java,v 1.10 2004/01/15 01:33:30 ray Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
import com.samskivert.util.Crypt;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* User related utility functions.
|
||||
*/
|
||||
public class UserUtil
|
||||
{
|
||||
/**
|
||||
* Generates a new random session identifier for the supplied user.
|
||||
*/
|
||||
public static String genAuthCode (User user)
|
||||
{
|
||||
// concatenate a bunch of secret stuff together
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(user.password);
|
||||
buf.append(System.currentTimeMillis());
|
||||
buf.append(Math.random());
|
||||
|
||||
// and MD5 hash it
|
||||
return StringUtil.md5hex(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the supplied username and password and returns the value
|
||||
* that would be stored in the user record were the password to be
|
||||
* updated via {@link User#setPassword}.
|
||||
*/
|
||||
public static String encryptPassword (String password)
|
||||
{
|
||||
return StringUtil.md5hex(password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts passwords the way we used to.
|
||||
*/
|
||||
public static String legacyEncrypt (String username, String password,
|
||||
boolean ignoreUserCase)
|
||||
{
|
||||
if (ignoreUserCase) {
|
||||
username = username.toLowerCase();
|
||||
}
|
||||
return Crypt.crypt(StringUtil.truncate(username, 2), password);
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: UserUtil password");
|
||||
System.exit(-1);
|
||||
}
|
||||
System.out.println("Encrypted password: " + encryptPassword(args[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// $Id: Username.java,v 1.3 2004/01/31 13:03:39 mdb Exp $
|
||||
|
||||
package com.samskivert.servlet.user;
|
||||
|
||||
/**
|
||||
* Allows us to require a valid username as a parameter without having to
|
||||
* do the checking ourselves.
|
||||
*/
|
||||
public class Username
|
||||
{
|
||||
/** The minimum allowable length of a username. */
|
||||
public static final int MINIMUM_USERNAME_LENGTH = 3;
|
||||
|
||||
/** The maximum allowable length of a username. */
|
||||
public static final int MAXIMUM_USERNAME_LENGTH = 12;
|
||||
|
||||
/** The regular expression defining valid names. */
|
||||
public static final String NAME_REGEX = "^[_A-Za-z0-9]*$";
|
||||
|
||||
/**
|
||||
* Creates a username instance. Usernames must consist only of
|
||||
* characters that match the following regular expression:
|
||||
* <code>[_A-Za-z0-9]+</code> and be longer than two characters.
|
||||
*/
|
||||
public Username (String username)
|
||||
throws InvalidUsernameException
|
||||
{
|
||||
// check length
|
||||
if (username.length() < MINIMUM_USERNAME_LENGTH) {
|
||||
throw new InvalidUsernameException("error.username_too_short");
|
||||
}
|
||||
if (username.length() > MAXIMUM_USERNAME_LENGTH) {
|
||||
throw new InvalidUsernameException("error.username_too_long");
|
||||
}
|
||||
|
||||
// check that it's only valid characters
|
||||
if (!username.matches(NAME_REGEX)) {
|
||||
throw new InvalidUsernameException("error.invalid_username");
|
||||
}
|
||||
|
||||
_username = username;
|
||||
}
|
||||
|
||||
/** Returns the text of this username. */
|
||||
public String getUsername ()
|
||||
{
|
||||
return _username;
|
||||
}
|
||||
|
||||
/** Returns a string representation of this instance. */
|
||||
public String toString ()
|
||||
{
|
||||
return _username;
|
||||
}
|
||||
|
||||
protected String _username;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides services for managing a database of users in a web
|
||||
application or suite of web applications.
|
||||
|
||||
<p> This is accomplished through the
|
||||
{@link com.samskivert.servlet.user.UserManager} which can be used to
|
||||
load user objects, create new users, update existing users and
|
||||
generally all of the user-related things that you'd like to do. The
|
||||
user manager manages cookies for persistent or session-only
|
||||
authentication and provides support for requiring that a user be
|
||||
authenticated in order to access a page.
|
||||
|
||||
<p> The user manager stores user information in an SQL database which
|
||||
is accomplished by the
|
||||
{@link com.samskivert.servlet.user.UserRepository} class. Presently,
|
||||
it assumes that it is making use of a MySQL database, but the
|
||||
dependencies are minimal. Eventually I'll modify the repository
|
||||
abstraction so that the database-specific support code can be chosen
|
||||
at runtime rather than compile time. Why I designed it the way it is
|
||||
in the first place, I can't imagine. I'm a bad monkey.
|
||||
|
||||
<p> The user table (and associated
|
||||
{@link com.samskivert.servlet.user.User} object) are inentionally
|
||||
minimal. The expectation is that application specific data will be
|
||||
stored and accessed via some other table that is keyed on the userid
|
||||
provided by these services.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// $Id: CookieUtil.java,v 1.3 2003/10/14 02:00:45 mdb Exp $
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Utility methods for dealing with cookies.
|
||||
*/
|
||||
public class CookieUtil
|
||||
{
|
||||
/**
|
||||
* Get the cookie of the specified name, or null if not found.
|
||||
*/
|
||||
public static Cookie getCookie (HttpServletRequest req, String name)
|
||||
{
|
||||
Cookie[] cookies = req.getCookies();
|
||||
if (cookies != null) {
|
||||
for (int ii=0, nn=cookies.length; ii < nn; ii++) {
|
||||
if (cookies[ii].getName().equals(name)) {
|
||||
return cookies[ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // not found
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the cookie for the cookie of the specified name,
|
||||
* or null if not found.
|
||||
*/
|
||||
public static String getCookieValue (HttpServletRequest req, String name)
|
||||
{
|
||||
Cookie c = getCookie(req, name);
|
||||
return (c == null) ? null : c.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cookie with the specified name.
|
||||
*/
|
||||
public static void clearCookie (HttpServletResponse rsp, String name)
|
||||
{
|
||||
Cookie c = new Cookie(name, "x");
|
||||
c.setPath("/");
|
||||
c.setMaxAge(0);
|
||||
rsp.addCookie(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the domain of the specified cookie to the server name
|
||||
* associated with the supplied request minus the hostname
|
||||
* (ie. <code>www.samskivert.com</code> becomes
|
||||
* <code>.samskivert.com</code>).
|
||||
*/
|
||||
public static void widenDomain (HttpServletRequest req, Cookie cookie)
|
||||
{
|
||||
String domain = req.getServerName();
|
||||
int didx = domain.indexOf(".");
|
||||
if (didx != -1) {
|
||||
domain = domain.substring(didx);
|
||||
}
|
||||
cookie.setDomain(domain);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// $Id: DataValidationException.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* A data validation exception is thrown when a value supplied in a form
|
||||
* element is not valid.
|
||||
*
|
||||
* @see ParameterUtil
|
||||
*/
|
||||
public class DataValidationException extends FriendlyException
|
||||
{
|
||||
public DataValidationException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// $Id: ExceptionMap.java,v 1.2 2004/02/25 13:17:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.util.ConfigUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* The exception map is used to map exceptions to error messages based on
|
||||
* a static, server-wide configuration.
|
||||
*
|
||||
* <p>The configuration file is loaded via the classpath. The file should
|
||||
* be named <code>exceptionmap.properties</code> and placed in the
|
||||
* classpath of the JVM in which the servlet is executed. The file should
|
||||
* contain colon-separated mappings from exception classes to friendly
|
||||
* error messages. For example:
|
||||
*
|
||||
* <pre>
|
||||
* # Exception mappings (lines beginning with # are ignored)
|
||||
* com.samskivert.servlet.util.FriendlyException: An error occurred while \
|
||||
* processing your request: {m}
|
||||
*
|
||||
* # lines ending with \ are continued on the next line
|
||||
* java.sql.SQLException: The database is currently unavailable. Please \
|
||||
* try your request again later.
|
||||
*
|
||||
* java.lang.Exception: An unexpected error occurred while processing \
|
||||
* your request. Please try again later.
|
||||
* </pre>
|
||||
*
|
||||
* The message associated with the exception will be substituted into the
|
||||
* error string in place of <code>{m}</code>. The exceptions should be
|
||||
* listed in order of most to least specific, as the first mapping for
|
||||
* which the exception to report is an instance of the listed exception
|
||||
* will be used.
|
||||
*
|
||||
* <p><em>Note:</em> These exception mappings will generally be used for
|
||||
* all requests (perhaps some day only for requests associated with a
|
||||
* particular application). Regardless, this error handling mechanism
|
||||
* should not be used for request specific errors. For example, an SQL
|
||||
* exception reporting a duplicate key should probably be caught and
|
||||
* reported specifically by the appropriate populator (it can still
|
||||
* leverage the pattern of inserting the error message into the context as
|
||||
* <code>"error"</code>) rather than relying on the default SQL exception
|
||||
* error message which is not likely to be meaningful for such a
|
||||
* situation.
|
||||
*/
|
||||
public class ExceptionMap
|
||||
{
|
||||
/**
|
||||
* Searches for the <code>exceptionmap.properties</code> file in the
|
||||
* classpath and loads it. If the file could not be found, an error is
|
||||
* reported and a default set of mappings is used.
|
||||
*/
|
||||
public static synchronized void init ()
|
||||
{
|
||||
// only initialize ourselves once
|
||||
if (_keys != null) {
|
||||
return;
|
||||
} else {
|
||||
_keys = new ArrayList();
|
||||
_values = new ArrayList();
|
||||
}
|
||||
|
||||
// first try loading the properties file without a leading slash
|
||||
ClassLoader cld = ExceptionMap.class.getClassLoader();
|
||||
InputStream config = ConfigUtil.getStream(PROPS_NAME, cld);
|
||||
if (config == null) {
|
||||
Log.warning("Unable to load " + PROPS_NAME + " from CLASSPATH.");
|
||||
|
||||
} else {
|
||||
// otherwise process ye old config file.
|
||||
try {
|
||||
// we'll do some serious jiggery pokery to leverage the
|
||||
// parsing implementation provided by
|
||||
// java.util.Properties. god bless method overloading
|
||||
Properties loader = new Properties() {
|
||||
public Object put (Object key, Object value)
|
||||
{
|
||||
_keys.add(key);
|
||||
_values.add(value);
|
||||
return key;
|
||||
}
|
||||
};
|
||||
loader.load(config);
|
||||
|
||||
// now cruise through and resolve the exceptions named as
|
||||
// keys and throw out any that don't appear to exist
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
String exclass = (String)_keys.get(i);
|
||||
try {
|
||||
Class cl = Class.forName(exclass);
|
||||
// replace the string with the class object
|
||||
_keys.set(i, cl);
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Unable to resolve exception class. " +
|
||||
"[class=" + exclass +
|
||||
", error=" + t + "].");
|
||||
_keys.remove(i);
|
||||
_values.remove(i);
|
||||
i--; // back on up a notch
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error reading exception mapping file: " + ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the supplied exception in the map and returns the most
|
||||
* specific error message available for exceptions of that class.
|
||||
*
|
||||
* @param ex The exception to resolve into an error message.
|
||||
*
|
||||
* @return The error message to which this exception maps (properly
|
||||
* populated with the message associated with this exception
|
||||
* instance).
|
||||
*/
|
||||
public static String getMessage (Throwable ex)
|
||||
{
|
||||
String msg = DEFAULT_ERROR_MSG;
|
||||
|
||||
for (int i = 0; i < _keys.size(); i++) {
|
||||
Class cl = (Class)_keys.get(i);
|
||||
if (cl.isInstance(ex)) {
|
||||
msg = (String)_values.get(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtil.replace(msg, MESSAGE_MARKER, ex.getMessage());
|
||||
}
|
||||
|
||||
public static void main (String[] args)
|
||||
{
|
||||
ExceptionMap map = new ExceptionMap();
|
||||
System.out.println(ExceptionMap.getMessage(new Exception("Test error")));
|
||||
}
|
||||
|
||||
protected static List _keys;
|
||||
protected static List _values;
|
||||
|
||||
// initialize ourselves
|
||||
static { init(); }
|
||||
|
||||
protected static final String PROPS_NAME = "exceptionmap.properties";
|
||||
protected static final String DEFAULT_ERROR_MSG = "Error: {m}";
|
||||
protected static final String MESSAGE_MARKER = "{m}";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// $Id: FriendlyException.java,v 1.1 2001/10/31 09:44:22 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* The friendly exception provides a mechanism by which a servlet or
|
||||
* underlying code can abort its processing and report a human readable
|
||||
* error to the servlet framework.
|
||||
*/
|
||||
public class FriendlyException extends Exception
|
||||
{
|
||||
public FriendlyException (String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// $Id: HTMLUtil.java,v 1.2 2002/12/30 04:52:36 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* HTML related utility functions.
|
||||
*/
|
||||
public class HTMLUtil
|
||||
{
|
||||
/**
|
||||
* Converts instances of <code><, >, & and "</code> into their
|
||||
* entified equivalents: <code><, >, & and "</code>.
|
||||
* These characters are mentioned in the HTML spec as being common
|
||||
* candidates for entification.
|
||||
*
|
||||
* @return the entified string.
|
||||
*/
|
||||
public static String entify (String text)
|
||||
{
|
||||
// this could perhaps be done more efficiently, but this function
|
||||
// is not likely to be called on large quantities of text
|
||||
// (first we turn the entified versions normal so that if text is
|
||||
// repeatedly run through this it doesn't keep changing successive
|
||||
// &'s into "&".
|
||||
text = StringUtil.replace(text, """, "\"");
|
||||
text = StringUtil.replace(text, ">", ">");
|
||||
text = StringUtil.replace(text, "<", "<");
|
||||
text = StringUtil.replace(text, "&", "&");
|
||||
text = StringUtil.replace(text, "&", "&");
|
||||
text = StringUtil.replace(text, "<", "<");
|
||||
text = StringUtil.replace(text, ">", ">");
|
||||
text = StringUtil.replace(text, "\"", """);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a <p> tag between every two consecutive newlines.
|
||||
*/
|
||||
public static String makeParagraphs (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
// handle both line ending formats
|
||||
text = StringUtil.replace(text, "\n\n", "\n<p>\n");
|
||||
text = StringUtil.replace(text, "\r\n\r\n", "\r\n<p>\r\n");
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a <br> tag before every newline.
|
||||
*/
|
||||
public static String makeLinear (String text)
|
||||
{
|
||||
if (text == null) {
|
||||
return text;
|
||||
}
|
||||
// handle both line ending formats
|
||||
text = StringUtil.replace(text, "\n", "<br>\n");
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does some simple HTML markup, matching bare image URLs and wrapping
|
||||
* them in image tags, matching other URLs and wrapping them in href
|
||||
* tags, and wrapping * prefixed lists into ul-style HTML lists.
|
||||
*/
|
||||
public static String simpleFormat (String text)
|
||||
{
|
||||
// first replace the image and other URLs
|
||||
Matcher m = _url.matcher(text);
|
||||
StringBuffer tbuf = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String match = m.group();
|
||||
String lmatch = match.toLowerCase();
|
||||
if (lmatch.endsWith(".png") ||
|
||||
lmatch.endsWith(".jpg") ||
|
||||
lmatch.endsWith(".gif")) {
|
||||
match = "<img src=\"" + match + "\">";
|
||||
} else {
|
||||
match = "<a href=\"" + match + "\">" + match + "</a>";
|
||||
}
|
||||
m.appendReplacement(tbuf, match);
|
||||
}
|
||||
m.appendTail(tbuf);
|
||||
|
||||
// then tackle the *s
|
||||
text = tbuf.toString();
|
||||
m = _star.matcher(text);
|
||||
tbuf.setLength(0);
|
||||
while (m.find()) {
|
||||
String match = m.group();
|
||||
int start = m.start();
|
||||
if (start == 0 || (start >= 2 && text.charAt(start-1) == '\n' &&
|
||||
text.charAt(start-2) == '\n')) {
|
||||
m.appendReplacement(tbuf, "<ul><li>");
|
||||
} else {
|
||||
m.appendReplacement(tbuf, "<li>");
|
||||
}
|
||||
}
|
||||
m.appendTail(tbuf);
|
||||
text = tbuf.toString();
|
||||
|
||||
// finally close the </ul>s and paragraphy
|
||||
String[] paras = _blank.split(text);
|
||||
tbuf.setLength(0);
|
||||
for (int ii = 0; ii < paras.length; ii++) {
|
||||
String para = paras[ii].trim();
|
||||
if (para.startsWith("<ul>")) {
|
||||
tbuf.append(para).append(" </ul>\n");
|
||||
} else {
|
||||
tbuf.append("<p> ").append(para).append(" </p>\n");
|
||||
}
|
||||
}
|
||||
|
||||
return tbuf.toString().trim();
|
||||
}
|
||||
|
||||
protected static Pattern _url =
|
||||
Pattern.compile("http://\\S+", Pattern.MULTILINE);
|
||||
protected static Pattern _star = Pattern.compile("^\\*", Pattern.MULTILINE);
|
||||
protected static Pattern _blank = Pattern.compile("^$", Pattern.MULTILINE);
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
//
|
||||
// $Id: ParameterUtil.java,v 1.14 2004/02/25 13:17:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.util.ArrayIntSet;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Utility functions for fetching and manipulating request parameters
|
||||
* (form fields).
|
||||
*/
|
||||
public class ParameterUtil
|
||||
{
|
||||
/**
|
||||
* An interface for validating form parameters.
|
||||
*/
|
||||
public static interface ParameterValidator
|
||||
{
|
||||
public void validateParameter (String name, String value)
|
||||
throws DataValidationException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter
|
||||
* does not exist, either null or the empty string will be returned
|
||||
* depending on the value of the <code>returnNull</code> parameter.
|
||||
*/
|
||||
public static String getParameter (
|
||||
HttpServletRequest req, String name, boolean returnNull)
|
||||
{
|
||||
String value = req.getParameter(name);
|
||||
if (value == null) {
|
||||
return returnNull ? null : "";
|
||||
} else {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* a float. If the parameter does not exist or is not a well-formed
|
||||
* float, a data validation exception is thrown with the supplied
|
||||
* message.
|
||||
*/
|
||||
public static float requireFloatParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseFloatParameter(
|
||||
getParameter(req, name, false), invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* an integer. If the parameter does not exist or is not a well-formed
|
||||
* integer, a data validation exception is thrown with the supplied
|
||||
* message.
|
||||
*/
|
||||
public static int requireIntParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseIntParameter(
|
||||
getParameter(req, name, false), invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* an integer. If the parameter does not exist or is not a well-formed
|
||||
* integer, a data validation exception is thrown with the supplied
|
||||
* message.
|
||||
*/
|
||||
public static int requireIntParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage,
|
||||
ParameterValidator validator)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
validator.validateParameter(name, value);
|
||||
return parseIntParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* an integer. If the parameter does not exist or is not a well-formed
|
||||
* integer, a data validation exception is thrown with the supplied
|
||||
* message.
|
||||
*/
|
||||
public static int requireIntParameter (
|
||||
HttpServletRequest req, String name, int low, int high,
|
||||
String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return requireIntParameter(req, name, invalidDataMessage,
|
||||
new IntRangeValidator(low, high, invalidDataMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the values from the request with the specified name and
|
||||
* converts them to an IntSet. If the parameter does not exist or is
|
||||
* not a well-formed integer, a data validation exception is thrown
|
||||
* with the supplied message.
|
||||
*/
|
||||
public static ArrayIntSet getIntParameters (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
ArrayIntSet ints = new ArrayIntSet();
|
||||
String[] values = req.getParameterValues(name);
|
||||
if (values != null) {
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (!StringUtil.isBlank(values[ii])) {
|
||||
ints.add(parseIntParameter(values[ii], invalidDataMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all the values from the request with the specified name and
|
||||
* converts them to a HashSet.
|
||||
*/
|
||||
public static HashSet getParameters (HttpServletRequest req, String name)
|
||||
throws DataValidationException
|
||||
{
|
||||
HashSet set = new HashSet();
|
||||
String[] values = req.getParameterValues(name);
|
||||
if (values != null) {
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (!StringUtil.isBlank(values[ii])) {
|
||||
set.add(values[ii]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter does
|
||||
* not exist, <code>defval</code> is returned.
|
||||
*/
|
||||
public static String getParameter (
|
||||
HttpServletRequest req, String name, String defval)
|
||||
{
|
||||
String value = req.getParameter(name);
|
||||
return StringUtil.isBlank(value) ? defval : value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* an integer. If the parameter does not exist, <code>defval</code> is
|
||||
* returned. If the parameter is not a well-formed integer, a data
|
||||
* validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static int getIntParameter (
|
||||
HttpServletRequest req, String name, int defval,
|
||||
String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return defval;
|
||||
}
|
||||
return parseIntParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* a long. If the parameter does not exist, <code>defval</code> is
|
||||
* returned. If the parameter is not a well-formed integer, a data
|
||||
* validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static long getLongParameter (
|
||||
HttpServletRequest req, String name, long defval,
|
||||
String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return defval;
|
||||
}
|
||||
return parseLongParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request. If the parameter
|
||||
* does not exist, a data validation exception is thrown with the
|
||||
* supplied message.
|
||||
*/
|
||||
public static String requireParameter (
|
||||
HttpServletRequest req, String name, String missingDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, true);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
throw new DataValidationException(missingDataMessage);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and ensures that
|
||||
* it is no longer than maxLength.
|
||||
*
|
||||
* Note that use of this method could be dangerous. If the specified
|
||||
* HttpServletRequest is used to pre-fill in values on a form, it will
|
||||
* not know to use the truncated version. A user may enter a version that
|
||||
* is too long, your code will see a truncated version, but then the
|
||||
* user will see on the page again the full-length reproduction of what
|
||||
* they typed in. Be careful.
|
||||
*/
|
||||
public static String requireParameter (
|
||||
HttpServletRequest req, String name, String missingDataMessage,
|
||||
int maxLength)
|
||||
throws DataValidationException
|
||||
{
|
||||
return StringUtil.truncate(
|
||||
requireParameter(req, name, missingDataMessage), maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* a date. The value of the parameter should be a date formatted like
|
||||
* so: 2001-12-25. If the parameter does not exist or is not a
|
||||
* well-formed date, a data validation exception is thrown with the
|
||||
* supplied message.
|
||||
*/
|
||||
public static Date requireDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
return parseDateParameter(getParameter(req, name, false),
|
||||
invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the supplied parameter from the request and converts it to
|
||||
* a date. The value of the parameter should be a date formatted like
|
||||
* so: 2001-12-25. If the parameter does not exist, null is
|
||||
* returned. If the parameter is not a well-formed date, a data
|
||||
* validation exception is thrown with the supplied message.
|
||||
*/
|
||||
public static Date getDateParameter (
|
||||
HttpServletRequest req, String name, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
String value = getParameter(req, name, false);
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
return parseDateParameter(value, invalidDataMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is set in the request.
|
||||
*
|
||||
* @return true if the specified parameter is set in the request
|
||||
* context, false otherwise.
|
||||
*/
|
||||
public static boolean isSet (HttpServletRequest req, String name)
|
||||
{
|
||||
return isSet(req, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is set to a non-blank value
|
||||
* in the request, false if the value is blank, or
|
||||
* <code>defaultValue</code> if the parameter is unspecified.
|
||||
*
|
||||
* @return true if the specified parameter is set to a non-blank value
|
||||
* in the request, false if the value is blank, or
|
||||
* <code>defaultValue</code> if the parameter is unspecified.
|
||||
*/
|
||||
public static boolean isSet (
|
||||
HttpServletRequest req, String name, boolean defaultValue)
|
||||
{
|
||||
String value = getParameter(req, name, true);
|
||||
return (value == null) ? defaultValue :
|
||||
!StringUtil.isBlank(req.getParameter(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified parameter is equal to the supplied
|
||||
* value. If the parameter is not set in the request, false is
|
||||
* returned.
|
||||
*
|
||||
* @param name The parameter whose value should be compared with the
|
||||
* supplied value.
|
||||
* @param value The value to which the parameter may be equal. This
|
||||
* should not be null.
|
||||
*
|
||||
* @return true if the specified parameter is equal to the supplied
|
||||
* parameter, false otherwise.
|
||||
*/
|
||||
public static boolean parameterEquals (
|
||||
HttpServletRequest req, String name, String value)
|
||||
{
|
||||
return value.equals(getParameter(req, name, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse integer values.
|
||||
*/
|
||||
protected static int parseIntParameter (
|
||||
String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse long values.
|
||||
*/
|
||||
protected static long parseLongParameter (
|
||||
String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse a float value.
|
||||
*/
|
||||
protected static float parseFloatParameter (
|
||||
String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return Float.parseFloat(value);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to parse a date.
|
||||
*/
|
||||
protected static Date parseDateParameter (
|
||||
String value, String invalidDataMessage)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
return _dparser.parse(value);
|
||||
} catch (ParseException pe) {
|
||||
throw new DataValidationException(invalidDataMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure integers are within a range.
|
||||
*/
|
||||
protected static class IntRangeValidator implements ParameterValidator
|
||||
{
|
||||
/**
|
||||
*/
|
||||
public IntRangeValidator (int low, int high, String outOfRangeError)
|
||||
{
|
||||
_low = low;
|
||||
_high = high;
|
||||
_err = outOfRangeError;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void validateParameter (String name, String value)
|
||||
throws DataValidationException
|
||||
{
|
||||
try {
|
||||
int ivalue = Integer.parseInt(value);
|
||||
if ((ivalue >= _low) && (ivalue <= _high)) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
throw new DataValidationException(_err);
|
||||
}
|
||||
|
||||
protected int _low, _high;
|
||||
protected String _err;
|
||||
}
|
||||
|
||||
/** We use this to parse dates in requireDateParameter(). */
|
||||
protected static SimpleDateFormat _dparser =
|
||||
new SimpleDateFormat("yyyy-MM-dd");
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// $Id: RequestUtils.java,v 1.10 2004/02/25 13:17:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A repository of utility functions related to HTTP servlet stuff.
|
||||
*/
|
||||
public class RequestUtils
|
||||
{
|
||||
/**
|
||||
* Reconstructs the current location (URL) from the request and
|
||||
* servlet configuration (which is the only way to know which server
|
||||
* we're on because that's not provided in the request) and returns it
|
||||
* URL encoded so that it can be substituted into another URL.
|
||||
*
|
||||
* @return The URL encoded URL that represents our current location.
|
||||
*/
|
||||
public static String getLocationEncoded (HttpServletRequest req)
|
||||
{
|
||||
return StringUtil.encode(getLocation(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the current location (URL) from the request and
|
||||
* servlet configuration (which is the only way to know which server
|
||||
* we're on because that's not provided in the request) and returns
|
||||
* it.
|
||||
*
|
||||
* @return The URL that represents our current location.
|
||||
*/
|
||||
public static String getLocation (HttpServletRequest req)
|
||||
{
|
||||
StringBuffer rurl = req.getRequestURL();
|
||||
String qs = req.getQueryString();
|
||||
if (qs != null) {
|
||||
rurl.append("?").append(qs);
|
||||
}
|
||||
return rurl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreates the URL used to make the supplied request, replacing the
|
||||
* server part of the URL with the supplied server name.
|
||||
*/
|
||||
public static String rehostLocation (
|
||||
HttpServletRequest req, String servername)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
String csname = req.getServerName();
|
||||
int csidx = buf.indexOf(csname);
|
||||
if (csidx != -1) {
|
||||
buf.delete(csidx, csidx + csname.length());
|
||||
buf.insert(csidx, servername);
|
||||
}
|
||||
String query = req.getQueryString();
|
||||
if (!StringUtil.isBlank(query)) {
|
||||
buf.append("?").append(query);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the server, port and servlet context path to the supplied
|
||||
* path, resulting in a fully-formed URL for requesting a servlet.
|
||||
*/
|
||||
public static String getServletURL (HttpServletRequest req, String path)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
String sname = req.getServletPath();
|
||||
buf.delete(buf.length() - sname.length(), buf.length());
|
||||
if (!path.startsWith("/")) {
|
||||
buf.append("/");
|
||||
}
|
||||
buf.append(path);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstructs the request URL including query parameters. <em>Note:</em>
|
||||
* the output of this method is purely for logging purposes only, thus POST
|
||||
* parameters are shown as if they were GET parameters and parameters are
|
||||
* <em>not</em> URL encoded.
|
||||
*/
|
||||
public static String reconstructURL (HttpServletRequest req)
|
||||
{
|
||||
StringBuffer buf = req.getRequestURL();
|
||||
Map map = req.getParameterMap();
|
||||
if (map.size() > 0) {
|
||||
buf.append("?");
|
||||
for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
buf.append(entry.getKey()).append("=");
|
||||
String[] values = (String[])entry.getValue();
|
||||
if (values.length == 1) {
|
||||
buf.append(values[0]);
|
||||
} else {
|
||||
buf.append("(");
|
||||
for (int ii = 0; ii < values.length; ii++) {
|
||||
if (ii > 0) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(values[ii]);
|
||||
}
|
||||
buf.append(")");
|
||||
}
|
||||
if (iter.hasNext()) {
|
||||
buf.append("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// $Id: ServiceWaiter.java,v 1.4 2003/08/15 03:07:52 ray Exp $
|
||||
|
||||
package com.samskivert.servlet.util;
|
||||
|
||||
/**
|
||||
* Extends the basic ServiceWaiter to be useful for servlets.
|
||||
*/
|
||||
public class ServiceWaiter extends com.samskivert.util.ServiceWaiter
|
||||
{
|
||||
/** Timeout to specify when you don't want a timeout. Use at your own
|
||||
* risk. */
|
||||
public static final int NO_TIMEOUT = -1;
|
||||
|
||||
/**
|
||||
* Construct a ServiceWaiter with the default (30 second) timeout.
|
||||
*/
|
||||
public ServiceWaiter ()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ServiceWaiter with the specified timeout.
|
||||
*
|
||||
* @param timeout the timeout, in seconds.
|
||||
*/
|
||||
public ServiceWaiter (int timeout)
|
||||
{
|
||||
super(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks waiting for the response.
|
||||
*
|
||||
* @return true if a success response was posted, false if a failure
|
||||
* repsonse was posted.
|
||||
*/
|
||||
public boolean awaitFriendlyResponse (String friendlyText)
|
||||
throws FriendlyException
|
||||
{
|
||||
try {
|
||||
return waitForResponse();
|
||||
|
||||
} catch (TimeoutException te) {
|
||||
throw new FriendlyException(friendlyText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
|
||||
$Id: package.html,v 1.1 2001/08/12 01:34:31 mdb Exp $
|
||||
|
||||
samskivert library - useful routines for java programs
|
||||
Copyright (C) 2001 Michael Bayne
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published
|
||||
by the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
Provides servlet-related utilities.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// $Id$
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import com.samskivert.util.ResultListener;
|
||||
|
||||
/**
|
||||
* Dispatches a {@link ResultListener}'s callbacks on the AWT thread
|
||||
* regardless of what thread on which they were originally dispatched.
|
||||
*/
|
||||
public class AWTResultListener implements ResultListener
|
||||
{
|
||||
/**
|
||||
* Creates an AWT result listener that will dispatch results to the
|
||||
* supplied target.
|
||||
*/
|
||||
public AWTResultListener (ResultListener target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestCompleted (final Object result)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
_target.requestCompleted(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void requestFailed (final Exception cause)
|
||||
{
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
_target.requestFailed(cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** The result listener for which we are proxying. */
|
||||
protected ResultListener _target;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// $Id: AbsoluteLayout.java,v 1.1 2002/09/25 23:49:10 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Insets;
|
||||
import java.awt.LayoutManager2;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Used to lay out components at absolute coordinates. This layout manager
|
||||
* will not gracefully deal with not having room to layout the components
|
||||
* at the coordinates specified, so be sure only to use it in very
|
||||
* controlled circumstances.
|
||||
*
|
||||
* <p> Components should be added with a {@link Point} or {@link
|
||||
* Rectangle} object as their constraints. If their constraints are a
|
||||
* {@link Point} they will be laid out at those coordinates with their
|
||||
* preferred size. If it is a {@link Rectangle} they will be laid out in
|
||||
* those exact bounds.
|
||||
*/
|
||||
public class AbsoluteLayout
|
||||
implements LayoutManager2
|
||||
{
|
||||
// documentation inherited from interface
|
||||
public void addLayoutComponent (String name, Component comp)
|
||||
{
|
||||
throw new RuntimeException("You must use " +
|
||||
"addLayoutComponent(Component, Object)");
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void addLayoutComponent (Component comp, Object constraints)
|
||||
{
|
||||
if (!(constraints instanceof Point ||
|
||||
constraints instanceof Rectangle)) {
|
||||
throw new IllegalArgumentException("Constraints must be " +
|
||||
"Point or Rectangle");
|
||||
}
|
||||
_constraints.put(comp, constraints);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void removeLayoutComponent (Component comp)
|
||||
{
|
||||
_constraints.remove(comp);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension preferredLayoutSize (Container parent)
|
||||
{
|
||||
Rectangle rect = new Rectangle();
|
||||
Rectangle temp = new Rectangle();
|
||||
int pcount = parent.getComponentCount();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
Component comp = parent.getComponent(ii);
|
||||
if (!comp.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object constr = _constraints.get(comp);
|
||||
if (constr == null) {
|
||||
Log.warning("No constraints for child!? [cont=" + parent +
|
||||
", comp=" + comp + "].");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constr instanceof Rectangle) {
|
||||
rect.add((Rectangle)constr);
|
||||
} else {
|
||||
Point p = (Point)constr;
|
||||
Dimension d = comp.getPreferredSize();
|
||||
temp.setBounds(p.x, p.y, d.width, d.height);
|
||||
rect.add(temp);
|
||||
}
|
||||
}
|
||||
|
||||
Dimension dims = new Dimension(rect.width, rect.height);
|
||||
|
||||
// account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
dims.width += insets.left + insets.right;
|
||||
dims.height += insets.top + insets.bottom;
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension minimumLayoutSize (Container parent)
|
||||
{
|
||||
// we don't do no fancy business
|
||||
return preferredLayoutSize(parent);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Dimension maximumLayoutSize (Container parent)
|
||||
{
|
||||
// we don't do no fancy business
|
||||
return preferredLayoutSize(parent);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void layoutContainer (Container parent)
|
||||
{
|
||||
Insets insets = parent.getInsets();
|
||||
int pcount = parent.getComponentCount();
|
||||
for (int ii = 0; ii < pcount; ii++) {
|
||||
Component comp = parent.getComponent(ii);
|
||||
if (!comp.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object constr = _constraints.get(comp);
|
||||
if (constr == null) {
|
||||
Log.warning("No constraints for child!? [cont=" + parent +
|
||||
", comp=" + comp + "].");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constr instanceof Rectangle) {
|
||||
Rectangle r = (Rectangle)constr;
|
||||
comp.setBounds(insets.left + r.x, insets.top + r.y,
|
||||
r.width, r.height);
|
||||
|
||||
} else {
|
||||
Point p = (Point)constr;
|
||||
Dimension d = comp.getPreferredSize();
|
||||
comp.setBounds(insets.left + p.x, insets.top + p.y,
|
||||
d.width, d.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void invalidateLayout (Container target)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public float getLayoutAlignmentX (Container target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public float getLayoutAlignmentY (Container target)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected HashMap _constraints = new HashMap();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// $Id: CollapsibleList.java,v 1.4 2002/05/20 18:06:07 shaper Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.ListModel;
|
||||
|
||||
/**
|
||||
* Displays a list of components in sections (with section headers) that
|
||||
* can be collapsed. Each section of a list uses a {@link JList} instance
|
||||
* to render the section elements.
|
||||
*/
|
||||
public class CollapsibleList extends JPanel
|
||||
{
|
||||
/**
|
||||
* Constructs an empty collapsible list.
|
||||
*/
|
||||
public CollapsibleList ()
|
||||
{
|
||||
// set up our layout manager
|
||||
VGroupLayout gl = new VGroupLayout(VGroupLayout.NONE);
|
||||
gl.setOffAxisPolicy(VGroupLayout.STRETCH);
|
||||
gl.setJustification(VGroupLayout.TOP);
|
||||
gl.setOffAxisJustification(VGroupLayout.LEFT);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a collapsible list with the supplied section labels and
|
||||
* models.
|
||||
*/
|
||||
public CollapsibleList (String[] sections, ListModel[] models)
|
||||
{
|
||||
this(); // set up our layout manager
|
||||
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
addSection(sections[i], models[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of sections.
|
||||
*/
|
||||
public int getSectionCount ()
|
||||
{
|
||||
return getComponentCount()/2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a section to this collapsible list.
|
||||
*
|
||||
* @param label the title of the section.
|
||||
* @param model the list model to use for the new section.
|
||||
*
|
||||
* @return the index of the newly added section.
|
||||
*/
|
||||
public int addSection (String label, ListModel model)
|
||||
{
|
||||
add(new JLabel(label));
|
||||
add(new JList(model));
|
||||
return getSectionCount()-1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label object associated with the title of the specified
|
||||
* section.
|
||||
*/
|
||||
public JLabel getSectionLabel (int index)
|
||||
{
|
||||
return (JLabel)getComponent(index*2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list object associated with the specified section.
|
||||
*/
|
||||
public JList getSectionList (int index)
|
||||
{
|
||||
return (JList)getComponent(index*2+1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the collapsed state of the specified section.
|
||||
*/
|
||||
public void toggleCollapsed (int index)
|
||||
{
|
||||
JList list = getSectionList(index);
|
||||
list.setVisible(!list.isVisible());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// $Id: CollapsiblePanel.java,v 1.8 2003/06/26 05:55:47 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* A panel that contains a button which will collapse the rest of the content.
|
||||
*/
|
||||
public class CollapsiblePanel extends JPanel
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* Construct a collapsible panel with the specified button as the
|
||||
* trigger. The text of the button will be used as the triggertext.
|
||||
*/
|
||||
public CollapsiblePanel (JButton trigger)
|
||||
{
|
||||
setTrigger(trigger, null, null);
|
||||
setTriggerContainer(trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a collapsible panel with the specified button text.
|
||||
*/
|
||||
public CollapsiblePanel (String triggertext)
|
||||
{
|
||||
this(new JButton(triggertext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a collapsible panel to which the trigger button will be
|
||||
* added later.
|
||||
*/
|
||||
public CollapsiblePanel ()
|
||||
{
|
||||
VGroupLayout gl = new VGroupLayout(VGroupLayout.NONE);
|
||||
gl.setOffAxisPolicy(VGroupLayout.STRETCH);
|
||||
gl.setGap(0);
|
||||
gl.setJustification(VGroupLayout.TOP);
|
||||
gl.setOffAxisJustification(VGroupLayout.LEFT);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a component which contains the trigger button. The simple case
|
||||
* is to just set the trigger button as this component.
|
||||
*/
|
||||
public void setTriggerContainer (JComponent comp)
|
||||
{
|
||||
setTriggerContainer(comp, new JPanel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a component which contains the trigger button.
|
||||
*/
|
||||
public void setTriggerContainer (JComponent comp, JPanel content)
|
||||
{
|
||||
// these are our only two components.
|
||||
add(comp);
|
||||
add(_content = content);
|
||||
|
||||
// When the content is shown, make sure it's scrolled visible
|
||||
_content.addComponentListener(new ComponentAdapter() {
|
||||
public void componentShown (ComponentEvent event)
|
||||
{
|
||||
// we can't do it just yet, the content doesn't know its size
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
// The content is offset a bit from the trigger
|
||||
// but we want the trigger to show up, so we add
|
||||
// in point 0,0
|
||||
Rectangle r = _content.getBounds();
|
||||
r.add(0, 0);
|
||||
scrollRectToVisible(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// and start us out not showing
|
||||
setCollapsed(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the trigger button.
|
||||
*/
|
||||
public void setTrigger (AbstractButton trigger,
|
||||
Icon collapsed, Icon uncollapsed)
|
||||
{
|
||||
_trigger = trigger;
|
||||
_trigger.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
_trigger.setHorizontalTextPosition(SwingConstants.RIGHT);
|
||||
_downIcon = collapsed;
|
||||
_upIcon = uncollapsed;
|
||||
_trigger.addActionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gap between the trigger button and the rest of the content.
|
||||
* Can be negative for an overlapping effect.
|
||||
*/
|
||||
public void setGap (int gap)
|
||||
{
|
||||
((VGroupLayout) getLayout()).setGap(gap);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content panel for filling in with sweet content goodness.
|
||||
*/
|
||||
public JPanel getContent ()
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
// documentation from interface ActionListener
|
||||
public void actionPerformed (ActionEvent e)
|
||||
{
|
||||
if (e.getSource() == _trigger) {
|
||||
setCollapsed(!isCollapsed());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the panel collapsed?
|
||||
*/
|
||||
public boolean isCollapsed ()
|
||||
{
|
||||
return !_content.isVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collapsion state.
|
||||
*/
|
||||
public void setCollapsed (boolean collapse)
|
||||
{
|
||||
if (collapse) {
|
||||
_content.setVisible(false);
|
||||
_trigger.setIcon(_downIcon);
|
||||
|
||||
} else {
|
||||
_content.setVisible(true);
|
||||
_trigger.setIcon(_upIcon);
|
||||
}
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
/** The button that triggers collapsion. */
|
||||
protected AbstractButton _trigger;
|
||||
|
||||
/** The icons for collapsed and uncollapsed. */
|
||||
protected Icon _upIcon, _downIcon;
|
||||
|
||||
/** The who in the what now? */
|
||||
protected JPanel _content;
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
//
|
||||
// $Id: ComboButtonBox.java,v 1.5 2004/02/25 13:17:41 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.ComboBoxModel;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* Used to display a horizontal or vertical array of buttons, out of which
|
||||
* only one is selectable at a time (which will be represented by
|
||||
* rendering it with an indented border, whereas the other buttons will
|
||||
* render with an extruded border.
|
||||
*/
|
||||
public class ComboButtonBox extends JPanel
|
||||
implements SwingConstants, ListDataListener, MouseListener
|
||||
{
|
||||
/**
|
||||
* Constructs a button box with the specified orientation (either
|
||||
* {@link #HORIZONTAL} or {@link #VERTICAL}.
|
||||
*/
|
||||
public ComboButtonBox (int orientation)
|
||||
{
|
||||
this(orientation, new DefaultComboBoxModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a button box with the specified orientation (either
|
||||
* {@link #HORIZONTAL} or {@link #VERTICAL}. The supplied model will
|
||||
* be used to populate the buttons (see {@link #setModel} for more
|
||||
* details).
|
||||
*/
|
||||
public ComboButtonBox (int orientation, ComboBoxModel model)
|
||||
{
|
||||
// set up our layout
|
||||
setOrientation(orientation);
|
||||
|
||||
// set up our contents
|
||||
setModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation of the box (either {@link #HORIZONTAL} or
|
||||
* {@link #VERTICAL}.
|
||||
*/
|
||||
public void setOrientation (int orientation)
|
||||
{
|
||||
GroupLayout gl = (orientation == HORIZONTAL) ?
|
||||
(GroupLayout)new HGroupLayout() : new VGroupLayout();
|
||||
gl.setPolicy(GroupLayout.EQUALIZE);
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the button box with a data model which it will display. If
|
||||
* the model contains {@link Image} objects, they will be used to make
|
||||
* icons for the buttons. Otherwise the button text will contain the
|
||||
* string representation of the elements in the model.
|
||||
*/
|
||||
public void setModel (ComboBoxModel model)
|
||||
{
|
||||
// if we had a previous model, unregister ourselves from it
|
||||
if (_model != null) {
|
||||
_model.removeListDataListener(this);
|
||||
}
|
||||
|
||||
// subscribe to our new model
|
||||
_model = model;
|
||||
_model.addListDataListener(this);
|
||||
|
||||
// rebuild the list
|
||||
removeAll();
|
||||
addButtons(0, _model.getSize());
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void setEnabled (boolean enabled)
|
||||
{
|
||||
super.setEnabled(enabled);
|
||||
|
||||
int ccount = getComponentCount();
|
||||
for (int i = 0; i < ccount; i++) {
|
||||
getComponent(i).setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model in use by the button box.
|
||||
*/
|
||||
public ComboBoxModel getModel ()
|
||||
{
|
||||
return _model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the selected component. A value of -1 will clear
|
||||
* the selection.
|
||||
*/
|
||||
public void setSelectedIndex (int selidx)
|
||||
{
|
||||
// update the display
|
||||
updateSelection(selidx);
|
||||
|
||||
// let the model know what's up
|
||||
Object item = (selidx == -1) ? null : _model.getElementAt(selidx);
|
||||
_model.setSelectedItem(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the selected item.
|
||||
*/
|
||||
public int getSelectedIndex ()
|
||||
{
|
||||
return _selectedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the command that will be used when generating action
|
||||
* events (which is done when the selection changes).
|
||||
*/
|
||||
public void setActionCommand (String actionCommand)
|
||||
{
|
||||
_actionCommand = actionCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to our list of entities to be notified when the
|
||||
* selection changes.
|
||||
*/
|
||||
public void addActionListener (ActionListener l)
|
||||
{
|
||||
listenerList.add(ActionListener.class, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from the list.
|
||||
*/
|
||||
public void removeActionListener (ActionListener l)
|
||||
{
|
||||
listenerList.remove(ActionListener.class, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies our listeners when the selection changed.
|
||||
*/
|
||||
protected void fireActionPerformed ()
|
||||
{
|
||||
// guaranteed to return a non-null array
|
||||
Object[] listeners = listenerList.getListenerList();
|
||||
// process the listeners last to first, notifying those that are
|
||||
// interested in this event
|
||||
for (int i = listeners.length-2; i >= 0; i -= 2) {
|
||||
if (listeners[i] == ActionListener.class) {
|
||||
// lazily create the event:
|
||||
if (_actionEvent == null) {
|
||||
_actionEvent = new ActionEvent(
|
||||
this, ActionEvent.ACTION_PERFORMED, _actionCommand);
|
||||
}
|
||||
((ActionListener)listeners[i+1]).actionPerformed(_actionEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void contentsChanged (ListDataEvent e)
|
||||
{
|
||||
// if this update is informing us of a new selection, reflect that
|
||||
// in the UI
|
||||
int start = e.getIndex0(), count = start - e.getIndex1() + 1;
|
||||
if (start == -1) {
|
||||
// figure out the selected index
|
||||
int selidx = -1;
|
||||
Object eitem = _model.getSelectedItem();
|
||||
if (eitem != null) {
|
||||
int ecount = _model.getSize();
|
||||
for (int i = 0; i < ecount; i++) {
|
||||
if (eitem == _model.getElementAt(i)) {
|
||||
selidx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and update it
|
||||
updateSelection(selidx);
|
||||
|
||||
} else {
|
||||
// replace the buttons in this range
|
||||
removeButtons(start, count);
|
||||
addButtons(start, count);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void intervalAdded (ListDataEvent e)
|
||||
{
|
||||
int start = e.getIndex0(), count = e.getIndex1() - start + 1;
|
||||
|
||||
// adjust the selected index
|
||||
if (_selectedIndex >= start) {
|
||||
_selectedIndex += count;
|
||||
}
|
||||
|
||||
// add buttons for the new interval
|
||||
addButtons(start, count);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void intervalRemoved (ListDataEvent e)
|
||||
{
|
||||
// remove the buttons in the specified interval
|
||||
int start = e.getIndex0(), count = e.getIndex1() - start + 1;
|
||||
removeButtons(start, count);
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseClicked (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mousePressed (MouseEvent e)
|
||||
{
|
||||
// ignore if we're not enabled
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keep track of the selected button
|
||||
_selectedButton = (JLabel)e.getSource();
|
||||
// if the selected button is already selected, ignore the click
|
||||
if (_selectedButton.getBorder() == SELECTED_BORDER) {
|
||||
_selectedButton = null;
|
||||
} else {
|
||||
_selectedButton.setBorder(SELECTED_BORDER);
|
||||
_selectedButton.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseReleased (MouseEvent e)
|
||||
{
|
||||
// if the mouse was released within the bounds of the button, go
|
||||
// ahead and select it properly
|
||||
if (_selectedButton != null) {
|
||||
if (_selectedButton.contains(e.getX(), e.getY())) {
|
||||
// tell the model that the selection has changed (and
|
||||
// we'll respond and do our business
|
||||
Object elem = _selectedButton.getClientProperty("element");
|
||||
_model.setSelectedItem(elem);
|
||||
|
||||
} else {
|
||||
_selectedButton.setBorder(DESELECTED_BORDER);
|
||||
_selectedButton.repaint();
|
||||
}
|
||||
|
||||
// clear out the selected button indicator
|
||||
_selectedButton = null;
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseEntered (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void mouseExited (MouseEvent e)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds buttons for the specified range of model elements.
|
||||
*/
|
||||
protected void addButtons (int start, int count)
|
||||
{
|
||||
Object selobj = _model.getSelectedItem();
|
||||
for (int i = start; i < count; i++) {
|
||||
Object elem = _model.getElementAt(i);
|
||||
if (selobj == elem) {
|
||||
_selectedIndex = i;
|
||||
}
|
||||
JLabel ibut = null;
|
||||
if (elem instanceof Image) {
|
||||
ibut = new JLabel(new ImageIcon((Image)elem));
|
||||
} else {
|
||||
ibut = new JLabel(elem.toString());
|
||||
}
|
||||
ibut.putClientProperty("element", elem);
|
||||
ibut.addMouseListener(this);
|
||||
ibut.setBorder((_selectedIndex == i) ?
|
||||
SELECTED_BORDER : DESELECTED_BORDER);
|
||||
add(ibut, i);
|
||||
}
|
||||
|
||||
SwingUtil.refresh(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the buttons in the specified interval.
|
||||
*/
|
||||
protected void removeButtons (int start, int count)
|
||||
{
|
||||
while (count-- > 0) {
|
||||
remove(start);
|
||||
}
|
||||
|
||||
// adjust the selected index
|
||||
if (_selectedIndex >= start) {
|
||||
if (start + count > _selectedIndex) {
|
||||
_selectedIndex = -1;
|
||||
} else {
|
||||
_selectedIndex -= count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection to the specified index and updates the buttons
|
||||
* to reflect the change. This does not update the model.
|
||||
*/
|
||||
protected void updateSelection (int selidx)
|
||||
{
|
||||
// do nothing if this element is already selected
|
||||
if (selidx == _selectedIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// unhighlight the old component
|
||||
if (_selectedIndex != -1) {
|
||||
JLabel but = (JLabel)getComponent(_selectedIndex);
|
||||
but.setBorder(DESELECTED_BORDER);
|
||||
}
|
||||
|
||||
// save the new selection
|
||||
_selectedIndex = selidx;
|
||||
|
||||
// if the selection is valid, highlight the new component
|
||||
if (_selectedIndex != -1) {
|
||||
JLabel but = (JLabel)getComponent(_selectedIndex);
|
||||
but.setBorder(SELECTED_BORDER);
|
||||
}
|
||||
|
||||
// fire an action performed to let listeners know about our
|
||||
// changed selection
|
||||
fireActionPerformed();
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** The contents of the box. */
|
||||
protected ComboBoxModel _model;
|
||||
|
||||
/** The index of the selected button. */
|
||||
protected int _selectedIndex = -1;
|
||||
|
||||
/** The button over which the mouse was pressed. */
|
||||
protected JLabel _selectedButton;
|
||||
|
||||
/** Used when notifying our listeners. */
|
||||
protected ActionEvent _actionEvent;
|
||||
|
||||
/** The action command we generate when the selection changes. */
|
||||
protected String _actionCommand;
|
||||
|
||||
/** The border used for selected components. */
|
||||
protected static final Border SELECTED_BORDER =
|
||||
BorderFactory.createLoweredBevelBorder();
|
||||
|
||||
/** The border used for non-selected components. */
|
||||
protected static final Border DESELECTED_BORDER =
|
||||
BorderFactory.createRaisedBevelBorder();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// $Id: CommandButton.java,v 1.2 2003/05/03 00:10:27 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.JButton;
|
||||
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
/**
|
||||
* A button that fires CommandEvents when it is actioned.
|
||||
*/
|
||||
public class CommandButton extends JButton
|
||||
{
|
||||
/**
|
||||
* Set the argument of the CommandEvents which we generate.
|
||||
*/
|
||||
public void setActionArgument (Object arg)
|
||||
{
|
||||
_argument = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the argument that we'll use when we generate CommandEvents.
|
||||
*/
|
||||
public Object getActionArgument ()
|
||||
{
|
||||
return _argument;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void fireActionPerformed (ActionEvent event)
|
||||
{
|
||||
Object[] listeners = listenerList.getListenerList();
|
||||
ActionEvent e = null;
|
||||
// Process the listeners last to first, notfiying
|
||||
// those that are interested in this event
|
||||
for (int ii = listeners.length - 2; ii >= 0; ii -= 2) {
|
||||
if (listeners[ii] == ActionListener.class) {
|
||||
// Lazily create the event:
|
||||
if (e == null) {
|
||||
e = createEventToFire(event);
|
||||
}
|
||||
((ActionListener) listeners[ii + 1]).actionPerformed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the event to fire from the prototype.
|
||||
* If this method were broken out in AbstractButton, we wouldn't even
|
||||
* have had to override fireActionPerformed().
|
||||
*/
|
||||
protected ActionEvent createEventToFire (ActionEvent proto)
|
||||
{
|
||||
String actionCommand = proto.getActionCommand();
|
||||
if (actionCommand == null) {
|
||||
actionCommand = getActionCommand();
|
||||
}
|
||||
|
||||
Object arg = (proto instanceof CommandEvent)
|
||||
? arg = ((CommandEvent) proto).getArgument()
|
||||
: null;
|
||||
|
||||
// if we were passed an action event, or if it was a command event
|
||||
// with a null arg...
|
||||
if (arg == null) {
|
||||
// ...use ours
|
||||
arg = getActionArgument();
|
||||
}
|
||||
|
||||
if (arg == null) {
|
||||
// just create a plain ActionEvent
|
||||
return new ActionEvent(CommandButton.this,
|
||||
ActionEvent.ACTION_PERFORMED, actionCommand,
|
||||
proto.getWhen(), proto.getModifiers());
|
||||
} else {
|
||||
// if we found an arg somewhere, create a CommandEvent
|
||||
return new CommandEvent(CommandButton.this, actionCommand, arg,
|
||||
proto.getWhen(), proto.getModifiers());
|
||||
}
|
||||
}
|
||||
|
||||
/** The CommandEvent argument for our actions. */
|
||||
protected Object _argument;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// $Id: ControlledPanel.java,v 1.2 2002/12/18 04:19:16 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* A controlled panel takes care of setting up its controller and
|
||||
* providing it. When operating in conjunction with a controlled panel,
|
||||
* the controller can automatically invoke {@link Controller#wasAdded} and
|
||||
* {@link Controller#wasRemoved}.
|
||||
*/
|
||||
public abstract class ControlledPanel extends JPanel
|
||||
implements ControllerProvider
|
||||
{
|
||||
/**
|
||||
* Creates a controlled panel and its associated controller.
|
||||
*/
|
||||
public ControlledPanel ()
|
||||
{
|
||||
_controller = createController();
|
||||
|
||||
// let the controller know about this panel
|
||||
if (_controller != null) {
|
||||
_controller.setControlledPanel(this);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public Controller getController ()
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the controller associated with this controlled
|
||||
* panel. Derived classes should override this and instantiate the
|
||||
* appropriate controller derived class. Note that this will be called
|
||||
* very early in the construction process, before derived classes have
|
||||
* access to their member data and as such, the derived panel will not
|
||||
* be able to pass anything interesting to its associated controller
|
||||
* constructor. The expectation is that it will obtain a reference to
|
||||
* its controller later in its own constructor and supply any extra
|
||||
* initialization data that is needed at that time.
|
||||
*/
|
||||
protected abstract Controller createController ();
|
||||
|
||||
/** The controller with which we are working. */
|
||||
protected Controller _controller;
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
//
|
||||
// $Id: Controller.java,v 1.18 2004/05/21 20:53:08 ray Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.EventQueue;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import java.awt.event.HierarchyEvent;
|
||||
import java.awt.event.HierarchyListener;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
/**
|
||||
* The controller class provides a basis for the separation of user
|
||||
* interface code into display code and control code. The display code
|
||||
* lives in a panel class (<code>javax.swing.JPanel</code> or something
|
||||
* conceptually similar) and the control code lives in an associated
|
||||
* controller class.
|
||||
*
|
||||
* <p> The controller philosophy is thus: The panel class (and its UI
|
||||
* components) convert basic user interface actions into higher level
|
||||
* actions that more cleanly encapsulate the action desired by the user
|
||||
* and they pass those actions on to their controller. The controller then
|
||||
* performs abstract processing based on the users desires and the
|
||||
* changing state of the application and calls back to the panel to affect
|
||||
* changes to the display.
|
||||
*
|
||||
* <p> Controllers also support the notion of scope. When a panel wishes
|
||||
* to post an action, it doesn't do it directly to the controller. Instead
|
||||
* it does it using a controller utility function called {@link
|
||||
* #postAction}, which searches up the user interface hierarchy looking
|
||||
* for a component that implements {@link
|
||||
* com.samskivert.swing.ControllerProvider} which it will use to obtain
|
||||
* the controller "in scope" for that component. That controller is
|
||||
* requested to handle the action, but if it cannot handle the action, the
|
||||
* next controller up the chain is located and requested to process the
|
||||
* action. In this manner, a hierarchy of controllers (often just two: one
|
||||
* application wide and one for whatever particular mode the application
|
||||
* is in at the moment) can provide a set of services that are available
|
||||
* to all user interface elements in the entire application and in a way
|
||||
* that doesn't require tight connectedness between the UI elements and
|
||||
* the controllers.
|
||||
*/
|
||||
public abstract class Controller
|
||||
implements ActionListener
|
||||
{
|
||||
/**
|
||||
* This action listener can be wired up to any action event generator
|
||||
* and it will take care of forwarding that event on to the controller
|
||||
* in scope for the component that generated the action event.
|
||||
*
|
||||
* <p> For example, wiring a button up to a dispatcher would look like
|
||||
* so:
|
||||
*
|
||||
* <pre>
|
||||
* JButton button = new JButton("Do thing");
|
||||
* button.setActionCommand("dothing");
|
||||
* button.addActionListener(Controller.DISPATCHER);
|
||||
* </pre>
|
||||
*
|
||||
* or, use the provided convenience function:
|
||||
*
|
||||
* <pre>
|
||||
* JButton button =
|
||||
* Controller.createActionButton("Do thing", "dothing");
|
||||
* </pre>
|
||||
*
|
||||
* The controllers in scope would then be requested (in order) to
|
||||
* process the <code>dothing</code> action whenever the button was
|
||||
* clicked.
|
||||
*/
|
||||
public static final ActionListener DISPATCHER = new ActionListener() {
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
Controller.postAction(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Lets this controller know about the panel that it is controlling.
|
||||
*/
|
||||
public void setControlledPanel (final JComponent panel)
|
||||
{
|
||||
panel.addHierarchyListener(new HierarchyListener() {
|
||||
public void hierarchyChanged (HierarchyEvent e) {
|
||||
boolean nowShowing = panel.isDisplayable();
|
||||
//System.err.println("Controller." + Controller.this +
|
||||
// " nowShowing=" + nowShowing +
|
||||
// ", wasShowing=" + _showing);
|
||||
if (_showing != nowShowing) {
|
||||
_showing = nowShowing;
|
||||
if (_showing) {
|
||||
wasAdded();
|
||||
} else {
|
||||
wasRemoved();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean _showing = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the panel controlled by this controller was added to
|
||||
* the user interface hierarchy. This assumes that the controlled
|
||||
* panel made itself known to the controller via {@link
|
||||
* #setControlledPanel} (which is done automatically by {@link
|
||||
* ControlledPanel} and derived classes).
|
||||
*/
|
||||
public void wasAdded ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the panel controlled by this controller was removed
|
||||
* from the user interface hierarchy. This assumes that the controlled
|
||||
* panel made itself known to the controller via {@link
|
||||
* #setControlledPanel} (which is done automatically by {@link
|
||||
* ControlledPanel} and derived classes).
|
||||
*/
|
||||
public void wasRemoved ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs this controller to process this action event. When an
|
||||
* action is posted by a user interface element, it will be posted to
|
||||
* the controller in closest scope for that element. If that
|
||||
* controller handles the event, it should return true from this
|
||||
* method to indicate that processing should stop. If it cannot handle
|
||||
* the event, it can return false to indicate that the event should be
|
||||
* propagated to the next controller up the chain.
|
||||
*
|
||||
* <p> This method will be called on the AWT thread, so the controller
|
||||
* can safely manipulate user interface components while handling an
|
||||
* action. However, this means that action handling cannot block and
|
||||
* should not take an undue amount of time. If the controller needs to
|
||||
* perform complicated, lengthy processing it should do so with a
|
||||
* separate thread, for example via {@link
|
||||
* com.samskivert.swing.util.TaskMaster}.
|
||||
*
|
||||
* <p> The default implementation of this method will reflect on the
|
||||
* controller class, looking for a method that matches the name of the
|
||||
* action event. For example, if the action was "Exit" a method named
|
||||
* "handleExit" would be sought. A handler method must provide one of
|
||||
* three signatures: one accepting no arguments, one including only a
|
||||
* reference to the source object, or one including the source object
|
||||
* and an extra argument (which can be used only if the action event
|
||||
* is an instance of {@link CommandEvent}). For example:
|
||||
*
|
||||
* <pre>
|
||||
* public void handleCancelClicked (Object source);
|
||||
* public void handleTextEntered (Object source, String text);
|
||||
* </pre>
|
||||
*
|
||||
* The arguments to the method can be as specific or as generic as
|
||||
* desired and reflection will perform the appropriate conversions at
|
||||
* runtime. For example, a method could be declared like so:
|
||||
*
|
||||
* <pre>
|
||||
* public void handleCancelClicked (JButton source);
|
||||
* </pre>
|
||||
*
|
||||
* One would have to ensure that the only action events generated with
|
||||
* the action command string "CancelClicked" were generated by
|
||||
* <code>JButton</code> instances if such a signature were used.
|
||||
*
|
||||
* @param action The action to be processed.
|
||||
*
|
||||
* @return true if the action was processed, false if it should be
|
||||
* propagated up to the next controller in scope.
|
||||
*/
|
||||
public boolean handleAction (ActionEvent action)
|
||||
{
|
||||
Method method = null;
|
||||
Object[] args = null;
|
||||
|
||||
try {
|
||||
// look for the appropriate method
|
||||
String targetName = "handle" + action.getActionCommand();
|
||||
Method[] methods = getClass().getMethods();
|
||||
int mcount = methods.length;
|
||||
|
||||
for (int i = 0; i < mcount; i++) {
|
||||
if (methods[i].getName().equals(targetName)) {
|
||||
// see if we can generate the appropriate arguments
|
||||
args = generateArguments(methods[i], action);
|
||||
// if we were able to, go ahead and use this method
|
||||
if (args != null) {
|
||||
method = methods[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Error searching for action handler method " +
|
||||
"[controller=" + this + ", event=" + action + "].");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (method != null) {
|
||||
method.invoke(this, args);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Error invoking action handler [controller=" + this +
|
||||
", event=" + action + "].");
|
||||
Log.logStackTrace(e);
|
||||
// even though we choked, we still "handled" the action
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for constructing and immediately handling an
|
||||
* event on this controller (via {@link #handleAction(ActionEvent)}).
|
||||
*
|
||||
* @return true if the controller knew how to handle the action, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean handleAction (Component source, String command)
|
||||
{
|
||||
return handleAction(new ActionEvent(source, 0, command));
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method for constructing and immediately handling an
|
||||
* event on this controller (via {@link #handleAction(ActionEvent)}).
|
||||
*
|
||||
* @return true if the controller knew how to handle the action, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean handleAction (Component source, String command, Object arg)
|
||||
{
|
||||
return handleAction(new CommandEvent(source, command, arg));
|
||||
}
|
||||
|
||||
// documentation inherited from interface ActionListener
|
||||
public void actionPerformed (ActionEvent event)
|
||||
{
|
||||
handleAction(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link #handleAction} to generate arguments to the action
|
||||
* handler method.
|
||||
*/
|
||||
protected Object[] generateArguments (Method method, ActionEvent action)
|
||||
{
|
||||
// figure out what sort of arguments are required by the method
|
||||
Class[] atypes = method.getParameterTypes();
|
||||
|
||||
if (atypes == null || atypes.length == 0) {
|
||||
return new Object[0];
|
||||
|
||||
} else if (atypes.length == 1) {
|
||||
return new Object[] { action.getSource() };
|
||||
|
||||
} else if (atypes.length == 2) {
|
||||
if (action instanceof CommandEvent) {
|
||||
CommandEvent command = (CommandEvent)action;
|
||||
return new Object[] { action.getSource(),
|
||||
command.getArgument() };
|
||||
}
|
||||
|
||||
Log.warning("Unable to map non-command event to " +
|
||||
"handler method that requires extra " +
|
||||
"argument [controller=" + this +
|
||||
", action=" + action + "].");
|
||||
}
|
||||
|
||||
// we would have handled it, but we couldn't
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts the specified action to the nearest controller in scope. The
|
||||
* controller search begins with the source component of the action
|
||||
* and traverses up the component tree looking for a controller to
|
||||
* handle the action. The controller location and action event
|
||||
* processing is guaranteed to take place on the AWT thread regardless
|
||||
* of what thread calls <code>postAction</code> and that processing
|
||||
* will not occur immediately but is instead appended to the AWT event
|
||||
* dispatch queue for processing.
|
||||
*/
|
||||
public static void postAction (ActionEvent action)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
EventQueue.invokeLater(new ActionInvoker(action));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #postAction(ActionEvent)} except that it constructs the
|
||||
* action event for you with the supplied source component and string
|
||||
* command. The <code>id</code> of the event will always be set to
|
||||
* zero.
|
||||
*/
|
||||
public static void postAction (Component source, String command)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
ActionEvent event = new ActionEvent(source, 0, command);
|
||||
EventQueue.invokeLater(new ActionInvoker(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #postAction(ActionEvent)} except that it constructs a
|
||||
* {@link CommandEvent} with the supplied source component, string
|
||||
* command and argument.
|
||||
*/
|
||||
public static void postAction (
|
||||
Component source, String command, Object argument)
|
||||
{
|
||||
// slip things onto the event queue for later
|
||||
CommandEvent event = new CommandEvent(source, command, argument);
|
||||
EventQueue.invokeLater(new ActionInvoker(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button and configures it with the specified label and
|
||||
* action command and adds {@link #DISPATCHER} as an action listener.
|
||||
*/
|
||||
public static JButton createActionButton (String label, String command)
|
||||
{
|
||||
JButton button = new JButton(label);
|
||||
button.setActionCommand(command);
|
||||
button.addActionListener(DISPATCHER);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to dispatch action events to controllers within
|
||||
* the context of the AWT event dispatch mechanism.
|
||||
*/
|
||||
protected static class ActionInvoker implements Runnable
|
||||
{
|
||||
public ActionInvoker (ActionEvent action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void run ()
|
||||
{
|
||||
// do some sanity checking on the source
|
||||
Object src = _action.getSource();
|
||||
if (src == null || !(src instanceof Component)) {
|
||||
Log.warning("Requested to dispatch action on " +
|
||||
"non-component source [source=" + src +
|
||||
", action=" + _action + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// scan up the component hierarchy looking for a controller on
|
||||
// which to dispatch this action
|
||||
for (Component source = (Component)src; source != null;
|
||||
source = source.getParent()) {
|
||||
if (!(source instanceof ControllerProvider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Controller ctrl = ((ControllerProvider)source).getController();
|
||||
if (ctrl == null) {
|
||||
Log.warning("Provider returned null controller " +
|
||||
"[provider=" + source + "].");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// if the controller returns true, it handled the
|
||||
// action and we can call this business done
|
||||
if (ctrl.handleAction(_action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Controller choked on action " +
|
||||
"[ctrl=" + ctrl +
|
||||
", action=" + _action + "].");
|
||||
Log.logStackTrace(e);
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we didn't find a controller
|
||||
Log.warning("Unable to find a controller to process action " +
|
||||
"[action=" + _action + "].");
|
||||
}
|
||||
|
||||
protected ActionEvent _action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// $Id: ControllerProvider.java,v 1.3 2001/08/14 00:59:19 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
/**
|
||||
* The controller provider interface is implemented by user interface
|
||||
* elements that have an associated {@link
|
||||
* com.samskivert.swing.Controller}. The hierarchy of the user interface
|
||||
* elements defines the hierarchy of controllers and at any point in the
|
||||
* UI element hierarchy, an element can implement controller provider and
|
||||
* provide a controller that will process actions received at that scope
|
||||
* or below.
|
||||
*/
|
||||
public interface ControllerProvider
|
||||
{
|
||||
/**
|
||||
* Returns the controller to be used at the scope of the UI element
|
||||
* that implements controller provider.
|
||||
*/
|
||||
public Controller getController ();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// $Id: DimenInfo.java,v 1.2 2001/08/11 22:43:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Dimension;
|
||||
|
||||
/**
|
||||
* This record is used by the group layout managers to return a set of
|
||||
* statistics computed for their target widgets.
|
||||
*/
|
||||
public class DimenInfo
|
||||
{
|
||||
public int count;
|
||||
|
||||
public int totwid;
|
||||
public int tothei;
|
||||
|
||||
public int maxwid;
|
||||
public int maxhei;
|
||||
|
||||
public int numfix;
|
||||
public int fixwid;
|
||||
public int fixhei;
|
||||
|
||||
public int maxfreewid;
|
||||
public int maxfreehei;
|
||||
|
||||
public int totweight;
|
||||
|
||||
public Dimension[] dimens;
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("[count=").append(count);
|
||||
buf.append(", totwid=").append(totwid);
|
||||
buf.append(", tothei=").append(tothei);
|
||||
buf.append(", maxwid=").append(maxwid);
|
||||
buf.append(", maxhei=").append(maxhei);
|
||||
buf.append(", numfix=").append(numfix);
|
||||
buf.append(", fixwid=").append(fixwid);
|
||||
buf.append(", fixhei=").append(fixhei);
|
||||
buf.append(", maxfreewid=").append(maxfreewid);
|
||||
buf.append(", maxfreehei=").append(maxfreehei);
|
||||
buf.append(", totweight=").append(totweight);
|
||||
return buf.append("]").toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// $Id: DimmedIcon.java,v 1.1 2004/04/23 16:46:37 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Component;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
/**
|
||||
* Draws an icon with a specified alpha level.
|
||||
*/
|
||||
public class DimmedIcon implements Icon
|
||||
{
|
||||
/**
|
||||
* Construct a dimmed icon that is drawn at 50% normal.
|
||||
*/
|
||||
public DimmedIcon (Icon icon)
|
||||
{
|
||||
this(icon, .5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a dimmed icon that is drawn at the specified alpha level.
|
||||
*/
|
||||
public DimmedIcon (Icon icon, float alpha)
|
||||
{
|
||||
_alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public int getIconWidth ()
|
||||
{
|
||||
return _icon.getIconWidth();
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public int getIconHeight ()
|
||||
{
|
||||
return _icon.getIconHeight();
|
||||
}
|
||||
|
||||
// documentation inherited from interface Icon
|
||||
public void paintIcon (Component c, Graphics g, int x, int y)
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D) g;
|
||||
Composite ocomp = gfx.getComposite();
|
||||
gfx.setComposite(_alpha);
|
||||
_icon.paintIcon(c, gfx, x, y);
|
||||
gfx.setComposite(ocomp);
|
||||
}
|
||||
|
||||
/** The icon we're actually drawing. */
|
||||
protected Icon _icon;
|
||||
|
||||
/** Our alpha composite. */
|
||||
protected Composite _alpha;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// $Id: EnablingAdapter.java,v 1.1 2002/10/23 02:06:14 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Used to enable or disable a source component based on some
|
||||
* asynchronously changing state.
|
||||
*/
|
||||
public class EnablingAdapter
|
||||
{
|
||||
/**
|
||||
* Creates and returns an enabler that listens for changes in the
|
||||
* specified property (which must be a {@link Boolean} valued
|
||||
* property) and updates the target's enabled state accordingly.
|
||||
*/
|
||||
public static PropertyChangeListener getPropChangeEnabler (
|
||||
String property, JComponent target, boolean invert)
|
||||
{
|
||||
return new PropertyChangeEnabler(property, target, invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an enabling adapter with the specified target component.
|
||||
*
|
||||
* @param invert if true, the target component's enabled state is set
|
||||
* to the inverse of the monitored state.
|
||||
*/
|
||||
protected EnablingAdapter (JComponent target, boolean invert)
|
||||
{
|
||||
_target = target;
|
||||
_invert = invert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the appropriate derived adapter to adjust the target's
|
||||
* enabled state.
|
||||
*/
|
||||
protected void stateChanged (boolean newState)
|
||||
{
|
||||
_target.setEnabled(_invert ? !newState : newState);
|
||||
}
|
||||
|
||||
/** Used by {@link #getPropChangeEnabler}. */
|
||||
protected static class PropertyChangeEnabler extends EnablingAdapter
|
||||
implements PropertyChangeListener
|
||||
{
|
||||
public PropertyChangeEnabler (
|
||||
String property, JComponent target, boolean invert)
|
||||
{
|
||||
super(target, invert);
|
||||
_property = property;
|
||||
}
|
||||
|
||||
public void propertyChange (PropertyChangeEvent evt)
|
||||
{
|
||||
if (evt.getPropertyName().equals(_property)) {
|
||||
Object value = evt.getNewValue();
|
||||
if (value instanceof Boolean) {
|
||||
stateChanged(((Boolean)value).booleanValue());
|
||||
} else {
|
||||
Log.warning("PropertyChangeEnabler connected to " +
|
||||
"non-Boolean property [got=" + value + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String _property;
|
||||
}
|
||||
|
||||
protected JComponent _target;
|
||||
protected boolean _invert;
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
//
|
||||
// $Id: GroupLayout.java,v 1.8 2004/02/25 13:17:41 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Group layout managers lay out widgets in horizontal or vertical groups.
|
||||
*/
|
||||
public abstract class GroupLayout
|
||||
implements LayoutManager2
|
||||
{
|
||||
/**
|
||||
* The group layout managers supports two constraints: fixedness
|
||||
* and weight. A fixed component will not be stretched along the major
|
||||
* axis of the group. Those components that are stretched will have
|
||||
* the extra space divided among them according to their weight
|
||||
* (specifically receiving the ratio of their weight to the total
|
||||
* weight of all of the free components in the container).
|
||||
*
|
||||
* To add a component with the fixed constraints, use the FIXED constant.
|
||||
*/
|
||||
public static class Constraints
|
||||
{
|
||||
/**
|
||||
* Constructs a new constraints object with the specified weight,
|
||||
* which is only applicable with the STRETCH policy.
|
||||
*/
|
||||
public Constraints (int weight)
|
||||
{
|
||||
_weight = weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this Constraints specifying fixed?
|
||||
*/
|
||||
public final boolean isFixed ()
|
||||
{
|
||||
return (this == FIXED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weight.
|
||||
*/
|
||||
public final int getWeight ()
|
||||
{
|
||||
return _weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* The weight of this component relative to the other components
|
||||
* in the container. Only valid if the layout policy is STRETCH.
|
||||
*/
|
||||
protected int _weight = 1;
|
||||
}
|
||||
|
||||
/** A class used to make our policy constants type-safe. */
|
||||
public static class Policy
|
||||
{
|
||||
int code;
|
||||
|
||||
public Policy (int code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/** A class used to make our policy constants type-safe. */
|
||||
public static class Justification
|
||||
{
|
||||
int code;
|
||||
|
||||
public Justification (int code)
|
||||
{
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constraints object that indicates that the component should be
|
||||
* fixed and have the default weight of one. This is so commonly used
|
||||
* that we create and make this object available here.
|
||||
*/
|
||||
public final static Constraints FIXED = new Constraints(Integer.MIN_VALUE);
|
||||
|
||||
/**
|
||||
* Do not adjust the widgets on this axis.
|
||||
*/
|
||||
public final static Policy NONE = new Policy(0);
|
||||
|
||||
/**
|
||||
* Stretch all the widgets to their maximum possible size on this
|
||||
* axis.
|
||||
*/
|
||||
public final static Policy STRETCH = new Policy(1);
|
||||
|
||||
/**
|
||||
* Stretch all the widgets to be equal to the size of the largest
|
||||
* widget on this axis.
|
||||
*/
|
||||
public final static Policy EQUALIZE = new Policy(2);
|
||||
|
||||
/**
|
||||
* Only valid for off-axis policy, this leaves widgets alone unless
|
||||
* they are larger in the off-axis direction than their container, in
|
||||
* which case it constrains them to fit on the off-axis.
|
||||
*/
|
||||
public final static Policy CONSTRAIN = new Policy(3);
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification CENTER = new Justification(0);
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification LEFT = new Justification(1);
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification RIGHT = new Justification(2);
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification TOP = new Justification(3);
|
||||
|
||||
/** A justification constant. */
|
||||
public final static Justification BOTTOM = new Justification(4);
|
||||
|
||||
public void setPolicy (Policy policy)
|
||||
{
|
||||
_policy = policy;
|
||||
}
|
||||
|
||||
public Policy getPolicy ()
|
||||
{
|
||||
return _policy;
|
||||
}
|
||||
|
||||
public void setOffAxisPolicy (Policy offpolicy)
|
||||
{
|
||||
_offpolicy = offpolicy;
|
||||
}
|
||||
|
||||
public Policy getOffAxisPolicy ()
|
||||
{
|
||||
return _offpolicy;
|
||||
}
|
||||
|
||||
public void setGap (int gap)
|
||||
{
|
||||
_gap = gap;
|
||||
}
|
||||
|
||||
public int getGap ()
|
||||
{
|
||||
return _gap;
|
||||
}
|
||||
|
||||
public void setJustification (Justification justification)
|
||||
{
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public Justification getJustification ()
|
||||
{
|
||||
return _justification;
|
||||
}
|
||||
|
||||
public void setOffAxisJustification (Justification justification)
|
||||
{
|
||||
_offjust = justification;
|
||||
}
|
||||
|
||||
public Justification getOffAxisJustification ()
|
||||
{
|
||||
return _offjust;
|
||||
}
|
||||
|
||||
public void addLayoutComponent (String name, Component comp)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
public void removeLayoutComponent (Component comp)
|
||||
{
|
||||
if (_constraints != null) {
|
||||
_constraints.remove(comp);
|
||||
}
|
||||
}
|
||||
|
||||
public void addLayoutComponent (Component comp, Object constraints)
|
||||
{
|
||||
if (constraints != null) {
|
||||
if (constraints instanceof Constraints) {
|
||||
if (_constraints == null) {
|
||||
_constraints = new HashMap();
|
||||
}
|
||||
_constraints.put(comp, constraints);
|
||||
|
||||
} else {
|
||||
throw new RuntimeException("GroupLayout constraints " +
|
||||
"object must be of type " +
|
||||
"GroupLayout.Constraints");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float getLayoutAlignmentX (Container target)
|
||||
{
|
||||
// we don't support alignment like this
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public float getLayoutAlignmentY (Container target)
|
||||
{
|
||||
// we don't support alignment like this
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public Dimension minimumLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, MINIMUM);
|
||||
}
|
||||
|
||||
public Dimension preferredLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, PREFERRED);
|
||||
}
|
||||
|
||||
public Dimension maximumLayoutSize (Container parent)
|
||||
{
|
||||
return getLayoutSize(parent, MAXIMUM);
|
||||
}
|
||||
|
||||
protected abstract Dimension getLayoutSize (Container parent, int type);
|
||||
|
||||
public abstract void layoutContainer (Container parent);
|
||||
|
||||
public void invalidateLayout (Container target)
|
||||
{
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Constraints for the specified child component.
|
||||
*
|
||||
* @return a Constraints object, never null.
|
||||
*/
|
||||
protected Constraints getConstraints (Component child)
|
||||
{
|
||||
if (_constraints != null) {
|
||||
Constraints c = (Constraints) _constraints.get(child);
|
||||
if (c != null) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
return DEFAULT_CONSTRAINTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes dimensions of the children widgets that are useful for the
|
||||
* group layout managers.
|
||||
*/
|
||||
protected DimenInfo computeDimens (Container parent, int type)
|
||||
{
|
||||
int count = parent.getComponentCount();
|
||||
DimenInfo info = new DimenInfo();
|
||||
info.dimens = new Dimension[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
Component child = parent.getComponent(i);
|
||||
if (!child.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Dimension csize;
|
||||
switch (type) {
|
||||
case MINIMUM:
|
||||
csize = child.getMinimumSize();
|
||||
break;
|
||||
|
||||
case MAXIMUM:
|
||||
csize = child.getMaximumSize();
|
||||
break;
|
||||
|
||||
default:
|
||||
csize = child.getPreferredSize();
|
||||
break;
|
||||
}
|
||||
|
||||
info.count++;
|
||||
info.totwid += csize.width;
|
||||
info.tothei += csize.height;
|
||||
|
||||
if (csize.width > info.maxwid) {
|
||||
info.maxwid = csize.width;
|
||||
}
|
||||
if (csize.height > info.maxhei) {
|
||||
info.maxhei = csize.height;
|
||||
}
|
||||
|
||||
Constraints c = getConstraints(child);
|
||||
if (c.isFixed()) {
|
||||
info.fixwid += csize.width;
|
||||
info.fixhei += csize.height;
|
||||
info.numfix++;
|
||||
|
||||
} else {
|
||||
info.totweight += c.getWeight();
|
||||
|
||||
if (csize.width > info.maxfreewid) {
|
||||
info.maxfreewid = csize.width;
|
||||
}
|
||||
if (csize.height > info.maxfreehei) {
|
||||
info.maxfreehei = csize.height;
|
||||
}
|
||||
}
|
||||
|
||||
info.dimens[i] = csize;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with a configuration conducive to containing a row of
|
||||
* buttons.
|
||||
*/
|
||||
public static JPanel makeButtonBox (Justification justification)
|
||||
{
|
||||
return makeButtonBox(justification, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with a configuration conducive to containing a row of
|
||||
* buttons. The supplied button is added to the box.
|
||||
*/
|
||||
public static JPanel makeButtonBox (
|
||||
Justification justification, JComponent button)
|
||||
{
|
||||
JPanel box = new JPanel(new HGroupLayout(NONE, justification));
|
||||
if (button != null) {
|
||||
box.add(button);
|
||||
box.setOpaque(false);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the default configuration.
|
||||
*/
|
||||
public static JPanel makeHBox ()
|
||||
{
|
||||
return new JPanel(new HGroupLayout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with a configuration that stretches in both
|
||||
* directions, with the specified gap.
|
||||
*/
|
||||
public static JPanel makeHStretchBox (int gap)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(STRETCH, STRETCH, gap, CENTER));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy (default
|
||||
* configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy and justification
|
||||
* (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy, Justification justification)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* HGroupLayout} with the specified on-axis policy, justification and
|
||||
* off-axis policy (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeHBox (Policy policy, Justification justification,
|
||||
Policy offAxisPolicy)
|
||||
{
|
||||
return new JPanel(new HGroupLayout(policy, offAxisPolicy,
|
||||
DEFAULT_GAP, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the default configuration.
|
||||
*/
|
||||
public static JPanel makeVBox ()
|
||||
{
|
||||
return new JPanel(new VGroupLayout());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy (default
|
||||
* configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy and justification
|
||||
* (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy, Justification justification)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with the specified on-axis policy, justification and
|
||||
* off-axis policy (default configuration otherwise).
|
||||
*/
|
||||
public static JPanel makeVBox (Policy policy, Justification justification,
|
||||
Policy offAxisPolicy)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(policy, offAxisPolicy,
|
||||
DEFAULT_GAP, justification));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link JPanel} that is configured with an {@link
|
||||
* VGroupLayout} with a configuration that stretches in both
|
||||
* directions, with the specified gap.
|
||||
*/
|
||||
public static JPanel makeVStretchBox (int gap)
|
||||
{
|
||||
return new JPanel(new VGroupLayout(STRETCH, STRETCH, gap, CENTER));
|
||||
}
|
||||
|
||||
protected Policy _policy = NONE;
|
||||
protected Policy _offpolicy = CONSTRAIN;
|
||||
protected int _gap = DEFAULT_GAP;
|
||||
protected Justification _justification = CENTER;
|
||||
protected Justification _offjust = CENTER;
|
||||
|
||||
protected HashMap _constraints;
|
||||
|
||||
protected static final int MINIMUM = 0;
|
||||
protected static final int PREFERRED = 1;
|
||||
protected static final int MAXIMUM = 2;
|
||||
|
||||
protected static final int DEFAULT_GAP = 5;
|
||||
|
||||
/** All children added without a Constraints object are
|
||||
* constrained by this Constraints object. */
|
||||
protected static final Constraints DEFAULT_CONSTRAINTS = new Constraints(1);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// $Id: GroupLayoutTest.java,v 1.3 2001/10/09 19:46:31 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import javax.swing.*;
|
||||
|
||||
public class GroupLayoutTest
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
JFrame frame = new JFrame("GroupLayoutTest");
|
||||
JPanel panel = new JPanel();
|
||||
GroupLayout layout = new HGroupLayout();
|
||||
layout.setJustification(GroupLayout.CENTER);
|
||||
layout.setPolicy(GroupLayout.STRETCH);
|
||||
layout.setJustification(GroupLayout.RIGHT);
|
||||
layout.setGap(15);
|
||||
panel.setLayout(layout);
|
||||
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
JButton butone = new JButton("One");
|
||||
panel.add(butone, GroupLayout.FIXED);
|
||||
JButton buttwo = new JButton("Two");
|
||||
panel.add(buttwo, GroupLayout.FIXED);
|
||||
JButton butthree = new JButton("Three to get ready");
|
||||
panel.add(butthree, GroupLayout.FIXED);
|
||||
|
||||
frame.addWindowListener(new WindowAdapter ()
|
||||
{
|
||||
public void windowClosing (WindowEvent e)
|
||||
{
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// $Id: HGroupLayout.java,v 1.10 2003/04/10 21:43:13 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2001 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class HGroupLayout extends GroupLayout
|
||||
{
|
||||
public HGroupLayout (Policy policy, Policy offpolicy, int gap,
|
||||
Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_offpolicy = offpolicy;
|
||||
_gap = gap;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy, int gap, Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_gap = gap;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy, Justification justification)
|
||||
{
|
||||
_policy = policy;
|
||||
_justification = justification;
|
||||
}
|
||||
|
||||
public HGroupLayout (Policy policy)
|
||||
{
|
||||
_policy = policy;
|
||||
}
|
||||
|
||||
public HGroupLayout ()
|
||||
{
|
||||
}
|
||||
|
||||
protected Dimension getLayoutSize (Container parent, int type)
|
||||
{
|
||||
DimenInfo info = computeDimens(parent, type);
|
||||
Dimension dims = new Dimension();
|
||||
|
||||
if (_policy == STRETCH) {
|
||||
dims.width = info.maxfreewid * (info.count - info.numfix) +
|
||||
info.fixwid;
|
||||
} else if (_policy == EQUALIZE) {
|
||||
dims.width = info.maxwid * info.count;
|
||||
} else { // NONE or CONSTRAIN
|
||||
dims.width = info.totwid;
|
||||
}
|
||||
|
||||
dims.width += (info.count - 1) * _gap;
|
||||
dims.height = info.maxhei;
|
||||
|
||||
// account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
dims.width += insets.left + insets.right;
|
||||
dims.height += insets.top + insets.bottom;
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
public void layoutContainer (Container parent)
|
||||
{
|
||||
Rectangle b = parent.getBounds();
|
||||
DimenInfo info = computeDimens(parent, PREFERRED);
|
||||
|
||||
// adjust the bounds width and height to account for the insets
|
||||
Insets insets = parent.getInsets();
|
||||
b.width -= (insets.left + insets.right);
|
||||
b.height -= (insets.top + insets.bottom);
|
||||
|
||||
int nk = parent.getComponentCount();
|
||||
int sx, sy;
|
||||
int totwid, totgap = _gap * (info.count-1);
|
||||
int freecount = info.count - info.numfix;
|
||||
|
||||
// when stretching, there is the possibility that a pixel or more
|
||||
// will be lost to rounding error. we account for that here and
|
||||
// assign the extra space to the first free component
|
||||
int freefrac = 0;
|
||||
|
||||
// do the on-axis policy calculations
|
||||
int defwid = 0;
|
||||
if (_policy == STRETCH) {
|
||||
if (freecount > 0) {
|
||||
int freewid = b.width - info.fixwid - totgap;
|
||||
defwid = freewid / info.totweight;
|
||||
freefrac = freewid % info.totweight;
|
||||
totwid = b.width;
|
||||
} else {
|
||||
totwid = info.fixwid + totgap;
|
||||
}
|
||||
|
||||
} else if (_policy == EQUALIZE) {
|
||||
defwid = info.maxwid;
|
||||
totwid = info.fixwid + defwid * freecount + totgap;
|
||||
|
||||
} else { // NONE or CONSTRAIN
|
||||
totwid = info.totwid + totgap;
|
||||
}
|
||||
|
||||
// do the off-axis policy calculations
|
||||
int defhei = 0;
|
||||
if (_offpolicy == STRETCH) {
|
||||
defhei = b.height;
|
||||
} else if (_offpolicy == EQUALIZE) {
|
||||
defhei = info.maxhei;
|
||||
}
|
||||
|
||||
// do the justification-related calculations
|
||||
if (_justification == LEFT || _justification == TOP) {
|
||||
sx = insets.left;
|
||||
} else if (_justification == CENTER) {
|
||||
sx = insets.left + (b.width - totwid)/2;
|
||||
} else { // RIGHT or BOTTOM
|
||||
sx = insets.left + b.width - totwid;
|
||||
}
|
||||
|
||||
// do the layout
|
||||
for (int i = 0; i < nk; i++) {
|
||||
// skip non-visible kids
|
||||
if (info.dimens[i] == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Component child = parent.getComponent(i);
|
||||
Constraints c = getConstraints(child);
|
||||
int newwid, newhei;
|
||||
|
||||
if (_policy == NONE || c.isFixed()) {
|
||||
newwid = info.dimens[i].width;
|
||||
} else {
|
||||
newwid = freefrac +
|
||||
((_policy == STRETCH) ? defwid * c.getWeight() : defwid);
|
||||
// clear out the extra pixels the first time they're used
|
||||
freefrac = 0;
|
||||
}
|
||||
|
||||
if (_offpolicy == NONE) {
|
||||
newhei = info.dimens[i].height;
|
||||
} else if (_offpolicy == CONSTRAIN) {
|
||||
newhei = Math.min(info.dimens[i].height, b.height);
|
||||
} else {
|
||||
newhei = defhei;
|
||||
}
|
||||
|
||||
// determine our off-axis position
|
||||
if (_offjust == LEFT || _offjust == TOP) {
|
||||
sy = insets.top;
|
||||
} else if (_offjust == RIGHT || _offjust == BOTTOM) {
|
||||
sy = insets.top + b.height - newhei;
|
||||
} else { // CENTER
|
||||
sy = insets.top + (b.height - newhei)/2;
|
||||
}
|
||||
|
||||
child.setBounds(sx, sy, newwid, newhei);
|
||||
sx += child.getSize().width + _gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
//
|
||||
// $Id: IntField.java,v 1.8 2004/06/08 21:15:18 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.EventQueue;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import javax.swing.event.DocumentEvent;
|
||||
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.DocumentFilter;
|
||||
import javax.swing.text.PlainDocument;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* A text field that will only allow editing of integer values within
|
||||
* the specified range.
|
||||
*/
|
||||
public class IntField extends JTextField
|
||||
{
|
||||
/**
|
||||
* Create an IntField with the range 0 - Integer.MAX_VALUE, with
|
||||
* 0 as the initial value.
|
||||
*/
|
||||
public IntField ()
|
||||
{
|
||||
this(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified maximum.
|
||||
*/
|
||||
public IntField (int maxValue)
|
||||
{
|
||||
this(0, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified minimum and maximum, with
|
||||
* the minimum value initially displayed.
|
||||
*/
|
||||
public IntField (int minValue, int maxValue)
|
||||
{
|
||||
this(minValue, minValue, maxValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an IntField with the specified initial, minimum, and
|
||||
* maximum values.
|
||||
*/
|
||||
public IntField (int initial, int minValue, int maxValue)
|
||||
{
|
||||
super(new IntDocument(), format(initial), 5);
|
||||
|
||||
validateMinMax(minValue, maxValue);
|
||||
if (initial > maxValue || initial < minValue) {
|
||||
throw new IllegalArgumentException("initial value not between " +
|
||||
"min and max");
|
||||
}
|
||||
_minValue = minValue;
|
||||
_maxValue = maxValue;
|
||||
|
||||
setHorizontalAlignment(JTextField.RIGHT);
|
||||
|
||||
// create a document filter which enforces the restrictions on
|
||||
// what text may be entered. This is similar to the filter
|
||||
// that is configured in SwingUtil.setDocumentHelpers, only the
|
||||
// remove operation is modified to do the sneaky highlighting we do.
|
||||
final IntDocument doc = (IntDocument) getDocument();
|
||||
doc.setDocumentFilter(new DocumentFilter() {
|
||||
public void remove (FilterBypass fb, int offset, int length)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) +
|
||||
current.substring(offset + length);
|
||||
// if the bit to be removed is still a valid value, go ahead
|
||||
if (unformatSafe(potential) >= _minValue) {
|
||||
transform(fb, current, potential);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, we are going to highlight the bit instead
|
||||
// of removing it
|
||||
int selStart = getSelectionStart();
|
||||
if (selStart > 0) {
|
||||
// see if the next highlighted character is a digit
|
||||
char replace = current.charAt(selStart - 1);
|
||||
if (Character.isDigit(replace)) {
|
||||
// and can be zeroed out
|
||||
String zeroed = current.substring(0, selStart - 1) +
|
||||
"0" + current.substring(selStart);
|
||||
// if so, change it to a zero
|
||||
if (unformatSafe(zeroed) >= _minValue) {
|
||||
fb.replace(selStart - 1, 1, "0", null);
|
||||
|
||||
} else {
|
||||
// otherwise, re-set the entire field to
|
||||
// contain the minimum value, and highlight it
|
||||
String min = format(_minValue);
|
||||
fb.replace(0, current.length(), min, null);
|
||||
setSelectionStart(0);
|
||||
setSelectionEnd(min.length());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// grow the selection to encompass one more character
|
||||
setSelectionStart(selStart - 1);
|
||||
}
|
||||
|
||||
public void insertString (FilterBypass fb, int offset,
|
||||
String s, AttributeSet attr)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) + s +
|
||||
current.substring(offset);
|
||||
transform(fb, current, potential);
|
||||
}
|
||||
|
||||
public void replace (FilterBypass fb, int offset, int length,
|
||||
String text, AttributeSet attrs)
|
||||
throws BadLocationException
|
||||
{
|
||||
String current = doc.getText(0, doc.getLength());
|
||||
String potential = current.substring(0, offset) + text +
|
||||
current.substring(offset + length);
|
||||
transform(fb, current, potential);
|
||||
}
|
||||
|
||||
protected void transform (FilterBypass fb,
|
||||
String current, String potential)
|
||||
throws BadLocationException
|
||||
{
|
||||
boolean wouldaBeenEqual = current.equals(potential);
|
||||
potential = transform(potential);
|
||||
boolean selection = (getSelectionEnd() != getSelectionStart());
|
||||
// we only change it if it needs changing
|
||||
if (!current.equals(potential) ||
|
||||
// or if it would have been the same pre-transforming
|
||||
// and there is a selection (IE undo the selection)
|
||||
(wouldaBeenEqual && selection)) {
|
||||
if (selection) {
|
||||
// undo the selection to not cause an exception
|
||||
setCaretPosition(0);
|
||||
}
|
||||
fb.replace(0, doc.getLength(), potential, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the specified text is formatted and within
|
||||
* the bounds.
|
||||
*/
|
||||
protected String transform (String text)
|
||||
{
|
||||
// if we're at least the minvalue, everything's ok
|
||||
int val = unformatSafe(text);
|
||||
if (val >= _minValue) {
|
||||
return format(Math.min(_maxValue, val));
|
||||
}
|
||||
|
||||
// otherwise, see if we can append zeros and make a valid value,
|
||||
// remembering how many digits we added
|
||||
int newVal = val;
|
||||
int digits = 0;
|
||||
if (newVal > 0) {
|
||||
while (newVal * 10 <= _maxValue) {
|
||||
newVal *= 10;
|
||||
digits++;
|
||||
if (newVal >= _minValue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if that didn't work, just set it to the min value and
|
||||
// highlight all the digits
|
||||
if (newVal < _minValue) {
|
||||
newVal = _minValue;
|
||||
digits = String.valueOf(newVal).length();
|
||||
}
|
||||
|
||||
// return the new value, but post an event to immediately
|
||||
// highlight the digits that the user did not enter, so
|
||||
// that they can type over them
|
||||
final String newText = format(newVal);
|
||||
final int fdigits = digits;
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
public void run () {
|
||||
String text = getText();
|
||||
if (text.equals(newText)) {
|
||||
int len = text.length();
|
||||
setSelectionEnd(len);
|
||||
int digits = fdigits;
|
||||
for (int ii = len - 1; ii >= 0; ii--) {
|
||||
if (Character.isDigit(text.charAt(ii))) {
|
||||
--digits;
|
||||
if (digits == 0) {
|
||||
setSelectionStart(ii);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return newText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate min/max.
|
||||
*/
|
||||
protected void validateMinMax (int minValue, int maxValue)
|
||||
{
|
||||
if (minValue < 0) {
|
||||
throw new IllegalArgumentException("minValue must be at least 0");
|
||||
}
|
||||
if (maxValue < minValue) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxValue must be greater than minValue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the int that is represented by this field.
|
||||
*/
|
||||
public int getValue ()
|
||||
{
|
||||
return unformatSafe(getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text to the value specified.
|
||||
*/
|
||||
public void setValue (int value)
|
||||
{
|
||||
setText(format(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current min value.
|
||||
*/
|
||||
public void setMinValue (int minValue)
|
||||
{
|
||||
validateMinMax(minValue, _maxValue);
|
||||
_minValue = minValue;
|
||||
validateText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current max value.
|
||||
*/
|
||||
public void setMaxValue (int maxValue)
|
||||
{
|
||||
validateMinMax(_minValue, maxValue);
|
||||
_maxValue = maxValue;
|
||||
validateText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the value we're displaying is between the minimum and
|
||||
* the maximum.
|
||||
*/
|
||||
protected void validateText ()
|
||||
{
|
||||
setText(getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the specified monetary value into a string.
|
||||
* This just puts commas in.
|
||||
*/
|
||||
public static String format (int value)
|
||||
{
|
||||
return _formatter.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers, with commas being ok.
|
||||
*/
|
||||
public static int unformat (String text)
|
||||
throws ParseException
|
||||
{
|
||||
return _formatter.parse(text).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse numbers, don't throw exceptions.
|
||||
*/
|
||||
public static int unformatSafe (String text)
|
||||
{
|
||||
try {
|
||||
return unformat(text);
|
||||
} catch (ParseException pe) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our own special Document class.
|
||||
*/
|
||||
protected static class IntDocument extends PlainDocument
|
||||
{
|
||||
// documentation inherited
|
||||
protected void fireRemoveUpdate (DocumentEvent e)
|
||||
{
|
||||
// suppress: the DocumentFilter implements replace by doing
|
||||
// a remove and then an insert. We do not want listeners to ever
|
||||
// be notified when the document contains invalid text.
|
||||
// (Gosh, it'd be nice if we didn't have to hack this, but
|
||||
// the standard implementation has a bunch of methods with
|
||||
// package-protected access. Thanks Sun!)
|
||||
}
|
||||
}
|
||||
|
||||
/** min/max */
|
||||
protected int _minValue, _maxValue;
|
||||
|
||||
/** Formats and parses numbers with commas in them. */
|
||||
protected static NumberFormat _formatter =
|
||||
NumberFormat.getIntegerInstance();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// $Id: IntegerTableCellRenderer.java,v 1.2 2004/05/18 19:08:45 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
/**
|
||||
* A table cell renderer that that formats integers according to the
|
||||
* locale's desires.
|
||||
*/
|
||||
public class IntegerTableCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public IntegerTableCellRenderer ()
|
||||
{
|
||||
setHorizontalAlignment(RIGHT);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void setValue (Object value)
|
||||
{
|
||||
if ((value instanceof Integer) || (value instanceof Long)) {
|
||||
setText(_nfi.format(value) + " ");
|
||||
} else {
|
||||
super.setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// our number formatter
|
||||
protected NumberFormat _nfi = NumberFormat.getIntegerInstance();
|
||||
|
||||
/**
|
||||
* A convenience method for installing this renderer.
|
||||
*/
|
||||
public static void install (JTable table)
|
||||
{
|
||||
table.setDefaultRenderer(Number.class, new IntegerTableCellRenderer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// $Id: JInternalDialog.java,v 1.1 2002/07/09 17:48:23 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JInternalFrame;
|
||||
import javax.swing.JLayeredPane;
|
||||
|
||||
/**
|
||||
* Used for displaying dialogs internally. Be sure to use {@link
|
||||
* #showDialog} and {@link #dismissDialog} to show and hide these dialogs
|
||||
* because they need to do some extra fiddling that the regular show and
|
||||
* hide wouldn't do.
|
||||
*/
|
||||
public class JInternalDialog extends JInternalFrame
|
||||
{
|
||||
/**
|
||||
* Creates a dialog that will display itself in the layered pane of
|
||||
* the frame that contains the supplied component.
|
||||
*/
|
||||
public JInternalDialog (JComponent friend)
|
||||
{
|
||||
this(JLayeredPane.getLayeredPaneAbove(friend));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog that will display itself in the layered pane of
|
||||
* the supplied frame.
|
||||
*/
|
||||
public JInternalDialog (JFrame frame)
|
||||
{
|
||||
this(frame.getLayeredPane());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog that will display itself in the specified layered
|
||||
* pane.
|
||||
*/
|
||||
public JInternalDialog (JLayeredPane parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this dialog to its parent and shows it.
|
||||
*/
|
||||
public void showDialog ()
|
||||
{
|
||||
_parent.add(this, JLayeredPane.PALETTE_LAYER);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides this dialog and removes it from its parent.
|
||||
*/
|
||||
public void dismissDialog ()
|
||||
{
|
||||
setVisible(false);
|
||||
_parent.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans up the interface hierarchy looking for the {@link
|
||||
* JInternalDialog} that contains the supplied child component and
|
||||
* dismisses it.
|
||||
*/
|
||||
public static void dismissDialog (Component child)
|
||||
{
|
||||
if (child == null) {
|
||||
return;
|
||||
} else if (child instanceof JInternalDialog) {
|
||||
((JInternalDialog)child).dismissDialog();
|
||||
} else {
|
||||
dismissDialog(child.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
/** Our parent. */
|
||||
protected JLayeredPane _parent;
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
//
|
||||
// $Id: Label.java,v 1.37 2004/02/25 13:17:41 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Michael Bayne
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import java.awt.font.TextLayout;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.TextAttribute;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
||||
import java.text.AttributedString;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
import com.samskivert.Log;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
import com.samskivert.util.StringUtil;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
/**
|
||||
* The label is a multipurpose text display mechanism that can display
|
||||
* small amounts of text wrapped to fit into a variety of constrained
|
||||
* spaces. It can be requested to conform to a particular width or height
|
||||
* and will expand into the other dimension in order to accomodate the
|
||||
* text at hand. It is not a component, but is intended for use by
|
||||
* components and other more heavyweight entities.
|
||||
*/
|
||||
public class Label implements SwingConstants, LabelStyleConstants
|
||||
{
|
||||
/** The pattern used to mark the start/end of color blocks. */
|
||||
public static final Pattern COLOR_PATTERN =
|
||||
Pattern.compile("#([Xx]|[0-9A-Fa-f]{6}+)");
|
||||
|
||||
private static final Pattern ESCAPED_PATTERN =
|
||||
Pattern.compile("#''([Xx]|[0-9A-Fa-f]{6}+)");
|
||||
|
||||
/**
|
||||
* Filter out any color tags from the specified text.
|
||||
*/
|
||||
public static String filterColors (String txt)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
return COLOR_PATTERN.matcher(txt).replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape any special tags so that they won't be interpreted by the
|
||||
* label.
|
||||
*/
|
||||
public static String escapeColors (String txt)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
return COLOR_PATTERN.matcher(txt).replaceAll("#''$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escape special tags so that they again look correct. Called by
|
||||
* rendering components that do not understand the color tags
|
||||
* to filter colors and unescape any escaped colors.
|
||||
*/
|
||||
public static String unescapeColors (String txt)
|
||||
{
|
||||
return unescapeColors(filterColors(txt), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escape escaped tags so that they look as the users intended.
|
||||
*/
|
||||
private static String unescapeColors (String txt, boolean restore)
|
||||
{
|
||||
if (txt == null) return null;
|
||||
String prefix = restore ? "#" : "%";
|
||||
return ESCAPED_PATTERN.matcher(txt).replaceAll(prefix + "$1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a blank label.
|
||||
*/
|
||||
public Label ()
|
||||
{
|
||||
this("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text.
|
||||
*/
|
||||
public Label (String text)
|
||||
{
|
||||
this(text, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text and configuration
|
||||
* parameters.
|
||||
*/
|
||||
public Label (String text, Color textColor, Font font)
|
||||
{
|
||||
this(text, NORMAL, textColor, null, font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a label with the supplied text and configuration
|
||||
* parameters.
|
||||
*/
|
||||
public Label (
|
||||
String text, int style, Color textColor, Color altColor, Font font)
|
||||
{
|
||||
setText(text);
|
||||
setStyle(style);
|
||||
setTextColor(textColor);
|
||||
setAlternateColor(altColor);
|
||||
setFont(font);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text displayed by this label.
|
||||
*/
|
||||
public String getText ()
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text to be displayed by this label.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*
|
||||
* @return true if the text changed as a result of being set, false if
|
||||
* the label was already displaying the requested text.
|
||||
*/
|
||||
public boolean setText (String text)
|
||||
{
|
||||
// the Java text stuff freaks out in a variety of ways if it is
|
||||
// asked to deal with the empty string, so we fake blank labels by
|
||||
// just using a space
|
||||
if (StringUtil.isBlank(text)) {
|
||||
text = " ";
|
||||
}
|
||||
|
||||
// if there is no change then avoid doing anything
|
||||
if (text.equals((_rawText == null) ? _text : _rawText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// _text should contain the text without any tags
|
||||
// _rawText will be null if there are no tags
|
||||
_text = filterColors(text);
|
||||
_rawText = text.equals(_text) ? null : text;
|
||||
|
||||
// if what we were passed contains escaped color tags, unescape them
|
||||
_text = unescapeColors(_text, true);
|
||||
if (_rawText != null) {
|
||||
_rawText = unescapeColors(_rawText, false);
|
||||
}
|
||||
|
||||
invalidate("setText");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the font to be used by this label. If the font is not set, the
|
||||
* current font of the graphics context will be used.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setFont (Font font)
|
||||
{
|
||||
_font = font;
|
||||
invalidate("setFont");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color used to render the text.
|
||||
*/
|
||||
public Color getTextColor ()
|
||||
{
|
||||
return _textColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color used to render the text. Setting the text color to
|
||||
* <code>null</code> will render the label in the graphics context
|
||||
* color (which is the default).
|
||||
*/
|
||||
public void setTextColor (Color color)
|
||||
{
|
||||
_textColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alternate color used to render the text's outline or
|
||||
* shadow, if any.
|
||||
*/
|
||||
public Color getAlternateColor ()
|
||||
{
|
||||
return _alternateColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the label to render the text with the specified alternate
|
||||
* color when rendering. The text itself will be rendered in whatever
|
||||
* color is currently set in the graphics context, but the outline or
|
||||
* shadow (if any) will always be in the specified color.
|
||||
*/
|
||||
public void setAlternateColor (Color color)
|
||||
{
|
||||
_alternateColor = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alignment of the text within the label.
|
||||
*/
|
||||
public int getAlignment ()
|
||||
{
|
||||
return _align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alignment of the text within the label to either {@link
|
||||
* SwingConstants#LEFT}, {@link SwingConstants#RIGHT}, or {@link
|
||||
* SwingConstants#CENTER}. The default alignment is selected to be
|
||||
* appropriate for the locale of the text being rendered.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setAlignment (int align)
|
||||
{
|
||||
_align = align;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of the text within the label to one of the styles
|
||||
* defined in {@link LabelStyleConstants}. Some styles can be combined
|
||||
* together into a mask, ie. <code>BOLD|UNDERLINE</code>.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setStyle (int style)
|
||||
{
|
||||
_style = style;
|
||||
invalidate("setStyle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Instructs the label to attempt to achieve a balance between width
|
||||
* and height that approximates the golden ratio (width ~1.618 times
|
||||
* height).
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setGoldenLayout ()
|
||||
{
|
||||
// use -1 as an indicator that we should be golden
|
||||
_constraints.width = -1;
|
||||
_constraints.height = -1;
|
||||
invalidate("setGoldenLayout");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target width for this label. Text will be wrapped to fit
|
||||
* into this width, forcibly breaking words on character boundaries if
|
||||
* a single word is too long to fit into the target width. Calling
|
||||
* this method will annul any previously established target height as
|
||||
* we must have one degree of freedom in which to maneuver.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setTargetWidth (int targetWidth)
|
||||
{
|
||||
if (targetWidth <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid target width '" + targetWidth + "'");
|
||||
}
|
||||
_constraints.width = targetWidth;
|
||||
_constraints.height = 0;
|
||||
invalidate("setTargetWidth");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the target height for this label. A simple algorithm will be
|
||||
* used to balance the width of the text in order that there are only
|
||||
* as many lines of text as fit into the target height. If rendering
|
||||
* the label as a single line of text causes it to be taller than the
|
||||
* target height, we simply render ourselves anyway. Calling this
|
||||
* method will annul any previously established target width as we
|
||||
* must have one degree of freedom in which to maneuver.
|
||||
*
|
||||
* <p> This should be followed by a call to {@link #layout} before a
|
||||
* call is made to {@link #render} as this method invalidates the
|
||||
* layout information.
|
||||
*/
|
||||
public void setTargetHeight (int targetHeight)
|
||||
{
|
||||
if (targetHeight <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid target height '" + targetHeight + "'");
|
||||
}
|
||||
_constraints.width = 0;
|
||||
_constraints.height = targetHeight;
|
||||
invalidate("setTargetHeight");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears out previously configured target dimensions for this label.
|
||||
*/
|
||||
public void clearTargetDimens ()
|
||||
{
|
||||
_constraints.width = 0;
|
||||
_constraints.height = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of lines used by this label.
|
||||
*/
|
||||
public int getLineCount ()
|
||||
{
|
||||
return _layouts.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns our computed dimensions. Only valid after a call to {@link
|
||||
* #layout}.
|
||||
*/
|
||||
public Dimension getSize ()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this label has been laid out, false if not.
|
||||
*/
|
||||
public boolean isLaidOut ()
|
||||
{
|
||||
return (_layouts != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #layout(Graphics2D)} with the graphics context for the
|
||||
* given component.
|
||||
*/
|
||||
public void layout (Component comp)
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D)comp.getGraphics();
|
||||
if (gfx != null) {
|
||||
layout(gfx);
|
||||
gfx.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this label lay out its text, obtaining information
|
||||
* from the supplied graphics context to do so. It is expected that
|
||||
* the label will be subsequently rendered in the same graphics
|
||||
* context or at least one that is configured very similarly. If not,
|
||||
* wackiness may ensue.
|
||||
*/
|
||||
public void layout (Graphics2D gfx)
|
||||
{
|
||||
FontRenderContext frc = gfx.getFontRenderContext();
|
||||
ArrayList layouts = null;
|
||||
|
||||
// if we have a target height, do some processing and convert that
|
||||
// into a target width
|
||||
if (_constraints.height > 0 || _constraints.width == -1) {
|
||||
int targetHeight = _constraints.height;
|
||||
|
||||
// if we're approximating the golden ratio, target a height
|
||||
// that gets us near that ratio, then we can err on the side
|
||||
// of being a bit wider which is generally nicer than being
|
||||
// taller (for those of us that don't speak verticall written
|
||||
// languages, anyway)
|
||||
if (_constraints.width == -1) {
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
|
||||
int lines = 1;
|
||||
double width = getWidth(bounds)/lines;
|
||||
double height = getHeight(layout)*lines;
|
||||
double delta = Math.abs(width/height - GOLDEN_RATIO);
|
||||
|
||||
do {
|
||||
width = getWidth(bounds) / (lines+1);
|
||||
double nheight = getHeight(layout) * (lines+1);
|
||||
double ndelta = Math.abs(width/nheight - GOLDEN_RATIO);
|
||||
if (delta <= ndelta) {
|
||||
break;
|
||||
}
|
||||
delta = ndelta;
|
||||
height = nheight;
|
||||
} while (++lines < 200); // cap ourselves at 200 lines
|
||||
|
||||
targetHeight = (int)Math.ceil(height);
|
||||
}
|
||||
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
int lines = Math.round(targetHeight / getHeight(layout));
|
||||
if (lines > 1) {
|
||||
int targetWidth = (int)Math.round(getWidth(bounds) / lines);
|
||||
|
||||
// attempt to lay the text out in the specified width,
|
||||
// incrementing by 10% each time; limit our attempts to 10
|
||||
// expansions to avoid infinite loops if something is fucked
|
||||
for (int i = 0; i < 10; i++) {
|
||||
LineBreakMeasurer measurer =
|
||||
new LineBreakMeasurer(textIterator(gfx), frc);
|
||||
layouts = computeLines(measurer, targetWidth, _size, true);
|
||||
if ((layouts != null) && (layouts.size() <= lines)) {
|
||||
break;
|
||||
}
|
||||
targetWidth = (int)Math.round(targetWidth * 1.1);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (_constraints.width > 0) {
|
||||
LineBreakMeasurer measurer =
|
||||
new LineBreakMeasurer(textIterator(gfx), frc);
|
||||
layouts = computeLines(measurer, _constraints.width, _size, false);
|
||||
}
|
||||
|
||||
// if no constraint, or our constraining height puts us on one line
|
||||
// then layout on one line and call it good
|
||||
if (layouts == null) {
|
||||
TextLayout layout = new TextLayout(textIterator(gfx), frc);
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
// for some reason JDK1.3 on Linux chokes on setSize(double,double)
|
||||
_size.setSize(Math.ceil(getWidth(bounds)),
|
||||
Math.ceil(getHeight(layout)));
|
||||
layouts = new ArrayList();
|
||||
layouts.add(new Tuple(layout, bounds));
|
||||
}
|
||||
|
||||
// create our layouts array
|
||||
int lcount = layouts.size();
|
||||
_layouts = new TextLayout[lcount];
|
||||
_lbounds = new Rectangle2D[lcount];
|
||||
_leaders = new float[lcount];
|
||||
for (int ii = 0; ii < lcount; ii++) {
|
||||
Tuple tup = (Tuple)layouts.get(ii);
|
||||
_layouts[ii] = (TextLayout)tup.left;
|
||||
_lbounds[ii] = (Rectangle2D)tup.right;
|
||||
// account for potential leaders
|
||||
if (_lbounds[ii].getX() < 0) {
|
||||
_leaders[ii] = (float)-_lbounds[ii].getX();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the lines of text for this label given the specified
|
||||
* target width. The overall size of the computed lines is stored into
|
||||
* the <code>size</code> parameter.
|
||||
*
|
||||
* @return an {@link ArrayList} or null if <code>keepWordsWhole</code>
|
||||
* was true and the lines could not be layed out in the target width.
|
||||
*/
|
||||
protected ArrayList computeLines (
|
||||
LineBreakMeasurer measurer, int targetWidth, Dimension size,
|
||||
boolean keepWordsWhole)
|
||||
{
|
||||
// start with a size of zero
|
||||
double width = 0, height = 0;
|
||||
ArrayList layouts = new ArrayList();
|
||||
|
||||
try {
|
||||
// obtain our new dimensions by using a line break iterator to
|
||||
// lay out our text one line at a time
|
||||
TextLayout layout;
|
||||
int lastposition = _text.length();
|
||||
while (true) {
|
||||
int nextret = _text.indexOf('\n', measurer.getPosition() + 1);
|
||||
if (nextret == -1) {
|
||||
nextret = lastposition;
|
||||
}
|
||||
layout = measurer.nextLayout(
|
||||
targetWidth, nextret, keepWordsWhole);
|
||||
if (layout == null) {
|
||||
break;
|
||||
}
|
||||
Rectangle2D bounds = getBounds(layout);
|
||||
width = Math.max(width, getWidth(bounds));
|
||||
height += getHeight(layout);
|
||||
layouts.add(new Tuple(layout, bounds));
|
||||
}
|
||||
|
||||
// fill in the computed size; for some reason JDK1.3 on Linux
|
||||
// chokes on setSize(double,double)
|
||||
size.setSize(Math.ceil(width), Math.ceil(height));
|
||||
|
||||
// this can only happen if keepWordsWhole is true
|
||||
if (measurer.getPosition() < lastposition) {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Throwable t) {
|
||||
Log.warning("Label layout failed [text=" + _text +
|
||||
", t=" + t + "].");
|
||||
Log.logStackTrace(t);
|
||||
}
|
||||
|
||||
return layouts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the layout at the specified position in the supplied
|
||||
* graphics context.
|
||||
*/
|
||||
public void render (Graphics2D gfx, float x, float y)
|
||||
{
|
||||
// nothing to do if we haven't been laid out
|
||||
if (_layouts == null) {
|
||||
Log.warning(hashCode() + " Unlaid-out label asked to render " +
|
||||
"[text=" + _text +
|
||||
// ", last=" + _invalidator +
|
||||
"].");
|
||||
return;
|
||||
}
|
||||
|
||||
Color old = gfx.getColor();
|
||||
if (_textColor != null) {
|
||||
gfx.setColor(_textColor);
|
||||
}
|
||||
|
||||
// render our text
|
||||
for (int i = 0; i < _layouts.length; i++) {
|
||||
TextLayout layout = _layouts[i];
|
||||
Rectangle2D lbounds = _lbounds[i];
|
||||
y += layout.getAscent();
|
||||
|
||||
float extra = (float)Math.floor(_size.width - getWidth(lbounds));
|
||||
float rx;
|
||||
switch (_align) {
|
||||
case -1: rx = x + (layout.isLeftToRight() ? 0 : extra); break;
|
||||
default:
|
||||
case LEFT: rx = x; break;
|
||||
case RIGHT: rx = x + extra; break;
|
||||
case CENTER: rx = x + extra/2; break;
|
||||
}
|
||||
|
||||
// shift over any lines that start with a font that extends
|
||||
// into negative x-land
|
||||
rx += _leaders[i];
|
||||
|
||||
// System.out.println(i + " x: " + x + " y: " + y + " rx: " + rx +
|
||||
// " a: " + _align + " width: " + _size.width +
|
||||
// " lx: " + lbounds.getX() +
|
||||
// " lwidth: " + getWidth(lbounds) +
|
||||
// " extra: " + extra);
|
||||
|
||||
Color textColor;
|
||||
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
// render the outline using the hacky, but much nicer than
|
||||
// using "real" outlines (via TextLayout.getOutline), method
|
||||
textColor = gfx.getColor();
|
||||
gfx.setColor(_alternateColor);
|
||||
layout.draw(gfx, rx, y);
|
||||
layout.draw(gfx, rx, y + 1);
|
||||
layout.draw(gfx, rx, y + 2);
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
layout.draw(gfx, rx + 1, y + 2);
|
||||
layout.draw(gfx, rx + 2, y);
|
||||
layout.draw(gfx, rx + 2, y + 1);
|
||||
layout.draw(gfx, rx + 2, y + 2);
|
||||
gfx.setColor(textColor);
|
||||
layout.draw(gfx, rx + 1, y + 1);
|
||||
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
textColor = gfx.getColor();
|
||||
gfx.setColor(_alternateColor);
|
||||
layout.draw(gfx, rx, y + 1);
|
||||
gfx.setColor(textColor);
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
|
||||
} else if ((_style & BOLD) != 0) {
|
||||
layout.draw(gfx, rx, y);
|
||||
layout.draw(gfx, rx + 1, y);
|
||||
|
||||
} else {
|
||||
layout.draw(gfx, rx, y);
|
||||
}
|
||||
|
||||
y += layout.getDescent() + layout.getLeading();
|
||||
}
|
||||
|
||||
gfx.setColor(old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an attributed character iterator with our text and the
|
||||
* appropriate font.
|
||||
*/
|
||||
protected AttributedCharacterIterator textIterator (Graphics2D gfx)
|
||||
{
|
||||
// first set up any attributes that apply to the entire text
|
||||
Font font = (_font == null) ? gfx.getFont() : _font;
|
||||
HashMap map = new HashMap();
|
||||
map.put(TextAttribute.FONT, font);
|
||||
if ((_style & UNDERLINE) != 0) {
|
||||
map.put(TextAttribute.UNDERLINE,
|
||||
TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
|
||||
}
|
||||
AttributedString text = new AttributedString(_text, map);
|
||||
|
||||
// add any color attributes for specific segments
|
||||
if (_rawText != null) {
|
||||
Matcher m = COLOR_PATTERN.matcher(_rawText);
|
||||
int startSeg = 0, endSeg = 0;
|
||||
Color lastColor = null;
|
||||
while (m.find()) {
|
||||
// color the segment just passed
|
||||
endSeg += m.start();
|
||||
if (lastColor != null) {
|
||||
text.addAttribute(TextAttribute.FOREGROUND, lastColor,
|
||||
startSeg, endSeg);
|
||||
}
|
||||
|
||||
// parse the tag: start or end a color
|
||||
String group = m.group(1);
|
||||
if ("x".equalsIgnoreCase(group)) {
|
||||
lastColor = null;
|
||||
} else {
|
||||
try {
|
||||
lastColor = new Color(Integer.parseInt(group, 16));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.warning("This shouldn't be possible, the regex " +
|
||||
"is precise [badcolor=" + group + "].");
|
||||
}
|
||||
}
|
||||
|
||||
// prepare for the next segment
|
||||
startSeg = endSeg;
|
||||
// Subtract the end of the segment from endSeg
|
||||
// so that when we add the start of the next match we have
|
||||
// actually added the length of the characters in between.
|
||||
endSeg -= m.end();
|
||||
}
|
||||
// apply any final color to the tail segment
|
||||
if (lastColor != null) {
|
||||
text.addAttribute(TextAttribute.FOREGROUND, lastColor,
|
||||
startSeg, _text.length());
|
||||
}
|
||||
}
|
||||
|
||||
return text.getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the total width of a {@link TextLayout} given bounds
|
||||
* returned from a call to {@link TextLayout#getBounds}.
|
||||
*/
|
||||
protected double getWidth (Rectangle2D laybounds)
|
||||
{
|
||||
double width = Math.max(laybounds.getX(), 0) + laybounds.getWidth();
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
width += 2;
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
width += 1;
|
||||
} else if ((_style & BOLD) != 0) {
|
||||
width += 1;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounds of the supplied text layout in a way that works
|
||||
* around the various befuckeries that currently happen on the Mac.
|
||||
*/
|
||||
protected Rectangle2D getBounds (TextLayout layout)
|
||||
{
|
||||
if (RunAnywhere.isMacOS()) {
|
||||
return layout.getOutline(null).getBounds();
|
||||
} else {
|
||||
return layout.getBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the height based on the leading, ascent and descent rather
|
||||
* than what the layout reports via <code>getBounds()</code> which
|
||||
* rarely seems to have any bearing on reality.
|
||||
*/
|
||||
protected float getHeight (TextLayout layout)
|
||||
{
|
||||
float height = layout.getLeading() + layout.getAscent() +
|
||||
layout.getDescent();
|
||||
if ((_style & OUTLINE) != 0) {
|
||||
height += 2;
|
||||
} else if ((_style & SHADOW) != 0) {
|
||||
height += 1;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the label is changed in such a way that it must be
|
||||
* relaid out before again being rendered.
|
||||
*/
|
||||
protected void invalidate (String where)
|
||||
{
|
||||
_layouts = null;
|
||||
// _invalidator = where;
|
||||
}
|
||||
|
||||
/** The text of the label. */
|
||||
protected String _text;
|
||||
|
||||
/** The raw text, with color tags, or null if there are no color tags. */
|
||||
protected String _rawText;
|
||||
|
||||
/** The text style. */
|
||||
protected int _style;
|
||||
|
||||
/** The text alignment. */
|
||||
protected int _align = -1; // -1 means default according to locale
|
||||
|
||||
/** Our size constraints in either the x or y direction. */
|
||||
protected Dimension _constraints = new Dimension();
|
||||
|
||||
/** Our calculated size. */
|
||||
protected Dimension _size = new Dimension();
|
||||
|
||||
/** Some fonts (God bless 'em) extend to the left of the position at
|
||||
* which you request that they be rendered. We opt to push such lines
|
||||
* to the right sufficiently that they line up with the rest of the
|
||||
* lines (perhaps not the typographically ideal thing to do, but we're
|
||||
* in computer land and when we say our bounds are (0, 0, width,
|
||||
* height) we damned well better not render outside those bounds,
|
||||
* which these wonderful fonts are choosing to do). */
|
||||
protected float[] _leaders;
|
||||
|
||||
/** The font we use when laying out and rendering out text, or null if
|
||||
* we're to use the default font. */
|
||||
protected Font _font;
|
||||
|
||||
/** Formatted text layout instances that contain each line of text. */
|
||||
protected TextLayout[] _layouts;
|
||||
|
||||
/** Formatted text layout instances that contain each line of text. */
|
||||
protected Rectangle2D[] _lbounds;
|
||||
|
||||
/** The color in which to render the text outline or shadow if we're
|
||||
* rendering in outline or shadow mode. */
|
||||
protected Color _alternateColor = null;
|
||||
|
||||
/** The color in which to render the text or null if the text should
|
||||
* be rendered with the graphics context color. */
|
||||
protected Color _textColor = null;
|
||||
|
||||
// /** Used for debugging. */
|
||||
// protected String _invalidator;
|
||||
|
||||
/** An approximation of the golden ratio. */
|
||||
protected static final double GOLDEN_RATIO = 1.618034;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// $Id: LabelSausage.java,v 1.3 2003/05/14 01:52:52 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
|
||||
/**
|
||||
* An abstract lightweight renderer that sizes and renders a label (with
|
||||
* optional icon) in a roundy-ended sausage.
|
||||
*/
|
||||
public abstract class LabelSausage
|
||||
{
|
||||
/**
|
||||
* Constructs a label sausage.
|
||||
*/
|
||||
protected LabelSausage (Label label, Icon icon)
|
||||
{
|
||||
_label = label;
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out the label sausage. It is assumed that the desired label
|
||||
* font is already set in the label.
|
||||
*/
|
||||
protected void layout (Graphics2D gfx, int extraPadding)
|
||||
{
|
||||
layout(gfx, 0, extraPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out the label sausage. It is assumed that the desired label
|
||||
* font is already set in the label.
|
||||
*
|
||||
* @param iconPadding the number of pixels in the x direction to pad
|
||||
* around the icon.
|
||||
*/
|
||||
protected void layout (Graphics2D gfx, int iconPadding, int extraPadding)
|
||||
{
|
||||
// if we have an icon, let that dictate our size; otherwise just
|
||||
// lay out our label all on one line
|
||||
int sqwid, sqhei;
|
||||
if (_icon == null) {
|
||||
sqwid = sqhei = 0;
|
||||
|
||||
} else {
|
||||
sqwid = _icon.getIconWidth();
|
||||
sqhei = _icon.getIconHeight();
|
||||
_label.setTargetHeight(sqhei);
|
||||
}
|
||||
|
||||
// lay out our label
|
||||
_label.layout(gfx);
|
||||
Dimension lsize = _label.getSize();
|
||||
|
||||
// if we have no icon, make sure that the label has enough room
|
||||
if (_icon == null) {
|
||||
sqhei = lsize.height + extraPadding * 2;
|
||||
sqwid = extraPadding * 2;
|
||||
}
|
||||
|
||||
// compute the diameter of the circle that perfectly encompasses our
|
||||
// icon
|
||||
int hhei = sqhei / 2;
|
||||
int hwid = sqwid / 2;
|
||||
_dia = (int) (Math.sqrt(hwid * hwid + hhei * hhei) * 2);
|
||||
|
||||
// compute the x and y offsets at which we'll start rendering
|
||||
_xoff = (_dia - sqwid) / 2;
|
||||
_yoff = (_dia - sqhei) / 2;
|
||||
|
||||
// and for the label
|
||||
_lxoff = _dia - _xoff;
|
||||
_lyoff = (_dia - lsize.height) / 2;
|
||||
|
||||
// now compute our closed and open sizes
|
||||
_size.height = _dia;
|
||||
|
||||
// width is the diameter of the circle that contains
|
||||
// the icon plus space for the label when we're open
|
||||
_size.width = _dia + lsize.width + _xoff;
|
||||
|
||||
// and if we are actually rendering the icon, we need to
|
||||
// account for the space between it and the label.
|
||||
if (_icon != null) {
|
||||
// and add the padding needed for the icon
|
||||
_size.width += _xoff + (iconPadding * 2);
|
||||
_xoff += iconPadding;
|
||||
_lxoff += iconPadding * 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints the label sausage.
|
||||
*/
|
||||
protected void paint (
|
||||
Graphics2D gfx, int x, int y, Color background, Object cliData)
|
||||
{
|
||||
// turn on anti-aliasing
|
||||
Object oalias = SwingUtil.activateAntiAliasing(gfx);
|
||||
|
||||
// draw the base sausage
|
||||
gfx.setColor(background);
|
||||
drawBase(gfx, x, y);
|
||||
|
||||
// render our icon if we've got one
|
||||
drawIcon(gfx, x, y, cliData);
|
||||
|
||||
drawLabel(gfx, x, y);
|
||||
drawBorder(gfx, x, y);
|
||||
|
||||
drawExtras(gfx, x, y, cliData);
|
||||
|
||||
// restore original hints
|
||||
SwingUtil.restoreAntiAliasing(gfx, oalias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the base sausage within which all the other decorations are
|
||||
* added.
|
||||
*/
|
||||
protected void drawBase (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
gfx.fillRoundRect(
|
||||
x, y, _size.width - 1, _size.height - 1, _dia, _dia);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the icon, if applicable.
|
||||
*/
|
||||
protected void drawIcon (Graphics2D gfx, int x, int y, Object cliData)
|
||||
{
|
||||
if (_icon != null) {
|
||||
_icon.paintIcon(null, gfx, x + _xoff, y + _yoff);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the label.
|
||||
*/
|
||||
protected void drawLabel (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
_label.render(gfx, x + _lxoff, y + _lyoff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the black outer border.
|
||||
*/
|
||||
protected void drawBorder (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
// draw the black outer border
|
||||
gfx.setColor(Color.black);
|
||||
gfx.drawRoundRect(x, y, _size.width - 1, _size.height - 1,
|
||||
_dia, _dia);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws any extras that may be required.
|
||||
*/
|
||||
protected void drawExtras (Graphics2D gfx, int x, int y, Object cliData)
|
||||
{
|
||||
// nothing by default
|
||||
}
|
||||
|
||||
/** The label. */
|
||||
protected Label _label;
|
||||
|
||||
/** The optional icon. */
|
||||
protected Icon _icon;
|
||||
|
||||
/** The size of this label sausage. */
|
||||
protected Dimension _size = new Dimension();
|
||||
|
||||
/** The diameter of the circle that perfectly surrounds our icon. */
|
||||
protected int _dia = 0;
|
||||
|
||||
/** The x offset of the icon. */
|
||||
protected int _xoff = 0;
|
||||
|
||||
/** The y offset of the icon. */
|
||||
protected int _yoff = 0;
|
||||
|
||||
/** The y offset for the label. */
|
||||
protected int _lyoff = 0;
|
||||
|
||||
/** The x offset for the label. */
|
||||
protected int _lxoff = 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// $Id: LabelStyleConstants.java,v 1.4 2003/11/15 03:17:28 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Walter Korman
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
/**
|
||||
* Defines text style constants for use with the {@link Label} and {@link
|
||||
* MultiLineLabel}.
|
||||
*/
|
||||
public interface LabelStyleConstants
|
||||
{
|
||||
/** Constant denoting normal text style. */
|
||||
public static final int NORMAL = 0;
|
||||
|
||||
/** Constant denoting bold text style. */
|
||||
public static final int BOLD = 1 << 0;
|
||||
|
||||
/** Constant denoting outline text style. */
|
||||
public static final int OUTLINE = 1 << 1;
|
||||
|
||||
/** Constant denoting shadow text style. */
|
||||
public static final int SHADOW = 1 << 2;
|
||||
|
||||
/** Constant denoting underline text style. */
|
||||
public static final int UNDERLINE = 1 << 3;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// $Id: LazyComponent.java,v 1.1 2004/05/21 20:57:12 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
/**
|
||||
* A component that doesn't actually create its content until it is
|
||||
* visible and actually showing. This was made to support lazy-creation
|
||||
* in JTabbedPane, since normally adding a compent to a JTabbedPane
|
||||
* creates each component and their associated controllers.
|
||||
*/
|
||||
public class LazyComponent extends JComponent
|
||||
{
|
||||
/**
|
||||
* An interface for creating the actual content that will live
|
||||
* in this component.
|
||||
*/
|
||||
public interface ContentCreator
|
||||
{
|
||||
/**
|
||||
* Create the content at the time that it is needed.
|
||||
*/
|
||||
public JComponent createContent ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a lazy component.
|
||||
*/
|
||||
public LazyComponent (ContentCreator creator)
|
||||
{
|
||||
_creator = creator;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void addNotify ()
|
||||
{
|
||||
super.addNotify();
|
||||
|
||||
checkCreate();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void setVisible (boolean vis)
|
||||
{
|
||||
super.setVisible(vis);
|
||||
|
||||
checkCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if we should now create the content.
|
||||
*/
|
||||
protected void checkCreate ()
|
||||
{
|
||||
if (_creator != null && isShowing()) {
|
||||
// ideally, we would replace ourselves in our parent, but that
|
||||
// doesn't seem to work in JTabbedPane
|
||||
setLayout(new BorderLayout());
|
||||
add(_creator.createContent(), BorderLayout.CENTER);
|
||||
_creator = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The content creator. */
|
||||
protected ContentCreator _creator;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
//
|
||||
// $Id: MultiLineLabel.java,v 1.14 2004/02/25 13:17:41 mdb Exp $
|
||||
//
|
||||
// samskivert library - useful routines for java programs
|
||||
// Copyright (C) 2002 Walter Korman
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.RenderingHints;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* A Swing component that displays a {@link Label}.
|
||||
*/
|
||||
public class MultiLineLabel extends JComponent
|
||||
implements SwingConstants, LabelStyleConstants
|
||||
{
|
||||
/** A layout constant used by {@link #setLayout}. */
|
||||
public static final int GOLDEN = HORIZONTAL+VERTICAL+1;
|
||||
|
||||
/** A layout constant used by {@link #setLayout}. */
|
||||
public static final int NONE = GOLDEN+1;
|
||||
|
||||
/**
|
||||
* Constructs an empty multi line label.
|
||||
*/
|
||||
public MultiLineLabel ()
|
||||
{
|
||||
this("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a multi line label that displays the supplied text with
|
||||
* center-alignment. The default layout is all on one line.
|
||||
*
|
||||
* @see #setLayout
|
||||
*/
|
||||
public MultiLineLabel (String text)
|
||||
{
|
||||
this(text, CENTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a multi line label that displays the supplied text with
|
||||
* the specified alignment. The default layout is all on one line.
|
||||
*
|
||||
* @see #setLayout
|
||||
*/
|
||||
public MultiLineLabel (String text, int align)
|
||||
{
|
||||
this(text, align, NONE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a multi line label that displays the supplied text with
|
||||
* the specified alignment. The default layout is all on one line.
|
||||
*
|
||||
* @see #setLayout
|
||||
*/
|
||||
public MultiLineLabel (String text, int align, int constrain, int size)
|
||||
{
|
||||
_label = new Label(text);
|
||||
_label.setAlignment(align);
|
||||
noteConstraints(constrain, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this label's text should be rendered with
|
||||
* anti-aliasing.
|
||||
*/
|
||||
public void setAntiAliased (boolean antialiased)
|
||||
{
|
||||
_antialiased = antialiased;
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraints to be used when laying out the label.
|
||||
*
|
||||
* @param constrain {@link #HORIZONTAL} or {@link #VERTICAL} or {@link
|
||||
* #GOLDEN} if the label should be laid out in a rectangle whose
|
||||
* bounds approximate the golden ratio.
|
||||
* @param size the width or height respectively to be targeted by the
|
||||
* label or 0 if the label should react the first time it is laid out
|
||||
* and use the dimension available at that point. <em>Note:</em> this
|
||||
* requires that the label invalidate itself during its first
|
||||
* validation which will cause it to change size visibly in the user
|
||||
* interface. This argument is ignored if <code>constrain</code> is
|
||||
* {@link #GOLDEN}.
|
||||
*/
|
||||
public void setLayout (int constrain, int size)
|
||||
{
|
||||
noteConstraints(constrain, size);
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** Helper function. */
|
||||
protected void noteConstraints (int constrain, int size)
|
||||
{
|
||||
switch (constrain) {
|
||||
case HORIZONTAL:
|
||||
if (size == 0) {
|
||||
_constrain = HORIZONTAL;
|
||||
} else {
|
||||
_label.setTargetWidth(size);
|
||||
}
|
||||
break;
|
||||
|
||||
case VERTICAL:
|
||||
if (size == 0) {
|
||||
_constrain = VERTICAL;
|
||||
} else {
|
||||
_label.setTargetHeight(size);
|
||||
}
|
||||
break;
|
||||
|
||||
case GOLDEN:
|
||||
_label.setGoldenLayout();
|
||||
break;
|
||||
|
||||
case NONE:
|
||||
// nothing doing
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid constraint orientation " + constrain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text displayed by this label.
|
||||
*/
|
||||
public void setText (String text)
|
||||
{
|
||||
if (_label.setText(text)) {
|
||||
_dirty = true;
|
||||
// clear out our constrained size where appropriate
|
||||
if (_constrain == HORIZONTAL || _constrain == VERTICAL) {
|
||||
_constrainedSize = 0;
|
||||
_label.clearTargetDimens();
|
||||
}
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text displayed by this label.
|
||||
*/
|
||||
public String getText ()
|
||||
{
|
||||
return _label.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alternate color used to display the label text.
|
||||
*/
|
||||
public void setAlternateColor (Color color)
|
||||
{
|
||||
_label.setAlternateColor(color);
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alignment of the text displayed by this label.
|
||||
*/
|
||||
public void setAlignment (int align)
|
||||
{
|
||||
_label.setAlignment(align);
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the off-axis alignment of the text displayed by this label.
|
||||
*/
|
||||
public void setOffAxisAlignment (int align)
|
||||
{
|
||||
_offalign = align;
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text style used to render this label.
|
||||
*/
|
||||
public void setStyle (int style)
|
||||
{
|
||||
_label.setStyle(style);
|
||||
_dirty = true;
|
||||
repaint();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void paintComponent (Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
|
||||
// if we're dirty, re-lay things out before painting ourselves
|
||||
if (_dirty) {
|
||||
layoutLabel();
|
||||
}
|
||||
|
||||
Graphics2D gfx = (Graphics2D)g;
|
||||
int align = _label.getAlignment();
|
||||
int dx = 0, dy = 0;
|
||||
int wid = getWidth(), hei = getHeight();
|
||||
Dimension ld = _label.getSize();
|
||||
|
||||
// set the text color to our foreground color
|
||||
_label.setTextColor(getForeground());
|
||||
|
||||
// calculate the x-offset at which the label is rendered
|
||||
switch (align) {
|
||||
case CENTER: dx = (wid - ld.width) / 2; break;
|
||||
case RIGHT: dx = wid - ld.width; break;
|
||||
}
|
||||
|
||||
// calculate the y-offset at which the label is rendered
|
||||
switch (_offalign) {
|
||||
case CENTER: dy = (hei - ld.height) / 2; break;
|
||||
case BOTTOM: dy = hei - ld.height; break;
|
||||
}
|
||||
|
||||
// draw the label
|
||||
_label.render(gfx, dx, dy);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void doLayout ()
|
||||
{
|
||||
super.doLayout();
|
||||
|
||||
// if we have been configured to relay ourselves out once we know
|
||||
// our constrained width or height, take care of that here
|
||||
int size;
|
||||
boolean delayedRevalidate = false;
|
||||
switch (_constrain) {
|
||||
case HORIZONTAL:
|
||||
size = getWidth();
|
||||
// sanity check; sometimes labels are laid out with completely
|
||||
// invalid dimensions, so we just quietly play along
|
||||
if (size > 0 && size != _constrainedSize) {
|
||||
_constrainedSize = size;
|
||||
_label.setTargetWidth(size);
|
||||
delayedRevalidate = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case VERTICAL:
|
||||
size = getHeight();
|
||||
if (size > 0 && size != _constrainedSize) {
|
||||
_constrainedSize = size;
|
||||
_label.setTargetHeight(size);
|
||||
delayedRevalidate = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// we can't just call revalidate() because we're in the middle of
|
||||
// a validation traversal; our parent will, when we return from
|
||||
// this method, declare itself to be valid; if we call
|
||||
// revalidate() it will mark us and all of our parents as invalid,
|
||||
// but then those of our parents that are involved in the first
|
||||
// validation traversal will mark themselves as valid and in the
|
||||
// validation pass that results from our call to revalidate() we
|
||||
// will be skipped over; dooh! instead we delay a call to
|
||||
// revalidate so that the current validation traversal will be
|
||||
// completed before we mark ourselves and our parents as invalid
|
||||
if (delayedRevalidate) {
|
||||
Runnable callRevalidate = new Runnable() {
|
||||
public void run() {
|
||||
revalidate();
|
||||
}
|
||||
};
|
||||
SwingUtilities.invokeLater(callRevalidate);
|
||||
}
|
||||
|
||||
// go ahead and lay out the label in all cases so that we assume
|
||||
// some sort of size
|
||||
layoutLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the label has changed in some meaningful way and we'd
|
||||
* accordingly like to re-layout the label, update our component's
|
||||
* size, and repaint everything to suit.
|
||||
*/
|
||||
protected void layoutLabel ()
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D)getGraphics();
|
||||
if (gfx != null) {
|
||||
// re-layout the label
|
||||
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
(_antialiased) ?
|
||||
RenderingHints.VALUE_ANTIALIAS_ON :
|
||||
RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
_label.layout(gfx);
|
||||
gfx.dispose();
|
||||
|
||||
// note that we're no longer dirty
|
||||
_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Dimension getPreferredSize ()
|
||||
{
|
||||
if (_dirty) {
|
||||
// attempt to lay out the label before obtaining its preferred
|
||||
// dimensions
|
||||
layoutLabel();
|
||||
}
|
||||
|
||||
Dimension size = _label.getSize();
|
||||
if (size != null) {
|
||||
// never let our preferred size shrink in our constrained
|
||||
// direction
|
||||
switch (_constrain) {
|
||||
case HORIZONTAL:
|
||||
_prefd.width = Math.max(_prefd.width, size.width);
|
||||
_prefd.height = size.height;
|
||||
break;
|
||||
|
||||
case VERTICAL:
|
||||
_prefd.width = size.width;
|
||||
_prefd.height = Math.max(_prefd.height, size.height);
|
||||
break;
|
||||
|
||||
default:
|
||||
_prefd.width = size.width;
|
||||
_prefd.height = size.height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _prefd;
|
||||
}
|
||||
|
||||
/** Our preferred size. */
|
||||
protected Dimension _prefd = new Dimension(5, 5);
|
||||
|
||||
/** The label we're displaying. */
|
||||
protected Label _label;
|
||||
|
||||
/** The off-axis alignment with which the label is positioned. */
|
||||
protected int _offalign;
|
||||
|
||||
/** Pending constraint adjustments. */
|
||||
protected int _constrain = NONE;
|
||||
|
||||
/** The size to which we constrained ourselves when most recently laid
|
||||
* out. */
|
||||
protected int _constrainedSize;
|
||||
|
||||
/** Whether to render the label with anti-aliasing. */
|
||||
protected boolean _antialiased;
|
||||
|
||||
/** Whether this label is dirty and should be re-layed out. */
|
||||
protected boolean _dirty = true;
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
//
|
||||
// $Id: ObjectEditorTable.java,v 1.4 2004/06/15 17:57:17 ray Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
|
||||
import com.samskivert.util.ClassUtil;
|
||||
import com.samskivert.util.CollectionUtil;
|
||||
import com.samskivert.util.ListUtil;
|
||||
|
||||
import com.samskivert.Log;
|
||||
|
||||
/**
|
||||
* Allows simple displaying and editing of Objects in a table format.
|
||||
*/
|
||||
public class ObjectEditorTable extends JTable
|
||||
{
|
||||
/**
|
||||
* The default FieldInterpreter, which can be used to customize the
|
||||
* name, values, and editing of a field in an Object.
|
||||
*
|
||||
* There are a number of ways that the field editing can be customized.
|
||||
* A custom renderer (and editor) may be installed on the table, possibly
|
||||
* in conjunction with overriding getClass(). Or you may simply override
|
||||
* getValue (and setValue) to interpret between types, say for instance
|
||||
* turning an integer field that may be one of three constant values into
|
||||
* String names of the values.
|
||||
*/
|
||||
public static class FieldInterpreter
|
||||
{
|
||||
/**
|
||||
* Get the name that she be used for the column header for the specified
|
||||
* field. By default it's merely the name of the field.
|
||||
*/
|
||||
public String getName (Field field)
|
||||
{
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class of the specified field. By default, the class of
|
||||
* the field is used, or its object equivalent if it is a primitive
|
||||
* class.
|
||||
*/
|
||||
public Class getClass (Field field)
|
||||
{
|
||||
Class clazz = field.getType();
|
||||
return ClassUtil.objectEquivalentOf(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the specified field in the specified object.
|
||||
* By default, the field is used to directly access the value.
|
||||
*/
|
||||
public Object getValue (Object obj, Field field)
|
||||
{
|
||||
try {
|
||||
return field.get(obj);
|
||||
} catch (IllegalAccessException iae) {
|
||||
Log.logStackTrace(iae);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the specified field in the specified object.
|
||||
* By default, the field is used to directly set the value.
|
||||
*/
|
||||
public void setValue (Object obj, Object value, Field field)
|
||||
{
|
||||
try {
|
||||
field.set(obj, value);
|
||||
} catch (IllegalAccessException iae) {
|
||||
Log.logStackTrace(iae);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a table to display the specified class.
|
||||
*
|
||||
* @param protoClass the Class of the data that will be displayed.
|
||||
*/
|
||||
public ObjectEditorTable (Class protoClass)
|
||||
{
|
||||
this(protoClass, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a table to display and edit the specified class.
|
||||
*
|
||||
* @param protoClass the Class of the data that will be displayed.
|
||||
* @param editableFields the names of the fields that are editable.
|
||||
*/
|
||||
public ObjectEditorTable (Class protoClass, String[] editableFields)
|
||||
{
|
||||
this(protoClass, editableFields, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a table to display and edit the specified class.
|
||||
*
|
||||
* @param protoClass the Class of the data that will be displayed.
|
||||
* @param editableFields the names of the fields that are editable.
|
||||
* @param interp The {@link FieldInterpreter} to use.
|
||||
*/
|
||||
public ObjectEditorTable (Class protoClass, String[] editableFields,
|
||||
FieldInterpreter interp)
|
||||
{
|
||||
this(protoClass, editableFields, interp, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a table to display and edit the specified class.
|
||||
*
|
||||
* @param protoClass the Class of the data that will be displayed.
|
||||
* @param editableFields the names of the fields that are editable.
|
||||
* @param interp The {@link FieldInterpreter} to use.
|
||||
* @param displayFields the fields to display, or null to display all.
|
||||
*/
|
||||
public ObjectEditorTable (Class protoClass, String[] editableFields,
|
||||
FieldInterpreter interp, String[] displayFields)
|
||||
{
|
||||
_interp = (interp != null) ? interp : new FieldInterpreter();
|
||||
|
||||
// figure out which fields we're going to display
|
||||
Field[] fields = ClassUtil.getFields(protoClass);
|
||||
if (displayFields != null) {
|
||||
_fields = new Field[displayFields.length];
|
||||
for (int ii=0; ii < displayFields.length; ii++) {
|
||||
for (int jj=0; jj < fields.length; jj++) {
|
||||
if (displayFields[ii].equals(fields[jj].getName())) {
|
||||
_fields[ii] = fields[jj];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_fields[ii] == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Field not found in prototype class! " +
|
||||
"[class=" + protoClass +
|
||||
", field=" + displayFields[ii] + "].");
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_fields = fields; // use all the fields
|
||||
}
|
||||
|
||||
// figure out which fields are editable
|
||||
for (int ii=0, nn=_fields.length; ii < nn; ii++) {
|
||||
if (ListUtil.contains(editableFields, _fields[ii].getName())) {
|
||||
_editable.set(ii);
|
||||
}
|
||||
}
|
||||
|
||||
setModel(_model);
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data to be viewed or edited.
|
||||
*/
|
||||
public void setData (Object[] data)
|
||||
{
|
||||
_data.clear();
|
||||
CollectionUtil.addAll(_data, data);
|
||||
_model.fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data to be viewed or edited.
|
||||
*/
|
||||
public void setData (Collection data)
|
||||
{
|
||||
_data.clear();
|
||||
_data.addAll(data);
|
||||
_model.fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this table to merely display / edit one lousy Object.
|
||||
*/
|
||||
public void setData (Object data)
|
||||
{
|
||||
_data.clear();
|
||||
_data.add(data);
|
||||
_model.fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified element to the end of the data set.
|
||||
*/
|
||||
public void addDatum (Object element)
|
||||
{
|
||||
insertDatum(element, _data.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the specified element at the specified row.
|
||||
*/
|
||||
public void insertDatum (Object element, int row)
|
||||
{
|
||||
_data.add(row, element);
|
||||
_model.fireTableRowsInserted(row, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value of the specified row.
|
||||
*/
|
||||
public void updateDatum (Object element, int row)
|
||||
{
|
||||
_data.set(row, element);
|
||||
_model.fireTableRowsUpdated(row, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified element from the set of data.
|
||||
*/
|
||||
public void removeDatum (Object element)
|
||||
{
|
||||
int dex = _data.indexOf(element);
|
||||
if (dex != -1) {
|
||||
removeDatum(dex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified row.
|
||||
*/
|
||||
public void removeDatum (int row)
|
||||
{
|
||||
_data.remove(row);
|
||||
_model.fireTableRowsDeleted(row, row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edited data. Not really needed, since the the data being
|
||||
* edited is the same objects that were passed in.
|
||||
*/
|
||||
public Object[] getData ()
|
||||
{
|
||||
return _data.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected object, or null if none selected.
|
||||
*/
|
||||
public Object getSelectedObject ()
|
||||
{
|
||||
int row = getSelectedRow();
|
||||
return (row == -1) ? null : _data.get(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action listener to this table.
|
||||
*
|
||||
* When any field is changed in the table, an action will be fired with
|
||||
* a {@link CommandEvent}, with source being the table, the command
|
||||
* being the name of the field that was updated, and the argument being
|
||||
* the object that was updated. Note that no event is fired if a field
|
||||
* was edited but the value did not change.
|
||||
*/
|
||||
public void addActionListener (ActionListener listener)
|
||||
{
|
||||
listenerList.add(ActionListener.class, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified action listener.
|
||||
*/
|
||||
public void removeActionListener (ActionListener listener)
|
||||
{
|
||||
listenerList.remove(ActionListener.class, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* A table model that uses the FieldInterpreter to muck with the objects.
|
||||
*/
|
||||
protected AbstractTableModel _model = new AbstractTableModel() {
|
||||
/**
|
||||
* Get the object at the specified row. Useful for our subclass.
|
||||
*/
|
||||
public Object getObjectAt (int row) {
|
||||
return _data.get(row);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getColumnCount ()
|
||||
{
|
||||
return _fields.length;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public int getRowCount()
|
||||
{
|
||||
return _data.size();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public String getColumnName (int col)
|
||||
{
|
||||
return _interp.getName(_fields[col]);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public boolean isCellEditable (int row, int col)
|
||||
{
|
||||
return _editable.get(col);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Class getColumnClass (int col)
|
||||
{
|
||||
return _interp.getClass(_fields[col]);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Object getValueAt (int row, int col)
|
||||
{
|
||||
Object o = getObjectAt(row);
|
||||
return _interp.getValue(o, _fields[col]);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void setValueAt (Object value, int row, int col)
|
||||
{
|
||||
Object o = getObjectAt(row);
|
||||
Object oldValue = _interp.getValue(o, _fields[col]);
|
||||
// we only set the value if it has changed
|
||||
if ((oldValue == null && value != null) ||
|
||||
!oldValue.equals(value)) {
|
||||
_interp.setValue(o, value, _fields[col]);
|
||||
|
||||
// fire the event
|
||||
CommandEvent event = null;
|
||||
Object[] listeners = ObjectEditorTable.this.listenerList
|
||||
.getListenerList();
|
||||
for (int ii=listeners.length-2; ii >= 0; ii -= 2) {
|
||||
if (listeners[ii] == ActionListener.class) {
|
||||
// lazy-create the event
|
||||
if (event == null) {
|
||||
event = new CommandEvent(ObjectEditorTable.this,
|
||||
_fields[col].getName(), o);
|
||||
}
|
||||
((ActionListener) listeners[ii+1]).actionPerformed(
|
||||
event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** The list of fields in the prototypical object. */
|
||||
protected Field[] _fields;
|
||||
|
||||
/** An interpreter that is used to massage values in and out of the
|
||||
* objects. */
|
||||
protected FieldInterpreter _interp;
|
||||
|
||||
/** A list of flags corresponding to the _fields (and the table columns)
|
||||
* that indicate if the field is editable. */
|
||||
protected BitSet _editable = new BitSet();
|
||||
|
||||
/** The data being edited. */
|
||||
protected ArrayList _data = new ArrayList();
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// $Id: RadialLabelSausage.java,v 1.1 2003/04/15 20:28:36 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Stroke;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
/**
|
||||
* Specializes the label sausage to suit the needs of items placed within
|
||||
* a radial menu. The colors used to render the radial label sausage must
|
||||
* be configured via the {@link UIManager}. These colors are:
|
||||
*
|
||||
* <pre>
|
||||
* RadialLabelSausage.activeBorder
|
||||
* RadialLabelSausage.inactiveBorder
|
||||
* RadialLabelSausage.background
|
||||
* </pre>
|
||||
*
|
||||
* This would be configured in a custom look and feel like so:
|
||||
*
|
||||
* <pre>
|
||||
* protected void initComponentDefaults (UIDefaults table)
|
||||
* {
|
||||
* super.initComponentDefaults(table);
|
||||
* Object[] defaults = {
|
||||
* "RadialLabelSausage.inactiveBorder", new ColorUIResource(0x478ABA),
|
||||
* "RadialLabelSausage.activeBorder", new ColorUIResource(0xE0A000),
|
||||
* "RadialLabelSausage.background", new ColorUIResource(0xD5E7E7),
|
||||
* };
|
||||
* table.putDefaults(defaults);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* An application that is not using a custom look and feel can simply set
|
||||
* these values with {@link UIManager#put}.
|
||||
*/
|
||||
public class RadialLabelSausage extends LabelSausage
|
||||
{
|
||||
/** The dimensions of this item when we are in our closed state as
|
||||
* well as our most recently laid out coordinates. This is only valid
|
||||
* after a call to {@link #layout}. */
|
||||
public Rectangle closedBounds = new Rectangle();
|
||||
|
||||
/** The dimensions of this item when we are in open state. This is
|
||||
* only valid after a call to {@link #layout}. */
|
||||
public Rectangle openBounds = new Rectangle();
|
||||
|
||||
/**
|
||||
* Constructs a radial label sausage.
|
||||
*/
|
||||
public RadialLabelSausage (String label, Icon icon)
|
||||
{
|
||||
super(new Label(label), icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this menu item should be enabled when it is
|
||||
* displayed. The default implementation always returns true.
|
||||
*/
|
||||
public boolean isEnabled (RadialMenu menu)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the label is to be rendered as an active label.
|
||||
*/
|
||||
public void setActive (boolean active)
|
||||
{
|
||||
_active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the dimensions of this label based on the specified font
|
||||
* and in the specified graphics context.
|
||||
*/
|
||||
public void layout (Graphics2D gfx, Font font)
|
||||
{
|
||||
_label.setFont(font);
|
||||
layout(gfx, BORDER_THICKNESS);
|
||||
|
||||
openBounds.width = _size.width;
|
||||
openBounds.height = _size.height;
|
||||
|
||||
// and closed up, we're just a circle
|
||||
closedBounds.height = closedBounds.width = _size.height;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void drawLabel (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
if (_active) {
|
||||
super.drawLabel(gfx, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void drawBorder (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
// then around all that draw the borders
|
||||
Stroke ostroke = gfx.getStroke();
|
||||
gfx.setStroke(BORDER_STROKE);
|
||||
if (_active) {
|
||||
// draw the active border
|
||||
gfx.setColor(UIManager.getColor("RadialLabelSausage.activeBorder"));
|
||||
gfx.drawRoundRect(
|
||||
x + (BORDER_THICKNESS / 2), y + (BORDER_THICKNESS / 2),
|
||||
openBounds.width - 1 - BORDER_THICKNESS,
|
||||
openBounds.height - 1 - BORDER_THICKNESS,
|
||||
_dia - BORDER_THICKNESS, _dia - BORDER_THICKNESS);
|
||||
|
||||
// draw the black outer border
|
||||
gfx.setStroke(ostroke);
|
||||
super.drawBorder(gfx, x, y);
|
||||
|
||||
} else {
|
||||
// draw the inactive border
|
||||
gfx.setColor(UIManager.getColor(
|
||||
"RadialLabelSausage.inactiveBorder"));
|
||||
gfx.drawOval(
|
||||
x + (BORDER_THICKNESS / 2), y + (BORDER_THICKNESS / 2),
|
||||
closedBounds.width - 1 - BORDER_THICKNESS,
|
||||
closedBounds.height - 1 - BORDER_THICKNESS);
|
||||
|
||||
// draw the black outer border
|
||||
gfx.setStroke(ostroke);
|
||||
gfx.setColor(Color.black);
|
||||
gfx.drawOval(x, y, closedBounds.width - 1, closedBounds.height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void drawExtras (Graphics2D gfx, int x, int y, Object cliData)
|
||||
{
|
||||
RadialMenu menu = (RadialMenu) cliData;
|
||||
// finally, if we're dimmed out: dim us out
|
||||
if (!isEnabled(menu)) {
|
||||
Composite ocomp = gfx.getComposite();
|
||||
gfx.setComposite(DISABLED_ALPHA);
|
||||
gfx.setColor(Color.black);
|
||||
drawBase(gfx, x, y);
|
||||
gfx.setComposite(ocomp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the base circle or sausage within which all the other
|
||||
* decorations are added.
|
||||
*/
|
||||
protected void drawBase (Graphics2D gfx, int x, int y)
|
||||
{
|
||||
if (_active) {
|
||||
// render the sausage
|
||||
super.drawBase(gfx, x, y);
|
||||
|
||||
} else {
|
||||
// render the circle
|
||||
gfx.fillOval(
|
||||
x, y, closedBounds.width - 1, closedBounds.height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints the radial label sausage.
|
||||
*/
|
||||
protected void paint (Graphics2D gfx, int x, int y, RadialMenu menu)
|
||||
{
|
||||
paint(gfx, x, y, UIManager.getColor(
|
||||
"RadialLabelSausage.background"), menu);
|
||||
}
|
||||
|
||||
/** Indicates whether or not this label is active. */
|
||||
protected boolean _active = false;
|
||||
|
||||
/** The thickness of the colored active/inactive border. */
|
||||
protected static final int BORDER_THICKNESS = 4;
|
||||
|
||||
/** The stroke to use when drawing the selected/unselected color. */
|
||||
protected static final Stroke BORDER_STROKE =
|
||||
new BasicStroke(BORDER_THICKNESS);
|
||||
|
||||
/** The alpha level for how much we dim an inactive menu item by. */
|
||||
protected static final Composite DISABLED_ALPHA =
|
||||
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
//
|
||||
// $Id: RadialMenu.java,v 1.6 2004/02/25 13:17:41 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import com.samskivert.swing.event.CommandEvent;
|
||||
import com.samskivert.swing.util.MouseHijacker;
|
||||
import com.samskivert.swing.util.SwingUtil;
|
||||
import com.samskivert.util.ObserverList;
|
||||
import com.samskivert.util.RunAnywhere;
|
||||
|
||||
/**
|
||||
* Provides a radial menu with iconic menu items that expand to include
|
||||
* textual descriptions when moused over.
|
||||
*/
|
||||
public class RadialMenu
|
||||
implements MouseMotionListener, MouseListener
|
||||
{
|
||||
/**
|
||||
* The interface used to communicate back to the component that hosts
|
||||
* this menu.
|
||||
*/
|
||||
public static interface Host
|
||||
{
|
||||
/**
|
||||
* Returns the component on which we will be rendered.
|
||||
*/
|
||||
public Component getComponent ();
|
||||
|
||||
/**
|
||||
* Requests the bounds of the visible region in which we'll be
|
||||
* rendering the menu. The menu will be constrained to fit within
|
||||
* these bounds starting with a natural position around the
|
||||
* supplied target bounds and adjusting minimally to fit.
|
||||
*/
|
||||
public Rectangle getViewBounds ();
|
||||
|
||||
/**
|
||||
* Requests that the appropriate region of the host be repainted.
|
||||
*/
|
||||
public void repaintRect (int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* Instructs the host component to stop rendering this menu
|
||||
* because it has been popped down.
|
||||
*/
|
||||
public void menuDeactivated (RadialMenu menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine at the time that a menu is shown, whether its
|
||||
* items should be included and/or enabled. Predicate methods are
|
||||
* called immediately before showing a radial menu and thus should not
|
||||
* perform complicated computations. They generally will check some
|
||||
* simple model to determine a menu item's status.
|
||||
*/
|
||||
public static interface Predicate
|
||||
{
|
||||
/**
|
||||
* If true, the menu will be included in the character menu when
|
||||
* it is displayed.
|
||||
*
|
||||
* @param menu the menu that will include or not include the item
|
||||
* in question.
|
||||
* @param item the menu item that will or will not be included.
|
||||
*/
|
||||
public boolean isIncluded (RadialMenu menu, RadialMenuItem item);
|
||||
|
||||
/**
|
||||
* If true, the menu will be enabled when displayed in a
|
||||
* character's menu.
|
||||
*
|
||||
* @param menu the menu that contains the item in question.
|
||||
* @param item the menu item that will or will not be enabled.
|
||||
*/
|
||||
public boolean isEnabled (RadialMenu menu, RadialMenuItem item);
|
||||
}
|
||||
|
||||
/**
|
||||
* An additional interface that can be implemented by the predicate
|
||||
* to display extra status.
|
||||
*/
|
||||
public static interface IconPredicate extends Predicate
|
||||
{
|
||||
/**
|
||||
* Return an additional predicate that should be drawn.
|
||||
*/
|
||||
public Icon getIcon (RadialMenu menu, RadialMenuItem item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a radial menu.
|
||||
*/
|
||||
public RadialMenu ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a menu item to the menu. The menu should not currently be
|
||||
* active.
|
||||
*
|
||||
* @param command the command to be issued when the item is selected.
|
||||
* @param label the textual label to be displayed with the menu item.
|
||||
* @param icon the icon to display next to the menu text or null if no
|
||||
* icon is desired.
|
||||
* @param predicate a predicate that will be used to determine whether
|
||||
* or not this menu item should be included in the menu and enabled
|
||||
* when it is shown.
|
||||
*
|
||||
* @return the item that was added to the menu. It can be modified
|
||||
* while the menu is not being displayed.
|
||||
*/
|
||||
public RadialMenuItem addMenuItem (String command, String label,
|
||||
Image icon, Predicate predicate)
|
||||
{
|
||||
RadialMenuItem item = new RadialMenuItem(
|
||||
command, label, new ImageIcon(icon), predicate);
|
||||
addMenuItem(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already constructed menu item to the menu. The menu should
|
||||
* not currently be active.
|
||||
*
|
||||
* @param item the menu item instance to be added.
|
||||
*/
|
||||
public void addMenuItem (RadialMenuItem item, boolean makeDefault)
|
||||
{
|
||||
addMenuItem(item);
|
||||
if (makeDefault) {
|
||||
_defaultItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an already constructed menu item to the menu. The menu should
|
||||
* not currently be active.
|
||||
*
|
||||
* @param item the menu item instance to be added.
|
||||
*/
|
||||
public void addMenuItem (RadialMenuItem item)
|
||||
{
|
||||
_items.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified menu item from this menu.
|
||||
*/
|
||||
public void removeMenuItem (RadialMenuItem item)
|
||||
{
|
||||
_items.remove(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first menu item that matches the specified command from
|
||||
* this menu.
|
||||
*
|
||||
* @return true if a matching menu item was removed, false if not.
|
||||
*/
|
||||
public boolean removeMenuItem (String command)
|
||||
{
|
||||
int icount = _items.size();
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||
if (item.command.equals(command)) {
|
||||
_items.remove(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* May be called by an item when it changes in a material way.
|
||||
*/
|
||||
public void itemUpdated ()
|
||||
{
|
||||
if (_host != null) {
|
||||
layout();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be notified when a menu item is
|
||||
* selected. When the the menu is popped down, all listeners will be
|
||||
* cleared.
|
||||
*/
|
||||
public void addActionListener (ActionListener listener)
|
||||
{
|
||||
_actlist.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional centerpiece icon to be displayed in the center of
|
||||
* the menu.
|
||||
*/
|
||||
public void setCenterIcon (Icon icon)
|
||||
{
|
||||
_centerIcon = icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the radial menu, rendering a menu around the prescribed
|
||||
* bounds. It is expected that the host component will subsequently
|
||||
* call {@link #render} if the menu invalidates the region of the
|
||||
* component occupied by the menu and requests it to repaint.
|
||||
*
|
||||
* @param host the host component within which the radial menu is
|
||||
* displayed.
|
||||
* @param bounds the bounds of the object that was clicked to activate
|
||||
* the menu.
|
||||
* @param argument a reference to an object that will be provided
|
||||
* along with the command that is issued if the user selects a menu
|
||||
* item (unless that item has an overriding argument, in which case
|
||||
* the overriding argument will be used).
|
||||
*/
|
||||
public void activate (Host host, Rectangle bounds, Object argument)
|
||||
{
|
||||
setActivationArgument(argument);
|
||||
activate(host, bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the radial menu, rendering a menu around the prescribed
|
||||
* bounds. It is expected that the host component will subsequently
|
||||
* call {@link #render} if the menu invalidates the region of the
|
||||
* component occupied by the menu and requests it to repaint.
|
||||
*
|
||||
* @param host the host component within which the radial menu is
|
||||
* displayed.
|
||||
* @param bounds the bounds of the object that was clicked to activate
|
||||
* the menu.
|
||||
*/
|
||||
public void activate (Host host, Rectangle bounds)
|
||||
{
|
||||
_host = host;
|
||||
_tbounds = bounds;
|
||||
_poptime = System.currentTimeMillis();
|
||||
_msMode = false;
|
||||
|
||||
Component comp = _host.getComponent();
|
||||
_hijacker = new MouseHijacker(comp);
|
||||
|
||||
// start listening to the host component
|
||||
comp.addMouseListener(this);
|
||||
comp.addMouseMotionListener(this);
|
||||
|
||||
// lay ourselves out and repaint
|
||||
layout();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the argument that will be defaultly posted along with commands.
|
||||
*/
|
||||
public void setActivationArgument (Object arg)
|
||||
{
|
||||
_argument = arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the argument provided to this menu when {@link #activate}
|
||||
* was called. This will only be non-null while the menu is active and
|
||||
* only then if an argument was provided in the call to {@link
|
||||
* #activate}.
|
||||
*/
|
||||
public Object getActivationArgument ()
|
||||
{
|
||||
return _argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the menu.
|
||||
*/
|
||||
public void deactivate ()
|
||||
{
|
||||
if (_host != null) {
|
||||
// unwire ourselves from the host component
|
||||
Component comp = _host.getComponent();
|
||||
comp.removeMouseListener(this);
|
||||
comp.removeMouseMotionListener(this);
|
||||
|
||||
// reinstate the previous mouse listeners
|
||||
if (_hijacker != null) {
|
||||
_hijacker.release();
|
||||
_hijacker = null;
|
||||
}
|
||||
|
||||
// tell our host that we are no longer
|
||||
_host.menuDeactivated(this);
|
||||
|
||||
// fire off a last repaint to clean up after ourselves
|
||||
repaint();
|
||||
}
|
||||
|
||||
// clear out our references
|
||||
_host = null;
|
||||
_tbounds = null;
|
||||
_argument = null;
|
||||
|
||||
// clear out our action listeners
|
||||
_actlist.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the current configuration of this menu.
|
||||
*/
|
||||
public void render (Graphics2D gfx)
|
||||
{
|
||||
Component host = _host.getComponent();
|
||||
|
||||
int x = _bounds.x, y = _bounds.y;
|
||||
|
||||
if (_centerLabel != null) {
|
||||
// render the centerpiece label
|
||||
_centerLabel.paint(gfx, x + _centerLabel.closedBounds.x,
|
||||
y + _centerLabel.closedBounds.y, this);
|
||||
}
|
||||
|
||||
// render each of our items in turn
|
||||
int icount = _items.size();
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||
if (!item.isIncluded(this)) {
|
||||
continue;
|
||||
}
|
||||
// we have to wait and render the active item last
|
||||
if (item != _activeItem) {
|
||||
item.render(host, this, gfx, x + item.closedBounds.x,
|
||||
y + item.closedBounds.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (_activeItem != null) {
|
||||
_activeItem.render(host, this, gfx, x + _activeItem.closedBounds.x,
|
||||
y + _activeItem.closedBounds.y);
|
||||
}
|
||||
|
||||
// render our bounds
|
||||
// gfx.setColor(Color.green);
|
||||
// gfx.draw(_bounds);
|
||||
// gfx.setColor(Color.blue);
|
||||
// gfx.draw(_tbounds);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseDragged (MouseEvent event)
|
||||
{
|
||||
// same as moved
|
||||
mouseMoved(event);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseMoved (MouseEvent event)
|
||||
{
|
||||
int x = event.getX(), y = event.getY();
|
||||
// translate the coords into our space
|
||||
x -= _bounds.x;
|
||||
y -= _bounds.y;
|
||||
boolean repaint = false;
|
||||
|
||||
// see if we dragged out of the active item
|
||||
if (_activeItem != null) {
|
||||
// oldway: if (!_activeItem.openBounds.contains(x, y)) {
|
||||
if (!_activeItem.closedBounds.contains(x, y)) {
|
||||
_activeItem.setActive(false);
|
||||
_activeItem = null;
|
||||
repaint = true;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we dragged into a new menu item
|
||||
if ((_activeItem == null) &&
|
||||
(RunAnywhere.getWhen(event) > _poptime + DEBOUNCE_DELAY)) {
|
||||
|
||||
int icount = _items.size();
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||
if (item.isIncluded(this) &&
|
||||
item.closedBounds.contains(x, y)) {
|
||||
_activeItem = item;
|
||||
_activeItem.setActive(true);
|
||||
repaint = true;
|
||||
|
||||
// if we got here with a mouse moved, we've definately
|
||||
// made the decision to be in non drag mode
|
||||
if (event.getID() == MouseEvent.MOUSE_MOVED) {
|
||||
_msMode = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// repaint ourselves if something changed
|
||||
if (repaint) {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseClicked (MouseEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseEntered (MouseEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseExited (MouseEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mousePressed (MouseEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public void mouseReleased (MouseEvent event)
|
||||
{
|
||||
if (_activeItem == null) {
|
||||
long when = RunAnywhere.getWhen(event);
|
||||
if (!_msMode &&
|
||||
(when < _poptime + DRAGMODE_DELAY)) {
|
||||
// if the dragmode delay time hasn't passed, then we're
|
||||
// going to leave the menu up because the user wants it
|
||||
// to behave like a MS Windows menu.
|
||||
_msMode = true;
|
||||
return;
|
||||
|
||||
// if they've double clicked, then activate the default item
|
||||
} else if (_msMode && (when < _poptime + DOUBLECLICK_DELAY)) {
|
||||
_activeItem = _defaultItem;
|
||||
}
|
||||
}
|
||||
|
||||
// deactivate the active item and post a command for it
|
||||
if (_activeItem != null) {
|
||||
// only process the selection if the item is enabled
|
||||
if (_activeItem.isEnabled(this) && _activeItem.command != null) {
|
||||
// if the item has an overriding argument, use that
|
||||
Object itemArg = _activeItem.argument;
|
||||
Object arg = (itemArg == null) ? _argument : itemArg;
|
||||
|
||||
// notify our listeners that the action is posted
|
||||
final CommandEvent evt = new CommandEvent(
|
||||
_host.getComponent(), _activeItem.command, arg);
|
||||
_actlist.apply(new ObserverList.ObserverOp() {
|
||||
public boolean apply (Object obs) {
|
||||
((ActionListener)obs).actionPerformed(evt);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
_activeItem.setActive(false);
|
||||
_activeItem = null;
|
||||
}
|
||||
|
||||
// deactive ourselves
|
||||
deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays the radial menu items out based on the currently configured
|
||||
* bounds information.
|
||||
*/
|
||||
protected void layout ()
|
||||
{
|
||||
Graphics2D gfx = (Graphics2D)_host.getComponent().getGraphics();
|
||||
Font font = gfx.getFont();
|
||||
_bounds = new Rectangle();
|
||||
|
||||
// figure out which items are included
|
||||
int icount = _items.size();
|
||||
ArrayList items = new ArrayList();
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||
if (item.isIncluded(this)) {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
// lay out all of our menu items
|
||||
int maxwid = 0, maxhei = 0;
|
||||
icount = items.size();
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||
item.layout(gfx, font);
|
||||
|
||||
// track maximum menu item size
|
||||
if (item.closedBounds.width > maxwid) {
|
||||
maxwid = item.closedBounds.width;
|
||||
}
|
||||
if (item.closedBounds.height > maxhei) {
|
||||
maxhei = item.closedBounds.height;
|
||||
}
|
||||
}
|
||||
gfx.dispose();
|
||||
|
||||
// use the maximum of either width or height and make a circle
|
||||
// around that
|
||||
double radius = Math.max(_tbounds.height, _tbounds.width) / 2;
|
||||
|
||||
// be sure to add a gap and space for the menu item itself
|
||||
radius += (5 + maxwid/2);
|
||||
|
||||
// compute the angle between menu items (we use the diameter of
|
||||
// the menu items as an approximate measure of the distance along
|
||||
// the circumference)
|
||||
double theta = (maxwid + 10) / radius ;
|
||||
|
||||
// now position each item accordingly
|
||||
double angle = -Math.PI/2;
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||
int ix = (int)(radius * Math.cos(angle));
|
||||
int iy = (int)(radius * Math.sin(angle));
|
||||
item.openBounds.x = item.closedBounds.x = ix - maxwid/2;
|
||||
item.openBounds.y = item.closedBounds.y = iy - maxhei/2;
|
||||
|
||||
// move along the circle
|
||||
angle += theta;
|
||||
}
|
||||
|
||||
// create and position the centerpiece label
|
||||
if (_centerIcon != null) {
|
||||
_centerLabel = new RadialLabelSausage("", _centerIcon);
|
||||
_centerLabel.layout(gfx, font);
|
||||
_centerLabel.openBounds.x = _centerLabel.closedBounds.x =
|
||||
-(_centerLabel.closedBounds.width / 2);
|
||||
_centerLabel.openBounds.y = _centerLabel.closedBounds.y =
|
||||
-(_centerLabel.closedBounds.height / 2);
|
||||
}
|
||||
|
||||
// now compute the rectangle that encloses the entire menu
|
||||
|
||||
// include the bounds for the centerpiece label
|
||||
if (_centerLabel != null) {
|
||||
_bounds.add(_centerLabel.openBounds);
|
||||
}
|
||||
|
||||
// include the bounds for all menu items
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||
// we need the open bounds rather than the closed ones
|
||||
_bounds.add(item.openBounds);
|
||||
}
|
||||
|
||||
// now translate everything from the center of the target bounds
|
||||
// to the upper left of the menu bounds
|
||||
if (_centerLabel != null) {
|
||||
_centerLabel.openBounds.translate(-_bounds.x, -_bounds.y);
|
||||
_centerLabel.closedBounds.translate(-_bounds.x, -_bounds.y);
|
||||
}
|
||||
for (int i = 0; i < icount; i++) {
|
||||
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||
item.openBounds.translate(-_bounds.x, -_bounds.y);
|
||||
item.closedBounds.translate(-_bounds.x, -_bounds.y);
|
||||
}
|
||||
|
||||
// the origin was at the center of the encircled rectangle; we
|
||||
// need to translate it such that the origin of our bounds are in
|
||||
// screen coordinates and at the upper left rather than the center
|
||||
_bounds.x += (_tbounds.x + _tbounds.width/2);
|
||||
_bounds.y += (_tbounds.y + _tbounds.height/2);
|
||||
|
||||
// now make sure the whole shebang is fully visible within the
|
||||
// host component
|
||||
Rectangle hbounds = _host.getViewBounds();
|
||||
Point pos = SwingUtil.fitRectInRect(_bounds, hbounds);
|
||||
_bounds.setLocation(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that our host component repaint the part of itself that we
|
||||
* occupy.
|
||||
*/
|
||||
protected void repaint ()
|
||||
{
|
||||
// only repaint the area that we overlap
|
||||
_host.repaintRect(_bounds.x, _bounds.y,
|
||||
_bounds.width+1, _bounds.height+1);
|
||||
}
|
||||
|
||||
/** Our host component. */
|
||||
protected Host _host;
|
||||
|
||||
/** The bounds around which we're rendering a menu. */
|
||||
protected Rectangle _tbounds;
|
||||
|
||||
/** The bounds that all of our menu items occupy. */
|
||||
protected Rectangle _bounds;
|
||||
|
||||
/** The argument object, which will be delivered along with a posted
|
||||
* command when a menu item is selected. */
|
||||
protected Object _argument;
|
||||
|
||||
/** The centerpiece icon or null if there is none. */
|
||||
protected Icon _centerIcon;
|
||||
|
||||
/** The label used to render the centerpiece icon if there is one. */
|
||||
protected RadialLabelSausage _centerLabel;
|
||||
|
||||
/** Our menu items. */
|
||||
protected ArrayList _items = new ArrayList();
|
||||
|
||||
/** The item that has the mouse over it presently, and the default item
|
||||
* if we're double clicked. */
|
||||
protected RadialMenuItem _activeItem, _defaultItem;
|
||||
|
||||
/** The amount of time the user must hold down the mouse in order
|
||||
* to put it in drag mode, after which the first mouse-up will pop
|
||||
* down the menu. */
|
||||
protected static final long DRAGMODE_DELAY = 1000L;
|
||||
|
||||
/** The amount of time after the menu has been popped up before we
|
||||
* accept selections. */
|
||||
protected static final long DEBOUNCE_DELAY = 200L;
|
||||
|
||||
/** How quickly a second click must arrive for us to count it as a double
|
||||
* click and not two single clicks. */
|
||||
protected static final long DOUBLECLICK_DELAY = 400L;
|
||||
|
||||
/** The time at which the menu was popped up. */
|
||||
protected long _poptime;
|
||||
|
||||
/** Whether we're popping the menu up in microsoft mode, where the
|
||||
* user does not need to drag to the item they want to select. */
|
||||
protected boolean _msMode = false;
|
||||
|
||||
/** This hijacks and holds onto the previous mouse listeners. */
|
||||
protected MouseHijacker _hijacker;
|
||||
|
||||
/** Maintains a list of action listeners. */
|
||||
protected ObserverList _actlist = new ObserverList(
|
||||
ObserverList.SAFE_IN_ORDER_NOTIFY);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// $Id: RadialMenuItem.java,v 1.2 2004/02/25 13:17:41 mdb Exp $
|
||||
|
||||
package com.samskivert.swing;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
/**
|
||||
* Used to track info for each menu item in a {@link RadialMenu}.
|
||||
*/
|
||||
public class RadialMenuItem extends RadialLabelSausage
|
||||
{
|
||||
/** The command to issue if this item is selected. */
|
||||
public String command;
|
||||
|
||||
/** A special argument to be used when this menu item is selected,
|
||||
* rather than the argument provided to the radial menu when it was
|
||||
* activated. */
|
||||
public Object argument;
|
||||
|
||||
/** Used to determine whether or not this menu item should be included
|
||||
* in a menu and whether or not it should be enabled. If no predicate
|
||||
* is available, a menu item is assumed always to be included and
|
||||
* enabled. */
|
||||
public RadialMenu.Predicate predicate;
|
||||
|
||||
/**
|
||||
* Constructs a radial menu item with the specified command and label.
|
||||
* No icon or menu predicate will be used.
|
||||
*/
|
||||
public RadialMenuItem (String command, String label)
|
||||
{
|
||||
this(command, label, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a radial menu item with the specified command, label and
|
||||
* icon. No menu predicate will be used.
|
||||
*/
|
||||
public RadialMenuItem (String command, String label, Icon icon)
|
||||
{
|
||||
this(command, label, icon, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a radial menu item with the specified command, label and
|
||||
* icon.
|
||||
*/
|
||||
public RadialMenuItem (String command, String label, Icon icon,
|
||||
RadialMenu.Predicate predicate)
|
||||
{
|
||||
super(label, icon);
|
||||
|
||||
_label.setTextColor(Color.black);
|
||||
|
||||
this.command = command;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this menu item should be included in a menu when it
|
||||
* is displayed. Calls through to the {@link RadialMenu.Predicate} if
|
||||
* we have one.
|
||||
*/
|
||||
public boolean isIncluded (RadialMenu menu)
|
||||
{
|
||||
return (predicate == null || predicate.isIncluded(menu, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this menu item should be enabled when it is
|
||||
* displayed. Calls through to the {@link RadialMenu.Predicate} if we
|
||||
* have one.
|
||||
*/
|
||||
public boolean isEnabled (RadialMenu menu)
|
||||
{
|
||||
return (predicate == null || predicate.isEnabled(menu, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu items are equal if their commands are equal. We also
|
||||
* declare ourselves to be equal to a string with the same value
|
||||
* as our command.
|
||||
*/
|
||||
public boolean equals (Object other)
|
||||
{
|
||||
if (other instanceof RadialMenuItem) {
|
||||
return command.equals(((RadialMenuItem)other).command);
|
||||
} else if (other instanceof String) {
|
||||
return command.equals(other);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this menu item at the specified location.
|
||||
*/
|
||||
public void render (
|
||||
Component host, RadialMenu menu, Graphics2D gfx, int x, int y)
|
||||
{
|
||||
paint(gfx, x, y, menu);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
protected void drawIcon (Graphics2D gfx, int x, int y, Object cliData)
|
||||
{
|
||||
super.drawIcon(gfx, x, y, cliData);
|
||||
if (predicate instanceof RadialMenu.IconPredicate) {
|
||||
Icon icon = ((RadialMenu.IconPredicate) predicate).getIcon(
|
||||
(RadialMenu) cliData, this);
|
||||
if (icon != null) {
|
||||
icon.paintIcon(null, gfx, x + _xoff, y + _yoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user