aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/rmi
diff options
context:
space:
mode:
authorduke <none@none>2007-12-01 00:00:00 +0000
committerduke <none@none>2007-12-01 00:00:00 +0000
commit59308f67f9b7038cfa2ceb9ee9ba27645b927cb5 (patch)
tree182810ab2fece13f57a928d026f93e9ede0827f9 /src/share/classes/sun/rmi
Initial loadjdk7-b24
Diffstat (limited to 'src/share/classes/sun/rmi')
-rw-r--r--src/share/classes/sun/rmi/log/LogHandler.java140
-rw-r--r--src/share/classes/sun/rmi/log/LogInputStream.java134
-rw-r--r--src/share/classes/sun/rmi/log/LogOutputStream.java83
-rw-r--r--src/share/classes/sun/rmi/log/ReliableLog.java829
-rw-r--r--src/share/classes/sun/rmi/registry/RegistryImpl.java360
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties30
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties31
-rw-r--r--src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties31
-rw-r--r--src/share/classes/sun/rmi/rmic/BatchEnvironment.java445
-rw-r--r--src/share/classes/sun/rmi/rmic/Constants.java44
-rw-r--r--src/share/classes/sun/rmi/rmic/Generator.java79
-rw-r--r--src/share/classes/sun/rmi/rmic/IndentingWriter.java299
-rw-r--r--src/share/classes/sun/rmi/rmic/Main.java884
-rw-r--r--src/share/classes/sun/rmi/rmic/Names.java88
-rw-r--r--src/share/classes/sun/rmi/rmic/RMIConstants.java80
-rw-r--r--src/share/classes/sun/rmi/rmic/RMIGenerator.java1303
-rw-r--r--src/share/classes/sun/rmi/rmic/RemoteClass.java875
-rw-r--r--src/share/classes/sun/rmi/rmic/Util.java136
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java145
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/Constants.java48
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/Generator.java87
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java291
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/Main.java689
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/Resources.java95
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java70
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java226
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java710
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java1079
-rw-r--r--src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java149
-rw-r--r--src/share/classes/sun/rmi/rmic/resources/rmic.properties222
-rw-r--r--src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties221
-rw-r--r--src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties222
-rw-r--r--src/share/classes/sun/rmi/runtime/Log.java456
-rw-r--r--src/share/classes/sun/rmi/runtime/NewThreadAction.java138
-rw-r--r--src/share/classes/sun/rmi/runtime/RuntimeUtil.java131
-rw-r--r--src/share/classes/sun/rmi/server/ActivatableRef.java411
-rw-r--r--src/share/classes/sun/rmi/server/ActivatableServerRef.java93
-rw-r--r--src/share/classes/sun/rmi/server/Activation.java2481
-rw-r--r--src/share/classes/sun/rmi/server/ActivationGroupImpl.java505
-rw-r--r--src/share/classes/sun/rmi/server/ActivationGroupInit.java85
-rw-r--r--src/share/classes/sun/rmi/server/Dispatcher.java49
-rw-r--r--src/share/classes/sun/rmi/server/InactiveGroupException.java50
-rw-r--r--src/share/classes/sun/rmi/server/LoaderHandler.java1198
-rw-r--r--src/share/classes/sun/rmi/server/MarshalInputStream.java320
-rw-r--r--src/share/classes/sun/rmi/server/MarshalOutputStream.java111
-rw-r--r--src/share/classes/sun/rmi/server/UnicastRef.java516
-rw-r--r--src/share/classes/sun/rmi/server/UnicastRef2.java79
-rw-r--r--src/share/classes/sun/rmi/server/UnicastServerRef.java563
-rw-r--r--src/share/classes/sun/rmi/server/UnicastServerRef2.java89
-rw-r--r--src/share/classes/sun/rmi/server/Util.java461
-rw-r--r--src/share/classes/sun/rmi/server/WeakClassHashMap.java88
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_de.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_es.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_fr.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_it.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_ja.properties134
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_ko.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_sv.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties133
-rw-r--r--src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties132
-rw-r--r--src/share/classes/sun/rmi/transport/Channel.java50
-rw-r--r--src/share/classes/sun/rmi/transport/Connection.java64
-rw-r--r--src/share/classes/sun/rmi/transport/ConnectionInputStream.java162
-rw-r--r--src/share/classes/sun/rmi/transport/ConnectionOutputStream.java109
-rw-r--r--src/share/classes/sun/rmi/transport/DGCAckHandler.java148
-rw-r--r--src/share/classes/sun/rmi/transport/DGCClient.java806
-rw-r--r--src/share/classes/sun/rmi/transport/DGCImpl.java340
-rw-r--r--src/share/classes/sun/rmi/transport/Endpoint.java55
-rw-r--r--src/share/classes/sun/rmi/transport/LiveRef.java317
-rw-r--r--src/share/classes/sun/rmi/transport/ObjectEndpoint.java90
-rw-r--r--src/share/classes/sun/rmi/transport/ObjectTable.java370
-rw-r--r--src/share/classes/sun/rmi/transport/StreamRemoteCall.java312
-rw-r--r--src/share/classes/sun/rmi/transport/Target.java471
-rw-r--r--src/share/classes/sun/rmi/transport/Transport.java235
-rw-r--r--src/share/classes/sun/rmi/transport/TransportConstants.java61
-rw-r--r--src/share/classes/sun/rmi/transport/WeakRef.java139
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/CGIHandler.java401
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java114
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java201
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java80
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java128
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java161
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java105
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java343
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java47
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java55
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java53
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java478
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java39
-rw-r--r--src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java183
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java459
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java55
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java213
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java231
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/TCPChannel.java536
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/TCPConnection.java236
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java787
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/TCPTransport.java867
106 files changed, 28997 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/log/LogHandler.java b/src/share/classes/sun/rmi/log/LogHandler.java
new file mode 100644
index 000000000..0c7ede040
--- /dev/null
+++ b/src/share/classes/sun/rmi/log/LogHandler.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 1997-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.log;
+
+import java.io.*;
+import sun.rmi.server.MarshalOutputStream;
+import sun.rmi.server.MarshalInputStream;
+
+/**
+ * A LogHandler represents snapshots and update records as serializable
+ * objects.
+ *
+ * This implementation does not know how to create an initial snaphot or
+ * apply an update to a snapshot. The client must specifiy these methods
+ * via a subclass.
+ *
+ * @see ReliableLog
+ *
+ * @author Ann Wollrath
+ */
+public abstract
+class LogHandler {
+
+ /**
+ * Creates a LogHandler for a ReliableLog.
+ */
+ public LogHandler() {}
+
+ /**
+ * Creates and returns the initial state of data structure that needs
+ * to be stably stored. This method is called when a ReliableLog is
+ * created.
+ * @return the initial state
+ * @exception Exception can raise any exception
+ */
+ public abstract
+ Object initialSnapshot() throws Exception;
+
+ /**
+ * Writes the snapshot object to a stream. This callback is
+ * invoked when the client calls the snaphot method of ReliableLog.
+ * @param out the output stream
+ * @param value the snapshot
+ * @exception Exception can raise any exception
+ */
+ public
+ void snapshot(OutputStream out, Object value) throws Exception {
+ MarshalOutputStream s = new MarshalOutputStream(out);
+ s.writeObject(value);
+ s.flush();
+ }
+
+ /**
+ * Read the snapshot object from a stream and returns the snapshot.
+ * This callback is invoked when the client calls the recover method
+ * of ReliableLog.
+ * @param in the input stream
+ * @return the state (snapshot)
+ * @exception Exception can raise any exception
+ */
+
+ public
+ Object recover(InputStream in) throws Exception {
+ MarshalInputStream s = new MarshalInputStream(in);
+ return s.readObject();
+ }
+
+ /**
+ * Writes the representation (a serializable object) of an update
+ * to a stream. This callback is invoked when the client calls the
+ * update method of ReliableLog.
+ * @param out the output stream
+ * @param value the snapshot
+ * @exception Exception can raise any exception
+ */
+ public
+ void writeUpdate(LogOutputStream out, Object value) throws Exception {
+
+ MarshalOutputStream s = new MarshalOutputStream(out);
+ s.writeObject(value);
+ s.flush();
+ }
+
+ /**
+ * Reads a stably logged update (a serializable object) from a
+ * stream. This callback is invoked during recovery, once for
+ * every record in the log. After reading the update, this method
+ * invokes the applyUpdate (abstract) method in order to obtain
+ * the new snapshot value. It then returns the new snapshot.
+ *
+ * @param in the input stream
+ * @param state the current state
+ * @return the new state
+ * @exception Exception can raise any exception
+ */
+ public
+ Object readUpdate(LogInputStream in, Object state) throws Exception {
+ MarshalInputStream s = new MarshalInputStream(in);
+ return applyUpdate(s.readObject(), state);
+ }
+
+ /**
+ * Reads a stably logged update (a serializable object) from a stream.
+ * This callback is invoked during recovery, once for every record in the
+ * log. After reading the update, this method is invoked in order to
+ * obtain the new snapshot value. The method should apply the update
+ * object to the current state <code>state</code> and return the new
+ * state (the new snapshot value).
+ * @param update the update object
+ * @param state the current state
+ * @return the new state
+ * @exception Exception can raise any exception
+ */
+ public abstract
+ Object applyUpdate(Object update, Object state) throws Exception;
+
+}
diff --git a/src/share/classes/sun/rmi/log/LogInputStream.java b/src/share/classes/sun/rmi/log/LogInputStream.java
new file mode 100644
index 000000000..cdf6e2a1d
--- /dev/null
+++ b/src/share/classes/sun/rmi/log/LogInputStream.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 1997-1999 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.log;
+
+import java.io.*;
+
+public
+class LogInputStream extends InputStream {
+ private InputStream in;
+ private int length;
+
+ /**
+ * Creates a log input file with the specified system dependent
+ * file descriptor.
+ * @param fd the system dependent file descriptor
+ * @param length the total number of bytes allowed to be read
+ * @exception IOException If an I/O error has occurred.
+ */
+ public LogInputStream(InputStream in, int length) throws IOException {
+ this.in = in;
+ this.length = length;
+ }
+
+ /**
+ * Reads a byte of data. This method will block if no input is
+ * available.
+ * @return the byte read, or -1 if the end of the log or end of the
+ * stream is reached.
+ * @exception IOException If an I/O error has occurred.
+ */
+ public int read() throws IOException {
+ if (length == 0)
+ return -1;
+ int c = in.read();
+ length = (c != -1) ? length - 1 : 0;
+ return c;
+ }
+
+ /**
+ * Reads data into an array of bytes.
+ * This method blocks until some input is available.
+ * @param b the buffer into which the data is read
+ * @return the actual number of bytes read, or -1 if the end of the log
+ * or end of the stream is reached.
+ * @exception IOException If an I/O error has occurred.
+ */
+ public int read(byte b[]) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Reads data into an array of bytes.
+ * This method blocks until some input is available.
+ * @param b the buffer into which the data is read
+ * @param off the start offset of the data
+ * @param len the maximum number of bytes read
+ * @return the actual number of bytes read, or -1 if the end of the log or
+ * end of the stream is reached.
+ * @exception IOException If an I/O error has occurred.
+ */
+ public int read(byte b[], int off, int len) throws IOException {
+ if (length == 0)
+ return -1;
+ len = (length < len) ? length : len;
+ int n = in.read(b, off, len);
+ length = (n != -1) ? length - n : 0;
+ return n;
+ }
+
+ /**
+ * Skips n bytes of input.
+ * @param n the number of bytes to be skipped
+ * @return the actual number of bytes skipped.
+ * @exception IOException If an I/O error has occurred.
+ */
+ public long skip(long n) throws IOException {
+ if (n > Integer.MAX_VALUE)
+ throw new IOException("Too many bytes to skip - " + n);
+ if (length == 0)
+ return 0;
+ n = (length < n) ? length : n;
+ n = in.skip(n);
+ length -= n;
+ return n;
+ }
+
+ /**
+ * Returns the number of bytes that can be read without blocking.
+ * @return the number of available bytes, which is initially
+ * equal to the file size.
+ */
+ public int available() throws IOException {
+ int avail = in.available();
+ return (length < avail) ? length : avail;
+ }
+
+ /**
+ * Closes the input stream. No further input can be read.
+ * the stream.
+ */
+ public void close() {
+ length = 0;
+ }
+
+ /**
+ * Closes the stream when garbage is collected.
+ */
+ protected void finalize() throws IOException {
+ close();
+ }
+}
diff --git a/src/share/classes/sun/rmi/log/LogOutputStream.java b/src/share/classes/sun/rmi/log/LogOutputStream.java
new file mode 100644
index 000000000..b706aecf0
--- /dev/null
+++ b/src/share/classes/sun/rmi/log/LogOutputStream.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 1997 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.log;
+
+import java.io.*;
+
+public
+class LogOutputStream extends OutputStream {
+
+ private RandomAccessFile raf;
+
+ /**
+ * Creates an output file with the specified system dependent
+ * file descriptor.
+ * @param fd the system dependent file descriptor
+ * @exception IOException If an I/O error has occurred.
+ */
+ public LogOutputStream(RandomAccessFile raf) throws IOException {
+ this.raf = raf;
+ }
+
+ /**
+ * Writes a byte of data. This method will block until the byte is
+ * actually written.
+ * @param b the byte to be written
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void write(int b) throws IOException {
+ raf.write(b);
+ }
+
+ /**
+ * Writes an array of bytes. Will block until the bytes
+ * are actually written.
+ * @param b the data to be written
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void write(byte b[]) throws IOException {
+ raf.write(b);
+ }
+
+ /**
+ * Writes a sub array of bytes.
+ * @param b the data to be written
+ * @param off the start offset in the data
+ * @param len the number of bytes that are written
+ * @exception IOException If an I/O error has occurred.
+ */
+ public void write(byte b[], int off, int len) throws IOException {
+ raf.write(b, off, len);
+ }
+
+ /**
+ * Can not close a LogOutputStream, so this does nothing.
+ * @exception IOException If an I/O error has occurred.
+ */
+ public final void close() throws IOException {
+ }
+
+}
diff --git a/src/share/classes/sun/rmi/log/ReliableLog.java b/src/share/classes/sun/rmi/log/ReliableLog.java
new file mode 100644
index 000000000..7ac285819
--- /dev/null
+++ b/src/share/classes/sun/rmi/log/ReliableLog.java
@@ -0,0 +1,829 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.log;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.rmi.server.RMIClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * This class is a simple implementation of a reliable Log. The
+ * client of a ReliableLog must provide a set of callbacks (via a
+ * LogHandler) that enables a ReliableLog to read and write
+ * checkpoints and log records. This implementation ensures that the
+ * current value of the data stored (via a ReliableLog) is recoverable
+ * after a system crash. <p>
+ *
+ * The secondary storage strategy is to record values in files using a
+ * representation of the caller's choosing. Two sorts of files are
+ * kept: snapshots and logs. At any instant, one snapshot is current.
+ * The log consists of a sequence of updates that have occurred since
+ * the current snapshot was taken. The current stable state is the
+ * value of the snapshot, as modified by the sequence of updates in
+ * the log. From time to time, the client of a ReliableLog instructs
+ * the package to make a new snapshot and clear the log. A ReliableLog
+ * arranges disk writes such that updates are stable (as long as the
+ * changes are force-written to disk) and atomic : no update is lost,
+ * and each update either is recorded completely in the log or not at
+ * all. Making a new snapshot is also atomic. <p>
+ *
+ * Normal use for maintaining the recoverable store is as follows: The
+ * client maintains the relevant data structure in virtual memory. As
+ * updates happen to the structure, the client informs the ReliableLog
+ * (all it "log") by calling log.update. Periodically, the client
+ * calls log.snapshot to provide the current value of the data
+ * structure. On restart, the client calls log.recover to obtain the
+ * latest snapshot and the following sequences of updates; the client
+ * applies the updates to the snapshot to obtain the state that
+ * existed before the crash. <p>
+ *
+ * The current logfile format is: <ol>
+ * <li> a format version number (two 4-octet integers, major and
+ * minor), followed by
+ * <li> a sequence of log records. Each log record contains, in
+ * order, <ol>
+ * <li> a 4-octet integer representing the length of the following log
+ * data,
+ * <li> the log data (variable length). </ol> </ol> <p>
+ *
+ * @see LogHandler
+ *
+ * @author Ann Wollrath
+ *
+ */
+public class ReliableLog {
+
+ public final static int PreferredMajorVersion = 0;
+ public final static int PreferredMinorVersion = 2;
+
+ // sun.rmi.log.debug=false
+ private boolean Debug = false;
+
+ private static String snapshotPrefix = "Snapshot.";
+ private static String logfilePrefix = "Logfile.";
+ private static String versionFile = "Version_Number";
+ private static String newVersionFile = "New_Version_Number";
+ private static int intBytes = 4;
+ private static long diskPageSize = 512;
+
+ private File dir; // base directory
+ private int version = 0; // current snapshot and log version
+ private String logName = null;
+ private LogFile log = null;
+ private long snapshotBytes = 0;
+ private long logBytes = 0;
+ private int logEntries = 0;
+ private long lastSnapshot = 0;
+ private long lastLog = 0;
+ //private long padBoundary = intBytes;
+ private LogHandler handler;
+ private final byte[] intBuf = new byte[4];
+
+ // format version numbers read from/written to this.log
+ private int majorFormatVersion = 0;
+ private int minorFormatVersion = 0;
+
+
+ /**
+ * Constructor for the log file. If the system property
+ * sun.rmi.log.class is non-null and the class specified by this
+ * property a) can be loaded, b) is a subclass of LogFile, and c) has a
+ * public two-arg constructor (String, String), ReliableLog uses the
+ * constructor to construct the LogFile.
+ **/
+ private static final Constructor<? extends LogFile>
+ logClassConstructor = getLogClassConstructor();
+
+ /**
+ * Creates a ReliableLog to handle checkpoints and logging in a
+ * stable storage directory.
+ *
+ * @param dirPath path to the stable storage directory
+ * @param logCl the closure object containing callbacks for logging and
+ * recovery
+ * @param pad ignored
+ * @exception IOException If a directory creation error has
+ * occurred or if initialSnapshot callback raises an exception or
+ * if an exception occurs during invocation of the handler's
+ * snapshot method or if other IOException occurs.
+ */
+ public ReliableLog(String dirPath,
+ LogHandler handler,
+ boolean pad)
+ throws IOException
+ {
+ super();
+ this.Debug = ((Boolean) AccessController.doPrivileged(
+ new GetBooleanAction("sun.rmi.log.debug"))).booleanValue();
+ dir = new File(dirPath);
+ if (!(dir.exists() && dir.isDirectory())) {
+ // create directory
+ if (!dir.mkdir()) {
+ throw new IOException("could not create directory for log: " +
+ dirPath);
+ }
+ }
+ //padBoundary = (pad ? diskPageSize : intBytes);
+ this.handler = handler;
+ lastSnapshot = 0;
+ lastLog = 0;
+ getVersion();
+ if (version == 0) {
+ try {
+ snapshot(handler.initialSnapshot());
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException("initial snapshot failed with " +
+ "exception: " + e);
+ }
+ }
+ }
+
+ /**
+ * Creates a ReliableLog to handle checkpoints and logging in a
+ * stable storage directory.
+ *
+ * @param dirPath path to the stable storage directory
+ * @param logCl the closure object containing callbacks for logging and
+ * recovery
+ * @exception IOException If a directory creation error has
+ * occurred or if initialSnapshot callback raises an exception
+ */
+ public ReliableLog(String dirPath,
+ LogHandler handler)
+ throws IOException
+ {
+ this(dirPath, handler, false);
+ }
+
+ /* public methods */
+
+ /**
+ * Returns an object which is the value recorded in the current
+ * snapshot. This snapshot is recovered by calling the client
+ * supplied callback "recover" and then subsequently invoking
+ * the "readUpdate" callback to apply any logged updates to the state.
+ *
+ * @exception IOException If recovery fails due to serious log
+ * corruption, read update failure, or if an exception occurs
+ * during the recover callback
+ */
+ public synchronized Object recover()
+ throws IOException
+ {
+ if (Debug)
+ System.err.println("log.debug: recover()");
+
+ if (version == 0)
+ return null;
+
+ Object snapshot;
+ String fname = versionName(snapshotPrefix);
+ File snapshotFile = new File(fname);
+ InputStream in =
+ new BufferedInputStream(new FileInputStream(snapshotFile));
+
+ if (Debug)
+ System.err.println("log.debug: recovering from " + fname);
+
+ try {
+ try {
+ snapshot = handler.recover(in);
+
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ if (Debug)
+ System.err.println("log.debug: recovery failed: " + e);
+ throw new IOException("log recover failed with " +
+ "exception: " + e);
+ }
+ snapshotBytes = snapshotFile.length();
+ } finally {
+ in.close();
+ }
+
+ return recoverUpdates(snapshot);
+ }
+
+ /**
+ * Records this update in the log file (does not force update to disk).
+ * The update is recorded by calling the client's "writeUpdate" callback.
+ * This method must not be called until this log's recover method has
+ * been invoked (and completed).
+ *
+ * @param value the object representing the update
+ * @exception IOException If an exception occurred during a
+ * writeUpdate callback or if other I/O error has occurred.
+ */
+ public synchronized void update(Object value) throws IOException {
+ update(value, true);
+ }
+
+ /**
+ * Records this update in the log file. The update is recorded by
+ * calling the client's writeUpdate callback. This method must not be
+ * called until this log's recover method has been invoked
+ * (and completed).
+ *
+ * @param value the object representing the update
+ * @param forceToDisk ignored; changes are always forced to disk
+ * @exception IOException If force-write to log failed or an
+ * exception occurred during the writeUpdate callback or if other
+ * I/O error occurs while updating the log.
+ */
+ public synchronized void update(Object value, boolean forceToDisk)
+ throws IOException
+ {
+ // avoid accessing a null log field.
+ if (log == null) {
+ throw new IOException("log is inaccessible, " +
+ "it may have been corrupted or closed");
+ }
+
+ /*
+ * If the entry length field spans a sector boundary, write
+ * the high order bit of the entry length, otherwise write zero for
+ * the entry length.
+ */
+ long entryStart = log.getFilePointer();
+ boolean spansBoundary = log.checkSpansBoundary(entryStart);
+ writeInt(log, spansBoundary? 1<<31 : 0);
+
+ /*
+ * Write update, and sync.
+ */
+ try {
+ handler.writeUpdate(new LogOutputStream(log), value);
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw (IOException)
+ new IOException("write update failed").initCause(e);
+ }
+ log.sync();
+
+ long entryEnd = log.getFilePointer();
+ int updateLen = (int) ((entryEnd - entryStart) - intBytes);
+ log.seek(entryStart);
+
+ if (spansBoundary) {
+ /*
+ * If length field spans a sector boundary, then
+ * the next two steps are required (see 4652922):
+ *
+ * 1) Write actual length with high order bit set; sync.
+ * 2) Then clear high order bit of length; sync.
+ */
+ writeInt(log, updateLen | 1<<31);
+ log.sync();
+
+ log.seek(entryStart);
+ log.writeByte(updateLen >> 24);
+ log.sync();
+
+ } else {
+ /*
+ * Write actual length; sync.
+ */
+ writeInt(log, updateLen);
+ log.sync();
+ }
+
+ log.seek(entryEnd);
+ logBytes = entryEnd;
+ lastLog = System.currentTimeMillis();
+ logEntries++;
+ }
+
+ /**
+ * Returns the constructor for the log file if the system property
+ * sun.rmi.log.class is non-null and the class specified by the
+ * property a) can be loaded, b) is a subclass of LogFile, and c) has a
+ * public two-arg constructor (String, String); otherwise returns null.
+ **/
+ private static Constructor<? extends LogFile>
+ getLogClassConstructor() {
+
+ String logClassName = ((String) AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.log.class")));
+ if (logClassName != null) {
+ try {
+ ClassLoader loader =
+ AccessController.doPrivileged(
+ new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return ClassLoader.getSystemClassLoader();
+ }
+ });
+ Class cl = loader.loadClass(logClassName);
+ if (LogFile.class.isAssignableFrom(cl)) {
+ return cl.getConstructor(String.class, String.class);
+ }
+ } catch (Exception e) {
+ System.err.println("Exception occurred:");
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Records this value as the current snapshot by invoking the client
+ * supplied "snapshot" callback and then empties the log.
+ *
+ * @param value the object representing the new snapshot
+ * @exception IOException If an exception occurred during the
+ * snapshot callback or if other I/O error has occurred during the
+ * snapshot process
+ */
+ public synchronized void snapshot(Object value)
+ throws IOException
+ {
+ int oldVersion = version;
+ incrVersion();
+
+ String fname = versionName(snapshotPrefix);
+ File snapshotFile = new File(fname);
+ FileOutputStream out = new FileOutputStream(snapshotFile);
+ try {
+ try {
+ handler.snapshot(out, value);
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException("snapshot failed with exception of type: " +
+ e.getClass().getName() +
+ ", message was: " + e.getMessage());
+ }
+ lastSnapshot = System.currentTimeMillis();
+ } finally {
+ out.close();
+ snapshotBytes = snapshotFile.length();
+ }
+
+ openLogFile(true);
+ writeVersionFile(true);
+ commitToNewVersion();
+ deleteSnapshot(oldVersion);
+ deleteLogFile(oldVersion);
+ }
+
+ /**
+ * Close the stable storage directory in an orderly manner.
+ *
+ * @exception IOException If an I/O error occurs when the log is
+ * closed
+ */
+ public synchronized void close() throws IOException {
+ if (log == null) return;
+ try {
+ log.close();
+ } finally {
+ log = null;
+ }
+ }
+
+ /**
+ * Returns the size of the snapshot file in bytes;
+ */
+ public long snapshotSize() {
+ return snapshotBytes;
+ }
+
+ /**
+ * Returns the size of the log file in bytes;
+ */
+ public long logSize() {
+ return logBytes;
+ }
+
+ /* private methods */
+
+ /**
+ * Write an int value in single write operation. This method
+ * assumes that the caller is synchronized on the log file.
+ *
+ * @param out output stream
+ * @param val int value
+ * @throws IOException if any other I/O error occurs
+ */
+ private void writeInt(DataOutput out, int val)
+ throws IOException
+ {
+ intBuf[0] = (byte) (val >> 24);
+ intBuf[1] = (byte) (val >> 16);
+ intBuf[2] = (byte) (val >> 8);
+ intBuf[3] = (byte) val;
+ out.write(intBuf);
+ }
+
+ /**
+ * Generates a filename prepended with the stable storage directory path.
+ *
+ * @param name the leaf name of the file
+ */
+ private String fName(String name) {
+ return dir.getPath() + File.separator + name;
+ }
+
+ /**
+ * Generates a version 0 filename prepended with the stable storage
+ * directory path
+ *
+ * @param name version file name
+ */
+ private String versionName(String name) {
+ return versionName(name, 0);
+ }
+
+ /**
+ * Generates a version filename prepended with the stable storage
+ * directory path with the version number as a suffix.
+ *
+ * @param name version file name
+ * @thisversion a version number
+ */
+ private String versionName(String prefix, int ver) {
+ ver = (ver == 0) ? version : ver;
+ return fName(prefix) + String.valueOf(ver);
+ }
+
+ /**
+ * Increments the directory version number.
+ */
+ private void incrVersion() {
+ do { version++; } while (version==0);
+ }
+
+ /**
+ * Delete a file.
+ *
+ * @param name the name of the file
+ * @exception IOException If new version file couldn't be removed
+ */
+ private void deleteFile(String name) throws IOException {
+
+ File f = new File(name);
+ if (!f.delete())
+ throw new IOException("couldn't remove file: " + name);
+ }
+
+ /**
+ * Removes the new version number file.
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void deleteNewVersionFile() throws IOException {
+ deleteFile(fName(newVersionFile));
+ }
+
+ /**
+ * Removes the snapshot file.
+ *
+ * @param ver the version to remove
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void deleteSnapshot(int ver) throws IOException {
+ if (ver == 0) return;
+ deleteFile(versionName(snapshotPrefix, ver));
+ }
+
+ /**
+ * Removes the log file.
+ *
+ * @param ver the version to remove
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void deleteLogFile(int ver) throws IOException {
+ if (ver == 0) return;
+ deleteFile(versionName(logfilePrefix, ver));
+ }
+
+ /**
+ * Opens the log file in read/write mode. If file does not exist, it is
+ * created.
+ *
+ * @param truncate if true and file exists, file is truncated to zero
+ * length
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void openLogFile(boolean truncate) throws IOException {
+ try {
+ close();
+ } catch (IOException e) { /* assume this is okay */
+ }
+
+ logName = versionName(logfilePrefix);
+
+ try {
+ log = (logClassConstructor == null ?
+ new LogFile(logName, "rw") :
+ logClassConstructor.newInstance(logName, "rw"));
+ } catch (Exception e) {
+ throw (IOException) new IOException(
+ "unable to construct LogFile instance").initCause(e);
+ }
+
+ if (truncate) {
+ initializeLogFile();
+ }
+ }
+
+ /**
+ * Creates a new log file, truncated and initialized with the format
+ * version number preferred by this implementation.
+ * <p>Environment: inited, synchronized
+ * <p>Precondition: valid: log, log contains nothing useful
+ * <p>Postcondition: if successful, log is initialised with the format
+ * version number (Preferred{Major,Minor}Version), and logBytes is
+ * set to the resulting size of the updatelog, and logEntries is set to
+ * zero. Otherwise, log is in an indeterminate state, and logBytes
+ * is unchanged, and logEntries is unchanged.
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void initializeLogFile()
+ throws IOException
+ {
+ log.setLength(0);
+ majorFormatVersion = PreferredMajorVersion;
+ writeInt(log, PreferredMajorVersion);
+ minorFormatVersion = PreferredMinorVersion;
+ writeInt(log, PreferredMinorVersion);
+ logBytes = intBytes * 2;
+ logEntries = 0;
+ }
+
+
+ /**
+ * Writes out version number to file.
+ *
+ * @param newVersion if true, writes to a new version file
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void writeVersionFile(boolean newVersion) throws IOException {
+ String name;
+ if (newVersion) {
+ name = newVersionFile;
+ } else {
+ name = versionFile;
+ }
+ DataOutputStream out =
+ new DataOutputStream(new FileOutputStream(fName(name)));
+ writeInt(out, version);
+ out.close();
+ }
+
+ /**
+ * Creates the initial version file
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void createFirstVersion() throws IOException {
+ version = 0;
+ writeVersionFile(false);
+ }
+
+ /**
+ * Commits (atomically) the new version.
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void commitToNewVersion() throws IOException {
+ writeVersionFile(false);
+ deleteNewVersionFile();
+ }
+
+ /**
+ * Reads version number from a file.
+ *
+ * @param name the name of the version file
+ * @return the version
+ * @exception IOException If an I/O error has occurred.
+ */
+ private int readVersion(String name) throws IOException {
+ DataInputStream in = new DataInputStream(new FileInputStream(name));
+ try {
+ return in.readInt();
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Sets the version. If version file does not exist, the initial
+ * version file is created.
+ *
+ * @exception IOException If an I/O error has occurred.
+ */
+ private void getVersion() throws IOException {
+ try {
+ version = readVersion(fName(newVersionFile));
+ commitToNewVersion();
+ } catch (IOException e) {
+ try {
+ deleteNewVersionFile();
+ }
+ catch (IOException ex) {
+ }
+
+ try {
+ version = readVersion(fName(versionFile));
+ }
+ catch (IOException ex) {
+ createFirstVersion();
+ }
+ }
+ }
+
+ /**
+ * Applies outstanding updates to the snapshot.
+ *
+ * @param state the most recent snapshot
+ * @exception IOException If serious log corruption is detected or
+ * if an exception occurred during a readUpdate callback or if
+ * other I/O error has occurred.
+ * @return the resulting state of the object after all updates
+ */
+ private Object recoverUpdates(Object state)
+ throws IOException
+ {
+ logBytes = 0;
+ logEntries = 0;
+
+ if (version == 0) return state;
+
+ String fname = versionName(logfilePrefix);
+ InputStream in =
+ new BufferedInputStream(new FileInputStream(fname));
+ DataInputStream dataIn = new DataInputStream(in);
+
+ if (Debug)
+ System.err.println("log.debug: reading updates from " + fname);
+
+ try {
+ majorFormatVersion = dataIn.readInt(); logBytes += intBytes;
+ minorFormatVersion = dataIn.readInt(); logBytes += intBytes;
+ } catch (EOFException e) {
+ /* This is a log which was corrupted and/or cleared (by
+ * fsck or equivalent). This is not an error.
+ */
+ openLogFile(true); // create and truncate
+ in = null;
+ }
+ /* A new major version number is a catastrophe (it means
+ * that the file format is incompatible with older
+ * clients, and we'll only be breaking things by trying to
+ * use the log). A new minor version is no big deal for
+ * upward compatibility.
+ */
+ if (majorFormatVersion != PreferredMajorVersion) {
+ if (Debug) {
+ System.err.println("log.debug: major version mismatch: " +
+ majorFormatVersion + "." + minorFormatVersion);
+ }
+ throw new IOException("Log file " + logName + " has a " +
+ "version " + majorFormatVersion +
+ "." + minorFormatVersion +
+ " format, and this implementation " +
+ " understands only version " +
+ PreferredMajorVersion + "." +
+ PreferredMinorVersion);
+ }
+
+ try {
+ while (in != null) {
+ int updateLen = 0;
+
+ try {
+ updateLen = dataIn.readInt();
+ } catch (EOFException e) {
+ if (Debug)
+ System.err.println("log.debug: log was sync'd cleanly");
+ break;
+ }
+ if (updateLen <= 0) {/* crashed while writing last log entry */
+ if (Debug) {
+ System.err.println(
+ "log.debug: last update incomplete, " +
+ "updateLen = 0x" +
+ Integer.toHexString(updateLen));
+ }
+ break;
+ }
+
+ // this is a fragile use of available() which relies on the
+ // twin facts that BufferedInputStream correctly consults
+ // the underlying stream, and that FileInputStream returns
+ // the number of bytes remaining in the file (via FIONREAD).
+ if (in.available() < updateLen) {
+ /* corrupted record at end of log (can happen since we
+ * do only one fsync)
+ */
+ if (Debug)
+ System.err.println("log.debug: log was truncated");
+ break;
+ }
+
+ if (Debug)
+ System.err.println("log.debug: rdUpdate size " + updateLen);
+ try {
+ state = handler.readUpdate(new LogInputStream(in, updateLen),
+ state);
+ } catch (IOException e) {
+ throw e;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new IOException("read update failed with " +
+ "exception: " + e);
+ }
+ logBytes += (intBytes + updateLen);
+ logEntries++;
+ } /* while */
+ } finally {
+ if (in != null)
+ in.close();
+ }
+
+ if (Debug)
+ System.err.println("log.debug: recovered updates: " + logEntries);
+
+ /* reopen log file at end */
+ openLogFile(false);
+
+ // avoid accessing a null log field
+ if (log == null) {
+ throw new IOException("rmid's log is inaccessible, " +
+ "it may have been corrupted or closed");
+ }
+
+ log.seek(logBytes);
+ log.setLength(logBytes);
+
+ return state;
+ }
+
+ /**
+ * ReliableLog's log file implementation. This implementation
+ * is subclassable for testing purposes.
+ */
+ public static class LogFile extends RandomAccessFile {
+
+ private final FileDescriptor fd;
+
+ /**
+ * Constructs a LogFile and initializes the file descriptor.
+ **/
+ public LogFile(String name, String mode)
+ throws FileNotFoundException, IOException
+ {
+ super(name, mode);
+ this.fd = getFD();
+ }
+
+ /**
+ * Invokes sync on the file descriptor for this log file.
+ */
+ protected void sync() throws IOException {
+ fd.sync();
+ }
+
+ /**
+ * Returns true if writing 4 bytes starting at the specified file
+ * position, would span a 512 byte sector boundary; otherwise returns
+ * false.
+ **/
+ protected boolean checkSpansBoundary(long fp) {
+ return fp % 512 > 508;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/registry/RegistryImpl.java b/src/share/classes/sun/rmi/registry/RegistryImpl.java
new file mode 100644
index 000000000..2f29cd570
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/RegistryImpl.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.registry;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.io.IOException;
+import java.net.*;
+import java.rmi.*;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteServer;
+import java.rmi.server.ServerNotActiveException;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.security.PrivilegedActionException;
+import java.text.MessageFormat;
+import sun.rmi.server.UnicastServerRef;
+import sun.rmi.server.UnicastServerRef2;
+import sun.rmi.transport.LiveRef;
+import sun.rmi.transport.ObjectTable;
+import sun.rmi.transport.Target;
+
+/**
+ * A "registry" exists on every node that allows RMI connections to
+ * servers on that node. The registry on a particular node contains a
+ * transient database that maps names to remote objects. When the
+ * node boots, the registry database is empty. The names stored in the
+ * registry are pure and are not parsed. A service storing itself in
+ * the registry may want to prefix its name of the service by a package
+ * name (although not required), to reduce name collisions in the
+ * registry.
+ *
+ * The LocateRegistry class is used to obtain registry for different hosts.
+ *
+ * @see java.rmi.registry.LocateRegistry
+ */
+public class RegistryImpl extends java.rmi.server.RemoteServer
+ implements Registry
+{
+
+ /* indicate compatibility with JDK 1.1.x version of class */
+ private static final long serialVersionUID = 4666870661827494597L;
+ private Hashtable bindings = new Hashtable(101);
+ private static Hashtable allowedAccessCache = new Hashtable(3);
+ private static RegistryImpl registry;
+ private static ObjID id = new ObjID(ObjID.REGISTRY_ID);
+
+ private static ResourceBundle resources = null;
+
+ /**
+ * Construct a new RegistryImpl on the specified port with the
+ * given custom socket factory pair.
+ */
+ public RegistryImpl(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ throws RemoteException
+ {
+ LiveRef lref = new LiveRef(id, port, csf, ssf);
+ setup(new UnicastServerRef2(lref));
+ }
+
+ /**
+ * Construct a new RegistryImpl on the specified port.
+ */
+ public RegistryImpl(int port)
+ throws RemoteException
+ {
+ LiveRef lref = new LiveRef(id, port);
+ setup(new UnicastServerRef(lref));
+ }
+
+ /*
+ * Create the export the object using the parameter
+ * <code>uref</code>
+ */
+ private void setup(UnicastServerRef uref)
+ throws RemoteException
+ {
+ /* Server ref must be created and assigned before remote
+ * object 'this' can be exported.
+ */
+ ref = uref;
+ uref.exportObject(this, null, true);
+ }
+
+ /**
+ * Returns the remote object for specified name in the registry.
+ * @exception RemoteException If remote operation failed.
+ * @exception NotBound If name is not currently bound.
+ */
+ public Remote lookup(String name)
+ throws RemoteException, NotBoundException
+ {
+ synchronized (bindings) {
+ Remote obj = (Remote)bindings.get(name);
+ if (obj == null)
+ throw new NotBoundException(name);
+ return obj;
+ }
+ }
+
+ /**
+ * Binds the name to the specified remote object.
+ * @exception RemoteException If remote operation failed.
+ * @exception AlreadyBoundException If name is already bound.
+ */
+ public void bind(String name, Remote obj)
+ throws RemoteException, AlreadyBoundException, AccessException
+ {
+ checkAccess("Registry.bind");
+ synchronized (bindings) {
+ Remote curr = (Remote)bindings.get(name);
+ if (curr != null)
+ throw new AlreadyBoundException(name);
+ bindings.put(name, obj);
+ }
+ }
+
+ /**
+ * Unbind the name.
+ * @exception RemoteException If remote operation failed.
+ * @exception NotBound If name is not currently bound.
+ */
+ public void unbind(String name)
+ throws RemoteException, NotBoundException, AccessException
+ {
+ checkAccess("Registry.unbind");
+ synchronized (bindings) {
+ Remote obj = (Remote)bindings.get(name);
+ if (obj == null)
+ throw new NotBoundException(name);
+ bindings.remove(name);
+ }
+ }
+
+ /**
+ * Rebind the name to a new object, replaces any existing binding.
+ * @exception RemoteException If remote operation failed.
+ */
+ public void rebind(String name, Remote obj)
+ throws RemoteException, AccessException
+ {
+ checkAccess("Registry.rebind");
+ bindings.put(name, obj);
+ }
+
+ /**
+ * Returns an enumeration of the names in the registry.
+ * @exception RemoteException If remote operation failed.
+ */
+ public String[] list()
+ throws RemoteException
+ {
+ String[] names;
+ synchronized (bindings) {
+ int i = bindings.size();
+ names = new String[i];
+ Enumeration enum_ = bindings.keys();
+ while ((--i) >= 0)
+ names[i] = (String)enum_.nextElement();
+ }
+ return names;
+ }
+
+ /**
+ * Check that the caller has access to perform indicated operation.
+ * The client must be on same the same host as this server.
+ */
+ public static void checkAccess(String op) throws AccessException {
+
+ try {
+ /*
+ * Get client host that this registry operation was made from.
+ */
+ final String clientHostName = getClientHost();
+ InetAddress clientHost;
+
+ try {
+ clientHost = (InetAddress)
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedExceptionAction() {
+ public Object run()
+ throws java.net.UnknownHostException
+ {
+ return InetAddress.getByName(clientHostName);
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ throw (java.net.UnknownHostException) pae.getException();
+ }
+
+ // if client not yet seen, make sure client allowed access
+ if (allowedAccessCache.get(clientHost) == null) {
+
+ if (clientHost.isAnyLocalAddress()) {
+ throw new AccessException(
+ "Registry." + op + " disallowed; origin unknown");
+ }
+
+ try {
+ final InetAddress finalClientHost = clientHost;
+
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedExceptionAction() {
+ public Object run() throws java.io.IOException {
+ /*
+ * if a ServerSocket can be bound to the client's
+ * address then that address must be local
+ */
+ (new ServerSocket(0, 10, finalClientHost)).close();
+ allowedAccessCache.put(finalClientHost,
+ finalClientHost);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ // must have been an IOException
+
+ throw new AccessException(
+ "Registry." + op + " disallowed; origin " +
+ clientHost + " is non-local host");
+ }
+ }
+ } catch (ServerNotActiveException ex) {
+ /*
+ * Local call from this VM: allow access.
+ */
+ } catch (java.net.UnknownHostException ex) {
+ throw new AccessException("Registry." + op +
+ " disallowed; origin is unknown host");
+ }
+ }
+
+ public static ObjID getID() {
+ return id;
+ }
+
+ /**
+ * Retrieves text resources from the locale-specific properties file.
+ */
+ private static String getTextResource(String key) {
+ if (resources == null) {
+ try {
+ resources = ResourceBundle.getBundle(
+ "sun.rmi.registry.resources.rmiregistry");
+ } catch (MissingResourceException mre) {
+ }
+ if (resources == null) {
+ // throwing an Error is a bit extreme, methinks
+ return ("[missing resource file: " + key + "]");
+ }
+ }
+
+ String val = null;
+ try {
+ val = resources.getString(key);
+ } catch (MissingResourceException mre) {
+ }
+
+ if (val == null) {
+ return ("[missing resource: " + key + "]");
+ } else {
+ return (val);
+ }
+ }
+
+ /**
+ * Main program to start a registry. <br>
+ * The port number can be specified on the command line.
+ */
+ public static void main(String args[])
+ {
+ // Create and install the security manager if one is not installed
+ // already.
+ if (System.getSecurityManager() == null) {
+ System.setSecurityManager(new RMISecurityManager());
+ }
+
+ try {
+ /*
+ * Fix bugid 4147561: When JDK tools are executed, the value of
+ * the CLASSPATH environment variable for the shell in which they
+ * were invoked is no longer incorporated into the application
+ * class path; CLASSPATH's only effect is to be the value of the
+ * system property "env.class.path". To preserve the previous
+ * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its
+ * CLASSPATH should still be considered when resolving classes
+ * being unmarshalled. To effect this old behavior, a class
+ * loader that loads from the file path specified in the
+ * "env.class.path" property is created and set to be the context
+ * class loader before the remote object is exported.
+ */
+ String envcp = System.getProperty("env.class.path");
+ if (envcp == null) {
+ envcp = "."; // preserve old default behavior
+ }
+ URL[] urls = sun.misc.URLClassPath.pathToURLs(envcp);
+ ClassLoader cl = new URLClassLoader(urls);
+
+ /*
+ * Fix bugid 4242317: Classes defined by this class loader should
+ * be annotated with the value of the "java.rmi.server.codebase"
+ * property, not the "file:" URLs for the CLASSPATH elements.
+ */
+ sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl);
+
+ Thread.currentThread().setContextClassLoader(cl);
+
+ int regPort = Registry.REGISTRY_PORT;
+ if (args.length >= 1) {
+ regPort = Integer.parseInt(args[0]);
+ }
+ registry = new RegistryImpl(regPort);
+ // prevent registry from exiting
+ while (true) {
+ try {
+ Thread.sleep(Long.MAX_VALUE);
+ } catch (InterruptedException e) {
+ }
+ }
+ } catch (NumberFormatException e) {
+ System.err.println(MessageFormat.format(
+ getTextResource("rmiregistry.port.badnumber"),
+ args[0] ));
+ System.err.println(MessageFormat.format(
+ getTextResource("rmiregistry.usage"),
+ "rmiregistry" ));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ }
+}
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties
new file mode 100644
index 000000000..49b789d47
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=Usage: {0} <options> <port>\
+\n\
+\nwhere <options> includes:\
+\n -J<runtime flag> Pass argument to the java interpreter
+rmiregistry.port.badnumber=port argument, {0}, is not a number.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties
new file mode 100644
index 000000000..d01ab9850
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_de.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=Syntax: {0} <Optionen> <Port>\
+\n\
+\nmit folgendem Wert f\u00fcr <Optionen>:\
+\n -J <Laufzeit-Flag> \u00dcbergeben des Arguments an den Java-Interpreter.
+rmiregistry.port.badnumber=Anschlussargument {0} ist keine Zahl.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties
new file mode 100644
index 000000000..1d9fc13a9
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_es.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=Sintaxis: {0} <opciones> <puerto>\
+\n\
+\ndonde las <opciones> son:\
+\n -J<indicador de runtime> Pasar argumento al int\u00e9rprete de java
+rmiregistry.port.badnumber=argumento de puerto, {0}, no es un n\u00famero.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties
new file mode 100644
index 000000000..74ff3903a
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_fr.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=Syntaxe : {0} <options> <port>\
+\n\
+\no\u00f9 <options> comprend :\
+\n -J<indicateur d''ex\u00e9cution> Transmettre l''argument \u00e0 l''interpr\u00e9teur Java
+rmiregistry.port.badnumber=l''argument de port, {0}, n''est pas un nombre.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties
new file mode 100644
index 000000000..e53e4a6a8
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_it.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=Utilizzo: {0} <opzioni> <porta>\
+\n\
+\ndove <opzioni> include:\
+\n -J<flag di runtime> Passa l''argomento all''interprete java
+rmiregistry.port.badnumber=l''argomento della porta, {0}, non \u00e8 un numero.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties
new file mode 100644
index 000000000..9959b187b
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ja.properties
@@ -0,0 +1,30 @@
+#
+# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=\u4f7f\u3044\u65b9: {0} <options> <port>\
+\n\
+\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\
+\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059
+rmiregistry.port.badnumber=\u30dd\u30fc\u30c8\u5f15\u6570 {0} \u306f\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties
new file mode 100644
index 000000000..49cdf4d24
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_ko.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=\uc0ac\uc6a9\ubc95: {0} <options> <port>\
+\n\
+\n<options>\ub294 \ub2e4\uc74c\uc744 \ud3ec\ud568\ud569\ub2c8\ub2e4:\
+\n -J<runtime flag> Java \uc778\ud130\ud504\ub9ac\ud130\uc5d0 \uc778\uc790\ub97c \uc804\ub2ec\ud569\ub2c8\ub2e4.
+rmiregistry.port.badnumber=\ud3ec\ud2b8 \uc778\uc790 {0}\uc740(\ub294) \uc22b\uc790\uac00 \uc544\ub2d9\ub2c8\ub2e4.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties
new file mode 100644
index 000000000..2d1cf052e
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_sv.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=G\u00f6r s\u00e5 h\u00e4r: {0} <alternativ> <port>\
+\n\
+\nd\u00e4r <alternativ> omfattar:\
+\n -J<k\u00f6rtidsflagga> Skicka argumentet till java-tolken
+rmiregistry.port.badnumber=portargumentet, {0}, \u00e4r inte en siffra.
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties
new file mode 100644
index 000000000..a018932a0
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_CN.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=\u7528\u6cd5\uff1a {0} <\u9009\u9879> <\u7aef\u53e3>\
+\n\
+\n\u5176\u4e2d\uff0c<\u9009\u9879> \u5305\u62ec\uff1a\
+\n -J<runtime \u6807\u8bb0> \u5c06\u53c2\u6570\u4f20\u9012\u5230 java \u89e3\u91ca\u7a0b\u5e8f
+rmiregistry.port.badnumber=\u7aef\u53e3\u53c2\u6570\uff1a{0}, \u4e0d\u662f\u6570\u5b57\u3002
diff --git a/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties
new file mode 100644
index 000000000..41862b1c7
--- /dev/null
+++ b/src/share/classes/sun/rmi/registry/resources/rmiregistry_zh_TW.properties
@@ -0,0 +1,31 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+rmiregistry.usage=\u7528\u6cd5: {0} <options> <port>\
+\n\
+\n\u5176\u4e2d <options> \u5305\u62ec\ufe30\
+\n -J<runtime flag> \u50b3\u905e\u5f15\u6578\u5230 java \u76f4\u8b6f\u5668
+rmiregistry.port.badnumber=\u9023\u63a5\u57e0\u5f15\u6578\uff0c{0}\uff0c\u4e0d\u662f\u4e00\u500b\u6578\u5b57
diff --git a/src/share/classes/sun/rmi/rmic/BatchEnvironment.java b/src/share/classes/sun/rmi/rmic/BatchEnvironment.java
new file mode 100644
index 000000000..7e86857fb
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/BatchEnvironment.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*****************************************************************************/
+/* Copyright (c) IBM Corporation 1998 */
+/* */
+/* (C) Copyright IBM Corp. 1998 */
+/* */
+/*****************************************************************************/
+
+package sun.rmi.rmic;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes;
+import sun.tools.java.ClassPath;
+
+/**
+ * BatchEnvironment for rmic extends javac's version in four ways:
+ * 1. It overrides errorString() to handle looking for rmic-specific
+ * error messages in rmic's resource bundle
+ * 2. It provides a mechanism for recording intermediate generated
+ * files so that they can be deleted later.
+ * 3. It holds a reference to the Main instance so that generators
+ * can refer to it.
+ * 4. It provides access to the ClassPath passed to the constructor.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+
+public class BatchEnvironment extends sun.tools.javac.BatchEnvironment {
+
+ /** instance of Main which created this environment */
+ private Main main;
+
+ /**
+ * Create a ClassPath object for rmic from a class path string.
+ */
+ public static ClassPath createClassPath(String classPathString) {
+ ClassPath[] paths = classPaths(null, classPathString, null, null);
+ return paths[1];
+ }
+
+ /**
+ * Create a ClassPath object for rmic from the relevant command line
+ * options for class path, boot class path, and extension directories.
+ */
+ public static ClassPath createClassPath(String classPathString,
+ String sysClassPathString,
+ String extDirsString)
+ {
+ /**
+ * Previously, this method delegated to the
+ * sun.tools.javac.BatchEnvironment.classPaths method in order
+ * to supply default values for paths not specified on the
+ * command line, expand extensions directories into specific
+ * JAR files, and construct the ClassPath object-- but as part
+ * of the fix for 6473331, which adds support for Class-Path
+ * manifest entries in JAR files, those steps are now handled
+ * here directly, with the help of a Path utility class copied
+ * from the new javac implementation (see below).
+ */
+ Path path = new Path();
+
+ if (sysClassPathString == null) {
+ sysClassPathString = System.getProperty("sun.boot.class.path");
+ }
+ if (sysClassPathString != null) {
+ path.addFiles(sysClassPathString);
+ }
+
+ /*
+ * Class-Path manifest entries are supported for JAR files
+ * everywhere except in the boot class path.
+ */
+ path.expandJarClassPaths(true);
+
+ if (extDirsString == null) {
+ extDirsString = System.getProperty("java.ext.dirs");
+ }
+ if (extDirsString != null) {
+ path.addDirectories(extDirsString);
+ }
+
+ /*
+ * In the application class path, an empty element means
+ * the current working directory.
+ */
+ path.emptyPathDefault(".");
+
+ if (classPathString == null) {
+ // The env.class.path property is the user's CLASSPATH
+ // environment variable, and it set by the wrapper (ie,
+ // javac.exe).
+ classPathString = System.getProperty("env.class.path");
+ if (classPathString == null) {
+ classPathString = ".";
+ }
+ }
+ path.addFiles(classPathString);
+
+ return new ClassPath(path.toArray(new String[path.size()]));
+ }
+
+ /**
+ * Create a BatchEnvironment for rmic with the given class path,
+ * stream for messages and Main.
+ */
+ public BatchEnvironment(OutputStream out, ClassPath path, Main main) {
+ super(out, new ClassPath(""), path);
+ // use empty "sourcePath" (see 4666958)
+ this.main = main;
+ }
+
+ /**
+ * Get the instance of Main which created this environment.
+ */
+ public Main getMain() {
+ return main;
+ }
+
+ /**
+ * Get the ClassPath.
+ */
+ public ClassPath getClassPath() {
+ return binaryPath;
+ }
+
+ /** list of generated source files created in this environment */
+ private Vector generatedFiles = new Vector();
+
+ /**
+ * Remember a generated source file generated so that it
+ * can be removed later, if appropriate.
+ */
+ public void addGeneratedFile(File file) {
+ generatedFiles.addElement(file);
+ }
+
+ /**
+ * Delete all the generated source files made during the execution
+ * of this environment (those that have been registered with the
+ * "addGeneratedFile" method).
+ */
+ public void deleteGeneratedFiles() {
+ synchronized(generatedFiles) {
+ Enumeration enumeration = generatedFiles.elements();
+ while (enumeration.hasMoreElements()) {
+ File file = (File) enumeration.nextElement();
+ file.delete();
+ }
+ generatedFiles.removeAllElements();
+ }
+ }
+
+ /**
+ * Release resources, if any.
+ */
+ public void shutdown() {
+ main = null;
+ generatedFiles = null;
+ super.shutdown();
+ }
+
+ /**
+ * Return the formatted, localized string for a named error message
+ * and supplied arguments. For rmic error messages, with names that
+ * being with "rmic.", look up the error message in rmic's resource
+ * bundle; otherwise, defer to java's superclass method.
+ */
+ public String errorString(String err,
+ Object arg0, Object arg1, Object arg2)
+ {
+ if (err.startsWith("rmic.") || err.startsWith("warn.rmic.")) {
+ String result = Main.getText(err,
+ (arg0 != null ? arg0.toString() : null),
+ (arg1 != null ? arg1.toString() : null),
+ (arg2 != null ? arg2.toString() : null));
+
+ if (err.startsWith("warn.")) {
+ result = "warning: " + result;
+ }
+ return result;
+ } else {
+ return super.errorString(err, arg0, arg1, arg2);
+ }
+ }
+ public void reset() {
+ }
+
+ /**
+ * Utility for building paths of directories and JAR files. This
+ * class was copied from com.sun.tools.javac.util.Paths as part of
+ * the fix for 6473331, which adds support for Class-Path manifest
+ * entries in JAR files. Diagnostic code is simply commented out
+ * because rmic silently ignored these conditions historically.
+ */
+ private static class Path extends LinkedHashSet<String> {
+ private static final long serialVersionUID = 0;
+ private static final boolean warn = false;
+
+ private static class PathIterator implements Collection<String> {
+ private int pos = 0;
+ private final String path;
+ private final String emptyPathDefault;
+
+ public PathIterator(String path, String emptyPathDefault) {
+ this.path = path;
+ this.emptyPathDefault = emptyPathDefault;
+ }
+ public PathIterator(String path) { this(path, null); }
+ public Iterator<String> iterator() {
+ return new Iterator<String>() {
+ public boolean hasNext() {
+ return pos <= path.length();
+ }
+ public String next() {
+ int beg = pos;
+ int end = path.indexOf(File.pathSeparator, beg);
+ if (end == -1)
+ end = path.length();
+ pos = end + 1;
+
+ if (beg == end && emptyPathDefault != null)
+ return emptyPathDefault;
+ else
+ return path.substring(beg, end);
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ // required for Collection.
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+ public boolean isEmpty() {
+ throw new UnsupportedOperationException();
+ }
+ public boolean contains(Object o) {
+ throw new UnsupportedOperationException();
+ }
+ public Object[] toArray() {
+ throw new UnsupportedOperationException();
+ }
+ public <T> T[] toArray(T[] a) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean add(String o) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean containsAll(Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean addAll(Collection<? extends String> c) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean removeAll(Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean retainAll(Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+ public boolean equals(Object o) {
+ throw new UnsupportedOperationException();
+ }
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** Is this the name of a zip file? */
+ private static boolean isZip(String name) {
+ return new File(name).isFile();
+ }
+
+ private boolean expandJarClassPaths = false;
+
+ public Path expandJarClassPaths(boolean x) {
+ expandJarClassPaths = x;
+ return this;
+ }
+
+ /** What to use when path element is the empty string */
+ private String emptyPathDefault = null;
+
+ public Path emptyPathDefault(String x) {
+ emptyPathDefault = x;
+ return this;
+ }
+
+ public Path() { super(); }
+
+ public Path addDirectories(String dirs, boolean warn) {
+ if (dirs != null)
+ for (String dir : new PathIterator(dirs))
+ addDirectory(dir, warn);
+ return this;
+ }
+
+ public Path addDirectories(String dirs) {
+ return addDirectories(dirs, warn);
+ }
+
+ private void addDirectory(String dir, boolean warn) {
+ if (! new File(dir).isDirectory()) {
+// if (warn)
+// log.warning(Position.NOPOS,
+// "dir.path.element.not.found", dir);
+ return;
+ }
+
+ for (String direntry : new File(dir).list()) {
+ String canonicalized = direntry.toLowerCase();
+ if (canonicalized.endsWith(".jar") ||
+ canonicalized.endsWith(".zip"))
+ addFile(dir + File.separator + direntry, warn);
+ }
+ }
+
+ public Path addFiles(String files, boolean warn) {
+ if (files != null)
+ for (String file : new PathIterator(files, emptyPathDefault))
+ addFile(file, warn);
+ return this;
+ }
+
+ public Path addFiles(String files) {
+ return addFiles(files, warn);
+ }
+
+ private void addFile(String file, boolean warn) {
+ if (contains(file)) {
+ /* Discard duplicates and avoid infinite recursion */
+ return;
+ }
+
+ File ele = new File(file);
+ if (! ele.exists()) {
+ /* No such file or directory exist */
+ if (warn)
+// log.warning(Position.NOPOS,
+// "path.element.not.found", file);
+ return;
+ }
+
+ if (ele.isFile()) {
+ /* File is an ordinay file */
+ String arcname = file.toLowerCase();
+ if (! (arcname.endsWith(".zip") ||
+ arcname.endsWith(".jar"))) {
+ /* File name don't have right extension */
+// if (warn)
+// log.warning(Position.NOPOS,
+// "invalid.archive.file", file);
+ return;
+ }
+ }
+
+ /* Now what we have left is either a directory or a file name
+ confirming to archive naming convention */
+
+ super.add(file);
+ if (expandJarClassPaths && isZip(file))
+ addJarClassPath(file, warn);
+ }
+
+ // Adds referenced classpath elements from a jar's Class-Path
+ // Manifest entry. In some future release, we may want to
+ // update this code to recognize URLs rather than simple
+ // filenames, but if we do, we should redo all path-related code.
+ private void addJarClassPath(String jarFileName, boolean warn) {
+ try {
+ String jarParent = new File(jarFileName).getParent();
+ JarFile jar = new JarFile(jarFileName);
+
+ try {
+ Manifest man = jar.getManifest();
+ if (man == null) return;
+
+ Attributes attr = man.getMainAttributes();
+ if (attr == null) return;
+
+ String path = attr.getValue(Attributes.Name.CLASS_PATH);
+ if (path == null) return;
+
+ for (StringTokenizer st = new StringTokenizer(path);
+ st.hasMoreTokens();) {
+ String elt = st.nextToken();
+ if (jarParent != null)
+ elt = new File(jarParent, elt).toString();
+ addFile(elt, warn);
+ }
+ } finally {
+ jar.close();
+ }
+ } catch (IOException e) {
+// log.error(Position.NOPOS,
+// "error.reading.file", jarFileName,
+// e.getLocalizedMessage());
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/Constants.java b/src/share/classes/sun/rmi/rmic/Constants.java
new file mode 100644
index 000000000..96bef7b29
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/Constants.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic;
+
+import sun.tools.java.Identifier;
+
+/**
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+public interface Constants extends sun.tools.java.Constants {
+
+ /*
+ * Identifiers potentially useful for all Generators
+ */
+ public static final Identifier idRemote =
+ Identifier.lookup("java.rmi.Remote");
+ public static final Identifier idRemoteException =
+ Identifier.lookup("java.rmi.RemoteException");
+}
diff --git a/src/share/classes/sun/rmi/rmic/Generator.java b/src/share/classes/sun/rmi/rmic/Generator.java
new file mode 100644
index 000000000..0126e8c03
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/Generator.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1998-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * Licensed Materials - Property of IBM
+ * RMI-IIOP v1.0
+ * Copyright IBM Corp. 1998 1999 All Rights Reserved
+ *
+ */
+
+package sun.rmi.rmic;
+
+import java.io.File;
+import sun.tools.java.ClassDefinition;
+
+/**
+ * Generator defines the protocol for back-end implementations to be added
+ * to rmic. See the rmic.properties file for a description of the format for
+ * adding new Generators to rmic.
+ * <p>
+ * Classes implementing this interface must have a public default constructor
+ * which should set any required arguments to their defaults. When Main
+ * encounters a command line argument which maps to a specific Generator
+ * subclass, it will instantiate one and call parseArgs(...). At some later
+ * point, Main will invoke the generate(...) method once for _each_ class passed
+ * on the command line.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Bryan Atsatt
+ */
+public interface Generator {
+
+ /**
+ * Examine and consume command line arguments.
+ * @param argv The command line arguments. Ignore null
+ * and unknown arguments. Set each consumed argument to null.
+ * @param main Report any errors using the main.error() methods.
+ * @return true if no errors, false otherwise.
+ */
+ public boolean parseArgs(String argv[], Main main);
+
+ /**
+ * Generate output. Any source files created which need compilation should
+ * be added to the compiler environment using the addGeneratedFile(File)
+ * method.
+ *
+ * @param env The compiler environment
+ * @param cdef The definition for the implementation class or interface from
+ * which to generate output
+ * @param destDir The directory for the root of the package hierarchy
+ * for generated files. May be null.
+ */
+ public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir);
+}
diff --git a/src/share/classes/sun/rmi/rmic/IndentingWriter.java b/src/share/classes/sun/rmi/rmic/IndentingWriter.java
new file mode 100644
index 000000000..43c879d78
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/IndentingWriter.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*****************************************************************************/
+/* Copyright (c) IBM Corporation 1998 */
+/* */
+/* (C) Copyright IBM Corp. 1998 */
+/* */
+/*****************************************************************************/
+
+package sun.rmi.rmic;
+
+import java.io.Writer;
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * IndentingWriter is a BufferedWriter subclass that supports automatic
+ * indentation of lines of text written to the underlying Writer.
+ *
+ * Methods are provided for compact, convenient indenting, writing text,
+ * and writing lines in various combinations.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+public class IndentingWriter extends BufferedWriter {
+
+ /** true if the next character written is the first on a line */
+ private boolean beginningOfLine = true;
+
+ /** current number of spaces to prepend to lines */
+ private int currentIndent = 0;
+
+ /** number of spaces to change indent when indenting in or out */
+ private int indentStep = 4;
+
+ /** number of spaces to convert into tabs. Use MAX_VALUE to disable */
+ private int tabSize = 8;
+
+ /**
+ * Create a new IndentingWriter that writes indented text to the
+ * given Writer. Use the default indent step of four spaces.
+ */
+ public IndentingWriter(Writer out) {
+ super(out);
+ }
+
+ /**
+ * Create a new IndentingWriter that writes indented text to the
+ * given Writer and uses the supplied indent step.
+ */
+ public IndentingWriter(Writer out, int step) {
+ this(out);
+
+ if (indentStep < 0)
+ throw new IllegalArgumentException("negative indent step");
+
+ indentStep = step;
+ }
+
+ /**
+ * Create a new IndentingWriter that writes indented text to the
+ * given Writer and uses the supplied indent step and tab size.
+ */
+ public IndentingWriter(Writer out, int step, int tabSize) {
+ this(out);
+
+ if (indentStep < 0)
+ throw new IllegalArgumentException("negative indent step");
+
+ indentStep = step;
+ this.tabSize = tabSize;
+ }
+
+ /**
+ * Write a single character.
+ */
+ public void write(int c) throws IOException {
+ checkWrite();
+ super.write(c);
+ }
+
+ /**
+ * Write a portion of an array of characters.
+ */
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ if (len > 0) {
+ checkWrite();
+ }
+ super.write(cbuf, off, len);
+ }
+
+ /**
+ * Write a portion of a String.
+ */
+ public void write(String s, int off, int len) throws IOException {
+ if (len > 0) {
+ checkWrite();
+ }
+ super.write(s, off, len);
+ }
+
+ /**
+ * Write a line separator. The next character written will be
+ * preceded by an indent.
+ */
+ public void newLine() throws IOException {
+ super.newLine();
+ beginningOfLine = true;
+ }
+
+ /**
+ * Check if an indent needs to be written before writing the next
+ * character.
+ *
+ * The indent generation is optimized (and made consistent with
+ * certain coding conventions) by condensing groups of eight spaces
+ * into tab characters.
+ */
+ protected void checkWrite() throws IOException {
+ if (beginningOfLine) {
+ beginningOfLine = false;
+ int i = currentIndent;
+ while (i >= tabSize) {
+ super.write('\t');
+ i -= tabSize;
+ }
+ while (i > 0) {
+ super.write(' ');
+ -- i;
+ }
+ }
+ }
+
+ /**
+ * Increase the current indent by the indent step.
+ */
+ protected void indentIn() {
+ currentIndent += indentStep;
+ }
+
+ /**
+ * Decrease the current indent by the indent step.
+ */
+ protected void indentOut() {
+ currentIndent -= indentStep;
+ if (currentIndent < 0)
+ currentIndent = 0;
+ }
+
+ /**
+ * Indent in.
+ */
+ public void pI() {
+ indentIn();
+ }
+
+ /**
+ * Indent out.
+ */
+ public void pO() {
+ indentOut();
+ }
+
+ /**
+ * Write string.
+ */
+ public void p(String s) throws IOException {
+ write(s);
+ }
+
+ /**
+ * End current line.
+ */
+ public void pln() throws IOException {
+ newLine();
+ }
+
+ /**
+ * Write string; end current line.
+ */
+ public void pln(String s) throws IOException {
+ p(s);
+ pln();
+ }
+
+ /**
+ * Write string; end current line; indent in.
+ */
+ public void plnI(String s) throws IOException {
+ p(s);
+ pln();
+ pI();
+ }
+
+ /**
+ * Indent out; write string.
+ */
+ public void pO(String s) throws IOException {
+ pO();
+ p(s);
+ }
+
+ /**
+ * Indent out; write string; end current line.
+ */
+ public void pOln(String s) throws IOException {
+ pO(s);
+ pln();
+ }
+
+ /**
+ * Indent out; write string; end current line; indent in.
+ *
+ * This method is useful for generating lines of code that both
+ * end and begin nested blocks, like "} else {".
+ */
+ public void pOlnI(String s) throws IOException {
+ pO(s);
+ pln();
+ pI();
+ }
+
+ /**
+ * Write Object.
+ */
+ public void p(Object o) throws IOException {
+ write(o.toString());
+ }
+ /**
+ * Write Object; end current line.
+ */
+ public void pln(Object o) throws IOException {
+ p(o.toString());
+ pln();
+ }
+
+ /**
+ * Write Object; end current line; indent in.
+ */
+ public void plnI(Object o) throws IOException {
+ p(o.toString());
+ pln();
+ pI();
+ }
+
+ /**
+ * Indent out; write Object.
+ */
+ public void pO(Object o) throws IOException {
+ pO();
+ p(o.toString());
+ }
+
+ /**
+ * Indent out; write Object; end current line.
+ */
+ public void pOln(Object o) throws IOException {
+ pO(o.toString());
+ pln();
+ }
+
+ /**
+ * Indent out; write Object; end current line; indent in.
+ *
+ * This method is useful for generating lines of code that both
+ * end and begin nested blocks, like "} else {".
+ */
+ public void pOlnI(Object o) throws IOException {
+ pO(o.toString());
+ pln();
+ pI();
+ }
+
+}
diff --git a/src/share/classes/sun/rmi/rmic/Main.java b/src/share/classes/sun/rmi/rmic/Main.java
new file mode 100644
index 000000000..bb9719aaf
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/Main.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * Licensed Materials - Property of IBM
+ * RMI-IIOP v1.0
+ * Copyright IBM Corp. 1998 1999 All Rights Reserved
+ *
+ */
+
+package sun.rmi.rmic;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+import java.util.MissingResourceException;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.IOException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.ByteArrayOutputStream;
+
+import sun.tools.java.ClassFile;
+import sun.tools.java.ClassDefinition;
+import sun.tools.java.ClassDeclaration;
+import sun.tools.java.ClassNotFound;
+import sun.tools.java.Identifier;
+import sun.tools.java.ClassPath;
+
+import sun.tools.javac.SourceClass;
+import sun.tools.util.CommandLine;
+import java.lang.reflect.Constructor;
+import java.util.Properties;
+
+/**
+ * Main "rmic" program.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+public class Main implements sun.rmi.rmic.Constants {
+ String sourcePathArg;
+ String sysClassPathArg;
+ String extDirsArg;
+ String classPathString;
+ File destDir;
+ int flags;
+ long tm;
+ Vector classes;
+ boolean nowrite;
+ boolean nocompile;
+ boolean keepGenerated;
+ boolean status;
+ String[] generatorArgs;
+ Vector generators;
+ Class environmentClass = BatchEnvironment.class;
+ boolean iiopGeneration = false;
+
+ /**
+ * Name of the program.
+ */
+ String program;
+
+ /**
+ * The stream where error message are printed.
+ */
+ OutputStream out;
+
+ /**
+ * Constructor.
+ */
+ public Main(OutputStream out, String program) {
+ this.out = out;
+ this.program = program;
+ }
+
+ /**
+ * Output a message.
+ */
+ public void output(String msg) {
+ PrintStream out =
+ this.out instanceof PrintStream ? (PrintStream)this.out
+ : new PrintStream(this.out, true);
+ out.println(msg);
+ }
+
+ /**
+ * Top level error message. This method is called when the
+ * environment could not be set up yet.
+ */
+ public void error(String msg) {
+ output(getText(msg));
+ }
+
+ public void error(String msg, String arg1) {
+ output(getText(msg, arg1));
+ }
+
+ public void error(String msg, String arg1, String arg2) {
+ output(getText(msg, arg1, arg2));
+ }
+
+ /**
+ * Usage
+ */
+ public void usage() {
+ error("rmic.usage", program);
+ }
+
+ /**
+ * Run the compiler
+ */
+ public synchronized boolean compile(String argv[]) {
+
+ /*
+ * Handle internal option to use the new (and incomplete) rmic
+ * implementation. This option is handled here, rather than
+ * in parseArgs, so that none of the arguments will be nulled
+ * before delegating to the new implementation.
+ */
+ for (int i = 0; i < argv.length; i++) {
+ if (argv[i].equals("-Xnew")) {
+ return (new sun.rmi.rmic.newrmic.Main(out,
+ program)).compile(argv);
+ }
+ }
+
+ if (!parseArgs(argv)) {
+ return false;
+ }
+
+ if (classes.size() == 0) {
+ usage();
+ return false;
+ }
+
+ return doCompile();
+ }
+
+ /**
+ * Get the destination directory.
+ */
+ public File getDestinationDir() {
+ return destDir;
+ }
+
+ /**
+ * Parse the arguments for compile.
+ */
+ public boolean parseArgs(String argv[]) {
+ sourcePathArg = null;
+ sysClassPathArg = null;
+ extDirsArg = null;
+
+ classPathString = null;
+ destDir = null;
+ flags = F_WARNINGS;
+ tm = System.currentTimeMillis();
+ classes = new Vector();
+ nowrite = false;
+ nocompile = false;
+ keepGenerated = false;
+ generatorArgs = getArray("generator.args",true);
+ if (generatorArgs == null) {
+ return false;
+ }
+ generators = new Vector();
+
+ // Pre-process command line for @file arguments
+ try {
+ argv = CommandLine.parse(argv);
+ } catch (FileNotFoundException e) {
+ error("rmic.cant.read", e.getMessage());
+ return false;
+ } catch (IOException e) {
+ e.printStackTrace(out instanceof PrintStream ?
+ (PrintStream) out :
+ new PrintStream(out, true));
+ return false;
+ }
+
+ // Parse arguments
+ for (int i = 0 ; i < argv.length ; i++) {
+ if (argv[i] != null) {
+ if (argv[i].equals("-g")) {
+ flags &= ~F_OPT;
+ flags |= F_DEBUG_LINES | F_DEBUG_VARS;
+ argv[i] = null;
+ } else if (argv[i].equals("-O")) {
+ flags &= ~F_DEBUG_LINES;
+ flags &= ~F_DEBUG_VARS;
+ flags |= F_OPT | F_DEPENDENCIES;
+ argv[i] = null;
+ } else if (argv[i].equals("-nowarn")) {
+ flags &= ~F_WARNINGS;
+ argv[i] = null;
+ } else if (argv[i].equals("-debug")) {
+ flags |= F_DUMP;
+ argv[i] = null;
+ } else if (argv[i].equals("-depend")) {
+ flags |= F_DEPENDENCIES;
+ argv[i] = null;
+ } else if (argv[i].equals("-verbose")) {
+ flags |= F_VERBOSE;
+ argv[i] = null;
+ } else if (argv[i].equals("-nowrite")) {
+ nowrite = true;
+ argv[i] = null;
+ } else if (argv[i].equals("-Xnocompile")) {
+ nocompile = true;
+ keepGenerated = true;
+ argv[i] = null;
+ } else if (argv[i].equals("-keep") ||
+ argv[i].equals("-keepgenerated")) {
+ keepGenerated = true;
+ argv[i] = null;
+ } else if (argv[i].equals("-show")) {
+ error("rmic.option.unsupported", "-show");
+ usage();
+ return false;
+ } else if (argv[i].equals("-classpath")) {
+ if ((i + 1) < argv.length) {
+ if (classPathString != null) {
+ error("rmic.option.already.seen", "-classpath");
+ usage();
+ return false;
+ }
+ argv[i] = null;
+ classPathString = argv[++i];
+ argv[i] = null;
+ } else {
+ error("rmic.option.requires.argument", "-classpath");
+ usage();
+ return false;
+ }
+ } else if (argv[i].equals("-sourcepath")) {
+ if ((i + 1) < argv.length) {
+ if (sourcePathArg != null) {
+ error("rmic.option.already.seen", "-sourcepath");
+ usage();
+ return false;
+ }
+ argv[i] = null;
+ sourcePathArg = argv[++i];
+ argv[i] = null;
+ } else {
+ error("rmic.option.requires.argument", "-sourcepath");
+ usage();
+ return false;
+ }
+ } else if (argv[i].equals("-bootclasspath")) {
+ if ((i + 1) < argv.length) {
+ if (sysClassPathArg != null) {
+ error("rmic.option.already.seen", "-bootclasspath");
+ usage();
+ return false;
+ }
+ argv[i] = null;
+ sysClassPathArg = argv[++i];
+ argv[i] = null;
+ } else {
+ error("rmic.option.requires.argument", "-bootclasspath");
+ usage();
+ return false;
+ }
+ } else if (argv[i].equals("-extdirs")) {
+ if ((i + 1) < argv.length) {
+ if (extDirsArg != null) {
+ error("rmic.option.already.seen", "-extdirs");
+ usage();
+ return false;
+ }
+ argv[i] = null;
+ extDirsArg = argv[++i];
+ argv[i] = null;
+ } else {
+ error("rmic.option.requires.argument", "-extdirs");
+ usage();
+ return false;
+ }
+ } else if (argv[i].equals("-d")) {
+ if ((i + 1) < argv.length) {
+ if (destDir != null) {
+ error("rmic.option.already.seen", "-d");
+ usage();
+ return false;
+ }
+ argv[i] = null;
+ destDir = new File(argv[++i]);
+ argv[i] = null;
+ if (!destDir.exists()) {
+ error("rmic.no.such.directory", destDir.getPath());
+ usage();
+ return false;
+ }
+ } else {
+ error("rmic.option.requires.argument", "-d");
+ usage();
+ return false;
+ }
+ } else {
+ if (!checkGeneratorArg(argv,i)) {
+ usage();
+ return false;
+ }
+ }
+ }
+ }
+
+
+ // Now that all generators have had a chance at the args,
+ // scan what's left for classes and illegal args...
+
+ for (int i = 0; i < argv.length; i++) {
+ if (argv[i] != null) {
+ if (argv[i].startsWith("-")) {
+ error("rmic.no.such.option", argv[i]);
+ usage();
+ return false;
+ } else {
+ classes.addElement(argv[i]);
+ }
+ }
+ }
+
+
+ // If the generators vector is empty, add the default generator...
+
+ if (generators.size() == 0) {
+ addGenerator("default");
+ }
+
+ return true;
+ }
+
+ /**
+ * If this argument is for a generator, instantiate it, call
+ * parseArgs(...) and add generator to generators vector.
+ * Returns false on error.
+ */
+ protected boolean checkGeneratorArg(String[] argv, int currentIndex) {
+ boolean result = true;
+ if (argv[currentIndex].startsWith("-")) {
+ String arg = argv[currentIndex].substring(1).toLowerCase(); // Remove '-'
+ for (int i = 0; i < generatorArgs.length; i++) {
+ if (arg.equalsIgnoreCase(generatorArgs[i])) {
+ // Got a match, add Generator and call parseArgs...
+ Generator gen = addGenerator(arg);
+ if (gen == null) {
+ return false;
+ }
+ result = gen.parseArgs(argv,this);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Instantiate and add a generator to the generators array.
+ */
+ protected Generator addGenerator(String arg) {
+
+ Generator gen;
+
+ // Create an instance of the generator and add it to
+ // the array...
+
+ String className = getString("generator.class." + arg);
+ if (className == null) {
+ error("rmic.missing.property",arg);
+ return null;
+ }
+
+ try {
+ gen = (Generator) Class.forName(className).newInstance();
+ } catch (Exception e) {
+ error("rmic.cannot.instantiate",className);
+ return null;
+ }
+
+ generators.addElement(gen);
+
+ // Get the environment required by this generator...
+
+ Class envClass = BatchEnvironment.class;
+ String env = getString("generator.env." + arg);
+ if (env != null) {
+ try {
+ envClass = Class.forName(env);
+
+ // Is the new class a subclass of the current one?
+
+ if (environmentClass.isAssignableFrom(envClass)) {
+
+ // Yes, so switch to the new one...
+
+ environmentClass = envClass;
+
+ } else {
+
+ // No. Is the current class a subclass of the
+ // new one?
+
+ if (!envClass.isAssignableFrom(environmentClass)) {
+
+ // No, so it's a conflict...
+
+ error("rmic.cannot.use.both",environmentClass.getName(),envClass.getName());
+ return null;
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ error("rmic.class.not.found",env);
+ return null;
+ }
+ }
+
+ // If this is the iiop stub generator, cache
+ // that fact for the jrmp generator...
+
+ if (arg.equals("iiop")) {
+ iiopGeneration = true;
+ }
+ return gen;
+ }
+
+ /**
+ * Grab a resource string and parse it into an array of strings. Assumes
+ * comma separated list.
+ * @param name The resource name.
+ * @param mustExist If true, throws error if resource does not exist. If
+ * false and resource does not exist, returns zero element array.
+ */
+ protected String[] getArray(String name, boolean mustExist) {
+ String[] result = null;
+ String value = getString(name);
+ if (value == null) {
+ if (mustExist) {
+ error("rmic.resource.not.found",name);
+ return null;
+ } else {
+ return new String[0];
+ }
+ }
+
+ StringTokenizer parser = new StringTokenizer(value,", \t\n\r", false);
+ int count = parser.countTokens();
+ result = new String[count];
+ for (int i = 0; i < count; i++) {
+ result[i] = parser.nextToken();
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the correct type of BatchEnvironment
+ */
+ public BatchEnvironment getEnv() {
+
+ ClassPath classPath =
+ BatchEnvironment.createClassPath(classPathString,
+ sysClassPathArg,
+ extDirsArg);
+ BatchEnvironment result = null;
+ try {
+ Class[] ctorArgTypes = {OutputStream.class,ClassPath.class,Main.class};
+ Object[] ctorArgs = {out,classPath,this};
+ Constructor constructor = environmentClass.getConstructor(ctorArgTypes);
+ result = (BatchEnvironment) constructor.newInstance(ctorArgs);
+ result.reset();
+ }
+ catch (Exception e) {
+ error("rmic.cannot.instantiate",environmentClass.getName());
+ }
+ return result;
+ }
+
+
+ /**
+ * Do the compile with the switches and files already supplied
+ */
+ public boolean doCompile() {
+ // Create batch environment
+ BatchEnvironment env = getEnv();
+ env.flags |= flags;
+
+ // Set the classfile version numbers
+ // Compat and 1.1 stubs must retain the old version number.
+ env.majorVersion = 45;
+ env.minorVersion = 3;
+
+ // Preload the "out of memory" error string just in case we run
+ // out of memory during the compile.
+ String noMemoryErrorString = getText("rmic.no.memory");
+ String stackOverflowErrorString = getText("rmic.stack.overflow");
+
+ try {
+ /** Load the classes on the command line
+ * Replace the entries in classes with the ClassDefinition for the class
+ */
+ for (int i = classes.size()-1; i >= 0; i-- ) {
+ Identifier implClassName =
+ Identifier.lookup((String)classes.elementAt(i));
+
+ /*
+ * Fix bugid 4049354: support using '.' as an inner class
+ * qualifier on the command line (previously, only mangled
+ * inner class names were understood, like "pkg.Outer$Inner").
+ *
+ * The following method, also used by "javap", resolves the
+ * given unmangled inner class name to the appropriate
+ * internal identifier. For example, it translates
+ * "pkg.Outer.Inner" to "pkg.Outer. Inner".
+ */
+ implClassName = env.resolvePackageQualifiedName(implClassName);
+ /*
+ * But if we use such an internal inner class name identifier
+ * to load the class definition, the Java compiler will notice
+ * if the impl class is a "private" inner class and then deny
+ * skeletons (needed unless "-v1.2" is used) the ability to
+ * cast to it. To work around this problem, we mangle inner
+ * class name identifiers to their binary "outer" class name:
+ * "pkg.Outer. Inner" becomes "pkg.Outer$Inner".
+ */
+ implClassName = Names.mangleClass(implClassName);
+
+ ClassDeclaration decl = env.getClassDeclaration(implClassName);
+ try {
+ ClassDefinition def = decl.getClassDefinition(env);
+ for (int j = 0; j < generators.size(); j++) {
+ Generator gen = (Generator)generators.elementAt(j);
+ gen.generate(env, def, destDir);
+ }
+ } catch (ClassNotFound ex) {
+ env.error(0, "rmic.class.not.found", implClassName);
+ }
+
+ }
+
+ // compile all classes that need compilation
+ if (!nocompile) {
+ compileAllClasses(env);
+ }
+ } catch (OutOfMemoryError ee) {
+ // The compiler has run out of memory. Use the error string
+ // which we preloaded.
+ env.output(noMemoryErrorString);
+ return false;
+ } catch (StackOverflowError ee) {
+ env.output(stackOverflowErrorString);
+ return false;
+ } catch (Error ee) {
+ // We allow the compiler to take an exception silently if a program
+ // error has previously been detected. Presumably, this makes the
+ // compiler more robust in the face of bad error recovery.
+ if (env.nerrors == 0 || env.dump()) {
+ env.error(0, "fatal.error");
+ ee.printStackTrace(out instanceof PrintStream ?
+ (PrintStream) out :
+ new PrintStream(out, true));
+ }
+ } catch (Exception ee) {
+ if (env.nerrors == 0 || env.dump()) {
+ env.error(0, "fatal.exception");
+ ee.printStackTrace(out instanceof PrintStream ?
+ (PrintStream) out :
+ new PrintStream(out, true));
+ }
+ }
+
+ env.flushErrors();
+
+ boolean status = true;
+ if (env.nerrors > 0) {
+ String msg = "";
+ if (env.nerrors > 1) {
+ msg = getText("rmic.errors", env.nerrors);
+ } else {
+ msg = getText("rmic.1error");
+ }
+ if (env.nwarnings > 0) {
+ if (env.nwarnings > 1) {
+ msg += ", " + getText("rmic.warnings", env.nwarnings);
+ } else {
+ msg += ", " + getText("rmic.1warning");
+ }
+ }
+ output(msg);
+ status = false;
+ } else {
+ if (env.nwarnings > 0) {
+ if (env.nwarnings > 1) {
+ output(getText("rmic.warnings", env.nwarnings));
+ } else {
+ output(getText("rmic.1warning"));
+ }
+ }
+ }
+
+ // last step is to delete generated source files
+ if (!keepGenerated) {
+ env.deleteGeneratedFiles();
+ }
+
+ // We're done
+ if (env.verbose()) {
+ tm = System.currentTimeMillis() - tm;
+ output(getText("rmic.done_in", Long.toString(tm)));
+ }
+
+ // Shutdown the environment object and release our resources.
+ // Note that while this is unneccessary when rmic is invoked
+ // the command line, there are environments in which rmic
+ // from is invoked within a server process, so resource
+ // reclamation is important...
+
+ env.shutdown();
+
+ sourcePathArg = null;
+ sysClassPathArg = null;
+ extDirsArg = null;
+ classPathString = null;
+ destDir = null;
+ classes = null;
+ generatorArgs = null;
+ generators = null;
+ environmentClass = null;
+ program = null;
+ out = null;
+
+ return status;
+ }
+
+ /*
+ * Compile all classes that need to be compiled.
+ */
+ public void compileAllClasses (BatchEnvironment env)
+ throws ClassNotFound,
+ IOException,
+ InterruptedException {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream(4096);
+ boolean done;
+
+ do {
+ done = true;
+ for (Enumeration e = env.getClasses() ; e.hasMoreElements() ; ) {
+ ClassDeclaration c = (ClassDeclaration)e.nextElement();
+ done = compileClass(c,buf,env);
+ }
+ } while (!done);
+ }
+
+ /*
+ * Compile a single class.
+ */
+ public boolean compileClass (ClassDeclaration c,
+ ByteArrayOutputStream buf,
+ BatchEnvironment env)
+ throws ClassNotFound,
+ IOException,
+ InterruptedException {
+ boolean done = true;
+ env.flushErrors();
+ SourceClass src;
+
+ switch (c.getStatus()) {
+ case CS_UNDEFINED:
+ {
+ if (!env.dependencies()) {
+ break;
+ }
+ // fall through
+ }
+
+ case CS_SOURCE:
+ {
+ done = false;
+ env.loadDefinition(c);
+ if (c.getStatus() != CS_PARSED) {
+ break;
+ }
+ // fall through
+ }
+
+ case CS_PARSED:
+ {
+ if (c.getClassDefinition().isInsideLocal()) {
+ break;
+ }
+ // If we get to here, then compilation is going
+ // to occur. If the -Xnocompile switch is set
+ // then fail. Note that this check is required
+ // here because this method is called from
+ // generators, not just from within this class...
+
+ if (nocompile) {
+ throw new IOException("Compilation required, but -Xnocompile option in effect");
+ }
+
+ done = false;
+
+ src = (SourceClass)c.getClassDefinition(env);
+ src.check(env);
+ c.setDefinition(src, CS_CHECKED);
+ // fall through
+ }
+
+ case CS_CHECKED:
+ {
+ src = (SourceClass)c.getClassDefinition(env);
+ // bail out if there were any errors
+ if (src.getError()) {
+ c.setDefinition(src, CS_COMPILED);
+ break;
+ }
+ done = false;
+ buf.reset();
+ src.compile(buf);
+ c.setDefinition(src, CS_COMPILED);
+ src.cleanup(env);
+
+ if (src.getError() || nowrite) {
+ break;
+ }
+
+ String pkgName = c.getName().getQualifier().toString().replace('.', File.separatorChar);
+ String className = c.getName().getFlatName().toString().replace('.', SIGC_INNERCLASS) + ".class";
+
+ File file;
+ if (destDir != null) {
+ if (pkgName.length() > 0) {
+ file = new File(destDir, pkgName);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ file = new File(file, className);
+ } else {
+ file = new File(destDir, className);
+ }
+ } else {
+ ClassFile classfile = (ClassFile)src.getSource();
+ if (classfile.isZipped()) {
+ env.error(0, "cant.write", classfile.getPath());
+ break;
+ }
+ file = new File(classfile.getPath());
+ file = new File(file.getParent(), className);
+ }
+
+ // Create the file
+ try {
+ FileOutputStream out = new FileOutputStream(file.getPath());
+ buf.writeTo(out);
+ out.close();
+ if (env.verbose()) {
+ output(getText("rmic.wrote", file.getPath()));
+ }
+ } catch (IOException ee) {
+ env.error(0, "cant.write", file.getPath());
+ }
+ }
+ }
+ return done;
+ }
+
+ /**
+ * Main program
+ */
+ public static void main(String argv[]) {
+ Main compiler = new Main(System.out, "rmic");
+ System.exit(compiler.compile(argv) ? 0 : 1);
+ }
+
+ /**
+ * Return the string value of a named resource in the rmic.properties
+ * resource bundle. If the resource is not found, null is returned.
+ */
+ public static String getString(String key) {
+ if (!resourcesInitialized) {
+ initResources();
+ }
+
+ // To enable extensions, search the 'resourcesExt'
+ // bundle first, followed by the 'resources' bundle...
+
+ if (resourcesExt != null) {
+ try {
+ return resourcesExt.getString(key);
+ } catch (MissingResourceException e) {}
+ }
+
+ try {
+ return resources.getString(key);
+ } catch (MissingResourceException ignore) {
+ }
+ return null;
+ }
+
+ private static boolean resourcesInitialized = false;
+ private static ResourceBundle resources;
+ private static ResourceBundle resourcesExt = null;
+
+ private static void initResources() {
+ try {
+ resources =
+ ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic");
+ resourcesInitialized = true;
+ try {
+ resourcesExt =
+ ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext");
+ } catch (MissingResourceException e) {}
+ } catch (MissingResourceException e) {
+ throw new Error("fatal: missing resource bundle: " +
+ e.getClassName());
+ }
+ }
+
+ public static String getText(String key) {
+ String message = getString(key);
+ if (message == null) {
+ message = "no text found: \"" + key + "\"";
+ }
+ return message;
+ }
+
+ public static String getText(String key, int num) {
+ return getText(key, Integer.toString(num), null, null);
+ }
+
+ public static String getText(String key, String arg0) {
+ return getText(key, arg0, null, null);
+ }
+
+ public static String getText(String key, String arg0, String arg1) {
+ return getText(key, arg0, arg1, null);
+ }
+
+ public static String getText(String key,
+ String arg0, String arg1, String arg2)
+ {
+ String format = getString(key);
+ if (format == null) {
+ format = "no text found: key = \"" + key + "\", " +
+ "arguments = \"{0}\", \"{1}\", \"{2}\"";
+ }
+
+ String[] args = new String[3];
+ args[0] = (arg0 != null ? arg0.toString() : "null");
+ args[1] = (arg1 != null ? arg1.toString() : "null");
+ args[2] = (arg2 != null ? arg2.toString() : "null");
+
+ return java.text.MessageFormat.format(format, args);
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/Names.java b/src/share/classes/sun/rmi/rmic/Names.java
new file mode 100644
index 000000000..05bb4b2e3
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/Names.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic;
+
+import sun.tools.java.Identifier;
+
+/**
+ * Names provides static utility methods used by other rmic classes
+ * for dealing with identifiers.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+public class Names {
+
+ /**
+ * Return stub class name for impl class name.
+ */
+ static final public Identifier stubFor(Identifier name) {
+ return Identifier.lookup(name + "_Stub");
+ }
+
+ /**
+ * Return skeleton class name for impl class name.
+ */
+ static final public Identifier skeletonFor(Identifier name) {
+ return Identifier.lookup(name + "_Skel");
+ }
+
+ /**
+ * If necessary, convert a class name to its mangled form, i.e. the
+ * non-inner class name used in the binary representation of
+ * inner classes. This is necessary to be able to name inner
+ * classes in the generated source code in places where the language
+ * does not permit it, such as when synthetically defining an inner
+ * class outside of its outer class, and for generating file names
+ * corresponding to inner classes.
+ *
+ * Currently this mangling involves modifying the internal names of
+ * inner classes by converting occurrences of ". " into "$".
+ *
+ * This code is taken from the "mangleInnerType" method of
+ * the "sun.tools.java.Type" class; this method cannot be accessed
+ * itself because it is package protected.
+ */
+ static final public Identifier mangleClass(Identifier className) {
+ if (!className.isInner())
+ return className;
+
+ /*
+ * Get '.' qualified inner class name (with outer class
+ * qualification and no package qualification) and replace
+ * each '.' with '$'.
+ */
+ Identifier mangled = Identifier.lookup(
+ className.getFlatName().toString()
+ .replace('.', sun.tools.java.Constants.SIGC_INNERCLASS));
+ if (mangled.isInner())
+ throw new Error("failed to mangle inner class name");
+
+ // prepend package qualifier back for returned identifier
+ return Identifier.lookup(className.getQualifier(), mangled);
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/RMIConstants.java b/src/share/classes/sun/rmi/rmic/RMIConstants.java
new file mode 100644
index 000000000..b28ac105b
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/RMIConstants.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 1998-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * Licensed Materials - Property of IBM
+ * RMI-IIOP v1.0
+ * Copyright IBM Corp. 1998 1999 All Rights Reserved
+ *
+ */
+
+package sun.rmi.rmic;
+
+import sun.tools.java.Identifier;
+
+/**
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ */
+public interface RMIConstants extends sun.rmi.rmic.Constants {
+
+ /*
+ * identifiers for RMI classes referenced by rmic
+ */
+ public static final Identifier idRemoteObject =
+ Identifier.lookup("java.rmi.server.RemoteObject");
+ public static final Identifier idRemoteStub =
+ Identifier.lookup("java.rmi.server.RemoteStub");
+ public static final Identifier idRemoteRef =
+ Identifier.lookup("java.rmi.server.RemoteRef");
+ public static final Identifier idOperation =
+ Identifier.lookup("java.rmi.server.Operation");
+ public static final Identifier idSkeleton =
+ Identifier.lookup("java.rmi.server.Skeleton");
+ public static final Identifier idSkeletonMismatchException =
+ Identifier.lookup("java.rmi.server.SkeletonMismatchException");
+ public static final Identifier idRemoteCall =
+ Identifier.lookup("java.rmi.server.RemoteCall");
+ public static final Identifier idMarshalException =
+ Identifier.lookup("java.rmi.MarshalException");
+ public static final Identifier idUnmarshalException =
+ Identifier.lookup("java.rmi.UnmarshalException");
+ public static final Identifier idUnexpectedException =
+ Identifier.lookup("java.rmi.UnexpectedException");
+
+ /*
+ * stub protocol versions
+ */
+ public static final int STUB_VERSION_1_1 = 1;
+ public static final int STUB_VERSION_FAT = 2;
+ public static final int STUB_VERSION_1_2 = 3;
+
+ /** serialVersionUID for all stubs that can use 1.2 protocol */
+ public static final long STUB_SERIAL_VERSION_UID = 2;
+
+ /** version number used to seed interface hash computation */
+ public static final int INTERFACE_HASH_STUB_VERSION = 1;
+}
diff --git a/src/share/classes/sun/rmi/rmic/RMIGenerator.java b/src/share/classes/sun/rmi/rmic/RMIGenerator.java
new file mode 100644
index 000000000..41e94ff36
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/RMIGenerator.java
@@ -0,0 +1,1303 @@
+/*
+ * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*****************************************************************************/
+/* Copyright (c) IBM Corporation 1998 */
+/* */
+/* (C) Copyright IBM Corp. 1998 */
+/* */
+/*****************************************************************************/
+
+package sun.rmi.rmic;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+import sun.tools.java.Type;
+import sun.tools.java.Identifier;
+import sun.tools.java.ClassDefinition;
+import sun.tools.java.ClassDeclaration;
+import sun.tools.java.ClassNotFound;
+import sun.tools.java.ClassFile;
+import sun.tools.java.MemberDefinition;
+import com.sun.corba.se.impl.util.Utility;
+
+/**
+ * A Generator object will generate the Java source code of the stub
+ * and skeleton classes for an RMI remote implementation class, using
+ * a particular stub protocol version.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones, Bryan Atsatt
+ */
+public class RMIGenerator implements RMIConstants, Generator {
+
+ private static final Hashtable versionOptions = new Hashtable();
+ static {
+ versionOptions.put("-v1.1", new Integer(STUB_VERSION_1_1));
+ versionOptions.put("-vcompat", new Integer(STUB_VERSION_FAT));
+ versionOptions.put("-v1.2", new Integer(STUB_VERSION_1_2));
+ }
+
+ /**
+ * Default constructor for Main to use.
+ */
+ public RMIGenerator() {
+ version = STUB_VERSION_1_2; // default is -v1.2 (see 4638155)
+ }
+
+ /**
+ * Examine and consume command line arguments.
+ * @param argv The command line arguments. Ignore null
+ * and unknown arguments. Set each consumed argument to null.
+ * @param error Report any errors using the main.error() methods.
+ * @return true if no errors, false otherwise.
+ */
+ public boolean parseArgs(String argv[], Main main) {
+ String explicitVersion = null;
+ for (int i = 0; i < argv.length; i++) {
+ if (argv[i] != null) {
+ String arg = argv[i].toLowerCase();
+ if (versionOptions.containsKey(arg)) {
+ if (explicitVersion != null &&
+ !explicitVersion.equals(arg))
+ {
+ main.error("rmic.cannot.use.both",
+ explicitVersion, arg);
+ return false;
+ }
+ explicitVersion = arg;
+ version = ((Integer) versionOptions.get(arg)).intValue();
+ argv[i] = null;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Generate the source files for the stub and/or skeleton classes
+ * needed by RMI for the given remote implementation class.
+ *
+ * @param env compiler environment
+ * @param cdef definition of remote implementation class
+ * to generate stubs and/or skeletons for
+ * @param destDir directory for the root of the package hierarchy
+ * for generated files
+ */
+ public void generate(BatchEnvironment env, ClassDefinition cdef, File destDir) {
+ RemoteClass remoteClass = RemoteClass.forClass(env, cdef);
+ if (remoteClass == null) // exit if an error occurred
+ return;
+
+ RMIGenerator gen;
+ try {
+ gen = new RMIGenerator(env, cdef, destDir, remoteClass, version);
+ } catch (ClassNotFound e) {
+ env.error(0, "rmic.class.not.found", e.name);
+ return;
+ }
+ gen.generate();
+ }
+
+ private void generate() {
+ env.addGeneratedFile(stubFile);
+
+ try {
+ IndentingWriter out = new IndentingWriter(
+ new OutputStreamWriter(new FileOutputStream(stubFile)));
+ writeStub(out);
+ out.close();
+ if (env.verbose()) {
+ env.output(Main.getText("rmic.wrote", stubFile.getPath()));
+ }
+ env.parseFile(new ClassFile(stubFile));
+ } catch (IOException e) {
+ env.error(0, "cant.write", stubFile.toString());
+ return;
+ }
+
+ if (version == STUB_VERSION_1_1 ||
+ version == STUB_VERSION_FAT)
+ {
+ env.addGeneratedFile(skeletonFile);
+
+ try {
+ IndentingWriter out = new IndentingWriter(
+ new OutputStreamWriter(
+ new FileOutputStream(skeletonFile)));
+ writeSkeleton(out);
+ out.close();
+ if (env.verbose()) {
+ env.output(Main.getText("rmic.wrote",
+ skeletonFile.getPath()));
+ }
+ env.parseFile(new ClassFile(skeletonFile));
+ } catch (IOException e) {
+ env.error(0, "cant.write", stubFile.toString());
+ return;
+ }
+ } else {
+ /*
+ * For bugid 4135136: if skeleton files are not being generated
+ * for this compilation run, delete old skeleton source or class
+ * files for this remote implementation class that were
+ * (presumably) left over from previous runs, to avoid user
+ * confusion from extraneous or inconsistent generated files.
+ */
+
+ File outputDir = Util.getOutputDirectoryFor(remoteClassName,destDir,env);
+ File skeletonClassFile = new File(outputDir,skeletonClassName.getName().toString() + ".class");
+
+ skeletonFile.delete(); // ignore failures (no big deal)
+ skeletonClassFile.delete();
+ }
+ }
+
+ /**
+ * Return the File object that should be used as the source file
+ * for the given Java class, using the supplied destination
+ * directory for the top of the package hierarchy.
+ */
+ protected static File sourceFileForClass(Identifier className,
+ Identifier outputClassName,
+ File destDir,
+ BatchEnvironment env)
+ {
+ File packageDir = Util.getOutputDirectoryFor(className,destDir,env);
+ String outputName = Names.mangleClass(outputClassName).getName().toString();
+
+ // Is there any existing _Tie equivalent leftover from a
+ // previous invocation of rmic -iiop? Only do this once per
+ // class by looking for skeleton generation...
+
+ if (outputName.endsWith("_Skel")) {
+ String classNameStr = className.getName().toString();
+ File temp = new File(packageDir, Utility.tieName(classNameStr) + ".class");
+ if (temp.exists()) {
+
+ // Found a tie. Is IIOP generation also being done?
+
+ if (!env.getMain().iiopGeneration) {
+
+ // No, so write a warning...
+
+ env.error(0,"warn.rmic.tie.found",
+ classNameStr,
+ temp.getAbsolutePath());
+ }
+ }
+ }
+
+ String outputFileName = outputName + ".java";
+ return new File(packageDir, outputFileName);
+ }
+
+
+ /** rmic environment for this object */
+ private BatchEnvironment env;
+
+ /** the remote class that this instance is generating code for */
+ private RemoteClass remoteClass;
+
+ /** version of the stub protocol to use in code generation */
+ private int version;
+
+ /** remote methods for remote class, indexed by operation number */
+ private RemoteClass.Method[] remoteMethods;
+
+ /**
+ * Names for the remote class and the stub and skeleton classes
+ * to be generated for it.
+ */
+ private Identifier remoteClassName;
+ private Identifier stubClassName;
+ private Identifier skeletonClassName;
+
+ private ClassDefinition cdef;
+ private File destDir;
+ private File stubFile;
+ private File skeletonFile;
+
+ /**
+ * Names to use for the java.lang.reflect.Method static fields
+ * corresponding to each remote method.
+ */
+ private String[] methodFieldNames;
+
+ /** cached definition for certain exception classes in this environment */
+ private ClassDefinition defException;
+ private ClassDefinition defRemoteException;
+ private ClassDefinition defRuntimeException;
+
+ /**
+ * Create a new stub/skeleton Generator object for the given
+ * remote implementation class to generate code according to
+ * the given stub protocol version.
+ */
+ private RMIGenerator(BatchEnvironment env, ClassDefinition cdef,
+ File destDir, RemoteClass remoteClass, int version)
+ throws ClassNotFound
+ {
+ this.destDir = destDir;
+ this.cdef = cdef;
+ this.env = env;
+ this.remoteClass = remoteClass;
+ this.version = version;
+
+ remoteMethods = remoteClass.getRemoteMethods();
+
+ remoteClassName = remoteClass.getName();
+ stubClassName = Names.stubFor(remoteClassName);
+ skeletonClassName = Names.skeletonFor(remoteClassName);
+
+ methodFieldNames = nameMethodFields(remoteMethods);
+
+ stubFile = sourceFileForClass(remoteClassName,stubClassName, destDir , env);
+ skeletonFile = sourceFileForClass(remoteClassName,skeletonClassName, destDir, env);
+
+ /*
+ * Initialize cached definitions for exception classes used
+ * in the generation process.
+ */
+ defException =
+ env.getClassDeclaration(idJavaLangException).
+ getClassDefinition(env);
+ defRemoteException =
+ env.getClassDeclaration(idRemoteException).
+ getClassDefinition(env);
+ defRuntimeException =
+ env.getClassDeclaration(idJavaLangRuntimeException).
+ getClassDefinition(env);
+ }
+
+ /**
+ * Write the stub for the remote class to a stream.
+ */
+ private void writeStub(IndentingWriter p) throws IOException {
+
+ /*
+ * Write boiler plate comment.
+ */
+ p.pln("// Stub class generated by rmic, do not edit.");
+ p.pln("// Contents subject to change without notice.");
+ p.pln();
+
+ /*
+ * If remote implementation class was in a particular package,
+ * declare the stub class to be in the same package.
+ */
+ if (remoteClassName.isQualified()) {
+ p.pln("package " + remoteClassName.getQualifier() + ";");
+ p.pln();
+ }
+
+ /*
+ * Declare the stub class; implement all remote interfaces.
+ */
+ p.plnI("public final class " +
+ Names.mangleClass(stubClassName.getName()));
+ p.pln("extends " + idRemoteStub);
+ ClassDefinition[] remoteInterfaces = remoteClass.getRemoteInterfaces();
+ if (remoteInterfaces.length > 0) {
+ p.p("implements ");
+ for (int i = 0; i < remoteInterfaces.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(remoteInterfaces[i].getName().toString());
+ }
+ p.pln();
+ }
+ p.pOlnI("{");
+
+ if (version == STUB_VERSION_1_1 ||
+ version == STUB_VERSION_FAT)
+ {
+ writeOperationsArray(p);
+ p.pln();
+ writeInterfaceHash(p);
+ p.pln();
+ }
+
+ if (version == STUB_VERSION_FAT ||
+ version == STUB_VERSION_1_2)
+ {
+ p.pln("private static final long serialVersionUID = " +
+ STUB_SERIAL_VERSION_UID + ";");
+ p.pln();
+
+ /*
+ * We only need to declare and initialize the static fields of
+ * Method objects for each remote method if there are any remote
+ * methods; otherwise, skip this code entirely, to avoid generating
+ * a try/catch block for a checked exception that cannot occur
+ * (see bugid 4125181).
+ */
+ if (methodFieldNames.length > 0) {
+ if (version == STUB_VERSION_FAT) {
+ p.pln("private static boolean useNewInvoke;");
+ }
+ writeMethodFieldDeclarations(p);
+ p.pln();
+
+ /*
+ * Initialize java.lang.reflect.Method fields for each remote
+ * method in a static initializer.
+ */
+ p.plnI("static {");
+ p.plnI("try {");
+ if (version == STUB_VERSION_FAT) {
+ /*
+ * Fat stubs must determine whether the API required for
+ * the JDK 1.2 stub protocol is supported in the current
+ * runtime, so that it can use it if supported. This is
+ * determined by using the Reflection API to test if the
+ * new invoke method on RemoteRef exists, and setting the
+ * static boolean "useNewInvoke" to true if it does, or
+ * to false if a NoSuchMethodException is thrown.
+ */
+ p.plnI(idRemoteRef + ".class.getMethod(\"invoke\",");
+ p.plnI("new java.lang.Class[] {");
+ p.pln(idRemote + ".class,");
+ p.pln("java.lang.reflect.Method.class,");
+ p.pln("java.lang.Object[].class,");
+ p.pln("long.class");
+ p.pOln("});");
+ p.pO();
+ p.pln("useNewInvoke = true;");
+ }
+ writeMethodFieldInitializers(p);
+ p.pOlnI("} catch (java.lang.NoSuchMethodException e) {");
+ if (version == STUB_VERSION_FAT) {
+ p.pln("useNewInvoke = false;");
+ } else {
+ /*
+ * REMIND: By throwing an Error here, the application will
+ * get the NoSuchMethodError directly when the stub class
+ * is initialized. If we throw a RuntimeException
+ * instead, the application would get an
+ * ExceptionInInitializerError. Would that be more
+ * appropriate, and if so, which RuntimeException should
+ * be thrown?
+ */
+ p.plnI("throw new java.lang.NoSuchMethodError(");
+ p.pln("\"stub class initialization failed\");");
+ p.pO();
+ }
+ p.pOln("}"); // end try/catch block
+ p.pOln("}"); // end static initializer
+ p.pln();
+ }
+ }
+
+ writeStubConstructors(p);
+ p.pln();
+
+ /*
+ * Write each stub method.
+ */
+ if (remoteMethods.length > 0) {
+ p.pln("// methods from remote interfaces");
+ for (int i = 0; i < remoteMethods.length; ++i) {
+ p.pln();
+ writeStubMethod(p, i);
+ }
+ }
+
+ p.pOln("}"); // end stub class
+ }
+
+ /**
+ * Write the constructors for the stub class.
+ */
+ private void writeStubConstructors(IndentingWriter p)
+ throws IOException
+ {
+ p.pln("// constructors");
+
+ /*
+ * Only stubs compatible with the JDK 1.1 stub protocol need
+ * a no-arg constructor; later versions use reflection to find
+ * the constructor that directly takes a RemoteRef argument.
+ */
+ if (version == STUB_VERSION_1_1 ||
+ version == STUB_VERSION_FAT)
+ {
+ p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
+ "() {");
+ p.pln("super();");
+ p.pOln("}");
+ }
+
+ p.plnI("public " + Names.mangleClass(stubClassName.getName()) +
+ "(" + idRemoteRef + " ref) {");
+ p.pln("super(ref);");
+ p.pOln("}");
+ }
+
+ /**
+ * Write the stub method for the remote method with the given "opnum".
+ */
+ private void writeStubMethod(IndentingWriter p, int opnum)
+ throws IOException
+ {
+ RemoteClass.Method method = remoteMethods[opnum];
+ Identifier methodName = method.getName();
+ Type methodType = method.getType();
+ Type paramTypes[] = methodType.getArgumentTypes();
+ String paramNames[] = nameParameters(paramTypes);
+ Type returnType = methodType.getReturnType();
+ ClassDeclaration[] exceptions = method.getExceptions();
+
+ /*
+ * Declare stub method; throw exceptions declared in remote
+ * interface(s).
+ */
+ p.pln("// implementation of " +
+ methodType.typeString(methodName.toString(), true, false));
+ p.p("public " + returnType + " " + methodName + "(");
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(paramTypes[i] + " " + paramNames[i]);
+ }
+ p.plnI(")");
+ if (exceptions.length > 0) {
+ p.p("throws ");
+ for (int i = 0; i < exceptions.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(exceptions[i].getName().toString());
+ }
+ p.pln();
+ }
+ p.pOlnI("{");
+
+ /*
+ * The RemoteRef.invoke methods throw Exception, but unless this
+ * stub method throws Exception as well, we must catch Exceptions
+ * thrown from the invocation. So we must catch Exception and
+ * rethrow something we can throw: UnexpectedException, which is a
+ * subclass of RemoteException. But for any subclasses of Exception
+ * that we can throw, like RemoteException, RuntimeException, and
+ * any of the exceptions declared by this stub method, we want them
+ * to pass through unharmed, so first we must catch any such
+ * exceptions and rethrow it directly.
+ *
+ * We have to be careful generating the rethrowing catch blocks
+ * here, because javac will flag an error if there are any
+ * unreachable catch blocks, i.e. if the catch of an exception class
+ * follows a previous catch of it or of one of its superclasses.
+ * The following method invocation takes care of these details.
+ */
+ Vector catchList = computeUniqueCatchList(exceptions);
+
+ /*
+ * If we need to catch any particular exceptions (i.e. this method
+ * does not declare java.lang.Exception), put the entire stub
+ * method in a try block.
+ */
+ if (catchList.size() > 0) {
+ p.plnI("try {");
+ }
+
+ if (version == STUB_VERSION_FAT) {
+ p.plnI("if (useNewInvoke) {");
+ }
+ if (version == STUB_VERSION_FAT ||
+ version == STUB_VERSION_1_2)
+ {
+ if (!returnType.isType(TC_VOID)) {
+ p.p("Object $result = "); // REMIND: why $?
+ }
+ p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", ");
+ if (paramTypes.length > 0) {
+ p.p("new java.lang.Object[] {");
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(wrapArgumentCode(paramTypes[i], paramNames[i]));
+ }
+ p.p("}");
+ } else {
+ p.p("null");
+ }
+ p.pln(", " + method.getMethodHash() + "L);");
+ if (!returnType.isType(TC_VOID)) {
+ p.pln("return " +
+ unwrapArgumentCode(returnType, "$result") + ";");
+ }
+ }
+ if (version == STUB_VERSION_FAT) {
+ p.pOlnI("} else {");
+ }
+ if (version == STUB_VERSION_1_1 ||
+ version == STUB_VERSION_FAT)
+ {
+ p.pln(idRemoteCall + " call = ref.newCall((" + idRemoteObject +
+ ") this, operations, " + opnum + ", interfaceHash);");
+
+ if (paramTypes.length > 0) {
+ p.plnI("try {");
+ p.pln("java.io.ObjectOutput out = call.getOutputStream();");
+ writeMarshalArguments(p, "out", paramTypes, paramNames);
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + idMarshalException +
+ "(\"error marshalling arguments\", e);");
+ p.pOln("}");
+ }
+
+ p.pln("ref.invoke(call);");
+
+ if (returnType.isType(TC_VOID)) {
+ p.pln("ref.done(call);");
+ } else {
+ p.pln(returnType + " $result;"); // REMIND: why $?
+ p.plnI("try {");
+ p.pln("java.io.ObjectInput in = call.getInputStream();");
+ boolean objectRead =
+ writeUnmarshalArgument(p, "in", returnType, "$result");
+ p.pln(";");
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + idUnmarshalException +
+ "(\"error unmarshalling return\", e);");
+ /*
+ * If any only if readObject has been invoked, we must catch
+ * ClassNotFoundException as well as IOException.
+ */
+ if (objectRead) {
+ p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
+ p.pln("throw new " + idUnmarshalException +
+ "(\"error unmarshalling return\", e);");
+ }
+ p.pOlnI("} finally {");
+ p.pln("ref.done(call);");
+ p.pOln("}");
+ p.pln("return $result;");
+ }
+ }
+ if (version == STUB_VERSION_FAT) {
+ p.pOln("}"); // end if/else (useNewInvoke) block
+ }
+
+ /*
+ * If we need to catch any particular exceptions, finally write
+ * the catch blocks for them, rethrow any other Exceptions with an
+ * UnexpectedException, and end the try block.
+ */
+ if (catchList.size() > 0) {
+ for (Enumeration enumeration = catchList.elements();
+ enumeration.hasMoreElements();)
+ {
+ ClassDefinition def = (ClassDefinition) enumeration.nextElement();
+ p.pOlnI("} catch (" + def.getName() + " e) {");
+ p.pln("throw e;");
+ }
+ p.pOlnI("} catch (java.lang.Exception e) {");
+ p.pln("throw new " + idUnexpectedException +
+ "(\"undeclared checked exception\", e);");
+ p.pOln("}"); // end try/catch block
+ }
+
+ p.pOln("}"); // end stub method
+ }
+
+ /**
+ * Compute the exceptions which need to be caught and rethrown in a
+ * stub method before wrapping Exceptions in UnexpectedExceptions,
+ * given the exceptions declared in the throws clause of the method.
+ * Returns a Vector containing ClassDefinition objects for each
+ * exception to catch. Each exception is guaranteed to be unique,
+ * i.e. not a subclass of any of the other exceptions in the Vector,
+ * so the catch blocks for these exceptions may be generated in any
+ * order relative to each other.
+ *
+ * RemoteException and RuntimeException are each automatically placed
+ * in the returned Vector (if none of their superclasses are already
+ * present), since those exceptions should always be directly rethrown
+ * by a stub method.
+ *
+ * The returned Vector will be empty if java.lang.Exception or one
+ * of its superclasses is in the throws clause of the method, indicating
+ * that no exceptions need to be caught.
+ */
+ private Vector computeUniqueCatchList(ClassDeclaration[] exceptions) {
+ Vector uniqueList = new Vector(); // unique exceptions to catch
+
+ uniqueList.addElement(defRuntimeException);
+ uniqueList.addElement(defRemoteException);
+
+ /* For each exception declared by the stub method's throws clause: */
+ nextException:
+ for (int i = 0; i < exceptions.length; i++) {
+ ClassDeclaration decl = exceptions[i];
+ try {
+ if (defException.subClassOf(env, decl)) {
+ /*
+ * (If java.lang.Exception (or a superclass) was declared
+ * in the throws clause of this stub method, then we don't
+ * have to bother catching anything; clear the list and
+ * return.)
+ */
+ uniqueList.clear();
+ break;
+ } else if (!defException.superClassOf(env, decl)) {
+ /*
+ * Ignore other Throwables that do not extend Exception,
+ * since they do not need to be caught anyway.
+ */
+ continue;
+ }
+ /*
+ * Compare this exception against the current list of
+ * exceptions that need to be caught:
+ */
+ for (int j = 0; j < uniqueList.size();) {
+ ClassDefinition def =
+ (ClassDefinition) uniqueList.elementAt(j);
+ if (def.superClassOf(env, decl)) {
+ /*
+ * If a superclass of this exception is already on
+ * the list to catch, then ignore and continue;
+ */
+ continue nextException;
+ } else if (def.subClassOf(env, decl)) {
+ /*
+ * If a subclass of this exception is on the list
+ * to catch, then remove it.
+ */
+ uniqueList.removeElementAt(j);
+ } else {
+ j++; // else continue comparing
+ }
+ }
+ /* This exception is unique: add it to the list to catch. */
+ uniqueList.addElement(decl.getClassDefinition(env));
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name, decl.getName());
+ /*
+ * REMIND: We do not exit from this exceptional condition,
+ * generating questionable code and likely letting the
+ * compiler report a resulting error later.
+ */
+ }
+ }
+ return uniqueList;
+ }
+
+ /**
+ * Write the skeleton for the remote class to a stream.
+ */
+ private void writeSkeleton(IndentingWriter p) throws IOException {
+ if (version == STUB_VERSION_1_2) {
+ throw new Error("should not generate skeleton for version");
+ }
+
+ /*
+ * Write boiler plate comment.
+ */
+ p.pln("// Skeleton class generated by rmic, do not edit.");
+ p.pln("// Contents subject to change without notice.");
+ p.pln();
+
+ /*
+ * If remote implementation class was in a particular package,
+ * declare the skeleton class to be in the same package.
+ */
+ if (remoteClassName.isQualified()) {
+ p.pln("package " + remoteClassName.getQualifier() + ";");
+ p.pln();
+ }
+
+ /*
+ * Declare the skeleton class.
+ */
+ p.plnI("public final class " +
+ Names.mangleClass(skeletonClassName.getName()));
+ p.pln("implements " + idSkeleton);
+ p.pOlnI("{");
+
+ writeOperationsArray(p);
+ p.pln();
+
+ writeInterfaceHash(p);
+ p.pln();
+
+ /*
+ * Define the getOperations() method.
+ */
+ p.plnI("public " + idOperation + "[] getOperations() {");
+ p.pln("return (" + idOperation + "[]) operations.clone();");
+ p.pOln("}");
+ p.pln();
+
+ /*
+ * Define the dispatch() method.
+ */
+ p.plnI("public void dispatch(" + idRemote + " obj, " +
+ idRemoteCall + " call, int opnum, long hash)");
+ p.pln("throws java.lang.Exception");
+ p.pOlnI("{");
+
+ if (version == STUB_VERSION_FAT) {
+ p.plnI("if (opnum < 0) {");
+ if (remoteMethods.length > 0) {
+ for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
+ if (opnum > 0)
+ p.pO("} else ");
+ p.plnI("if (hash == " +
+ remoteMethods[opnum].getMethodHash() + "L) {");
+ p.pln("opnum = " + opnum + ";");
+ }
+ p.pOlnI("} else {");
+ }
+ /*
+ * Skeleton throws UnmarshalException if it does not recognize
+ * the method hash; this is what UnicastServerRef.dispatch()
+ * would do.
+ */
+ p.pln("throw new " +
+ idUnmarshalException + "(\"invalid method hash\");");
+ if (remoteMethods.length > 0) {
+ p.pOln("}");
+ }
+ /*
+ * Ignore the validation of the interface hash if the
+ * operation number was negative, since it is really a
+ * method hash instead.
+ */
+ p.pOlnI("} else {");
+ }
+
+ p.plnI("if (hash != interfaceHash)");
+ p.pln("throw new " +
+ idSkeletonMismatchException + "(\"interface hash mismatch\");");
+ p.pO();
+
+ if (version == STUB_VERSION_FAT) {
+ p.pOln("}"); // end if/else (opnum < 0) block
+ }
+ p.pln();
+
+ /*
+ * Cast remote object instance to our specific implementation class.
+ */
+ p.pln(remoteClassName + " server = (" + remoteClassName + ") obj;");
+
+ /*
+ * Process call according to the operation number.
+ */
+ p.plnI("switch (opnum) {");
+ for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
+ writeSkeletonDispatchCase(p, opnum);
+ }
+ p.pOlnI("default:");
+ /*
+ * Skeleton throws UnmarshalException if it does not recognize
+ * the operation number; this is consistent with the case of an
+ * unrecognized method hash.
+ */
+ p.pln("throw new " + idUnmarshalException +
+ "(\"invalid method number\");");
+ p.pOln("}"); // end switch statement
+
+ p.pOln("}"); // end dispatch() method
+
+ p.pOln("}"); // end skeleton class
+ }
+
+ /**
+ * Write the case block for the skeleton's dispatch method for
+ * the remote method with the given "opnum".
+ */
+ private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
+ throws IOException
+ {
+ RemoteClass.Method method = remoteMethods[opnum];
+ Identifier methodName = method.getName();
+ Type methodType = method.getType();
+ Type paramTypes[] = methodType.getArgumentTypes();
+ String paramNames[] = nameParameters(paramTypes);
+ Type returnType = methodType.getReturnType();
+
+ p.pOlnI("case " + opnum + ": // " +
+ methodType.typeString(methodName.toString(), true, false));
+ /*
+ * Use nested block statement inside case to provide an independent
+ * namespace for local variables used to unmarshal parameters for
+ * this remote method.
+ */
+ p.pOlnI("{");
+
+ if (paramTypes.length > 0) {
+ /*
+ * Declare local variables to hold arguments.
+ */
+ for (int i = 0; i < paramTypes.length; i++) {
+ p.pln(paramTypes[i] + " " + paramNames[i] + ";");
+ }
+
+ /*
+ * Unmarshal arguments from call stream.
+ */
+ p.plnI("try {");
+ p.pln("java.io.ObjectInput in = call.getInputStream();");
+ boolean objectsRead = writeUnmarshalArguments(p, "in",
+ paramTypes, paramNames);
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + idUnmarshalException +
+ "(\"error unmarshalling arguments\", e);");
+ /*
+ * If any only if readObject has been invoked, we must catch
+ * ClassNotFoundException as well as IOException.
+ */
+ if (objectsRead) {
+ p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
+ p.pln("throw new " + idUnmarshalException +
+ "(\"error unmarshalling arguments\", e);");
+ }
+ p.pOlnI("} finally {");
+ p.pln("call.releaseInputStream();");
+ p.pOln("}");
+ } else {
+ p.pln("call.releaseInputStream();");
+ }
+
+ if (!returnType.isType(TC_VOID)) {
+ /*
+ * Declare variable to hold return type, if not void.
+ */
+ p.p(returnType + " $result = "); // REMIND: why $?
+ }
+
+ /*
+ * Invoke the method on the server object.
+ */
+ p.p("server." + methodName + "(");
+ for (int i = 0; i < paramNames.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(paramNames[i]);
+ }
+ p.pln(");");
+
+ /*
+ * Always invoke getResultStream(true) on the call object to send
+ * the indication of a successful invocation to the caller. If
+ * the return type is not void, keep the result stream and marshal
+ * the return value.
+ */
+ p.plnI("try {");
+ if (!returnType.isType(TC_VOID)) {
+ p.p("java.io.ObjectOutput out = ");
+ }
+ p.pln("call.getResultStream(true);");
+ if (!returnType.isType(TC_VOID)) {
+ writeMarshalArgument(p, "out", returnType, "$result");
+ p.pln(";");
+ }
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " +
+ idMarshalException + "(\"error marshalling return\", e);");
+ p.pOln("}");
+
+ p.pln("break;"); // break from switch statement
+
+ p.pOlnI("}"); // end nested block statement
+ p.pln();
+ }
+
+ /**
+ * Write declaration and initializer for "operations" static array.
+ */
+ private void writeOperationsArray(IndentingWriter p)
+ throws IOException
+ {
+ p.plnI("private static final " + idOperation + "[] operations = {");
+ for (int i = 0; i < remoteMethods.length; i++) {
+ if (i > 0)
+ p.pln(",");
+ p.p("new " + idOperation + "(\"" +
+ remoteMethods[i].getOperationString() + "\")");
+ }
+ p.pln();
+ p.pOln("};");
+ }
+
+ /**
+ * Write declaration and initializer for "interfaceHash" static field.
+ */
+ private void writeInterfaceHash(IndentingWriter p)
+ throws IOException
+ {
+ p.pln("private static final long interfaceHash = " +
+ remoteClass.getInterfaceHash() + "L;");
+ }
+
+ /**
+ * Write declaration for java.lang.reflect.Method static fields
+ * corresponding to each remote method in a stub.
+ */
+ private void writeMethodFieldDeclarations(IndentingWriter p)
+ throws IOException
+ {
+ for (int i = 0; i < methodFieldNames.length; i++) {
+ p.pln("private static java.lang.reflect.Method " +
+ methodFieldNames[i] + ";");
+ }
+ }
+
+ /**
+ * Write code to initialize the static fields for each method
+ * using the Java Reflection API.
+ */
+ private void writeMethodFieldInitializers(IndentingWriter p)
+ throws IOException
+ {
+ for (int i = 0; i < methodFieldNames.length; i++) {
+ p.p(methodFieldNames[i] + " = ");
+ /*
+ * Here we look up the Method object in the arbitrary interface
+ * that we find in the RemoteClass.Method object.
+ * REMIND: Is this arbitrary choice OK?
+ * REMIND: Should this access be part of RemoteClass.Method's
+ * abstraction?
+ */
+ RemoteClass.Method method = remoteMethods[i];
+ MemberDefinition def = method.getMemberDefinition();
+ Identifier methodName = method.getName();
+ Type methodType = method.getType();
+ Type paramTypes[] = methodType.getArgumentTypes();
+
+ p.p(def.getClassDefinition().getName() + ".class.getMethod(\"" +
+ methodName + "\", new java.lang.Class[] {");
+ for (int j = 0; j < paramTypes.length; j++) {
+ if (j > 0)
+ p.p(", ");
+ p.p(paramTypes[j] + ".class");
+ }
+ p.pln("});");
+ }
+ }
+
+
+ /*
+ * Following are a series of static utility methods useful during
+ * the code generation process:
+ */
+
+ /**
+ * Generate an array of names for fields that correspond to the given
+ * array of remote methods. Each name in the returned array is
+ * guaranteed to be unique.
+ *
+ * The name of a method is included in its corresponding field name
+ * to enhance readability of the generated code.
+ */
+ private static String[] nameMethodFields(RemoteClass.Method[] methods) {
+ String[] names = new String[methods.length];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = "$method_" + methods[i].getName() + "_" + i;
+ }
+ return names;
+ }
+
+ /**
+ * Generate an array of names for parameters corresponding to the
+ * given array of types for the parameters. Each name in the returned
+ * array is guaranteed to be unique.
+ *
+ * A representation of the type of a parameter is included in its
+ * corresponding field name to enhance the readability of the generated
+ * code.
+ */
+ private static String[] nameParameters(Type[] types) {
+ String[] names = new String[types.length];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = "$param_" +
+ generateNameFromType(types[i]) + "_" + (i + 1);
+ }
+ return names;
+ }
+
+ /**
+ * Generate a readable string representing the given type suitable
+ * for embedding within a Java identifier.
+ */
+ private static String generateNameFromType(Type type) {
+ int typeCode = type.getTypeCode();
+ switch (typeCode) {
+ case TC_BOOLEAN:
+ case TC_BYTE:
+ case TC_CHAR:
+ case TC_SHORT:
+ case TC_INT:
+ case TC_LONG:
+ case TC_FLOAT:
+ case TC_DOUBLE:
+ return type.toString();
+ case TC_ARRAY:
+ return "arrayOf_" + generateNameFromType(type.getElementType());
+ case TC_CLASS:
+ return Names.mangleClass(type.getClassName().getName()).toString();
+ default:
+ throw new Error("unexpected type code: " + typeCode);
+ }
+ }
+
+ /**
+ * Write a snippet of Java code to marshal a value named "name" of
+ * type "type" to the java.io.ObjectOutput stream named "stream".
+ *
+ * Primitive types are marshalled with their corresponding methods
+ * in the java.io.DataOutput interface, and objects (including arrays)
+ * are marshalled using the writeObject method.
+ */
+ private static void writeMarshalArgument(IndentingWriter p,
+ String streamName,
+ Type type, String name)
+ throws IOException
+ {
+ int typeCode = type.getTypeCode();
+ switch (typeCode) {
+ case TC_BOOLEAN:
+ p.p(streamName + ".writeBoolean(" + name + ")");
+ break;
+ case TC_BYTE:
+ p.p(streamName + ".writeByte(" + name + ")");
+ break;
+ case TC_CHAR:
+ p.p(streamName + ".writeChar(" + name + ")");
+ break;
+ case TC_SHORT:
+ p.p(streamName + ".writeShort(" + name + ")");
+ break;
+ case TC_INT:
+ p.p(streamName + ".writeInt(" + name + ")");
+ break;
+ case TC_LONG:
+ p.p(streamName + ".writeLong(" + name + ")");
+ break;
+ case TC_FLOAT:
+ p.p(streamName + ".writeFloat(" + name + ")");
+ break;
+ case TC_DOUBLE:
+ p.p(streamName + ".writeDouble(" + name + ")");
+ break;
+ case TC_ARRAY:
+ case TC_CLASS:
+ p.p(streamName + ".writeObject(" + name + ")");
+ break;
+ default:
+ throw new Error("unexpected type code: " + typeCode);
+ }
+ }
+
+ /**
+ * Write Java statements to marshal a series of values in order as
+ * named in the "names" array, with types as specified in the "types"
+ * array", to the java.io.ObjectOutput stream named "stream".
+ */
+ private static void writeMarshalArguments(IndentingWriter p,
+ String streamName,
+ Type[] types, String[] names)
+ throws IOException
+ {
+ if (types.length != names.length) {
+ throw new Error("paramter type and name arrays different sizes");
+ }
+
+ for (int i = 0; i < types.length; i++) {
+ writeMarshalArgument(p, streamName, types[i], names[i]);
+ p.pln(";");
+ }
+ }
+
+ /**
+ * Write a snippet of Java code to unmarshal a value of type "type"
+ * from the java.io.ObjectInput stream named "stream" into a variable
+ * named "name" (if "name" is null, the value in unmarshalled and
+ * discarded).
+ *
+ * Primitive types are unmarshalled with their corresponding methods
+ * in the java.io.DataInput interface, and objects (including arrays)
+ * are unmarshalled using the readObject method.
+ */
+ private static boolean writeUnmarshalArgument(IndentingWriter p,
+ String streamName,
+ Type type, String name)
+ throws IOException
+ {
+ boolean readObject = false;
+
+ if (name != null) {
+ p.p(name + " = ");
+ }
+
+ int typeCode = type.getTypeCode();
+ switch (type.getTypeCode()) {
+ case TC_BOOLEAN:
+ p.p(streamName + ".readBoolean()");
+ break;
+ case TC_BYTE:
+ p.p(streamName + ".readByte()");
+ break;
+ case TC_CHAR:
+ p.p(streamName + ".readChar()");
+ break;
+ case TC_SHORT:
+ p.p(streamName + ".readShort()");
+ break;
+ case TC_INT:
+ p.p(streamName + ".readInt()");
+ break;
+ case TC_LONG:
+ p.p(streamName + ".readLong()");
+ break;
+ case TC_FLOAT:
+ p.p(streamName + ".readFloat()");
+ break;
+ case TC_DOUBLE:
+ p.p(streamName + ".readDouble()");
+ break;
+ case TC_ARRAY:
+ case TC_CLASS:
+ p.p("(" + type + ") " + streamName + ".readObject()");
+ readObject = true;
+ break;
+ default:
+ throw new Error("unexpected type code: " + typeCode);
+ }
+ return readObject;
+ }
+
+ /**
+ * Write Java statements to unmarshal a series of values in order of
+ * types as in the "types" array from the java.io.ObjectInput stream
+ * named "stream" into variables as named in "names" (for any element
+ * of "names" that is null, the corresponding value is unmarshalled
+ * and discarded).
+ */
+ private static boolean writeUnmarshalArguments(IndentingWriter p,
+ String streamName,
+ Type[] types,
+ String[] names)
+ throws IOException
+ {
+ if (types.length != names.length) {
+ throw new Error("paramter type and name arrays different sizes");
+ }
+
+ boolean readObject = false;
+ for (int i = 0; i < types.length; i++) {
+ if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
+ readObject = true;
+ }
+ p.pln(";");
+ }
+ return readObject;
+ }
+
+ /**
+ * Return a snippet of Java code to wrap a value named "name" of
+ * type "type" into an object as appropriate for use by the
+ * Java Reflection API.
+ *
+ * For primitive types, an appropriate wrapper class instantiated
+ * with the primitive value. For object types (including arrays),
+ * no wrapping is necessary, so the value is named directly.
+ */
+ private static String wrapArgumentCode(Type type, String name) {
+ int typeCode = type.getTypeCode();
+ switch (typeCode) {
+ case TC_BOOLEAN:
+ return ("(" + name +
+ " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
+ case TC_BYTE:
+ return "new java.lang.Byte(" + name + ")";
+ case TC_CHAR:
+ return "new java.lang.Character(" + name + ")";
+ case TC_SHORT:
+ return "new java.lang.Short(" + name + ")";
+ case TC_INT:
+ return "new java.lang.Integer(" + name + ")";
+ case TC_LONG:
+ return "new java.lang.Long(" + name + ")";
+ case TC_FLOAT:
+ return "new java.lang.Float(" + name + ")";
+ case TC_DOUBLE:
+ return "new java.lang.Double(" + name + ")";
+ case TC_ARRAY:
+ case TC_CLASS:
+ return name;
+ default:
+ throw new Error("unexpected type code: " + typeCode);
+ }
+ }
+
+ /**
+ * Return a snippet of Java code to unwrap a value named "name" into
+ * a value of type "type", as appropriate for the Java Reflection API.
+ *
+ * For primitive types, the value is assumed to be of the corresponding
+ * wrapper type, and a method is called on the wrapper type to retrieve
+ * the primitive value. For object types (include arrays), no
+ * unwrapping is necessary; the value is simply cast to the expected
+ * real object type.
+ */
+ private static String unwrapArgumentCode(Type type, String name) {
+ int typeCode = type.getTypeCode();
+ switch (typeCode) {
+ case TC_BOOLEAN:
+ return "((java.lang.Boolean) " + name + ").booleanValue()";
+ case TC_BYTE:
+ return "((java.lang.Byte) " + name + ").byteValue()";
+ case TC_CHAR:
+ return "((java.lang.Character) " + name + ").charValue()";
+ case TC_SHORT:
+ return "((java.lang.Short) " + name + ").shortValue()";
+ case TC_INT:
+ return "((java.lang.Integer) " + name + ").intValue()";
+ case TC_LONG:
+ return "((java.lang.Long) " + name + ").longValue()";
+ case TC_FLOAT:
+ return "((java.lang.Float) " + name + ").floatValue()";
+ case TC_DOUBLE:
+ return "((java.lang.Double) " + name + ").doubleValue()";
+ case TC_ARRAY:
+ case TC_CLASS:
+ return "((" + type + ") " + name + ")";
+ default:
+ throw new Error("unexpected type code: " + typeCode);
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/RemoteClass.java b/src/share/classes/sun/rmi/rmic/RemoteClass.java
new file mode 100644
index 000000000..0caa07fe0
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/RemoteClass.java
@@ -0,0 +1,875 @@
+/*
+ * Copyright 1997-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic;
+
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.security.MessageDigest;
+import java.security.DigestOutputStream;
+import java.security.NoSuchAlgorithmException;
+import sun.tools.java.Type;
+import sun.tools.java.ClassDefinition;
+import sun.tools.java.ClassDeclaration;
+import sun.tools.java.MemberDefinition;
+import sun.tools.java.Identifier;
+import sun.tools.java.ClassNotFound;
+
+/**
+ * A RemoteClass object encapsulates RMI-specific information about
+ * a remote implementation class, i.e. a class that implements
+ * one or more remote interfaces.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ */
+public class RemoteClass implements sun.rmi.rmic.RMIConstants {
+
+ /**
+ * Create a RemoteClass object representing the remote meta-information
+ * of the given class.
+ *
+ * Returns true if successful. If the class is not a properly formed
+ * remote implementation class or if some other error occurs, the
+ * return value will be null, and errors will have been reported to
+ * the supplied BatchEnvironment.
+ */
+ public static RemoteClass forClass(BatchEnvironment env,
+ ClassDefinition implClassDef)
+ {
+ RemoteClass rc = new RemoteClass(env, implClassDef);
+ if (rc.initialize()) {
+ return rc;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the ClassDefinition for this class.
+ */
+ public ClassDefinition getClassDefinition() {
+ return implClassDef;
+ }
+
+ /**
+ * Return the name of the class represented by this object.
+ */
+ public Identifier getName() {
+ return implClassDef.getName();
+ }
+
+ /**
+ * Return an array of ClassDefinitions representing all of the remote
+ * interfaces implemented by this class.
+ *
+ * A remote interface is any interface that extends Remote,
+ * directly or indirectly. The remote interfaces of a class
+ * are the interfaces directly listed in either the class's
+ * "implements" clause, or the "implements" clause of any
+ * of its superclasses, that are remote interfaces.
+ *
+ * The order of the array returned is arbitrary, and some elements
+ * may be superfluous (i.e., superinterfaces of other interfaces
+ * in the array).
+ */
+ public ClassDefinition[] getRemoteInterfaces() {
+ return (ClassDefinition[]) remoteInterfaces.clone();
+ }
+
+ /**
+ * Return an array of RemoteClass.Method objects representing all of
+ * the remote methods implemented by this class, i.e. all of the
+ * methods in the class's remote interfaces.
+ *
+ * The methods in the array are ordered according to the comparision
+ * of the strings consisting of their method name followed by their
+ * type signature, so each method's index in the array corresponds
+ * to its "operation number" in the JDK 1.1 version of the
+ * stub/skeleton protocol.
+ */
+ public Method[] getRemoteMethods() {
+ return (Method[]) remoteMethods.clone();
+ }
+
+ /**
+ * Return the "interface hash" used to match a stub/skeleton pair for
+ * this class in the JDK 1.1 version of the stub/skeleton protocol.
+ */
+ public long getInterfaceHash() {
+ return interfaceHash;
+ }
+
+ /**
+ * Return string representation of this object, consisting of
+ * the string "remote class " followed by the class name.
+ */
+ public String toString() {
+ return "remote class " + implClassDef.getName().toString();
+ }
+
+ /** rmic environment for this object */
+ private BatchEnvironment env;
+
+ /** the remote implementation class this object corresponds to */
+ private ClassDefinition implClassDef;
+
+ /** remote interfaces implemented by this class */
+ private ClassDefinition[] remoteInterfaces;
+
+ /** all the remote methods of this class */
+ private Method[] remoteMethods;
+
+ /** stub/skeleton "interface hash" for this class */
+ private long interfaceHash;
+
+ /** cached definition for certain classes used in this environment */
+ private ClassDefinition defRemote;
+ private ClassDefinition defException;
+ private ClassDefinition defRemoteException;
+
+ /**
+ * Create a RemoteClass instance for the given class. The resulting
+ * object is not yet initialized.
+ */
+ private RemoteClass(BatchEnvironment env, ClassDefinition implClassDef) {
+ this.env = env;
+ this.implClassDef = implClassDef;
+ }
+
+ /**
+ * Validate that the remote implementation class is properly formed
+ * and fill in the data structures required by the public interface.
+ */
+ private boolean initialize() {
+ /*
+ * Verify that the "impl" is really a class, not an interface.
+ */
+ if (implClassDef.isInterface()) {
+ env.error(0, "rmic.cant.make.stubs.for.interface",
+ implClassDef.getName());
+ return false;
+ }
+
+ /*
+ * Initialize cached definitions for the Remote interface and
+ * the RemoteException class.
+ */
+ try {
+ defRemote =
+ env.getClassDeclaration(idRemote).getClassDefinition(env);
+ defException =
+ env.getClassDeclaration(idJavaLangException).
+ getClassDefinition(env);
+ defRemoteException =
+ env.getClassDeclaration(idRemoteException).
+ getClassDefinition(env);
+ } catch (ClassNotFound e) {
+ env.error(0, "rmic.class.not.found", e.name);
+ return false;
+ }
+
+ /*
+ * Here we find all of the remote interfaces of our remote
+ * implementation class. For each class up the superclass
+ * chain, add each directly-implemented interface that
+ * somehow extends Remote to a list.
+ */
+ Vector remotesImplemented = // list of remote interfaces found
+ new Vector();
+ for (ClassDefinition classDef = implClassDef;
+ classDef != null;)
+ {
+ try {
+ ClassDeclaration[] interfaces = classDef.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ ClassDefinition interfaceDef =
+ interfaces[i].getClassDefinition(env);
+ /*
+ * Add interface to the list if it extends Remote and
+ * it is not already there.
+ */
+ if (!remotesImplemented.contains(interfaceDef) &&
+ defRemote.implementedBy(env, interfaces[i]))
+ {
+ remotesImplemented.addElement(interfaceDef);
+ /***** <DEBUG> */
+ if (env.verbose()) {
+ System.out.println("[found remote interface: " +
+ interfaceDef.getName() + "]");
+ /***** </DEBUG> */
+ }
+ }
+ }
+
+ /*
+ * Verify that the candidate remote implementation class
+ * implements at least one remote interface directly.
+ */
+ if (classDef == implClassDef && remotesImplemented.isEmpty()) {
+ if (defRemote.implementedBy(env,
+ implClassDef.getClassDeclaration()))
+ {
+ /*
+ * This error message is used if the class does
+ * implement a remote interface through one of
+ * its superclasses, but not directly.
+ */
+ env.error(0, "rmic.must.implement.remote.directly",
+ implClassDef.getName());
+ } else {
+ /*
+ * This error message is used if the class never
+ * implements a remote interface.
+ */
+ env.error(0, "rmic.must.implement.remote",
+ implClassDef.getName());
+ }
+ return false;
+ }
+
+ /*
+ * Get definition for next superclass.
+ */
+ classDef = (classDef.getSuperClass() != null ?
+ classDef.getSuperClass().getClassDefinition(env) :
+ null);
+
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name, classDef.getName());
+ return false;
+ }
+ }
+
+ /*
+ * The "remotesImplemented" vector now contains all of the remote
+ * interfaces directly implemented by the remote class or by any
+ * of its superclasses.
+ *
+ * At this point, we could optimize the list by removing superfluous
+ * entries, i.e. any interfaces that are implemented by some other
+ * interface in the list anyway.
+ *
+ * This should be correct; would it be worthwhile?
+ *
+ * for (int i = 0; i < remotesImplemented.size();) {
+ * ClassDefinition interfaceDef =
+ * (ClassDefinition) remotesImplemented.elementAt(i);
+ * boolean isOtherwiseImplemented = false;
+ * for (int j = 0; j < remotesImplemented.size; j++) {
+ * if (j != i &&
+ * interfaceDef.implementedBy(env, (ClassDefinition)
+ * remotesImplemented.elementAt(j).
+ * getClassDeclaration()))
+ * {
+ * isOtherwiseImplemented = true;
+ * break;
+ * }
+ * }
+ * if (isOtherwiseImplemented) {
+ * remotesImplemented.removeElementAt(i);
+ * } else {
+ * ++i;
+ * }
+ * }
+ */
+
+ /*
+ * Now we collect the methods from all of the remote interfaces
+ * into a hashtable.
+ */
+ Hashtable methods = new Hashtable();
+ boolean errors = false;
+ for (Enumeration enumeration = remotesImplemented.elements();
+ enumeration.hasMoreElements();)
+ {
+ ClassDefinition interfaceDef =
+ (ClassDefinition) enumeration.nextElement();
+ if (!collectRemoteMethods(interfaceDef, methods))
+ errors = true;
+ }
+ if (errors)
+ return false;
+
+ /*
+ * Convert vector of remote interfaces to an array
+ * (order is not important for this array).
+ */
+ remoteInterfaces = new ClassDefinition[remotesImplemented.size()];
+ remotesImplemented.copyInto(remoteInterfaces);
+
+ /*
+ * Sort table of remote methods into an array. The elements are
+ * sorted in ascending order of the string of the method's name
+ * and type signature, so that each elements index is equal to
+ * its operation number of the JDK 1.1 version of the stub/skeleton
+ * protocol.
+ */
+ String[] orderedKeys = new String[methods.size()];
+ int count = 0;
+ for (Enumeration enumeration = methods.elements();
+ enumeration.hasMoreElements();)
+ {
+ Method m = (Method) enumeration.nextElement();
+ String key = m.getNameAndDescriptor();
+ int i;
+ for (i = count; i > 0; --i) {
+ if (key.compareTo(orderedKeys[i - 1]) >= 0) {
+ break;
+ }
+ orderedKeys[i] = orderedKeys[i - 1];
+ }
+ orderedKeys[i] = key;
+ ++count;
+ }
+ remoteMethods = new Method[methods.size()];
+ for (int i = 0; i < remoteMethods.length; i++) {
+ remoteMethods[i] = (Method) methods.get(orderedKeys[i]);
+ /***** <DEBUG> */
+ if (env.verbose()) {
+ System.out.print("[found remote method <" + i + ">: " +
+ remoteMethods[i].getOperationString());
+ ClassDeclaration[] exceptions =
+ remoteMethods[i].getExceptions();
+ if (exceptions.length > 0)
+ System.out.print(" throws ");
+ for (int j = 0; j < exceptions.length; j++) {
+ if (j > 0)
+ System.out.print(", ");
+ System.out.print(exceptions[j].getName());
+ }
+ System.out.println("]");
+ }
+ /***** </DEBUG> */
+ }
+
+ /**
+ * Finally, pre-compute the interface hash to be used by
+ * stubs/skeletons for this remote class.
+ */
+ interfaceHash = computeInterfaceHash();
+
+ return true;
+ }
+
+ /**
+ * Collect and validate all methods from given interface and all of
+ * its superinterfaces as remote methods. Remote methods are added
+ * to the supplied hashtable. Returns true if successful,
+ * or false if an error occurred.
+ */
+ private boolean collectRemoteMethods(ClassDefinition interfaceDef,
+ Hashtable table)
+ {
+ if (!interfaceDef.isInterface()) {
+ throw new Error(
+ "expected interface, not class: " + interfaceDef.getName());
+ }
+
+ /*
+ * rmic used to enforce that a remote interface could not extend
+ * a non-remote interface, i.e. an interface that did not itself
+ * extend from Remote. The current version of rmic does not have
+ * this restriction, so the following code is now commented out.
+ *
+ * Verify that this interface extends Remote, since all interfaces
+ * extended by a remote interface must implement Remote.
+ *
+ * try {
+ * if (!defRemote.implementedBy(env,
+ * interfaceDef.getClassDeclaration()))
+ * {
+ * env.error(0, "rmic.can.mix.remote.nonremote",
+ * interfaceDef.getName());
+ * return false;
+ * }
+ * } catch (ClassNotFound e) {
+ * env.error(0, "class.not.found", e.name,
+ * interfaceDef.getName());
+ * return false;
+ * }
+ */
+
+ boolean errors = false;
+
+ /*
+ * Search interface's members for methods.
+ */
+ nextMember:
+ for (MemberDefinition member = interfaceDef.getFirstMember();
+ member != null;
+ member = member.getNextMember())
+ {
+ if (member.isMethod() &&
+ !member.isConstructor() && !member.isInitializer())
+ {
+ /*
+ * Verify that each method throws RemoteException.
+ */
+ ClassDeclaration[] exceptions = member.getExceptions(env);
+ boolean hasRemoteException = false;
+ for (int i = 0; i < exceptions.length; i++) {
+ /*
+ * rmic used to enforce that a remote method had to
+ * explicitly list RemoteException in its "throws"
+ * clause; i.e., just throwing Exception was not
+ * acceptable. The current version of rmic does not
+ * have this restriction, so the following code is
+ * now commented out. Instead, the method is
+ * considered valid if RemoteException is a subclass
+ * of any of the methods declared exceptions.
+ *
+ * if (exceptions[i].getName().equals(
+ * idRemoteException))
+ * {
+ * hasRemoteException = true;
+ * break;
+ * }
+ */
+ try {
+ if (defRemoteException.subClassOf(
+ env, exceptions[i]))
+ {
+ hasRemoteException = true;
+ break;
+ }
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name,
+ interfaceDef.getName());
+ continue nextMember;
+ }
+ }
+ /*
+ * If this method did not throw RemoteException as required,
+ * generate the error but continue, so that multiple such
+ * errors can be reported.
+ */
+ if (!hasRemoteException) {
+ env.error(0, "rmic.must.throw.remoteexception",
+ interfaceDef.getName(), member.toString());
+ errors = true;
+ continue nextMember;
+ }
+
+ /*
+ * Verify that the implementation of this method throws only
+ * java.lang.Exception or its subclasses (fix bugid 4092486).
+ * JRMP does not support remote methods throwing
+ * java.lang.Throwable or other subclasses.
+ */
+ try {
+ MemberDefinition implMethod = implClassDef.findMethod(
+ env, member.getName(), member.getType());
+ if (implMethod != null) { // should not be null
+ exceptions = implMethod.getExceptions(env);
+ for (int i = 0; i < exceptions.length; i++) {
+ if (!defException.superClassOf(
+ env, exceptions[i]))
+ {
+ env.error(0, "rmic.must.only.throw.exception",
+ implMethod.toString(),
+ exceptions[i].getName());
+ errors = true;
+ continue nextMember;
+ }
+ }
+ }
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name,
+ implClassDef.getName());
+ continue nextMember;
+ }
+
+ /*
+ * Create RemoteClass.Method object to represent this method
+ * found in a remote interface.
+ */
+ Method newMethod = new Method(member);
+ /*
+ * Store remote method's representation in the table of
+ * remote methods found, keyed by its name and parameter
+ * signature.
+ *
+ * If the table already contains an entry with the same
+ * method name and parameter signature, then we must
+ * replace the old entry with a Method object that
+ * represents a legal combination of the old and the new
+ * methods; specifically, the combined method must have
+ * a throws list that contains (only) all of the checked
+ * exceptions that can be thrown by both the old or
+ * the new method (see bugid 4070653).
+ */
+ String key = newMethod.getNameAndDescriptor();
+ Method oldMethod = (Method) table.get(key);
+ if (oldMethod != null) {
+ newMethod = newMethod.mergeWith(oldMethod);
+ if (newMethod == null) {
+ errors = true;
+ continue nextMember;
+ }
+ }
+ table.put(key, newMethod);
+ }
+ }
+
+ /*
+ * Recursively collect methods for all superinterfaces.
+ */
+ try {
+ ClassDeclaration[] superDefs = interfaceDef.getInterfaces();
+ for (int i = 0; i < superDefs.length; i++) {
+ ClassDefinition superDef =
+ superDefs[i].getClassDefinition(env);
+ if (!collectRemoteMethods(superDef, table))
+ errors = true;
+ }
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name, interfaceDef.getName());
+ return false;
+ }
+
+ return !errors;
+ }
+
+ /**
+ * Compute the "interface hash" of the stub/skeleton pair for this
+ * remote implementation class. This is the 64-bit value used to
+ * enforce compatibility between a stub and a skeleton using the
+ * JDK 1.1 version of the stub/skeleton protocol.
+ *
+ * It is calculated using the first 64 bits of a SHA digest. The
+ * digest is from a stream consisting of the following data:
+ * (int) stub version number, always 1
+ * for each remote method, in order of operation number:
+ * (UTF) method name
+ * (UTF) method type signature
+ * for each declared exception, in alphabetical name order:
+ * (UTF) name of exception class
+ *
+ */
+ private long computeInterfaceHash() {
+ long hash = 0;
+ ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ DataOutputStream out = new DataOutputStream(
+ new DigestOutputStream(sink, md));
+
+ out.writeInt(INTERFACE_HASH_STUB_VERSION);
+ for (int i = 0; i < remoteMethods.length; i++) {
+ MemberDefinition m = remoteMethods[i].getMemberDefinition();
+ Identifier name = m.getName();
+ Type type = m.getType();
+
+ out.writeUTF(name.toString());
+ // type signatures already use mangled class names
+ out.writeUTF(type.getTypeSignature());
+
+ ClassDeclaration exceptions[] = m.getExceptions(env);
+ sortClassDeclarations(exceptions);
+ for (int j = 0; j < exceptions.length; j++) {
+ out.writeUTF(Names.mangleClass(
+ exceptions[j].getName()).toString());
+ }
+ }
+ out.flush();
+
+ // use only the first 64 bits of the digest for the hash
+ byte hashArray[] = md.digest();
+ for (int i = 0; i < Math.min(8, hashArray.length); i++) {
+ hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
+ }
+ } catch (IOException e) {
+ throw new Error(
+ "unexpected exception computing intetrface hash: " + e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error(
+ "unexpected exception computing intetrface hash: " + e);
+ }
+
+ return hash;
+ }
+
+ /**
+ * Sort array of class declarations alphabetically by their mangled
+ * fully-qualfied class name. This is used to feed a method's exceptions
+ * in a canonical order into the digest stream for the interface hash
+ * computation.
+ */
+ private void sortClassDeclarations(ClassDeclaration[] decl) {
+ for (int i = 1; i < decl.length; i++) {
+ ClassDeclaration curr = decl[i];
+ String name = Names.mangleClass(curr.getName()).toString();
+ int j;
+ for (j = i; j > 0; j--) {
+ if (name.compareTo(
+ Names.mangleClass(decl[j - 1].getName()).toString()) >= 0)
+ {
+ break;
+ }
+ decl[j] = decl[j - 1];
+ }
+ decl[j] = curr;
+ }
+ }
+
+
+ /**
+ * A RemoteClass.Method object encapsulates RMI-specific information
+ * about a particular remote method in the remote implementation class
+ * represented by the outer instance.
+ */
+ public class Method implements Cloneable {
+
+ /**
+ * Return the definition of the actual class member corresponing
+ * to this method of a remote interface.
+ *
+ * REMIND: Can this method be removed?
+ */
+ public MemberDefinition getMemberDefinition() {
+ return memberDef;
+ }
+
+ /**
+ * Return the name of this method.
+ */
+ public Identifier getName() {
+ return memberDef.getName();
+ }
+
+ /**
+ * Return the type of this method.
+ */
+ public Type getType() {
+ return memberDef.getType();
+ }
+
+ /**
+ * Return an array of the exception classes declared to be
+ * thrown by this remote method.
+ *
+ * For methods with the same name and type signature inherited
+ * from multiple remote interfaces, the array will contain
+ * the set of exceptions declared in all of the interfaces'
+ * methods that can be legally thrown in each of them.
+ */
+ public ClassDeclaration[] getExceptions() {
+ return (ClassDeclaration[]) exceptions.clone();
+ }
+
+ /**
+ * Return the "method hash" used to identify this remote method
+ * in the JDK 1.2 version of the stub protocol.
+ */
+ public long getMethodHash() {
+ return methodHash;
+ }
+
+ /**
+ * Return the string representation of this method.
+ */
+ public String toString() {
+ return memberDef.toString();
+ }
+
+ /**
+ * Return the string representation of this method appropriate
+ * for the construction of a java.rmi.server.Operation object.
+ */
+ public String getOperationString() {
+ return memberDef.toString();
+ }
+
+ /**
+ * Return a string consisting of this method's name followed by
+ * its method descriptor, using the Java VM's notation for
+ * method descriptors (see section 4.3.3 of The Java Virtual
+ * Machine Specification).
+ */
+ public String getNameAndDescriptor() {
+ return memberDef.getName().toString() +
+ memberDef.getType().getTypeSignature();
+ }
+
+ /**
+ * Member definition for this method, from one of the remote
+ * interfaces that this method was found in.
+ *
+ * Note that this member definition may be only one of several
+ * member defintions that correspond to this remote method object,
+ * if several of this class's remote interfaces contain methods
+ * with the same name and type signature. Therefore, this member
+ * definition may declare more exceptions thrown that this remote
+ * method does.
+ */
+ private MemberDefinition memberDef;
+
+ /** stub "method hash" to identify this method */
+ private long methodHash;
+
+ /**
+ * Exceptions declared to be thrown by this remote method.
+ *
+ * This list can include superfluous entries, such as
+ * unchecked exceptions and subclasses of other entries.
+ */
+ private ClassDeclaration[] exceptions;
+
+ /**
+ * Create a new Method object corresponding to the given
+ * method definition.
+ */
+ /*
+ * Temporarily comment out the private modifier until
+ * the VM allows outer class to access inner class's
+ * private constructor
+ */
+ /* private */ Method(MemberDefinition memberDef) {
+ this.memberDef = memberDef;
+ exceptions = memberDef.getExceptions(env);
+ methodHash = computeMethodHash();
+ }
+
+ /**
+ * Cloning is supported by returning a shallow copy of this object.
+ */
+ protected Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new Error("clone failed");
+ }
+ }
+
+ /**
+ * Return a new Method object that is a legal combination of
+ * this method object and another one.
+ *
+ * This requires determining the exceptions declared by the
+ * combined method, which must be (only) all of the exceptions
+ * declared in both old Methods that may thrown in either of
+ * them.
+ */
+ private Method mergeWith(Method other) {
+ if (!getName().equals(other.getName()) ||
+ !getType().equals(other.getType()))
+ {
+ throw new Error("attempt to merge method \"" +
+ other.getNameAndDescriptor() + "\" with \"" +
+ getNameAndDescriptor());
+ }
+
+ Vector legalExceptions = new Vector();
+ try {
+ collectCompatibleExceptions(
+ other.exceptions, exceptions, legalExceptions);
+ collectCompatibleExceptions(
+ exceptions, other.exceptions, legalExceptions);
+ } catch (ClassNotFound e) {
+ env.error(0, "class.not.found", e.name,
+ getClassDefinition().getName());
+ return null;
+ }
+
+ Method merged = (Method) clone();
+ merged.exceptions = new ClassDeclaration[legalExceptions.size()];
+ legalExceptions.copyInto(merged.exceptions);
+
+ return merged;
+ }
+
+ /**
+ * Add to the supplied list all exceptions in the "from" array
+ * that are subclasses of an exception in the "with" array.
+ */
+ private void collectCompatibleExceptions(ClassDeclaration[] from,
+ ClassDeclaration[] with,
+ Vector list)
+ throws ClassNotFound
+ {
+ for (int i = 0; i < from.length; i++) {
+ ClassDefinition exceptionDef = from[i].getClassDefinition(env);
+ if (!list.contains(from[i])) {
+ for (int j = 0; j < with.length; j++) {
+ if (exceptionDef.subClassOf(env, with[j])) {
+ list.addElement(from[i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Compute the "method hash" of this remote method. The method
+ * hash is a long containing the first 64 bits of the SHA digest
+ * from the UTF encoded string of the method name and descriptor.
+ *
+ * REMIND: Should this method share implementation code with
+ * the outer class's computeInterfaceHash() method?
+ */
+ private long computeMethodHash() {
+ long hash = 0;
+ ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ DataOutputStream out = new DataOutputStream(
+ new DigestOutputStream(sink, md));
+
+ String methodString = getNameAndDescriptor();
+ /***** <DEBUG> */
+ if (env.verbose()) {
+ System.out.println("[string used for method hash: \"" +
+ methodString + "\"]");
+ }
+ /***** </DEBUG> */
+ out.writeUTF(methodString);
+
+ // use only the first 64 bits of the digest for the hash
+ out.flush();
+ byte hashArray[] = md.digest();
+ for (int i = 0; i < Math.min(8, hashArray.length); i++) {
+ hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
+ }
+ } catch (IOException e) {
+ throw new Error(
+ "unexpected exception computing intetrface hash: " + e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error(
+ "unexpected exception computing intetrface hash: " + e);
+ }
+
+ return hash;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/Util.java b/src/share/classes/sun/rmi/rmic/Util.java
new file mode 100644
index 000000000..06a2ae343
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/Util.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * Licensed Materials - Property of IBM
+ * RMI-IIOP v1.0
+ * Copyright IBM Corp. 1998 1999 All Rights Reserved
+ *
+ */
+
+package sun.rmi.rmic;
+
+import java.io.File;
+import sun.tools.java.Identifier;
+
+/**
+ * Util provides static utility methods used by other rmic classes.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Bryan Atsatt
+ */
+
+public class Util implements sun.rmi.rmic.Constants {
+
+ /**
+ * Return the directory that should be used for output for a given
+ * class.
+ * @param theClass The fully qualified name of the class.
+ * @param rootDir The directory to use as the root of the
+ * package heirarchy. May be null, in which case the current
+ * working directory is used as the root.
+ */
+ public static File getOutputDirectoryFor(Identifier theClass,
+ File rootDir,
+ BatchEnvironment env) {
+
+ File outputDir = null;
+ String className = theClass.getFlatName().toString().replace('.', SIGC_INNERCLASS);
+ String qualifiedClassName = className;
+ String packagePath = null;
+ String packageName = theClass.getQualifier().toString();
+
+ if (packageName.length() > 0) {
+ qualifiedClassName = packageName + "." + className;
+ packagePath = packageName.replace('.', File.separatorChar);
+ }
+
+ // Do we have a root directory?
+
+ if (rootDir != null) {
+
+ // Yes, do we have a package name?
+
+ if (packagePath != null) {
+
+ // Yes, so use it as the root. Open the directory...
+
+ outputDir = new File(rootDir, packagePath);
+
+ // Make sure the directory exists...
+
+ ensureDirectory(outputDir,env);
+
+ } else {
+
+ // Default package, so use root as output dir...
+
+ outputDir = rootDir;
+ }
+ } else {
+
+ // No root directory. Get the current working directory...
+
+ String workingDirPath = System.getProperty("user.dir");
+ File workingDir = new File(workingDirPath);
+
+ // Do we have a package name?
+
+ if (packagePath == null) {
+
+ // No, so use working directory...
+
+ outputDir = workingDir;
+
+ } else {
+
+ // Yes, so use working directory as the root...
+
+ outputDir = new File(workingDir, packagePath);
+
+ // Make sure the directory exists...
+
+ ensureDirectory(outputDir,env);
+ }
+ }
+
+ // Finally, return the directory...
+
+ return outputDir;
+ }
+
+ private static void ensureDirectory (File dir, BatchEnvironment env) {
+ if (!dir.exists()) {
+ dir.mkdirs();
+ if (!dir.exists()) {
+ env.error(0,"rmic.cannot.create.dir",dir.getAbsolutePath());
+ throw new InternalError();
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java b/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java
new file mode 100644
index 000000000..584e21ba0
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/BatchEnvironment.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.RootDoc;
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static sun.rmi.rmic.newrmic.Constants.*;
+
+/**
+ * The environment for an rmic compilation batch.
+ *
+ * A BatchEnvironment contains a RootDoc, which is the entry point
+ * into the doclet environment for the associated rmic compilation
+ * batch. A BatchEnvironment collects the source files generated
+ * during the batch's execution, for eventual source code compilation
+ * and, possibly, deletion. Errors that occur during generation
+ * activity should be reported through the BatchEnvironment's "error"
+ * method.
+ *
+ * A protocol-specific generator class may require the use of a
+ * particular BatchEnvironment subclass for enhanced environment
+ * functionality. A BatchEnvironment subclass must declare a
+ * public constructor with one parameter of type RootDoc.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public class BatchEnvironment {
+
+ private final RootDoc rootDoc;
+
+ /** cached ClassDoc for certain types used by rmic */
+ private final ClassDoc docRemote;
+ private final ClassDoc docException;
+ private final ClassDoc docRemoteException;
+ private final ClassDoc docRuntimeException;
+
+ private boolean verbose = false;
+ private final List<File> generatedFiles = new ArrayList<File>();
+
+ /**
+ * Creates a new BatchEnvironment with the specified RootDoc.
+ **/
+ public BatchEnvironment(RootDoc rootDoc) {
+ this.rootDoc = rootDoc;
+
+ /*
+ * Initialize cached ClassDoc for types used by rmic. Note
+ * that any of these could be null if the boot class path is
+ * incorrect, which could cause a NullPointerException later.
+ */
+ docRemote = rootDoc().classNamed(REMOTE);
+ docException = rootDoc().classNamed(EXCEPTION);
+ docRemoteException = rootDoc().classNamed(REMOTE_EXCEPTION);
+ docRuntimeException = rootDoc().classNamed(RUNTIME_EXCEPTION);
+ }
+
+ /**
+ * Returns the RootDoc for this environment.
+ **/
+ public RootDoc rootDoc() {
+ return rootDoc;
+ }
+
+ public ClassDoc docRemote() { return docRemote; }
+ public ClassDoc docException() { return docException; }
+ public ClassDoc docRemoteException() { return docRemoteException; }
+ public ClassDoc docRuntimeException() { return docRuntimeException; }
+
+ /**
+ * Sets this environment's verbosity status.
+ **/
+ public void setVerbose(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ /**
+ * Returns this environment's verbosity status.
+ **/
+ public boolean verbose() {
+ return verbose;
+ }
+
+ /**
+ * Adds the specified file to the list of source files generated
+ * during this batch.
+ **/
+ public void addGeneratedFile(File file) {
+ generatedFiles.add(file);
+ }
+
+ /**
+ * Returns the list of files generated during this batch.
+ **/
+ public List<File> generatedFiles() {
+ return Collections.unmodifiableList(generatedFiles);
+ }
+
+ /**
+ * Outputs the specified (non-error) message.
+ **/
+ public void output(String msg) {
+ rootDoc.printNotice(msg);
+ }
+
+ /**
+ * Reports an error using the specified resource key and text
+ * formatting arguments.
+ **/
+ public void error(String key, String... args) {
+ rootDoc.printError(Resources.getText(key, args));
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Constants.java b/src/share/classes/sun/rmi/rmic/newrmic/Constants.java
new file mode 100644
index 000000000..30eb923fc
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/Constants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+/**
+ * Constants potentially useful to all rmic generators.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public final class Constants {
+
+ private Constants() { throw new AssertionError(); }
+
+ /*
+ * fully-qualified names of types used by rmic
+ */
+ public static final String REMOTE = "java.rmi.Remote";
+ public static final String EXCEPTION = "java.lang.Exception";
+ public static final String REMOTE_EXCEPTION = "java.rmi.RemoteException";
+ public static final String RUNTIME_EXCEPTION = "java.lang.RuntimeException";
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Generator.java b/src/share/classes/sun/rmi/rmic/newrmic/Generator.java
new file mode 100644
index 000000000..ee457597a
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/Generator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+import com.sun.javadoc.ClassDoc;
+import java.io.File;
+import java.util.Set;
+
+/**
+ * The interface to rmic back end implementations. Classes that
+ * implement this interface correspond to the various generation modes
+ * of rmic (JRMP, IIOP, IDL, etc.).
+ *
+ * A Generator instance corresponds to a particular rmic compilation
+ * batch, and its instance state represents the generator-specific
+ * command line options for that batch. Main will instantiate a
+ * generator class when the command line arguments indicate selection
+ * of the corresponding generation mode. Main will then invoke the
+ * "parseArgs" method to allow the generator to process any
+ * generator-specific command line options and set its instance state
+ * accordingly.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public interface Generator {
+
+ /**
+ * Processes the command line options specific to this generator.
+ * Processed options are set to null in the specified array.
+ * Returns true if successful or false if an error occurs. Errors
+ * are output to the specific Main instance.
+ **/
+ public boolean parseArgs(String[] args, Main main);
+
+ /**
+ * Returns the most specific environment class required by this
+ * generator.
+ **/
+ public Class<? extends BatchEnvironment> envClass();
+
+ /**
+ * Returns the names of the classes that must be available through
+ * the doclet API in order for this generator to function.
+ **/
+ public Set<String> bootstrapClassNames();
+
+ /**
+ * Generates the protocol-specific rmic output files for the
+ * specified remote class. This method is invoked once for each
+ * class or interface specified on the command line for the rmic
+ * compilation batch associated with this instance.
+ *
+ * Any generated source files (to be compiled with javac) are
+ * passed to the addGeneratedFile method of the specified
+ * BatchEnvironment.
+ **/
+ public void generate(BatchEnvironment env,
+ ClassDoc inputClass,
+ File destDir);
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java b/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java
new file mode 100644
index 000000000..746551dd0
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/IndentingWriter.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+import java.io.Writer;
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * A BufferedWriter that supports automatic indentation of lines of
+ * text written to the underlying Writer.
+ *
+ * Methods are provided for compact/convenient indenting in and out,
+ * writing text, and writing lines of text in various combinations.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public class IndentingWriter extends BufferedWriter {
+
+ /** number of spaces to change indent when indenting in or out */
+ private final int indentStep;
+
+ /** number of spaces to convert into tabs (use MAX_VALUE to disable) */
+ private final int tabSize;
+
+ /** true if the next character written is the first on a line */
+ private boolean beginningOfLine = true;
+
+ /** current number of spaces to prepend to lines */
+ private int currentIndent = 0;
+
+ /**
+ * Creates a new IndentingWriter that writes indented text to the
+ * given Writer. Use the default indent step of four spaces.
+ **/
+ public IndentingWriter(Writer out) {
+ this(out, 4);
+ }
+
+ /**
+ * Creates a new IndentingWriter that writes indented text to the
+ * given Writer and uses the supplied indent step.
+ **/
+ public IndentingWriter(Writer out, int indentStep) {
+ this(out, indentStep, 8);
+ }
+
+ /**
+ * Creates a new IndentingWriter that writes indented text to the
+ * given Writer and uses the supplied indent step and tab size.
+ **/
+ public IndentingWriter(Writer out, int indentStep, int tabSize) {
+ super(out);
+ if (indentStep < 0) {
+ throw new IllegalArgumentException("negative indent step");
+ }
+ if (tabSize < 0) {
+ throw new IllegalArgumentException("negative tab size");
+ }
+ this.indentStep = indentStep;
+ this.tabSize = tabSize;
+ }
+
+ /**
+ * Writes a single character.
+ **/
+ public void write(int c) throws IOException {
+ checkWrite();
+ super.write(c);
+ }
+
+ /**
+ * Writes a portion of an array of characters.
+ **/
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ if (len > 0) {
+ checkWrite();
+ }
+ super.write(cbuf, off, len);
+ }
+
+ /**
+ * Writes a portion of a String.
+ **/
+ public void write(String s, int off, int len) throws IOException {
+ if (len > 0) {
+ checkWrite();
+ }
+ super.write(s, off, len);
+ }
+
+ /**
+ * Writes a line separator. The next character written will be
+ * preceded by an indent.
+ **/
+ public void newLine() throws IOException {
+ super.newLine();
+ beginningOfLine = true;
+ }
+
+ /**
+ * Checks if an indent needs to be written before writing the next
+ * character.
+ *
+ * The indent generation is optimized (and made consistent with
+ * certain coding conventions) by condensing groups of eight
+ * spaces into tab characters.
+ **/
+ protected void checkWrite() throws IOException {
+ if (beginningOfLine) {
+ beginningOfLine = false;
+ int i = currentIndent;
+ while (i >= tabSize) {
+ super.write('\t');
+ i -= tabSize;
+ }
+ while (i > 0) {
+ super.write(' ');
+ i--;
+ }
+ }
+ }
+
+ /**
+ * Increases the current indent by the indent step.
+ **/
+ protected void indentIn() {
+ currentIndent += indentStep;
+ }
+
+ /**
+ * Decreases the current indent by the indent step.
+ **/
+ protected void indentOut() {
+ currentIndent -= indentStep;
+ if (currentIndent < 0)
+ currentIndent = 0;
+ }
+
+ /**
+ * Indents in.
+ **/
+ public void pI() {
+ indentIn();
+ }
+
+ /**
+ * Indents out.
+ **/
+ public void pO() {
+ indentOut();
+ }
+
+ /**
+ * Writes string.
+ **/
+ public void p(String s) throws IOException {
+ write(s);
+ }
+
+ /**
+ * Ends current line.
+ **/
+ public void pln() throws IOException {
+ newLine();
+ }
+
+ /**
+ * Writes string; ends current line.
+ **/
+ public void pln(String s) throws IOException {
+ p(s);
+ pln();
+ }
+
+ /**
+ * Writes string; ends current line; indents in.
+ **/
+ public void plnI(String s) throws IOException {
+ p(s);
+ pln();
+ pI();
+ }
+
+ /**
+ * Indents out; writes string.
+ **/
+ public void pO(String s) throws IOException {
+ pO();
+ p(s);
+ }
+
+ /**
+ * Indents out; writes string; ends current line.
+ **/
+ public void pOln(String s) throws IOException {
+ pO(s);
+ pln();
+ }
+
+ /**
+ * Indents out; writes string; ends current line; indents in.
+ *
+ * This method is useful for generating lines of code that both
+ * end and begin nested blocks, like "} else {".
+ **/
+ public void pOlnI(String s) throws IOException {
+ pO(s);
+ pln();
+ pI();
+ }
+
+ /**
+ * Writes object.
+ **/
+ public void p(Object o) throws IOException {
+ write(o.toString());
+ }
+
+ /**
+ * Writes object; ends current line.
+ **/
+ public void pln(Object o) throws IOException {
+ p(o.toString());
+ pln();
+ }
+
+ /**
+ * Writes object; ends current line; indents in.
+ **/
+ public void plnI(Object o) throws IOException {
+ p(o.toString());
+ pln();
+ pI();
+ }
+
+ /**
+ * Indents out; writes object.
+ **/
+ public void pO(Object o) throws IOException {
+ pO();
+ p(o.toString());
+ }
+
+ /**
+ * Indents out; writes object; ends current line.
+ **/
+ public void pOln(Object o) throws IOException {
+ pO(o.toString());
+ pln();
+ }
+
+ /**
+ * Indents out; writes object; ends current line; indents in.
+ *
+ * This method is useful for generating lines of code that both
+ * end and begin nested blocks, like "} else {".
+ **/
+ public void pOlnI(Object o) throws IOException {
+ pO(o.toString());
+ pln();
+ pI();
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Main.java b/src/share/classes/sun/rmi/rmic/newrmic/Main.java
new file mode 100644
index 000000000..e7dc7c899
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/Main.java
@@ -0,0 +1,689 @@
+/*
+ * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.RootDoc;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import sun.rmi.rmic.newrmic.jrmp.JrmpGenerator;
+import sun.tools.util.CommandLine;
+
+/**
+ * The rmic front end. This class contains the "main" method for rmic
+ * command line invocation.
+ *
+ * A Main instance contains the stream to output error messages and
+ * other diagnostics to.
+ *
+ * An rmic compilation batch (for example, one rmic command line
+ * invocation) is executed by invoking the "compile" method of a Main
+ * instance.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * NOTE: If and when there is a J2SE API for invoking SDK tools, this
+ * class should be updated to support that API.
+ *
+ * NOTE: This class is the front end for a "new" rmic implementation,
+ * which uses javadoc and the doclet API for reading class files and
+ * javac for compiling generated source files. This implementation is
+ * incomplete: it lacks any CORBA-based back end implementations, and
+ * thus the command line options "-idl", "-iiop", and their related
+ * options are not yet supported. The front end for the "old",
+ * oldjavac-based rmic implementation is sun.rmi.rmic.Main.
+ *
+ * @author Peter Jones
+ **/
+public class Main {
+
+ /*
+ * Implementation note:
+ *
+ * In order to use the doclet API to read class files, much of
+ * this implementation of rmic executes as a doclet within an
+ * invocation of javadoc. This class is used as the doclet class
+ * for such javadoc invocations, via its static "start" and
+ * "optionLength" methods. There is one javadoc invocation per
+ * rmic compilation batch.
+ *
+ * The only guaranteed way to pass data to a doclet through a
+ * javadoc invocation is through doclet-specific options on the
+ * javadoc "command line". Rather than passing numerous pieces of
+ * individual data in string form as javadoc options, we use a
+ * single doclet-specific option ("-batchID") to pass a numeric
+ * identifier that uniquely identifies the rmic compilation batch
+ * that the javadoc invocation is for, and that identifier can
+ * then be used as a key in a global table to retrieve an object
+ * containing all of batch-specific data (rmic command line
+ * arguments, etc.).
+ */
+
+ /** guards "batchCount" */
+ private static final Object batchCountLock = new Object();
+
+ /** number of batches run; used to generated batch IDs */
+ private static long batchCount = 0;
+
+ /** maps batch ID to batch data */
+ private static final Map<Long,Batch> batchTable =
+ Collections.synchronizedMap(new HashMap<Long,Batch>());
+
+ /** stream to output error messages and other diagnostics to */
+ private final PrintStream out;
+
+ /** name of this program, to use in error messages */
+ private final String program;
+
+ /**
+ * Command line entry point.
+ **/
+ public static void main(String[] args) {
+ Main rmic = new Main(System.err, "rmic");
+ System.exit(rmic.compile(args) ? 0 : 1);
+ }
+
+ /**
+ * Creates a Main instance that writes output to the specified
+ * stream. The specified program name is used in error messages.
+ **/
+ public Main(OutputStream out, String program) {
+ this.out = out instanceof PrintStream ?
+ (PrintStream) out : new PrintStream(out);
+ this.program = program;
+ }
+
+ /**
+ * Compiles a batch of input classes, as given by the specified
+ * command line arguments. Protocol-specific generators are
+ * determined by the choice options on the command line. Returns
+ * true if successful, or false if an error occurred.
+ *
+ * NOTE: This method is retained for transitional consistency with
+ * previous implementations.
+ **/
+ public boolean compile(String[] args) {
+ long startTime = System.currentTimeMillis();
+
+ long batchID;
+ synchronized (batchCountLock) {
+ batchID = batchCount++; // assign batch ID
+ }
+
+ // process command line
+ Batch batch = parseArgs(args);
+ if (batch == null) {
+ return false; // terminate if error occurred
+ }
+
+ /*
+ * With the batch data retrievable in the global table, run
+ * javadoc to continue the rest of the batch's compliation as
+ * a doclet.
+ */
+ boolean status;
+ try {
+ batchTable.put(batchID, batch);
+ status = invokeJavadoc(batch, batchID);
+ } finally {
+ batchTable.remove(batchID);
+ }
+
+ if (batch.verbose) {
+ long deltaTime = System.currentTimeMillis() - startTime;
+ output(Resources.getText("rmic.done_in",
+ Long.toString(deltaTime)));
+ }
+
+ return status;
+ }
+
+ /**
+ * Prints the specified string to the output stream of this Main
+ * instance.
+ **/
+ public void output(String msg) {
+ out.println(msg);
+ }
+
+ /**
+ * Prints an error message to the output stream of this Main
+ * instance. The first argument is used as a key in rmic's
+ * resource bundle, and the rest of the arguments are used as
+ * arguments in the formatting of the resource string.
+ **/
+ public void error(String msg, String... args) {
+ output(Resources.getText(msg, args));
+ }
+
+ /**
+ * Prints rmic's usage message to the output stream of this Main
+ * instance.
+ *
+ * This method is public so that it can be used by the "parseArgs"
+ * methods of Generator implementations.
+ **/
+ public void usage() {
+ error("rmic.usage", program);
+ }
+
+ /**
+ * Processes rmic command line arguments. Returns a Batch object
+ * representing the command line arguments if successful, or null
+ * if an error occurred. Processed elements of the args array are
+ * set to null.
+ **/
+ private Batch parseArgs(String[] args) {
+ Batch batch = new Batch();
+
+ /*
+ * Pre-process command line for @file arguments.
+ */
+ try {
+ args = CommandLine.parse(args);
+ } catch (FileNotFoundException e) {
+ error("rmic.cant.read", e.getMessage());
+ return null;
+ } catch (IOException e) {
+ e.printStackTrace(out);
+ return null;
+ }
+
+ for (int i = 0; i < args.length; i++) {
+
+ if (args[i] == null) {
+ // already processed by a generator
+ continue;
+
+ } else if (args[i].equals("-Xnew")) {
+ // we're already using the "new" implementation
+ args[i] = null;
+
+ } else if (args[i].equals("-show")) {
+ // obselete: fail
+ error("rmic.option.unsupported", args[i]);
+ usage();
+ return null;
+
+ } else if (args[i].equals("-O")) {
+ // obselete: warn but tolerate
+ error("rmic.option.unsupported", args[i]);
+ args[i] = null;
+
+ } else if (args[i].equals("-debug")) {
+ // obselete: warn but tolerate
+ error("rmic.option.unsupported", args[i]);
+ args[i] = null;
+
+ } else if (args[i].equals("-depend")) {
+ // obselete: warn but tolerate
+ // REMIND: should this fail instead?
+ error("rmic.option.unsupported", args[i]);
+ args[i] = null;
+
+ } else if (args[i].equals("-keep") ||
+ args[i].equals("-keepgenerated"))
+ {
+ batch.keepGenerated = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-g")) {
+ batch.debug = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-nowarn")) {
+ batch.noWarn = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-nowrite")) {
+ batch.noWrite = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-verbose")) {
+ batch.verbose = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-Xnocompile")) {
+ batch.noCompile = true;
+ batch.keepGenerated = true;
+ args[i] = null;
+
+ } else if (args[i].equals("-bootclasspath")) {
+ if ((i + 1) >= args.length) {
+ error("rmic.option.requires.argument", args[i]);
+ usage();
+ return null;
+ }
+ if (batch.bootClassPath != null) {
+ error("rmic.option.already.seen", args[i]);
+ usage();
+ return null;
+ }
+ args[i] = null;
+ batch.bootClassPath = args[++i];
+ assert batch.bootClassPath != null;
+ args[i] = null;
+
+ } else if (args[i].equals("-extdirs")) {
+ if ((i + 1) >= args.length) {
+ error("rmic.option.requires.argument", args[i]);
+ usage();
+ return null;
+ }
+ if (batch.extDirs != null) {
+ error("rmic.option.already.seen", args[i]);
+ usage();
+ return null;
+ }
+ args[i] = null;
+ batch.extDirs = args[++i];
+ assert batch.extDirs != null;
+ args[i] = null;
+
+ } else if (args[i].equals("-classpath")) {
+ if ((i + 1) >= args.length) {
+ error("rmic.option.requires.argument", args[i]);
+ usage();
+ return null;
+ }
+ if (batch.classPath != null) {
+ error("rmic.option.already.seen", args[i]);
+ usage();
+ return null;
+ }
+ args[i] = null;
+ batch.classPath = args[++i];
+ assert batch.classPath != null;
+ args[i] = null;
+
+ } else if (args[i].equals("-d")) {
+ if ((i + 1) >= args.length) {
+ error("rmic.option.requires.argument", args[i]);
+ usage();
+ return null;
+ }
+ if (batch.destDir != null) {
+ error("rmic.option.already.seen", args[i]);
+ usage();
+ return null;
+ }
+ args[i] = null;
+ batch.destDir = new File(args[++i]);
+ assert batch.destDir != null;
+ args[i] = null;
+ if (!batch.destDir.exists()) {
+ error("rmic.no.such.directory", batch.destDir.getPath());
+ usage();
+ return null;
+ }
+
+ } else if (args[i].equals("-v1.1") ||
+ args[i].equals("-vcompat") ||
+ args[i].equals("-v1.2"))
+ {
+ Generator gen = new JrmpGenerator();
+ batch.generators.add(gen);
+ // JrmpGenerator only requires base BatchEnvironment class
+ if (!gen.parseArgs(args, this)) {
+ return null;
+ }
+
+ } else if (args[i].equalsIgnoreCase("-iiop")) {
+ error("rmic.option.unimplemented", args[i]);
+ return null;
+
+ // Generator gen = new IiopGenerator();
+ // batch.generators.add(gen);
+ // if (!batch.envClass.isAssignableFrom(gen.envClass())) {
+ // error("rmic.cannot.use.both",
+ // batch.envClass.getName(), gen.envClass().getName());
+ // return null;
+ // }
+ // batch.envClass = gen.envClass();
+ // if (!gen.parseArgs(args, this)) {
+ // return null;
+ // }
+
+ } else if (args[i].equalsIgnoreCase("-idl")) {
+ error("rmic.option.unimplemented", args[i]);
+ return null;
+
+ // see implementation sketch above
+
+ } else if (args[i].equalsIgnoreCase("-xprint")) {
+ error("rmic.option.unimplemented", args[i]);
+ return null;
+
+ // see implementation sketch above
+ }
+ }
+
+ /*
+ * At this point, all that remains non-null in the args
+ * array are input class names or illegal options.
+ */
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] != null) {
+ if (args[i].startsWith("-")) {
+ error("rmic.no.such.option", args[i]);
+ usage();
+ return null;
+ } else {
+ batch.classes.add(args[i]);
+ }
+ }
+ }
+ if (batch.classes.isEmpty()) {
+ usage();
+ return null;
+ }
+
+ /*
+ * If options did not specify at least one protocol-specific
+ * generator, then JRMP is the default.
+ */
+ if (batch.generators.isEmpty()) {
+ batch.generators.add(new JrmpGenerator());
+ }
+ return batch;
+ }
+
+ /**
+ * Doclet class entry point.
+ **/
+ public static boolean start(RootDoc rootDoc) {
+
+ /*
+ * Find batch ID among javadoc options, and retrieve
+ * corresponding batch data from global table.
+ */
+ long batchID = -1;
+ for (String[] option : rootDoc.options()) {
+ if (option[0].equals("-batchID")) {
+ try {
+ batchID = Long.parseLong(option[1]);
+ } catch (NumberFormatException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+ Batch batch = batchTable.get(batchID);
+ assert batch != null;
+
+ /*
+ * Construct batch environment using class agreed upon by
+ * generator implementations.
+ */
+ BatchEnvironment env;
+ try {
+ Constructor<? extends BatchEnvironment> cons =
+ batch.envClass.getConstructor(new Class[] { RootDoc.class });
+ env = cons.newInstance(rootDoc);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ }
+
+ env.setVerbose(batch.verbose);
+
+ /*
+ * Determine the destination directory (the top of the package
+ * hierarchy) for the output of this batch; if no destination
+ * directory was specified on the command line, then the
+ * default is the current working directory.
+ */
+ File destDir = batch.destDir;
+ if (destDir == null) {
+ destDir = new File(System.getProperty("user.dir"));
+ }
+
+ /*
+ * Run each input class through each generator.
+ */
+ for (String inputClassName : batch.classes) {
+ ClassDoc inputClass = rootDoc.classNamed(inputClassName);
+ try {
+ for (Generator gen : batch.generators) {
+ gen.generate(env, inputClass, destDir);
+ }
+ } catch (NullPointerException e) {
+ /*
+ * We assume that this means that some class that was
+ * needed (perhaps even a bootstrap class) was not
+ * found, and that javadoc has already reported this
+ * as an error. There is nothing for us to do here
+ * but try to continue with the next input class.
+ *
+ * REMIND: More explicit error checking throughout
+ * would be preferable, however.
+ */
+ }
+ }
+
+ /*
+ * Compile any generated source files, if configured to do so.
+ */
+ boolean status = true;
+ List<File> generatedFiles = env.generatedFiles();
+ if (!batch.noCompile && !batch.noWrite && !generatedFiles.isEmpty()) {
+ status = batch.enclosingMain().invokeJavac(batch, generatedFiles);
+ }
+
+ /*
+ * Delete any generated source files, if configured to do so.
+ */
+ if (!batch.keepGenerated) {
+ for (File file : generatedFiles) {
+ file.delete();
+ }
+ }
+
+ return status;
+ }
+
+ /**
+ * Doclet class method that indicates that this doclet class
+ * recognizes (only) the "-batchID" option on the javadoc command
+ * line, and that the "-batchID" option comprises two arguments on
+ * the javadoc command line.
+ **/
+ public static int optionLength(String option) {
+ if (option.equals("-batchID")) {
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Runs the javadoc tool to invoke this class as a doclet, passing
+ * command line options derived from the specified batch data and
+ * indicating the specified batch ID.
+ *
+ * NOTE: This method currently uses a J2SE-internal API to run
+ * javadoc. If and when there is a J2SE API for invoking SDK
+ * tools, this method should be updated to use that API instead.
+ **/
+ private boolean invokeJavadoc(Batch batch, long batchID) {
+ List<String> javadocArgs = new ArrayList<String>();
+
+ // include all types, regardless of language-level access
+ javadocArgs.add("-private");
+
+ // inputs are class names, not source files
+ javadocArgs.add("-Xclasses");
+
+ // reproduce relevant options from rmic invocation
+ if (batch.verbose) {
+ javadocArgs.add("-verbose");
+ }
+ if (batch.bootClassPath != null) {
+ javadocArgs.add("-bootclasspath");
+ javadocArgs.add(batch.bootClassPath);
+ }
+ if (batch.extDirs != null) {
+ javadocArgs.add("-extdirs");
+ javadocArgs.add(batch.extDirs);
+ }
+ if (batch.classPath != null) {
+ javadocArgs.add("-classpath");
+ javadocArgs.add(batch.classPath);
+ }
+
+ // specify batch ID
+ javadocArgs.add("-batchID");
+ javadocArgs.add(Long.toString(batchID));
+
+ /*
+ * Run javadoc on union of rmic input classes and all
+ * generators' bootstrap classes, so that they will all be
+ * available to the doclet code.
+ */
+ Set<String> classNames = new HashSet<String>();
+ for (Generator gen : batch.generators) {
+ classNames.addAll(gen.bootstrapClassNames());
+ }
+ classNames.addAll(batch.classes);
+ for (String s : classNames) {
+ javadocArgs.add(s);
+ }
+
+ // run javadoc with our program name and output stream
+ int status = com.sun.tools.javadoc.Main.execute(
+ program,
+ new PrintWriter(out, true),
+ new PrintWriter(out, true),
+ new PrintWriter(out, true),
+ this.getClass().getName(), // doclet class is this class
+ javadocArgs.toArray(new String[javadocArgs.size()]));
+ return status == 0;
+ }
+
+ /**
+ * Runs the javac tool to compile the specified source files,
+ * passing command line options derived from the specified batch
+ * data.
+ *
+ * NOTE: This method currently uses a J2SE-internal API to run
+ * javac. If and when there is a J2SE API for invoking SDK tools,
+ * this method should be updated to use that API instead.
+ **/
+ private boolean invokeJavac(Batch batch, List<File> files) {
+ List<String> javacArgs = new ArrayList<String>();
+
+ // rmic never wants to display javac warnings
+ javacArgs.add("-nowarn");
+
+ // reproduce relevant options from rmic invocation
+ if (batch.debug) {
+ javacArgs.add("-g");
+ }
+ if (batch.verbose) {
+ javacArgs.add("-verbose");
+ }
+ if (batch.bootClassPath != null) {
+ javacArgs.add("-bootclasspath");
+ javacArgs.add(batch.bootClassPath);
+ }
+ if (batch.extDirs != null) {
+ javacArgs.add("-extdirs");
+ javacArgs.add(batch.extDirs);
+ }
+ if (batch.classPath != null) {
+ javacArgs.add("-classpath");
+ javacArgs.add(batch.classPath);
+ }
+
+ /*
+ * For now, rmic still always produces class files that have a
+ * class file format version compatible with JDK 1.1.
+ */
+ javacArgs.add("-source");
+ javacArgs.add("1.3");
+ javacArgs.add("-target");
+ javacArgs.add("1.1");
+
+ // add source files to compile
+ for (File file : files) {
+ javacArgs.add(file.getPath());
+ }
+
+ // run javac with our output stream
+ int status = com.sun.tools.javac.Main.compile(
+ javacArgs.toArray(new String[javacArgs.size()]),
+ new PrintWriter(out, true));
+ return status == 0;
+ }
+
+ /**
+ * The data for an rmic compliation batch: the processed command
+ * line arguments.
+ **/
+ private class Batch {
+ boolean keepGenerated = false; // -keep or -keepgenerated
+ boolean debug = false; // -g
+ boolean noWarn = false; // -nowarn
+ boolean noWrite = false; // -nowrite
+ boolean verbose = false; // -verbose
+ boolean noCompile = false; // -Xnocompile
+ String bootClassPath = null; // -bootclasspath
+ String extDirs = null; // -extdirs
+ String classPath = null; // -classpath
+ File destDir = null; // -d
+ List<Generator> generators = new ArrayList<Generator>();
+ Class<? extends BatchEnvironment> envClass = BatchEnvironment.class;
+ List<String> classes = new ArrayList<String>();
+
+ Batch() { }
+
+ /**
+ * Returns the Main instance for this batch.
+ **/
+ Main enclosingMain() {
+ return Main.this;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/Resources.java b/src/share/classes/sun/rmi/rmic/newrmic/Resources.java
new file mode 100644
index 000000000..fa245589c
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/Resources.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Provides resource support for rmic.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public final class Resources {
+
+ private static ResourceBundle resources = null;
+ private static ResourceBundle resourcesExt = null;
+ static {
+ try {
+ resources =
+ ResourceBundle.getBundle("sun.rmi.rmic.resources.rmic");
+ } catch (MissingResourceException e) {
+ // gracefully handle this later
+ }
+ try {
+ resourcesExt =
+ ResourceBundle.getBundle("sun.rmi.rmic.resources.rmicext");
+ } catch (MissingResourceException e) {
+ // OK if this isn't found
+ }
+ }
+
+ private Resources() { throw new AssertionError(); }
+
+ /**
+ * Returns the text of the rmic resource for the specified key
+ * formatted with the specified arguments.
+ **/
+ public static String getText(String key, String... args) {
+ String format = getString(key);
+ if (format == null) {
+ format = "missing resource key: key = \"" + key + "\", " +
+ "arguments = \"{0}\", \"{1}\", \"{2}\"";
+ }
+ return MessageFormat.format(format, args);
+ }
+
+ /**
+ * Returns the rmic resource string for the specified key.
+ **/
+ private static String getString(String key) {
+ if (resourcesExt != null) {
+ try {
+ return resourcesExt.getString(key);
+ } catch (MissingResourceException e) {
+ }
+ }
+ if (resources != null) {
+ try {
+ return resources.getString(key);
+ } catch (MissingResourceException e) {
+ return null;
+ }
+ }
+ return "missing resource bundle: key = \"" + key + "\", " +
+ "arguments = \"{0}\", \"{1}\", \"{2}\"";
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java
new file mode 100644
index 000000000..1275a5e2f
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Constants.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+/**
+ * Constants specific to the JRMP rmic generator.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+final class Constants {
+
+ private Constants() { throw new AssertionError(); }
+
+ /*
+ * fully-qualified names of types used by rmic
+ */
+ static final String REMOTE_OBJECT = "java.rmi.server.RemoteObject";
+ static final String REMOTE_STUB = "java.rmi.server.RemoteStub";
+ static final String REMOTE_REF = "java.rmi.server.RemoteRef";
+ static final String OPERATION = "java.rmi.server.Operation";
+ static final String SKELETON = "java.rmi.server.Skeleton";
+ static final String SKELETON_MISMATCH_EXCEPTION =
+ "java.rmi.server.SkeletonMismatchException";
+ static final String REMOTE_CALL = "java.rmi.server.RemoteCall";
+ static final String MARSHAL_EXCEPTION = "java.rmi.MarshalException";
+ static final String UNMARSHAL_EXCEPTION = "java.rmi.UnmarshalException";
+ static final String UNEXPECTED_EXCEPTION = "java.rmi.UnexpectedException";
+
+ /*
+ * stub protocol versions
+ */
+ enum StubVersion { V1_1, VCOMPAT, V1_2 };
+
+ /*
+ * serialVersionUID for all stubs that can use 1.2 protocol
+ */
+ static final long STUB_SERIAL_VERSION_UID = 2;
+
+ /*
+ * version number used to seed interface hash computation
+ */
+ static final int INTERFACE_HASH_STUB_VERSION = 1;
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java
new file mode 100644
index 000000000..49e83a4bd
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/JrmpGenerator.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+import com.sun.javadoc.ClassDoc;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import sun.rmi.rmic.newrmic.BatchEnvironment;
+import sun.rmi.rmic.newrmic.Generator;
+import sun.rmi.rmic.newrmic.IndentingWriter;
+import sun.rmi.rmic.newrmic.Main;
+import sun.rmi.rmic.newrmic.Resources;
+
+import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
+
+/**
+ * JRMP rmic back end; generates source code for JRMP stub and
+ * skeleton classes.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+public class JrmpGenerator implements Generator {
+
+ private static final Map<String,StubVersion> versionOptions =
+ new HashMap<String,StubVersion>();
+ static {
+ versionOptions.put("-v1.1", StubVersion.V1_1);
+ versionOptions.put("-vcompat", StubVersion.VCOMPAT);
+ versionOptions.put("-v1.2", StubVersion.V1_2);
+ }
+
+ private static final Set<String> bootstrapClassNames =
+ new HashSet<String>();
+ static {
+ bootstrapClassNames.add("java.lang.Exception");
+ bootstrapClassNames.add("java.rmi.Remote");
+ bootstrapClassNames.add("java.rmi.RemoteException");
+ bootstrapClassNames.add("java.lang.RuntimeException");
+ };
+
+ /** version of the JRMP stub protocol to generate code for */
+ private StubVersion version = StubVersion.V1_2; // default is -v1.2
+
+ /**
+ * Creates a new JrmpGenerator.
+ **/
+ public JrmpGenerator() { }
+
+ /**
+ * The JRMP generator recognizes command line options for
+ * selecting the JRMP stub protocol version to generate classes
+ * for. Only one such option is allowed.
+ **/
+ public boolean parseArgs(String[] args, Main main) {
+ String explicitVersion = null;
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (versionOptions.containsKey(arg)) {
+ if (explicitVersion != null && !explicitVersion.equals(arg)) {
+ main.error("rmic.cannot.use.both", explicitVersion, arg);
+ return false;
+ }
+ explicitVersion = arg;
+ version = versionOptions.get(arg);
+ args[i] = null;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * The JRMP generator does not require an environment class more
+ * specific than BatchEnvironment.
+ **/
+ public Class<? extends BatchEnvironment> envClass() {
+ return BatchEnvironment.class;
+ }
+
+ public Set<String> bootstrapClassNames() {
+ return Collections.unmodifiableSet(bootstrapClassNames);
+ }
+
+ /**
+ * Generates the source file(s) for the JRMP stub class and
+ * (optionally) skeleton class for the specified remote
+ * implementation class.
+ **/
+ public void generate(BatchEnvironment env,
+ ClassDoc inputClass,
+ File destDir)
+ {
+ RemoteClass remoteClass = RemoteClass.forClass(env, inputClass);
+ if (remoteClass == null) {
+ return; // an error must have occurred
+ }
+
+ StubSkeletonWriter writer =
+ new StubSkeletonWriter(env, remoteClass, version);
+
+ File stubFile = sourceFileForClass(writer.stubClassName(), destDir);
+ try {
+ IndentingWriter out = new IndentingWriter(
+ new OutputStreamWriter(new FileOutputStream(stubFile)));
+ writer.writeStub(out);
+ out.close();
+ if (env.verbose()) {
+ env.output(Resources.getText("rmic.wrote",
+ stubFile.getPath()));
+ }
+ env.addGeneratedFile(stubFile);
+ } catch (IOException e) {
+ env.error("rmic.cant.write", stubFile.toString());
+ return;
+ }
+
+ File skeletonFile =
+ sourceFileForClass(writer.skeletonClassName(), destDir);
+ if (version == StubVersion.V1_1 ||
+ version == StubVersion.VCOMPAT)
+ {
+ try {
+ IndentingWriter out = new IndentingWriter(
+ new OutputStreamWriter(
+ new FileOutputStream(skeletonFile)));
+ writer.writeSkeleton(out);
+ out.close();
+ if (env.verbose()) {
+ env.output(Resources.getText("rmic.wrote",
+ skeletonFile.getPath()));
+ }
+ env.addGeneratedFile(skeletonFile);
+ } catch (IOException e) {
+ env.error("rmic.cant.write", skeletonFile.toString());
+ return;
+ }
+ } else {
+ /*
+ * If skeleton files are not being generated for this run,
+ * delete old skeleton source or class files for this
+ * remote implementation class that were (presumably) left
+ * over from previous runs, to avoid user confusion from
+ * extraneous or inconsistent generated files.
+ */
+ File skeletonClassFile =
+ classFileForClass(writer.skeletonClassName(), destDir);
+
+ skeletonFile.delete(); // ignore failures (no big deal)
+ skeletonClassFile.delete();
+ }
+ }
+
+
+ /**
+ * Returns the File object to be used as the source file for a
+ * class with the specified binary name, with the specified
+ * destination directory as the top of the package hierarchy.
+ **/
+ private File sourceFileForClass(String binaryName, File destDir) {
+ return fileForClass(binaryName, destDir, ".java");
+ }
+
+ /**
+ * Returns the File object to be used as the class file for a
+ * class with the specified binary name, with the supplied
+ * destination directory as the top of the package hierarchy.
+ **/
+ private File classFileForClass(String binaryName, File destDir) {
+ return fileForClass(binaryName, destDir, ".class");
+ }
+
+ private File fileForClass(String binaryName, File destDir, String ext) {
+ int i = binaryName.lastIndexOf('.');
+ String classFileName = binaryName.substring(i + 1) + ext;
+ if (i != -1) {
+ String packageName = binaryName.substring(0, i);
+ String packagePath = packageName.replace('.', File.separatorChar);
+ File packageDir = new File(destDir, packagePath);
+ /*
+ * Make sure that the directory for this package exists.
+ * We assume that the caller has verified that the top-
+ * level destination directory exists, so we need not
+ * worry about creating it unintentionally.
+ */
+ if (!packageDir.exists()) {
+ packageDir.mkdirs();
+ }
+ return new File(packageDir, classFileName);
+ } else {
+ return new File(destDir, classFileName);
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java
new file mode 100644
index 000000000..f22a652df
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/RemoteClass.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.Type;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.security.MessageDigest;
+import java.security.DigestOutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import sun.rmi.rmic.newrmic.BatchEnvironment;
+
+import static sun.rmi.rmic.newrmic.Constants.*;
+import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
+
+/**
+ * Encapsulates RMI-specific information about a remote implementation
+ * class (a class that implements one or more remote interfaces).
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+final class RemoteClass {
+
+ /** rmic environment for this object */
+ private final BatchEnvironment env;
+
+ /** the remote implementation class this object represents */
+ private final ClassDoc implClass;
+
+ /** remote interfaces implemented by this class */
+ private ClassDoc[] remoteInterfaces;
+
+ /** the remote methods of this class */
+ private Method[] remoteMethods;
+
+ /** stub/skeleton "interface hash" for this class */
+ private long interfaceHash;
+
+ /**
+ * Creates a RemoteClass instance that represents the RMI-specific
+ * information about the specified remote implementation class.
+ *
+ * If the class is not a valid remote implementation class or if
+ * some other error occurs, the return value will be null, and
+ * errors will have been reported to the supplied
+ * BatchEnvironment.
+ **/
+ static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) {
+ RemoteClass remoteClass = new RemoteClass(env, implClass);
+ if (remoteClass.init()) {
+ return remoteClass;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a RemoteClass instance for the specified class. The
+ * resulting object is not yet initialized.
+ **/
+ private RemoteClass(BatchEnvironment env, ClassDoc implClass) {
+ this.env = env;
+ this.implClass = implClass;
+ }
+
+ /**
+ * Returns the ClassDoc for this remote implementation class.
+ **/
+ ClassDoc classDoc() {
+ return implClass;
+ }
+
+ /**
+ * Returns the remote interfaces implemented by this remote
+ * implementation class.
+ *
+ * A remote interface is an interface that is a subinterface of
+ * java.rmi.Remote. The remote interfaces of a class are the
+ * direct superinterfaces of the class and all of its superclasses
+ * that are remote interfaces.
+ *
+ * The order of the array returned is arbitrary, and some elements
+ * may be superfluous (i.e., superinterfaces of other interfaces
+ * in the array).
+ **/
+ ClassDoc[] remoteInterfaces() {
+ return (ClassDoc[]) remoteInterfaces.clone();
+ }
+
+ /**
+ * Returns an array of RemoteClass.Method objects representing all
+ * of the remote methods of this remote implementation class (all
+ * of the member methods of the class's remote interfaces).
+ *
+ * The methods in the array are ordered according to a comparison
+ * of strings consisting of their name followed by their
+ * descriptor, so each method's index in the array corresponds to
+ * its "operation number" in the JDK 1.1 version of the JRMP
+ * stub/skeleton protocol.
+ **/
+ Method[] remoteMethods() {
+ return (Method[]) remoteMethods.clone();
+ }
+
+ /**
+ * Returns the "interface hash" used to match a stub/skeleton pair
+ * for this remote implementation class in the JDK 1.1 version of
+ * the JRMP stub/skeleton protocol.
+ **/
+ long interfaceHash() {
+ return interfaceHash;
+ }
+
+ /**
+ * Validates this remote implementation class and computes the
+ * RMI-specific information. Returns true if successful, or false
+ * if an error occurred.
+ **/
+ private boolean init() {
+ /*
+ * Verify that it is really a class, not an interface.
+ */
+ if (implClass.isInterface()) {
+ env.error("rmic.cant.make.stubs.for.interface",
+ implClass.qualifiedName());
+ return false;
+ }
+
+ /*
+ * Find all of the remote interfaces of our remote
+ * implementation class-- for each class up the superclass
+ * chain, add each directly-implemented interface that somehow
+ * extends Remote to a list.
+ */
+ List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>();
+ for (ClassDoc cl = implClass; cl != null; cl = cl.superclass()) {
+ for (ClassDoc intf : cl.interfaces()) {
+ /*
+ * Add interface to the list if it extends Remote and
+ * it is not already there.
+ */
+ if (!remotesImplemented.contains(intf) &&
+ intf.subclassOf(env.docRemote()))
+ {
+ remotesImplemented.add(intf);
+ if (env.verbose()) {
+ env.output("[found remote interface: " +
+ intf.qualifiedName() + "]");
+ }
+ }
+ }
+
+ /*
+ * Verify that the candidate remote implementation class
+ * implements at least one remote interface directly.
+ */
+ if (cl == implClass && remotesImplemented.isEmpty()) {
+ if (implClass.subclassOf(env.docRemote())) {
+ /*
+ * This error message is used if the class does
+ * implement a remote interface through one of its
+ * superclasses, but not directly.
+ */
+ env.error("rmic.must.implement.remote.directly",
+ implClass.qualifiedName());
+ } else {
+ /*
+ * This error message is used if the class does
+ * not implement a remote interface at all.
+ */
+ env.error("rmic.must.implement.remote",
+ implClass.qualifiedName());
+ }
+ return false;
+ }
+ }
+
+ /*
+ * Convert list of remote interfaces to an array
+ * (order is not important for this array).
+ */
+ remoteInterfaces =
+ remotesImplemented.toArray(
+ new ClassDoc[remotesImplemented.size()]);
+
+ /*
+ * Collect the methods from all of the remote interfaces into
+ * a table, which maps from method name-and-descriptor string
+ * to Method object.
+ */
+ Map<String,Method> methods = new HashMap<String,Method>();
+ boolean errors = false;
+ for (ClassDoc intf : remotesImplemented) {
+ if (!collectRemoteMethods(intf, methods)) {
+ /*
+ * Continue iterating despite errors in order to
+ * generate more complete error report.
+ */
+ errors = true;
+ }
+ }
+ if (errors) {
+ return false;
+ }
+
+ /*
+ * Sort table of remote methods into an array. The elements
+ * are sorted in ascending order of the string of the method's
+ * name and descriptor, so that each elements index is equal
+ * to its operation number in the JDK 1.1 version of the JRMP
+ * stub/skeleton protocol.
+ */
+ String[] orderedKeys =
+ methods.keySet().toArray(new String[methods.size()]);
+ Arrays.sort(orderedKeys);
+ remoteMethods = new Method[methods.size()];
+ for (int i = 0; i < remoteMethods.length; i++) {
+ remoteMethods[i] = methods.get(orderedKeys[i]);
+ if (env.verbose()) {
+ String msg = "[found remote method <" + i + ">: " +
+ remoteMethods[i].operationString();
+ ClassDoc[] exceptions = remoteMethods[i].exceptionTypes();
+ if (exceptions.length > 0) {
+ msg += " throws ";
+ for (int j = 0; j < exceptions.length; j++) {
+ if (j > 0) {
+ msg += ", ";
+ }
+ msg += exceptions[j].qualifiedName();
+ }
+ }
+ msg += "\n\tname and descriptor = \"" +
+ remoteMethods[i].nameAndDescriptor();
+ msg += "\n\tmethod hash = " +
+ remoteMethods[i].methodHash() + "]";
+ env.output(msg);
+ }
+ }
+
+ /*
+ * Finally, pre-compute the interface hash to be used by
+ * stubs/skeletons for this remote class in the JDK 1.1
+ * version of the JRMP stub/skeleton protocol.
+ */
+ interfaceHash = computeInterfaceHash();
+
+ return true;
+ }
+
+ /**
+ * Collects and validates all methods from the specified interface
+ * and all of its superinterfaces as remote methods. Remote
+ * methods are added to the supplied table. Returns true if
+ * successful, or false if an error occurred.
+ **/
+ private boolean collectRemoteMethods(ClassDoc intf,
+ Map<String,Method> table)
+ {
+ if (!intf.isInterface()) {
+ throw new AssertionError(
+ intf.qualifiedName() + " not an interface");
+ }
+
+ boolean errors = false;
+
+ /*
+ * Search interface's declared methods.
+ */
+ nextMethod:
+ for (MethodDoc method : intf.methods()) {
+
+ /*
+ * Verify that each method throws RemoteException (or a
+ * superclass of RemoteException).
+ */
+ boolean hasRemoteException = false;
+ for (ClassDoc ex : method.thrownExceptions()) {
+ if (env.docRemoteException().subclassOf(ex)) {
+ hasRemoteException = true;
+ break;
+ }
+ }
+
+ /*
+ * If this method did not throw RemoteException as required,
+ * generate the error but continue, so that multiple such
+ * errors can be reported.
+ */
+ if (!hasRemoteException) {
+ env.error("rmic.must.throw.remoteexception",
+ intf.qualifiedName(),
+ method.name() + method.signature());
+ errors = true;
+ continue nextMethod;
+ }
+
+ /*
+ * Verify that the implementation of this method throws only
+ * java.lang.Exception or its subclasses (fix bugid 4092486).
+ * JRMP does not support remote methods throwing
+ * java.lang.Throwable or other subclasses.
+ */
+ MethodDoc implMethod = findImplMethod(method);
+ if (implMethod != null) { // should not be null
+ for (ClassDoc ex : implMethod.thrownExceptions()) {
+ if (!ex.subclassOf(env.docException())) {
+ env.error("rmic.must.only.throw.exception",
+ implMethod.name() + implMethod.signature(),
+ ex.qualifiedName());
+ errors = true;
+ continue nextMethod;
+ }
+ }
+ }
+
+ /*
+ * Create RemoteClass.Method object to represent this method
+ * found in a remote interface.
+ */
+ Method newMethod = new Method(method);
+
+ /*
+ * Store remote method's representation in the table of
+ * remote methods found, keyed by its name and descriptor.
+ *
+ * If the table already contains an entry with the same
+ * method name and descriptor, then we must replace the
+ * old entry with a Method object that represents a legal
+ * combination of the old and the new methods;
+ * specifically, the combined method must have a throws
+ * clause that contains (only) all of the checked
+ * exceptions that can be thrown by both the old and the
+ * new method (see bugid 4070653).
+ */
+ String key = newMethod.nameAndDescriptor();
+ Method oldMethod = table.get(key);
+ if (oldMethod != null) {
+ newMethod = newMethod.mergeWith(oldMethod);
+ }
+ table.put(key, newMethod);
+ }
+
+ /*
+ * Recursively collect methods for all superinterfaces.
+ */
+ for (ClassDoc superintf : intf.interfaces()) {
+ if (!collectRemoteMethods(superintf, table)) {
+ errors = true;
+ }
+ }
+
+ return !errors;
+ }
+
+ /**
+ * Returns the MethodDoc for the method of this remote
+ * implementation class that implements the specified remote
+ * method of a remote interface. Returns null if no matching
+ * method was found in this remote implementation class.
+ **/
+ private MethodDoc findImplMethod(MethodDoc interfaceMethod) {
+ String name = interfaceMethod.name();
+ String desc = Util.methodDescriptorOf(interfaceMethod);
+ for (MethodDoc implMethod : implClass.methods()) {
+ if (name.equals(implMethod.name()) &&
+ desc.equals(Util.methodDescriptorOf(implMethod)))
+ {
+ return implMethod;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Computes the "interface hash" of the stub/skeleton pair for
+ * this remote implementation class. This is the 64-bit value
+ * used to enforce compatibility between a stub class and a
+ * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton
+ * protocol.
+ *
+ * It is calculated using the first 64 bits of an SHA digest. The
+ * digest is of a stream consisting of the following data:
+ * (int) stub version number, always 1
+ * for each remote method, in order of operation number:
+ * (UTF-8) method name
+ * (UTF-8) method descriptor
+ * for each declared exception, in alphabetical name order:
+ * (UTF-8) name of exception class
+ * (where "UTF-8" includes a 16-bit length prefix as written by
+ * java.io.DataOutput.writeUTF).
+ **/
+ private long computeInterfaceHash() {
+ long hash = 0;
+ ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ DataOutputStream out = new DataOutputStream(
+ new DigestOutputStream(sink, md));
+
+ out.writeInt(INTERFACE_HASH_STUB_VERSION);
+
+ for (Method method : remoteMethods) {
+ MethodDoc methodDoc = method.methodDoc();
+
+ out.writeUTF(methodDoc.name());
+ out.writeUTF(Util.methodDescriptorOf(methodDoc));
+ // descriptors already use binary names
+
+ ClassDoc exceptions[] = methodDoc.thrownExceptions();
+ Arrays.sort(exceptions, new ClassDocComparator());
+ for (ClassDoc ex : exceptions) {
+ out.writeUTF(Util.binaryNameOf(ex));
+ }
+ }
+ out.flush();
+
+ // use only the first 64 bits of the digest for the hash
+ byte hashArray[] = md.digest();
+ for (int i = 0; i < Math.min(8, hashArray.length); i++) {
+ hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
+ }
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+
+ return hash;
+ }
+
+ /**
+ * Compares ClassDoc instances according to the lexicographic
+ * order of their binary names.
+ **/
+ private static class ClassDocComparator implements Comparator<ClassDoc> {
+ public int compare(ClassDoc o1, ClassDoc o2) {
+ return Util.binaryNameOf(o1).compareTo(Util.binaryNameOf(o2));
+ }
+ }
+
+ /**
+ * Encapsulates RMI-specific information about a particular remote
+ * method in the remote implementation class represented by the
+ * enclosing RemoteClass.
+ **/
+ final class Method implements Cloneable {
+
+ /**
+ * MethodDoc for this remove method, from one of the remote
+ * interfaces that this method was found in.
+ *
+ * Note that this MethodDoc may be only one of multiple that
+ * correspond to this remote method object, if multiple of
+ * this class's remote interfaces contain methods with the
+ * same name and descriptor. Therefore, this MethodDoc may
+ * declare more exceptions thrown that this remote method
+ * does.
+ **/
+ private final MethodDoc methodDoc;
+
+ /** java.rmi.server.Operation string for this remote method */
+ private final String operationString;
+
+ /** name and descriptor of this remote method */
+ private final String nameAndDescriptor;
+
+ /** JRMP "method hash" for this remote method */
+ private final long methodHash;
+
+ /**
+ * Exceptions declared to be thrown by this remote method.
+ *
+ * This list may include superfluous entries, such as
+ * unchecked exceptions and subclasses of other entries.
+ **/
+ private ClassDoc[] exceptionTypes;
+
+ /**
+ * Creates a new Method instance for the specified method.
+ **/
+ Method(MethodDoc methodDoc) {
+ this.methodDoc = methodDoc;
+ exceptionTypes = methodDoc.thrownExceptions();
+ /*
+ * Sort exception types to improve consistency with
+ * previous implementations.
+ */
+ Arrays.sort(exceptionTypes, new ClassDocComparator());
+ operationString = computeOperationString();
+ nameAndDescriptor =
+ methodDoc.name() + Util.methodDescriptorOf(methodDoc);
+ methodHash = computeMethodHash();
+ }
+
+ /**
+ * Returns the MethodDoc object corresponding to this method
+ * of a remote interface.
+ **/
+ MethodDoc methodDoc() {
+ return methodDoc;
+ }
+
+ /**
+ * Returns the parameter types declared by this method.
+ **/
+ Type[] parameterTypes() {
+ Parameter[] parameters = methodDoc.parameters();
+ Type[] paramTypes = new Type[parameters.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ paramTypes[i] = parameters[i].type();
+ }
+ return paramTypes;
+ }
+
+ /**
+ * Returns the exception types declared to be thrown by this
+ * remote method.
+ *
+ * For methods with the same name and descriptor inherited
+ * from multiple remote interfaces, the array will contain the
+ * set of exceptions declared in all of the interfaces'
+ * methods that can be legally thrown by all of them.
+ **/
+ ClassDoc[] exceptionTypes() {
+ return (ClassDoc[]) exceptionTypes.clone();
+ }
+
+ /**
+ * Returns the JRMP "method hash" used to identify this remote
+ * method in the JDK 1.2 version of the stub protocol.
+ **/
+ long methodHash() {
+ return methodHash;
+ }
+
+ /**
+ * Returns the string representation of this method
+ * appropriate for the construction of a
+ * java.rmi.server.Operation object.
+ **/
+ String operationString() {
+ return operationString;
+ }
+
+ /**
+ * Returns a string consisting of this method's name followed
+ * by its descriptor.
+ **/
+ String nameAndDescriptor() {
+ return nameAndDescriptor;
+ }
+
+ /**
+ * Returns a new Method object that is a legal combination of
+ * this Method object and another one.
+ *
+ * Doing this requires determining the exceptions declared by
+ * the combined method, which must be (only) all of the
+ * exceptions declared in both old Methods that may thrown in
+ * either of them.
+ **/
+ Method mergeWith(Method other) {
+ if (!nameAndDescriptor().equals(other.nameAndDescriptor())) {
+ throw new AssertionError(
+ "attempt to merge method \"" +
+ other.nameAndDescriptor() + "\" with \"" +
+ nameAndDescriptor());
+ }
+
+ List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>();
+ collectCompatibleExceptions(
+ other.exceptionTypes, exceptionTypes, legalExceptions);
+ collectCompatibleExceptions(
+ exceptionTypes, other.exceptionTypes, legalExceptions);
+
+ Method merged = clone();
+ merged.exceptionTypes =
+ legalExceptions.toArray(new ClassDoc[legalExceptions.size()]);
+
+ return merged;
+ }
+
+ /**
+ * Cloning is supported by returning a shallow copy of this
+ * object.
+ **/
+ protected Method clone() {
+ try {
+ return (Method) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Adds to the supplied list all exceptions in the "froms"
+ * array that are subclasses of an exception in the "withs"
+ * array.
+ **/
+ private void collectCompatibleExceptions(ClassDoc[] froms,
+ ClassDoc[] withs,
+ List<ClassDoc> list)
+ {
+ for (ClassDoc from : froms) {
+ if (!list.contains(from)) {
+ for (ClassDoc with : withs) {
+ if (from.subclassOf(with)) {
+ list.add(from);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Computes the JRMP "method hash" of this remote method. The
+ * method hash is a long containing the first 64 bits of the
+ * SHA digest from the UTF-8 encoded string of the method name
+ * and descriptor.
+ **/
+ private long computeMethodHash() {
+ long hash = 0;
+ ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ DataOutputStream out = new DataOutputStream(
+ new DigestOutputStream(sink, md));
+
+ String methodString = nameAndDescriptor();
+ out.writeUTF(methodString);
+
+ // use only the first 64 bits of the digest for the hash
+ out.flush();
+ byte hashArray[] = md.digest();
+ for (int i = 0; i < Math.min(8, hashArray.length); i++) {
+ hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
+ }
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+
+ return hash;
+ }
+
+ /**
+ * Computes the string representation of this method
+ * appropriate for the construction of a
+ * java.rmi.server.Operation object.
+ **/
+ private String computeOperationString() {
+ /*
+ * To be consistent with previous implementations, we use
+ * the deprecated style of placing the "[]" for the return
+ * type (if any) after the parameter list.
+ */
+ Type returnType = methodDoc.returnType();
+ String op = returnType.qualifiedTypeName() + " " +
+ methodDoc.name() + "(";
+ Parameter[] parameters = methodDoc.parameters();
+ for (int i = 0; i < parameters.length; i++) {
+ if (i > 0) {
+ op += ", ";
+ }
+ op += parameters[i].type().toString();
+ }
+ op += ")" + returnType.dimension();
+ return op;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java
new file mode 100644
index 000000000..0c52cfba9
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/StubSkeletonWriter.java
@@ -0,0 +1,1079 @@
+/*
+ * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.Type;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import sun.rmi.rmic.newrmic.BatchEnvironment;
+import sun.rmi.rmic.newrmic.IndentingWriter;
+
+import static sun.rmi.rmic.newrmic.Constants.*;
+import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
+
+/**
+ * Writes the source code for the stub class and (optionally) skeleton
+ * class for a particular remote implementation class.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+class StubSkeletonWriter {
+
+ /** rmic environment for this object */
+ private final BatchEnvironment env;
+
+ /** the remote implemention class to generate code for */
+ private final RemoteClass remoteClass;
+
+ /** version of the JRMP stub protocol to generate code for */
+ private final StubVersion version;
+
+ /*
+ * binary names of the stub and skeleton classes to generate for
+ * the remote class
+ */
+ private final String stubClassName;
+ private final String skeletonClassName;
+
+ /* package name and simple names of the stub and skeleton classes */
+ private final String packageName;
+ private final String stubClassSimpleName;
+ private final String skeletonClassSimpleName;
+
+ /** remote methods of class, indexed by operation number */
+ private final RemoteClass.Method[] remoteMethods;
+
+ /**
+ * Names to use for the java.lang.reflect.Method static fields in
+ * the generated stub class corresponding to each remote method.
+ **/
+ private final String[] methodFieldNames;
+
+ /**
+ * Creates a StubSkeletonWriter instance for the specified remote
+ * implementation class. The generated code will implement the
+ * specified JRMP stub protocol version.
+ **/
+ StubSkeletonWriter(BatchEnvironment env,
+ RemoteClass remoteClass,
+ StubVersion version)
+ {
+ this.env = env;
+ this.remoteClass = remoteClass;
+ this.version = version;
+
+ stubClassName = Util.binaryNameOf(remoteClass.classDoc()) + "_Stub";
+ skeletonClassName =
+ Util.binaryNameOf(remoteClass.classDoc()) + "_Skel";
+
+ int i = stubClassName.lastIndexOf('.');
+ packageName = (i != -1 ? stubClassName.substring(0, i) : "");
+ stubClassSimpleName = stubClassName.substring(i + 1);
+ skeletonClassSimpleName = skeletonClassName.substring(i + 1);
+
+ remoteMethods = remoteClass.remoteMethods();
+ methodFieldNames = nameMethodFields(remoteMethods);
+ }
+
+ /**
+ * Returns the binary name of the stub class to generate for the
+ * remote implementation class.
+ **/
+ String stubClassName() {
+ return stubClassName;
+ }
+
+ /**
+ * Returns the binary name of the skeleton class to generate for
+ * the remote implementation class.
+ **/
+ String skeletonClassName() {
+ return skeletonClassName;
+ }
+
+ /**
+ * Writes the stub class for the remote class to a stream.
+ **/
+ void writeStub(IndentingWriter p) throws IOException {
+
+ /*
+ * Write boiler plate comment.
+ */
+ p.pln("// Stub class generated by rmic, do not edit.");
+ p.pln("// Contents subject to change without notice.");
+ p.pln();
+
+ /*
+ * If remote implementation class was in a particular package,
+ * declare the stub class to be in the same package.
+ */
+ if (!packageName.equals("")) {
+ p.pln("package " + packageName + ";");
+ p.pln();
+ }
+
+ /*
+ * Declare the stub class; implement all remote interfaces.
+ */
+ p.plnI("public final class " + stubClassSimpleName);
+ p.pln("extends " + REMOTE_STUB);
+ ClassDoc[] remoteInterfaces = remoteClass.remoteInterfaces();
+ if (remoteInterfaces.length > 0) {
+ p.p("implements ");
+ for (int i = 0; i < remoteInterfaces.length; i++) {
+ if (i > 0) {
+ p.p(", ");
+ }
+ p.p(remoteInterfaces[i].qualifiedName());
+ }
+ p.pln();
+ }
+ p.pOlnI("{");
+
+ if (version == StubVersion.V1_1 ||
+ version == StubVersion.VCOMPAT)
+ {
+ writeOperationsArray(p);
+ p.pln();
+ writeInterfaceHash(p);
+ p.pln();
+ }
+
+ if (version == StubVersion.VCOMPAT ||
+ version == StubVersion.V1_2)
+ {
+ p.pln("private static final long serialVersionUID = " +
+ STUB_SERIAL_VERSION_UID + ";");
+ p.pln();
+
+ /*
+ * We only need to declare and initialize the static fields of
+ * Method objects for each remote method if there are any remote
+ * methods; otherwise, skip this code entirely, to avoid generating
+ * a try/catch block for a checked exception that cannot occur
+ * (see bugid 4125181).
+ */
+ if (methodFieldNames.length > 0) {
+ if (version == StubVersion.VCOMPAT) {
+ p.pln("private static boolean useNewInvoke;");
+ }
+ writeMethodFieldDeclarations(p);
+ p.pln();
+
+ /*
+ * Initialize java.lang.reflect.Method fields for each remote
+ * method in a static initializer.
+ */
+ p.plnI("static {");
+ p.plnI("try {");
+ if (version == StubVersion.VCOMPAT) {
+ /*
+ * Fat stubs must determine whether the API required for
+ * the JDK 1.2 stub protocol is supported in the current
+ * runtime, so that it can use it if supported. This is
+ * determined by using the Reflection API to test if the
+ * new invoke method on RemoteRef exists, and setting the
+ * static boolean "useNewInvoke" to true if it does, or
+ * to false if a NoSuchMethodException is thrown.
+ */
+ p.plnI(REMOTE_REF + ".class.getMethod(\"invoke\",");
+ p.plnI("new java.lang.Class[] {");
+ p.pln(REMOTE + ".class,");
+ p.pln("java.lang.reflect.Method.class,");
+ p.pln("java.lang.Object[].class,");
+ p.pln("long.class");
+ p.pOln("});");
+ p.pO();
+ p.pln("useNewInvoke = true;");
+ }
+ writeMethodFieldInitializers(p);
+ p.pOlnI("} catch (java.lang.NoSuchMethodException e) {");
+ if (version == StubVersion.VCOMPAT) {
+ p.pln("useNewInvoke = false;");
+ } else {
+ p.plnI("throw new java.lang.NoSuchMethodError(");
+ p.pln("\"stub class initialization failed\");");
+ p.pO();
+ }
+ p.pOln("}"); // end try/catch block
+ p.pOln("}"); // end static initializer
+ p.pln();
+ }
+ }
+
+ writeStubConstructors(p);
+ p.pln();
+
+ /*
+ * Write each stub method.
+ */
+ if (remoteMethods.length > 0) {
+ p.pln("// methods from remote interfaces");
+ for (int i = 0; i < remoteMethods.length; ++i) {
+ p.pln();
+ writeStubMethod(p, i);
+ }
+ }
+
+ p.pOln("}"); // end stub class
+ }
+
+ /**
+ * Writes the constructors for the stub class.
+ **/
+ private void writeStubConstructors(IndentingWriter p)
+ throws IOException
+ {
+ p.pln("// constructors");
+
+ /*
+ * Only stubs compatible with the JDK 1.1 stub protocol need
+ * a no-arg constructor; later versions use reflection to find
+ * the constructor that directly takes a RemoteRef argument.
+ */
+ if (version == StubVersion.V1_1 ||
+ version == StubVersion.VCOMPAT)
+ {
+ p.plnI("public " + stubClassSimpleName + "() {");
+ p.pln("super();");
+ p.pOln("}");
+ }
+
+ p.plnI("public " + stubClassSimpleName + "(" + REMOTE_REF + " ref) {");
+ p.pln("super(ref);");
+ p.pOln("}");
+ }
+
+ /**
+ * Writes the stub method for the remote method with the given
+ * operation number.
+ **/
+ private void writeStubMethod(IndentingWriter p, int opnum)
+ throws IOException
+ {
+ RemoteClass.Method method = remoteMethods[opnum];
+ MethodDoc methodDoc = method.methodDoc();
+ String methodName = methodDoc.name();
+ Type[] paramTypes = method.parameterTypes();
+ String paramNames[] = nameParameters(paramTypes);
+ Type returnType = methodDoc.returnType();
+ ClassDoc[] exceptions = method.exceptionTypes();
+
+ /*
+ * Declare stub method; throw exceptions declared in remote
+ * interface(s).
+ */
+ p.pln("// implementation of " +
+ Util.getFriendlyUnqualifiedSignature(methodDoc));
+ p.p("public " + returnType.toString() + " " + methodName + "(");
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (i > 0) {
+ p.p(", ");
+ }
+ p.p(paramTypes[i].toString() + " " + paramNames[i]);
+ }
+ p.plnI(")");
+ if (exceptions.length > 0) {
+ p.p("throws ");
+ for (int i = 0; i < exceptions.length; i++) {
+ if (i > 0) {
+ p.p(", ");
+ }
+ p.p(exceptions[i].qualifiedName());
+ }
+ p.pln();
+ }
+ p.pOlnI("{");
+
+ /*
+ * The RemoteRef.invoke methods throw Exception, but unless
+ * this stub method throws Exception as well, we must catch
+ * Exceptions thrown from the invocation. So we must catch
+ * Exception and rethrow something we can throw:
+ * UnexpectedException, which is a subclass of
+ * RemoteException. But for any subclasses of Exception that
+ * we can throw, like RemoteException, RuntimeException, and
+ * any of the exceptions declared by this stub method, we want
+ * them to pass through unmodified, so first we must catch any
+ * such exceptions and rethrow them directly.
+ *
+ * We have to be careful generating the rethrowing catch
+ * blocks here, because javac will flag an error if there are
+ * any unreachable catch blocks, i.e. if the catch of an
+ * exception class follows a previous catch of it or of one of
+ * its superclasses. The following method invocation takes
+ * care of these details.
+ */
+ List<ClassDoc> catchList = computeUniqueCatchList(exceptions);
+
+ /*
+ * If we need to catch any particular exceptions (i.e. this method
+ * does not declare java.lang.Exception), put the entire stub
+ * method in a try block.
+ */
+ if (catchList.size() > 0) {
+ p.plnI("try {");
+ }
+
+ if (version == StubVersion.VCOMPAT) {
+ p.plnI("if (useNewInvoke) {");
+ }
+ if (version == StubVersion.VCOMPAT ||
+ version == StubVersion.V1_2)
+ {
+ if (!Util.isVoid(returnType)) {
+ p.p("Object $result = "); // REMIND: why $?
+ }
+ p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", ");
+ if (paramTypes.length > 0) {
+ p.p("new java.lang.Object[] {");
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(wrapArgumentCode(paramTypes[i], paramNames[i]));
+ }
+ p.p("}");
+ } else {
+ p.p("null");
+ }
+ p.pln(", " + method.methodHash() + "L);");
+ if (!Util.isVoid(returnType)) {
+ p.pln("return " +
+ unwrapArgumentCode(returnType, "$result") + ";");
+ }
+ }
+ if (version == StubVersion.VCOMPAT) {
+ p.pOlnI("} else {");
+ }
+ if (version == StubVersion.V1_1 ||
+ version == StubVersion.VCOMPAT)
+ {
+ p.pln(REMOTE_CALL + " call = ref.newCall((" + REMOTE_OBJECT +
+ ") this, operations, " + opnum + ", interfaceHash);");
+
+ if (paramTypes.length > 0) {
+ p.plnI("try {");
+ p.pln("java.io.ObjectOutput out = call.getOutputStream();");
+ writeMarshalArguments(p, "out", paramTypes, paramNames);
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + MARSHAL_EXCEPTION +
+ "(\"error marshalling arguments\", e);");
+ p.pOln("}");
+ }
+
+ p.pln("ref.invoke(call);");
+
+ if (Util.isVoid(returnType)) {
+ p.pln("ref.done(call);");
+ } else {
+ p.pln(returnType.toString() + " $result;");
+ // REMIND: why $?
+ p.plnI("try {");
+ p.pln("java.io.ObjectInput in = call.getInputStream();");
+ boolean objectRead =
+ writeUnmarshalArgument(p, "in", returnType, "$result");
+ p.pln(";");
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + UNMARSHAL_EXCEPTION +
+ "(\"error unmarshalling return\", e);");
+ /*
+ * If any only if readObject has been invoked, we must catch
+ * ClassNotFoundException as well as IOException.
+ */
+ if (objectRead) {
+ p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
+ p.pln("throw new " + UNMARSHAL_EXCEPTION +
+ "(\"error unmarshalling return\", e);");
+ }
+ p.pOlnI("} finally {");
+ p.pln("ref.done(call);");
+ p.pOln("}");
+ p.pln("return $result;");
+ }
+ }
+ if (version == StubVersion.VCOMPAT) {
+ p.pOln("}"); // end if/else (useNewInvoke) block
+ }
+
+ /*
+ * If we need to catch any particular exceptions, finally write
+ * the catch blocks for them, rethrow any other Exceptions with an
+ * UnexpectedException, and end the try block.
+ */
+ if (catchList.size() > 0) {
+ for (ClassDoc catchClass : catchList) {
+ p.pOlnI("} catch (" + catchClass.qualifiedName() + " e) {");
+ p.pln("throw e;");
+ }
+ p.pOlnI("} catch (java.lang.Exception e) {");
+ p.pln("throw new " + UNEXPECTED_EXCEPTION +
+ "(\"undeclared checked exception\", e);");
+ p.pOln("}"); // end try/catch block
+ }
+
+ p.pOln("}"); // end stub method
+ }
+
+ /**
+ * Computes the exceptions that need to be caught and rethrown in
+ * a stub method before wrapping Exceptions in
+ * UnexpectedExceptions, given the exceptions declared in the
+ * throws clause of the method. Returns a list containing the
+ * exception to catch. Each exception is guaranteed to be unique,
+ * i.e. not a subclass of any of the other exceptions in the list,
+ * so the catch blocks for these exceptions may be generated in
+ * any order relative to each other.
+ *
+ * RemoteException and RuntimeException are each automatically
+ * placed in the returned list (unless any of their superclasses
+ * are already present), since those exceptions should always be
+ * directly rethrown by a stub method.
+ *
+ * The returned list will be empty if java.lang.Exception or one
+ * of its superclasses is in the throws clause of the method,
+ * indicating that no exceptions need to be caught.
+ **/
+ private List<ClassDoc> computeUniqueCatchList(ClassDoc[] exceptions) {
+ List<ClassDoc> uniqueList = new ArrayList<ClassDoc>();
+
+ uniqueList.add(env.docRuntimeException());
+ uniqueList.add(env.docRemoteException()); // always catch/rethrow these
+
+ /* For each exception declared by the stub method's throws clause: */
+ nextException:
+ for (ClassDoc ex : exceptions) {
+ if (env.docException().subclassOf(ex)) {
+ /*
+ * If java.lang.Exception (or a superclass) was declared
+ * in the throws clause of this stub method, then we don't
+ * have to bother catching anything; clear the list and
+ * return.
+ */
+ uniqueList.clear();
+ break;
+ } else if (!ex.subclassOf(env.docException())) {
+ /*
+ * Ignore other Throwables that do not extend Exception,
+ * because they cannot be thrown by the invoke methods.
+ */
+ continue;
+ }
+ /*
+ * Compare this exception against the current list of
+ * exceptions that need to be caught:
+ */
+ for (Iterator<ClassDoc> i = uniqueList.iterator(); i.hasNext();) {
+ ClassDoc ex2 = i.next();
+ if (ex.subclassOf(ex2)) {
+ /*
+ * If a superclass of this exception is already on
+ * the list to catch, then ignore this one and continue;
+ */
+ continue nextException;
+ } else if (ex2.subclassOf(ex)) {
+ /*
+ * If a subclass of this exception is on the list
+ * to catch, then remove it;
+ */
+ i.remove();
+ }
+ }
+ /* This exception is unique: add it to the list to catch. */
+ uniqueList.add(ex);
+ }
+ return uniqueList;
+ }
+
+ /**
+ * Writes the skeleton for the remote class to a stream.
+ **/
+ void writeSkeleton(IndentingWriter p) throws IOException {
+ if (version == StubVersion.V1_2) {
+ throw new AssertionError(
+ "should not generate skeleton for version " + version);
+ }
+
+ /*
+ * Write boiler plate comment.
+ */
+ p.pln("// Skeleton class generated by rmic, do not edit.");
+ p.pln("// Contents subject to change without notice.");
+ p.pln();
+
+ /*
+ * If remote implementation class was in a particular package,
+ * declare the skeleton class to be in the same package.
+ */
+ if (!packageName.equals("")) {
+ p.pln("package " + packageName + ";");
+ p.pln();
+ }
+
+ /*
+ * Declare the skeleton class.
+ */
+ p.plnI("public final class " + skeletonClassSimpleName);
+ p.pln("implements " + SKELETON);
+ p.pOlnI("{");
+
+ writeOperationsArray(p);
+ p.pln();
+
+ writeInterfaceHash(p);
+ p.pln();
+
+ /*
+ * Define the getOperations() method.
+ */
+ p.plnI("public " + OPERATION + "[] getOperations() {");
+ p.pln("return (" + OPERATION + "[]) operations.clone();");
+ p.pOln("}");
+ p.pln();
+
+ /*
+ * Define the dispatch() method.
+ */
+ p.plnI("public void dispatch(" + REMOTE + " obj, " +
+ REMOTE_CALL + " call, int opnum, long hash)");
+ p.pln("throws java.lang.Exception");
+ p.pOlnI("{");
+
+ if (version == StubVersion.VCOMPAT) {
+ p.plnI("if (opnum < 0) {");
+ if (remoteMethods.length > 0) {
+ for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
+ if (opnum > 0)
+ p.pO("} else ");
+ p.plnI("if (hash == " +
+ remoteMethods[opnum].methodHash() + "L) {");
+ p.pln("opnum = " + opnum + ";");
+ }
+ p.pOlnI("} else {");
+ }
+ /*
+ * Skeleton throws UnmarshalException if it does not recognize
+ * the method hash; this is what UnicastServerRef.dispatch()
+ * would do.
+ */
+ p.pln("throw new " +
+ UNMARSHAL_EXCEPTION + "(\"invalid method hash\");");
+ if (remoteMethods.length > 0) {
+ p.pOln("}");
+ }
+ /*
+ * Ignore the validation of the interface hash if the
+ * operation number was negative, since it is really a
+ * method hash instead.
+ */
+ p.pOlnI("} else {");
+ }
+
+ p.plnI("if (hash != interfaceHash)");
+ p.pln("throw new " +
+ SKELETON_MISMATCH_EXCEPTION + "(\"interface hash mismatch\");");
+ p.pO();
+
+ if (version == StubVersion.VCOMPAT) {
+ p.pOln("}"); // end if/else (opnum < 0) block
+ }
+ p.pln();
+
+ /*
+ * Cast remote object reference to the remote implementation
+ * class, if it's not private. We don't use the binary name
+ * of the class like previous implementations did because that
+ * would not compile with javac (since 1.4.1). If the remote
+ * implementation class is private, then we can't cast to it
+ * like previous implementations did because that also would
+ * not compile with javac-- so instead, we'll have to try to
+ * cast to the remote interface for each remote method.
+ */
+ if (!remoteClass.classDoc().isPrivate()) {
+ p.pln(remoteClass.classDoc().qualifiedName() + " server = (" +
+ remoteClass.classDoc().qualifiedName() + ") obj;");
+ }
+
+ /*
+ * Process call according to the operation number.
+ */
+ p.plnI("switch (opnum) {");
+ for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
+ writeSkeletonDispatchCase(p, opnum);
+ }
+ p.pOlnI("default:");
+ /*
+ * Skeleton throws UnmarshalException if it does not recognize
+ * the operation number; this is consistent with the case of an
+ * unrecognized method hash.
+ */
+ p.pln("throw new " + UNMARSHAL_EXCEPTION +
+ "(\"invalid method number\");");
+ p.pOln("}"); // end switch statement
+
+ p.pOln("}"); // end dispatch() method
+
+ p.pOln("}"); // end skeleton class
+ }
+
+ /**
+ * Writes the case block for the skeleton's dispatch method for
+ * the remote method with the given "opnum".
+ **/
+ private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
+ throws IOException
+ {
+ RemoteClass.Method method = remoteMethods[opnum];
+ MethodDoc methodDoc = method.methodDoc();
+ String methodName = methodDoc.name();
+ Type paramTypes[] = method.parameterTypes();
+ String paramNames[] = nameParameters(paramTypes);
+ Type returnType = methodDoc.returnType();
+
+ p.pOlnI("case " + opnum + ": // " +
+ Util.getFriendlyUnqualifiedSignature(methodDoc));
+ /*
+ * Use nested block statement inside case to provide an independent
+ * namespace for local variables used to unmarshal parameters for
+ * this remote method.
+ */
+ p.pOlnI("{");
+
+ if (paramTypes.length > 0) {
+ /*
+ * Declare local variables to hold arguments.
+ */
+ for (int i = 0; i < paramTypes.length; i++) {
+ p.pln(paramTypes[i].toString() + " " + paramNames[i] + ";");
+ }
+
+ /*
+ * Unmarshal arguments from call stream.
+ */
+ p.plnI("try {");
+ p.pln("java.io.ObjectInput in = call.getInputStream();");
+ boolean objectsRead = writeUnmarshalArguments(p, "in",
+ paramTypes, paramNames);
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " + UNMARSHAL_EXCEPTION +
+ "(\"error unmarshalling arguments\", e);");
+ /*
+ * If any only if readObject has been invoked, we must catch
+ * ClassNotFoundException as well as IOException.
+ */
+ if (objectsRead) {
+ p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
+ p.pln("throw new " + UNMARSHAL_EXCEPTION +
+ "(\"error unmarshalling arguments\", e);");
+ }
+ p.pOlnI("} finally {");
+ p.pln("call.releaseInputStream();");
+ p.pOln("}");
+ } else {
+ p.pln("call.releaseInputStream();");
+ }
+
+ if (!Util.isVoid(returnType)) {
+ /*
+ * Declare variable to hold return type, if not void.
+ */
+ p.p(returnType.toString() + " $result = ");
+ // REMIND: why $?
+ }
+
+ /*
+ * Invoke the method on the server object. If the remote
+ * implementation class is private, then we don't have a
+ * reference cast to it, and so we try to cast to the remote
+ * object reference to the method's declaring interface here.
+ */
+ String target = remoteClass.classDoc().isPrivate() ?
+ "((" + methodDoc.containingClass().qualifiedName() + ") obj)" :
+ "server";
+ p.p(target + "." + methodName + "(");
+ for (int i = 0; i < paramNames.length; i++) {
+ if (i > 0)
+ p.p(", ");
+ p.p(paramNames[i]);
+ }
+ p.pln(");");
+
+ /*
+ * Always invoke getResultStream(true) on the call object to send
+ * the indication of a successful invocation to the caller. If
+ * the return type is not void, keep the result stream and marshal
+ * the return value.
+ */
+ p.plnI("try {");
+ if (!Util.isVoid(returnType)) {
+ p.p("java.io.ObjectOutput out = ");
+ }
+ p.pln("call.getResultStream(true);");
+ if (!Util.isVoid(returnType)) {
+ writeMarshalArgument(p, "out", returnType, "$result");
+ p.pln(";");
+ }
+ p.pOlnI("} catch (java.io.IOException e) {");
+ p.pln("throw new " +
+ MARSHAL_EXCEPTION + "(\"error marshalling return\", e);");
+ p.pOln("}");
+
+ p.pln("break;"); // break from switch statement
+
+ p.pOlnI("}"); // end nested block statement
+ p.pln();
+ }
+
+ /**
+ * Writes declaration and initializer for "operations" static array.
+ **/
+ private void writeOperationsArray(IndentingWriter p)
+ throws IOException
+ {
+ p.plnI("private static final " + OPERATION + "[] operations = {");
+ for (int i = 0; i < remoteMethods.length; i++) {
+ if (i > 0)
+ p.pln(",");
+ p.p("new " + OPERATION + "(\"" +
+ remoteMethods[i].operationString() + "\")");
+ }
+ p.pln();
+ p.pOln("};");
+ }
+
+ /**
+ * Writes declaration and initializer for "interfaceHash" static field.
+ **/
+ private void writeInterfaceHash(IndentingWriter p)
+ throws IOException
+ {
+ p.pln("private static final long interfaceHash = " +
+ remoteClass.interfaceHash() + "L;");
+ }
+
+ /**
+ * Writes declaration for java.lang.reflect.Method static fields
+ * corresponding to each remote method in a stub.
+ **/
+ private void writeMethodFieldDeclarations(IndentingWriter p)
+ throws IOException
+ {
+ for (String name : methodFieldNames) {
+ p.pln("private static java.lang.reflect.Method " + name + ";");
+ }
+ }
+
+ /**
+ * Writes code to initialize the static fields for each method
+ * using the Java Reflection API.
+ **/
+ private void writeMethodFieldInitializers(IndentingWriter p)
+ throws IOException
+ {
+ for (int i = 0; i < methodFieldNames.length; i++) {
+ p.p(methodFieldNames[i] + " = ");
+ /*
+ * Look up the Method object in the somewhat arbitrary
+ * interface that we find in the Method object.
+ */
+ RemoteClass.Method method = remoteMethods[i];
+ MethodDoc methodDoc = method.methodDoc();
+ String methodName = methodDoc.name();
+ Type paramTypes[] = method.parameterTypes();
+
+ p.p(methodDoc.containingClass().qualifiedName() + ".class.getMethod(\"" +
+ methodName + "\", new java.lang.Class[] {");
+ for (int j = 0; j < paramTypes.length; j++) {
+ if (j > 0)
+ p.p(", ");
+ p.p(paramTypes[j].toString() + ".class");
+ }
+ p.pln("});");
+ }
+ }
+
+
+ /*
+ * Following are a series of static utility methods useful during
+ * the code generation process:
+ */
+
+ /**
+ * Generates an array of names for fields correspondins to the
+ * given array of remote methods. Each name in the returned array
+ * is guaranteed to be unique.
+ *
+ * The name of a method is included in its corresponding field
+ * name to enhance readability of the generated code.
+ **/
+ private static String[] nameMethodFields(RemoteClass.Method[] methods) {
+ String[] names = new String[methods.length];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = "$method_" + methods[i].methodDoc().name() + "_" + i;
+ }
+ return names;
+ }
+
+ /**
+ * Generates an array of names for parameters corresponding to the
+ * given array of types for the parameters. Each name in the
+ * returned array is guaranteed to be unique.
+ *
+ * A representation of the type of a parameter is included in its
+ * corresponding parameter name to enhance the readability of the
+ * generated code.
+ **/
+ private static String[] nameParameters(Type[] types) {
+ String[] names = new String[types.length];
+ for (int i = 0; i < names.length; i++) {
+ names[i] = "$param_" +
+ generateNameFromType(types[i]) + "_" + (i + 1);
+ }
+ return names;
+ }
+
+ /**
+ * Generates a readable string representing the given type
+ * suitable for embedding within a Java identifier.
+ **/
+ private static String generateNameFromType(Type type) {
+ String name = type.typeName().replace('.', '$');
+ int dimensions = type.dimension().length() / 2;
+ for (int i = 0; i < dimensions; i++) {
+ name = "arrayOf_" + name;
+ }
+ return name;
+ }
+
+ /**
+ * Writes a snippet of Java code to marshal a value named "name"
+ * of type "type" to the java.io.ObjectOutput stream named
+ * "stream".
+ *
+ * Primitive types are marshalled with their corresponding methods
+ * in the java.io.DataOutput interface, and objects (including
+ * arrays) are marshalled using the writeObject method.
+ **/
+ private static void writeMarshalArgument(IndentingWriter p,
+ String streamName,
+ Type type, String name)
+ throws IOException
+ {
+ if (type.dimension().length() > 0 || type.asClassDoc() != null) {
+ p.p(streamName + ".writeObject(" + name + ")");
+ } else if (type.typeName().equals("boolean")) {
+ p.p(streamName + ".writeBoolean(" + name + ")");
+ } else if (type.typeName().equals("byte")) {
+ p.p(streamName + ".writeByte(" + name + ")");
+ } else if (type.typeName().equals("char")) {
+ p.p(streamName + ".writeChar(" + name + ")");
+ } else if (type.typeName().equals("short")) {
+ p.p(streamName + ".writeShort(" + name + ")");
+ } else if (type.typeName().equals("int")) {
+ p.p(streamName + ".writeInt(" + name + ")");
+ } else if (type.typeName().equals("long")) {
+ p.p(streamName + ".writeLong(" + name + ")");
+ } else if (type.typeName().equals("float")) {
+ p.p(streamName + ".writeFloat(" + name + ")");
+ } else if (type.typeName().equals("double")) {
+ p.p(streamName + ".writeDouble(" + name + ")");
+ } else {
+ throw new AssertionError(type);
+ }
+ }
+
+ /**
+ * Writes Java statements to marshal a series of values in order
+ * as named in the "names" array, with types as specified in the
+ * "types" array, to the java.io.ObjectOutput stream named
+ * "stream".
+ **/
+ private static void writeMarshalArguments(IndentingWriter p,
+ String streamName,
+ Type[] types, String[] names)
+ throws IOException
+ {
+ assert types.length == names.length;
+
+ for (int i = 0; i < types.length; i++) {
+ writeMarshalArgument(p, streamName, types[i], names[i]);
+ p.pln(";");
+ }
+ }
+
+ /**
+ * Writes a snippet of Java code to unmarshal a value of type
+ * "type" from the java.io.ObjectInput stream named "stream" into
+ * a variable named "name" (if "name" is null, the value is
+ * unmarshalled and discarded).
+ *
+ * Primitive types are unmarshalled with their corresponding
+ * methods in the java.io.DataInput interface, and objects
+ * (including arrays) are unmarshalled using the readObject
+ * method.
+ *
+ * Returns true if code to invoke readObject was written, and
+ * false otherwise.
+ **/
+ private static boolean writeUnmarshalArgument(IndentingWriter p,
+ String streamName,
+ Type type, String name)
+ throws IOException
+ {
+ boolean readObject = false;
+
+ if (name != null) {
+ p.p(name + " = ");
+ }
+
+ if (type.dimension().length() > 0 || type.asClassDoc() != null) {
+ p.p("(" + type.toString() + ") " + streamName + ".readObject()");
+ readObject = true;
+ } else if (type.typeName().equals("boolean")) {
+ p.p(streamName + ".readBoolean()");
+ } else if (type.typeName().equals("byte")) {
+ p.p(streamName + ".readByte()");
+ } else if (type.typeName().equals("char")) {
+ p.p(streamName + ".readChar()");
+ } else if (type.typeName().equals("short")) {
+ p.p(streamName + ".readShort()");
+ } else if (type.typeName().equals("int")) {
+ p.p(streamName + ".readInt()");
+ } else if (type.typeName().equals("long")) {
+ p.p(streamName + ".readLong()");
+ } else if (type.typeName().equals("float")) {
+ p.p(streamName + ".readFloat()");
+ } else if (type.typeName().equals("double")) {
+ p.p(streamName + ".readDouble()");
+ } else {
+ throw new AssertionError(type);
+ }
+
+ return readObject;
+ }
+
+ /**
+ * Writes Java statements to unmarshal a series of values in order
+ * of types as in the "types" array from the java.io.ObjectInput
+ * stream named "stream" into variables as named in "names" (for
+ * any element of "names" that is null, the corresponding value is
+ * unmarshalled and discarded).
+ **/
+ private static boolean writeUnmarshalArguments(IndentingWriter p,
+ String streamName,
+ Type[] types,
+ String[] names)
+ throws IOException
+ {
+ assert types.length == names.length;
+
+ boolean readObject = false;
+ for (int i = 0; i < types.length; i++) {
+ if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
+ readObject = true;
+ }
+ p.pln(";");
+ }
+ return readObject;
+ }
+
+ /**
+ * Returns a snippet of Java code to wrap a value named "name" of
+ * type "type" into an object as appropriate for use by the Java
+ * Reflection API.
+ *
+ * For primitive types, an appropriate wrapper class is
+ * instantiated with the primitive value. For object types
+ * (including arrays), no wrapping is necessary, so the value is
+ * named directly.
+ **/
+ private static String wrapArgumentCode(Type type, String name) {
+ if (type.dimension().length() > 0 || type.asClassDoc() != null) {
+ return name;
+ } else if (type.typeName().equals("boolean")) {
+ return ("(" + name +
+ " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
+ } else if (type.typeName().equals("byte")) {
+ return "new java.lang.Byte(" + name + ")";
+ } else if (type.typeName().equals("char")) {
+ return "new java.lang.Character(" + name + ")";
+ } else if (type.typeName().equals("short")) {
+ return "new java.lang.Short(" + name + ")";
+ } else if (type.typeName().equals("int")) {
+ return "new java.lang.Integer(" + name + ")";
+ } else if (type.typeName().equals("long")) {
+ return "new java.lang.Long(" + name + ")";
+ } else if (type.typeName().equals("float")) {
+ return "new java.lang.Float(" + name + ")";
+ } else if (type.typeName().equals("double")) {
+ return "new java.lang.Double(" + name + ")";
+ } else {
+ throw new AssertionError(type);
+ }
+ }
+
+ /**
+ * Returns a snippet of Java code to unwrap a value named "name"
+ * into a value of type "type", as appropriate for the Java
+ * Reflection API.
+ *
+ * For primitive types, the value is assumed to be of the
+ * corresponding wrapper class, and a method is called on the
+ * wrapper to retrieve the primitive value. For object types
+ * (include arrays), no unwrapping is necessary; the value is
+ * simply cast to the expected real object type.
+ **/
+ private static String unwrapArgumentCode(Type type, String name) {
+ if (type.dimension().length() > 0 || type.asClassDoc() != null) {
+ return "((" + type.toString() + ") " + name + ")";
+ } else if (type.typeName().equals("boolean")) {
+ return "((java.lang.Boolean) " + name + ").booleanValue()";
+ } else if (type.typeName().equals("byte")) {
+ return "((java.lang.Byte) " + name + ").byteValue()";
+ } else if (type.typeName().equals("char")) {
+ return "((java.lang.Character) " + name + ").charValue()";
+ } else if (type.typeName().equals("short")) {
+ return "((java.lang.Short) " + name + ").shortValue()";
+ } else if (type.typeName().equals("int")) {
+ return "((java.lang.Integer) " + name + ").intValue()";
+ } else if (type.typeName().equals("long")) {
+ return "((java.lang.Long) " + name + ").longValue()";
+ } else if (type.typeName().equals("float")) {
+ return "((java.lang.Float) " + name + ").floatValue()";
+ } else if (type.typeName().equals("double")) {
+ return "((java.lang.Double) " + name + ").doubleValue()";
+ } else {
+ throw new AssertionError(type);
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java
new file mode 100644
index 000000000..a5e5f6926
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/newrmic/jrmp/Util.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.rmic.newrmic.jrmp;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.Type;
+
+/**
+ * Provides static utility methods.
+ *
+ * WARNING: The contents of this source file are not part of any
+ * supported API. Code that depends on them does so at its own risk:
+ * they are subject to change or removal without notice.
+ *
+ * @author Peter Jones
+ **/
+final class Util {
+
+ private Util() { throw new AssertionError(); }
+
+ /**
+ * Returns the binary name of the class or interface represented
+ * by the specified ClassDoc.
+ **/
+ static String binaryNameOf(ClassDoc cl) {
+ String flat = cl.name().replace('.', '$');
+ String packageName = cl.containingPackage().name();
+ return packageName.equals("") ? flat : packageName + "." + flat;
+ }
+
+ /**
+ * Returns the method descriptor for the specified method.
+ *
+ * See section 4.3.3 of The Java Virtual Machine Specification
+ * Second Edition for the definition of a "method descriptor".
+ **/
+ static String methodDescriptorOf(MethodDoc method) {
+ String desc = "(";
+ Parameter[] parameters = method.parameters();
+ for (int i = 0; i < parameters.length; i++) {
+ desc += typeDescriptorOf(parameters[i].type());
+ }
+ desc += ")" + typeDescriptorOf(method.returnType());
+ return desc;
+ }
+
+ /**
+ * Returns the descriptor for the specified type, as appropriate
+ * for either a parameter or return type in a method descriptor.
+ **/
+ private static String typeDescriptorOf(Type type) {
+ String desc;
+ ClassDoc classDoc = type.asClassDoc();
+ if (classDoc == null) {
+ /*
+ * Handle primitive types.
+ */
+ String name = type.typeName();
+ if (name.equals("boolean")) {
+ desc = "Z";
+ } else if (name.equals("byte")) {
+ desc = "B";
+ } else if (name.equals("char")) {
+ desc = "C";
+ } else if (name.equals("short")) {
+ desc = "S";
+ } else if (name.equals("int")) {
+ desc = "I";
+ } else if (name.equals("long")) {
+ desc = "J";
+ } else if (name.equals("float")) {
+ desc = "F";
+ } else if (name.equals("double")) {
+ desc = "D";
+ } else if (name.equals("void")) {
+ desc = "V";
+ } else {
+ throw new AssertionError(
+ "unrecognized primitive type: " + name);
+ }
+ } else {
+ /*
+ * Handle non-array reference types.
+ */
+ desc = "L" + binaryNameOf(classDoc).replace('.', '/') + ";";
+ }
+
+ /*
+ * Handle array types.
+ */
+ int dimensions = type.dimension().length() / 2;
+ for (int i = 0; i < dimensions; i++) {
+ desc = "[" + desc;
+ }
+
+ return desc;
+ }
+
+ /**
+ * Returns a reader-friendly string representation of the
+ * specified method's signature. Names of reference types are not
+ * package-qualified.
+ **/
+ static String getFriendlyUnqualifiedSignature(MethodDoc method) {
+ String sig = method.name() + "(";
+ Parameter[] parameters = method.parameters();
+ for (int i = 0; i < parameters.length; i++) {
+ if (i > 0) {
+ sig += ", ";
+ }
+ Type paramType = parameters[i].type();
+ sig += paramType.typeName() + paramType.dimension();
+ }
+ sig += ")";
+ return sig;
+ }
+
+ /**
+ * Returns true if the specified type is void.
+ **/
+ static boolean isVoid(Type type) {
+ return type.asClassDoc() == null && type.typeName().equals("void");
+ }
+}
diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic.properties b/src/share/classes/sun/rmi/rmic/resources/rmic.properties
new file mode 100644
index 000000000..662ffbf5d
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/resources/rmic.properties
@@ -0,0 +1,222 @@
+#
+#
+# Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+
+#*****************************************************************************
+#* Copyright (c) IBM Corporation 1998 *
+#* *
+#* (C) Copyright IBM Corp. 1998 *
+#* *
+#*****************************************************************************
+
+# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option:
+#
+# 1. Add "foo" to generator.args list.
+# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo
+# 3. Update rmic.usage string to include new arguments.
+
+# For each available generator, list the command line argument used
+# to invoke it. The value can be a single item or a comma separated
+# list.
+
+generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint
+
+# For each generator, specify the class to invoke, using the following
+# syntax:
+#
+# generator.class.{arg}=fullClassName
+#
+# The 'default' entry is required and will be used if none of the args
+# specified in generator.args is passed. Note that {arg} is compared
+# using String.equalsIgnoreCase().
+
+generator.class.default=sun.rmi.rmic.RMIGenerator
+
+generator.class.v1.1=sun.rmi.rmic.RMIGenerator
+generator.class.vcompat=sun.rmi.rmic.RMIGenerator
+generator.class.v1.2=sun.rmi.rmic.RMIGenerator
+generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator
+generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator
+generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator
+
+# If a generator needs a BatchEnvironment other than
+# sun.rmi.rmic.BatchEnvironment, specify it as follows:
+#
+# generator.env.{arg}=fullClassName
+
+generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment
+
+rmic.usage=Usage: {0} <options> <class names>\
+\n\
+\nwhere <options> includes:\
+\n -keep Do not delete intermediate generated source files\
+\n -keepgenerated (same as "-keep")\
+\n -v1.1 Create stubs/skeletons for 1.1 stub protocol version\
+\n -vcompat Create stubs/skeletons compatible with both\
+\n 1.1 and 1.2 stub protocol versions\
+\n -v1.2 (default) Create stubs for 1.2 stub protocol version only\
+\n -iiop Create stubs for IIOP. When present, <options> also includes:\
+\n\
+\n -always Create stubs even when they appear current\
+\n -alwaysgenerate (same as "-always")\
+\n -nolocalstubs Do not create stubs optimized for same process\
+\n\
+\n -idl Create IDL. When present, <options> also includes:\
+\n\
+\n -noValueMethods Do not generate methods for valuetypes \
+\n -always Create IDL even when it appears current\
+\n -alwaysgenerate (same as "-always")\
+\n\
+\n -g Generate debugging info\
+\n -nowarn Generate no warnings\
+\n -nowrite Do not write compiled classes to the file system\
+\n -verbose Output messages about what the compiler is doing\
+\n -classpath <path> Specify where to find input class files\
+\n -bootclasspath <path> Override location of bootstrap class files\
+\n -extdirs <path> Override location of installed extensions\
+\n -d <directory> Specify where to place generated class files\
+\n -J<runtime flag> Pass argument to the java interpreter
+\n\
+
+#
+# Generic Messages
+#
+
+rmic.cant.read=Can''t read: {0}
+rmic.cant.write=Can''t write: {0}
+rmic.option.unsupported=The {0} option is no longer supported.
+rmic.option.unimplemented=The {0} option is not yet implemented.
+rmic.option.already.seen=The {0} option may be specified no more than once.
+rmic.option.requires.argument=The {0} option requires an argument.
+rmic.no.such.directory=The {0} directory does not exist.
+rmic.no.such.option={0} is an invalid option or argument.
+rmic.wrote=[wrote {0}]
+rmic.errors={0} errors
+rmic.1error=1 error
+rmic.warnings={0} warnings
+rmic.1warning=1 warning
+rmic.done_in=[done in {0} ms]
+rmic.no.memory=\
+ The compiler has run out of memory. Consider using the "-J-Xmx<size>" command line option to increase the maximum heap size.
+rmic.stack.overflow=\
+ The compiler has run out of stack space. Consider using the "-J-Xss<size>" command line option to increase the memory allocated for the Java stack.
+rmic.class.not.found=\
+ Class {0} not found.
+rmic.missing.property=Missing property generator.class.{0}
+rmic.cannot.instantiate=Cannot instantiate class {0}
+rmic.cannot.use.both=Cannot use both {0} and {1}
+rmic.resource.not.found={0} not found.
+rmic.no.output.dir=\
+ Cannot find suitable output directory for {0}. Use the -d option to specify a root directory.
+rmic.cannot.create.dir=\
+ Cannot create output directory {0}.
+
+#
+# JRMP Messages
+#
+
+rmic.cant.make.stubs.for.interface=\
+ {0} is an interface; stubs are needed only for remote object classes.
+rmic.must.implement.remote=\
+ Class {0} does not implement an interface that extends java.rmi.Remote; only remote objects need stubs and skeletons.
+rmic.must.implement.remote.directly=\
+ Stubs are only needed for classes that directly implement an interface that extends java.rmi.Remote; class {0} does not directly implement a remote interface.
+rmic.must.throw.remoteexception=\
+ {0} is not a valid remote interface: method {1} must throw java.rmi.RemoteException.
+rmic.must.only.throw.exception=\
+ Method {0} is not a valid remote method implementation because it throws {1}; implementations of remote methods may only throw java.lang.Exception or its subclasses.
+warn.rmic.tie.found=\
+ An IIOP "tie" exists for class {0}:\
+ \n {1}\
+ \nIf you use PortableRemoteObject.exportObject, you should remove this file; otherwise, your server object will be exported to IIOP rather than to JRMP.
+
+#
+# RMI-IIOP Messages
+#
+
+rmic.generated=[generated {0} in {1} ms]
+rmic.previously.generated=[previously generated file {0} is current]
+warn.rmic.member.not.mapped=\
+ Data member {0} of class {1} was not mapped to IDL.
+
+rmic.iiop.constraint.1=\
+ {0} is not a valid interface: does not inherit from java.rmi.Remote.
+rmic.iiop.constraint.2=\
+ serialPersistentFields array of class {0} is invalid: references non-existent members.
+rmic.iiop.constraint.3=\
+ {0} is not a valid remote interface: {1} is not a valid primitive or String constant.
+rmic.iiop.constraint.4=\
+ {0} is not a valid value: serialPersistentFields must be private static final.
+rmic.iiop.constraint.5=\
+ {0} is not a valid remote interface: method {1} must throw RemoteException or a superclass of RemoteException.
+rmic.iiop.constraint.6=\
+ {0} is not a valid remote interface: inherited interfaces {1} both declare method {2}.
+rmic.iiop.constraint.7=\
+ {0} is not a valid type: {1} differ only in case.
+rmic.iiop.constraint.8=\
+ {0} is not a valid remote implementation: has no remote interfaces.
+rmic.iiop.constraint.9=\
+ serialPersistentFields array member {0} of class {1} is invalid: type does not match declared member.
+rmic.iiop.constraint.10=\
+ {0} is not a valid value: implements java.rmi.Remote.
+rmic.iiop.constraint.11=\
+ {0} is not a valid value: does not implement java.io.Serializable.
+rmic.iiop.constraint.12=\
+ {0} is not a valid value: invalid parent.
+rmic.iiop.constraint.13=\
+ {0} is not a valid interface: the idl name for method {1} conflicts with another method.
+rmic.iiop.constraint.14=\
+ {0} is not a valid abstract interface: not an interface.
+rmic.iiop.constraint.15=\
+ {0} is not a valid abstract interface: implements java.rmi.Remote.
+rmic.iiop.constraint.16=\
+ {0} is not a valid remote interface: not an interface.
+rmic.iiop.constraint.17=\
+ {0} is not a valid remote implementation: not a class.
+rmic.iiop.constraint.18=\
+ {0} is not a valid interface: method {1} may not pass an exception which implements org.omg.CORBA.portable.IDLEntity.
+rmic.iiop.constraint.19=\
+ {0} is not a valid interface: the idl name for constant {1} conflicts with another constant.
+rmic.iiop.constraint.20=\
+ {0} is not a valid class: the idl name for member {1} conflicts with another member.
+rmic.iiop.constraint.21=\
+ {0} is a remote implementation class and cannot be used as a method argument or return type in {1}.
+rmic.iiop.constraint.22=\
+ Internal failure: (Method) exception {0} not a class type.
+rmic.iiop.constraint.23=\
+ Internal failure: (Method) caught null pointer exception for {0}.
+rmic.iiop.constraint.24=\
+ Class {0} contains an invalid return type.
+rmic.iiop.constraint.25=\
+ Class {0} contains an invalid argument type in method {1}.
+rmic.iiop.constraint.26=\
+ Could not compile {0}.
+rmic.iiop.constraint.27=\
+ Could not load class {0}.
+rmic.iiop.constraint.28=\
+ {0} is a remote implementation class and cannot be used as a data member in {1}.
diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties b/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties
new file mode 100644
index 000000000..22d51a82d
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/resources/rmic_ja.properties
@@ -0,0 +1,221 @@
+#
+#
+# Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+
+#*****************************************************************************
+#* Copyright (c) IBM Corporation 1998 *
+#* *
+#* (C) Copyright IBM Corp. 1998 *
+#* *
+#*****************************************************************************
+
+# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option:
+#
+# 1. Add "foo" to generator.args list.
+# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo
+# 3. Update rmic.usage string to include new arguments.
+
+# For each available generator, list the command line argument used
+# to invoke it. The value can be a single item or a comma separated
+# list.
+
+generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint
+
+# For each generator, specify the class to invoke, using the following
+# syntax:
+#
+# generator.class.{arg}=fullClassName
+#
+# The 'default' entry is required and will be used if none of the args
+# specified in generator.args is passed. Note that {arg} is compared
+# using String.equalsIgnoreCase().
+
+generator.class.default=sun.rmi.rmic.RMIGenerator
+
+generator.class.v1.1=sun.rmi.rmic.RMIGenerator
+generator.class.vcompat=sun.rmi.rmic.RMIGenerator
+generator.class.v1.2=sun.rmi.rmic.RMIGenerator
+generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator
+generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator
+generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator
+
+# If a generator needs a BatchEnvironment other than
+# sun.rmi.rmic.BatchEnvironment, specify it as follows:
+#
+# generator.env.{arg}=fullClassName
+
+generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment
+
+rmic.usage=\u4f7f\u3044\u65b9: {0} <options> <class names>\
+\n\
+\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\
+\n -keep \u4e2d\u9593\u751f\u6210\u3055\u308c\u305f\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3057\u306a\u3044\
+\n -keepgenerated ("-keep" \u3068\u540c\u3058)\
+\n -v1.1 1.1 \u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u7528\u306e\u30b9\u30bf\u30d6/\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u4f5c\u6210\u3059\u308b\
+\n -vcompat 1.1 \u3068 1.2 \u306e\u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u3068\
+\n \u4e92\u63db\u6027\u306e\u3042\u308b\u30b9\u30bf\u30d6/\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u4f5c\u6210\u3059\u308b\
+\n -v1.2 (\u30c7\u30d5\u30a9\u30eb\u30c8) 1.2 \u30b9\u30bf\u30d6\u30d7\u30ed\u30c8\u30b3\u30eb\u7248\u5c02\u7528\u306e\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\
+\n -iiop IIOP \u7528\u306e\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\u3002\u6307\u5b9a\u3059\u308b\u3068 <options> \u306b\u306f\u6b21\u306e\u3082\u306e\u3082\u542b\u307e\u308c\u307e\u3059\u3002\
+\n\
+\n -always \u6700\u65b0\u306e\u5834\u5408\u3067\u3082\u30b9\u30bf\u30d6\u3092\u4f5c\u6210\u3059\u308b\
+\n -alwaysgenerate ("-always" \u3068\u540c\u3058)\
+\n -nolocalstubs \u540c\u3058\u30d7\u30ed\u30bb\u30b9\u306b\u3064\u3044\u3066\u6700\u9069\u5316\u3055\u308c\u305f\u30b9\u30bf\u30d6\u306f\u4f5c\u6210\u3057\u306a\u3044\
+\n\
+\n -idl IDL \u3092\u4f5c\u6210\u3059\u308b\u3002\u6307\u5b9a\u3059\u308b\u3068 <options> \u306b\u306f\u6b21\u306e\u3082\u306e\u3082\u542b\u307e\u308c\u307e\u3059\u3002\
+\n\
+\n -noValueMethods valuetypes \u306b\u5bfe\u3057\u3066\u30e1\u30bd\u30c3\u30c9\u3092\u751f\u6210\u3057\u306a\u3044 \
+\n -always \u6700\u65b0\u306e\u5834\u5408\u3067\u3082 IDL \u3092\u751f\u6210\u3059\u308b\
+\n -alwaysgenerate ("-always" \u3068\u540c\u3058)\
+\n\
+\n -g \u30c7\u30d0\u30c3\u30b0\u60c5\u5831\u3092\u751f\u6210\u3059\u308b\
+\n -nowarn \u8b66\u544a\u3092\u751f\u6210\u3057\u306a\u3044\
+\n -nowrite \u30b3\u30f3\u30d1\u30a4\u30eb\u3057\u305f\u30af\u30e9\u30b9\u3092\u30d5\u30a1\u30a4\u30eb\u30b7\u30b9\u30c6\u30e0\u306b\u66f8\u304d\u8fbc\u307e\u306a\u3044\
+\n -verbose \u30b3\u30f3\u30d1\u30a4\u30e9\u306e\u52d5\u4f5c\u306b\u95a2\u3059\u308b\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u51fa\u529b\u3059\u308b\
+\n -classpath <path> \u5165\u529b\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22\u3059\u308b\u4f4d\u7f6e\u3092\u6307\u5b9a\u3059\u308b\
+\n -bootclasspath <path> \u30d6\u30fc\u30c8\u30b9\u30c8\u30e9\u30c3\u30d7\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u306e\u4f4d\u7f6e\u3092\u7f6e\u304d\u63db\u3048\u308b\
+\n -extdirs <path> \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u6e08\u307f\u62e1\u5f35\u6a5f\u80fd\u306e\u4f4d\u7f6e\u3092\u7f6e\u304d\u63db\u3048\u308b\
+\n -d <directory> \u751f\u6210\u3055\u308c\u305f\u30af\u30e9\u30b9\u30d5\u30a1\u30a4\u30eb\u3092\u683c\u7d0d\u3059\u308b\u4f4d\u7f6e\u3092\u6307\u5b9a\u3059\u308b\
+\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059
+\n\
+
+#
+# Generic Messages
+#
+
+rmic.cant.read={0} \u3092\u8aad\u307f\u8fbc\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.cant.write={0} \u304c\u66f8\u304d\u8fbc\u3081\u307e\u305b\u3093\u3002
+rmic.option.unsupported=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306f\u73fe\u5728\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+rmic.option.unimplemented=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306f\u307e\u3060\u5b9f\u88c5\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+rmic.option.already.seen=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306e\u6307\u5b9a\u306f 1 \u56de\u3060\u3051\u3067\u3059\u3002
+rmic.option.requires.argument=\u30aa\u30d7\u30b7\u30e7\u30f3 {0} \u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059\u3002
+rmic.no.such.directory=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002
+rmic.no.such.option={0} \u306f\u4e0d\u6b63\u306a\u30aa\u30d7\u30b7\u30e7\u30f3\u307e\u305f\u306f\u5f15\u6570\u3067\u3059\u3002
+rmic.wrote=[{0} \u3092\u66f8\u304d\u8fbc\u307f\u307e\u3057\u305f]
+rmic.errors=\u30a8\u30e9\u30fc {0} \u500b
+rmic.1error=\u30a8\u30e9\u30fc 1 \u500b
+rmic.warnings=\u8b66\u544a {0} \u500b
+rmic.1warning=\u8b66\u544a 1 \u500b
+rmic.done_in=[{0} ms \u3067\u5b8c\u4e86]
+rmic.no.memory=\
+ \u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u30e1\u30e2\u30ea\u30fc\u304c\u4e0d\u8db3\u3057\u3066\u3044\u307e\u3059\u3002"-J-Xmx<size>" \u30b3\u30de\u30f3\u30c9\u884c\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u4f7f\u3063\u3066\u3001\u6700\u5927\u30d2\u30fc\u30d7\u30b5\u30a4\u30ba\u3092\u5897\u3084\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+rmic.stack.overflow=\
+ \u30b3\u30f3\u30d1\u30a4\u30e9\u306b\u30b9\u30bf\u30c3\u30af\u7a7a\u9593\u304c\u4e0d\u8db3\u3057\u3066\u3044\u307e\u3059\u3002"-J-Xss<size>" \u30b3\u30de\u30f3\u30c9\u884c\u30aa\u30d7\u30b7\u30e7\u30f3\u3092\u4f7f\u3063\u3066\u3001Java \u30b9\u30bf\u30c3\u30af\u306b\u5272\u308a\u5f53\u3066\u308b\u30e1\u30e2\u30ea\u30fc\u3092\u5897\u3084\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+rmic.class.not.found=\u30af\u30e9\u30b9 {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002
+rmic.missing.property=\u30d7\u30ed\u30d1\u30c6\u30a3 generator.class.{0} \u304c\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.cannot.instantiate=\u30af\u30e9\u30b9 {0} \u306e\u30a4\u30f3\u30b9\u30bf\u30f3\u30b9\u3092\u751f\u6210\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.cannot.use.both={0} \u3068 {1} \u306e\u4e21\u65b9\u3092\u4f7f\u3046\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.resource.not.found={0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002
+rmic.no.output.dir=\
+ {0} \u306b\u9069\u5207\u306a\u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002-d \u3092\u4f7f\u7528\u3057\u3066\u30eb\u30fc\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+rmic.cannot.create.dir=\
+ \u51fa\u529b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3002
+
+#
+# JRMP Messages
+#
+
+rmic.cant.make.stubs.for.interface=\
+ {0} \u306f\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30b9\u30bf\u30d6\u306f\u30ea\u30e2\u30fc\u30c8\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u30af\u30e9\u30b9\u3060\u3051\u306b\u5fc5\u8981\u3067\u3059\u3002
+rmic.must.implement.remote=\
+ \u30af\u30e9\u30b9 {0} \u306f java.rmi.Remote \u3092\u62e1\u5f35\u3059\u308b\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u5b9f\u88c5\u3057\u307e\u305b\u3093\u3002\u30b9\u30bf\u30d6\u3068\u30b9\u30b1\u30eb\u30c8\u30f3\u3092\u5fc5\u8981\u3068\u3059\u308b\u306e\u306f\u30ea\u30e2\u30fc\u30c8\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u3060\u3051\u3067\u3059\u3002
+rmic.must.implement.remote.directly=\
+ \u30b9\u30bf\u30d6\u3092\u5fc5\u8981\u3068\u3059\u308b\u306e\u306f java.rmi.Remote \u3092\u62e1\u5f35\u3059\u308b\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u76f4\u63a5\u5b9f\u88c5\u3059\u308b\u30af\u30e9\u30b9\u3060\u3051\u3067\u3059\u3002\n\t\u30af\u30e9\u30b9 {0} \u306f\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u76f4\u63a5\u306b\u306f\u5b9f\u88c5\u3057\u307e\u305b\u3093\u3002
+rmic.must.throw.remoteexception=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f java.rmi.RemoteException \u3092\u30b9\u30ed\u30fc\u3057\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+rmic.must.only.throw.exception=\
+ \u30e1\u30bd\u30c3\u30c9 {0} \u306f {1} \u3092\u30b9\u30ed\u30fc\u3059\u308b\u306e\u3067\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30e1\u30bd\u30c3\u30c9\u5b9f\u88c5\u3067\u3059\u3002\u30ea\u30e2\u30fc\u30c8\u30e1\u30bd\u30c3\u30c9\u306e\u5b9f\u88c5\u304c\u30b9\u30ed\u30fc\u3059\u308b\u306e\u306f java.lang.Exception \u304b\u305d\u306e\u30b5\u30d6\u30af\u30e9\u30b9\u3060\u3051\u3067\u3059\u3002
+warn.rmic.tie.found=\
+ IIOP "tie" \u306f\u30af\u30e9\u30b9 {0} \u306e\u305f\u3081\u306b\u3042\u308a\u307e\u3059\u3002\
+ \n {1}\
+ \nPortableRemoteObject.exportObject \u3092\u4f7f\u3046\u5834\u5408\u306f\u3053\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u524a\u9664\u3057\u307e\u3059\u3002\u524a\u9664\u3057\u306a\u3044\u3068\u3001\u30b5\u30fc\u30d0\u30fc\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306f JRMP \u3067\u306a\u304f IIOP \u306b\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3055\u308c\u307e\u3059\u3002
+
+#
+# RMI-IIOP Messages
+#
+
+rmic.generated=[{1} ms \u3067 {0} \u3092\u751f\u6210]
+rmic.previously.generated=[\u4ee5\u524d\u306b\u751f\u6210\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb {0} \u306f\u6700\u65b0\u3067\u3059]
+warn.rmic.member.not.mapped=\
+ \u30af\u30e9\u30b9 {1} \u306e\u30c7\u30fc\u30bf\u30e1\u30f3\u30d0\u30fc {0} \u306f IDL \u306b\u30de\u30c3\u30d7\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+
+rmic.iiop.constraint.1=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002java.rmi.Remote \u304b\u3089\u306f\u7d99\u627f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.2=\
+ \u30af\u30e9\u30b9 {0} \u306e serialPersistentFields \u914d\u5217\u306f\u4e0d\u6b63\u3067\u3059\u3002\u5b58\u5728\u3057\u306a\u3044\u30e1\u30f3\u30d0\u30fc\u3092\u53c2\u7167\u3057\u3066\u3044\u307e\u3059\u3002
+rmic.iiop.constraint.3=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002{1} \u306f\u6b63\u3057\u3044\u30d7\u30ea\u30df\u30c6\u30a3\u30d6\u307e\u305f\u306f String \u5b9a\u6570\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.4=\
+ {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002serialPersistentFields \u306f private static final \u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.5=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f RemoteException \u307e\u305f\u306f RemoteException \u306e\u30b9\u30fc\u30d1\u30fc\u30af\u30e9\u30b9\u3092\u30b9\u30ed\u30fc\u3059\u3079\u304d\u3067\u3059\u3002
+rmic.iiop.constraint.6=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u7d99\u627f\u3055\u308c\u305f\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9 {1} \u3082\u4e21\u65b9\u30e1\u30bd\u30c3\u30c9 {2} \u3092\u5ba3\u8a00\u3057\u3066\u3044\u307e\u3059\u3002
+rmic.iiop.constraint.7=\
+ {0} \u306f\u4e0d\u6b63\u306a\u578b\u3067\u3059\u3002{1} \u306f\u7279\u5225\u306a\u5834\u5408\u306b\u306e\u307f\u7570\u306a\u308a\u307e\u3059\u3002
+rmic.iiop.constraint.8=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u3067\u3059\u3002\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.9=\
+ \u30af\u30e9\u30b9 {1} \u306e serialPersistentFields \u914d\u5217\u30e1\u30f3\u30d0\u30fc {0} \u306f\u4e0d\u6b63\u3067\u3059\u3002\u578b\u304c\u5ba3\u8a00\u3055\u308c\u305f\u30e1\u30f3\u30d0\u30fc\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.10=\
+ {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002java.rmi.Remote \u3092\u5b9f\u88c5\u3057\u307e\u3059\u3002
+rmic.iiop.constraint.11=\
+ {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002java.io.Serializable \u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.12=\
+ {0} \u306f\u4e0d\u6b63\u306a\u5024\u3067\u3059\u3002\u4e0d\u6b63\u306a\u89aa\u3067\u3059\u3002
+rmic.iiop.constraint.13=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306e idl \u540d\u304c\u4ed6\u306e\u30e1\u30bd\u30c3\u30c9\u3068\u7af6\u5408\u3057\u307e\u3059\u3002
+rmic.iiop.constraint.14=\
+ {0} \u306f\u4e0d\u6b63\u306a abstract \u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.15=\
+ {0} \u306f\u4e0d\u6b63\u306a abstract \u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002java.rmi.Remote \u3092\u5b9f\u88c5\u3057\u3066\u3044\u307e\u3059\u3002
+rmic.iiop.constraint.16=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3001\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.17=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u3067\u3059\u3002\u30af\u30e9\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.18=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u30e1\u30bd\u30c3\u30c9 {1} \u306f org.omg.CORBA.portable.IDLEntity \u3092\u5b9f\u88c5\u3059\u308b\u4f8b\u5916\u3092\u6e21\u3057\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.19=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u3067\u3059\u3002\u5b9a\u6570 {1} \u306e idl \u540d\u304c\u4ed6\u306e\u5b9a\u6570\u3068\u7af6\u5408\u3057\u307e\u3059\u3002
+rmic.iiop.constraint.20=\
+ {0} \u306f\u4e0d\u6b63\u306a\u30af\u30e9\u30b9\u3067\u3059\u3002\u30e1\u30f3\u30d0\u30fc {1} \u306e idl \u540d\u304c\u4ed6\u306e\u30e1\u30f3\u30d0\u30fc\u3068\u7af6\u5408\u3057\u307e\u3059\u3002
+rmic.iiop.constraint.21=\
+ {0} \u306f\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u30af\u30e9\u30b9\u3067\u3042\u308a\u3001{1} \u306e\u30e1\u30bd\u30c3\u30c9\u5f15\u6570\u3042\u308b\u3044\u306f\u623b\u308a\u578b\u3068\u3057\u3066\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.22=\
+ \u5185\u90e8\u969c\u5bb3: (\u30e1\u30bd\u30c3\u30c9) \u4f8b\u5916 {0} \u306f\u30af\u30e9\u30b9\u578b\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.23=\
+ \u5185\u90e8\u969c\u5bb3: (\u30e1\u30bd\u30c3\u30c9) \u306f {0} \u306e null \u30dd\u30a4\u30f3\u30bf\u4f8b\u5916\u3092\u30ad\u30e3\u30c3\u30c1\u3057\u307e\u3057\u305f\u3002
+rmic.iiop.constraint.24=\
+ \u30af\u30e9\u30b9 {0} \u306f\u4e0d\u6b63\u306a\u623b\u308a\u578b\u3092\u542b\u3093\u3067\u3044\u307e\u3059\u3002
+rmic.iiop.constraint.25=\
+ \u30af\u30e9\u30b9 {0} \u306f\u30e1\u30bd\u30c3\u30c9 {1} \u306b\u4e0d\u6b63\u306a\u5f15\u6570\u578b\u3092\u542b\u3093\u3067\u3044\u307e\u3059\u3002
+rmic.iiop.constraint.26=\
+ {0} \u3092\u30b3\u30f3\u30d1\u30a4\u30eb\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.27=\
+ \u30af\u30e9\u30b9 {0} \u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002
+rmic.iiop.constraint.28=\
+ {0} \u306f\u30ea\u30e2\u30fc\u30c8\u5b9f\u88c5\u30af\u30e9\u30b9\u3067\u3042\u308a\u3001{1} \u306e\u30c7\u30fc\u30bf\u30e1\u30f3\u30d0\u30fc\u3068\u3057\u3066\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002
diff --git a/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties b/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties
new file mode 100644
index 000000000..2fd51a021
--- /dev/null
+++ b/src/share/classes/sun/rmi/rmic/resources/rmic_zh_CN.properties
@@ -0,0 +1,222 @@
+#
+#
+# Copyright 2005-2007 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+
+#*****************************************************************************
+#* Copyright (c) IBM Corporation 1998 *
+#* *
+#* (C) Copyright IBM Corp. 1998 *
+#* *
+#*****************************************************************************
+
+# To add a generator sun.rmi.rmic.Foo which is invoked via the -foo option:
+#
+# 1. Add "foo" to generator.args list.
+# 2. Add line: generator.class.foo=sun.rmi.rmic.Foo
+# 3. Update rmic.usage string to include new arguments.
+
+# For each available generator, list the command line argument used
+# to invoke it. The value can be a single item or a comma separated
+# list.
+
+generator.args=v1.1,vcompat,v1.2,iiop,idl,xprint
+
+# For each generator, specify the class to invoke, using the following
+# syntax:
+#
+# generator.class.{arg}=fullClassName
+#
+# The 'default' entry is required and will be used if none of the args
+# specified in generator.args is passed. Note that {arg} is compared
+# using String.equalsIgnoreCase().
+
+generator.class.default=sun.rmi.rmic.RMIGenerator
+
+generator.class.v1.1=sun.rmi.rmic.RMIGenerator
+generator.class.vcompat=sun.rmi.rmic.RMIGenerator
+generator.class.v1.2=sun.rmi.rmic.RMIGenerator
+generator.class.iiop=sun.rmi.rmic.iiop.StubGenerator
+generator.class.idl=sun.rmi.rmic.iiop.IDLGenerator
+generator.class.xprint=sun.rmi.rmic.iiop.PrintGenerator
+
+# If a generator needs a BatchEnvironment other than
+# sun.rmi.rmic.BatchEnvironment, specify it as follows:
+#
+# generator.env.{arg}=fullClassName
+
+generator.env.iiop=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.idl=sun.rmi.rmic.iiop.BatchEnvironment
+generator.env.xprint=sun.rmi.rmic.iiop.BatchEnvironment
+
+rmic.usage=\u7528\u6cd5\uff1a{0} <\u9009\u9879> <\u7c7b\u540d\u79f0>\
+\n\
+\n\u5176\u4e2d <\u9009\u9879> \u5305\u62ec\uff1a\
+\n -keep \u4e0d\u5220\u9664\u4e2d\u95f4\u751f\u6210\u7684\u6e90\u6587\u4ef6\
+\n -keepgenerated\uff08\u4e0e "-keep" \u76f8\u540c\uff09\
+\n -v1.1 \u521b\u5efa 1.1 \u5b58\u6839\u534f\u8bae\u7248\u672c\u7684\u5b58\u6839/\u6846\u67b6\
+\n -vcompat \u521b\u5efa\u4e0e 1.1 \u548c 1.2 \u5b58\u6839\u534f\u8bae\u7248\u672c\u90fd\u517c\u5bb9\u7684\
+\n \u5b58\u6839/\u6846\u67b6\
+\n -v1.2 \uff08\u9ed8\u8ba4\u503c\uff09\u4ec5\u521b\u5efa 1.2 \u5b58\u6839\u534f\u8bae\u7248\u672c\u7684\u5b58\u6839\
+\n -iiop \u521b\u5efa IIOP \u7684\u5b58\u6839\u3002\u4f7f\u7528\u6b64\u9009\u9879\u65f6\uff0c<\u9009\u9879> \u8fd8\u5305\u62ec\uff1a\
+\n\
+\n -always \u5373\u4f7f\u5f53\u524d\u663e\u793a\u5b58\u6839\uff0c\u4ecd\u521b\u5efa\u5b83\u4eec\
+\n -alwaysgenerate \uff08\u4e0e "-always" \u76f8\u540c\uff09\
+\n -nolocalstubs \u4e0d\u521b\u5efa\u4e3a\u76f8\u540c\u8fdb\u7a0b\u4f18\u5316\u7684\u5b58\u6839\
+\n\
+\n -idl \u521b\u5efa IDL\u3002\u4f7f\u7528\u6b64\u9009\u9879\u65f6\uff0c<\u9009\u9879> \u8fd8\u5305\u62ec\uff1a\
+\n\
+\n -noValueMethods \u4e0d\u751f\u6210 valuetypes \u7684\u65b9\u6cd5\
+\n -always \u5373\u4f7f\u5f53\u524d\u663e\u793a IDL\uff0c\u4ecd\u521b\u5efa\u5b83\
+\n -alwaysgenerate \uff08\u4e0e "-always" \u76f8\u540c\uff09\
+\n\
+\n -g \u751f\u6210\u8c03\u8bd5\u4fe1\u606f\
+\n -nowarn \u4e0d\u751f\u6210\u4efb\u4f55\u8b66\u544a\
+\n -nowrite \u4e0d\u5411\u6587\u4ef6\u7cfb\u7edf\u5199\u5165\u7f16\u8bd1\u7684\u7c7b\
+\n -verbose \u8f93\u51fa\u6709\u5173\u7f16\u8bd1\u5668\u6b63\u5728\u6267\u884c\u7684\u64cd\u4f5c\u7684\u6d88\u606f\
+\n -classpath <\u8def\u5f84> \u6307\u5b9a\u67e5\u627e\u8f93\u5165\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\
+\n -bootclasspath <\u8def\u5f84> \u8986\u76d6\u5f15\u5bfc\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\
+\n -extdirs <\u8def\u5f84> \u8986\u76d6\u5b89\u88c5\u7684\u6269\u5c55\u76ee\u5f55\u7684\u4f4d\u7f6e\
+\n -d <\u76ee\u5f55> \u6307\u5b9a\u5b58\u653e\u751f\u6210\u7684\u7c7b\u6587\u4ef6\u7684\u4f4d\u7f6e\
+\n -J <\u8fd0\u884c\u65f6\u6807\u5fd7> \u5411 java \u89e3\u91ca\u7a0b\u5e8f\u4f20\u9012\u53c2\u6570
+\n\
+
+#
+# Generic Messages
+#
+
+rmic.cant.read=\u65e0\u6cd5\u8bfb\u53d6\uff1a{0}
+rmic.cant.write=\u65e0\u6cd5\u5199\u5165\uff1a{0}
+rmic.option.unsupported=\u4e0d\u518d\u652f\u6301 {0} \u9009\u9879\u3002
+rmic.option.unimplemented=\u4ecd\u672a\u5b9e\u73b0 {0} \u9009\u9879\u3002
+rmic.option.already.seen={0} \u9009\u9879\u53ea\u80fd\u88ab\u6307\u5b9a\u4e00\u6b21\u3002
+rmic.option.requires.argument=\u9009\u9879 {0} \u9700\u8981\u53c2\u6570\u3002
+rmic.no.such.directory={0} \u76ee\u5f55\u4e0d\u5b58\u5728\u3002
+rmic.no.such.option={0} \u4e3a\u65e0\u6548\u9009\u9879\u6216\u53c2\u6570\u3002
+rmic.wrote=[\u5df2\u5199\u5165 {0}]
+rmic.errors={0} \u4e2a\u9519\u8bef
+rmic.1error=1 \u4e2a\u9519\u8bef
+rmic.warnings={0} \u4e2a\u8b66\u544a
+rmic.1warning=1 \u4e2a\u8b66\u544a
+rmic.done_in=[\u5728 {0} \u6beb\u79d2\u5185\u5b8c\u6210]
+rmic.no.memory=\
+ \u7f16\u8bd1\u5668\u6240\u7528\u5185\u5b58\u4e0d\u8db3\u3002\u8bf7\u8003\u8651\u4f7f\u7528\u201c-J-Xmx<\u5927\u5c0f>\u201d\u547d\u4ee4\u884c\u9009\u9879\u6765\u589e\u52a0\u5806\u5927\u5c0f\u7684\u6700\u5927\u503c\u3002
+rmic.stack.overflow=\
+ \u7f16\u8bd1\u5668\u6240\u7528\u5806\u6808\u7a7a\u95f4\u4e0d\u8db3\u3002\u8bf7\u8003\u8651\u4f7f\u7528\u201c-J-Xss<\u5927\u5c0f>\u201d\u547d\u4ee4\u884c\u9009\u9879\u6765\u589e\u52a0\u5206\u914d\u7ed9 Java \u5806\u6808\u7684\u5185\u5b58\u5927\u5c0f\u3002
+rmic.class.not.found=\
+ \u672a\u627e\u5230\u7c7b {0}\u3002
+rmic.missing.property=\u7f3a\u5c11\u5c5e\u6027 generator.class.{0}
+rmic.cannot.instantiate=\u65e0\u6cd5\u5b9e\u4f8b\u5316\u7c7b {0}
+rmic.cannot.use.both=\u65e0\u6cd5\u540c\u65f6\u4f7f\u7528 {0} \u548c {1}
+rmic.resource.not.found=\u672a\u627e\u5230 {0}\u3002
+rmic.no.output.dir=\
+ \u65e0\u6cd5\u627e\u5230\u9002\u7528\u4e8e {0} \u7684\u8f93\u51fa\u76ee\u5f55\u3002\u8bf7\u4f7f\u7528 -d \u9009\u9879\u6765\u6307\u5b9a\u6839\u76ee\u5f55\u3002
+rmic.cannot.create.dir=\
+ \u65e0\u6cd5\u521b\u5efa\u8f93\u51fa\u76ee\u5f55 {0}\u3002
+
+#
+# JRMP Messages
+#
+
+rmic.cant.make.stubs.for.interface=\
+ {0} \u662f\u63a5\u53e3\uff1b\u4ec5\u8fdc\u7a0b\u5bf9\u8c61\u7c7b\u9700\u8981\u5b58\u6839\u3002
+rmic.must.implement.remote=\
+ \u7c7b {0} \u4e0d\u5b9e\u73b0\u6269\u5c55 java.rmi.Remote \u7684\u63a5\u53e3\uff1b\u4ec5\u8fdc\u7a0b\u5bf9\u8c61\u9700\u8981\u5b58\u6839\u548c\u6846\u67b6\u3002
+rmic.must.implement.remote.directly=\
+ \u4ec5\u76f4\u63a5\u5b9e\u73b0\u6269\u5c55 java.rmi.Remote \u7684\u63a5\u53e3\u7684\u7c7b\u9700\u8981\u5b58\u6839\uff1b\u7c7b {0} \u4e0d\u76f4\u63a5\u5b9e\u73b0\u8fdc\u7a0b\u63a5\u53e3\u3002
+rmic.must.throw.remoteexception=\
+ {0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u5fc5\u987b\u629b\u51fa java.rmi.RemoteException\u3002
+rmic.must.only.throw.exception=\
+ \u65b9\u6cd5 {0} \u4e0d\u662f\u6709\u6548\u7684\u8fdc\u7a0b\u65b9\u6cd5\u5b9e\u73b0\uff0c\u56e0\u4e3a\u5b83\u629b\u51fa\u4e86 {1}\uff1b\u8fdc\u7a0b\u65b9\u6cd5\u5b9e\u73b0\u53ea\u53ef\u80fd\u629b\u51fa java.lang.Exception \u6216\u5176\u5b50\u7c7b\u3002
+warn.rmic.tie.found=\
+ \u5bf9\u4e8e\u7c7b {0}\uff0c\u5b58\u5728 IIOP "tie"\uff1a\
+ \n {1}\
+ \n\u5982\u679c\u4f7f\u7528 PortableRemoteObject.exportObject\uff0c\u5219\u5e94\u8be5\u5220\u9664\u6b64\u6587\u4ef6\uff0c\u5426\u5219\uff0c\u60a8\u7684\u670d\u52a1\u5668\u5bf9\u8c61\u5c06\u4f1a\u88ab\u5bfc\u51fa\u5230 IIOP \u800c\u975e JRMP\u3002
+
+#
+# RMI-IIOP Messages
+#
+
+rmic.generated=[\u5728 {1} \u6beb\u79d2\u5185\u751f\u6210\u7684 {0}]
+rmic.previously.generated=[\u4ee5\u524d\u751f\u6210\u7684\u6587\u4ef6 {0} \u4e3a\u5f53\u524d\u6587\u4ef6]
+warn.rmic.member.not.mapped=\
+\u7c7b {1} \u7684\u6570\u636e\u6210\u5458 {0} \u672a\u6620\u5c04\u5230 IDL\u3002
+
+rmic.iiop.constraint.1=\
+{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u6ca1\u6709\u4ece java.rmi.Remote \u7ee7\u627f\u3002
+rmic.iiop.constraint.2=\
+\u7c7b {0} \u7684 serialPersistentFields \u6570\u7ec4\u65e0\u6548\uff1a\u5f15\u7528\u4e86\u4e0d\u5b58\u5728\u7684\u6210\u5458\u3002
+rmic.iiop.constraint.3=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a{1} \u4e0d\u662f\u6709\u6548\u7684\u539f\u59cb\u6216\u5b57\u7b26\u4e32\u5e38\u91cf\u3002
+rmic.iiop.constraint.4=\
+{0} \u4e0d\u662f\u6709\u6548\u503c\uff1aserialPersistentFields \u5fc5\u987b\u4e3a\u4e13\u7528\u9759\u6001\u6700\u7ec8\u7c7b\u578b\u3002
+rmic.iiop.constraint.5=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u5fc5\u987b\u629b\u51fa RemoteException \u6216 RemoteException \u7684\u7236\u7c7b\u3002
+rmic.iiop.constraint.6=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u7ee7\u627f\u7684\u63a5\u53e3 {1} \u5747\u58f0\u660e\u4e86\u65b9\u6cd5 {2}\u3002
+rmic.iiop.constraint.7=\
+{0} \u4e0d\u662f\u6709\u6548\u7c7b\u578b\uff1a{1} \u4ec5\u5728\u5927\u5c0f\u5199\u4e0a\u4e0d\u540c\u3002
+rmic.iiop.constraint.8=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u5b9e\u73b0\uff1a\u4e0d\u5177\u6709\u8fdc\u7a0b\u63a5\u53e3\u3002
+rmic.iiop.constraint.9=\
+\u7c7b {1} \u7684 serialPersistentFields \u6570\u7ec4\u6210\u5458 {0} \u65e0\u6548\uff1a\u7c7b\u578b\u4e0e\u58f0\u660e\u7684\u6210\u5458\u4e0d\u5339\u914d\u3002
+rmic.iiop.constraint.10=\
+{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u5b9e\u73b0 java.rmi.Remote\u3002
+rmic.iiop.constraint.11=\
+{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u4e0d\u5b9e\u73b0 java.io.Serializable\u3002
+rmic.iiop.constraint.12=\
+{0} \u4e0d\u662f\u6709\u6548\u503c\uff1a\u7236\u503c\u65e0\u6548\u3002
+rmic.iiop.constraint.13=\
+{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u65b9\u6cd5\u51b2\u7a81\u3002
+rmic.iiop.constraint.14=\
+{0} \u4e0d\u662f\u6709\u6548\u62bd\u8c61\u63a5\u53e3\uff1a\u4e0d\u662f\u63a5\u53e3\u3002
+rmic.iiop.constraint.15=\
+{0} \u4e0d\u662f\u6709\u6548\u62bd\u8c61\u63a5\u53e3\uff1a\u5b9e\u73b0 java.rmi.Remote\u3002
+rmic.iiop.constraint.16=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u63a5\u53e3\uff1a\u4e0d\u662f\u63a5\u53e3\u3002
+rmic.iiop.constraint.17=\
+{0} \u4e0d\u662f\u6709\u6548\u8fdc\u7a0b\u5b9e\u73b0\uff1a\u4e0d\u662f\u7c7b\u3002
+rmic.iiop.constraint.18=\
+{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u65b9\u6cd5 {1} \u4e0d\u80fd\u4f20\u9012\u5b9e\u73b0 org.omg.CORBA.portable.IDLEntity \u7684\u5f02\u5e38\u3002
+rmic.iiop.constraint.19=\
+{0} \u4e0d\u662f\u6709\u6548\u63a5\u53e3\uff1a\u5e38\u91cf {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u5e38\u91cf\u51b2\u7a81\u3002
+rmic.iiop.constraint.20=\
+{0} \u4e0d\u662f\u6709\u6548\u7c7b\uff1a\u6210\u5458 {1} \u7684 idl \u540d\u79f0\u4e0e\u5176\u4ed6\u6210\u5458\u51b2\u7a81\u3002
+rmic.iiop.constraint.21=\
+{0} \u662f\u8fdc\u7a0b\u5b9e\u73b0\u7c7b\u5e76\u4e14\u4e0d\u80fd\u7528\u4f5c {1} \u4e2d\u7684\u65b9\u6cd5\u53c2\u6570\u6216\u8fd4\u56de\u7c7b\u578b\u3002
+rmic.iiop.constraint.22=\
+\u5185\u90e8\u5931\u8d25\uff1a\uff08\u65b9\u6cd5\uff09\u5f02\u5e38 {0} \u4e0d\u662f\u7c7b\u7c7b\u578b\u3002
+rmic.iiop.constraint.23=\
+\u5185\u90e8\u5931\u8d25\uff1a\uff08\u65b9\u6cd5\uff09\u6355\u6349\u5230 {0} \u7684\u7a7a\u6307\u9488\u5f02\u5e38\u3002
+rmic.iiop.constraint.24=\
+\u7c7b {0} \u5305\u542b\u65e0\u6548\u8fd4\u56de\u7c7b\u578b\u3002
+rmic.iiop.constraint.25=\
+\u7c7b {0} \u5305\u542b\u65b9\u6cd5 {1} \u4e2d\u7684\u65e0\u6548\u53c2\u6570\u7c7b\u578b\u3002
+rmic.iiop.constraint.26=\
+\u65e0\u6cd5\u7f16\u8bd1 {0}\u3002
+rmic.iiop.constraint.27=\
+\u65e0\u6cd5\u88c5\u5165\u7c7b {0}\u3002
+rmic.iiop.constraint.28=\
+{0} \u662f\u8fdc\u7a0b\u5b9e\u73b0\u7c7b\u5e76\u4e14\u65e0\u6cd5\u7528\u4f5c {1} \u4e2d\u7684\u6570\u636e\u6210\u5458\u3002
diff --git a/src/share/classes/sun/rmi/runtime/Log.java b/src/share/classes/sun/rmi/runtime/Log.java
new file mode 100644
index 000000000..3eb8ce9cf
--- /dev/null
+++ b/src/share/classes/sun/rmi/runtime/Log.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2001-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.runtime;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.OutputStream;
+import java.rmi.server.LogStream;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Formatter;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.StreamHandler;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Utility which provides an abstract "logger" like RMI internal API
+ * which can be directed to use one of two types of logging
+ * infrastructure: the java.util.logging API or the
+ * java.rmi.server.LogStream API. The default behavior is to use the
+ * java.util.logging API. The LogStream API may be used instead by
+ * setting the system property sun.rmi.log.useOld to true.
+ *
+ * For backwards compatibility, supports the RMI system logging
+ * properties which pre-1.4 comprised the only way to configure RMI
+ * logging. If the java.util.logging API is used and RMI system log
+ * properties are set, the system properties override initial RMI
+ * logger values as appropriate. If the java.util.logging API is
+ * turned off, pre-1.4 logging behavior is used.
+ *
+ * @author Laird Dornin
+ * @since 1.4
+ */
+public abstract class Log {
+
+ /** Logger re-definition of old RMI log values */
+ public static final Level BRIEF = Level.FINE;
+ public static final Level VERBOSE = Level.FINER;
+
+ /* selects log implementation */
+ private static final LogFactory logFactory;
+ static {
+ boolean useOld =
+ Boolean.valueOf((String) java.security.AccessController.
+ doPrivileged(new sun.security.action.GetPropertyAction(
+ "sun.rmi.log.useOld"))).booleanValue();
+
+ /* set factory to select the logging facility to use */
+ logFactory = (useOld ? (LogFactory) new LogStreamLogFactory() :
+ (LogFactory) new LoggerLogFactory());
+ }
+
+ /** "logger like" API to be used by RMI implementation */
+ public abstract boolean isLoggable(Level level);
+ public abstract void log(Level level, String message);
+ public abstract void log(Level level, String message, Throwable thrown);
+
+ /** get and set the RMI server call output stream */
+ public abstract void setOutputStream(OutputStream stream);
+ public abstract PrintStream getPrintStream();
+
+ /** factory interface enables Logger and LogStream implementations */
+ private static interface LogFactory {
+ Log createLog(String loggerName, String oldLogName, Level level);
+ }
+
+ /* access log objects */
+
+ /**
+ * Access log for a tri-state system property.
+ *
+ * Need to first convert override value to a log level, taking
+ * care to interpret a range of values between BRIEF, VERBOSE and
+ * SILENT.
+ *
+ * An override < 0 is interpreted to mean that the logging
+ * configuration should not be overridden. The level passed to the
+ * factories createLog method will be null in this case.
+ *
+ * Note that if oldLogName is null and old logging is on, the
+ * returned LogStreamLog will ignore the override parameter - the
+ * log will never log messages. This permits new logs that only
+ * write to Loggers to do nothing when old logging is active.
+ *
+ * Do not call getLog multiple times on the same logger name.
+ * Since this is an internal API, no checks are made to ensure
+ * that multiple logs do not exist for the same logger.
+ */
+ public static Log getLog(String loggerName, String oldLogName,
+ int override)
+ {
+ Level level;
+
+ if (override < 0) {
+ level = null;
+ } else if (override == LogStream.SILENT) {
+ level = Level.OFF;
+ } else if ((override > LogStream.SILENT) &&
+ (override <= LogStream.BRIEF)) {
+ level = BRIEF;
+ } else if ((override > LogStream.BRIEF) &&
+ (override <= LogStream.VERBOSE))
+ {
+ level = VERBOSE;
+ } else {
+ level = Level.FINEST;
+ }
+ return logFactory.createLog(loggerName, oldLogName, level);
+ }
+
+ /**
+ * Access logs associated with boolean properties
+ *
+ * Do not call getLog multiple times on the same logger name.
+ * Since this is an internal API, no checks are made to ensure
+ * that multiple logs do not exist for the same logger.
+ */
+ public static Log getLog(String loggerName, String oldLogName,
+ boolean override)
+ {
+ Level level = (override ? VERBOSE : null);
+ return logFactory.createLog(loggerName, oldLogName, level);
+ }
+
+ /**
+ * Factory to create Log objects which deliver log messages to the
+ * java.util.logging API.
+ */
+ private static class LoggerLogFactory implements LogFactory {
+ LoggerLogFactory() {}
+
+ /*
+ * Accessor to obtain an arbitrary RMI logger with name
+ * loggerName. If the level of the logger is greater than the
+ * level for the system property with name, the logger level
+ * will be set to the value of system property.
+ */
+ public Log createLog(final String loggerName, String oldLogName,
+ final Level level)
+ {
+ Logger logger = Logger.getLogger(loggerName);
+ return new LoggerLog(logger, level);
+ }
+ }
+
+ /**
+ * Class specialized to log messages to the java.util.logging API
+ */
+ private static class LoggerLog extends Log {
+
+ /* alternate console handler for RMI loggers */
+ private static final Handler alternateConsole = (Handler)
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ InternalStreamHandler alternate =
+ new InternalStreamHandler(System.err);
+ alternate.setLevel(Level.ALL);
+ return alternate;
+ }
+ }
+ );
+
+ /** handler to which messages are copied */
+ private InternalStreamHandler copyHandler = null;
+
+ /* logger to which log messages are written */
+ private final Logger logger;
+
+ /* used as return value of RemoteServer.getLog */
+ private LoggerPrintStream loggerSandwich;
+
+ /** creates a Log which will delegate to the given logger */
+ private LoggerLog(final Logger logger, final Level level) {
+ this.logger = logger;
+
+ if (level != null){
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ if (!logger.isLoggable(level)) {
+ logger.setLevel(level);
+ }
+ logger.addHandler(alternateConsole);
+ return null;
+ }
+ }
+ );
+ }
+ }
+
+ public boolean isLoggable(Level level) {
+ return logger.isLoggable(level);
+ }
+
+ public void log(Level level, String message) {
+ if (isLoggable(level)) {
+ String[] source = getSource();
+ logger.logp(level, source[0], source[1],
+ Thread.currentThread().getName() + ": " + message);
+ }
+ }
+
+ public void log(Level level, String message, Throwable thrown) {
+ if (isLoggable(level)) {
+ String[] source = getSource();
+ logger.logp(level, source[0], source[1],
+ Thread.currentThread().getName() + ": " +
+ message, thrown);
+ }
+ }
+
+ /**
+ * Set the output stream associated with the RMI server call
+ * logger.
+ *
+ * Calling code needs LoggingPermission "control".
+ */
+ public synchronized void setOutputStream(OutputStream out) {
+ if (out != null) {
+ if (!logger.isLoggable(VERBOSE)) {
+ logger.setLevel(VERBOSE);
+ }
+ copyHandler = new InternalStreamHandler(out);
+ copyHandler.setLevel(Log.VERBOSE);
+ logger.addHandler(copyHandler);
+ } else {
+ /* ensure that messages are not logged */
+ if (copyHandler != null) {
+ logger.removeHandler(copyHandler);
+ }
+ copyHandler = null;
+ }
+ }
+
+ public synchronized PrintStream getPrintStream() {
+ if (loggerSandwich == null) {
+ loggerSandwich = new LoggerPrintStream(logger);
+ }
+ return loggerSandwich;
+ }
+ }
+
+ /**
+ * Subclass of StreamHandler for redirecting log output. flush
+ * must be called in the publish and close methods.
+ */
+ private static class InternalStreamHandler extends StreamHandler {
+ InternalStreamHandler(OutputStream out) {
+ super(out, new SimpleFormatter());
+ }
+
+ public void publish(LogRecord record) {
+ super.publish(record);
+ flush();
+ }
+
+ public void close() {
+ flush();
+ }
+ }
+
+ /**
+ * PrintStream which forwards log messages to the logger. Class
+ * is needed to maintain backwards compatibility with
+ * RemoteServer.{set|get}Log().
+ */
+ private static class LoggerPrintStream extends PrintStream {
+
+ /** logger where output of this log is sent */
+ private final Logger logger;
+
+ /** record the last character written to this stream */
+ private int last = -1;
+
+ /** stream used for buffering lines */
+ private final ByteArrayOutputStream bufOut;
+
+ private LoggerPrintStream(Logger logger)
+ {
+ super(new ByteArrayOutputStream());
+ bufOut = (ByteArrayOutputStream) super.out;
+ this.logger = logger;
+ }
+
+ public void write(int b) {
+ if ((last == '\r') && (b == '\n')) {
+ last = -1;
+ return;
+ } else if ((b == '\n') || (b == '\r')) {
+ try {
+ /* write the converted bytes of the log message */
+ String message =
+ Thread.currentThread().getName() + ": " +
+ bufOut.toString();
+ logger.logp(Level.INFO, "LogStream", "print", message);
+ } finally {
+ bufOut.reset();
+ }
+ } else {
+ super.write(b);
+ }
+ last = b;
+ }
+
+ public void write(byte b[], int off, int len) {
+ if (len < 0) {
+ throw new ArrayIndexOutOfBoundsException(len);
+ }
+ for (int i = 0; i < len; i++) {
+ write(b[off + i]);
+ }
+ }
+
+ public String toString() {
+ return "RMI";
+ }
+ }
+
+ /**
+ * Factory to create Log objects which deliver log messages to the
+ * java.rmi.server.LogStream API
+ */
+ private static class LogStreamLogFactory implements LogFactory {
+ LogStreamLogFactory() {}
+
+ /* create a new LogStreamLog for the specified log */
+ public Log createLog(String loggerName, String oldLogName,
+ Level level)
+ {
+ LogStream stream = null;
+ if (oldLogName != null) {
+ stream = LogStream.log(oldLogName);
+ }
+ return new LogStreamLog(stream, level);
+ }
+ }
+
+ /**
+ * Class specialized to log messages to the
+ * java.rmi.server.LogStream API
+ */
+ private static class LogStreamLog extends Log {
+ /** Log stream to which log messages are written */
+ private final LogStream stream;
+
+ /** the level of the log as set by associated property */
+ private int levelValue = Level.OFF.intValue();
+
+ private LogStreamLog(LogStream stream, Level level) {
+ if ((stream != null) && (level != null)) {
+ /* if the stream or level is null, dont log any
+ * messages
+ */
+ levelValue = level.intValue();
+ }
+ this.stream = stream;
+ }
+
+ public synchronized boolean isLoggable(Level level) {
+ return (level.intValue() >= levelValue);
+ }
+
+ public void log(Level messageLevel, String message) {
+ if (isLoggable(messageLevel)) {
+ String[] source = getSource();
+ stream.println(unqualifiedName(source[0]) +
+ "." + source[1] + ": " + message);
+ }
+ }
+
+ public void log(Level level, String message, Throwable thrown) {
+ if (isLoggable(level)) {
+ /*
+ * keep output contiguous and maintain the contract of
+ * RemoteServer.getLog
+ */
+ synchronized (stream) {
+ String[] source = getSource();
+ stream.println(unqualifiedName(source[0]) + "." +
+ source[1] + ": " + message);
+ thrown.printStackTrace(stream);
+ }
+ }
+ }
+
+ public PrintStream getPrintStream() {
+ return stream;
+ }
+
+ public synchronized void setOutputStream(OutputStream out) {
+ if (out != null) {
+ if (VERBOSE.intValue() < levelValue) {
+ levelValue = VERBOSE.intValue();
+ }
+ stream.setOutputStream(out);
+ } else {
+ /* ensure that messages are not logged */
+ levelValue = Level.OFF.intValue();
+ }
+ }
+
+ /*
+ * Mimic old log messages that only contain unqualified names.
+ */
+ private static String unqualifiedName(String name) {
+ int lastDot = name.lastIndexOf(".");
+ if (lastDot >= 0) {
+ name = name.substring(lastDot + 1);
+ }
+ name = name.replace('$', '.');
+ return name;
+ }
+ }
+
+ /**
+ * Obtain class and method names of code calling a log method.
+ */
+ private static String[] getSource() {
+ StackTraceElement[] trace = (new Exception()).getStackTrace();
+ return new String[] {
+ trace[3].getClassName(),
+ trace[3].getMethodName()
+ };
+ }
+}
diff --git a/src/share/classes/sun/rmi/runtime/NewThreadAction.java b/src/share/classes/sun/rmi/runtime/NewThreadAction.java
new file mode 100644
index 000000000..65c411d4f
--- /dev/null
+++ b/src/share/classes/sun/rmi/runtime/NewThreadAction.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.runtime;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.security.util.SecurityConstants;
+
+/**
+ * A PrivilegedAction for creating a new thread conveniently with an
+ * AccessController.doPrivileged construct.
+ *
+ * All constructors allow the choice of the Runnable for the new
+ * thread to execute, the name of the new thread (which will be
+ * prefixed with "RMI "), and whether or not it will be a daemon
+ * thread.
+ *
+ * The new thread may be created in the system thread group (the root
+ * of the thread group tree) or an internally created non-system
+ * thread group, as specified at construction of this class.
+ *
+ * The new thread will have the system class loader as its initial
+ * context class loader (that is, its context class loader will NOT be
+ * inherited from the current thread).
+ *
+ * @author Peter Jones
+ **/
+public final class NewThreadAction implements PrivilegedAction<Thread> {
+
+ /** cached reference to the system (root) thread group */
+ static final ThreadGroup systemThreadGroup =
+ AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() {
+ public ThreadGroup run() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ ThreadGroup parent;
+ while ((parent = group.getParent()) != null) {
+ group = parent;
+ }
+ return group;
+ }
+ });
+
+ /**
+ * special child of the system thread group for running tasks that
+ * may execute user code, so that the security policy for threads in
+ * the system thread group will not apply
+ */
+ static final ThreadGroup userThreadGroup =
+ AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() {
+ public ThreadGroup run() {
+ return new ThreadGroup(systemThreadGroup, "RMI Runtime");
+ }
+ });
+
+ private final ThreadGroup group;
+ private final Runnable runnable;
+ private final String name;
+ private final boolean daemon;
+
+ NewThreadAction(ThreadGroup group, Runnable runnable,
+ String name, boolean daemon)
+ {
+ this.group = group;
+ this.runnable = runnable;
+ this.name = name;
+ this.daemon = daemon;
+ }
+
+ /**
+ * Creates an action that will create a new thread in the
+ * system thread group.
+ *
+ * @param runnable the Runnable for the new thread to execute
+ *
+ * @param name the name of the new thread
+ *
+ * @param daemon if true, new thread will be a daemon thread;
+ * if false, new thread will not be a daemon thread
+ */
+ public NewThreadAction(Runnable runnable, String name, boolean daemon) {
+ this(systemThreadGroup, runnable, name, daemon);
+ }
+
+ /**
+ * Creates an action that will create a new thread.
+ *
+ * @param runnable the Runnable for the new thread to execute
+ *
+ * @param name the name of the new thread
+ *
+ * @param daemon if true, new thread will be a daemon thread;
+ * if false, new thread will not be a daemon thread
+ *
+ * @param user if true, thread will be created in a non-system
+ * thread group; if false, thread will be created in the system
+ * thread group
+ */
+ public NewThreadAction(Runnable runnable, String name, boolean daemon,
+ boolean user)
+ {
+ this(user ? userThreadGroup : systemThreadGroup,
+ runnable, name, daemon);
+ }
+
+ public Thread run() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
+ }
+ Thread t = new Thread(group, runnable, "RMI " + name);
+ t.setContextClassLoader(ClassLoader.getSystemClassLoader());
+ t.setDaemon(daemon);
+ return t;
+ }
+}
diff --git a/src/share/classes/sun/rmi/runtime/RuntimeUtil.java b/src/share/classes/sun/rmi/runtime/RuntimeUtil.java
new file mode 100644
index 000000000..3146db77f
--- /dev/null
+++ b/src/share/classes/sun/rmi/runtime/RuntimeUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.runtime;
+
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import sun.security.action.GetIntegerAction;
+
+/**
+ * RMI runtime implementation utilities.
+ *
+ * There is a single instance of this class, which can be obtained
+ * with a GetInstanceAction. Getting the instance requires
+ * RuntimePermission("sun.rmi.runtime.RuntimeUtil.getInstance")
+ * because the public methods of this class expose security-sensitive
+ * capabilities.
+ *
+ * @author Peter Jones
+ **/
+public final class RuntimeUtil {
+
+ /** runtime package log */
+ private static final Log runtimeLog =
+ Log.getLog("sun.rmi.runtime", null, false);
+
+ /** number of scheduler threads */
+ private static final int schedulerThreads = // default 1
+ AccessController.doPrivileged(
+ new GetIntegerAction("sun.rmi.runtime.schedulerThreads", 1));
+
+ /** permission required to get instance */
+ private static final Permission GET_INSTANCE_PERMISSION =
+ new RuntimePermission("sun.rmi.runtime.RuntimeUtil.getInstance");
+
+ /** the singleton instance of this class */
+ private static final RuntimeUtil instance = new RuntimeUtil();
+
+ /** thread pool for scheduling delayed tasks */
+ private final ScheduledThreadPoolExecutor scheduler;
+
+ private RuntimeUtil() {
+ scheduler = new ScheduledThreadPoolExecutor(
+ schedulerThreads,
+ new ThreadFactory() {
+ private final AtomicInteger count = new AtomicInteger(0);
+ public Thread newThread(Runnable runnable) {
+ try {
+ return AccessController.doPrivileged(
+ new NewThreadAction(runnable,
+ "Scheduler(" + count.getAndIncrement() + ")",
+ true));
+ } catch (Throwable t) {
+ runtimeLog.log(Level.WARNING,
+ "scheduler thread factory throws", t);
+ return null;
+ }
+ }
+ });
+ /*
+ * We would like to allow the scheduler's threads to terminate
+ * if possible, but a bug in DelayQueue.poll can cause code
+ * like this to result in a busy loop:
+ */
+ // stpe.setKeepAliveTime(10, TimeUnit.MINUTES);
+ // stpe.allowCoreThreadTimeOut(true);
+ }
+
+ /**
+ * A PrivilegedAction for getting the RuntimeUtil instance.
+ **/
+ public static class GetInstanceAction
+ implements PrivilegedAction<RuntimeUtil>
+ {
+ /**
+ * Creates an action that returns the RuntimeUtil instance.
+ **/
+ public GetInstanceAction() {
+ }
+
+ public RuntimeUtil run() {
+ return getInstance();
+ }
+ }
+
+ private static RuntimeUtil getInstance() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(GET_INSTANCE_PERMISSION);
+ }
+ return instance;
+ }
+
+ /**
+ * Returns the shared thread pool for scheduling delayed tasks.
+ *
+ * Note that the returned pool has limited concurrency, so
+ * submitted tasks should be short-lived and should not block.
+ **/
+ public ScheduledThreadPoolExecutor getScheduler() {
+ return scheduler;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/ActivatableRef.java b/src/share/classes/sun/rmi/server/ActivatableRef.java
new file mode 100644
index 000000000..94af9b01b
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/ActivatableRef.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.rmi.*;
+import java.rmi.activation.*;
+import java.rmi.server.Operation;
+import java.rmi.server.RMIClassLoader;
+import java.rmi.server.RemoteCall;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.RemoteObjectInvocationHandler;
+import java.rmi.server.RemoteRef;
+import java.rmi.server.RemoteStub;
+
+public class ActivatableRef implements RemoteRef {
+
+ private static final long serialVersionUID = 7579060052569229166L;
+
+ protected ActivationID id;
+ protected RemoteRef ref;
+ transient boolean force = false;
+
+ private static final int MAX_RETRIES = 3;
+ private static final String versionComplaint =
+ "activation requires 1.2 stubs";
+
+ /**
+ * Create a new (empty) ActivatableRef
+ */
+ public ActivatableRef()
+ {}
+
+ /**
+ * Create a ActivatableRef with the specified id
+ */
+ public ActivatableRef(ActivationID id, RemoteRef ref)
+ {
+ this.id = id;
+ this.ref = ref;
+ }
+
+ /**
+ * Returns the stub for the remote object whose class is
+ * specified in the activation descriptor. The ActivatableRef
+ * in the resulting stub has its activation id set to the
+ * activation id supplied as the second argument.
+ */
+ public static Remote getStub(ActivationDesc desc, ActivationID id)
+ throws StubNotFoundException
+ {
+ String className = desc.getClassName();
+
+ try {
+ Class cl =
+ RMIClassLoader.loadClass(desc.getLocation(), className);
+ RemoteRef clientRef = new ActivatableRef(id, null);
+ return Util.createProxy(cl, clientRef, false);
+
+ } catch (IllegalArgumentException e) {
+ throw new StubNotFoundException(
+ "class implements an illegal remote interface", e);
+
+ } catch (ClassNotFoundException e) {
+ throw new StubNotFoundException("unable to load class: " +
+ className, e);
+ } catch (MalformedURLException e) {
+ throw new StubNotFoundException("malformed URL", e);
+ }
+ }
+
+ /**
+ * Invoke method on remote object. This method delegates remote
+ * method invocation to the underlying ref type. If the
+ * underlying reference is not known (is null), then the object
+ * must be activated first. If an attempt at method invocation
+ * fails, the object should force reactivation. Method invocation
+ * must preserve "at most once" call semantics. In RMI, "at most
+ * once" applies to parameter deserialization at the remote site
+ * and the remote object's method execution. "At most once" does
+ * not apply to parameter serialization at the client so the
+ * parameters of a call don't need to be buffered in anticipation
+ * of call retry. Thus, a method call is only be retried if the
+ * initial method invocation does not execute at all at the server
+ * (including parameter deserialization).
+ */
+ public Object invoke(Remote obj,
+ java.lang.reflect.Method method,
+ Object[] params,
+ long opnum)
+ throws Exception
+ {
+
+ boolean force = false;
+ RemoteRef localRef;
+ Exception exception = null;
+
+ /*
+ * Attempt object activation if active ref is unknown.
+ * Throws a RemoteException if object can't be activated.
+ */
+ synchronized (this) {
+ if (ref == null) {
+ localRef = activate(force);
+ force = true;
+ } else {
+ localRef = ref;
+ }
+ }
+
+ for (int retries = MAX_RETRIES; retries > 0; retries--) {
+
+ try {
+ return localRef.invoke(obj, method, params, opnum);
+ } catch (NoSuchObjectException e) {
+ /*
+ * Object is not active in VM; retry call
+ */
+ exception = e;
+ } catch (ConnectException e) {
+ /*
+ * Failure during connection setup; retry call
+ */
+ exception = e;
+ } catch (UnknownHostException e) {
+ /*
+ * Failure during connection setup; retry call.
+ */
+ exception = e;
+ } catch (ConnectIOException e) {
+ /*
+ * Failure setting up multiplexed connection or reusing
+ * cached connection; retry call
+ */
+ exception = e;
+ } catch (MarshalException e) {
+ /*
+ * Failure during parameter serialization; call may
+ * have reached server, so call retry not possible.
+ */
+ throw e;
+ } catch (ServerError e) {
+ /*
+ * Call reached server; propagate remote exception.
+ */
+ throw e;
+ } catch (ServerException e) {
+ /*
+ * Call reached server; propagate remote exception
+ */
+ throw e;
+ } catch (RemoteException e) {
+ /*
+ * This is a catch-all for other RemoteExceptions.
+ * UnmarshalException being the only one relevant.
+ *
+ * StubNotFoundException should never show up because
+ * it is generally thrown when attempting to locate
+ * a stub.
+ *
+ * UnexpectedException should never show up because
+ * it is only thrown by a stub and would be wrapped
+ * in a ServerException if it was propagated by a
+ * remote call.
+ */
+ synchronized (this) {
+ if (localRef == ref) {
+ ref = null; // this may be overly conservative
+ }
+ }
+
+ throw e;
+ }
+
+ if (retries > 1) {
+ /*
+ * Activate object, since object could not be reached.
+ */
+ synchronized (this) {
+ if (localRef.remoteEquals(ref) || ref == null) {
+ RemoteRef newRef = activate(force);
+
+ if (newRef.remoteEquals(localRef) &&
+ exception instanceof NoSuchObjectException &&
+ force == false) {
+ /*
+ * If last exception was NoSuchObjectException,
+ * then old value of ref is definitely wrong,
+ * so make sure that it is different.
+ */
+ newRef = activate(true);
+ }
+
+ localRef = newRef;
+ force = true;
+ } else {
+ localRef = ref;
+ force = false;
+ }
+ }
+ }
+ }
+
+ /*
+ * Retries unsuccessful, so throw last exception
+ */
+ throw exception;
+ }
+
+ /**
+ * private method to obtain the ref for a call.
+ */
+ private synchronized RemoteRef getRef()
+ throws RemoteException
+ {
+ if (ref == null) {
+ ref = activate(false);
+ }
+
+ return ref;
+ }
+
+ /**
+ * private method to activate the remote object.
+ *
+ * NOTE: the caller must be synchronized on "this" before
+ * calling this method.
+ */
+ private RemoteRef activate(boolean force)
+ throws RemoteException
+ {
+ assert Thread.holdsLock(this);
+
+ ref = null;
+ try {
+ /*
+ * Activate the object and retrieve the remote reference
+ * from inside the stub returned as the result. Then
+ * set this activatable ref's internal ref to be the
+ * ref inside the ref of the stub. In more clear terms,
+ * the stub returned from the activate call contains an
+ * ActivatableRef. We need to set the ref in *this*
+ * ActivatableRef to the ref inside the ActivatableRef
+ * retrieved from the stub. The ref type embedded in the
+ * ActivatableRef is typically a UnicastRef.
+ */
+
+ Remote proxy = id.activate(force);
+ ActivatableRef newRef = null;
+
+ if (proxy instanceof RemoteStub) {
+ newRef = (ActivatableRef) ((RemoteStub) proxy).getRef();
+ } else {
+ /*
+ * Assume that proxy is an instance of a dynamic proxy
+ * class. If that assumption is not correct, or either of
+ * the casts below fails, the resulting exception will be
+ * wrapped in an ActivateFailedException below.
+ */
+ RemoteObjectInvocationHandler handler =
+ (RemoteObjectInvocationHandler)
+ Proxy.getInvocationHandler(proxy);
+ newRef = (ActivatableRef) handler.getRef();
+ }
+ ref = newRef.ref;
+ return ref;
+
+ } catch (ConnectException e) {
+ throw new ConnectException("activation failed", e);
+ } catch (RemoteException e) {
+ throw new ConnectIOException("activation failed", e);
+ } catch (UnknownObjectException e) {
+ throw new NoSuchObjectException("object not registered");
+ } catch (ActivationException e) {
+ throw new ActivateFailedException("activation failed", e);
+ }
+ }
+
+ /**
+ * This call is used by the old 1.1 stub protocol and is
+ * unsupported since activation requires 1.2 stubs.
+ */
+ public synchronized RemoteCall newCall(RemoteObject obj,
+ Operation[] ops,
+ int opnum,
+ long hash)
+ throws RemoteException
+ {
+ throw new UnsupportedOperationException(versionComplaint);
+ }
+
+ /**
+ * This call is used by the old 1.1 stub protocol and is
+ * unsupported since activation requires 1.2 stubs.
+ */
+ public void invoke(RemoteCall call) throws Exception
+ {
+ throw new UnsupportedOperationException(versionComplaint);
+ }
+
+ /**
+ * This call is used by the old 1.1 stub protocol and is
+ * unsupported since activation requires 1.2 stubs.
+ */
+ public void done(RemoteCall call) throws RemoteException {
+ throw new UnsupportedOperationException(versionComplaint);
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized
+ */
+ public String getRefClass(ObjectOutput out)
+ {
+ return "ActivatableRef";
+ }
+
+ /**
+ * Write out external representation for remote ref.
+ */
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ RemoteRef localRef = ref;
+
+ out.writeObject(id);
+ if (localRef == null) {
+ out.writeUTF("");
+ } else {
+ out.writeUTF(localRef.getRefClass(out));
+ localRef.writeExternal(out);
+ }
+ }
+
+ /**
+ * Read in external representation for remote ref.
+ * @exception ClassNotFoundException If the class for an object
+ * being restored cannot be found.
+ */
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ id = (ActivationID)in.readObject();
+ ref = null;
+ String className = in.readUTF();
+
+ if (className.equals("")) return;
+
+ try {
+ Class refClass = Class.forName(RemoteRef.packagePrefix + "." +
+ className);
+ ref = (RemoteRef)refClass.newInstance();
+ ref.readExternal(in);
+ } catch (InstantiationException e) {
+ throw new UnmarshalException("Unable to create remote reference",
+ e);
+ } catch (IllegalAccessException e) {
+ throw new UnmarshalException("Illegal access creating remote reference");
+ }
+ }
+
+ //----------------------------------------------------------------------;
+ /**
+ * Method from object, forward from RemoteObject
+ */
+ public String remoteToString() {
+ return Util.getUnqualifiedName(getClass()) +
+ " [remoteRef: " + ref + "]";
+ }
+
+ /**
+ * default implementation of hashCode for remote objects
+ */
+ public int remoteHashCode() {
+ return id.hashCode();
+ }
+
+ /** default implementation of equals for remote objects
+ */
+ public boolean remoteEquals(RemoteRef ref) {
+ if (ref instanceof ActivatableRef)
+ return id.equals(((ActivatableRef)ref).id);
+ return false;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/ActivatableServerRef.java b/src/share/classes/sun/rmi/server/ActivatableServerRef.java
new file mode 100644
index 000000000..6e439f0c4
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/ActivatableServerRef.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 1997-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectOutput;
+import java.rmi.*;
+import java.rmi.server.*;
+import java.rmi.activation.ActivationID;
+import sun.rmi.transport.LiveRef;
+
+/**
+ * Server-side ref for a persistent remote impl.
+ *
+ * @author Ann Wollrath
+ */
+public class ActivatableServerRef extends UnicastServerRef2 {
+
+ private static final long serialVersionUID = 2002967993223003793L;
+
+ private ActivationID id;
+
+ /**
+ * Construct a Unicast server remote reference to be exported
+ * on the specified port.
+ */
+ public ActivatableServerRef(ActivationID id, int port)
+ {
+ this(id, port, null, null);
+ }
+
+ /**
+ * Construct a Unicast server remote reference to be exported
+ * on the specified port.
+ */
+ public ActivatableServerRef(ActivationID id, int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ super(new LiveRef(port, csf, ssf));
+ this.id = id;
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized
+ */
+ public String getRefClass(ObjectOutput out)
+ {
+ return "ActivatableServerRef";
+ }
+
+ /**
+ * Return the client remote reference for this remoteRef.
+ * In the case of a client RemoteRef "this" is the answer.
+ * For a server remote reference, a client side one will have to
+ * found or created.
+ */
+ protected RemoteRef getClientRef() {
+ return new ActivatableRef(id, new UnicastRef2(ref));
+ }
+
+ /**
+ * Prevents serialization (because deserializaion is impossible).
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ throw new NotSerializableException(
+ "ActivatableServerRef not serializable");
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/Activation.java b/src/share/classes/sun/rmi/server/Activation.java
new file mode 100644
index 000000000..41c202737
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/Activation.java
@@ -0,0 +1,2481 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.lang.Process;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.ServerSocketChannel;
+import java.rmi.AccessException;
+import java.rmi.AlreadyBoundException;
+import java.rmi.ConnectException;
+import java.rmi.ConnectIOException;
+import java.rmi.MarshalledObject;
+import java.rmi.NoSuchObjectException;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.activation.ActivationDesc;
+import java.rmi.activation.ActivationException;
+import java.rmi.activation.ActivationGroupDesc;
+import java.rmi.activation.ActivationGroup;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.ActivationID;
+import java.rmi.activation.ActivationInstantiator;
+import java.rmi.activation.ActivationMonitor;
+import java.rmi.activation.ActivationSystem;
+import java.rmi.activation.Activator;
+import java.rmi.activation.UnknownGroupException;
+import java.rmi.activation.UnknownObjectException;
+import java.rmi.registry.Registry;
+import java.rmi.server.ObjID;
+import java.rmi.server.RMIClassLoader;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.RemoteServer;
+import java.rmi.server.UnicastRemoteObject;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.security.cert.Certificate;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import sun.rmi.log.LogHandler;
+import sun.rmi.log.ReliableLog;
+import sun.rmi.registry.RegistryImpl;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.server.UnicastServerRef;
+import sun.rmi.transport.LiveRef;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetPropertyAction;
+import sun.security.provider.PolicyFile;
+import com.sun.rmi.rmid.ExecPermission;
+import com.sun.rmi.rmid.ExecOptionPermission;
+
+/**
+ * The Activator facilitates remote object activation. A "faulting"
+ * remote reference calls the activator's <code>activate</code> method
+ * to obtain a "live" reference to a activatable remote object. Upon
+ * receiving a request for activation, the activator looks up the
+ * activation descriptor for the activation identifier, id, determines
+ * the group in which the object should be activated and invokes the
+ * activate method on the object's activation group (described by the
+ * remote interface <code>ActivationInstantiator</code>). The
+ * activator initiates the execution of activation groups as
+ * necessary. For example, if an activation group for a specific group
+ * identifier is not already executing, the activator will spawn a
+ * child process for the activation group. <p>
+ *
+ * The activator is responsible for monitoring and detecting when
+ * activation groups fail so that it can remove stale remote references
+ * from its internal tables. <p>
+ *
+ * @author Ann Wollrath
+ * @since 1.2
+ */
+public class Activation implements Serializable {
+
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 2921265612698155191L;
+
+ private static final byte MAJOR_VERSION = 1;
+ private static final byte MINOR_VERSION = 0;
+
+ /** exec policy object */
+ private static Object execPolicy;
+ private static Method execPolicyMethod;
+ private static boolean debugExec;
+
+ /** maps activation id to its respective group id */
+ private Map<ActivationID,ActivationGroupID> idTable =
+ new HashMap<ActivationID,ActivationGroupID>();
+ /** maps group id to its GroupEntry groups */
+ private Map<ActivationGroupID,GroupEntry> groupTable =
+ new HashMap<ActivationGroupID,GroupEntry>();
+
+ private byte majorVersion = MAJOR_VERSION;
+ private byte minorVersion = MINOR_VERSION;
+
+ /** number of simultaneous group exec's */
+ private transient int groupSemaphore;
+ /** counter for numbering groups */
+ private transient int groupCounter;
+ /** reliable log to hold descriptor table */
+ private transient ReliableLog log;
+ /** number of updates since last snapshot */
+ private transient int numUpdates;
+
+ /** the java command */
+ // accessed by GroupEntry
+ private transient String[] command;
+ /** timeout on wait for child process to be created or destroyed */
+ private static final long groupTimeout =
+ getInt("sun.rmi.activation.groupTimeout", 60000);
+ /** take snapshot after this many updates */
+ private static final int snapshotInterval =
+ getInt("sun.rmi.activation.snapshotInterval", 200);
+ /** timeout on wait for child process to be created */
+ private static final long execTimeout =
+ getInt("sun.rmi.activation.execTimeout", 30000);
+
+ private static final Object initLock = new Object();
+ private static boolean initDone = false;
+
+ // this should be a *private* method since it is privileged
+ private static int getInt(String name, int def) {
+ return AccessController.doPrivileged(new GetIntegerAction(name, def));
+ }
+
+ private transient Activator activator;
+ private transient Activator activatorStub;
+ private transient ActivationSystem system;
+ private transient ActivationSystem systemStub;
+ private transient ActivationMonitor monitor;
+ private transient Registry registry;
+ private transient volatile boolean shuttingDown = false;
+ private transient volatile Object startupLock;
+ private transient Thread shutdownHook;
+
+ private static ResourceBundle resources = null;
+
+ /**
+ * Create an uninitialized instance of Activation that can be
+ * populated with log data. This is only called when the initial
+ * snapshot is taken during the first incarnation of rmid.
+ */
+ private Activation() {}
+
+ /**
+ * Recover activation state from the reliable log and initialize
+ * activation services.
+ */
+ private static void startActivation(int port,
+ RMIServerSocketFactory ssf,
+ String logName,
+ String[] childArgs)
+ throws Exception
+ {
+ ReliableLog log = new ReliableLog(logName, new ActLogHandler());
+ Activation state = (Activation) log.recover();
+ state.init(port, ssf, log, childArgs);
+ }
+
+ /**
+ * Initialize the Activation instantiation; start activation
+ * services.
+ */
+ private void init(int port,
+ RMIServerSocketFactory ssf,
+ ReliableLog log,
+ String[] childArgs)
+ throws Exception
+ {
+ // initialize
+ this.log = log;
+ numUpdates = 0;
+ shutdownHook = new ShutdownHook();
+ groupSemaphore = getInt("sun.rmi.activation.groupThrottle", 3);
+ groupCounter = 0;
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ ActivationGroupID[] gids =
+ groupTable.keySet().toArray(
+ new ActivationGroupID[groupTable.size()]);
+
+ synchronized (startupLock = new Object()) {
+ // all the remote methods briefly synchronize on startupLock
+ // (via checkShutdown) to make sure they don't happen in the
+ // middle of this block. This block must not cause any such
+ // incoming remote calls to happen, or deadlock would result!
+ activator = new ActivatorImpl(port, ssf);
+ activatorStub = (Activator) RemoteObject.toStub(activator);
+ system = new ActivationSystemImpl(port, ssf);
+ systemStub = (ActivationSystem) RemoteObject.toStub(system);
+ monitor = new ActivationMonitorImpl(port, ssf);
+ initCommand(childArgs);
+ registry = new SystemRegistryImpl(port, null, ssf, systemStub);
+
+ if (ssf != null) {
+ synchronized (initLock) {
+ initDone = true;
+ initLock.notifyAll();
+ }
+ }
+ }
+ startupLock = null;
+
+ // restart services
+ for (int i = gids.length; --i >= 0; ) {
+ try {
+ getGroupEntry(gids[i]).restartServices();
+ } catch (UnknownGroupException e) {
+ System.err.println(
+ getTextResource("rmid.restart.group.warning"));
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static class SystemRegistryImpl extends RegistryImpl {
+
+ private static final String NAME = ActivationSystem.class.getName();
+ private final ActivationSystem systemStub;
+
+ SystemRegistryImpl(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf,
+ ActivationSystem systemStub)
+ throws RemoteException
+ {
+ super(port, csf, ssf);
+ this.systemStub = systemStub;
+ }
+
+ /**
+ * Returns the activation system stub if the specified name
+ * matches the activation system's class name, otherwise
+ * returns the result of invoking super.lookup with the specified
+ * name.
+ */
+ public Remote lookup(String name)
+ throws RemoteException, NotBoundException
+ {
+ if (name.equals(NAME)) {
+ return systemStub;
+ } else {
+ return super.lookup(name);
+ }
+ }
+
+ public String[] list() throws RemoteException {
+ String[] list1 = super.list();
+ int length = list1.length;
+ String[] list2 = new String[length + 1];
+ if (length > 0) {
+ System.arraycopy(list1, 0, list2, 0, length);
+ }
+ list2[length] = NAME;
+ return list2;
+ }
+
+ public void bind(String name, Remote obj)
+ throws RemoteException, AlreadyBoundException, AccessException
+ {
+ if (name.equals(NAME)) {
+ throw new AccessException(
+ "binding ActivationSystem is disallowed");
+ } else {
+ super.bind(name, obj);
+ }
+ }
+
+ public void unbind(String name)
+ throws RemoteException, NotBoundException, AccessException
+ {
+ if (name.equals(NAME)) {
+ throw new AccessException(
+ "unbinding ActivationSystem is disallowed");
+ } else {
+ super.unbind(name);
+ }
+ }
+
+
+ public void rebind(String name, Remote obj)
+ throws RemoteException, AccessException
+ {
+ if (name.equals(NAME)) {
+ throw new AccessException(
+ "binding ActivationSystem is disallowed");
+ } else {
+ super.rebind(name, obj);
+ }
+ }
+ }
+
+
+ class ActivatorImpl extends RemoteServer implements Activator {
+ // Because ActivatorImpl has a fixed ObjID, it can be
+ // called by clients holding stale remote references. Each of
+ // its remote methods, then, must check startupLock (calling
+ // checkShutdown() is easiest).
+
+ private static final long serialVersionUID = -3654244726254566136L;
+
+ /**
+ * Construct a new Activator on a specified port.
+ */
+ ActivatorImpl(int port, RMIServerSocketFactory ssf)
+ throws RemoteException
+ {
+ /* Server ref must be created and assigned before remote object
+ * 'this' can be exported.
+ */
+ LiveRef lref =
+ new LiveRef(new ObjID(ObjID.ACTIVATOR_ID), port, null, ssf);
+ UnicastServerRef uref = new UnicastServerRef(lref);
+ ref = uref;
+ uref.exportObject(this, null, false);
+ }
+
+ public MarshalledObject<? extends Remote> activate(ActivationID id,
+ boolean force)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+ checkShutdown();
+ return getGroupEntry(id).activate(id, force);
+ }
+ }
+
+ class ActivationMonitorImpl extends UnicastRemoteObject
+ implements ActivationMonitor
+ {
+ private static final long serialVersionUID = -6214940464757948867L;
+
+ ActivationMonitorImpl(int port, RMIServerSocketFactory ssf)
+ throws RemoteException
+ {
+ super(port, null, ssf);
+ }
+
+ public void inactiveObject(ActivationID id)
+ throws UnknownObjectException, RemoteException
+ {
+ try {
+ checkShutdown();
+ } catch (ActivationException e) {
+ return;
+ }
+ RegistryImpl.checkAccess("Activator.inactiveObject");
+ getGroupEntry(id).inactiveObject(id);
+ }
+
+ public void activeObject(ActivationID id,
+ MarshalledObject<? extends Remote> mobj)
+ throws UnknownObjectException, RemoteException
+ {
+ try {
+ checkShutdown();
+ } catch (ActivationException e) {
+ return;
+ }
+ RegistryImpl.checkAccess("ActivationSystem.activeObject");
+ getGroupEntry(id).activeObject(id, mobj);
+ }
+
+ public void inactiveGroup(ActivationGroupID id,
+ long incarnation)
+ throws UnknownGroupException, RemoteException
+ {
+ try {
+ checkShutdown();
+ } catch (ActivationException e) {
+ return;
+ }
+ RegistryImpl.checkAccess("ActivationMonitor.inactiveGroup");
+ getGroupEntry(id).inactiveGroup(incarnation, false);
+ }
+ }
+
+
+ class ActivationSystemImpl
+ extends RemoteServer
+ implements ActivationSystem
+ {
+ private static final long serialVersionUID = 9100152600327688967L;
+
+ // Because ActivationSystemImpl has a fixed ObjID, it can be
+ // called by clients holding stale remote references. Each of
+ // its remote methods, then, must check startupLock (calling
+ // checkShutdown() is easiest).
+ ActivationSystemImpl(int port, RMIServerSocketFactory ssf)
+ throws RemoteException
+ {
+ /* Server ref must be created and assigned before remote object
+ * 'this' can be exported.
+ */
+ LiveRef lref = new LiveRef(new ObjID(4), port, null, ssf);
+ UnicastServerRef uref = new UnicastServerRef(lref);
+ ref = uref;
+ uref.exportObject(this, null);
+ }
+
+ public ActivationID registerObject(ActivationDesc desc)
+ throws ActivationException, UnknownGroupException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.registerObject");
+
+ ActivationGroupID groupID = desc.getGroupID();
+ ActivationID id = new ActivationID(activatorStub);
+ getGroupEntry(groupID).registerObject(id, desc, true);
+ return id;
+ }
+
+ public void unregisterObject(ActivationID id)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.unregisterObject");
+ getGroupEntry(id).unregisterObject(id, true);
+ }
+
+ public ActivationGroupID registerGroup(ActivationGroupDesc desc)
+ throws ActivationException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.registerGroup");
+ checkArgs(desc, null);
+
+ ActivationGroupID id = new ActivationGroupID(systemStub);
+ GroupEntry entry = new GroupEntry(id, desc);
+ // table insertion must take place before log update
+ synchronized (groupTable) {
+ groupTable.put(id, entry);
+ }
+ addLogRecord(new LogRegisterGroup(id, desc));
+ return id;
+ }
+
+ public ActivationMonitor activeGroup(ActivationGroupID id,
+ ActivationInstantiator group,
+ long incarnation)
+ throws ActivationException, UnknownGroupException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.activeGroup");
+
+ getGroupEntry(id).activeGroup(group, incarnation);
+ return monitor;
+ }
+
+ public void unregisterGroup(ActivationGroupID id)
+ throws ActivationException, UnknownGroupException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.unregisterGroup");
+
+ // remove entry before unregister so state is updated before
+ // logged
+ synchronized (groupTable) {
+ GroupEntry entry = getGroupEntry(id);
+ groupTable.remove(id);
+ entry.unregisterGroup(true);
+ }
+ }
+
+ public ActivationDesc setActivationDesc(ActivationID id,
+ ActivationDesc desc)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.setActivationDesc");
+
+ if (!getGroupID(id).equals(desc.getGroupID())) {
+ throw new ActivationException(
+ "ActivationDesc contains wrong group");
+ }
+ return getGroupEntry(id).setActivationDesc(id, desc, true);
+ }
+
+ public ActivationGroupDesc setActivationGroupDesc(ActivationGroupID id,
+ ActivationGroupDesc desc)
+ throws ActivationException, UnknownGroupException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess(
+ "ActivationSystem.setActivationGroupDesc");
+
+ checkArgs(desc, null);
+ return getGroupEntry(id).setActivationGroupDesc(id, desc, true);
+ }
+
+ public ActivationDesc getActivationDesc(ActivationID id)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess("ActivationSystem.getActivationDesc");
+
+ return getGroupEntry(id).getActivationDesc(id);
+ }
+
+ public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id)
+ throws ActivationException, UnknownGroupException, RemoteException
+ {
+ checkShutdown();
+ RegistryImpl.checkAccess
+ ("ActivationSystem.getActivationGroupDesc");
+
+ return getGroupEntry(id).desc;
+ }
+
+ /**
+ * Shutdown the activation system. Destroys all groups spawned by
+ * the activation daemon and exits the activation daemon.
+ */
+ public void shutdown() throws AccessException {
+ RegistryImpl.checkAccess("ActivationSystem.shutdown");
+
+ Object lock = startupLock;
+ if (lock != null) {
+ synchronized (lock) {
+ // nothing
+ }
+ }
+
+ synchronized (Activation.this) {
+ if (!shuttingDown) {
+ shuttingDown = true;
+ (new Shutdown()).start();
+ }
+ }
+ }
+ }
+
+ private void checkShutdown() throws ActivationException {
+ // if the startup critical section is running, wait until it
+ // completes/fails before continuing with the remote call.
+ Object lock = startupLock;
+ if (lock != null) {
+ synchronized (lock) {
+ // nothing
+ }
+ }
+
+ if (shuttingDown == true) {
+ throw new ActivationException(
+ "activation system shutting down");
+ }
+ }
+
+ private static void unexport(Remote obj) {
+ for (;;) {
+ try {
+ if (UnicastRemoteObject.unexportObject(obj, false) == true) {
+ break;
+ } else {
+ Thread.sleep(100);
+ }
+ } catch (Exception e) {
+ continue;
+ }
+ }
+ }
+
+ /**
+ * Thread to shutdown rmid.
+ */
+ private class Shutdown extends Thread {
+ Shutdown() {
+ super("rmid Shutdown");
+ }
+
+ public void run() {
+ try {
+ /*
+ * Unexport activation system services
+ */
+ unexport(activator);
+ unexport(system);
+
+ // destroy all child processes (groups)
+ GroupEntry[] groupEntries;
+ synchronized (groupTable) {
+ groupEntries = groupTable.values().
+ toArray(new GroupEntry[groupTable.size()]);
+ }
+ for (GroupEntry groupEntry : groupEntries) {
+ groupEntry.shutdown();
+ }
+
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+
+ /*
+ * Unexport monitor safely since all processes are destroyed.
+ */
+ unexport(monitor);
+
+ /*
+ * Close log file, fix for 4243264: rmid shutdown thread
+ * interferes with remote calls in progress. Make sure
+ * the log file is only closed when it is impossible for
+ * its closure to interfere with any pending remote calls.
+ * We close the log when all objects in the rmid VM are
+ * unexported.
+ */
+ try {
+ synchronized (log) {
+ log.close();
+ }
+ } catch (IOException e) {
+ }
+
+ } finally {
+ /*
+ * Now exit... A System.exit should only be done if
+ * the RMI activation system daemon was started up
+ * by the main method below (in which should always
+ * be the case since the Activation contructor is private).
+ */
+ System.err.println(getTextResource("rmid.daemon.shutdown"));
+ System.exit(0);
+ }
+ }
+ }
+
+ /** Thread to destroy children in the event of abnormal termination. */
+ private class ShutdownHook extends Thread {
+ ShutdownHook() {
+ super("rmid ShutdownHook");
+ }
+
+ public void run() {
+ synchronized (Activation.this) {
+ shuttingDown = true;
+ }
+
+ // destroy all child processes (groups) quickly
+ synchronized (groupTable) {
+ for (GroupEntry groupEntry : groupTable.values()) {
+ groupEntry.shutdownFast();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the groupID for a given id of an object in the group.
+ * Throws UnknownObjectException if the object is not registered.
+ */
+ private ActivationGroupID getGroupID(ActivationID id)
+ throws UnknownObjectException
+ {
+ synchronized (idTable) {
+ ActivationGroupID groupID = idTable.get(id);
+ if (groupID != null) {
+ return groupID;
+ }
+ }
+ throw new UnknownObjectException("unknown object: " + id);
+ }
+
+ /**
+ * Returns the group entry for the group id. Throws
+ * UnknownGroupException if the group is not registered.
+ */
+ private GroupEntry getGroupEntry(ActivationGroupID id)
+ throws UnknownGroupException
+ {
+ if (id.getClass() == ActivationGroupID.class) {
+ synchronized (groupTable) {
+ GroupEntry entry = groupTable.get(id);
+ if (entry != null && !entry.removed) {
+ return entry;
+ }
+ }
+ }
+ throw new UnknownGroupException("group unknown");
+ }
+
+ /**
+ * Returns the group entry for the object's id. Throws
+ * UnknownObjectException if the object is not registered or the
+ * object's group is not registered.
+ */
+ private GroupEntry getGroupEntry(ActivationID id)
+ throws UnknownObjectException
+ {
+ ActivationGroupID gid = getGroupID(id);
+ synchronized (groupTable) {
+ GroupEntry entry = groupTable.get(gid);
+ if (entry != null) {
+ return entry;
+ }
+ }
+ throw new UnknownObjectException("object's group removed");
+ }
+
+ /**
+ * Container for group information: group's descriptor, group's
+ * instantiator, flag to indicate pending group creation, and
+ * table of the group's actived objects.
+ *
+ * WARNING: GroupEntry objects should not be written into log file
+ * updates. GroupEntrys are inner classes of Activation and they
+ * can not be serialized independent of this class. If the
+ * complete Activation system is written out as a log update, the
+ * point of having updates is nullified.
+ */
+ private class GroupEntry implements Serializable {
+
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 7222464070032993304L;
+ private static final int MAX_TRIES = 2;
+ private static final int NORMAL = 0;
+ private static final int CREATING = 1;
+ private static final int TERMINATE = 2;
+ private static final int TERMINATING = 3;
+
+ ActivationGroupDesc desc = null;
+ ActivationGroupID groupID = null;
+ long incarnation = 0;
+ Map<ActivationID,ObjectEntry> objects =
+ new HashMap<ActivationID,ObjectEntry>();
+ Set<ActivationID> restartSet = new HashSet<ActivationID>();
+
+ transient ActivationInstantiator group = null;
+ transient int status = NORMAL;
+ transient long waitTime = 0;
+ transient String groupName = null;
+ transient Process child = null;
+ transient boolean removed = false;
+ transient Watchdog watchdog = null;
+
+ GroupEntry(ActivationGroupID groupID, ActivationGroupDesc desc) {
+ this.groupID = groupID;
+ this.desc = desc;
+ }
+
+ void restartServices() {
+ Iterator<ActivationID> iter = null;
+
+ synchronized (this) {
+ if (restartSet.isEmpty()) {
+ return;
+ }
+
+ /*
+ * Clone the restartSet so the set does not have to be locked
+ * during iteration. Locking the restartSet could cause
+ * deadlock if an object we are restarting caused another
+ * object in this group to be activated.
+ */
+ iter = (new HashSet<ActivationID>(restartSet)).iterator();
+ }
+
+ while (iter.hasNext()) {
+ ActivationID id = iter.next();
+ try {
+ activate(id, true);
+ } catch (Exception e) {
+ if (shuttingDown) {
+ return;
+ }
+ System.err.println(
+ getTextResource("rmid.restart.service.warning"));
+ e.printStackTrace();
+ }
+ }
+ }
+
+ synchronized void activeGroup(ActivationInstantiator inst,
+ long instIncarnation)
+ throws ActivationException, UnknownGroupException
+ {
+ if (incarnation != instIncarnation) {
+ throw new ActivationException("invalid incarnation");
+ }
+
+ if (group != null) {
+ if (group.equals(inst)) {
+ return;
+ } else {
+ throw new ActivationException("group already active");
+ }
+ }
+
+ if (child != null && status != CREATING) {
+ throw new ActivationException("group not being created");
+ }
+
+ group = inst;
+ status = NORMAL;
+ notifyAll();
+ }
+
+ private void checkRemoved() throws UnknownGroupException {
+ if (removed) {
+ throw new UnknownGroupException("group removed");
+ }
+ }
+
+ private ObjectEntry getObjectEntry(ActivationID id)
+ throws UnknownObjectException
+ {
+ if (removed) {
+ throw new UnknownObjectException("object's group removed");
+ }
+ ObjectEntry objEntry = objects.get(id);
+ if (objEntry == null) {
+ throw new UnknownObjectException("object unknown");
+ }
+ return objEntry;
+ }
+
+ synchronized void registerObject(ActivationID id,
+ ActivationDesc desc,
+ boolean addRecord)
+ throws UnknownGroupException, ActivationException
+ {
+ checkRemoved();
+ objects.put(id, new ObjectEntry(desc));
+ if (desc.getRestartMode() == true) {
+ restartSet.add(id);
+ }
+
+ // table insertion must take place before log update
+ synchronized (idTable) {
+ idTable.put(id, groupID);
+ }
+
+ if (addRecord) {
+ addLogRecord(new LogRegisterObject(id, desc));
+ }
+ }
+
+ synchronized void unregisterObject(ActivationID id, boolean addRecord)
+ throws UnknownGroupException, ActivationException
+ {
+ ObjectEntry objEntry = getObjectEntry(id);
+ objEntry.removed = true;
+ objects.remove(id);
+ if (objEntry.desc.getRestartMode() == true) {
+ restartSet.remove(id);
+ }
+
+ // table insertion must take place before log update
+ synchronized (idTable) {
+ idTable.remove(id);
+ }
+ if (addRecord) {
+ addLogRecord(new LogUnregisterObject(id));
+ }
+ }
+
+ synchronized void unregisterGroup(boolean addRecord)
+ throws UnknownGroupException, ActivationException
+ {
+ checkRemoved();
+ removed = true;
+ for (Map.Entry<ActivationID,ObjectEntry> entry :
+ objects.entrySet())
+ {
+ ActivationID id = entry.getKey();
+ synchronized (idTable) {
+ idTable.remove(id);
+ }
+ ObjectEntry objEntry = entry.getValue();
+ objEntry.removed = true;
+ }
+ objects.clear();
+ restartSet.clear();
+ reset();
+ childGone();
+
+ // removal should be recorded before log update
+ if (addRecord) {
+ addLogRecord(new LogUnregisterGroup(groupID));
+ }
+ }
+
+ synchronized ActivationDesc setActivationDesc(ActivationID id,
+ ActivationDesc desc,
+ boolean addRecord)
+ throws UnknownObjectException, UnknownGroupException,
+ ActivationException
+ {
+ ObjectEntry objEntry = getObjectEntry(id);
+ ActivationDesc oldDesc = objEntry.desc;
+ objEntry.desc = desc;
+ if (desc.getRestartMode() == true) {
+ restartSet.add(id);
+ } else {
+ restartSet.remove(id);
+ }
+ // restart information should be recorded before log update
+ if (addRecord) {
+ addLogRecord(new LogUpdateDesc(id, desc));
+ }
+
+ return oldDesc;
+ }
+
+ synchronized ActivationDesc getActivationDesc(ActivationID id)
+ throws UnknownObjectException, UnknownGroupException
+ {
+ return getObjectEntry(id).desc;
+ }
+
+ synchronized ActivationGroupDesc setActivationGroupDesc(
+ ActivationGroupID id,
+ ActivationGroupDesc desc,
+ boolean addRecord)
+ throws UnknownGroupException, ActivationException
+ {
+ checkRemoved();
+ ActivationGroupDesc oldDesc = this.desc;
+ this.desc = desc;
+ // state update should occur before log update
+ if (addRecord) {
+ addLogRecord(new LogUpdateGroupDesc(id, desc));
+ }
+ return oldDesc;
+ }
+
+ synchronized void inactiveGroup(long incarnation, boolean failure)
+ throws UnknownGroupException
+ {
+ checkRemoved();
+ if (this.incarnation != incarnation) {
+ throw new UnknownGroupException("invalid incarnation");
+ }
+
+ reset();
+ if (failure) {
+ terminate();
+ } else if (child != null && status == NORMAL) {
+ status = TERMINATE;
+ watchdog.noRestart();
+ }
+ }
+
+ synchronized void activeObject(ActivationID id,
+ MarshalledObject<? extends Remote> mobj)
+ throws UnknownObjectException
+ {
+ getObjectEntry(id).stub = mobj;
+ }
+
+ synchronized void inactiveObject(ActivationID id)
+ throws UnknownObjectException
+ {
+ getObjectEntry(id).reset();
+ }
+
+ private synchronized void reset() {
+ group = null;
+ for (ObjectEntry objectEntry : objects.values()) {
+ objectEntry.reset();
+ }
+ }
+
+ private void childGone() {
+ if (child != null) {
+ child = null;
+ watchdog.dispose();
+ watchdog = null;
+ status = NORMAL;
+ notifyAll();
+ }
+ }
+
+ private void terminate() {
+ if (child != null && status != TERMINATING) {
+ child.destroy();
+ status = TERMINATING;
+ waitTime = System.currentTimeMillis() + groupTimeout;
+ notifyAll();
+ }
+ }
+
+ private void await() {
+ while (true) {
+ switch (status) {
+ case NORMAL:
+ return;
+ case TERMINATE:
+ terminate();
+ case TERMINATING:
+ try {
+ child.exitValue();
+ } catch (IllegalThreadStateException e) {
+ long now = System.currentTimeMillis();
+ if (waitTime > now) {
+ try {
+ wait(waitTime - now);
+ } catch (InterruptedException ee) {
+ }
+ continue;
+ }
+ // REMIND: print message that group did not terminate?
+ }
+ childGone();
+ return;
+ case CREATING:
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ // no synchronization to avoid delay wrt getInstantiator
+ void shutdownFast() {
+ Process p = child;
+ if (p != null) {
+ p.destroy();
+ }
+ }
+
+ synchronized void shutdown() {
+ reset();
+ terminate();
+ await();
+ }
+
+ MarshalledObject<? extends Remote> activate(ActivationID id,
+ boolean force)
+ throws ActivationException
+ {
+ Exception detail = null;
+
+ /*
+ * Attempt to activate object and reattempt (several times)
+ * if activation fails due to communication problems.
+ */
+ for (int tries = MAX_TRIES; tries > 0; tries--) {
+ ActivationInstantiator inst;
+ long currentIncarnation;
+
+ // look up object to activate
+ ObjectEntry objEntry;
+ synchronized (this) {
+ objEntry = getObjectEntry(id);
+ // if not forcing activation, return cached stub
+ if (!force && objEntry.stub != null) {
+ return objEntry.stub;
+ }
+ inst = getInstantiator(groupID);
+ currentIncarnation = incarnation;
+ }
+
+ boolean groupInactive = false;
+ boolean failure = false;
+ // activate object
+ try {
+ return objEntry.activate(id, force, inst);
+ } catch (NoSuchObjectException e) {
+ groupInactive = true;
+ detail = e;
+ } catch (ConnectException e) {
+ groupInactive = true;
+ failure = true;
+ detail = e;
+ } catch (ConnectIOException e) {
+ groupInactive = true;
+ failure = true;
+ detail = e;
+ } catch (InactiveGroupException e) {
+ groupInactive = true;
+ detail = e;
+ } catch (RemoteException e) {
+ // REMIND: wait some here before continuing?
+ if (detail == null) {
+ detail = e;
+ }
+ }
+
+ if (groupInactive) {
+ // group has failed or is inactive; mark inactive
+ try {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.group.inactive"),
+ detail.toString()));
+ detail.printStackTrace();
+ getGroupEntry(groupID).
+ inactiveGroup(currentIncarnation, failure);
+ } catch (UnknownGroupException e) {
+ // not a problem
+ }
+ }
+ }
+
+ /**
+ * signal that group activation failed, nested exception
+ * specifies what exception occurred when the group did not
+ * activate
+ */
+ throw new ActivationException("object activation failed after " +
+ MAX_TRIES + " tries", detail);
+ }
+
+ /**
+ * Returns the instantiator for the group specified by id and
+ * entry. If the group is currently inactive, exec some
+ * bootstrap code to create the group.
+ */
+ private ActivationInstantiator getInstantiator(ActivationGroupID id)
+ throws ActivationException
+ {
+ assert Thread.holdsLock(this);
+
+ await();
+ if (group != null) {
+ return group;
+ }
+ checkRemoved();
+ boolean acquired = false;
+
+ try {
+ groupName = Pstartgroup();
+ acquired = true;
+ String[] argv = activationArgs(desc);
+ checkArgs(desc, argv);
+
+ if (debugExec) {
+ StringBuffer sb = new StringBuffer(argv[0]);
+ int j;
+ for (j = 1; j < argv.length; j++) {
+ sb.append(' ');
+ sb.append(argv[j]);
+ }
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.exec.command"),
+ sb.toString()));
+ }
+
+ try {
+ child = Runtime.getRuntime().exec(argv);
+ status = CREATING;
+ ++incarnation;
+ watchdog = new Watchdog();
+ watchdog.start();
+ addLogRecord(new LogGroupIncarnation(id, incarnation));
+
+ // handle child I/O streams before writing to child
+ PipeWriter.plugTogetherPair
+ (child.getInputStream(), System.out,
+ child.getErrorStream(), System.err);
+
+ MarshalOutputStream out =
+ new MarshalOutputStream(child.getOutputStream());
+ out.writeObject(id);
+ out.writeObject(desc);
+ out.writeLong(incarnation);
+ out.flush();
+ out.close();
+
+
+ } catch (IOException e) {
+ terminate();
+ throw new ActivationException(
+ "unable to create activation group", e);
+ }
+
+ try {
+ long now = System.currentTimeMillis();
+ long stop = now + execTimeout;
+ do {
+ wait(stop - now);
+ if (group != null) {
+ return group;
+ }
+ now = System.currentTimeMillis();
+ } while (status == CREATING && now < stop);
+ } catch (InterruptedException e) {
+ }
+
+ terminate();
+ throw new ActivationException(
+ (removed ?
+ "activation group unregistered" :
+ "timeout creating child process"));
+ } finally {
+ if (acquired) {
+ Vstartgroup();
+ }
+ }
+ }
+
+ /**
+ * Waits for process termination and then restarts services.
+ */
+ private class Watchdog extends Thread {
+ private final Process groupProcess = child;
+ private final long groupIncarnation = incarnation;
+ private boolean canInterrupt = true;
+ private boolean shouldQuit = false;
+ private boolean shouldRestart = true;
+
+ Watchdog() {
+ super("WatchDog-" + groupName + "-" + incarnation);
+ setDaemon(true);
+ }
+
+ public void run() {
+
+ if (shouldQuit) {
+ return;
+ }
+
+ /*
+ * Wait for the group to crash or exit.
+ */
+ try {
+ groupProcess.waitFor();
+ } catch (InterruptedException exit) {
+ return;
+ }
+
+ boolean restart = false;
+ synchronized (GroupEntry.this) {
+ if (shouldQuit) {
+ return;
+ }
+ canInterrupt = false;
+ interrupted(); // clear interrupt bit
+ /*
+ * Since the group crashed, we should
+ * reset the entry before activating objects
+ */
+ if (groupIncarnation == incarnation) {
+ restart = shouldRestart && !shuttingDown;
+ reset();
+ childGone();
+ }
+ }
+
+ /*
+ * Activate those objects that require restarting
+ * after a crash.
+ */
+ if (restart) {
+ restartServices();
+ }
+ }
+
+ /**
+ * Marks this thread as one that is no longer needed.
+ * If the thread is in a state in which it can be interrupted,
+ * then the thread is interrupted.
+ */
+ void dispose() {
+ shouldQuit = true;
+ if (canInterrupt) {
+ interrupt();
+ }
+ }
+
+ /**
+ * Marks this thread as no longer needing to restart objects.
+ */
+ void noRestart() {
+ shouldRestart = false;
+ }
+ }
+ }
+
+ private String[] activationArgs(ActivationGroupDesc desc) {
+ ActivationGroupDesc.CommandEnvironment cmdenv;
+ cmdenv = desc.getCommandEnvironment();
+
+ // argv is the literal command to exec
+ List<String> argv = new ArrayList<String>();
+
+ // Command name/path
+ argv.add((cmdenv != null && cmdenv.getCommandPath() != null)
+ ? cmdenv.getCommandPath()
+ : command[0]);
+
+ // Group-specific command options
+ if (cmdenv != null && cmdenv.getCommandOptions() != null) {
+ argv.addAll(Arrays.asList(cmdenv.getCommandOptions()));
+ }
+
+ // Properties become -D parameters
+ Properties props = desc.getPropertyOverrides();
+ if (props != null) {
+ for (Enumeration<?> p = props.propertyNames();
+ p.hasMoreElements();)
+ {
+ String name = (String) p.nextElement();
+ /* Note on quoting: it would be wrong
+ * here, since argv will be passed to
+ * Runtime.exec, which should not parse
+ * arguments or split on whitespace.
+ */
+ argv.add("-D" + name + "=" + props.getProperty(name));
+ }
+ }
+
+ /* Finally, rmid-global command options (e.g. -C options)
+ * and the classname
+ */
+ for (int i = 1; i < command.length; i++) {
+ argv.add(command[i]);
+ }
+
+ String[] realArgv = new String[argv.size()];
+ System.arraycopy(argv.toArray(), 0, realArgv, 0, realArgv.length);
+
+ return realArgv;
+ }
+
+ private void checkArgs(ActivationGroupDesc desc, String[] cmd)
+ throws SecurityException, ActivationException
+ {
+ /*
+ * Check exec command using execPolicy object
+ */
+ if (execPolicyMethod != null) {
+ if (cmd == null) {
+ cmd = activationArgs(desc);
+ }
+ try {
+ execPolicyMethod.invoke(execPolicy, desc, cmd);
+ } catch (InvocationTargetException e) {
+ Throwable targetException = e.getTargetException();
+ if (targetException instanceof SecurityException) {
+ throw (SecurityException) targetException;
+ } else {
+ throw new ActivationException(
+ execPolicyMethod.getName() + ": unexpected exception",
+ e);
+ }
+ } catch (Exception e) {
+ throw new ActivationException(
+ execPolicyMethod.getName() + ": unexpected exception", e);
+ }
+ }
+ }
+
+ private static class ObjectEntry implements Serializable {
+
+ private static final long serialVersionUID = -5500114225321357856L;
+
+ /** descriptor for object */
+ ActivationDesc desc;
+ /** the stub (if active) */
+ volatile transient MarshalledObject<? extends Remote> stub = null;
+ volatile transient boolean removed = false;
+
+ ObjectEntry(ActivationDesc desc) {
+ this.desc = desc;
+ }
+
+ synchronized MarshalledObject<? extends Remote>
+ activate(ActivationID id,
+ boolean force,
+ ActivationInstantiator inst)
+ throws RemoteException, ActivationException
+ {
+ MarshalledObject<? extends Remote> nstub = stub;
+ if (removed) {
+ throw new UnknownObjectException("object removed");
+ } else if (!force && nstub != null) {
+ return nstub;
+ }
+
+ nstub = inst.newInstance(id, desc);
+ stub = nstub;
+ /*
+ * stub could be set to null by a group reset, so return
+ * the newstub here to prevent returning null.
+ */
+ return nstub;
+ }
+
+ void reset() {
+ stub = null;
+ }
+ }
+
+ /**
+ * Add a record to the activation log. If the number of updates
+ * passes a predetermined threshold, record a snapshot.
+ */
+ private void addLogRecord(LogRecord rec) throws ActivationException {
+ synchronized (log) {
+ checkShutdown();
+ try {
+ log.update(rec, true);
+ } catch (Exception e) {
+ numUpdates = snapshotInterval;
+ System.err.println(getTextResource("rmid.log.update.warning"));
+ e.printStackTrace();
+ }
+ if (++numUpdates < snapshotInterval) {
+ return;
+ }
+ try {
+ log.snapshot(this);
+ numUpdates = 0;
+ } catch (Exception e) {
+ System.err.println(
+ getTextResource("rmid.log.snapshot.warning"));
+ e.printStackTrace();
+ try {
+ // shutdown activation system because snapshot failed
+ system.shutdown();
+ } catch (RemoteException ignore) {
+ // can't happen
+ }
+ // warn the client of the original update problem
+ throw new ActivationException("log snapshot failed", e);
+ }
+ }
+ }
+
+ /**
+ * Handler for the log that knows how to take the initial snapshot
+ * and apply an update (a LogRecord) to the current state.
+ */
+ private static class ActLogHandler extends LogHandler {
+
+ ActLogHandler() {
+ }
+
+ public Object initialSnapshot()
+ {
+ /**
+ * Return an empty Activation object. Log will update
+ * this object with recovered state.
+ */
+ return new Activation();
+ }
+
+ public Object applyUpdate(Object update, Object state)
+ throws Exception
+ {
+ return ((LogRecord) update).apply(state);
+ }
+
+ }
+
+ /**
+ * Abstract class for all log records. The subclass contains
+ * specific update information and implements the apply method
+ * that applys the update information contained in the record
+ * to the current state.
+ */
+ private static abstract class LogRecord implements Serializable {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 8395140512322687529L;
+ abstract Object apply(Object state) throws Exception;
+ }
+
+ /**
+ * Log record for registering an object.
+ */
+ private static class LogRegisterObject extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = -6280336276146085143L;
+ private ActivationID id;
+ private ActivationDesc desc;
+
+ LogRegisterObject(ActivationID id, ActivationDesc desc) {
+ this.id = id;
+ this.desc = desc;
+ }
+
+ Object apply(Object state) {
+ try {
+ ((Activation) state).getGroupEntry(desc.getGroupID()).
+ registerObject(id, desc, false);
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogRegisterObject"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Log record for unregistering an object.
+ */
+ private static class LogUnregisterObject extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 6269824097396935501L;
+ private ActivationID id;
+
+ LogUnregisterObject(ActivationID id) {
+ this.id = id;
+ }
+
+ Object apply(Object state) {
+ try {
+ ((Activation) state).getGroupEntry(id).
+ unregisterObject(id, false);
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogUnregisterObject"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Log record for registering a group.
+ */
+ private static class LogRegisterGroup extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = -1966827458515403625L;
+ private ActivationGroupID id;
+ private ActivationGroupDesc desc;
+
+ LogRegisterGroup(ActivationGroupID id, ActivationGroupDesc desc) {
+ this.id = id;
+ this.desc = desc;
+ }
+
+ Object apply(Object state) {
+ // modify state directly; cant ask a nonexistent GroupEntry
+ // to register itself.
+ ((Activation) state).groupTable.put(id, ((Activation) state).new
+ GroupEntry(id, desc));
+ return state;
+ }
+ }
+
+ /**
+ * Log record for udpating an activation desc
+ */
+ private static class LogUpdateDesc extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 545511539051179885L;
+
+ private ActivationID id;
+ private ActivationDesc desc;
+
+ LogUpdateDesc(ActivationID id, ActivationDesc desc) {
+ this.id = id;
+ this.desc = desc;
+ }
+
+ Object apply(Object state) {
+ try {
+ ((Activation) state).getGroupEntry(id).
+ setActivationDesc(id, desc, false);
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogUpdateDesc"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Log record for unregistering a group.
+ */
+ private static class LogUpdateGroupDesc extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = -1271300989218424337L;
+ private ActivationGroupID id;
+ private ActivationGroupDesc desc;
+
+ LogUpdateGroupDesc(ActivationGroupID id, ActivationGroupDesc desc) {
+ this.id = id;
+ this.desc = desc;
+ }
+
+ Object apply(Object state) {
+ try {
+ ((Activation) state).getGroupEntry(id).
+ setActivationGroupDesc(id, desc, false);
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogUpdateGroupDesc"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Log record for unregistering a group.
+ */
+ private static class LogUnregisterGroup extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = -3356306586522147344L;
+ private ActivationGroupID id;
+
+ LogUnregisterGroup(ActivationGroupID id) {
+ this.id = id;
+ }
+
+ Object apply(Object state) {
+ GroupEntry entry = ((Activation) state).groupTable.remove(id);
+ try {
+ entry.unregisterGroup(false);
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogUnregisterGroup"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Log record for an active group incarnation
+ */
+ private static class LogGroupIncarnation extends LogRecord {
+ /** indicate compatibility with JDK 1.2 version of class */
+ private static final long serialVersionUID = 4146872747377631897L;
+ private ActivationGroupID id;
+ private long inc;
+
+ LogGroupIncarnation(ActivationGroupID id, long inc) {
+ this.id = id;
+ this.inc = inc;
+ }
+
+ Object apply(Object state) {
+ try {
+ GroupEntry entry = ((Activation) state).getGroupEntry(id);
+ entry.incarnation = inc;
+ } catch (Exception ignore) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.log.recover.warning"),
+ "LogGroupIncarnation"));
+ ignore.printStackTrace();
+ }
+ return state;
+ }
+ }
+
+ /**
+ * Initialize command to exec a default group.
+ */
+ private void initCommand(String[] childArgs) {
+ command = new String[childArgs.length + 2];
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ try {
+ command[0] = System.getProperty("java.home") +
+ File.separator + "bin" + File.separator + "java";
+ } catch (Exception e) {
+ System.err.println(
+ getTextResource("rmid.unfound.java.home.property"));
+ command[0] = "java";
+ }
+ return null;
+ }
+ });
+ System.arraycopy(childArgs, 0, command, 1, childArgs.length);
+ command[command.length-1] = "sun.rmi.server.ActivationGroupInit";
+ }
+
+ private static void bomb(String error) {
+ System.err.println("rmid: " + error); // $NON-NLS$
+ System.err.println(MessageFormat.format(getTextResource("rmid.usage"),
+ "rmid"));
+ System.exit(1);
+ }
+
+ /**
+ * The default policy for checking a command before it is executed
+ * makes sure the appropriate com.sun.rmi.rmid.ExecPermission and
+ * set of com.sun.rmi.rmid.ExecOptionPermissions have been granted.
+ */
+ public static class DefaultExecPolicy {
+
+ public void checkExecCommand(ActivationGroupDesc desc, String[] cmd)
+ throws SecurityException
+ {
+ PermissionCollection perms = getExecPermissions();
+
+ /*
+ * Check properties overrides.
+ */
+ Properties props = desc.getPropertyOverrides();
+ if (props != null) {
+ Enumeration<?> p = props.propertyNames();
+ while (p.hasMoreElements()) {
+ String name = (String) p.nextElement();
+ String value = props.getProperty(name);
+ String option = "-D" + name + "=" + value;
+ try {
+ checkPermission(perms,
+ new ExecOptionPermission(option));
+ } catch (AccessControlException e) {
+ if (value.equals("")) {
+ checkPermission(perms,
+ new ExecOptionPermission("-D" + name));
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check group class name (allow nothing but the default),
+ * code location (must be null), and data (must be null).
+ */
+ String groupClassName = desc.getClassName();
+ if ((groupClassName != null &&
+ !groupClassName.equals(
+ ActivationGroupImpl.class.getName())) ||
+ (desc.getLocation() != null) ||
+ (desc.getData() != null))
+ {
+ throw new AccessControlException(
+ "access denied (custom group implementation not allowed)");
+ }
+
+ /*
+ * If group descriptor has a command environment, check
+ * command and options.
+ */
+ ActivationGroupDesc.CommandEnvironment cmdenv;
+ cmdenv = desc.getCommandEnvironment();
+ if (cmdenv != null) {
+ String path = cmdenv.getCommandPath();
+ if (path != null) {
+ checkPermission(perms, new ExecPermission(path));
+ }
+
+ String[] options = cmdenv.getCommandOptions();
+ if (options != null) {
+ for (String option : options) {
+ checkPermission(perms,
+ new ExecOptionPermission(option));
+ }
+ }
+ }
+ }
+
+ /**
+ * Prints warning message if installed Policy is the default Policy
+ * implementation and globally granted permissions do not include
+ * AllPermission or any ExecPermissions/ExecOptionPermissions.
+ */
+ static void checkConfiguration() {
+ Policy policy =
+ AccessController.doPrivileged(new PrivilegedAction<Policy>() {
+ public Policy run() {
+ return Policy.getPolicy();
+ }
+ });
+ if (!(policy instanceof PolicyFile)) {
+ return;
+ }
+ PermissionCollection perms = getExecPermissions();
+ for (Enumeration<Permission> e = perms.elements();
+ e.hasMoreElements();)
+ {
+ Permission p = e.nextElement();
+ if (p instanceof AllPermission ||
+ p instanceof ExecPermission ||
+ p instanceof ExecOptionPermission)
+ {
+ return;
+ }
+ }
+ System.err.println(getTextResource("rmid.exec.perms.inadequate"));
+ }
+
+ private static PermissionCollection getExecPermissions() {
+ /*
+ * The approach used here is taken from the similar method
+ * getLoaderAccessControlContext() in the class
+ * sun.rmi.server.LoaderHandler.
+ */
+
+ // obtain permissions granted to all code in current policy
+ PermissionCollection perms = AccessController.doPrivileged(
+ new PrivilegedAction<PermissionCollection>() {
+ public PermissionCollection run() {
+ CodeSource codesource =
+ new CodeSource(null, (Certificate[]) null);
+ Policy p = Policy.getPolicy();
+ if (p != null) {
+ return p.getPermissions(codesource);
+ } else {
+ return new Permissions();
+ }
+ }
+ });
+
+ return perms;
+ }
+
+ private static void checkPermission(PermissionCollection perms,
+ Permission p)
+ throws AccessControlException
+ {
+ if (!perms.implies(p)) {
+ throw new AccessControlException(
+ "access denied " + p.toString());
+ }
+ }
+ }
+
+ /**
+ * Main program to start the activation system. <br>
+ * The usage is as follows: rmid [-port num] [-log dir].
+ */
+ public static void main(String[] args) {
+ boolean stop = false;
+
+ // Create and install the security manager if one is not installed
+ // already.
+ if (System.getSecurityManager() == null) {
+ System.setSecurityManager(new SecurityManager());
+ }
+
+ try {
+ int port = ActivationSystem.SYSTEM_PORT;
+ RMIServerSocketFactory ssf = null;
+
+ /*
+ * If rmid has an inherited channel (meaning that it was
+ * launched from inetd), set the server socket factory to
+ * return the inherited server socket.
+ **/
+ Channel inheritedChannel = AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Channel>() {
+ public Channel run() throws IOException {
+ return System.inheritedChannel();
+ }
+ });
+
+ if (inheritedChannel != null &&
+ inheritedChannel instanceof ServerSocketChannel)
+ {
+ /*
+ * Redirect System.err output to a file.
+ */
+ AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Void>() {
+ public Void run() throws IOException {
+ File file =
+ File.createTempFile("rmid-err", null, null);
+ PrintStream errStream =
+ new PrintStream(new FileOutputStream(file));
+ System.setErr(errStream);
+ return null;
+ }
+ });
+
+ ServerSocket serverSocket =
+ ((ServerSocketChannel) inheritedChannel).socket();
+ port = serverSocket.getLocalPort();
+ ssf = new ActivationServerSocketFactory(serverSocket);
+
+ System.err.println(new Date());
+ System.err.println(getTextResource(
+ "rmid.inherited.channel.info") +
+ ": " + inheritedChannel);
+ }
+
+ String log = null;
+ List<String> childArgs = new ArrayList<String>();
+
+ /*
+ * Parse arguments
+ */
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("-port")) {
+ if (ssf != null) {
+ bomb(getTextResource("rmid.syntax.port.badarg"));
+ }
+ if ((i + 1) < args.length) {
+ try {
+ port = Integer.parseInt(args[++i]);
+ } catch (NumberFormatException nfe) {
+ bomb(getTextResource("rmid.syntax.port.badnumber"));
+ }
+ } else {
+ bomb(getTextResource("rmid.syntax.port.missing"));
+ }
+
+ } else if (args[i].equals("-log")) {
+ if ((i + 1) < args.length) {
+ log = args[++i];
+ } else {
+ bomb(getTextResource("rmid.syntax.log.missing"));
+ }
+
+ } else if (args[i].equals("-stop")) {
+ stop = true;
+
+ } else if (args[i].startsWith("-C")) {
+ childArgs.add(args[i].substring(2));
+
+ } else {
+ bomb(MessageFormat.format(
+ getTextResource("rmid.syntax.illegal.option"),
+ args[i]));
+ }
+ }
+
+ if (log == null) {
+ if (ssf != null) {
+ bomb(getTextResource("rmid.syntax.log.required"));
+ } else {
+ log = "log";
+ }
+ }
+
+ debugExec = AccessController.doPrivileged(
+ new GetBooleanAction("sun.rmi.server.activation.debugExec"));
+
+ /**
+ * Determine class name for activation exec policy (if any).
+ */
+ String execPolicyClassName = AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.activation.execPolicy", null));
+ if (execPolicyClassName == null) {
+ if (!stop) {
+ DefaultExecPolicy.checkConfiguration();
+ }
+ execPolicyClassName = "default";
+ }
+
+ /**
+ * Initialize method for activation exec policy.
+ */
+ if (!execPolicyClassName.equals("none")) {
+ if (execPolicyClassName.equals("") ||
+ execPolicyClassName.equals("default"))
+ {
+ execPolicyClassName = DefaultExecPolicy.class.getName();
+ }
+
+ try {
+ Class<?> execPolicyClass =
+ RMIClassLoader.loadClass(execPolicyClassName);
+ execPolicy = execPolicyClass.newInstance();
+ execPolicyMethod =
+ execPolicyClass.getMethod("checkExecCommand",
+ ActivationGroupDesc.class,
+ String[].class);
+ } catch (Exception e) {
+ if (debugExec) {
+ System.err.println(
+ getTextResource("rmid.exec.policy.exception"));
+ e.printStackTrace();
+ }
+ bomb(getTextResource("rmid.exec.policy.invalid"));
+ }
+ }
+
+ if (stop == true) {
+ final int finalPort = port;
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ System.setProperty("java.rmi.activation.port",
+ Integer.toString(finalPort));
+ return null;
+ }
+ });
+ ActivationSystem system = ActivationGroup.getSystem();
+ system.shutdown();
+ System.exit(0);
+ }
+
+ /*
+ * Fix for 4173960: Create and initialize activation using
+ * a static method, startActivation, which will build the
+ * Activation state in two ways: if when rmid is run, no
+ * log file is found, the ActLogHandler.recover(...)
+ * method will create a new Activation instance.
+ * Alternatively, if a logfile is available, a serialized
+ * instance of activation will be read from the log's
+ * snapshot file. Log updates will be applied to this
+ * Activation object until rmid's state has been fully
+ * recovered. In either case, only one instance of
+ * Activation is created.
+ */
+ startActivation(port, ssf, log,
+ childArgs.toArray(new String[childArgs.size()]));
+
+ // prevent activator from exiting
+ while (true) {
+ try {
+ Thread.sleep(Long.MAX_VALUE);
+ } catch (InterruptedException e) {
+ }
+ }
+ } catch (Exception e) {
+ System.err.println(
+ MessageFormat.format(
+ getTextResource("rmid.unexpected.exception"), e));
+ e.printStackTrace();
+ }
+ System.exit(1);
+ }
+
+ /**
+ * Retrieves text resources from the locale-specific properties file.
+ */
+ private static String getTextResource(String key) {
+ if (Activation.resources == null) {
+ try {
+ Activation.resources = ResourceBundle.getBundle(
+ "sun.rmi.server.resources.rmid");
+ } catch (MissingResourceException mre) {
+ }
+ if (Activation.resources == null) {
+ // throwing an Error is a bit extreme, methinks
+ return ("[missing resource file: " + key + "]");
+ }
+ }
+
+ String val = null;
+ try {
+ val = Activation.resources.getString (key);
+ } catch (MissingResourceException mre) {
+ }
+
+ if (val == null) {
+ return ("[missing resource: " + key + "]");
+ } else {
+ return val;
+ }
+ }
+
+ /*
+ * Dijkstra semaphore operations to limit the number of subprocesses
+ * rmid attempts to make at once.
+ */
+ /**
+ * Acquire the group semaphore and return a group name. Each
+ * Pstartgroup must be followed by a Vstartgroup. The calling thread
+ * will wait until there are fewer than <code>N</code> other threads
+ * holding the group semaphore. The calling thread will then acquire
+ * the semaphore and return.
+ */
+ private synchronized String Pstartgroup() throws ActivationException {
+ while (true) {
+ checkShutdown();
+ // Wait until positive, then decrement.
+ if (groupSemaphore > 0) {
+ groupSemaphore--;
+ return "Group-" + groupCounter++;
+ }
+
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ /**
+ * Release the group semaphore. Every P operation must be
+ * followed by a V operation. This may cause another thread to
+ * wake up and return from its P operation.
+ */
+ private synchronized void Vstartgroup() {
+ // Increment and notify a waiter (not necessarily FIFO).
+ groupSemaphore++;
+ notifyAll();
+ }
+
+ /**
+ * A server socket factory to use when rmid is launched via 'inetd'
+ * with 'wait' status. This socket factory's 'createServerSocket'
+ * method returns the server socket specified during construction that
+ * is specialized to delay accepting requests until the
+ * 'initDone' flag is 'true'. The server socket supplied to
+ * the constructor should be the server socket obtained from the
+ * ServerSocketChannel returned from the 'System.inheritedChannel'
+ * method.
+ **/
+ private static class ActivationServerSocketFactory
+ implements RMIServerSocketFactory
+ {
+ private final ServerSocket serverSocket;
+
+ /**
+ * Constructs an 'ActivationServerSocketFactory' with the specified
+ * 'serverSocket'.
+ **/
+ ActivationServerSocketFactory(ServerSocket serverSocket) {
+ this.serverSocket = serverSocket;
+ }
+
+ /**
+ * Returns the server socket specified during construction wrapped
+ * in a 'DelayedAcceptServerSocket'.
+ **/
+ public ServerSocket createServerSocket(int port)
+ throws IOException
+ {
+ return new DelayedAcceptServerSocket(serverSocket);
+ }
+
+ }
+
+ /**
+ * A server socket that delegates all public methods to the underlying
+ * server socket specified at construction. The accept method is
+ * overridden to delay calling accept on the underlying server socket
+ * until the 'initDone' flag is 'true'.
+ **/
+ private static class DelayedAcceptServerSocket extends ServerSocket {
+
+ private final ServerSocket serverSocket;
+
+ DelayedAcceptServerSocket(ServerSocket serverSocket)
+ throws IOException
+ {
+ this.serverSocket = serverSocket;
+ }
+
+ public void bind(SocketAddress endpoint) throws IOException {
+ serverSocket.bind(endpoint);
+ }
+
+ public void bind(SocketAddress endpoint, int backlog)
+ throws IOException
+ {
+ serverSocket.bind(endpoint, backlog);
+ }
+
+ public InetAddress getInetAddress() {
+ return serverSocket.getInetAddress();
+ }
+
+ public int getLocalPort() {
+ return serverSocket.getLocalPort();
+ }
+
+ public SocketAddress getLocalSocketAddress() {
+ return serverSocket.getLocalSocketAddress();
+ }
+
+ /**
+ * Delays calling accept on the underlying server socket until the
+ * remote service is bound in the registry.
+ **/
+ public Socket accept() throws IOException {
+ synchronized (initLock) {
+ try {
+ while (!initDone) {
+ initLock.wait();
+ }
+ } catch (InterruptedException ignore) {
+ throw new AssertionError(ignore);
+ }
+ }
+ return serverSocket.accept();
+ }
+
+ public void close() throws IOException {
+ serverSocket.close();
+ }
+
+ public ServerSocketChannel getChannel() {
+ return serverSocket.getChannel();
+ }
+
+ public boolean isBound() {
+ return serverSocket.isBound();
+ }
+
+ public boolean isClosed() {
+ return serverSocket.isClosed();
+ }
+
+ public void setSoTimeout(int timeout)
+ throws SocketException
+ {
+ serverSocket.setSoTimeout(timeout);
+ }
+
+ public int getSoTimeout() throws IOException {
+ return serverSocket.getSoTimeout();
+ }
+
+ public void setReuseAddress(boolean on) throws SocketException {
+ serverSocket.setReuseAddress(on);
+ }
+
+ public boolean getReuseAddress() throws SocketException {
+ return serverSocket.getReuseAddress();
+ }
+
+ public String toString() {
+ return serverSocket.toString();
+ }
+
+ public void setReceiveBufferSize(int size)
+ throws SocketException
+ {
+ serverSocket.setReceiveBufferSize(size);
+ }
+
+ public int getReceiveBufferSize()
+ throws SocketException
+ {
+ return serverSocket.getReceiveBufferSize();
+ }
+ }
+}
+
+/**
+ * PipeWriter plugs together two pairs of input and output streams by
+ * providing readers for input streams and writing through to
+ * appropriate output streams. Both output streams are annotated on a
+ * per-line basis.
+ *
+ * @author Laird Dornin, much code borrowed from Peter Jones, Ken
+ * Arnold and Ann Wollrath.
+ */
+class PipeWriter implements Runnable {
+
+ /** stream used for buffering lines */
+ private ByteArrayOutputStream bufOut;
+
+ /** count since last separator */
+ private int cLast;
+
+ /** current chunk of input being compared to lineSeparator.*/
+ private byte[] currSep;
+
+ private PrintWriter out;
+ private InputStream in;
+
+ private String pipeString;
+ private String execString;
+
+ private static String lineSeparator;
+ private static int lineSeparatorLength;
+
+ private static int numExecs = 0;
+
+ static {
+ lineSeparator = AccessController.doPrivileged(
+ new GetPropertyAction("line.separator"));
+ lineSeparatorLength = lineSeparator.length();
+ }
+
+ /**
+ * Create a new PipeWriter object. All methods of PipeWriter,
+ * except plugTogetherPair, are only accesible to PipeWriter
+ * itself. Synchronization is unnecessary on functions that will
+ * only be used internally in PipeWriter.
+ *
+ * @param in input stream from which pipe input flows
+ * @param out output stream to which log messages will be sent
+ * @param dest String which tags output stream as 'out' or 'err'
+ * @param nExecs number of execed processes, Activation groups.
+ */
+ private PipeWriter
+ (InputStream in, OutputStream out, String tag, int nExecs) {
+
+ this.in = in;
+ this.out = new PrintWriter(out);
+
+ bufOut = new ByteArrayOutputStream();
+ currSep = new byte[lineSeparatorLength];
+
+ /* set unique pipe/pair annotations */
+ execString = ":ExecGroup-" +
+ Integer.toString(nExecs) + ':' + tag + ':';
+ }
+
+ /**
+ * Create a thread to listen and read from input stream, in. buffer
+ * the data that is read until a marker which equals lineSeparator
+ * is read. Once such a string has been discovered; write out an
+ * annotation string followed by the buffered data and a line
+ * separator.
+ */
+ public void run() {
+ byte[] buf = new byte[256];
+ int count;
+
+ try {
+ /* read bytes till there are no more. */
+ while ((count = in.read(buf)) != -1) {
+ write(buf, 0, count);
+ }
+
+ /* flush internal buffer... may not have ended on a line
+ * separator, we also need a last annotation if
+ * something was left.
+ */
+ String lastInBuffer = bufOut.toString();
+ bufOut.reset();
+ if (lastInBuffer.length() > 0) {
+ out.println (createAnnotation() + lastInBuffer);
+ out.flush(); // add a line separator
+ // to make output nicer
+ }
+
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Write a subarray of bytes. Pass each through write byte method.
+ */
+ private void write(byte b[], int off, int len) throws IOException {
+
+ if (len < 0) {
+ throw new ArrayIndexOutOfBoundsException(len);
+ }
+ for (int i = 0; i < len; ++ i) {
+ write(b[off + i]);
+ }
+ }
+
+ /**
+ * Write a byte of data to the stream. If we have not matched a
+ * line separator string, then the byte is appended to the internal
+ * buffer. If we have matched a line separator, then the currently
+ * buffered line is sent to the output writer with a prepended
+ * annotation string.
+ */
+ private void write(byte b) throws IOException {
+ int i = 0;
+
+ /* shift current to the left */
+ for (i = 1 ; i < (currSep.length); i ++) {
+ currSep[i-1] = currSep[i];
+ }
+ currSep[i-1] = b;
+ bufOut.write(b);
+
+ /* enough characters for a separator? */
+ if ( (cLast >= (lineSeparatorLength - 1)) &&
+ (lineSeparator.equals(new String(currSep))) ) {
+
+ cLast = 0;
+
+ /* write prefix through to underlying byte stream */
+ out.print(createAnnotation() + bufOut.toString());
+ out.flush();
+ bufOut.reset();
+
+ if (out.checkError()) {
+ throw new IOException
+ ("PipeWriter: IO Exception when"+
+ " writing to output stream.");
+ }
+
+ } else {
+ cLast++;
+ }
+ }
+
+ /**
+ * Create an annotation string to be printed out after
+ * a new line and end of stream.
+ */
+ private String createAnnotation() {
+
+ /* construct prefix for log messages:
+ * date/time stamp...
+ */
+ return ((new Date()).toString() +
+ /* ... print pair # ... */
+ (execString));
+ }
+
+ /**
+ * Allow plugging together two pipes at a time, to associate
+ * output from an execed process. This is the only publicly
+ * accessible method of this object; this helps ensure that
+ * synchronization will not be an issue in the annotation
+ * process.
+ *
+ * @param in input stream from which pipe input comes
+ * @param out output stream to which log messages will be sent
+ * @param in1 input stream from which pipe input comes
+ * @param out1 output stream to which log messages will be sent
+ */
+ static void plugTogetherPair(InputStream in,
+ OutputStream out,
+ InputStream in1,
+ OutputStream out1) {
+ Thread inThread = null;
+ Thread outThread = null;
+
+ int nExecs = getNumExec();
+
+ /* start RMI threads to read output from child process */
+ inThread = AccessController.doPrivileged(
+ new NewThreadAction(new PipeWriter(in, out, "out", nExecs),
+ "out", true));
+ outThread = AccessController.doPrivileged(
+ new NewThreadAction(new PipeWriter(in1, out1, "err", nExecs),
+ "err", true));
+ inThread.start();
+ outThread.start();
+ }
+
+ private static synchronized int getNumExec() {
+ return numExecs++;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/ActivationGroupImpl.java b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
new file mode 100644
index 000000000..950ada2c2
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.ServerSocket;
+import java.rmi.MarshalledObject;
+import java.rmi.NoSuchObjectException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.activation.Activatable;
+import java.rmi.activation.ActivationDesc;
+import java.rmi.activation.ActivationException;
+import java.rmi.activation.ActivationGroup;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.ActivationID;
+import java.rmi.activation.UnknownObjectException;
+import java.rmi.server.RMIClassLoader;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import sun.rmi.registry.RegistryImpl;
+
+/**
+ * The default activation group implementation.
+ *
+ * @author Ann Wollrath
+ * @since 1.2
+ * @see java.rmi.activation.ActivationGroup
+ */
+public class ActivationGroupImpl extends ActivationGroup {
+
+ // use serialVersionUID from JDK 1.2.2 for interoperability
+ private static final long serialVersionUID = 5758693559430427303L;
+
+ /** maps persistent IDs to activated remote objects */
+ private final Hashtable<ActivationID,ActiveEntry> active =
+ new Hashtable<ActivationID,ActiveEntry>();
+ private boolean groupInactive = false;
+ private final ActivationGroupID groupID;
+ private final List<ActivationID> lockedIDs = new ArrayList<ActivationID>();
+
+ /**
+ * Creates a default activation group implementation.
+ *
+ * @param id the group's identifier
+ * @param data ignored
+ */
+ public ActivationGroupImpl(ActivationGroupID id, MarshalledObject<?> data)
+ throws RemoteException
+ {
+ super(id);
+ groupID = id;
+
+ /*
+ * Unexport activation group impl and attempt to export it on
+ * an unshared anonymous port. See 4692286.
+ */
+ unexportObject(this, true);
+ RMIServerSocketFactory ssf = new ServerSocketFactoryImpl();
+ UnicastRemoteObject.exportObject(this, 0, null, ssf);
+
+ if (System.getSecurityManager() == null) {
+ try {
+ // Provide a default security manager.
+ System.setSecurityManager(new SecurityManager());
+
+ } catch (Exception e) {
+ throw new RemoteException("unable to set security manager", e);
+ }
+ }
+ }
+
+ /**
+ * Trivial server socket factory used to export the activation group
+ * impl on an unshared port.
+ */
+ private static class ServerSocketFactoryImpl
+ implements RMIServerSocketFactory
+ {
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ RMISocketFactory sf = RMISocketFactory.getSocketFactory();
+ if (sf == null) {
+ sf = RMISocketFactory.getDefaultSocketFactory();
+ }
+ return sf.createServerSocket(port);
+ }
+ }
+
+ /*
+ * Obtains a lock on the ActivationID id before returning. Allows only one
+ * thread at a time to hold a lock on a particular id. If the lock for id
+ * is in use, all requests for an equivalent (in the Object.equals sense)
+ * id will wait for the id to be notified and use the supplied id as the
+ * next lock. The caller of "acquireLock" must execute the "releaseLock"
+ * method" to release the lock and "notifyAll" waiters for the id lock
+ * obtained from this method. The typical usage pattern is as follows:
+ *
+ * try {
+ * acquireLock(id);
+ * // do stuff pertaining to id...
+ * } finally {
+ * releaseLock(id);
+ * checkInactiveGroup();
+ * }
+ */
+ private void acquireLock(ActivationID id) {
+
+ ActivationID waitForID;
+
+ for (;;) {
+
+ synchronized (lockedIDs) {
+ int index = lockedIDs.indexOf(id);
+ if (index < 0) {
+ lockedIDs.add(id);
+ return;
+ } else {
+ waitForID = lockedIDs.get(index);
+ }
+ }
+
+ synchronized (waitForID) {
+ synchronized (lockedIDs) {
+ int index = lockedIDs.indexOf(waitForID);
+ if (index < 0) continue;
+ ActivationID actualID = lockedIDs.get(index);
+ if (actualID != waitForID)
+ /*
+ * don't wait on an id that won't be notified.
+ */
+ continue;
+ }
+
+ try {
+ waitForID.wait();
+ } catch (InterruptedException ignore) {
+ }
+ }
+ }
+
+ }
+
+ /*
+ * Releases the id lock obtained via the "acquireLock" method and then
+ * notifies all threads waiting on the lock.
+ */
+ private void releaseLock(ActivationID id) {
+ synchronized (lockedIDs) {
+ id = lockedIDs.remove(lockedIDs.indexOf(id));
+ }
+
+ synchronized (id) {
+ id.notifyAll();
+ }
+ }
+
+ /**
+ * Creates a new instance of an activatable remote object. The
+ * <code>Activator</code> calls this method to create an activatable
+ * object in this group. This method should be idempotent; a call to
+ * activate an already active object should return the previously
+ * activated object.
+ *
+ * Note: this method assumes that the Activator will only invoke
+ * newInstance for the same object in a serial fashion (i.e.,
+ * the activator will not allow the group to see concurrent requests
+ * to activate the same object.
+ *
+ * @param id the object's activation identifier
+ * @param desc the object's activation descriptor
+ * @return a marshalled object containing the activated object's stub
+ */
+ public MarshalledObject<? extends Remote>
+ newInstance(final ActivationID id,
+ final ActivationDesc desc)
+ throws ActivationException, RemoteException
+ {
+ RegistryImpl.checkAccess("ActivationInstantiator.newInstance");
+
+ if (!groupID.equals(desc.getGroupID()))
+ throw new ActivationException("newInstance in wrong group");
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new InactiveGroupException("group is inactive");
+ }
+
+ ActiveEntry entry = active.get(id);
+ if (entry != null)
+ return entry.mobj;
+
+ String className = desc.getClassName();
+
+ final Class<? extends Remote> cl =
+ RMIClassLoader.loadClass(desc.getLocation(), className)
+ .asSubclass(Remote.class);
+ Remote impl = null;
+
+ final Thread t = Thread.currentThread();
+ final ClassLoader savedCcl = t.getContextClassLoader();
+ ClassLoader objcl = cl.getClassLoader();
+ final ClassLoader ccl = covers(objcl, savedCcl) ? objcl : savedCcl;
+
+ /*
+ * Fix for 4164971: allow non-public activatable class
+ * and/or constructor, create the activatable object in a
+ * privileged block
+ */
+ try {
+ /*
+ * The code below is in a doPrivileged block to
+ * protect against user code which code might have set
+ * a global socket factory (in which case application
+ * code would be on the stack).
+ */
+ impl = AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Remote>() {
+ public Remote run() throws InstantiationException,
+ NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException
+ {
+ Constructor<? extends Remote> constructor =
+ cl.getDeclaredConstructor(
+ ActivationID.class, MarshalledObject.class);
+ constructor.setAccessible(true);
+ try {
+ /*
+ * Fix for 4289544: make sure to set the
+ * context class loader to be the class
+ * loader of the impl class before
+ * constructing that class.
+ */
+ t.setContextClassLoader(ccl);
+ return constructor.newInstance(id,
+ desc.getData());
+ } finally {
+ t.setContextClassLoader(savedCcl);
+ }
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ Throwable e = pae.getException();
+
+ // narrow the exception's type and rethrow it
+ if (e instanceof InstantiationException) {
+ throw (InstantiationException) e;
+ } else if (e instanceof NoSuchMethodException) {
+ throw (NoSuchMethodException) e;
+ } else if (e instanceof IllegalAccessException) {
+ throw (IllegalAccessException) e;
+ } else if (e instanceof InvocationTargetException) {
+ throw (InvocationTargetException) e;
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else if (e instanceof Error) {
+ throw (Error) e;
+ }
+ }
+
+ entry = new ActiveEntry(impl);
+ active.put(id, entry);
+ return entry.mobj;
+
+ } catch (NoSuchMethodException e) {
+ /* user forgot to provide activatable constructor? */
+ throw new ActivationException
+ ("Activatable object must provide an activation"+
+ " constructor", e);
+
+ } catch (NoSuchMethodError e) {
+ /* code recompiled and user forgot to provide
+ * activatable constructor?
+ */
+ throw new ActivationException
+ ("Activatable object must provide an activation"+
+ " constructor", e );
+
+ } catch (InvocationTargetException e) {
+ throw new ActivationException("exception in object constructor",
+ e.getTargetException());
+
+ } catch (Exception e) {
+ throw new ActivationException("unable to activate object", e);
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+ }
+
+
+ /**
+ * The group's <code>inactiveObject</code> method is called
+ * indirectly via a call to the <code>Activatable.inactive</code>
+ * method. A remote object implementation must call
+ * <code>Activatable</code>'s <code>inactive</code> method when
+ * that object deactivates (the object deems that it is no longer
+ * active). If the object does not call
+ * <code>Activatable.inactive</code> when it deactivates, the
+ * object will never be garbage collected since the group keeps
+ * strong references to the objects it creates. <p>
+ *
+ * The group's <code>inactiveObject</code> method
+ * unexports the remote object from the RMI runtime so that the
+ * object can no longer receive incoming RMI calls. This call will
+ * only succeed if the object has no pending/executing calls. If
+ * the object does have pending/executing RMI calls, then false
+ * will be returned.
+ *
+ * If the object has no pending/executing calls, the object is
+ * removed from the RMI runtime and the group informs its
+ * <code>ActivationMonitor</code> (via the monitor's
+ * <code>inactiveObject</code> method) that the remote object is
+ * not currently active so that the remote object will be
+ * re-activated by the activator upon a subsequent activation
+ * request.
+ *
+ * @param id the object's activation identifier
+ * @returns true if the operation succeeds (the operation will
+ * succeed if the object in currently known to be active and is
+ * either already unexported or is currently exported and has no
+ * pending/executing calls); false is returned if the object has
+ * pending/executing calls in which case it cannot be deactivated
+ * @exception UnknownObjectException if object is unknown (may already
+ * be inactive)
+ * @exception RemoteException if call informing monitor fails
+ */
+ public boolean inactiveObject(ActivationID id)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new ActivationException("group is inactive");
+ }
+
+ ActiveEntry entry = active.get(id);
+ if (entry == null) {
+ // REMIND: should this be silent?
+ throw new UnknownObjectException("object not active");
+ }
+
+ try {
+ if (Activatable.unexportObject(entry.impl, false) == false)
+ return false;
+ } catch (NoSuchObjectException allowUnexportedObjects) {
+ }
+
+ try {
+ super.inactiveObject(id);
+ } catch (UnknownObjectException allowUnregisteredObjects) {
+ }
+
+ active.remove(id);
+
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+
+ return true;
+ }
+
+ /*
+ * Determines if the group has become inactive and
+ * marks it as such.
+ */
+ private void checkInactiveGroup() {
+ boolean groupMarkedInactive = false;
+ synchronized (this) {
+ if (active.size() == 0 && lockedIDs.size() == 0 &&
+ groupInactive == false)
+ {
+ groupInactive = true;
+ groupMarkedInactive = true;
+ }
+ }
+
+ if (groupMarkedInactive) {
+ try {
+ super.inactiveGroup();
+ } catch (Exception ignoreDeactivateFailure) {
+ }
+
+ try {
+ UnicastRemoteObject.unexportObject(this, true);
+ } catch (NoSuchObjectException allowUnexportedGroup) {
+ }
+ }
+ }
+
+ /**
+ * The group's <code>activeObject</code> method is called when an
+ * object is exported (either by <code>Activatable</code> object
+ * construction or an explicit call to
+ * <code>Activatable.exportObject</code>. The group must inform its
+ * <code>ActivationMonitor</code> that the object is active (via
+ * the monitor's <code>activeObject</code> method) if the group
+ * hasn't already done so.
+ *
+ * @param id the object's identifier
+ * @param obj the remote object implementation
+ * @exception UnknownObjectException if object is not registered
+ * @exception RemoteException if call informing monitor fails
+ */
+ public void activeObject(ActivationID id, Remote impl)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new ActivationException("group is inactive");
+ }
+ if (!active.contains(id)) {
+ ActiveEntry entry = new ActiveEntry(impl);
+ active.put(id, entry);
+ // created new entry, so inform monitor of active object
+ try {
+ super.activeObject(id, entry.mobj);
+ } catch (RemoteException e) {
+ // daemon can still find it by calling newInstance
+ }
+ }
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+ }
+
+ /**
+ * Entry in table for active object.
+ */
+ private static class ActiveEntry {
+ Remote impl;
+ MarshalledObject<Remote> mobj;
+
+ ActiveEntry(Remote impl) throws ActivationException {
+ this.impl = impl;
+ try {
+ this.mobj = new MarshalledObject<Remote>(impl);
+ } catch (IOException e) {
+ throw new
+ ActivationException("failed to marshal remote object", e);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the first argument is either equal to, or is a
+ * descendant of, the second argument. Null is treated as the root of
+ * the tree.
+ */
+ private static boolean covers(ClassLoader sub, ClassLoader sup) {
+ if (sup == null) {
+ return true;
+ } else if (sub == null) {
+ return false;
+ }
+ do {
+ if (sub == sup) {
+ return true;
+ }
+ sub = sub.getParent();
+ } while (sub != null);
+ return false;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/ActivationGroupInit.java b/src/share/classes/sun/rmi/server/ActivationGroupInit.java
new file mode 100644
index 000000000..4ee82133c
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/ActivationGroupInit.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.rmi.activation.ActivationGroupDesc;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.ActivationGroup;
+
+/**
+ * This is the bootstrap code to start a VM executing an activation
+ * group.
+ *
+ * The activator spawns (as a child process) an activation group as needed
+ * and directs activation requests to the appropriate activation
+ * group. After spawning the VM, the activator passes some
+ * information to the bootstrap code via its stdin: <p>
+ * <ul>
+ * <li> the activation group's id,
+ * <li> the activation group's descriptor (an instance of the class
+ * java.rmi.activation.ActivationGroupDesc) for the group, adn
+ * <li> the group's incarnation number.
+ * </ul><p>
+ *
+ * When the bootstrap VM starts executing, it reads group id and
+ * descriptor from its stdin so that it can create the activation
+ * group for the VM.
+ *
+ * @author Ann Wollrath
+ */
+public abstract class ActivationGroupInit
+{
+ /**
+ * Main program to start a VM for an activation group.
+ */
+ public static void main(String args[])
+ {
+ try {
+ if (System.getSecurityManager() == null) {
+ System.setSecurityManager(new SecurityManager());
+ }
+ // read group id, descriptor, and incarnation number from stdin
+ MarshalInputStream in = new MarshalInputStream(System.in);
+ ActivationGroupID id = (ActivationGroupID)in.readObject();
+ ActivationGroupDesc desc = (ActivationGroupDesc)in.readObject();
+ long incarnation = in.readLong();
+
+ // create and set group for the VM
+ ActivationGroup.createGroup(id, desc, incarnation);
+ } catch (Exception e) {
+ System.err.println("Exception in starting ActivationGroupInit:");
+ e.printStackTrace();
+ } finally {
+ try {
+ System.in.close();
+ // note: system out/err shouldn't be closed
+ // since the parent may want to read them.
+ } catch (Exception ex) {
+ // ignore exceptions
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/Dispatcher.java b/src/share/classes/sun/rmi/server/Dispatcher.java
new file mode 100644
index 000000000..09d35fa3a
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/Dispatcher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.server;
+
+import java.rmi.Remote;
+import java.rmi.server.RemoteCall;
+
+/**
+ * The Dispatcher interface allows the transport to make
+ * the upcall to the server side remote reference.
+ */
+public interface Dispatcher {
+
+ /**
+ * Call to dispatch to the remote object (on the server side).
+ * The up-call to the server and the marshaling of return result
+ * (or exception) should be handled before returning from this
+ * method.
+ * @param obj the target remote object for the call
+ * @param call the "remote call" from which operation and
+ * method arguments can be obtained.
+ * @exception RemoteException unable to marshal
+ * return result
+ */
+ void dispatch(Remote obj, RemoteCall call)
+ throws java.io.IOException;
+}
diff --git a/src/share/classes/sun/rmi/server/InactiveGroupException.java b/src/share/classes/sun/rmi/server/InactiveGroupException.java
new file mode 100644
index 000000000..1464ef540
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/InactiveGroupException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.rmi.activation.ActivationException;
+
+/**
+ * Thrown if a local or remote call is made on a group implementation
+ * instance that is inactive.
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @since 1.6
+ */
+public class InactiveGroupException extends ActivationException {
+
+ private static final long serialVersionUID = -7491041778450214975L;
+
+ /**
+ * Constructs an instance with the specified detail message.
+ *
+ * @param s the detail message
+ */
+ public InactiveGroupException(String s) {
+ super(s);
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/LoaderHandler.java b/src/share/classes/sun/rmi/server/LoaderHandler.java
new file mode 100644
index 000000000..d4a9183e4
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/LoaderHandler.java
@@ -0,0 +1,1198 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketPermission;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.security.AccessControlContext;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.Permissions;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.rmi.server.LogStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.WeakHashMap;
+import sun.rmi.runtime.Log;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * <code>LoaderHandler</code> provides the implementation of the static
+ * methods of the <code>java.rmi.server.RMIClassLoader</code> class.
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ * @author Laird Dornin
+ */
+public final class LoaderHandler {
+
+ /** RMI class loader log level */
+ static final int logLevel = LogStream.parseLevel(
+ (String) java.security.AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.loader.logLevel")));
+
+ /* loader system log */
+ static final Log loaderLog =
+ Log.getLog("sun.rmi.loader", "loader", LoaderHandler.logLevel);
+
+ /**
+ * value of "java.rmi.server.codebase" property, as cached at class
+ * initialization time. It may contain malformed URLs.
+ */
+ private static String codebaseProperty = null;
+ static {
+ String prop = (String) java.security.AccessController.doPrivileged(
+ new GetPropertyAction("java.rmi.server.codebase"));
+ if (prop != null && prop.trim().length() > 0) {
+ codebaseProperty = prop;
+ }
+ }
+
+ /** list of URLs represented by the codebase property, if valid */
+ private static URL[] codebaseURLs = null;
+
+ /** table of class loaders that use codebase property for annotation */
+ private static final Map codebaseLoaders =
+ Collections.synchronizedMap(new IdentityHashMap(5));
+ static {
+ for (ClassLoader codebaseLoader = ClassLoader.getSystemClassLoader();
+ codebaseLoader != null;
+ codebaseLoader = codebaseLoader.getParent())
+ {
+ codebaseLoaders.put(codebaseLoader, null);
+ }
+ }
+
+ /**
+ * table mapping codebase URL path and context class loader pairs
+ * to class loader instances. Entries hold class loaders with weak
+ * references, so this table does not prevent loaders from being
+ * garbage collected.
+ */
+ private static final HashMap loaderTable = new HashMap(5);
+
+ /** reference queue for cleared class loader entries */
+ private static final ReferenceQueue refQueue = new ReferenceQueue();
+
+ /*
+ * Disallow anyone from creating one of these.
+ */
+ private LoaderHandler() {}
+
+ /**
+ * Returns an array of URLs initialized with the value of the
+ * java.rmi.server.codebase property as the URL path.
+ */
+ private static synchronized URL[] getDefaultCodebaseURLs()
+ throws MalformedURLException
+ {
+ /*
+ * If it hasn't already been done, convert the codebase property
+ * into an array of URLs; this may throw a MalformedURLException.
+ */
+ if (codebaseURLs == null) {
+ if (codebaseProperty != null) {
+ codebaseURLs = pathToURLs(codebaseProperty);
+ } else {
+ codebaseURLs = new URL[0];
+ }
+ }
+ return codebaseURLs;
+ }
+
+ /**
+ * Load a class from a network location (one or more URLs),
+ * but first try to resolve the named class through the given
+ * "default loader".
+ */
+ public static Class loadClass(String codebase, String name,
+ ClassLoader defaultLoader)
+ throws MalformedURLException, ClassNotFoundException
+ {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "name = \"" + name + "\", " +
+ "codebase = \"" + (codebase != null ? codebase : "") + "\"" +
+ (defaultLoader != null ?
+ ", defaultLoader = " + defaultLoader : ""));
+ }
+
+ URL[] urls;
+ if (codebase != null) {
+ urls = pathToURLs(codebase);
+ } else {
+ urls = getDefaultCodebaseURLs();
+ }
+
+ if (defaultLoader != null) {
+ try {
+ Class c = Class.forName(name, false, defaultLoader);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "class \"" + name + "\" found via defaultLoader, " +
+ "defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException e) {
+ }
+ }
+
+ return loadClass(urls, name);
+ }
+
+ /**
+ * Returns the class annotation (representing the location for
+ * a class) that RMI will use to annotate the call stream when
+ * marshalling objects of the given class.
+ */
+ public static String getClassAnnotation(Class cl) {
+ String name = cl.getName();
+
+ /*
+ * Class objects for arrays of primitive types never need an
+ * annotation, because they never need to be (or can be) downloaded.
+ *
+ * REMIND: should we (not) be annotating classes that are in
+ * "java.*" packages?
+ */
+ int nameLength = name.length();
+ if (nameLength > 0 && name.charAt(0) == '[') {
+ // skip past all '[' characters (see bugid 4211906)
+ int i = 1;
+ while (nameLength > i && name.charAt(i) == '[') {
+ i++;
+ }
+ if (nameLength > i && name.charAt(i) != 'L') {
+ return null;
+ }
+ }
+
+ /*
+ * Get the class's class loader. If it is null, the system class
+ * loader, an ancestor of the base class loader (such as the loader
+ * for installed extensions), return the value of the
+ * "java.rmi.server.codebase" property.
+ */
+ ClassLoader loader = cl.getClassLoader();
+ if (loader == null || codebaseLoaders.containsKey(loader)) {
+ return codebaseProperty;
+ }
+
+ /*
+ * Get the codebase URL path for the class loader, if it supports
+ * such a notion (i.e., if it is a URLClassLoader or subclass).
+ */
+ String annotation = null;
+ if (loader instanceof Loader) {
+ /*
+ * If the class loader is one of our RMI class loaders, we have
+ * already computed the class annotation string, and no
+ * permissions are required to know the URLs.
+ */
+ annotation = ((Loader) loader).getClassAnnotation();
+
+ } else if (loader instanceof URLClassLoader) {
+ try {
+ URL[] urls = ((URLClassLoader) loader).getURLs();
+ if (urls != null) {
+ /*
+ * If the class loader is not one of our RMI class loaders,
+ * we must verify that the current access control context
+ * has permission to know all of these URLs.
+ */
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ Permissions perms = new Permissions();
+ for (int i = 0; i < urls.length; i++) {
+ Permission p =
+ urls[i].openConnection().getPermission();
+ if (p != null) {
+ if (!perms.implies(p)) {
+ sm.checkPermission(p);
+ perms.add(p);
+ }
+ }
+ }
+ }
+
+ annotation = urlsToPath(urls);
+ }
+ } catch (SecurityException e) {
+ /*
+ * If access was denied to the knowledge of the class
+ * loader's URLs, fall back to the default behavior.
+ */
+ } catch (IOException e) {
+ /*
+ * This shouldn't happen, although it is declared to be
+ * thrown by openConnection() and getPermission(). If it
+ * does happen, forget about this class loader's URLs and
+ * fall back to the default behavior.
+ */
+ }
+ }
+
+ if (annotation != null) {
+ return annotation;
+ } else {
+ return codebaseProperty; // REMIND: does this make sense??
+ }
+ }
+
+ /**
+ * Returns a classloader that loads classes from the given codebase URL
+ * path. The parent classloader of the returned classloader is the
+ * context class loader.
+ */
+ public static ClassLoader getClassLoader(String codebase)
+ throws MalformedURLException
+ {
+ ClassLoader parent = getRMIContextClassLoader();
+
+ URL[] urls;
+ if (codebase != null) {
+ urls = pathToURLs(codebase);
+ } else {
+ urls = getDefaultCodebaseURLs();
+ }
+
+ /*
+ * If there is a security manager, the current access control
+ * context must have the "getClassLoader" RuntimePermission.
+ */
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("getClassLoader"));
+ } else {
+ /*
+ * But if no security manager is set, disable access to
+ * RMI class loaders and simply return the parent loader.
+ */
+ return parent;
+ }
+
+ Loader loader = lookupLoader(urls, parent);
+
+ /*
+ * Verify that the caller has permission to access this loader.
+ */
+ if (loader != null) {
+ loader.checkPermissions();
+ }
+
+ return loader;
+ }
+
+ /**
+ * Return the security context of the given class loader.
+ */
+ public static Object getSecurityContext(ClassLoader loader) {
+ /*
+ * REMIND: This is a bogus JDK1.1-compatible implementation.
+ * This method should never be called by application code anyway
+ * (hence the deprecation), but should it do something different
+ * and perhaps more useful, like return a String or a URL[]?
+ */
+ if (loader instanceof Loader) {
+ URL[] urls = ((Loader) loader).getURLs();
+ if (urls.length > 0) {
+ return urls[0];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Register a class loader as one whose classes should always be
+ * annotated with the value of the "java.rmi.server.codebase" property.
+ */
+ public static void registerCodebaseLoader(ClassLoader loader) {
+ codebaseLoaders.put(loader, null);
+ }
+
+ /**
+ * Load a class from the RMI class loader corresponding to the given
+ * codebase URL path in the current execution context.
+ */
+ private static Class loadClass(URL[] urls, String name)
+ throws ClassNotFoundException
+ {
+ ClassLoader parent = getRMIContextClassLoader();
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "(thread context class loader: " + parent + ")");
+ }
+
+ /*
+ * If no security manager is set, disable access to RMI class
+ * loaders and simply delegate request to the parent loader
+ * (see bugid 4140511).
+ */
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ try {
+ Class c = Class.forName(name, false, parent);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "class \"" + name + "\" found via " +
+ "thread context class loader " +
+ "(no security manager: codebase disabled), " +
+ "defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException e) {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "class \"" + name + "\" not found via " +
+ "thread context class loader " +
+ "(no security manager: codebase disabled)", e);
+ }
+ throw new ClassNotFoundException(e.getMessage() +
+ " (no security manager: RMI class loader disabled)",
+ e.getException());
+ }
+ }
+
+ /*
+ * Get or create the RMI class loader for this codebase URL path
+ * and parent class loader pair.
+ */
+ Loader loader = lookupLoader(urls, parent);
+
+ try {
+ if (loader != null) {
+ /*
+ * Verify that the caller has permission to access this loader.
+ */
+ loader.checkPermissions();
+ }
+ } catch (SecurityException e) {
+ /*
+ * If the current access control context does not have permission
+ * to access all of the URLs in the codebase path, wrap the
+ * resulting security exception in a ClassNotFoundException, so
+ * the caller can handle this outcome just like any other class
+ * loading failure (see bugid 4146529).
+ */
+ try {
+ /*
+ * But first, check to see if the named class could have been
+ * resolved without the security-offending codebase anyway;
+ * if so, return successfully (see bugids 4191926 & 4349670).
+ */
+ Class c = Class.forName(name, false, parent);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "class \"" + name + "\" found via " +
+ "thread context class loader " +
+ "(access to codebase denied), " +
+ "defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException unimportant) {
+ /*
+ * Presumably the security exception is the more important
+ * exception to report in this case.
+ */
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "class \"" + name + "\" not found via " +
+ "thread context class loader " +
+ "(access to codebase denied)", e);
+ }
+ throw new ClassNotFoundException(
+ "access to class loader denied", e);
+ }
+ }
+
+ try {
+ Class c = Class.forName(name, false, loader);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "class \"" + name + "\" " + "found via codebase, " +
+ "defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException e) {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "class \"" + name + "\" not found via codebase", e);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Define and return a dynamic proxy class in a class loader with
+ * URLs supplied in the given location. The proxy class will
+ * implement interface classes named by the given array of
+ * interface names.
+ */
+ public static Class loadProxyClass(String codebase, String[] interfaces,
+ ClassLoader defaultLoader)
+ throws MalformedURLException, ClassNotFoundException
+ {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "interfaces = " + Arrays.asList(interfaces) + ", " +
+ "codebase = \"" + (codebase != null ? codebase : "") + "\"" +
+ (defaultLoader != null ?
+ ", defaultLoader = " + defaultLoader : ""));
+ }
+
+ /*
+ * This method uses a fairly complex algorithm to load the
+ * proxy class and its interface classes in order to maximize
+ * the likelihood that the proxy's codebase annotation will be
+ * preserved. The algorithm is (assuming that all of the
+ * proxy interface classes are public):
+ *
+ * If the default loader is not null, try to load the proxy
+ * interfaces through that loader. If the interfaces can be
+ * loaded in that loader, try to define the proxy class in an
+ * RMI class loader (child of the context class loader) before
+ * trying to define the proxy in the default loader. If the
+ * attempt to define the proxy class succeeds, the codebase
+ * annotation is preserved. If the attempt fails, try to
+ * define the proxy class in the default loader.
+ *
+ * If the interface classes can not be loaded from the default
+ * loader or the default loader is null, try to load them from
+ * the RMI class loader. Then try to define the proxy class
+ * in the RMI class loader.
+ *
+ * Additionally, if any of the proxy interface classes are not
+ * public, all of the non-public interfaces must reside in the
+ * same class loader or it will be impossible to define the
+ * proxy class (an IllegalAccessError will be thrown). An
+ * attempt to load the interfaces from the default loader is
+ * made. If the attempt fails, a second attempt will be made
+ * to load the interfaces from the RMI loader. If all of the
+ * non-public interfaces classes do reside in the same class
+ * loader, then we attempt to define the proxy class in the
+ * class loader of the non-public interfaces. No other
+ * attempt to define the proxy class will be made.
+ */
+ ClassLoader parent = getRMIContextClassLoader();
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "(thread context class loader: " + parent + ")");
+ }
+
+ URL[] urls;
+ if (codebase != null) {
+ urls = pathToURLs(codebase);
+ } else {
+ urls = getDefaultCodebaseURLs();
+ }
+
+ /*
+ * If no security manager is set, disable access to RMI class
+ * loaders and use the would-de parent instead.
+ */
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ try {
+ Class c = loadProxyClass(interfaces, defaultLoader, parent,
+ false);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "(no security manager: codebase disabled) " +
+ "proxy class defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException e) {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "(no security manager: codebase disabled) " +
+ "proxy class resolution failed", e);
+ }
+ throw new ClassNotFoundException(e.getMessage() +
+ " (no security manager: RMI class loader disabled)",
+ e.getException());
+ }
+ }
+
+ /*
+ * Get or create the RMI class loader for this codebase URL path
+ * and parent class loader pair.
+ */
+ Loader loader = lookupLoader(urls, parent);
+
+ try {
+ if (loader != null) {
+ /*
+ * Verify that the caller has permission to access this loader.
+ */
+ loader.checkPermissions();
+ }
+ } catch (SecurityException e) {
+ /*
+ * If the current access control context does not have permission
+ * to access all of the URLs in the codebase path, wrap the
+ * resulting security exception in a ClassNotFoundException, so
+ * the caller can handle this outcome just like any other class
+ * loading failure (see bugid 4146529).
+ */
+ try {
+ /*
+ * But first, check to see if the proxy class could have been
+ * resolved without the security-offending codebase anyway;
+ * if so, return successfully (see bugids 4191926 & 4349670).
+ */
+ Class c = loadProxyClass(interfaces, defaultLoader, parent,
+ false);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "(access to codebase denied) " +
+ "proxy class defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException unimportant) {
+ /*
+ * Presumably the security exception is the more important
+ * exception to report in this case.
+ */
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "(access to codebase denied) " +
+ "proxy class resolution failed", e);
+ }
+ throw new ClassNotFoundException(
+ "access to class loader denied", e);
+ }
+ }
+
+ try {
+ Class c = loadProxyClass(interfaces, defaultLoader, loader, true);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "proxy class defined by " + c.getClassLoader());
+ }
+ return c;
+ } catch (ClassNotFoundException e) {
+ if (loaderLog.isLoggable(Log.BRIEF)) {
+ loaderLog.log(Log.BRIEF,
+ "proxy class resolution failed", e);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Define a proxy class in the default loader if appropriate.
+ * Define the class in an RMI class loader otherwise. The proxy
+ * class will implement classes which are named in the supplied
+ * interfaceNames.
+ */
+ private static Class loadProxyClass(String[] interfaceNames,
+ ClassLoader defaultLoader,
+ ClassLoader codebaseLoader,
+ boolean preferCodebase)
+ throws ClassNotFoundException
+ {
+ ClassLoader proxyLoader = null;
+ Class[] classObjs = new Class[interfaceNames.length];
+ boolean[] nonpublic = { false };
+
+ defaultLoaderCase:
+ if (defaultLoader != null) {
+ try {
+ proxyLoader =
+ loadProxyInterfaces(interfaceNames, defaultLoader,
+ classObjs, nonpublic);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ ClassLoader[] definingLoaders =
+ new ClassLoader[classObjs.length];
+ for (int i = 0; i < definingLoaders.length; i++) {
+ definingLoaders[i] = classObjs[i].getClassLoader();
+ }
+ loaderLog.log(Log.VERBOSE,
+ "proxy interfaces found via defaultLoader, " +
+ "defined by " + Arrays.asList(definingLoaders));
+ }
+ } catch (ClassNotFoundException e) {
+ break defaultLoaderCase;
+ }
+ if (!nonpublic[0]) {
+ if (preferCodebase) {
+ try {
+ return Proxy.getProxyClass(codebaseLoader, classObjs);
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ proxyLoader = defaultLoader;
+ }
+ return loadProxyClass(proxyLoader, classObjs);
+ }
+
+ nonpublic[0] = false;
+ proxyLoader = loadProxyInterfaces(interfaceNames, codebaseLoader,
+ classObjs, nonpublic);
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ ClassLoader[] definingLoaders = new ClassLoader[classObjs.length];
+ for (int i = 0; i < definingLoaders.length; i++) {
+ definingLoaders[i] = classObjs[i].getClassLoader();
+ }
+ loaderLog.log(Log.VERBOSE,
+ "proxy interfaces found via codebase, " +
+ "defined by " + Arrays.asList(definingLoaders));
+ }
+ if (!nonpublic[0]) {
+ proxyLoader = codebaseLoader;
+ }
+ return loadProxyClass(proxyLoader, classObjs);
+ }
+
+ /**
+ * Define a proxy class in the given class loader. The proxy
+ * class will implement the given interfaces Classes.
+ */
+ private static Class loadProxyClass(ClassLoader loader, Class[] interfaces)
+ throws ClassNotFoundException
+ {
+ try {
+ return Proxy.getProxyClass(loader, interfaces);
+ } catch (IllegalArgumentException e) {
+ throw new ClassNotFoundException(
+ "error creating dynamic proxy class", e);
+ }
+ }
+
+ /*
+ * Load Class objects for the names in the interfaces array fron
+ * the given class loader.
+ *
+ * We pass classObjs and nonpublic arrays to avoid needing a
+ * multi-element return value. nonpublic is an array to enable
+ * the method to take a boolean argument by reference.
+ *
+ * nonpublic array is needed to signal when the return value of
+ * this method should be used as the proxy class loader. Because
+ * null represents a valid class loader, that value is
+ * insufficient to signal that the return value should not be used
+ * as the proxy class loader.
+ */
+ private static ClassLoader loadProxyInterfaces(String[] interfaces,
+ ClassLoader loader,
+ Class[] classObjs,
+ boolean[] nonpublic)
+ throws ClassNotFoundException
+ {
+ /* loader of a non-public interface class */
+ ClassLoader nonpublicLoader = null;
+
+ for (int i = 0; i < interfaces.length; i++) {
+ Class cl =
+ (classObjs[i] = Class.forName(interfaces[i], false, loader));
+
+ if (!Modifier.isPublic(cl.getModifiers())) {
+ ClassLoader current = cl.getClassLoader();
+ if (loaderLog.isLoggable(Log.VERBOSE)) {
+ loaderLog.log(Log.VERBOSE,
+ "non-public interface \"" + interfaces[i] +
+ "\" defined by " + current);
+ }
+ if (!nonpublic[0]) {
+ nonpublicLoader = current;
+ nonpublic[0] = true;
+ } else if (current != nonpublicLoader) {
+ throw new IllegalAccessError(
+ "non-public interfaces defined in different " +
+ "class loaders");
+ }
+ }
+ }
+ return nonpublicLoader;
+ }
+
+ /**
+ * Convert a string containing a space-separated list of URLs into a
+ * corresponding array of URL objects, throwing a MalformedURLException
+ * if any of the URLs are invalid.
+ */
+ private static URL[] pathToURLs(String path)
+ throws MalformedURLException
+ {
+ synchronized (pathToURLsCache) {
+ Object[] v = (Object[]) pathToURLsCache.get(path);
+ if (v != null) {
+ return ((URL[])v[0]);
+ }
+ }
+ StringTokenizer st = new StringTokenizer(path); // divide by spaces
+ URL[] urls = new URL[st.countTokens()];
+ for (int i = 0; st.hasMoreTokens(); i++) {
+ urls[i] = new URL(st.nextToken());
+ }
+ synchronized (pathToURLsCache) {
+ pathToURLsCache.put(path,
+ new Object[] {urls, new SoftReference(path)});
+ }
+ return urls;
+ }
+
+ /** map from weak(key=string) to [URL[], soft(key)] */
+ private static final Map pathToURLsCache = new WeakHashMap(5);
+
+ /**
+ * Convert an array of URL objects into a corresponding string
+ * containing a space-separated list of URLs.
+ *
+ * Note that if the array has zero elements, the return value is
+ * null, not the empty string.
+ */
+ private static String urlsToPath(URL[] urls) {
+ if (urls.length == 0) {
+ return null;
+ } else if (urls.length == 1) {
+ return urls[0].toExternalForm();
+ } else {
+ StringBuffer path = new StringBuffer(urls[0].toExternalForm());
+ for (int i = 1; i < urls.length; i++) {
+ path.append(' ');
+ path.append(urls[i].toExternalForm());
+ }
+ return path.toString();
+ }
+ }
+
+ /**
+ * Return the class loader to be used as the parent for an RMI class
+ * loader used in the current execution context.
+ */
+ private static ClassLoader getRMIContextClassLoader() {
+ /*
+ * The current implementation simply uses the current thread's
+ * context class loader.
+ */
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ /**
+ * Look up the RMI class loader for the given codebase URL path
+ * and the given parent class loader. A new class loader instance
+ * will be created and returned if no match is found.
+ */
+ private static Loader lookupLoader(final URL[] urls,
+ final ClassLoader parent)
+ {
+ /*
+ * If the requested codebase URL path is empty, the supplied
+ * parent class loader will be sufficient.
+ *
+ * REMIND: To be conservative, this optimization is commented out
+ * for now so that it does not open a security hole in the future
+ * by providing untrusted code with direct access to the public
+ * loadClass() method of a class loader instance that it cannot
+ * get a reference to. (It's an unlikely optimization anyway.)
+ *
+ * if (urls.length == 0) {
+ * return parent;
+ * }
+ */
+
+ LoaderEntry entry;
+ Loader loader;
+
+ synchronized (LoaderHandler.class) {
+ /*
+ * Take this opportunity to remove from the table entries
+ * whose weak references have been cleared.
+ */
+ while ((entry = (LoaderEntry) refQueue.poll()) != null) {
+ if (!entry.removed) { // ignore entries removed below
+ loaderTable.remove(entry.key);
+ }
+ }
+
+ /*
+ * Look up the codebase URL path and parent class loader pair
+ * in the table of RMI class loaders.
+ */
+ LoaderKey key = new LoaderKey(urls, parent);
+ entry = (LoaderEntry) loaderTable.get(key);
+
+ if (entry == null || (loader = (Loader) entry.get()) == null) {
+ /*
+ * If entry was in table but it's weak reference was cleared,
+ * remove it from the table and mark it as explicitly cleared,
+ * so that new matching entry that we put in the table will
+ * not be erroneously removed when this entry is processed
+ * from the weak reference queue.
+ */
+ if (entry != null) {
+ loaderTable.remove(key);
+ entry.removed = true;
+ }
+
+ /*
+ * A matching loader was not found, so create a new class
+ * loader instance for the requested codebase URL path and
+ * parent class loader. The instance is created within an
+ * access control context retricted to the permissions
+ * necessary to load classes from its codebase URL path.
+ */
+ AccessControlContext acc = getLoaderAccessControlContext(urls);
+ loader = (Loader) java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ return new Loader(urls, parent);
+ }
+ }, acc);
+
+ /*
+ * Finally, create an entry to hold the new loader with a
+ * weak reference and store it in the table with the key.
+ */
+ entry = new LoaderEntry(key, loader);
+ loaderTable.put(key, entry);
+ }
+ }
+
+ return loader;
+ }
+
+ /**
+ * LoaderKey holds a codebase URL path and parent class loader pair
+ * used to look up RMI class loader instances in its class loader cache.
+ */
+ private static class LoaderKey {
+
+ private URL[] urls;
+
+ private ClassLoader parent;
+
+ private int hashValue;
+
+ public LoaderKey(URL[] urls, ClassLoader parent) {
+ this.urls = urls;
+ this.parent = parent;
+
+ if (parent != null) {
+ hashValue = parent.hashCode();
+ }
+ for (int i = 0; i < urls.length; i++) {
+ hashValue ^= urls[i].hashCode();
+ }
+ }
+
+ public int hashCode() {
+ return hashValue;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof LoaderKey) {
+ LoaderKey other = (LoaderKey) obj;
+ if (parent != other.parent) {
+ return false;
+ }
+ if (urls == other.urls) {
+ return true;
+ }
+ if (urls.length != other.urls.length) {
+ return false;
+ }
+ for (int i = 0; i < urls.length; i++) {
+ if (!urls[i].equals(other.urls[i])) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * LoaderEntry contains a weak reference to an RMIClassLoader. The
+ * weak reference is registered with the private static "refQueue"
+ * queue. The entry contains the codebase URL path and parent class
+ * loader key for the loader so that the mapping can be removed from
+ * the table efficiently when the weak reference is cleared.
+ */
+ private static class LoaderEntry extends WeakReference {
+
+ public LoaderKey key;
+
+ /**
+ * set to true if the entry has been removed from the table
+ * because it has been replaced, so it should not be attempted
+ * to be removed again
+ */
+ public boolean removed = false;
+
+ public LoaderEntry(LoaderKey key, Loader loader) {
+ super(loader, refQueue);
+ this.key = key;
+ }
+ }
+
+ /**
+ * Return the access control context that a loader for the given
+ * codebase URL path should execute with.
+ */
+ private static AccessControlContext getLoaderAccessControlContext(
+ URL[] urls)
+ {
+ /*
+ * The approach used here is taken from the similar method
+ * getAccessControlContext() in the sun.applet.AppletPanel class.
+ */
+ // begin with permissions granted to all code in current policy
+ PermissionCollection perms = (PermissionCollection)
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ CodeSource codesource = new CodeSource(null,
+ (java.security.cert.Certificate[]) null);
+ Policy p = java.security.Policy.getPolicy();
+ if (p != null) {
+ return p.getPermissions(codesource);
+ } else {
+ return new Permissions();
+ }
+ }
+ });
+
+ // createClassLoader permission needed to create loader in context
+ perms.add(new RuntimePermission("createClassLoader"));
+
+ // add permissions to read any "java.*" property
+ perms.add(new java.util.PropertyPermission("java.*","read"));
+
+ // add permissions reuiqred to load from codebase URL path
+ addPermissionsForURLs(urls, perms, true);
+
+ /*
+ * Create an AccessControlContext that consists of a single
+ * protection domain with only the permissions calculated above.
+ */
+ ProtectionDomain pd = new ProtectionDomain(
+ new CodeSource((urls.length > 0 ? urls[0] : null),
+ (java.security.cert.Certificate[]) null),
+ perms);
+ return new AccessControlContext(new ProtectionDomain[] { pd });
+ }
+
+ /**
+ * Adds to the specified permission collection the permissions
+ * necessary to load classes from a loader with the specified URL
+ * path; if "forLoader" is true, also adds URL-specific
+ * permissions necessary for the security context that such a
+ * loader operates within, such as permissions necessary for
+ * granting automatic permissions to classes defined by the
+ * loader. A given permission is only added to the collection if
+ * it is not already implied by the collection.
+ */
+ private static void addPermissionsForURLs(URL[] urls,
+ PermissionCollection perms,
+ boolean forLoader)
+ {
+ for (int i = 0; i < urls.length; i++) {
+ URL url = urls[i];
+ try {
+ URLConnection urlConnection = url.openConnection();
+ Permission p = urlConnection.getPermission();
+ if (p != null) {
+ if (p instanceof FilePermission) {
+ /*
+ * If the codebase is a file, the permission required
+ * to actually read classes from the codebase URL is
+ * the permission to read all files beneath the last
+ * directory in the file path, either because JAR
+ * files can refer to other JAR files in the same
+ * directory, or because permission to read a
+ * directory is not implied by permission to read the
+ * contents of a directory, which all that might be
+ * granted.
+ */
+ String path = p.getName();
+ int endIndex = path.lastIndexOf(File.separatorChar);
+ if (endIndex != -1) {
+ path = path.substring(0, endIndex+1);
+ if (path.endsWith(File.separator)) {
+ path += "-";
+ }
+ Permission p2 = new FilePermission(path, "read");
+ if (!perms.implies(p2)) {
+ perms.add(p2);
+ }
+ perms.add(new FilePermission(path, "read"));
+ } else {
+ /*
+ * No directory separator: use permission to
+ * read the file.
+ */
+ if (!perms.implies(p)) {
+ perms.add(p);
+ }
+ }
+ } else {
+ if (!perms.implies(p)) {
+ perms.add(p);
+ }
+
+ /*
+ * If the purpose of these permissions is to grant
+ * them to an instance of a URLClassLoader subclass,
+ * we must add permission to connect to and accept
+ * from the host of non-"file:" URLs, otherwise the
+ * getPermissions() method of URLClassLoader will
+ * throw a security exception.
+ */
+ if (forLoader) {
+ // get URL with meaningful host component
+ URL hostURL = url;
+ for (URLConnection conn = urlConnection;
+ conn instanceof JarURLConnection;)
+ {
+ hostURL =
+ ((JarURLConnection) conn).getJarFileURL();
+ conn = hostURL.openConnection();
+ }
+ String host = hostURL.getHost();
+ if (host != null &&
+ p.implies(new SocketPermission(host,
+ "resolve")))
+ {
+ Permission p2 =
+ new SocketPermission(host,
+ "connect,accept");
+ if (!perms.implies(p2)) {
+ perms.add(p2);
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ /*
+ * This shouldn't happen, although it is declared to be
+ * thrown by openConnection() and getPermission(). If it
+ * does, don't bother granting or requiring any permissions
+ * for this URL.
+ */
+ }
+ }
+ }
+
+ /**
+ * Loader is the actual class of the RMI class loaders created
+ * by the RMIClassLoader static methods.
+ */
+ private static class Loader extends URLClassLoader {
+
+ /** parent class loader, kept here as an optimization */
+ private ClassLoader parent;
+
+ /** string form of loader's codebase URL path, also an optimization */
+ private String annotation;
+
+ /** permissions required to access loader through public API */
+ private Permissions permissions;
+
+ private Loader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ this.parent = parent;
+
+ /*
+ * Precompute the permissions required to access the loader.
+ */
+ permissions = new Permissions();
+ addPermissionsForURLs(urls, permissions, false);
+
+ /*
+ * Caching the value of class annotation string here assumes
+ * that the protected method addURL() is never called on this
+ * class loader.
+ */
+ annotation = urlsToPath(urls);
+ }
+
+ /**
+ * Return the string to be annotated with all classes loaded from
+ * this class loader.
+ */
+ public String getClassAnnotation() {
+ return annotation;
+ }
+
+ /**
+ * Check that the current access control context has all of the
+ * permissions necessary to load classes from this loader.
+ */
+ private void checkPermissions() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) { // should never be null?
+ Enumeration enum_ = permissions.elements();
+ while (enum_.hasMoreElements()) {
+ sm.checkPermission((Permission) enum_.nextElement());
+ }
+ }
+ }
+
+ /**
+ * Return the permissions to be granted to code loaded from the
+ * given code source.
+ */
+ protected PermissionCollection getPermissions(CodeSource codesource) {
+ PermissionCollection perms = super.getPermissions(codesource);
+ /*
+ * Grant the same permissions that URLClassLoader would grant.
+ */
+ return perms;
+ }
+
+ /**
+ * Return a string representation of this loader (useful for
+ * debugging).
+ */
+ public String toString() {
+ return super.toString() + "[\"" + annotation + "\"]";
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/MarshalInputStream.java b/src/share/classes/sun/rmi/server/MarshalInputStream.java
new file mode 100644
index 000000000..a75b8051a
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/MarshalInputStream.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.StreamCorruptedException;
+import java.net.URL;
+import java.util.*;
+import java.security.AccessControlException;
+import java.security.Permission;
+
+import java.rmi.server.RMIClassLoader;
+
+/**
+ * MarshalInputStream is an extension of ObjectInputStream. When resolving
+ * a class, it reads an object from the stream written by a corresponding
+ * MarshalOutputStream. If the class to be resolved is not available
+ * locally, from the first class loader on the execution stack, or from the
+ * context class loader of the current thread, it will attempt to load the
+ * class from the location annotated by the sending MarshalOutputStream.
+ * This location object must be a string representing a path of URLs.
+ *
+ * A new MarshalInputStream should be created to deserialize remote objects or
+ * graphs containing remote objects. Objects are created from the stream
+ * using the ObjectInputStream.readObject method.
+ *
+ * @author Peter Jones
+ */
+public class MarshalInputStream extends ObjectInputStream {
+
+ /**
+ * value of "java.rmi.server.useCodebaseOnly" property,
+ * as cached at class initialization time.
+ */
+ private static final boolean useCodebaseOnlyProperty =
+ ((Boolean) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetBooleanAction(
+ "java.rmi.server.useCodebaseOnly"))).booleanValue();
+
+ /** table to hold sun classes to which access is explicitly permitted */
+ protected static Map permittedSunClasses = new HashMap(3);
+
+ /** if true, don't try superclass first in resolveClass() */
+ private boolean skipDefaultResolveClass = false;
+
+ /** callbacks to make when done() called: maps Object to Runnable */
+ private final Map doneCallbacks = new HashMap(3);
+
+ /**
+ * if true, load classes (if not available locally) only from the
+ * URL specified by the "java.rmi.server.codebase" property.
+ */
+ private boolean useCodebaseOnly = useCodebaseOnlyProperty;
+
+ /*
+ * Fix for 4179055: The remote object services inside the
+ * activation daemon use stubs that are in the package
+ * sun.rmi.server. Classes for these stubs should be loaded from
+ * the classpath by RMI system code and not by the normal
+ * unmarshalling process as applications should not need to have
+ * permission to access the sun implementation classes.
+ *
+ * Note: this fix should be redone when API changes may be
+ * integrated
+ *
+ * During parameter unmarshalling RMI needs to explicitly permit
+ * access to three sun.* stub classes
+ */
+ static {
+ try {
+ String system =
+ "sun.rmi.server.Activation$ActivationSystemImpl_Stub";
+ String registry = "sun.rmi.registry.RegistryImpl_Stub";
+
+ permittedSunClasses.put(system, Class.forName(system));
+ permittedSunClasses.put(registry, Class.forName(registry));
+
+ } catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError("Missing system class: " +
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Load the "rmi" native library.
+ */
+ static {
+ java.security.AccessController.doPrivileged(
+ new sun.security.action.LoadLibraryAction("rmi"));
+ }
+
+ /**
+ * Create a new MarshalInputStream object.
+ */
+ public MarshalInputStream(InputStream in)
+ throws IOException, StreamCorruptedException
+ {
+ super(in);
+ }
+
+ /**
+ * Returns a callback previously registered via the setDoneCallback
+ * method with given key, or null if no callback has yet been registered
+ * with that key.
+ */
+ public Runnable getDoneCallback(Object key) {
+ return (Runnable) doneCallbacks.get(key); // not thread-safe
+ }
+
+ /**
+ * Registers a callback to make when this stream's done() method is
+ * invoked, along with a key for retrieving the same callback instance
+ * subsequently from the getDoneCallback method.
+ */
+ public void setDoneCallback(Object key, Runnable callback) {
+ //assert(!doneCallbacks.contains(key));
+ doneCallbacks.put(key, callback); // not thread-safe
+ }
+
+ /**
+ * Indicates that the user of this MarshalInputStream is done reading
+ * objects from it, so all callbacks registered with the setDoneCallback
+ * method should now be (synchronously) executed. When this method
+ * returns, there are no more callbacks registered.
+ *
+ * This method is implicitly invoked by close() before it delegates to
+ * the superclass's close method.
+ */
+ public void done() {
+ Iterator iter = doneCallbacks.values().iterator();
+ while (iter.hasNext()) { // not thread-safe
+ Runnable callback = (Runnable) iter.next();
+ callback.run();
+ }
+ doneCallbacks.clear();
+ }
+
+ /**
+ * Closes this stream, implicitly invoking done() first.
+ */
+ public void close() throws IOException {
+ done();
+ super.close();
+ }
+
+ /**
+ * resolveClass is extended to acquire (if present) the location
+ * from which to load the specified class.
+ * It will find, load, and return the class.
+ */
+ protected Class resolveClass(ObjectStreamClass classDesc)
+ throws IOException, ClassNotFoundException
+ {
+ /*
+ * Always read annotation written by MarshalOutputStream
+ * describing where to load class from.
+ */
+ Object annotation = readLocation();
+
+ String className = classDesc.getName();
+
+ /*
+ * Unless we were told to skip this consideration, choose the
+ * "default loader" to simulate the default ObjectInputStream
+ * resolveClass mechanism (that is, choose the first non-null
+ * loader on the execution stack) to maximize the likelihood of
+ * type compatibility with calling code. (This consideration
+ * is skipped during server parameter unmarshalling using the 1.2
+ * stub protocol, because there would never be a non-null class
+ * loader on the stack in that situation anyway.)
+ */
+ ClassLoader defaultLoader =
+ skipDefaultResolveClass ? null : latestUserDefinedLoader();
+
+ /*
+ * If the "java.rmi.server.useCodebaseOnly" property was true or
+ * useCodebaseOnly() was called or the annotation is not a String,
+ * load from the local loader using the "java.rmi.server.codebase"
+ * URL. Otherwise, load from a loader using the codebase URL in
+ * the annotation.
+ */
+ String codebase = null;
+ if (!useCodebaseOnly && annotation instanceof String) {
+ codebase = (String) annotation;
+ }
+
+ try {
+ return RMIClassLoader.loadClass(codebase, className,
+ defaultLoader);
+ } catch (AccessControlException e) {
+ return checkSunClass(className, e);
+ } catch (ClassNotFoundException e) {
+ /*
+ * Fix for 4442373: delegate to ObjectInputStream.resolveClass()
+ * to resolve primitive classes.
+ */
+ try {
+ if (Character.isLowerCase(className.charAt(0)) &&
+ className.indexOf('.') == -1)
+ {
+ return super.resolveClass(classDesc);
+ }
+ } catch (ClassNotFoundException e2) {
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * resolveProxyClass is extended to acquire (if present) the location
+ * to determine the class loader to define the proxy class in.
+ */
+ protected Class resolveProxyClass(String[] interfaces)
+ throws IOException, ClassNotFoundException
+ {
+ /*
+ * Always read annotation written by MarshalOutputStream.
+ */
+ Object annotation = readLocation();
+
+ ClassLoader defaultLoader =
+ skipDefaultResolveClass ? null : latestUserDefinedLoader();
+
+ String codebase = null;
+ if (!useCodebaseOnly && annotation instanceof String) {
+ codebase = (String) annotation;
+ }
+
+ return RMIClassLoader.loadProxyClass(codebase, interfaces,
+ defaultLoader);
+ }
+
+ /*
+ * Returns the first non-null class loader up the execution stack, or null
+ * if only code from the null class loader is on the stack.
+ */
+ private static native ClassLoader latestUserDefinedLoader();
+
+ /**
+ * Fix for 4179055: Need to assist resolving sun stubs; resolve
+ * class locally if it is a "permitted" sun class
+ */
+ private Class checkSunClass(String className, AccessControlException e)
+ throws AccessControlException
+ {
+ // ensure that we are giving out a stub for the correct reason
+ Permission perm = e.getPermission();
+ String name = null;
+ if (perm != null) {
+ name = perm.getName();
+ }
+
+ Class resolvedClass =
+ (Class) permittedSunClasses.get(className);
+
+ // if class not permitted, throw the SecurityException
+ if ((name == null) ||
+ (resolvedClass == null) ||
+ ((!name.equals("accessClassInPackage.sun.rmi.server")) &&
+ (!name.equals("accessClassInPackage.sun.rmi.registry"))))
+ {
+ throw e;
+ }
+
+ return resolvedClass;
+ }
+
+ /**
+ * Return the location for the class in the stream. This method can
+ * be overridden by subclasses that store this annotation somewhere
+ * else than as the next object in the stream, as is done by this class.
+ */
+ protected Object readLocation()
+ throws IOException, ClassNotFoundException
+ {
+ return readObject();
+ }
+
+ /**
+ * Set a flag to indicate that the superclass's default resolveClass()
+ * implementation should not be invoked by our resolveClass().
+ */
+ void skipDefaultResolveClass() {
+ skipDefaultResolveClass = true;
+ }
+
+ /**
+ * Disable code downloading except from the URL specified by the
+ * "java.rmi.server.codebase" property.
+ */
+ void useCodebaseOnly() {
+ useCodebaseOnly = true;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/MarshalOutputStream.java b/src/share/classes/sun/rmi/server/MarshalOutputStream.java
new file mode 100644
index 000000000..63aed2bee
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/MarshalOutputStream.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 1996-2004 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.*;
+import java.rmi.Remote;
+import java.rmi.server.RemoteStub;
+import sun.rmi.transport.ObjectTable;
+import sun.rmi.transport.Target;
+
+/**
+ * A MarshalOutputStream extends ObjectOutputStream to add functions
+ * specific to marshaling of remote object references. If it is
+ * necessary to serialize remote objects or objects that contain
+ * references to remote objects a MarshalOutputStream must be used
+ * instead of ObjectOutputStream. <p>
+ *
+ * A new MarshalOutputStream is constructed to serialize remote
+ * objects or graphs containing remote objects. Objects are written to
+ * the stream using the ObjectOutputStream.writeObject method. <p>
+ *
+ * MarshalOutputStream maps remote objects to the corresponding remote
+ * stub and embeds the location from which to load the stub
+ * classes. The location may be ignored by the client but is supplied.
+ */
+public class MarshalOutputStream extends ObjectOutputStream
+{
+ /**
+ * Creates a marshal output stream with protocol version 1.
+ */
+ public MarshalOutputStream(OutputStream out) throws IOException {
+ this(out, ObjectStreamConstants.PROTOCOL_VERSION_1);
+ }
+
+ /**
+ * Creates a marshal output stream with the given protocol version.
+ */
+ public MarshalOutputStream(OutputStream out, int protocolVersion)
+ throws IOException
+ {
+ super(out);
+ this.useProtocolVersion(protocolVersion);
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ enableReplaceObject(true);
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Checks for objects that are instances of java.rmi.Remote
+ * that need to be serialized as proxy objects.
+ */
+ protected final Object replaceObject(Object obj) throws IOException {
+ if ((obj instanceof Remote) && !(obj instanceof RemoteStub)) {
+ Target target = ObjectTable.getTarget((Remote) obj);
+ if (target != null) {
+ return target.getStub();
+ }
+ }
+ return obj;
+ }
+
+ /**
+ * Serializes a location from which to load the the specified class.
+ */
+ protected void annotateClass(Class<?> cl) throws IOException {
+ writeLocation(java.rmi.server.RMIClassLoader.getClassAnnotation(cl));
+ }
+
+ /**
+ * Serializes a location from which to load the specified class.
+ */
+ protected void annotateProxyClass(Class<?> cl) throws IOException {
+ annotateClass(cl);
+ }
+
+ /**
+ * Writes the location for the class into the stream. This method can
+ * be overridden by subclasses that store this annotation somewhere
+ * else than as the next object in the stream, as is done by this class.
+ */
+ protected void writeLocation(String location) throws IOException {
+ writeObject(location);
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/UnicastRef.java b/src/share/classes/sun/rmi/server/UnicastRef.java
new file mode 100644
index 000000000..b9307f9ea
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/UnicastRef.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.Method;
+import java.rmi.MarshalException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.ServerException;
+import java.rmi.UnmarshalException;
+import java.rmi.server.Operation;
+import java.rmi.server.RemoteCall;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.RemoteRef;
+import java.security.AccessController;
+import sun.rmi.runtime.Log;
+import sun.rmi.transport.Connection;
+import sun.rmi.transport.LiveRef;
+import sun.rmi.transport.StreamRemoteCall;
+import sun.security.action.GetBooleanAction;
+
+/**
+ * NOTE: There is a JDK-internal dependency on the existence of this
+ * class's getLiveRef method (as it is inherited by UnicastRef2) in
+ * the implementation of javax.management.remote.rmi.RMIConnector.
+ **/
+public class UnicastRef implements RemoteRef {
+
+ /**
+ * Client-side transport log.
+ */
+ public static final Log clientRefLog =
+ Log.getLog("sun.rmi.client.ref", "transport", Util.logLevel);
+
+ /**
+ * Client-side call log.
+ */
+ public static final Log clientCallLog =
+ Log.getLog("sun.rmi.client.call", "RMI",
+ AccessController.doPrivileged(
+ new GetBooleanAction("sun.rmi.client.logCalls")));
+
+ protected LiveRef ref;
+
+ /**
+ * Create a new (empty) Unicast remote reference.
+ */
+ public UnicastRef() {
+ }
+
+ /**
+ * Create a new Unicast RemoteRef.
+ */
+ public UnicastRef(LiveRef liveRef) {
+ ref = liveRef;
+ }
+
+ /**
+ * Returns the current value of this UnicastRef's underlying
+ * LiveRef.
+ *
+ * NOTE: There is a JDK-internal dependency on the existence of
+ * this method (as it is inherited by UnicastRef) in the
+ * implementation of javax.management.remote.rmi.RMIConnector.
+ **/
+ public LiveRef getLiveRef() {
+ return ref;
+ }
+
+ /**
+ * Invoke a method. This form of delegating method invocation
+ * to the reference allows the reference to take care of
+ * setting up the connection to the remote host, marshalling
+ * some representation for the method and parameters, then
+ * communicating the method invocation to the remote host.
+ * This method either returns the result of a method invocation
+ * on the remote object which resides on the remote host or
+ * throws a RemoteException if the call failed or an
+ * application-level exception if the remote invocation throws
+ * an exception.
+ *
+ * @param obj the proxy for the remote object
+ * @param method the method to be invoked
+ * @param params the parameter list
+ * @param opnum a hash that may be used to represent the method
+ * @since 1.2
+ */
+ public Object invoke(Remote obj,
+ Method method,
+ Object[] params,
+ long opnum)
+ throws Exception
+ {
+ if (clientRefLog.isLoggable(Log.VERBOSE)) {
+ clientRefLog.log(Log.VERBOSE, "method: " + method);
+ }
+
+ if (clientCallLog.isLoggable(Log.VERBOSE)) {
+ logClientCall(obj, method);
+ }
+
+ Connection conn = ref.getChannel().newConnection();
+ RemoteCall call = null;
+ boolean reuse = true;
+
+ /* If the call connection is "reused" early, remember not to
+ * reuse again.
+ */
+ boolean alreadyFreed = false;
+
+ try {
+ if (clientRefLog.isLoggable(Log.VERBOSE)) {
+ clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);
+ }
+
+ // create call context
+ call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);
+
+ // marshal parameters
+ try {
+ ObjectOutput out = call.getOutputStream();
+ marshalCustomCallData(out);
+ Class<?>[] types = method.getParameterTypes();
+ for (int i = 0; i < types.length; i++) {
+ marshalValue(types[i], params[i], out);
+ }
+ } catch (IOException e) {
+ clientRefLog.log(Log.BRIEF,
+ "IOException marshalling arguments: ", e);
+ throw new MarshalException("error marshalling arguments", e);
+ }
+
+ // unmarshal return
+ call.executeCall();
+
+ try {
+ Class<?> rtype = method.getReturnType();
+ if (rtype == void.class)
+ return null;
+ ObjectInput in = call.getInputStream();
+
+ /* StreamRemoteCall.done() does not actually make use
+ * of conn, therefore it is safe to reuse this
+ * connection before the dirty call is sent for
+ * registered refs.
+ */
+ Object returnValue = unmarshalValue(rtype, in);
+
+ /* we are freeing the connection now, do not free
+ * again or reuse.
+ */
+ alreadyFreed = true;
+
+ /* if we got to this point, reuse must have been true. */
+ clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
+
+ /* Free the call's connection early. */
+ ref.getChannel().free(conn, true);
+
+ return returnValue;
+
+ } catch (IOException e) {
+ clientRefLog.log(Log.BRIEF,
+ "IOException unmarshalling return: ", e);
+ throw new UnmarshalException("error unmarshalling return", e);
+ } catch (ClassNotFoundException e) {
+ clientRefLog.log(Log.BRIEF,
+ "ClassNotFoundException unmarshalling return: ", e);
+
+ throw new UnmarshalException("error unmarshalling return", e);
+ } finally {
+ try {
+ call.done();
+ } catch (IOException e) {
+ /* WARNING: If the conn has been reused early,
+ * then it is too late to recover from thrown
+ * IOExceptions caught here. This code is relying
+ * on StreamRemoteCall.done() not actually
+ * throwing IOExceptions.
+ */
+ reuse = false;
+ }
+ }
+
+ } catch (RuntimeException e) {
+ /*
+ * Need to distinguish between client (generated by the
+ * invoke method itself) and server RuntimeExceptions.
+ * Client side RuntimeExceptions are likely to have
+ * corrupted the call connection and those from the server
+ * are not likely to have done so. If the exception came
+ * from the server the call connection should be reused.
+ */
+ if ((call == null) ||
+ (((StreamRemoteCall) call).getServerException() != e))
+ {
+ reuse = false;
+ }
+ throw e;
+
+ } catch (RemoteException e) {
+ /*
+ * Some failure during call; assume connection cannot
+ * be reused. Must assume failure even if ServerException
+ * or ServerError occurs since these failures can happen
+ * during parameter deserialization which would leave
+ * the connection in a corrupted state.
+ */
+ reuse = false;
+ throw e;
+
+ } catch (Error e) {
+ /* If errors occurred, the connection is most likely not
+ * reusable.
+ */
+ reuse = false;
+ throw e;
+
+ } finally {
+
+ /* alreadyFreed ensures that we do not log a reuse that
+ * may have already happened.
+ */
+ if (!alreadyFreed) {
+ if (clientRefLog.isLoggable(Log.BRIEF)) {
+ clientRefLog.log(Log.BRIEF, "free connection (reuse = " +
+ reuse + ")");
+ }
+ ref.getChannel().free(conn, reuse);
+ }
+ }
+ }
+
+ protected void marshalCustomCallData(ObjectOutput out) throws IOException
+ {}
+
+ /**
+ * Marshal value to an ObjectOutput sink using RMI's serialization
+ * format for parameters or return values.
+ */
+ protected static void marshalValue(Class<?> type, Object value,
+ ObjectOutput out)
+ throws IOException
+ {
+ if (type.isPrimitive()) {
+ if (type == int.class) {
+ out.writeInt(((Integer) value).intValue());
+ } else if (type == boolean.class) {
+ out.writeBoolean(((Boolean) value).booleanValue());
+ } else if (type == byte.class) {
+ out.writeByte(((Byte) value).byteValue());
+ } else if (type == char.class) {
+ out.writeChar(((Character) value).charValue());
+ } else if (type == short.class) {
+ out.writeShort(((Short) value).shortValue());
+ } else if (type == long.class) {
+ out.writeLong(((Long) value).longValue());
+ } else if (type == float.class) {
+ out.writeFloat(((Float) value).floatValue());
+ } else if (type == double.class) {
+ out.writeDouble(((Double) value).doubleValue());
+ } else {
+ throw new Error("Unrecognized primitive type: " + type);
+ }
+ } else {
+ out.writeObject(value);
+ }
+ }
+
+ /**
+ * Unmarshal value from an ObjectInput source using RMI's serialization
+ * format for parameters or return values.
+ */
+ protected static Object unmarshalValue(Class<?> type, ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ if (type.isPrimitive()) {
+ if (type == int.class) {
+ return Integer.valueOf(in.readInt());
+ } else if (type == boolean.class) {
+ return Boolean.valueOf(in.readBoolean());
+ } else if (type == byte.class) {
+ return Byte.valueOf(in.readByte());
+ } else if (type == char.class) {
+ return Character.valueOf(in.readChar());
+ } else if (type == short.class) {
+ return Short.valueOf(in.readShort());
+ } else if (type == long.class) {
+ return Long.valueOf(in.readLong());
+ } else if (type == float.class) {
+ return Float.valueOf(in.readFloat());
+ } else if (type == double.class) {
+ return Double.valueOf(in.readDouble());
+ } else {
+ throw new Error("Unrecognized primitive type: " + type);
+ }
+ } else {
+ return in.readObject();
+ }
+ }
+
+ /**
+ * Create an appropriate call object for a new call on this object.
+ * Passing operation array and index, allows the stubs generator to
+ * assign the operation indexes and interpret them. The RemoteRef
+ * may need the operation to encode in for the call.
+ */
+ public RemoteCall newCall(RemoteObject obj, Operation[] ops, int opnum,
+ long hash)
+ throws RemoteException
+ {
+ clientRefLog.log(Log.BRIEF, "get connection");
+
+ Connection conn = ref.getChannel().newConnection();
+ try {
+ clientRefLog.log(Log.VERBOSE, "create call context");
+
+ /* log information about the outgoing call */
+ if (clientCallLog.isLoggable(Log.VERBOSE)) {
+ logClientCall(obj, ops[opnum]);
+ }
+
+ RemoteCall call =
+ new StreamRemoteCall(conn, ref.getObjID(), opnum, hash);
+ try {
+ marshalCustomCallData(call.getOutputStream());
+ } catch (IOException e) {
+ throw new MarshalException("error marshaling " +
+ "custom call data");
+ }
+ return call;
+ } catch (RemoteException e) {
+ ref.getChannel().free(conn, false);
+ throw e;
+ }
+ }
+
+ /**
+ * Invoke makes the remote call present in the RemoteCall object.
+ *
+ * Invoke will raise any "user" exceptions which
+ * should pass through and not be caught by the stub. If any
+ * exception is raised during the remote invocation, invoke should
+ * take care of cleaning up the connection before raising the
+ * "user" or remote exception.
+ */
+ public void invoke(RemoteCall call) throws Exception {
+ try {
+ clientRefLog.log(Log.VERBOSE, "execute call");
+
+ call.executeCall();
+
+ } catch (RemoteException e) {
+ /*
+ * Call did not complete; connection can't be reused.
+ */
+ clientRefLog.log(Log.BRIEF, "exception: ", e);
+ free(call, false);
+ throw e;
+
+ } catch (Error e) {
+ /* If errors occurred, the connection is most likely not
+ * reusable.
+ */
+ clientRefLog.log(Log.BRIEF, "error: ", e);
+ free(call, false);
+ throw e;
+
+ } catch (RuntimeException e) {
+ /*
+ * REMIND: Since runtime exceptions are no longer wrapped,
+ * we can't assue that the connection was left in
+ * a reusable state. Is this okay?
+ */
+ clientRefLog.log(Log.BRIEF, "exception: ", e);
+ free(call, false);
+ throw e;
+
+ } catch (Exception e) {
+ /*
+ * Assume that these other exceptions are user exceptions
+ * and leave the connection in a reusable state.
+ */
+ clientRefLog.log(Log.BRIEF, "exception: ", e);
+ free(call, true);
+ /* reraise user (and unknown) exceptions. */
+ throw e;
+ }
+
+ /*
+ * Don't free the connection if an exception did not
+ * occur because the stub needs to unmarshal the
+ * return value. The connection will be freed
+ * by a call to the "done" method.
+ */
+ }
+
+ /**
+ * Private method to free a connection.
+ */
+ private void free(RemoteCall call, boolean reuse) throws RemoteException {
+ Connection conn = ((StreamRemoteCall)call).getConnection();
+ ref.getChannel().free(conn, reuse);
+ }
+
+ /**
+ * Done should only be called if the invoke returns successfully
+ * (non-exceptionally) to the stub. It allows the remote reference to
+ * clean up (or reuse) the connection.
+ */
+ public void done(RemoteCall call) throws RemoteException {
+
+ /* Done only uses the connection inside the call to obtain the
+ * channel the connection uses. Once all information is read
+ * from the connection, the connection may be freed.
+ */
+ clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
+
+ /* Free the call connection early. */
+ free(call, true);
+
+ try {
+ call.done();
+ } catch (IOException e) {
+ /* WARNING: If the conn has been reused early, then it is
+ * too late to recover from thrown IOExceptions caught
+ * here. This code is relying on StreamRemoteCall.done()
+ * not actually throwing IOExceptions.
+ */
+ }
+ }
+
+ /**
+ * Log the details of an outgoing call. The method parameter is either of
+ * type java.lang.reflect.Method or java.rmi.server.Operation.
+ */
+ void logClientCall(Object obj, Object method) {
+ clientCallLog.log(Log.VERBOSE, "outbound call: " +
+ ref + " : " + obj.getClass().getName() +
+ ref.getObjID().toString() + ": " + method);
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized
+ */
+ public String getRefClass(ObjectOutput out) {
+ return "UnicastRef";
+ }
+
+ /**
+ * Write out external representation for remote ref.
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ ref.write(out, false);
+ }
+
+ /**
+ * Read in external representation for remote ref.
+ * @exception ClassNotFoundException If the class for an object
+ * being restored cannot be found.
+ */
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ ref = LiveRef.read(in, false);
+ }
+
+ //----------------------------------------------------------------------;
+ /**
+ * Method from object, forward from RemoteObject
+ */
+ public String remoteToString() {
+ return Util.getUnqualifiedName(getClass()) + " [liveRef: " + ref + "]";
+ }
+
+ /**
+ * default implementation of hashCode for remote objects
+ */
+ public int remoteHashCode() {
+ return ref.hashCode();
+ }
+
+ /** default implementation of equals for remote objects
+ */
+ public boolean remoteEquals(RemoteRef sub) {
+ if (sub instanceof UnicastRef)
+ return ref.remoteEquals(((UnicastRef)sub).ref);
+ return false;
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/UnicastRef2.java b/src/share/classes/sun/rmi/server/UnicastRef2.java
new file mode 100644
index 000000000..011a27e50
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/UnicastRef2.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import sun.rmi.transport.LiveRef;
+
+/**
+ * NOTE: There is a JDK-internal dependency on the existence of this
+ * class and its getLiveRef method (inherited from UnicastRef) in the
+ * implementation of javax.management.remote.rmi.RMIConnector.
+ **/
+public class UnicastRef2 extends UnicastRef {
+
+ /**
+ * Create a new (empty) Unicast remote reference.
+ */
+ public UnicastRef2()
+ {}
+
+ /**
+ * Create a new Unicast RemoteRef.
+ */
+ public UnicastRef2(LiveRef liveRef) {
+ super(liveRef);
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized
+ */
+ public String getRefClass(ObjectOutput out)
+ {
+ return "UnicastRef2";
+ }
+
+ /**
+ * Write out external representation for remote ref.
+ */
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ ref.write(out, true);
+ }
+
+ /**
+ * Read in external representation for remote ref.
+ * @exception ClassNotFoundException If the class for an object
+ * being restored cannot be found.
+ */
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ ref = LiveRef.read(in, true);
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef.java b/src/share/classes/sun/rmi/server/UnicastServerRef.java
new file mode 100644
index 000000000..63510db4f
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/UnicastServerRef.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.rmi.MarshalException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.ServerError;
+import java.rmi.ServerException;
+import java.rmi.UnmarshalException;
+import java.rmi.server.ExportException;
+import java.rmi.server.RemoteCall;
+import java.rmi.server.RemoteRef;
+import java.rmi.server.RemoteStub;
+import java.rmi.server.ServerNotActiveException;
+import java.rmi.server.ServerRef;
+import java.rmi.server.Skeleton;
+import java.rmi.server.SkeletonNotFoundException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import sun.rmi.runtime.Log;
+import sun.rmi.transport.LiveRef;
+import sun.rmi.transport.Target;
+import sun.rmi.transport.tcp.TCPTransport;
+import sun.security.action.GetBooleanAction;
+
+/**
+ * UnicastServerRef implements the remote reference layer server-side
+ * behavior for remote objects exported with the "UnicastRef" reference
+ * type.
+ *
+ * @author Ann Wollrath
+ * @author Roger Riggs
+ * @author Peter Jones
+ */
+public class UnicastServerRef extends UnicastRef
+ implements ServerRef, Dispatcher
+{
+ /** value of server call log property */
+ public static final boolean logCalls = AccessController.doPrivileged(
+ new GetBooleanAction("java.rmi.server.logCalls"));
+
+ /** server call log */
+ public static final Log callLog =
+ Log.getLog("sun.rmi.server.call", "RMI", logCalls);
+
+ // use serialVersionUID from JDK 1.2.2 for interoperability
+ private static final long serialVersionUID = -7384275867073752268L;
+
+ /** flag to enable writing exceptions to System.err */
+ private static final boolean wantExceptionLog =
+ AccessController.doPrivileged(
+ new GetBooleanAction("sun.rmi.server.exceptionTrace"));
+
+ private boolean forceStubUse = false;
+
+ /**
+ * flag to remove server-side stack traces before marshalling
+ * exceptions thrown by remote invocations to this VM
+ */
+ private static final boolean suppressStackTraces =
+ AccessController.doPrivileged(
+ new GetBooleanAction(
+ "sun.rmi.server.suppressStackTraces"));
+
+ /**
+ * skeleton to dispatch remote calls through, for 1.1 stub protocol
+ * (may be null if stub class only uses 1.2 stub protocol)
+ */
+ private transient Skeleton skel;
+
+ /** maps method hash to Method object for each remote method */
+ private transient Map<Long,Method> hashToMethod_Map = null;
+
+ /**
+ * A weak hash map, mapping classes to hash maps that map method
+ * hashes to method objects.
+ **/
+ private static final WeakClassHashMap<Map<Long,Method>> hashToMethod_Maps =
+ new HashToMethod_Maps();
+
+ /** cache of impl classes that have no corresponding skeleton class */
+ private static final Map<Class<?>,?> withoutSkeletons =
+ Collections.synchronizedMap(new WeakHashMap<Class<?>,Void>());
+
+ /**
+ * Create a new (empty) Unicast server remote reference.
+ */
+ public UnicastServerRef() {
+ }
+
+ /**
+ * Construct a Unicast server remote reference for a specified
+ * liveRef.
+ */
+ public UnicastServerRef(LiveRef ref) {
+ super(ref);
+ }
+
+ /**
+ * Construct a Unicast server remote reference to be exported
+ * on the specified port.
+ */
+ public UnicastServerRef(int port) {
+ super(new LiveRef(port));
+ }
+
+ /**
+ * Constructs a UnicastServerRef to be exported on an
+ * anonymous port (i.e., 0) and that uses a pregenerated stub class
+ * (NOT a dynamic proxy instance) if 'forceStubUse' is 'true'.
+ *
+ * This constructor is only called by the method
+ * UnicastRemoteObject.exportObject(Remote) passing 'true' for
+ * 'forceStubUse'. The UnicastRemoteObject.exportObject(Remote) method
+ * returns RemoteStub, so it must ensure that the stub for the
+ * exported object is an instance of a pregenerated stub class that
+ * extends RemoteStub (instead of an instance of a dynamic proxy class
+ * which is not an instance of RemoteStub).
+ **/
+ public UnicastServerRef(boolean forceStubUse) {
+ this(0);
+ this.forceStubUse = forceStubUse;
+ }
+
+ /**
+ * With the addition of support for dynamic proxies as stubs, this
+ * method is obsolete because it returns RemoteStub instead of the more
+ * general Remote. It should not be called. It sets the
+ * 'forceStubUse' flag to true so that the stub for the exported object
+ * is forced to be an instance of the pregenerated stub class, which
+ * extends RemoteStub.
+ *
+ * Export this object, create the skeleton and stubs for this
+ * dispatcher. Create a stub based on the type of the impl,
+ * initialize it with the appropriate remote reference. Create the
+ * target defined by the impl, dispatcher (this) and stub.
+ * Export that target via the Ref.
+ **/
+ public RemoteStub exportObject(Remote impl, Object data)
+ throws RemoteException
+ {
+ forceStubUse = true;
+ return (RemoteStub) exportObject(impl, data, false);
+ }
+
+ /**
+ * Export this object, create the skeleton and stubs for this
+ * dispatcher. Create a stub based on the type of the impl,
+ * initialize it with the appropriate remote reference. Create the
+ * target defined by the impl, dispatcher (this) and stub.
+ * Export that target via the Ref.
+ */
+ public Remote exportObject(Remote impl, Object data,
+ boolean permanent)
+ throws RemoteException
+ {
+ Class implClass = impl.getClass();
+ Remote stub;
+
+ try {
+ stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
+ } catch (IllegalArgumentException e) {
+ throw new ExportException(
+ "remote object implements illegal remote interface", e);
+ }
+ if (stub instanceof RemoteStub) {
+ setSkeleton(impl);
+ }
+
+ Target target =
+ new Target(impl, this, stub, ref.getObjID(), permanent);
+ ref.exportObject(target);
+ hashToMethod_Map = hashToMethod_Maps.get(implClass);
+ return stub;
+ }
+
+ /**
+ * Return the hostname of the current client. When called from a
+ * thread actively handling a remote method invocation the
+ * hostname of the client is returned.
+ * @exception ServerNotActiveException If called outside of servicing
+ * a remote method invocation.
+ */
+ public String getClientHost() throws ServerNotActiveException {
+ return TCPTransport.getClientHost();
+ }
+
+ /**
+ * Discovers and sets the appropriate skeleton for the impl.
+ */
+ public void setSkeleton(Remote impl) throws RemoteException {
+ if (!withoutSkeletons.containsKey(impl.getClass())) {
+ try {
+ skel = Util.createSkeleton(impl);
+ } catch (SkeletonNotFoundException e) {
+ /*
+ * Ignore exception for skeleton class not found, because a
+ * skeleton class is not necessary with the 1.2 stub protocol.
+ * Remember that this impl's class does not have a skeleton
+ * class so we don't waste time searching for it again.
+ */
+ withoutSkeletons.put(impl.getClass(), null);
+ }
+ }
+ }
+
+ /**
+ * Call to dispatch to the remote object (on the server side).
+ * The up-call to the server and the marshalling of return result
+ * (or exception) should be handled before returning from this
+ * method.
+ * @param obj the target remote object for the call
+ * @param call the "remote call" from which operation and
+ * method arguments can be obtained.
+ * @exception IOException If unable to marshal return result or
+ * release input or output streams
+ */
+ public void dispatch(Remote obj, RemoteCall call) throws IOException {
+ // positive operation number in 1.1 stubs;
+ // negative version number in 1.2 stubs and beyond...
+ int num;
+ long op;
+
+ try {
+ // read remote call header
+ ObjectInput in;
+ try {
+ in = call.getInputStream();
+ num = in.readInt();
+ if (num >= 0) {
+ if (skel != null) {
+ oldDispatch(obj, call, num);
+ return;
+ } else {
+ throw new UnmarshalException(
+ "skeleton class not found but required " +
+ "for client version");
+ }
+ }
+ op = in.readLong();
+ } catch (Exception readEx) {
+ throw new UnmarshalException("error unmarshalling call header",
+ readEx);
+ }
+
+ /*
+ * Since only system classes (with null class loaders) will be on
+ * the execution stack during parameter unmarshalling for the 1.2
+ * stub protocol, tell the MarshalInputStream not to bother trying
+ * to resolve classes using its superclasses's default method of
+ * consulting the first non-null class loader on the stack.
+ */
+ MarshalInputStream marshalStream = (MarshalInputStream) in;
+ marshalStream.skipDefaultResolveClass();
+
+ Method method = hashToMethod_Map.get(op);
+ if (method == null) {
+ throw new UnmarshalException("unrecognized method hash: " +
+ "method not supported by remote object");
+ }
+
+ // if calls are being logged, write out object id and operation
+ logCall(obj, method);
+
+ // unmarshal parameters
+ Class[] types = method.getParameterTypes();
+ Object[] params = new Object[types.length];
+
+ try {
+ unmarshalCustomCallData(in);
+ for (int i = 0; i < types.length; i++) {
+ params[i] = unmarshalValue(types[i], in);
+ }
+ } catch (java.io.IOException e) {
+ throw new UnmarshalException(
+ "error unmarshalling arguments", e);
+ } catch (ClassNotFoundException e) {
+ throw new UnmarshalException(
+ "error unmarshalling arguments", e);
+ } finally {
+ call.releaseInputStream();
+ }
+
+ // make upcall on remote object
+ Object result;
+ try {
+ result = method.invoke(obj, params);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+
+ // marshal return value
+ try {
+ ObjectOutput out = call.getResultStream(true);
+ Class rtype = method.getReturnType();
+ if (rtype != void.class) {
+ marshalValue(rtype, result, out);
+ }
+ } catch (IOException ex) {
+ throw new MarshalException("error marshalling return", ex);
+ /*
+ * This throw is problematic because when it is caught below,
+ * we attempt to marshal it back to the client, but at this
+ * point, a "normal return" has already been indicated,
+ * so marshalling an exception will corrupt the stream.
+ * This was the case with skeletons as well; there is no
+ * immediately obvious solution without a protocol change.
+ */
+ }
+ } catch (Throwable e) {
+ logCallException(e);
+
+ ObjectOutput out = call.getResultStream(false);
+ if (e instanceof Error) {
+ e = new ServerError(
+ "Error occurred in server thread", (Error) e);
+ } else if (e instanceof RemoteException) {
+ e = new ServerException(
+ "RemoteException occurred in server thread",
+ (Exception) e);
+ }
+ if (suppressStackTraces) {
+ clearStackTraces(e);
+ }
+ out.writeObject(e);
+ } finally {
+ call.releaseInputStream(); // in case skeleton doesn't
+ call.releaseOutputStream();
+ }
+ }
+
+ protected void unmarshalCustomCallData(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {}
+
+ /**
+ * Handle server-side dispatch using the RMI 1.1 stub/skeleton
+ * protocol, given a non-negative operation number that has
+ * already been read from the call stream.
+ *
+ * @param obj the target remote object for the call
+ * @param call the "remote call" from which operation and
+ * method arguments can be obtained.
+ * @param op the operation number
+ * @exception IOException if unable to marshal return result or
+ * release input or output streams
+ */
+ public void oldDispatch(Remote obj, RemoteCall call, int op)
+ throws IOException
+ {
+ long hash; // hash for matching stub with skeleton
+
+ try {
+ // read remote call header
+ ObjectInput in;
+ try {
+ in = call.getInputStream();
+ hash = in.readLong();
+ } catch (Exception readEx) {
+ throw new UnmarshalException("error unmarshalling call header",
+ readEx);
+ }
+
+ // if calls are being logged, write out object id and operation
+ logCall(obj, skel.getOperations()[op]);
+ unmarshalCustomCallData(in);
+ // dispatch to skeleton for remote object
+ skel.dispatch(obj, call, op, hash);
+
+ } catch (Throwable e) {
+ logCallException(e);
+
+ ObjectOutput out = call.getResultStream(false);
+ if (e instanceof Error) {
+ e = new ServerError(
+ "Error occurred in server thread", (Error) e);
+ } else if (e instanceof RemoteException) {
+ e = new ServerException(
+ "RemoteException occurred in server thread",
+ (Exception) e);
+ }
+ if (suppressStackTraces) {
+ clearStackTraces(e);
+ }
+ out.writeObject(e);
+ } finally {
+ call.releaseInputStream(); // in case skeleton doesn't
+ call.releaseOutputStream();
+ }
+ }
+
+ /**
+ * Clear the stack trace of the given Throwable by replacing it with
+ * an empty StackTraceElement array, and do the same for all of its
+ * chained causative exceptions.
+ */
+ public static void clearStackTraces(Throwable t) {
+ StackTraceElement[] empty = new StackTraceElement[0];
+ while (t != null) {
+ t.setStackTrace(empty);
+ t = t.getCause();
+ }
+ }
+
+ /**
+ * Log the details of an incoming call. The method parameter is either of
+ * type java.lang.reflect.Method or java.rmi.server.Operation.
+ */
+ private void logCall(Remote obj, Object method) {
+ if (callLog.isLoggable(Log.VERBOSE)) {
+ String clientHost;
+ try {
+ clientHost = getClientHost();
+ } catch (ServerNotActiveException snae) {
+ clientHost = "(local)"; // shouldn't happen
+ }
+ callLog.log(Log.VERBOSE, "[" + clientHost + ": " +
+ obj.getClass().getName() +
+ ref.getObjID().toString() + ": " +
+ method + "]");
+ }
+ }
+
+ /**
+ * Log the exception detail of an incoming call.
+ */
+ private void logCallException(Throwable e) {
+ // if calls are being logged, log them
+ if (callLog.isLoggable(Log.BRIEF)) {
+ String clientHost = "";
+ try {
+ clientHost = "[" + getClientHost() + "] ";
+ } catch (ServerNotActiveException snae) {
+ }
+ callLog.log(Log.BRIEF, clientHost + "exception: ", e);
+ }
+
+ // write exceptions (only) to System.err if desired
+ if (wantExceptionLog) {
+ java.io.PrintStream log = System.err;
+ synchronized (log) {
+ log.println();
+ log.println("Exception dispatching call to " +
+ ref.getObjID() + " in thread \"" +
+ Thread.currentThread().getName() +
+ "\" at " + (new Date()) + ":");
+ e.printStackTrace(log);
+ }
+ }
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized.
+ */
+ public String getRefClass(ObjectOutput out) {
+ return "UnicastServerRef";
+ }
+
+ /**
+ * Return the client remote reference for this remoteRef.
+ * In the case of a client RemoteRef "this" is the answer.
+ * For a server remote reference, a client side one will have to
+ * found or created.
+ */
+ protected RemoteRef getClientRef() {
+ return new UnicastRef(ref);
+ }
+
+ /**
+ * Write out external representation for remote ref.
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ }
+
+ /**
+ * Read in external representation for remote ref.
+ * @exception ClassNotFoundException If the class for an object
+ * being restored cannot be found.
+ */
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ // object is re-exported elsewhere (e.g., by UnicastRemoteObject)
+ ref = null;
+ skel = null;
+ }
+
+
+ /**
+ * A weak hash map, mapping classes to hash maps that map method
+ * hashes to method objects.
+ **/
+ private static class HashToMethod_Maps
+ extends WeakClassHashMap<Map<Long,Method>>
+ {
+ HashToMethod_Maps() {}
+
+ protected Map<Long,Method> computeValue(Class<?> remoteClass) {
+ Map<Long,Method> map = new HashMap<Long,Method>();
+ for (Class<?> cl = remoteClass;
+ cl != null;
+ cl = cl.getSuperclass())
+ {
+ for (Class<?> intf : cl.getInterfaces()) {
+ if (Remote.class.isAssignableFrom(intf)) {
+ for (Method method : intf.getMethods()) {
+ final Method m = method;
+ /*
+ * Set this Method object to override language
+ * access checks so that the dispatcher can invoke
+ * methods from non-public remote interfaces.
+ */
+ AccessController.doPrivileged(
+ new PrivilegedAction<Void>() {
+ public Void run() {
+ m.setAccessible(true);
+ return null;
+ }
+ });
+ map.put(Util.computeMethodHash(m), m);
+ }
+ }
+ }
+ }
+ return map;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef2.java b/src/share/classes/sun/rmi/server/UnicastServerRef2.java
new file mode 100644
index 000000000..d2877ee70
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/UnicastServerRef2.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.rmi.*;
+import java.rmi.server.*;
+import sun.rmi.transport.*;
+import sun.rmi.transport.tcp.*;
+
+/**
+ * Server-side ref for a remote impl that uses a custom socket factory.
+ *
+ * @author Ann Wollrath
+ * @author Roger Riggs
+ */
+public class UnicastServerRef2 extends UnicastServerRef
+{
+ // use serialVersionUID from JDK 1.2.2 for interoperability
+ private static final long serialVersionUID = -2289703812660767614L;
+
+ /**
+ * Create a new (empty) Unicast server remote reference.
+ */
+ public UnicastServerRef2()
+ {}
+
+ /**
+ * Construct a Unicast server remote reference for a specified
+ * liveRef.
+ */
+ public UnicastServerRef2(LiveRef ref)
+ {
+ super(ref);
+ }
+
+ /**
+ * Construct a Unicast server remote reference to be exported
+ * on the specified port.
+ */
+ public UnicastServerRef2(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ super(new LiveRef(port, csf, ssf));
+ }
+
+ /**
+ * Returns the class of the ref type to be serialized
+ */
+ public String getRefClass(ObjectOutput out)
+ {
+ return "UnicastServerRef2";
+ }
+
+ /**
+ * Return the client remote reference for this remoteRef.
+ * In the case of a client RemoteRef "this" is the answer.
+ * For a server remote reference, a client side one will have to
+ * found or created.
+ */
+ protected RemoteRef getClientRef() {
+ return new UnicastRef2(ref);
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/Util.java b/src/share/classes/sun/rmi/server/Util.java
new file mode 100644
index 000000000..f103319ce
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/Util.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.StubNotFoundException;
+import java.rmi.registry.Registry;
+import java.rmi.server.LogStream;
+import java.rmi.server.ObjID;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RemoteObjectInvocationHandler;
+import java.rmi.server.RemoteRef;
+import java.rmi.server.RemoteStub;
+import java.rmi.server.Skeleton;
+import java.rmi.server.SkeletonNotFoundException;
+import java.security.AccessController;
+import java.security.MessageDigest;
+import java.security.DigestOutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import sun.rmi.registry.RegistryImpl;
+import sun.rmi.runtime.Log;
+import sun.rmi.transport.LiveRef;
+import sun.rmi.transport.tcp.TCPEndpoint;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * A utility class with static methods for creating stubs/proxies and
+ * skeletons for remote objects.
+ */
+public final class Util {
+
+ /** "server" package log level */
+ static final int logLevel = LogStream.parseLevel(
+ (String) AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.server.logLevel")));
+
+ /** server reference log */
+ public static final Log serverRefLog =
+ Log.getLog("sun.rmi.server.ref", "transport", Util.logLevel);
+
+ /** cached value of property java.rmi.server.ignoreStubClasses */
+ private static final boolean ignoreStubClasses =
+ ((Boolean) AccessController.doPrivileged(
+ new GetBooleanAction("java.rmi.server.ignoreStubClasses"))).
+ booleanValue();
+
+ /** cache of impl classes that have no corresponding stub class */
+ private static final Map withoutStubs =
+ Collections.synchronizedMap(new WeakHashMap(11));
+
+ /** parameter types for stub constructor */
+ private static final Class[] stubConsParamTypes = { RemoteRef.class };
+
+ private Util() {
+ }
+
+ /**
+ * Returns a proxy for the specified implClass.
+ *
+ * If both of the following criteria is satisfied, a dynamic proxy for
+ * the specified implClass is returned (otherwise a RemoteStub instance
+ * for the specified implClass is returned):
+ *
+ * a) either the property java.rmi.server.ignoreStubClasses is true or
+ * a pregenerated stub class does not exist for the impl class, and
+ * b) forceStubUse is false.
+ *
+ * If the above criteria are satisfied, this method constructs a
+ * dynamic proxy instance (that implements the remote interfaces of
+ * implClass) constructed with a RemoteObjectInvocationHandler instance
+ * constructed with the clientRef.
+ *
+ * Otherwise, this method loads the pregenerated stub class (which
+ * extends RemoteStub and implements the remote interfaces of
+ * implClass) and constructs an instance of the pregenerated stub
+ * class with the clientRef.
+ *
+ * @param implClass the class to obtain remote interfaces from
+ * @param clientRef the remote ref to use in the invocation handler
+ * @param forceStubUse if true, forces creation of a RemoteStub
+ * @throws IllegalArgumentException if implClass implements illegal
+ * remote interfaces
+ * @throws StubNotFoundException if problem locating/creating stub or
+ * creating the dynamic proxy instance
+ **/
+ public static Remote createProxy(Class implClass,
+ RemoteRef clientRef,
+ boolean forceStubUse)
+ throws StubNotFoundException
+ {
+ Class remoteClass;
+
+ try {
+ remoteClass = getRemoteClass(implClass);
+ } catch (ClassNotFoundException ex ) {
+ throw new StubNotFoundException(
+ "object does not implement a remote interface: " +
+ implClass.getName());
+ }
+
+ if (forceStubUse ||
+ !(ignoreStubClasses || !stubClassExists(remoteClass)))
+ {
+ return createStub(remoteClass, clientRef);
+ }
+
+ ClassLoader loader = implClass.getClassLoader();
+ Class[] interfaces = getRemoteInterfaces(implClass);
+ InvocationHandler handler =
+ new RemoteObjectInvocationHandler(clientRef);
+
+ /* REMIND: private remote interfaces? */
+
+ try {
+ return (Remote) Proxy.newProxyInstance(loader,
+ interfaces,
+ handler);
+ } catch (IllegalArgumentException e) {
+ throw new StubNotFoundException("unable to create proxy", e);
+ }
+ }
+
+ /**
+ * Returns true if a stub class for the given impl class can be loaded,
+ * otherwise returns false.
+ *
+ * @param remoteClass the class to obtain remote interfaces from
+ */
+ private static boolean stubClassExists(Class remoteClass) {
+ if (!withoutStubs.containsKey(remoteClass)) {
+ try {
+ Class.forName(remoteClass.getName() + "_Stub",
+ false,
+ remoteClass.getClassLoader());
+ return true;
+
+ } catch (ClassNotFoundException cnfe) {
+ withoutStubs.put(remoteClass, null);
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Returns the class/superclass that implements the remote interface.
+ * @throws ClassNotFoundException if no class is found to have a
+ * remote interface
+ */
+ private static Class getRemoteClass(Class cl)
+ throws ClassNotFoundException
+ {
+ while (cl != null) {
+ Class[] interfaces = cl.getInterfaces();
+ for (int i = interfaces.length -1; i >= 0; i--) {
+ if (Remote.class.isAssignableFrom(interfaces[i]))
+ return cl; // this class implements remote object
+ }
+ cl = cl.getSuperclass();
+ }
+ throw new ClassNotFoundException(
+ "class does not implement java.rmi.Remote");
+ }
+
+ /**
+ * Returns an array containing the remote interfaces implemented
+ * by the given class.
+ *
+ * @param remoteClass the class to obtain remote interfaces from
+ * @throws IllegalArgumentException if remoteClass implements
+ * any illegal remote interfaces
+ * @throws NullPointerException if remoteClass is null
+ */
+ private static Class[] getRemoteInterfaces(Class remoteClass) {
+ ArrayList list = new ArrayList();
+ getRemoteInterfaces(list, remoteClass);
+ return (Class []) list.toArray(new Class[list.size()]);
+ }
+
+ /**
+ * Fills the given array list with the remote interfaces implemented
+ * by the given class.
+ *
+ * @throws IllegalArgumentException if the specified class implements
+ * any illegal remote interfaces
+ * @throws NullPointerException if the specified class or list is null
+ */
+ private static void getRemoteInterfaces(ArrayList list, Class cl) {
+ Class superclass = cl.getSuperclass();
+ if (superclass != null) {
+ getRemoteInterfaces(list, superclass);
+ }
+
+ Class[] interfaces = cl.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ Class intf = interfaces[i];
+ /*
+ * If it is a remote interface (if it extends from
+ * java.rmi.Remote) and is not already in the list,
+ * then add the interface to the list.
+ */
+ if (Remote.class.isAssignableFrom(intf)) {
+ if (!(list.contains(intf))) {
+ Method[] methods = intf.getMethods();
+ for (int j = 0; j < methods.length; j++) {
+ checkMethod(methods[j]);
+ }
+ list.add(intf);
+ }
+ }
+ }
+ }
+
+ /**
+ * Verifies that the supplied method has at least one declared exception
+ * type that is RemoteException or one of its superclasses. If not,
+ * then this method throws IllegalArgumentException.
+ *
+ * @throws IllegalArgumentException if m is an illegal remote method
+ */
+ private static void checkMethod(Method m) {
+ Class[] ex = m.getExceptionTypes();
+ for (int i = 0; i < ex.length; i++) {
+ if (ex[i].isAssignableFrom(RemoteException.class))
+ return;
+ }
+ throw new IllegalArgumentException(
+ "illegal remote method encountered: " + m);
+ }
+
+ /**
+ * Creates a RemoteStub instance for the specified class, constructed
+ * with the specified RemoteRef. The supplied class must be the most
+ * derived class in the remote object's superclass chain that
+ * implements a remote interface. The stub class name is the name of
+ * the specified remoteClass with the suffix "_Stub". The loading of
+ * the stub class is initiated from class loader of the specified class
+ * (which may be the bootstrap class loader).
+ **/
+ private static RemoteStub createStub(Class remoteClass, RemoteRef ref)
+ throws StubNotFoundException
+ {
+ String stubname = remoteClass.getName() + "_Stub";
+
+ /* Make sure to use the local stub loader for the stub classes.
+ * When loaded by the local loader the load path can be
+ * propagated to remote clients, by the MarshalOutputStream/InStream
+ * pickle methods
+ */
+ try {
+ Class stubcl =
+ Class.forName(stubname, false, remoteClass.getClassLoader());
+ Constructor cons = stubcl.getConstructor(stubConsParamTypes);
+ return (RemoteStub) cons.newInstance(new Object[] { ref });
+
+ } catch (ClassNotFoundException e) {
+ throw new StubNotFoundException(
+ "Stub class not found: " + stubname, e);
+ } catch (NoSuchMethodException e) {
+ throw new StubNotFoundException(
+ "Stub class missing constructor: " + stubname, e);
+ } catch (InstantiationException e) {
+ throw new StubNotFoundException(
+ "Can't create instance of stub class: " + stubname, e);
+ } catch (IllegalAccessException e) {
+ throw new StubNotFoundException(
+ "Stub class constructor not public: " + stubname, e);
+ } catch (InvocationTargetException e) {
+ throw new StubNotFoundException(
+ "Exception creating instance of stub class: " + stubname, e);
+ } catch (ClassCastException e) {
+ throw new StubNotFoundException(
+ "Stub class not instance of RemoteStub: " + stubname, e);
+ }
+ }
+
+ /**
+ * Locate and return the Skeleton for the specified remote object
+ */
+ static Skeleton createSkeleton(Remote object)
+ throws SkeletonNotFoundException
+ {
+ Class cl;
+ try {
+ cl = getRemoteClass(object.getClass());
+ } catch (ClassNotFoundException ex ) {
+ throw new SkeletonNotFoundException(
+ "object does not implement a remote interface: " +
+ object.getClass().getName());
+ }
+
+ // now try to load the skeleton based ont he name of the class
+ String skelname = cl.getName() + "_Skel";
+ try {
+ Class skelcl = Class.forName(skelname, false, cl.getClassLoader());
+
+ return (Skeleton)skelcl.newInstance();
+ } catch (ClassNotFoundException ex) {
+ throw new SkeletonNotFoundException("Skeleton class not found: " +
+ skelname, ex);
+ } catch (InstantiationException ex) {
+ throw new SkeletonNotFoundException("Can't create skeleton: " +
+ skelname, ex);
+ } catch (IllegalAccessException ex) {
+ throw new SkeletonNotFoundException("No public constructor: " +
+ skelname, ex);
+ } catch (ClassCastException ex) {
+ throw new SkeletonNotFoundException(
+ "Skeleton not of correct class: " + skelname, ex);
+ }
+ }
+
+ /**
+ * Compute the "method hash" of a remote method. The method hash
+ * is a long containing the first 64 bits of the SHA digest from
+ * the UTF encoded string of the method name and descriptor.
+ */
+ public static long computeMethodHash(Method m) {
+ long hash = 0;
+ ByteArrayOutputStream sink = new ByteArrayOutputStream(127);
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ DataOutputStream out = new DataOutputStream(
+ new DigestOutputStream(sink, md));
+
+ String s = getMethodNameAndDescriptor(m);
+ if (serverRefLog.isLoggable(Log.VERBOSE)) {
+ serverRefLog.log(Log.VERBOSE,
+ "string used for method hash: \"" + s + "\"");
+ }
+ out.writeUTF(s);
+
+ // use only the first 64 bits of the digest for the hash
+ out.flush();
+ byte hasharray[] = md.digest();
+ for (int i = 0; i < Math.min(8, hasharray.length); i++) {
+ hash += ((long) (hasharray[i] & 0xFF)) << (i * 8);
+ }
+ } catch (IOException ignore) {
+ /* can't happen, but be deterministic anyway. */
+ hash = -1;
+ } catch (NoSuchAlgorithmException complain) {
+ throw new SecurityException(complain.getMessage());
+ }
+ return hash;
+ }
+
+ /**
+ * Return a string consisting of the given method's name followed by
+ * its "method descriptor", as appropriate for use in the computation
+ * of the "method hash".
+ *
+ * See section 4.3.3 of The Java Virtual Machine Specification for
+ * the definition of a "method descriptor".
+ */
+ private static String getMethodNameAndDescriptor(Method m) {
+ StringBuffer desc = new StringBuffer(m.getName());
+ desc.append('(');
+ Class[] paramTypes = m.getParameterTypes();
+ for (int i = 0; i < paramTypes.length; i++) {
+ desc.append(getTypeDescriptor(paramTypes[i]));
+ }
+ desc.append(')');
+ Class returnType = m.getReturnType();
+ if (returnType == void.class) { // optimization: handle void here
+ desc.append('V');
+ } else {
+ desc.append(getTypeDescriptor(returnType));
+ }
+ return desc.toString();
+ }
+
+ /**
+ * Get the descriptor of a particular type, as appropriate for either
+ * a parameter or return type in a method descriptor.
+ */
+ private static String getTypeDescriptor(Class type) {
+ if (type.isPrimitive()) {
+ if (type == int.class) {
+ return "I";
+ } else if (type == boolean.class) {
+ return "Z";
+ } else if (type == byte.class) {
+ return "B";
+ } else if (type == char.class) {
+ return "C";
+ } else if (type == short.class) {
+ return "S";
+ } else if (type == long.class) {
+ return "J";
+ } else if (type == float.class) {
+ return "F";
+ } else if (type == double.class) {
+ return "D";
+ } else if (type == void.class) {
+ return "V";
+ } else {
+ throw new Error("unrecognized primitive type: " + type);
+ }
+ } else if (type.isArray()) {
+ /*
+ * According to JLS 20.3.2, the getName() method on Class does
+ * return the VM type descriptor format for array classes (only);
+ * using that should be quicker than the otherwise obvious code:
+ *
+ * return "[" + getTypeDescriptor(type.getComponentType());
+ */
+ return type.getName().replace('.', '/');
+ } else {
+ return "L" + type.getName().replace('.', '/') + ";";
+ }
+ }
+
+ /**
+ * Returns the binary name of the given type without package
+ * qualification. Nested types are treated no differently from
+ * top-level types, so for a nested type, the returned name will
+ * still be qualified with the simple name of its enclosing
+ * top-level type (and perhaps other enclosing types), the
+ * separator will be '$', etc.
+ **/
+ public static String getUnqualifiedName(Class c) {
+ String binaryName = c.getName();
+ return binaryName.substring(binaryName.lastIndexOf('.') + 1);
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/WeakClassHashMap.java b/src/share/classes/sun/rmi/server/WeakClassHashMap.java
new file mode 100644
index 000000000..e822c2655
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/WeakClassHashMap.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.server;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Abstract class that maps Class objects to lazily-computed values of
+ * type V. A concrete subclass must implement the computeValue method
+ * to determine how the values are computed.
+ *
+ * The keys are only weakly reachable through this map, so this map
+ * does not prevent a class (along with its class loader, etc.) from
+ * being garbage collected if it is not otherwise strongly reachable.
+ * The values are only softly reachable through this map, so that the
+ * computed values generally persist while not otherwise strongly
+ * reachable, but their storage may be reclaimed if necessary. Also,
+ * note that if a key is strongly reachable from a value, then the key
+ * is effectively softly reachable through this map, which may delay
+ * garbage collection of classes (see 4429536).
+ **/
+public abstract class WeakClassHashMap<V> {
+
+ private Map<Class<?>,ValueCell<V>> internalMap =
+ new WeakHashMap<Class<?>,ValueCell<V>>();
+
+ protected WeakClassHashMap() { }
+
+ public V get(Class<?> remoteClass) {
+ /*
+ * Use a mutable cell (a one-element list) to hold the soft
+ * reference to a value, to allow the lazy value computation
+ * to be synchronized with entry-level granularity instead of
+ * by locking the whole table.
+ */
+ ValueCell<V> valueCell;
+ synchronized (internalMap) {
+ valueCell = internalMap.get(remoteClass);
+ if (valueCell == null) {
+ valueCell = new ValueCell<V>();
+ internalMap.put(remoteClass, valueCell);
+ }
+ }
+ synchronized (valueCell) {
+ V value = null;
+ if (valueCell.ref != null) {
+ value = (V) valueCell.ref.get();
+ }
+ if (value == null) {
+ value = computeValue(remoteClass);
+ valueCell.ref = new SoftReference<V>(value);
+ }
+ return value;
+ }
+ }
+
+ protected abstract V computeValue(Class<?> remoteClass);
+
+ private static class ValueCell<T> {
+ Reference<T> ref = null;
+ ValueCell() { }
+ }
+}
diff --git a/src/share/classes/sun/rmi/server/resources/rmid.properties b/src/share/classes/sun/rmi/server/resources/rmid.properties
new file mode 100644
index 000000000..51642b6e1
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 1998-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid was launched from inetd with an invalid status (must be wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ port cannot be specified if rmid is launched from inetd
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ port is not a number
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ -port option requires argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ -log option requires argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ -log option required
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ illegal option: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: an exception occurred: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: unable to locate java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ rmid startup with inherited channel
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: invalid exec policy class
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: attempt to obtain exec policy throws:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: running "{0}"
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: activation group inactive: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: warning: sun.rmi.activation.execPolicy system\n\
+ property unspecified and no ExecPermissions/ExecOptionPermissions\n\
+ granted; subsequent activation attempts may fail due to unsuccessful\n\
+ ExecPermission/ExecOptionPermission permission checks. For\n\
+ documentation on how to configure rmid security, refer to:\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=Usage: {0} <options>\
+\n\
+\nwhere <options> include:\
+\n -port <port> Specify port for rmid to use\
+\n -log <directory> Specify directory in which rmid writes log\
+\n -stop Stop current invocation of rmid (for specified port)\
+\n -C<runtime flag> Pass argument to each child process (activation group)\
+\n -J<runtime flag> Pass argument to the java interpreter\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ activation daemon shut down
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (WARNING) restart group throws:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (WARNING) restart service throws:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (WARNING) log update throws:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (SEVERE) log snapshot throws:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (WARNING) {0}: skipping log record during recovery:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_de.properties b/src/share/classes/sun/rmi/server/resources/rmid_de.properties
new file mode 100644
index 000000000..62fd4906b
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_de.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid wurde mit einem ung\u00fcltigen Status (muss wait sein) von inetd gestartet
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ Der Port kann nicht angegeben werden, wenn rmid von inetd gestartet wird
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ Port ist keine Zahl
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ F\u00fcr Option -port ist ein Argument erforderlich.
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ F\u00fcr Option -log ist ein Argument erforderlich.
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ Die Option -log ist erforderlich.
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+Unzul\u00e4ssige Option: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: Es ist eine Ausnahme aufgetreten: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: java.home konnte nicht gefunden werden.
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ rmid-Start mit \u00fcbernommenem Kanal
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: ung\u00fcltige exec-Verfahrensklasse
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: Der Versuch, die exec-Richtlinie abzurufen, bewirkt:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: "{0}" wird ausgef\u00fchrt
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: Aktivierungsgruppe inaktiv: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: Achtung: Systemeigenschaft sun.rmi.activation.execPolicy\n\
+ nicht angegeben, und keine ExecPermissions/ExecOptionPermissions\n\
+ gew\u00e4hrt; erneute Aktivierung kann wegen erfolgloser\n\
+ Berechtigungspr\u00fcfungen ExecPermission/ExecOptionPermission fehlschlagen. Weitere\n\
+ Dokumentation \u00fcber die Konfiguration von rmid-Sicherheit finden Sie unter:\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=Syntax: {0} <Optionen>\
+\n\
+\nwobei folgende <Optionen> m\u00f6glich sind:\
+\n -port <Port> Angabe des Ports f\u00fcr rmid\
+\n -log <Verzeichnis> Angabe des Verzeichnisses, in das rmid die Log-Datei schreibt\
+\n -stop Aktuellen Aufruf von rmid stoppen (f\u00fcr den angegebenen Anschluss)\
+\n -C<Laufzeitflag> \u00dcbergeben des Arguments an jeden untergeordneten Prozess (Aktivierungsgruppe)\
+\n -J<Laufzeitflag> Argument an den Java-Interpreter \u00fcbergeben\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+Aktivierungsdaemon wird geschlossen.
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (WARNUNG) Neustart der Gruppe bewirkt:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (WARNUNG) Neustart des Dienstes bewirkt:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (WARNUNG) Aktualisierung des Protokolls bewirkt:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (SCHWERW.) Momentaufnahme des Protokolls bewirkt:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (WARNUNG) {0}: \u00dcberspringen eines Protokolleintrags bei der Wiederherstellung:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_es.properties b/src/share/classes/sun/rmi/server/resources/rmid_es.properties
new file mode 100644
index 000000000..8fa7bfb91
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_es.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid se ha iniciado desde inetd con un estado no v\u00e1lido (debe ser wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ no se puede especificar el puerto si rmid se ha iniciado desde inetd
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ el puerto no es un n\u00famero
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ la opci\u00f3n -port requiere un argumento
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ la opci\u00f3n -log requiere un argumento
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ la opci\u00f3n -log es obligatoria
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+opci\u00f3n no permitida: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: ha ocurrido una excepci\u00f3n: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: imposible encontrar java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ inicio de rmid con canal heredado
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: Clase de norma de ejecuci\u00f3n no v\u00e1lida
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: el intento de obtener la norma de ejecuci\u00f3n indica:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: en ejecuci\u00f3n "{0}"
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: grupo de activaci\u00f3n inactivo: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: advertencia: no se han especificado las propiedades del sistema sun.rmi.activation.execPolicy\n\
+ y no se han concedido ExecPermissions/ExecOptionPermissions;\n\
+ los intentos de activaci\u00f3n posteriores pueden fallar debido a\n\
+ comprobaciones de permiso ExecPermission/ExecOptionPermission no satisfactorias. Para\n\
+ obtener documentaci\u00f3n sobre c\u00f3mo configurar la seguridad rmid, rem\u00edtase a:\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=Sintaxis: {0} <opciones>\
+\n\
+\ndonde <opciones> incluye:\
+\n -port <puerto> Especificar puerto para uso de rmid\
+\n -log <directorio> Especificar directorio en el que rmid escribir\u00e1 el registro\
+\n -stop Detener la llamada actual de rmid (para el puerto especificado)\
+\n -C<indicador runtime> Pasar argumento a cada uno de los procesos subordinados (grupo de activaci\u00f3n)\
+\n -J<indicador runtime> Pasar argumento al int\u00e9rprete de Java\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+daemon de activaci\u00f3n desactivado
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (ADVERTENCIA) el reinicio del grupo indica:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (ADVERTENCIA) el reinicio del servicio indica:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (ADVERTENCIA) la actualizaci\u00f3n del registro indica:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (GRAVE) la instant\u00e1nea del registro indica:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (ADVERTENCIA) {0}: omitiendo la escritura del registro durante la recuperaci\u00f3n:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_fr.properties b/src/share/classes/sun/rmi/server/resources/rmid_fr.properties
new file mode 100644
index 000000000..c8c0b1ecf
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_fr.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid a \u00e9t\u00e9 lanc\u00e9 depuis inetd avec un statut invalide (doit \u00eatre wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ impossible de sp\u00e9cifier port si rmid est lanc\u00e9 depuis inetd
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ port n'est pas un num\u00e9ro
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ l'option -port exige un argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ l'option -log exige un argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ option -log requise
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ option incorrecte : {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main : une exception s''est produite : {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl : impossible de localiser java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ d\u00e9marrage de rmid avec le canal existant
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main : classe de r\u00e8gle exec incorrecte
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: tenter d'obtenir des basculements de classes exec :
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec : ex\u00e9cution de "{0}"
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid : groupe d''activation inactif : {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: avertissement : syst\u00e8me sun.rmi.activation.execPolicy\n\
+ propri\u00e9t\u00e9 non sp\u00e9cifi\u00e9e et ExecPermissions/ExecOptionPermissions\n\
+ non autoris\u00e9s ; les tentatives d'activation suivantes risquent d'\u00e9chouer en raison de la v\u00e9rification des permissions\n\
+ ExecPermission/ExecOptionPermission. Pour obtenir de la\n\
+ documentation sur la configuration de la s\u00e9curit\u00e9 rmid, reportez-vous \u00e0 :\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://javasun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=Syntaxe : {0} <options>\
+\n\
+\no\u00f9 <options> comprend :\
+\n -port <port> Port que rmid doit utiliser\
+\n -log <directory> R\u00e9pertoire o\u00f9 rmid enregistre le journal\
+\n -stop Arr\u00eater l''appel courant de rmid (pour le port sp\u00e9cifi\u00e9)\
+\n -C<indicateur d''ex\u00e9cution> Passe l''argument \u00e0 chaque processus fils (groupe d''activation)\
+\n -J<indicateur d''ex\u00e9cution> Passe l''argument \u00e0 l''interpr\u00e9teur Java\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ d\u00e9mon d'activation arr\u00eat\u00e9
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid : (AVERTISSEMENT) red\u00e9marrer les basculements de groupes :
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid : (AVERTISSEMENT) red\u00e9marrer les basculements de services :
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid : (AVERTISSEMENT) consigner les basculements de mises \u00e0 jour :
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid : (GRAVE) consigner les basculements de captures instantan\u00e9es :
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid : (AVERTISSEMENT) {0} : enregistrement du journal ignor\u00e9 pendant la r\u00e9cup\u00e9ration :
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_it.properties b/src/share/classes/sun/rmi/server/resources/rmid_it.properties
new file mode 100644
index 000000000..0a47e055b
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_it.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid \u00e8 stato avviato da inetd con uno stato non valido (diverso da wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ non \u00e8 possibile specificare la porta se rmid viene avviato da inetd
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ la porta non \u00e8 un numero
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ L'opzione -port richiede un argomento
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ L'opzione -log richiede un argomento
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ \u00c8 richiesta l'opzione -log
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ opzione illegale: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: si \u00e8 verificata un''eccezione: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: impossibile individuare java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ Avvio di rmid con canale ereditato
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: classe di policy eseguibile non valida
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: il tentativo di ottenere i criteri di esecuzione ha restituito:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: esecuzione di "{0}" in corso
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: gruppo attivazione inattivo: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: avviso: sistema sun.rmi.activation.execPolicy\n\
+ Propriet\u00e0 non specificata e nessun ExecPermissions/ExecOptionPermissions\n\
+ garantito. I tentativi di attivazione successivi potrebbero fallire a causa di \n\
+ controlli di autorizzazione ExecPermission/ExecOptionPermission non andati a buon fine. Per\n\
+ la documentazione e le modalit\u00e0 di configurazione della protezione rmid, fare riferimento a (informazioni in inglese):\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=Utilizzo: {0} <opzioni>\
+\n\
+\ndove <opzioni> comprende:\
+\n -port <porta> Specifica la porta usata da rmid\
+\n -log <directory> Specifica la directory in cui rmid scrive il log\
+\n -stop Interrompe l''invocazione corrente di rmid (per la porta specificata)\
+\n -C<flag runtime> Passa l''argomento a ciascun processo figlio (gruppo di attivazione)\
+\n -J<flag runtime> Passa l''argomento all''interprete java\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ daemon di attivazione terminato
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (AVVERTENZA) il riavvio del gruppo ha restituito:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (AVVERTENZA) il riavvio del servizio ha restituito:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (AVVERTENZA) il log dell'aggiornamento ha restituito:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (GRAVE) il log dell'istantanea ha restituito:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (AVVERTENZA) {0}: record del log ignorato durante il ripristino:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_ja.properties b/src/share/classes/sun/rmi/server/resources/rmid_ja.properties
new file mode 100644
index 000000000..6bc0e917e
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_ja.properties
@@ -0,0 +1,134 @@
+#
+#
+# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid \u304c inetd \u304b\u3089\u7121\u52b9\u306a\u72b6\u614b\u3067\u8d77\u52d5\u3055\u308c\u307e\u3057\u305f (wait \u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093)\u3002
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ rmid \u304c inetd \u304b\u3089\u8d77\u52d5\u3055\u308c\u305f\u5834\u5408\u3001\u30dd\u30fc\u30c8\u306f\u6307\u5b9a\u3067\u304d\u307e\u305b\u3093\u3002
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ "port" \u306f\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ -port \u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ -log \u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u306f\u5f15\u6570\u304c\u5fc5\u8981\u3067\u3059
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ -log \u30aa\u30d7\u30b7\u30e7\u30f3\u304c\u5fc5\u8981\u3067\u3059
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ \u4e0d\u6b63\u306a\u30aa\u30d7\u30b7\u30e7\u30f3: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: \u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: java.home \u3092\u691c\u51fa\u3067\u304d\u307e\u305b\u3093\u3002
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ rmid \u306f\u7d99\u627f\u3055\u308c\u305f\u30c1\u30e3\u30cd\u30eb\u3067\u8d77\u52d5\u3057\u307e\u3059\u3002
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: \u7121\u52b9\u306a\u5b9f\u884c\u30dd\u30ea\u30b7\u30fc\u30af\u30e9\u30b9\u3067\u3059\u3002
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: \u5b9f\u884c\u30dd\u30ea\u30b7\u30fc\u306e\u53d6\u5f97\u6642\u306e\u4f8b\u5916:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: "{0}" \u3092\u5b9f\u884c\u4e2d
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: \u8d77\u52d5\u30b0\u30eb\u30fc\u30d7\u304c\u505c\u6b62\u3057\u3066\u3044\u307e\u3059: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: \u8b66\u544a:sun.rmi.activation.execPolicy \u30b7\u30b9\u30c6\u30e0\n\
+ \u30d7\u30ed\u30d1\u30c6\u30a3\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u306a\u3044\u3001\u307e\u305f\u306f \n\
+ ExecPermissions/ExecOptionPermissions \u304c\u8a31\u53ef\u3055\u308c\u307e\u305b\u3093\u3002\n\
+ ExecPermissions/ExecOptionPermissions \u30a2\u30af\u30bb\u30b9\u6a29\u691c\u67fb\u3067\u8a31\u53ef\u3055\u308c\n\
+ \u306a\u3044\u305f\u3081\u3001\u3042\u3068\u306b\u7d9a\u304f\u8d77\u52d5\u306f\u5931\u6557\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\
+ rmid \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u8a2d\u5b9a\u65b9\u6cd5\u306b\u3064\u3044\u3066\u306f\u3001\u6b21\u306e\u30de\u30cb\u30e5\u30a2\u30eb\u3092\u53c2\u7167\u3057\u3066\n\
+ \u304f\u3060\u3055\u3044\u3002:\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=\u4f7f\u3044\u65b9: {0} <options>\
+\n\
+\n<options> \u306b\u306f\u6b21\u306e\u3082\u306e\u304c\u3042\u308a\u307e\u3059\u3002\
+\n -port <port> rmid \u304c\u4f7f\u7528\u3059\u308b\u30dd\u30fc\u30c8\u3092\u6307\u5b9a\u3059\u308b\
+\n -log <directory> rmid \u304c\u30ed\u30b0\u3092\u66f8\u304d\u8fbc\u3080\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u6307\u5b9a\u3059\u308b\
+\n -stop \u6307\u5b9a\u30dd\u30fc\u30c8\u306b\u5bfe\u3059\u308b rmid \u306e\u73fe\u5728\u306e\u547c\u3073\u51fa\u3057\u3092\u4e2d\u6b62\u3059\u308b\
+\n -C<runtime flag> \u5404\u5b50\u30d7\u30ed\u30bb\u30b9 (\u8d77\u52d5\u30b0\u30eb\u30fc\u30d7) \u306b\u5f15\u6570\u3092\u6e21\u3059\
+\n -J<runtime flag> java \u30a4\u30f3\u30bf\u30d7\u30ea\u30bf\u306b\u5f15\u6570\u3092\u6e21\u3059\
+\n
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ \u8d77\u52d5\u30c7\u30fc\u30e2\u30f3\u304c\u505c\u6b62\u3057\u307e\u3057\u305f\u3002
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (\u8b66\u544a) \u30b0\u30eb\u30fc\u30d7\u306e\u518d\u8d77\u52d5\u6642\u306e\u4f8b\u5916:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (\u8b66\u544a) \u30b5\u30fc\u30d3\u30b9\u306e\u518d\u8d77\u52d5\u6642\u306e\u4f8b\u5916:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (\u8b66\u544a) \u30ed\u30b0\u66f4\u65b0\u6642\u306e\u4f8b\u5916:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (\u91cd\u5927) \u30ed\u30b0\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8\u6642\u306e\u4f8b\u5916:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (\u8b66\u544a) {0}: \u56de\u5fa9\u4e2d\u306e\u30ed\u30b0\u30ec\u30b3\u30fc\u30c9\u306e\u30b9\u30ad\u30c3\u30d7:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_ko.properties b/src/share/classes/sun/rmi/server/resources/rmid_ko.properties
new file mode 100644
index 000000000..73e02f75f
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_ko.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid\uac00 \uc798\ubabb\ub41c \uc0c1\ud0dc\ub97c \uac00\uc9c4 inetd\uc5d0\uc11c \uc2dc\uc791\ub418\uc5c8\uc2b5\ub2c8\ub2e4. (wait\uc774\uc5b4\uc57c \ud568)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ rmid\uac00 inetd\uc5d0\uc11c \uc2dc\uc791\ub41c \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc9c0\uc815\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ \ud3ec\ud2b8\ub294 \uc22b\uc790\uac00 \uc544\ub2d9\ub2c8\ub2e4.
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ -port \uc635\uc158\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \uc778\uc790\uac00 \ud544\uc694\ud569\ub2c8\ub2e4.
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ -log \uc635\uc158\uc744 \uc0ac\uc6a9\ud558\ub824\uba74 \uc778\uc790\uac00 \ud544\uc694\ud569\ub2c8\ub2e4.
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ -log \uc635\uc158\uc774 \ud544\uc694\ud569\ub2c8\ub2e4.
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ \uc798\ubabb\ub41c \uc635\uc158: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: \uc608\uc678 \ubc1c\uc0dd: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: java.home\uc744 \ucc3e\uc744 \uc218 \uc5c6\uc74c
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ \uc0c1\uc18d \ucc44\ub110\uc744 \uc0ac\uc6a9\ud558\uc5ec rmid \uc2dc\uc791
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: \uc798\ubabb\ub41c \uc2e4\ud589 \uc815\ucc45 \ud074\ub798\uc2a4
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: \uc2e4\ud589 \uc815\ucc45 \uac00\uc838\uc624\uae30 \uc2dc\ub3c4\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: "{0}" \uc2e4\ud589 \uc911
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: \ud65c\uc131\ud654 \uadf8\ub8f9 \ube44\ud65c\uc131: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: \uacbd\uace0: sun.rmi.activation.execPolicy \uc2dc\uc2a4\ud15c \ub4f1\ub85d \uc815\ubcf4\uac00\n\
+ \uc9c0\uc815\ub418\uc9c0 \uc54a\uc558\uace0 ExecPermissions/ExecOptionPermissions\uc774 \ubd80\uc5ec\ub418\uc9c0\n\
+ \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uacc4\uc18d\ub418\ub294 \ud65c\uc131\ud654 \uc2dc\ub3c4\ub294 ExecPermission/ExecOptionPermission\uc758\n\
+ \ud5c8\uac00 \ud655\uc778 \uc2e4\ud328\ub85c \uc778\ud574 \uc2e4\ud328\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.\n\
+ rmid \ubcf4\uc548\uc744 \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub294 \ub2e4\uc74c \uc6f9 \uc0ac\uc774\ud2b8\ub97c \ucc38\uc870\ud558\uc2ed\uc2dc\uc624.\n\
+\n\
+http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=\uc0ac\uc6a9\ubc95: {0} <\uc635\uc158> \
+\n\
+\n<\uc635\uc158>\uc740 \ub2e4\uc74c\uc744 \ud3ec\ud568\ud569\ub2c8\ub2e4:\
+\n -port <port> rmid\uac00 \uc0ac\uc6a9\ud560 \ud3ec\ud2b8 \uc9c0\uc815\
+\n -log <directory> rmid\uac00 \ub85c\uadf8\ub97c \uae30\ub85d\ud560 \ub514\ub809\ud1a0\ub9ac \uc9c0\uc815\
+\n -stop rmid\uc758 \ud604\uc7ac \ud638\ucd9c \uc911\ub2e8 (\uc9c0\uc815\ub41c \ud3ec\ud2b8\uc5d0 \ub300\ud574)\
+\n -C<runtime flag> \uac01\uac01\uc758 \uc790\uc2dd \ud504\ub85c\uc138\uc2a4\uc5d0 \uc778\uc790 \uc804\ub2ec (\ud65c\uc131\ud654 \uadf8\ub8f9)\
+\n -J<runtime flag> Java \uc778\ud130\ud504\ub9ac\ud130\uc5d0 \uc778\uc790\ub97c \uc804\ub2ec\ud569\ub2c8\ub2e4.\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ \ud65c\uc131 \ub370\ubaac \uc167\ub2e4\uc6b4
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (\uacbd\uace0) \uadf8\ub8f9 \ub2e4\uc2dc \uc2dc\uc791\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (\uacbd\uace0) \uc11c\ube44\uc2a4 \ub2e4\uc2dc \uc2dc\uc791\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (\uacbd\uace0) \ub85c\uadf8 \uc5c5\ub370\uc774\ud2b8\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (\uc2ec\uac01) \ub85c\uadf8 \uc2a4\ub0c5\uc0f7\uc73c\ub85c \uc778\ud574 \ub2e4\uc74c \uc624\ub958\uac00 \ubc1c\uc0dd\ud568:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (\uacbd\uace0) {0}: \ubcf5\uad6c\ud558\ub294 \ub3d9\uc548 \ub808\ucf54\ub4dc \uae30\ub85d \uac74\ub108\ub6f0\uae30:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_sv.properties b/src/share/classes/sun/rmi/server/resources/rmid_sv.properties
new file mode 100644
index 000000000..2eded0915
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_sv.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid startades fr\u00e5n inetd med ogiltig status (m\u00e5ste vara wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ port kan inte anges om rmid startas fr\u00e5n inetd
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ port \u00e4r inte ett nummer
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ alternativet -port kr\u00e4ver argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ alternativet -log kr\u00e4ver argument
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ alternativet -log kr\u00e4vs
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ogiltigt alternativ: {0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: ett undantagsavbrott har intr\u00e4ffat: {0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: det g\u00e5r inte att hitta java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ uppstart av rmid med \u00e4rvd kanal
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: ogiltig exec policy-klass
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: f\u00f6rs\u00f6k att h\u00e4mta throws f\u00f6r exec-policy:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: k\u00f6r "{0}"
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: aktiveringsgruppen \u00e4r inaktiv: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: varning: systemegenskapen sun.rmi.activation.execPolicy\n\
+ \u00e4r inte specificerad och inga ExecPermissions/ExecOptionPermissions\n\
+ har utdelats. Efterf\u00f6ljande aktiveringsf\u00f6rs\u00f6k kan misslyckas p\u00e5 grund av misslyckade\n\
+ kontroller av ExecPermission/ExecOptionPermission-beh\u00f6righet. Mer\n\
+ information om hur du konfigurerar rmid-s\u00e4kerhet finns i:\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=G\u00f6r s\u00e5 h\u00e4r: {0} <alternativ>\
+\n\
+\nd\u00e4r <alternativ> omfattar:\
+\n -port <port> Ange porten f\u00f6r rmid\
+\n -log <katalog> Ange katalogen d\u00e4r rmid ska spara loggen\
+\n -stop Stoppa p\u00e5g\u00e5ende rmid-anrop (f\u00f6r angiven port)\
+\n -C<k\u00f6rtidsflagga> Skicka argumentet till varje underordnad process (aktiveringsgrupp)\
+\n -J<k\u00f6rtidsflagga> Skicka argumentet till java-tolken\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ aktiveringsdemonen avslutas
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (VARNING) starta om grupp-throws:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (VARNING) starta om service-throws:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (VARNING) logga uppdaterings-throws:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (ALLVARLIGT) logga \u00f6gonblicks-throws:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (VARNING) {0}: hoppa \u00f6ver loggpost under \u00e5terst\u00e4llning:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties b/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties
new file mode 100644
index 000000000..80476ef2b
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_zh_CN.properties
@@ -0,0 +1,133 @@
+#
+#
+# Copyright 1999-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid \u5df2\u4ece inetd \u542f\u52a8\uff0c\u4e14\u72b6\u6001\u65e0\u6548 (\u5fc5\u987b\u7b49\u5f85)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ \u5982\u679c rmid \u4ece inetd \u542f\u52a8\uff0c\u5219\u65e0\u6cd5\u6307\u5b9a\u7aef\u53e3
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ \u7aef\u53e3\u4e0d\u662f\u4e00\u4e2a\u6570\u5b57
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ -port \u9009\u9879\u9700\u8981\u53c2\u6570
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ -log \u9009\u9879\u9700\u8981\u53c2\u6570
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ \u9700\u8981 -log \u9009\u9879
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ \u975e\u6cd5\u9009\u9879\uff1a{0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: \u51fa\u73b0\u5f02\u5e38\uff1a{0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: \u65e0\u6cd5\u5b9a\u4f4d java.home
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ rmid \u901a\u8fc7\u7ee7\u627f\u7684\u4fe1\u9053\u542f\u52a8
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\
+ Activation.main: \u65e0\u6548\u7684\u53ef\u6267\u884c policy \u7c7b
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: \u8bd5\u56fe\u83b7\u53d6\u53ef\u6267\u884c\u7b56\u7565\u629b\u51fa:
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec: \u6b63\u5728\u8fd0\u884c\u201c{0}\u201d
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid: \u6fc0\u6d3b\u7ec4\u65e0\u6548: {0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: \u8b66\u544a\uff1a sun.rmi.activation.execPolicy \u7cfb\u7edf\n\
+ \u5c5e\u6027\u672a\u88ab\u6307\u5b9a\u4e14 ExecPermissions/ExecOptionPermissions\n\
+ \u5747\u672a\u88ab\u6388\u6743\u3002\u968f\u540e\u7684\u6fc0\u6d3b\u5c1d\u8bd5\u5931\u8d25\uff0c\u539f\u56e0\u662f\u5bf9\n\
+ ExecPermission/ExecOptionPermission \u7684\u6743\u9650\u68c0\u67e5\u5931\u8d25\u3002 \u6709\u5173\u5982\u4f55\n\
+ \u914d\u7f6e rmid \u5b89\u5168\u6027\u7684\u6587\u6863\u8bf4\u660e\uff0c\u8bf7\u53c2\u9605\uff1a \n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=\u7528\u6cd5\uff1a{0} <option>\
+\n\
+\n\u5176\u4e2d\uff0c<option> \u5305\u62ec:\
+\n -port <option> \u6307\u5b9a\u4f9b rmid \u4f7f\u7528\u7684\u7aef\u53e3\
+\n -log <directory> \u6307\u5b9a rmid \u5c06\u65e5\u5fd7\u5199\u5165\u7684\u76ee\u5f55\
+\n -stop \u505c\u6b62\u5f53\u524d\u7684 rmid \u8c03\u7528\uff08\u5bf9\u6307\u5b9a\u7aef\u53e3\uff09\
+\n -C<runtime \u6807\u8bb0> \u5411\u6bcf\u4e2a\u5b50\u8fdb\u7a0b\u4f20\u9012\u53c2\u6570\uff08\u6fc0\u6d3b\u7ec4\uff09\
+\n -J<runtime \u6807\u8bb0> \u5411 java \u89e3\u91ca\u7a0b\u5e8f\u4f20\u9012\u53c2\u6570\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ \u6fc0\u6d3b\u7aef\u53e3\u76d1\u63a7\u7a0b\u5e8f\u5173\u95ed
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (\u8b66\u544a) \u91cd\u65b0\u542f\u52a8\u7ec4\u629b\u51fa:
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (\u8b66\u544a) \u91cd\u65b0\u542f\u52a8\u670d\u52a1\u629b\u51fa:
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (\u8b66\u544a) \u65e5\u5fd7\u66f4\u65b0\u629b\u51fa:
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (\u4e25\u91cd\u8b66\u544a) \u65e5\u5fd7\u5feb\u7167\u629b\u51fa:
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (\u8b66\u544a) {0}: \u6062\u590d\u671f\u95f4\u8df3\u8fc7\u65e5\u5fd7\u8bb0\u5f55:
diff --git a/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties b/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties
new file mode 100644
index 000000000..f47e79f79
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/resources/rmid_zh_TW.properties
@@ -0,0 +1,132 @@
+#
+#
+# Copyright 2000-2005 Sun Microsystems, Inc. All Rights Reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Sun designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Sun in the LICENSE file that accompanied this code.
+#
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+# CA 95054 USA or visit www.sun.com if you need additional information or
+# have any questions.
+#
+
+# "rmid", inetd", and "wait" should not be translated.
+rmid.syntax.exec.invalid=\
+ rmid \u5df2\u5f9e inetd \u555f\u52d5\uff0c\u4e14\u72c0\u614b\u7121\u6548 (\u5fc5\u9808\u662f wait)
+
+# "rmid" and "inetd" should not be translated.
+rmid.syntax.port.badarg=\
+ \u5982\u679c rmid \u5f9e inetd \u555f\u52d5\uff0c\u5247\u7121\u6cd5\u6307\u5b9a\u9023\u63a5\u57e0
+
+# "port" here refers to a TCP port for the server to listen on.
+rmid.syntax.port.badnumber=\
+ port \u4e0d\u662f\u4e00\u500b\u6578\u5b57
+
+# "-port" should not be translated, because it's part of command syntax.
+rmid.syntax.port.missing=\
+ -port \u9078\u9805\u9700\u8981\u5f15\u6578
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.missing=\
+ -log \u9078\u9805\u9700\u8981\u5f15\u6578
+
+# "-log" should not be translated, because it's part of command syntax.
+rmid.syntax.log.required=\
+ -log \u9078\u9805\u662f\u5fc5\u9700\u7684
+
+# {0} = the (string) illegal argument in question
+rmid.syntax.illegal.option=\
+ \u975e\u6cd5\u9078\u9805\uff1a{0}
+
+# {0} = the (string) reason text that came with a thrown exception
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.unexpected.exception=\
+ Activation.main: \u767c\u751f\u7570\u5e38\u72c0\u6cc1\ufe55{0}
+
+# "java.home" should not be translated, because it's a property name
+# "ActivatorImpl" should not be translated, because it's a codepoint
+rmid.unfound.java.home.property=\
+ ActivatorImpl: \u627e\u4e0d\u5230 java.home \u7684\u6240\u5728
+
+# "rmid" should not be translated
+rmid.inherited.channel.info=\
+ rmid \u4f7f\u7528\u7e7c\u627f\u7684\u901a\u9053\u555f\u52d5
+
+# "Activation.main" should not be translated, because it's a codepoint
+rmid.exec.policy.invalid=\Activation.main: \u7121\u6548\u7684\u57f7\u884c\u7b56\u7565\u985e\u5225
+
+# "rmid" should not be translated
+rmid.exec.policy.exception=\
+ rmid: \u5617\u8a66\u53d6\u5f97\u57f7\u884c\u7b56\u7565\u62cb\u68c4\uff1a
+
+# "rmid" should not be translated
+rmid.exec.command=\
+ rmid: debugExec\uff1a\u57f7\u884c "{0}"
+
+# "rmid" should not be translated
+rmid.group.inactive=\
+ rmid:\u555f\u52d5\u7fa4\u7d44\u672a\u5728\u4f7f\u7528\u4e2d\ufe30{0}
+
+# "Activation.main", "sun.rmi.activation.execPolicy", "ExecPermission" and
+# "ExecOptionPermission" should not be translated, since they refer to
+# class/permission names.
+rmid.exec.perms.inadequate=\
+ Activation.main: \u8b66\u544a: sun.rmi.activation.execPolicy \u7cfb\u7d71\n\
+ \u5c6c\u6027\u672a\u6307\u5b9a\uff0c\u4e26\u4e14\u672a\u6388\u4e88 ExecPermissions/ExecOptionPermissions\uff1b\n\
+ \u5f8c\u7e8c\u7684\u555f\u52d5\u5617\u8a66\u53ef\u80fd\u6703\u56e0\u70ba\u672a\u6210\u529f\u7684\n\
+ ExecPermission/ExecOptionPermission \u8a31\u53ef\u6b0a\u6aa2\u67e5\u800c\u5931\u6557\u3002\u5982\u9700\n\
+ \u95dc\u65bc\u5982\u4f55\u914d\u7f6e rmid \u5b89\u5168\u7684\u8aaa\u660e\u6587\u4ef6\uff0c\u8acb\u53c3\u8003\uff1a\n\
+\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/solaris/rmid.html\n\
+ http://java.sun.com/j2se/1.4/docs/tooldocs/win32/rmid.html\n
+
+# "rmid", "-port", "-log", "-stop", "-C" and "-J" should not be translated,
+# because they are syntax
+rmid.usage=\u7528\u6cd5: {0} <options>\
+\n\
+\n\u5176\u4e2d <options> \u5305\u62ec\uff1a\
+\n -port <port> \u6307\u5b9a\u4f9b rmid \u4f7f\u7528\u7684\u9023\u63a5\u57e0\
+\n -log <directory> \u6307\u5b9a\u4f9b rmid \u5beb\u5165\u65e5\u8a8c\u7684\u76ee\u9304\
+\n -stop \u505c\u6b62\u76ee\u524d rmid \u7684\u547c\u53eb (\u91dd\u5c0d\u6307\u5b9a\u7684\u9023\u63a5\u57e0)\
+\n -C<runtime flag> \u50b3\u905e\u5f15\u6578\u81f3\u6bcf\u4e00\u5b50\u904e\u7a0b (\u4f5c\u7528\u7fa4\u7d44)\
+\n -J<runtime flag> \u50b3\u905e\u5f15\u6578\u81f3 java \u89e3\u8b6f\u7a0b\u5f0f\
+\n\
+
+# This means "The currently running activation daemon has been shut down,
+# and is about to exit".
+rmid.daemon.shutdown=\
+ \u505c\u6b62 activation \u5e38\u99d0\u7a0b\u5f0f
+
+# "rmid" should not be translated
+rmid.restart.group.warning=\
+\nrmid: (\u8b66\u544a) \u91cd\u65b0\u555f\u52d5\u7fa4\u7d44\u62cb\u68c4\uff1a
+
+# "rmid" should not be translated
+rmid.restart.service.warning=\
+\nrmid: (\u8b66\u544a) \u91cd\u65b0\u555f\u52d5\u670d\u52d9\u62cb\u68c4\uff1a
+
+# "rmid" should not be translated
+rmid.log.update.warning=\
+\nrmid: (\u8b66\u544a) \u8a18\u9304\u66f4\u65b0\u62cb\u68c4\uff1a
+
+# "rmid" should not be translated
+rmid.log.snapshot.warning=\
+\nrmid: (\u56b4\u91cd) \u8a18\u9304\u5feb\u7167\u62cb\u68c4\uff1a
+
+# "rmid" should not be translated
+rmid.log.recover.warning=\
+\nrmid: (\u8b66\u544a) {0}\uff1a\u5728\u56de\u5fa9\u904e\u7a0b\u4e2d\u7565\u904e\u65e5\u8a8c\u8a18\u9304\uff1a
diff --git a/src/share/classes/sun/rmi/transport/Channel.java b/src/share/classes/sun/rmi/transport/Channel.java
new file mode 100644
index 000000000..def4f7951
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/Channel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.rmi.RemoteException;
+
+public interface Channel {
+
+ /**
+ * Generates a new connection to the endpoint of the address space
+ * for which this is a channel.
+ */
+ public Connection newConnection() throws RemoteException;
+
+ /**
+ * Returns the endpoint of the address space for which this is a
+ * channel.
+ */
+ public Endpoint getEndpoint();
+
+ /**
+ * Free the connection generated by this channel.
+ * @param c The connection
+ * @param reuse If true, the connection is in a state in which it
+ * can be reused for another method call.
+ */
+ public void free(Connection conn, boolean reuse) throws RemoteException;
+}
diff --git a/src/share/classes/sun/rmi/transport/Connection.java b/src/share/classes/sun/rmi/transport/Connection.java
new file mode 100644
index 000000000..49f9f651a
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/Connection.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.io.*;
+
+public interface Connection {
+ /**
+ * Gets the input stream for this connection.
+ */
+ public InputStream getInputStream() throws IOException;
+
+ /*
+ * Release the input stream for this connection.
+ */
+ public void releaseInputStream() throws IOException;
+
+ /**
+ * Gets the output stream for this connection
+ */
+ public OutputStream getOutputStream() throws IOException;
+
+ /*
+ * Release the output stream for this connection.
+ */
+ public void releaseOutputStream() throws IOException;
+
+ /**
+ * Return true if channel can be used for multiple operations.
+ */
+ public boolean isReusable();
+
+ /**
+ * Close connection.
+ */
+ public void close() throws IOException;
+
+ /**
+ * Returns the channel for this connection.
+ */
+ public Channel getChannel();
+}
diff --git a/src/share/classes/sun/rmi/transport/ConnectionInputStream.java b/src/share/classes/sun/rmi/transport/ConnectionInputStream.java
new file mode 100644
index 000000000..12ee3c55c
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/ConnectionInputStream.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.io.*;
+import java.util.*;
+import java.rmi.RemoteException;
+import java.rmi.server.UID;
+import sun.rmi.server.MarshalInputStream;
+import sun.rmi.runtime.Log;
+
+/**
+ * Special stream to keep track of refs being unmarshaled so that
+ * refs can be ref-counted locally.
+ *
+ * @author Ann Wollrath
+ */
+class ConnectionInputStream extends MarshalInputStream {
+
+ /** indicates whether ack is required for DGC */
+ private boolean dgcAckNeeded = false;
+
+ /** Hashtable mapping Endpoints to lists of LiveRefs to register */
+ private Map incomingRefTable = new HashMap(5);
+
+ /** identifier for gc ack*/
+ private UID ackID;
+
+ /**
+ * Constructs a marshal input stream using the underlying
+ * stream "in".
+ */
+ ConnectionInputStream(InputStream in) throws IOException {
+ super(in);
+ }
+
+ void readID() throws IOException {
+ ackID = UID.read((DataInput) this);
+ }
+
+ /**
+ * Save reference in order to send "dirty" call after all args/returns
+ * have been unmarshaled. Save in hashtable incomingRefTable. This
+ * table is keyed on endpoints, and holds objects of type
+ * IncomingRefTableEntry.
+ */
+ void saveRef(LiveRef ref) {
+ Endpoint ep = ref.getEndpoint();
+
+ // check whether endpoint is already in the hashtable
+ List refList = (List) incomingRefTable.get(ep);
+
+ if (refList == null) {
+ refList = new ArrayList();
+ incomingRefTable.put(ep, refList);
+ }
+
+ // add ref to list of refs for endpoint ep
+ refList.add(ref);
+ }
+
+ /**
+ * Add references to DGC table (and possibly send dirty call).
+ * RegisterRefs now calls DGCClient.referenced on all
+ * refs with the same endpoint at once to achieve batching of
+ * calls to the DGC
+ */
+ void registerRefs() throws IOException {
+ if (!incomingRefTable.isEmpty()) {
+ Set entrySet = incomingRefTable.entrySet();
+ Iterator iter = entrySet.iterator();
+ while (iter.hasNext()) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ Endpoint ep = (Endpoint) entry.getKey();
+ List refList = (List) entry.getValue();
+ DGCClient.registerRefs(ep, refList);
+ }
+ }
+ }
+
+ /**
+ * Indicate that an ack is required to the distributed
+ * collector.
+ */
+ void setAckNeeded() {
+ dgcAckNeeded = true;
+ }
+
+ /**
+ * Done with input stream for remote call. Send DGC ack if necessary.
+ * Allow sending of ack to fail without flagging an error.
+ */
+ void done(Connection c) {
+ /*
+ * WARNING: The connection c may have already been freed. It
+ * is only be safe to use c to obtain c's channel.
+ */
+
+ if (dgcAckNeeded) {
+ Connection conn = null;
+ Channel ch = null;
+ boolean reuse = true;
+
+ DGCImpl.dgcLog.log(Log.VERBOSE, "send ack");
+
+ try {
+ ch = c.getChannel();
+ conn = ch.newConnection();
+ DataOutputStream out =
+ new DataOutputStream(conn.getOutputStream());
+ out.writeByte(TransportConstants.DGCAck);
+ if (ackID == null) {
+ ackID = new UID();
+ }
+ ackID.write((DataOutput) out);
+ conn.releaseOutputStream();
+
+ /*
+ * Fix for 4221173: if this connection is on top of an
+ * HttpSendSocket, the DGCAck won't actually get sent until a
+ * read operation is attempted on the socket. Calling
+ * available() is the most innocuous way of triggering the
+ * write.
+ */
+ conn.getInputStream().available();
+ conn.releaseInputStream();
+ } catch (RemoteException e) {
+ reuse = false;
+ } catch (IOException e) {
+ reuse = false;
+ }
+ try {
+ if (conn != null)
+ ch.free(conn, reuse);
+ } catch (RemoteException e){
+ // eat exception
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java b/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java
new file mode 100644
index 000000000..22a8e3aeb
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/ConnectionOutputStream.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+import java.io.IOException;
+import java.rmi.server.UID;
+import sun.rmi.server.MarshalOutputStream;
+
+/**
+ * Special stream to keep track of refs being marshaled as return
+ * results to determine whether a special ack needs to be sent
+ * to the distributed collector.
+ *
+ * @author Ann Wollrath
+ */
+class ConnectionOutputStream extends MarshalOutputStream {
+
+ /** connection associated with ConnectionOutputStream */
+ private final Connection conn;
+ /** indicates whether output stream is used to marshal results */
+ private final boolean resultStream;
+ /** identifier for gc ack*/
+ private final UID ackID;
+
+ /** to store refs to returned remote object until DGC ack is received */
+ private DGCAckHandler dgcAckHandler = null;
+
+ /**
+ * Constructs an marshal output stream using the underlying
+ * stream associated with the connection, the parameter c.
+ * @param c is the Connection object associated with the
+ * ConnectionOutputStream object being constructed
+ * @param resultStream indicates whether this stream is used
+ * to marshal return results
+ */
+ ConnectionOutputStream(Connection conn, boolean resultStream)
+ throws IOException
+ {
+ super(conn.getOutputStream());
+ this.conn = conn;
+ this.resultStream = resultStream;
+ ackID = resultStream ? new UID() : null;
+ }
+
+ void writeID() throws IOException {
+ assert resultStream;
+ ackID.write(this);
+ }
+
+ /**
+ * Returns true if this output stream is used to marshal return
+ * results; otherwise returns false.
+ */
+ boolean isResultStream() {
+ return resultStream;
+ }
+
+ /**
+ * Saves a reference to the specified object in this stream's
+ * DGCAckHandler.
+ **/
+ void saveObject(Object obj) {
+ // should always be accessed from same thread
+ if (dgcAckHandler == null) {
+ dgcAckHandler = new DGCAckHandler(ackID);
+ }
+ dgcAckHandler.add(obj);
+ }
+
+ /**
+ * Returns this stream's DGCAckHandler, or null if it doesn't have
+ * one (saveObject was not invoked). This method should only be
+ * invoked after all objects have been written to the stream,
+ * because future objects written may yet cause a DGCAckHandler to
+ * be created (by invoking saveObject).
+ **/
+ DGCAckHandler getDGCAckHandler() {
+ return dgcAckHandler;
+ }
+
+ void done() {
+ if (dgcAckHandler != null) {
+ dgcAckHandler.startTimer();
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/DGCAckHandler.java b/src/share/classes/sun/rmi/transport/DGCAckHandler.java
new file mode 100644
index 000000000..37769fc5e
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/DGCAckHandler.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+import java.rmi.server.UID;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import sun.rmi.runtime.RuntimeUtil;
+import sun.security.action.GetLongAction;
+
+/**
+ * Holds strong references to a set of remote objects, or live remote
+ * references to remote objects, after they have been marshalled (as
+ * remote references) as parts of the arguments or the result of a
+ * remote invocation. The purpose is to prevent remote objects or
+ * live remote references that might otherwise be determined to be
+ * unreachable in this VM from being locally garbage collected before
+ * the receiver has had an opportunity to register the unmarshalled
+ * remote references for DGC.
+ *
+ * The references are held strongly until an acknowledgment has been
+ * received that the receiver has had an opportunity to process the
+ * remote references or until a timeout has expired. For remote
+ * references sent as parts of the arguments of a remote invocation,
+ * the acknowledgment is the beginning of the response indicating
+ * completion of the remote invocation. For remote references sent as
+ * parts of the result of a remote invocation, a UID is included as
+ * part of the result, and the acknowledgment is a transport-level
+ * "DGCAck" message containing that UID.
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ **/
+public class DGCAckHandler {
+
+ /** timeout for holding references without receiving an acknowledgment */
+ private static final long dgcAckTimeout = // default 5 minutes
+ AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.dgc.ackTimeout", 300000));
+
+ /** thread pool for scheduling delayed tasks */
+ private static final ScheduledExecutorService scheduler =
+ AccessController.doPrivileged(
+ new RuntimeUtil.GetInstanceAction()).getScheduler();
+
+ /** table mapping ack ID to handler */
+ private static final Map<UID,DGCAckHandler> idTable =
+ Collections.synchronizedMap(new HashMap<UID,DGCAckHandler>());
+
+ private final UID id;
+ private List<Object> objList = new ArrayList<Object>(); // null if released
+ private Future<?> task = null;
+
+ /**
+ * Creates a new DGCAckHandler, associated with the specified UID
+ * if the argument is not null.
+ *
+ * References added to this DGCAckHandler will be held strongly
+ * until its "release" method is invoked or (after the
+ * "startTimer" method has been invoked) the timeout has expired.
+ * If the argument is not null, then invoking the static
+ * "received" method with the specified UID is equivalent to
+ * invoking this instance's "release" method.
+ **/
+ DGCAckHandler(UID id) {
+ this.id = id;
+ if (id != null) {
+ assert !idTable.containsKey(id);
+ idTable.put(id, this);
+ }
+ }
+
+ /**
+ * Adds the specified reference to this DGCAckHandler.
+ **/
+ synchronized void add(Object obj) {
+ if (objList != null) {
+ objList.add(obj);
+ }
+ }
+
+ /**
+ * Starts the timer for this DGCAckHandler. After the timeout has
+ * expired, the references are released even if the acknowledgment
+ * has not been received.
+ **/
+ synchronized void startTimer() {
+ if (objList != null && task == null) {
+ task = scheduler.schedule(new Runnable() {
+ public void run() {
+ release();
+ }
+ }, dgcAckTimeout, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Releases the references held by this DGCAckHandler.
+ **/
+ synchronized void release() {
+ if (task != null) {
+ task.cancel(false);
+ task = null;
+ }
+ objList = null;
+ }
+
+ /**
+ * Causes the DGCAckHandler associated with the specified UID to
+ * release its references.
+ **/
+ public static void received(UID id) {
+ DGCAckHandler h = idTable.remove(id);
+ if (h != null) {
+ h.release();
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/DGCClient.java b/src/share/classes/sun/rmi/transport/DGCClient.java
new file mode 100644
index 000000000..31784b357
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/DGCClient.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.rmi.ConnectException;
+import java.rmi.RemoteException;
+import java.rmi.dgc.DGC;
+import java.rmi.dgc.Lease;
+import java.rmi.dgc.VMID;
+import java.rmi.server.ObjID;
+import sun.misc.GC;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.server.UnicastRef;
+import sun.rmi.server.Util;
+import sun.security.action.GetLongAction;
+
+/**
+ * DGCClient implements the client-side of the RMI distributed garbage
+ * collection system.
+ *
+ * The external interface to DGCClient is the "registerRefs" method.
+ * When a LiveRef to a remote object enters the VM, it needs to be
+ * registered with the DGCClient to participate in distributed garbage
+ * collection.
+ *
+ * When the first LiveRef to a particular remote object is registered,
+ * a "dirty" call is made to the server-side distributed garbage
+ * collector for the remote object, which returns a lease guaranteeing
+ * that the server-side DGC will not collect the remote object for a
+ * certain period of time. While LiveRef instances to remote objects
+ * on a particular server exist, the DGCClient periodically sends more
+ * "dirty" calls to renew its lease.
+ *
+ * The DGCClient tracks the local reachability of registered LiveRef
+ * instances (using phantom references). When the LiveRef instance
+ * for a particular remote object becomes garbage collected locally,
+ * a "clean" call is made to the server-side distributed garbage
+ * collector, indicating that the server no longer needs to keep the
+ * remote object alive for this client.
+ *
+ * @see java.rmi.dgc.DGC, sun.rmi.transport.DGCImpl
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ */
+final class DGCClient {
+
+ /** next sequence number for DGC calls (access synchronized on class) */
+ private static long nextSequenceNum = Long.MIN_VALUE;
+
+ /** unique identifier for this VM as a client of DGC */
+ private static VMID vmid = new VMID();
+
+ /** lease duration to request (usually ignored by server) */
+ private static final long leaseValue = // default 10 minutes
+ ((Long) AccessController.doPrivileged(
+ new GetLongAction("java.rmi.dgc.leaseValue",
+ 600000))).longValue();
+
+ /** maximum interval between retries of failed clean calls */
+ private static final long cleanInterval = // default 3 minutes
+ ((Long) AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.dgc.cleanInterval",
+ 180000))).longValue();
+
+ /** maximum interval between complete garbage collections of local heap */
+ private static final long gcInterval = // default 1 hour
+ ((Long) AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.dgc.client.gcInterval",
+ 3600000))).longValue();
+
+ /** minimum retry count for dirty calls that fail */
+ private static final int dirtyFailureRetries = 5;
+
+ /** retry count for clean calls that fail with ConnectException */
+ private static final int cleanFailureRetries = 5;
+
+ /** constant empty ObjID array for lease renewal optimization */
+ private static final ObjID[] emptyObjIDArray = new ObjID[0];
+
+ /** ObjID for server-side DGC object */
+ private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);
+
+ /*
+ * Disallow anyone from creating one of these.
+ */
+ private DGCClient() {}
+
+ /**
+ * Register the LiveRef instances in the supplied list to participate
+ * in distributed garbage collection.
+ *
+ * All of the LiveRefs in the list must be for remote objects at the
+ * given endpoint.
+ */
+ static void registerRefs(Endpoint ep, List refs) {
+ /*
+ * Look up the given endpoint and register the refs with it.
+ * The retrieved entry may get removed from the global endpoint
+ * table before EndpointEntry.registerRefs() is able to acquire
+ * its lock; in this event, it returns false, and we loop and
+ * try again.
+ */
+ EndpointEntry epEntry;
+ do {
+ epEntry = EndpointEntry.lookup(ep);
+ } while (!epEntry.registerRefs(refs));
+ }
+
+ /**
+ * Get the next sequence number to be used for a dirty or clean
+ * operation from this VM. This method should only be called while
+ * synchronized on the EndpointEntry whose data structures the
+ * operation affects.
+ */
+ private static synchronized long getNextSequenceNum() {
+ return nextSequenceNum++;
+ }
+
+ /**
+ * Given the length of a lease and the time that it was granted,
+ * compute the absolute time at which it should be renewed, giving
+ * room for reasonable computational and communication delays.
+ */
+ private static long computeRenewTime(long grantTime, long duration) {
+ /*
+ * REMIND: This algorithm should be more sophisticated, waiting
+ * a longer fraction of the lease duration for longer leases.
+ */
+ return grantTime + (duration / 2);
+ }
+
+ /**
+ * EndpointEntry encapsulates the client-side DGC information specific
+ * to a particular Endpoint. Of most significance is the table that
+ * maps LiveRef value to RefEntry objects and the renew/clean thread
+ * that handles asynchronous client-side DGC operations.
+ */
+ private static class EndpointEntry {
+
+ /** the endpoint that this entry is for */
+ private Endpoint endpoint;
+ /** synthesized reference to the remote server-side DGC */
+ private DGC dgc;
+
+ /** table of refs held for endpoint: maps LiveRef to RefEntry */
+ private Map refTable = new HashMap(5);
+ /** set of RefEntry instances from last (failed) dirty call */
+ private Set invalidRefs = new HashSet(5);
+
+ /** true if this entry has been removed from the global table */
+ private boolean removed = false;
+
+ /** absolute time to renew current lease to this endpoint */
+ private long renewTime = Long.MAX_VALUE;
+ /** absolute time current lease to this endpoint will expire */
+ private long expirationTime = Long.MIN_VALUE;
+ /** count of recent dirty calls that have failed */
+ private int dirtyFailures = 0;
+ /** absolute time of first recent failed dirty call */
+ private long dirtyFailureStartTime;
+ /** (average) elapsed time for recent failed dirty calls */
+ private long dirtyFailureDuration;
+
+ /** renew/clean thread for handling lease renewals and clean calls */
+ private Thread renewCleanThread;
+ /** true if renew/clean thread may be interrupted */
+ private boolean interruptible = false;
+
+ /** reference queue for phantom references */
+ private ReferenceQueue refQueue = new ReferenceQueue();
+ /** set of clean calls that need to be made */
+ private Set pendingCleans = new HashSet(5);
+
+ /** global endpoint table: maps Endpoint to EndpointEntry */
+ private static Map endpointTable = new HashMap(5);
+ /** handle for GC latency request (for future cancellation) */
+ private static GC.LatencyRequest gcLatencyRequest = null;
+
+ /**
+ * Look up the EndpointEntry for the given Endpoint. An entry is
+ * created if one does not already exist.
+ */
+ public static EndpointEntry lookup(Endpoint ep) {
+ synchronized (endpointTable) {
+ EndpointEntry entry = (EndpointEntry) endpointTable.get(ep);
+ if (entry == null) {
+ entry = new EndpointEntry(ep);
+ endpointTable.put(ep, entry);
+ /*
+ * While we are tracking live remote references registered
+ * in this VM, request a maximum latency for inspecting the
+ * entire heap from the local garbage collector, to place
+ * an upper bound on the time to discover remote references
+ * that have become unreachable (see bugid 4171278).
+ */
+ if (gcLatencyRequest == null) {
+ gcLatencyRequest = GC.requestLatency(gcInterval);
+ }
+ }
+ return entry;
+ }
+ }
+
+ private EndpointEntry(final Endpoint endpoint) {
+ this.endpoint = endpoint;
+ try {
+ LiveRef dgcRef = new LiveRef(dgcID, endpoint, false);
+ dgc = (DGC) Util.createProxy(DGCImpl.class,
+ new UnicastRef(dgcRef), true);
+ } catch (RemoteException e) {
+ throw new Error("internal error creating DGC stub");
+ }
+ renewCleanThread = (Thread) AccessController.doPrivileged(
+ new NewThreadAction(new RenewCleanThread(),
+ "RenewClean-" + endpoint, true));
+ renewCleanThread.start();
+ }
+
+ /**
+ * Register the LiveRef instances in the supplied list to participate
+ * in distributed garbage collection.
+ *
+ * This method returns false if this entry was removed from the
+ * global endpoint table (because it was empty) before these refs
+ * could be registered. In that case, a new EndpointEntry needs
+ * to be looked up.
+ *
+ * This method must NOT be called while synchronized on this entry.
+ */
+ public boolean registerRefs(List refs) {
+ assert !Thread.holdsLock(this);
+
+ Set refsToDirty = null; // entries for refs needing dirty
+ long sequenceNum; // sequence number for dirty call
+
+ synchronized (this) {
+ if (removed) {
+ return false;
+ }
+
+ Iterator iter = refs.iterator();
+ while (iter.hasNext()) {
+ LiveRef ref = (LiveRef) iter.next();
+ assert ref.getEndpoint().equals(endpoint);
+
+ RefEntry refEntry = (RefEntry) refTable.get(ref);
+ if (refEntry == null) {
+ LiveRef refClone = (LiveRef) ref.clone();
+ refEntry = new RefEntry(refClone);
+ refTable.put(refClone, refEntry);
+ if (refsToDirty == null) {
+ refsToDirty = new HashSet(5);
+ }
+ refsToDirty.add(refEntry);
+ }
+
+ refEntry.addInstanceToRefSet(ref);
+ }
+
+ if (refsToDirty == null) {
+ return true;
+ }
+
+ refsToDirty.addAll(invalidRefs);
+ invalidRefs.clear();
+
+ sequenceNum = getNextSequenceNum();
+ }
+
+ makeDirtyCall(refsToDirty, sequenceNum);
+ return true;
+ }
+
+ /**
+ * Remove the given RefEntry from the ref table. If that makes
+ * the ref table empty, remove this entry from the global endpoint
+ * table.
+ *
+ * This method must ONLY be called while synchronized on this entry.
+ */
+ private void removeRefEntry(RefEntry refEntry) {
+ assert Thread.holdsLock(this);
+ assert !removed;
+ assert refTable.containsKey(refEntry.getRef());
+
+ refTable.remove(refEntry.getRef());
+ invalidRefs.remove(refEntry);
+ if (refTable.isEmpty()) {
+ synchronized (endpointTable) {
+ endpointTable.remove(endpoint);
+ Transport transport = endpoint.getOutboundTransport();
+ transport.free(endpoint);
+ /*
+ * If there are no longer any live remote references
+ * registered, we are no longer concerned with the
+ * latency of local garbage collection here.
+ */
+ if (endpointTable.isEmpty()) {
+ assert gcLatencyRequest != null;
+ gcLatencyRequest.cancel();
+ gcLatencyRequest = null;
+ }
+ removed = true;
+ }
+ }
+ }
+
+ /**
+ * Make a DGC dirty call to this entry's endpoint, for the ObjIDs
+ * corresponding to the given set of refs and with the given
+ * sequence number.
+ *
+ * This method must NOT be called while synchronized on this entry.
+ */
+ private void makeDirtyCall(Set refEntries, long sequenceNum) {
+ assert !Thread.holdsLock(this);
+
+ ObjID[] ids;
+ if (refEntries != null) {
+ ids = createObjIDArray(refEntries);
+ } else {
+ ids = emptyObjIDArray;
+ }
+
+ long startTime = System.currentTimeMillis();
+ try {
+ Lease lease =
+ dgc.dirty(ids, sequenceNum, new Lease(vmid, leaseValue));
+ long duration = lease.getValue();
+
+ long newRenewTime = computeRenewTime(startTime, duration);
+ long newExpirationTime = startTime + duration;
+
+ synchronized (this) {
+ dirtyFailures = 0;
+ setRenewTime(newRenewTime);
+ expirationTime = newExpirationTime;
+ }
+
+ } catch (Exception e) {
+ long endTime = System.currentTimeMillis();
+
+ synchronized (this) {
+ dirtyFailures++;
+
+ if (dirtyFailures == 1) {
+ /*
+ * If this was the first recent failed dirty call,
+ * reschedule another one immediately, in case there
+ * was just a transient network problem, and remember
+ * the start time and duration of this attempt for
+ * future calculations of the delays between retries.
+ */
+ dirtyFailureStartTime = startTime;
+ dirtyFailureDuration = endTime - startTime;
+ setRenewTime(endTime);
+ } else {
+ /*
+ * For each successive failed dirty call, wait for a
+ * (binary) exponentially increasing delay before
+ * retrying, to avoid network congestion.
+ */
+ int n = dirtyFailures - 2;
+ if (n == 0) {
+ /*
+ * Calculate the initial retry delay from the
+ * average time elapsed for each of the first
+ * two failed dirty calls. The result must be
+ * at least 1000ms, to prevent a tight loop.
+ */
+ dirtyFailureDuration =
+ Math.max((dirtyFailureDuration +
+ (endTime - startTime)) >> 1, 1000);
+ }
+ long newRenewTime =
+ endTime + (dirtyFailureDuration << n);
+
+ /*
+ * Continue if the last known held lease has not
+ * expired, or else at least a fixed number of times,
+ * or at least until we've tried for a fixed amount
+ * of time (the default lease value we request).
+ */
+ if (newRenewTime < expirationTime ||
+ dirtyFailures < dirtyFailureRetries ||
+ newRenewTime < dirtyFailureStartTime + leaseValue)
+ {
+ setRenewTime(newRenewTime);
+ } else {
+ /*
+ * Give up: postpone lease renewals until next
+ * ref is registered for this endpoint.
+ */
+ setRenewTime(Long.MAX_VALUE);
+ }
+ }
+
+ if (refEntries != null) {
+ /*
+ * Add all of these refs to the set of refs for this
+ * endpoint that may be invalid (this VM may not be in
+ * the server's referenced set), so that we will
+ * attempt to explicitly dirty them again in the
+ * future.
+ */
+ invalidRefs.addAll(refEntries);
+
+ /*
+ * Record that a dirty call has failed for all of these
+ * refs, so that clean calls for them in the future
+ * will be strong.
+ */
+ Iterator iter = refEntries.iterator();
+ while (iter.hasNext()) {
+ RefEntry refEntry = (RefEntry) iter.next();
+ refEntry.markDirtyFailed();
+ }
+ }
+
+ /*
+ * If the last known held lease will have expired before
+ * the next renewal, all refs might be invalid.
+ */
+ if (renewTime >= expirationTime) {
+ invalidRefs.addAll(refTable.values());
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the absolute time at which the lease for this entry should
+ * be renewed.
+ *
+ * This method must ONLY be called while synchronized on this entry.
+ */
+ private void setRenewTime(long newRenewTime) {
+ assert Thread.holdsLock(this);
+
+ if (newRenewTime < renewTime) {
+ renewTime = newRenewTime;
+ if (interruptible) {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ renewCleanThread.interrupt();
+ return null;
+ }
+ });
+ }
+ } else {
+ renewTime = newRenewTime;
+ }
+ }
+
+ /**
+ * RenewCleanThread handles the asynchronous client-side DGC activity
+ * for this entry: renewing the leases and making clean calls.
+ */
+ private class RenewCleanThread implements Runnable {
+
+ public void run() {
+ do {
+ long timeToWait;
+ RefEntry.PhantomLiveRef phantom = null;
+ boolean needRenewal = false;
+ Set refsToDirty = null;
+ long sequenceNum = Long.MIN_VALUE;
+
+ synchronized (EndpointEntry.this) {
+ /*
+ * Calculate time to block (waiting for phantom
+ * reference notifications). It is the time until the
+ * lease renewal should be done, bounded on the low
+ * end by 1 ms so that the reference queue will always
+ * get processed, and if there are pending clean
+ * requests (remaining because some clean calls
+ * failed), bounded on the high end by the maximum
+ * clean call retry interval.
+ */
+ long timeUntilRenew =
+ renewTime - System.currentTimeMillis();
+ timeToWait = Math.max(timeUntilRenew, 1);
+ if (!pendingCleans.isEmpty()) {
+ timeToWait = Math.min(timeToWait, cleanInterval);
+ }
+
+ /*
+ * Set flag indicating that it is OK to interrupt this
+ * thread now, such as if a earlier lease renewal time
+ * is set, because we are only going to be blocking
+ * and can deal with interrupts.
+ */
+ interruptible = true;
+ }
+
+ try {
+ /*
+ * Wait for the duration calculated above for any of
+ * our phantom references to be enqueued.
+ */
+ phantom = (RefEntry.PhantomLiveRef)
+ refQueue.remove(timeToWait);
+ } catch (InterruptedException e) {
+ }
+
+ synchronized (EndpointEntry.this) {
+ /*
+ * Set flag indicating that it is NOT OK to interrupt
+ * this thread now, because we may be undertaking I/O
+ * operations that should not be interrupted (and we
+ * will not be blocking arbitrarily).
+ */
+ interruptible = false;
+ Thread.interrupted(); // clear interrupted state
+
+ /*
+ * If there was a phantom reference enqueued, process
+ * it and all the rest on the queue, generating
+ * clean requests as necessary.
+ */
+ if (phantom != null) {
+ processPhantomRefs(phantom);
+ }
+
+ /*
+ * Check if it is time to renew this entry's lease.
+ */
+ long currentTime = System.currentTimeMillis();
+ if (currentTime > renewTime) {
+ needRenewal = true;
+ if (!invalidRefs.isEmpty()) {
+ refsToDirty = invalidRefs;
+ invalidRefs = new HashSet(5);
+ }
+ sequenceNum = getNextSequenceNum();
+ }
+ }
+
+ if (needRenewal) {
+ makeDirtyCall(refsToDirty, sequenceNum);
+ }
+
+ if (!pendingCleans.isEmpty()) {
+ makeCleanCalls();
+ }
+ } while (!removed || !pendingCleans.isEmpty());
+ }
+ }
+
+ /**
+ * Process the notification of the given phantom reference and any
+ * others that are on this entry's reference queue. Each phantom
+ * reference is removed from its RefEntry's ref set. All ref
+ * entries that have no more registered instances are collected
+ * into up to two batched clean call requests: one for refs
+ * requiring a "strong" clean call, and one for the rest.
+ *
+ * This method must ONLY be called while synchronized on this entry.
+ */
+ private void processPhantomRefs(RefEntry.PhantomLiveRef phantom) {
+ assert Thread.holdsLock(this);
+
+ Set strongCleans = null;
+ Set normalCleans = null;
+
+ do {
+ RefEntry refEntry = phantom.getRefEntry();
+ refEntry.removeInstanceFromRefSet(phantom);
+ if (refEntry.isRefSetEmpty()) {
+ if (refEntry.hasDirtyFailed()) {
+ if (strongCleans == null) {
+ strongCleans = new HashSet(5);
+ }
+ strongCleans.add(refEntry);
+ } else {
+ if (normalCleans == null) {
+ normalCleans = new HashSet(5);
+ }
+ normalCleans.add(refEntry);
+ }
+ removeRefEntry(refEntry);
+ }
+ } while ((phantom =
+ (RefEntry.PhantomLiveRef) refQueue.poll()) != null);
+
+ if (strongCleans != null) {
+ pendingCleans.add(
+ new CleanRequest(createObjIDArray(strongCleans),
+ getNextSequenceNum(), true));
+ }
+ if (normalCleans != null) {
+ pendingCleans.add(
+ new CleanRequest(createObjIDArray(normalCleans),
+ getNextSequenceNum(), false));
+ }
+ }
+
+ /**
+ * CleanRequest holds the data for the parameters of a clean call
+ * that needs to be made.
+ */
+ private static class CleanRequest {
+
+ final ObjID[] objIDs;
+ final long sequenceNum;
+ final boolean strong;
+
+ /** how many times this request has failed */
+ int failures = 0;
+
+ CleanRequest(ObjID[] objIDs, long sequenceNum, boolean strong) {
+ this.objIDs = objIDs;
+ this.sequenceNum = sequenceNum;
+ this.strong = strong;
+ }
+ }
+
+ /**
+ * Make all of the clean calls described by the clean requests in
+ * this entry's set of "pending cleans". Clean requests for clean
+ * calls that succeed are removed from the "pending cleans" set.
+ *
+ * This method must NOT be called while synchronized on this entry.
+ */
+ private void makeCleanCalls() {
+ assert !Thread.holdsLock(this);
+
+ Iterator iter = pendingCleans.iterator();
+ while (iter.hasNext()) {
+ CleanRequest request = (CleanRequest) iter.next();
+ try {
+ dgc.clean(request.objIDs, request.sequenceNum, vmid,
+ request.strong);
+ iter.remove();
+ } catch (Exception e) {
+ /*
+ * Many types of exceptions here could have been
+ * caused by a transient failure, so try again a
+ * few times, but not forever.
+ */
+ if (++request.failures >= cleanFailureRetries) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Create an array of ObjIDs (needed for the DGC remote calls)
+ * from the ids in the given set of refs.
+ */
+ private static ObjID[] createObjIDArray(Set refEntries) {
+ ObjID[] ids = new ObjID[refEntries.size()];
+ Iterator iter = refEntries.iterator();
+ for (int i = 0; i < ids.length; i++) {
+ ids[i] = ((RefEntry) iter.next()).getRef().getObjID();
+ }
+ return ids;
+ }
+
+ /**
+ * RefEntry encapsulates the client-side DGC information specific
+ * to a particular LiveRef value. In particular, it contains a
+ * set of phantom references to all of the instances of the LiveRef
+ * value registered in the system (but not garbage collected
+ * locally).
+ */
+ private class RefEntry {
+
+ /** LiveRef value for this entry (not a registered instance) */
+ private LiveRef ref;
+ /** set of phantom references to registered instances */
+ private Set refSet = new HashSet(5);
+ /** true if a dirty call containing this ref has failed */
+ private boolean dirtyFailed = false;
+
+ public RefEntry(LiveRef ref) {
+ this.ref = ref;
+ }
+
+ /**
+ * Return the LiveRef value for this entry (not a registered
+ * instance).
+ */
+ public LiveRef getRef() {
+ return ref;
+ }
+
+ /**
+ * Add a LiveRef to the set of registered instances for this entry.
+ *
+ * This method must ONLY be invoked while synchronized on this
+ * RefEntry's EndpointEntry.
+ */
+ public void addInstanceToRefSet(LiveRef ref) {
+ assert Thread.holdsLock(EndpointEntry.this);
+ assert ref.equals(this.ref);
+
+ /*
+ * Only keep a phantom reference to the registered instance,
+ * so that it can be garbage collected normally (and we can be
+ * notified when that happens).
+ */
+ refSet.add(new PhantomLiveRef(ref));
+ }
+
+ /**
+ * Remove a PhantomLiveRef from the set of registered instances.
+ *
+ * This method must ONLY be invoked while synchronized on this
+ * RefEntry's EndpointEntry.
+ */
+ public void removeInstanceFromRefSet(PhantomLiveRef phantom) {
+ assert Thread.holdsLock(EndpointEntry.this);
+ assert refSet.contains(phantom);
+ refSet.remove(phantom);
+ }
+
+ /**
+ * Return true if there are no registered LiveRef instances for
+ * this entry still reachable in this VM.
+ *
+ * This method must ONLY be invoked while synchronized on this
+ * RefEntry's EndpointEntry.
+ */
+ public boolean isRefSetEmpty() {
+ assert Thread.holdsLock(EndpointEntry.this);
+ return refSet.size() == 0;
+ }
+
+ /**
+ * Record that a dirty call that explicitly contained this
+ * entry's ref has failed.
+ *
+ * This method must ONLY be invoked while synchronized on this
+ * RefEntry's EndpointEntry.
+ */
+ public void markDirtyFailed() {
+ assert Thread.holdsLock(EndpointEntry.this);
+ dirtyFailed = true;
+ }
+
+ /**
+ * Return true if a dirty call that explicitly contained this
+ * entry's ref has failed (and therefore a clean call for this
+ * ref needs to be marked "strong").
+ *
+ * This method must ONLY be invoked while synchronized on this
+ * RefEntry's EndpointEntry.
+ */
+ public boolean hasDirtyFailed() {
+ assert Thread.holdsLock(EndpointEntry.this);
+ return dirtyFailed;
+ }
+
+ /**
+ * PhantomLiveRef is a PhantomReference to a LiveRef instance,
+ * used to detect when the LiveRef becomes permanently
+ * unreachable in this VM.
+ */
+ private class PhantomLiveRef extends PhantomReference {
+
+ public PhantomLiveRef(LiveRef ref) {
+ super(ref, EndpointEntry.this.refQueue);
+ }
+
+ public RefEntry getRefEntry() {
+ return RefEntry.this;
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/DGCImpl.java b/src/share/classes/sun/rmi/transport/DGCImpl.java
new file mode 100644
index 000000000..7b771fc42
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/DGCImpl.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.dgc.DGC;
+import java.rmi.dgc.Lease;
+import java.rmi.dgc.VMID;
+import java.rmi.server.LogStream;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteServer;
+import java.rmi.server.ServerNotActiveException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.RuntimeUtil;
+import sun.rmi.server.UnicastRef;
+import sun.rmi.server.UnicastServerRef;
+import sun.rmi.server.Util;
+import sun.security.action.GetLongAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * This class implements the guts of the server-side distributed GC
+ * algorithm
+ *
+ * @author Ann Wollrath
+ */
+final class DGCImpl implements DGC {
+
+ /* dgc system log */
+ static final Log dgcLog = Log.getLog("sun.rmi.dgc", "dgc",
+ LogStream.parseLevel(AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.dgc.logLevel"))));
+
+ /** lease duration to grant to clients */
+ private static final long leaseValue = // default 10 minutes
+ AccessController.doPrivileged(
+ new GetLongAction("java.rmi.dgc.leaseValue", 600000));
+
+ /** lease check interval; default is half of lease grant duration */
+ private static final long leaseCheckInterval =
+ AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.dgc.checkInterval", leaseValue / 2));
+
+ /** thread pool for scheduling delayed tasks */
+ private static final ScheduledExecutorService scheduler =
+ AccessController.doPrivileged(
+ new RuntimeUtil.GetInstanceAction()).getScheduler();
+
+ /** remote implementation of DGC interface for this VM */
+ private static DGCImpl dgc;
+ /** table that maps VMID to LeaseInfo */
+ private Map<VMID,LeaseInfo> leaseTable = new HashMap<VMID,LeaseInfo>();
+ /** checks for lease expiration */
+ private Future<?> checker = null;
+
+ /**
+ * Return the remote implementation of the DGC interface for
+ * this VM.
+ */
+ static DGCImpl getDGCImpl() {
+ return dgc;
+ }
+
+ /**
+ * Construct a new server-side remote object collector at
+ * a particular port. Disallow construction from outside.
+ */
+ private DGCImpl() {}
+
+ /**
+ * The dirty call adds the VMID "vmid" to the set of clients
+ * that hold references to the object associated with the ObjID
+ * id. The long "sequenceNum" is used to detect late dirty calls. If
+ * the VMID "vmid" is null, a VMID will be generated on the
+ * server (for use by the client in subsequent calls) and
+ * returned.
+ *
+ * The client must call the "dirty" method to renew the lease
+ * before the "lease" time expires or all references to remote
+ * objects in this VM that the client holds are considered
+ * "unreferenced".
+ */
+ public Lease dirty(ObjID[] ids, long sequenceNum, Lease lease) {
+ VMID vmid = lease.getVMID();
+ /*
+ * The server specifies the lease value; the client has
+ * no say in the matter.
+ */
+ long duration = leaseValue;
+
+ if (dgcLog.isLoggable(Log.VERBOSE)) {
+ dgcLog.log(Log.VERBOSE, "vmid = " + vmid);
+ }
+
+ // create a VMID if one wasn't supplied
+ if (vmid == null) {
+ vmid = new VMID();
+
+ if (dgcLog.isLoggable(Log.BRIEF)) {
+ String clientHost;
+ try {
+ clientHost = RemoteServer.getClientHost();
+ } catch (ServerNotActiveException e) {
+ clientHost = "<unknown host>";
+ }
+ dgcLog.log(Log.BRIEF, " assigning vmid " + vmid +
+ " to client " + clientHost);
+ }
+ }
+
+ lease = new Lease(vmid, duration);
+ // record lease information
+ synchronized (leaseTable) {
+ LeaseInfo info = leaseTable.get(vmid);
+ if (info == null) {
+ leaseTable.put(vmid, new LeaseInfo(vmid, duration));
+ if (checker == null) {
+ checker = scheduler.scheduleWithFixedDelay(
+ new Runnable() {
+ public void run() {
+ checkLeases();
+ }
+ },
+ leaseCheckInterval,
+ leaseCheckInterval, TimeUnit.MILLISECONDS);
+ }
+ } else {
+ info.renew(duration);
+ }
+ }
+
+ for (ObjID id : ids) {
+ if (dgcLog.isLoggable(Log.VERBOSE)) {
+ dgcLog.log(Log.VERBOSE, "id = " + id +
+ ", vmid = " + vmid + ", duration = " + duration);
+ }
+
+ ObjectTable.referenced(id, sequenceNum, vmid);
+ }
+
+ // return the VMID used
+ return lease;
+ }
+
+ /**
+ * The clean call removes the VMID from the set of clients
+ * that hold references to the object associated with the LiveRef
+ * ref. The sequence number is used to detect late clean calls. If the
+ * argument "strong" is true, then the clean call is a result of a
+ * failed "dirty" call, thus the sequence number for the VMID needs
+ * to be remembered until the client goes away.
+ */
+ public void clean(ObjID[] ids, long sequenceNum, VMID vmid, boolean strong)
+ {
+ for (ObjID id : ids) {
+ if (dgcLog.isLoggable(Log.VERBOSE)) {
+ dgcLog.log(Log.VERBOSE, "id = " + id +
+ ", vmid = " + vmid + ", strong = " + strong);
+ }
+
+ ObjectTable.unreferenced(id, sequenceNum, vmid, strong);
+ }
+ }
+
+ /**
+ * Register interest in receiving a callback when this VMID
+ * becomes inaccessible.
+ */
+ void registerTarget(VMID vmid, Target target) {
+ synchronized (leaseTable) {
+ LeaseInfo info = leaseTable.get(vmid);
+ if (info == null) {
+ target.vmidDead(vmid);
+ } else {
+ info.notifySet.add(target);
+ }
+ }
+ }
+
+ /**
+ * Remove notification request.
+ */
+ void unregisterTarget(VMID vmid, Target target) {
+ synchronized (leaseTable) {
+ LeaseInfo info = leaseTable.get(vmid);
+ if (info != null) {
+ info.notifySet.remove(target);
+ }
+ }
+ }
+
+ /**
+ * Check if leases have expired. If a lease has expired, remove
+ * it from the table and notify all interested parties that the
+ * VMID is essentially "dead".
+ *
+ * @return if true, there are leases outstanding; otherwise leases
+ * no longer need to be checked
+ */
+ private void checkLeases() {
+ long time = System.currentTimeMillis();
+
+ /* List of vmids that need to be removed from the leaseTable */
+ List<LeaseInfo> toUnregister = new ArrayList<LeaseInfo>();
+
+ /* Build a list of leaseInfo objects that need to have
+ * targets removed from their notifySet. Remove expired
+ * leases from leaseTable.
+ */
+ synchronized (leaseTable) {
+ Iterator<LeaseInfo> iter = leaseTable.values().iterator();
+ while (iter.hasNext()) {
+ LeaseInfo info = iter.next();
+ if (info.expired(time)) {
+ toUnregister.add(info);
+ iter.remove();
+ }
+ }
+
+ if (leaseTable.isEmpty()) {
+ checker.cancel(false);
+ checker = null;
+ }
+ }
+
+ /* Notify and unegister targets without holding the lock on
+ * the leaseTable so we avoid deadlock.
+ */
+ for (LeaseInfo info : toUnregister) {
+ for (Target target : info.notifySet) {
+ target.vmidDead(info.vmid);
+ }
+ }
+ }
+
+ static {
+ /*
+ * "Export" the singleton DGCImpl in a context isolated from
+ * the arbitrary current thread context.
+ */
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ ClassLoader savedCcl =
+ Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(
+ ClassLoader.getSystemClassLoader());
+
+ /*
+ * Put remote collector object in table by hand to prevent
+ * listen on port. (UnicastServerRef.exportObject would
+ * cause transport to listen.)
+ */
+ try {
+ dgc = new DGCImpl();
+ ObjID dgcID = new ObjID(ObjID.DGC_ID);
+ LiveRef ref = new LiveRef(dgcID, 0);
+ UnicastServerRef disp = new UnicastServerRef(ref);
+ Remote stub =
+ Util.createProxy(DGCImpl.class,
+ new UnicastRef(ref), true);
+ disp.setSkeleton(dgc);
+ Target target =
+ new Target(dgc, disp, stub, dgcID, true);
+ ObjectTable.putTarget(target);
+ } catch (RemoteException e) {
+ throw new Error(
+ "exception initializing server-side DGC", e);
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(savedCcl);
+ }
+ return null;
+ }
+ });
+ }
+
+ private static class LeaseInfo {
+ VMID vmid;
+ long expiration;
+ Set<Target> notifySet = new HashSet<Target>();
+
+ LeaseInfo(VMID vmid, long lease) {
+ this.vmid = vmid;
+ expiration = System.currentTimeMillis() + lease;
+ }
+
+ synchronized void renew(long lease) {
+ long newExpiration = System.currentTimeMillis() + lease;
+ if (newExpiration > expiration)
+ expiration = newExpiration;
+ }
+
+ boolean expired(long time) {
+ if (expiration < time) {
+ if (dgcLog.isLoggable(Log.BRIEF)) {
+ dgcLog.log(Log.BRIEF, vmid.toString());
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/Endpoint.java b/src/share/classes/sun/rmi/transport/Endpoint.java
new file mode 100644
index 000000000..37e0d6971
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/Endpoint.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.rmi.RemoteException;
+import java.rmi.Remote;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteServer;
+
+public interface Endpoint {
+ /**
+ * Return a channel that generates connections to the remote
+ * endpoint.
+ */
+ Channel getChannel();
+
+ /**
+ * Export the object so that it can accept incoming calls at
+ * the endpoint.
+ */
+ void exportObject(Target target)
+ throws RemoteException;
+
+ /**
+ * Returns the transport for incoming connections to this endpoint.
+ **/
+ Transport getInboundTransport();
+
+ /**
+ * Returns transport for making connections to remote endpoints.
+ **/
+ Transport getOutboundTransport();
+}
diff --git a/src/share/classes/sun/rmi/transport/LiveRef.java b/src/share/classes/sun/rmi/transport/LiveRef.java
new file mode 100644
index 000000000..f8810c0d2
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/LiveRef.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.server.ObjID;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.util.Arrays;
+import sun.rmi.transport.tcp.TCPEndpoint;
+
+/**
+ * NOTE: There is a JDK-internal dependency on the existence of this
+ * class and its getClientSocketFactory method in the implementation
+ * of javax.management.remote.rmi.RMIConnector.
+ **/
+public class LiveRef implements Cloneable {
+ /** wire representation for the object*/
+ private final Endpoint ep;
+ private final ObjID id;
+
+ /** cached connection service for the object */
+ private transient Channel ch;
+
+ /** flag to indicate whether this ref specifies a local server or
+ * is a ref for a remote object (surrogate)
+ */
+ private final boolean isLocal;
+
+ /**
+ * Construct a "well-known" live reference to a remote object
+ * @param isLocalServer If true, indicates this ref specifies a local
+ * server in this address space; if false, the ref is for a remote
+ * object (hence a surrogate or proxy) in another address space.
+ */
+ public LiveRef(ObjID objID, Endpoint endpoint, boolean isLocal) {
+ ep = endpoint;
+ id = objID;
+ this.isLocal = isLocal;
+ }
+
+ /**
+ * Construct a new live reference for a server object in the local
+ * address space.
+ */
+ public LiveRef(int port) {
+ this((new ObjID()), port);
+ }
+
+ /**
+ * Construct a new live reference for a server object in the local
+ * address space, to use sockets of the specified type.
+ */
+ public LiveRef(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ this((new ObjID()), port, csf, ssf);
+ }
+
+ /**
+ * Construct a new live reference for a "well-known" server object
+ * in the local address space.
+ */
+ public LiveRef(ObjID objID, int port) {
+ this(objID, TCPEndpoint.getLocalEndpoint(port), true);
+ }
+
+ /**
+ * Construct a new live reference for a "well-known" server object
+ * in the local address space, to use sockets of the specified type.
+ */
+ public LiveRef(ObjID objID, int port, RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ this(objID, TCPEndpoint.getLocalEndpoint(port, csf, ssf), true);
+ }
+
+ /**
+ * Return a shallow copy of this ref.
+ */
+ public Object clone() {
+ try {
+ LiveRef newRef = (LiveRef) super.clone();
+ return newRef;
+ } catch (CloneNotSupportedException e) {
+ throw new InternalError(e.toString());
+ }
+ }
+
+ /**
+ * Return the port number associated with this ref.
+ */
+ public int getPort() {
+ return ((TCPEndpoint) ep).getPort();
+ }
+
+ /**
+ * Return the client socket factory associated with this ref.
+ *
+ * NOTE: There is a JDK-internal dependency on the existence of
+ * this method in the implementation of
+ * javax.management.remote.rmi.RMIConnector.
+ **/
+ public RMIClientSocketFactory getClientSocketFactory() {
+ return ((TCPEndpoint) ep).getClientSocketFactory();
+ }
+
+ /**
+ * Return the server socket factory associated with this ref.
+ */
+ public RMIServerSocketFactory getServerSocketFactory() {
+ return ((TCPEndpoint) ep).getServerSocketFactory();
+ }
+
+ /**
+ * Export the object to accept incoming calls.
+ */
+ public void exportObject(Target target) throws RemoteException {
+ ep.exportObject(target);
+ }
+
+ public Channel getChannel() throws RemoteException {
+ if (ch == null) {
+ ch = ep.getChannel();
+ }
+ return ch;
+ }
+
+ public ObjID getObjID() {
+ return id;
+ }
+
+ Endpoint getEndpoint() {
+ return ep;
+ }
+
+ public String toString() {
+ String type;
+
+ if (isLocal)
+ type = "local";
+ else
+ type = "remote";
+ return "[endpoint:" + ep + "(" + type + ")," +
+ "objID:" + id + "]";
+ }
+
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj != null && obj instanceof LiveRef) {
+ LiveRef ref = (LiveRef) obj;
+
+ return (ep.equals(ref.ep) && id.equals(ref.id) &&
+ isLocal == ref.isLocal);
+ } else {
+ return false;
+ }
+ }
+
+ public boolean remoteEquals(Object obj) {
+ if (obj != null && obj instanceof LiveRef) {
+ LiveRef ref = (LiveRef) obj;
+
+ TCPEndpoint thisEp = ((TCPEndpoint) ep);
+ TCPEndpoint refEp = ((TCPEndpoint) ref.ep);
+
+ RMIClientSocketFactory thisClientFactory =
+ thisEp.getClientSocketFactory();
+ RMIClientSocketFactory refClientFactory =
+ refEp.getClientSocketFactory();
+
+ /**
+ * Fix for 4254103: LiveRef.remoteEquals should not fail
+ * if one of the objects in the comparison has a null
+ * server socket. Comparison should only consider the
+ * following criteria:
+ *
+ * hosts, ports, client socket factories and object IDs.
+ */
+ if (thisEp.getPort() != refEp.getPort() ||
+ !thisEp.getHost().equals(refEp.getHost()))
+ {
+ return false;
+ }
+ if ((thisClientFactory == null) ^ (refClientFactory == null)) {
+ return false;
+ }
+ if ((thisClientFactory != null) &&
+ !((thisClientFactory.getClass() ==
+ refClientFactory.getClass()) &&
+ (thisClientFactory.equals(refClientFactory))))
+ {
+ return false;
+ }
+ return (id.equals(ref.id));
+ } else {
+ return false;
+ }
+ }
+
+ public void write(ObjectOutput out, boolean useNewFormat)
+ throws IOException
+ {
+ boolean isResultStream = false;
+ if (out instanceof ConnectionOutputStream) {
+ ConnectionOutputStream stream = (ConnectionOutputStream) out;
+ isResultStream = stream.isResultStream();
+ /*
+ * Ensure that referential integrity is not broken while
+ * this LiveRef is in transit. If it is being marshalled
+ * as part of a result, it may not otherwise be strongly
+ * reachable after the remote call has completed; even if
+ * it is being marshalled as part of an argument, the VM
+ * may determine that the reference on the stack is no
+ * longer reachable after marshalling (see 6181943)--
+ * therefore, tell the stream to save a reference until a
+ * timeout expires or, for results, a DGCAck message has
+ * been received from the caller, or for arguments, the
+ * remote call has completed. For a "local" LiveRef, save
+ * a reference to the impl directly, because the impl is
+ * not reachable from the LiveRef (see 4114579);
+ * otherwise, save a reference to the LiveRef, for the
+ * client-side DGC to watch over. (Also see 4017232.)
+ */
+ if (isLocal) {
+ ObjectEndpoint oe =
+ new ObjectEndpoint(id, ep.getInboundTransport());
+ Target target = ObjectTable.getTarget(oe);
+
+ if (target != null) {
+ Remote impl = target.getImpl();
+ if (impl != null) {
+ stream.saveObject(impl);
+ }
+ }
+ } else {
+ stream.saveObject(this);
+ }
+ }
+ // All together now write out the endpoint, id, and flag
+
+ // (need to choose whether or not to use old JDK1.1 endpoint format)
+ if (useNewFormat) {
+ ((TCPEndpoint) ep).write(out);
+ } else {
+ ((TCPEndpoint) ep).writeHostPortFormat(out);
+ }
+ id.write(out);
+ out.writeBoolean(isResultStream);
+ }
+
+ public static LiveRef read(ObjectInput in, boolean useNewFormat)
+ throws IOException, ClassNotFoundException
+ {
+ Endpoint ep;
+ ObjID id;
+
+ // Now read in the endpoint, id, and result flag
+ // (need to choose whether or not to read old JDK1.1 endpoint format)
+ if (useNewFormat) {
+ ep = TCPEndpoint.read(in);
+ } else {
+ ep = TCPEndpoint.readHostPortFormat(in);
+ }
+ id = ObjID.read(in);
+ boolean isResultStream = in.readBoolean();
+
+ LiveRef ref = new LiveRef(id, ep, false);
+
+ if (in instanceof ConnectionInputStream) {
+ ConnectionInputStream stream = (ConnectionInputStream)in;
+ // save ref to send "dirty" call after all args/returns
+ // have been unmarshaled.
+ stream.saveRef(ref);
+ if (isResultStream) {
+ // set flag in stream indicating that remote objects were
+ // unmarshaled. A DGC ack should be sent by the transport.
+ stream.setAckNeeded();
+ }
+ } else {
+ DGCClient.registerRefs(ep, Arrays.asList(new LiveRef[] { ref }));
+ }
+
+ return ref;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/ObjectEndpoint.java b/src/share/classes/sun/rmi/transport/ObjectEndpoint.java
new file mode 100644
index 000000000..fd376c9e5
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/ObjectEndpoint.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.rmi.server.ObjID;
+
+/**
+ * An object used as a key to the object table that maps an
+ * instance of this class to a Target.
+ *
+ * @author Ann Wollrath
+ **/
+class ObjectEndpoint {
+
+ private final ObjID id;
+ private final Transport transport;
+
+ /**
+ * Constructs a new ObjectEndpoint instance with the specified id and
+ * transport. The specified id must be non-null, and the specified
+ * transport must either be non-null or the specified id must be
+ * equivalent to an ObjID constructed with ObjID.DGC_ID.
+ *
+ * @param id the object identifier
+ * @param transport the transport
+ * @throws NullPointerException if id is null
+ **/
+ ObjectEndpoint(ObjID id, Transport transport) {
+ if (id == null) {
+ throw new NullPointerException();
+ }
+ assert transport != null || id.equals(new ObjID(ObjID.DGC_ID));
+
+ this.id = id;
+ this.transport = transport;
+ }
+
+ /**
+ * Compares the specified object with this object endpoint for
+ * equality.
+ *
+ * This method returns true if and only if the specified object is an
+ * ObjectEndpoint instance with the same object identifier and
+ * transport as this object.
+ **/
+ public boolean equals(Object obj) {
+ if (obj instanceof ObjectEndpoint) {
+ ObjectEndpoint oe = (ObjectEndpoint) obj;
+ return id.equals(oe.id) && transport == oe.transport;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the hash code value for this object endpoint.
+ */
+ public int hashCode() {
+ return id.hashCode() ^ (transport != null ? transport.hashCode() : 0);
+ }
+
+ /**
+ * Returns a string representation for this object endpoint.
+ */
+ public String toString() {
+ return id.toString();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/ObjectTable.java b/src/share/classes/sun/rmi/transport/ObjectTable.java
new file mode 100644
index 000000000..e431ecd74
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/ObjectTable.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.lang.ref.ReferenceQueue;
+import java.rmi.NoSuchObjectException;
+import java.rmi.Remote;
+import java.rmi.dgc.VMID;
+import java.rmi.server.ExportException;
+import java.rmi.server.ObjID;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+import sun.misc.GC;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.security.action.GetLongAction;
+
+/**
+ * Object table shared by all implementors of the Transport interface.
+ * This table maps object ids to remote object targets in this address
+ * space.
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ */
+public final class ObjectTable {
+
+ /** maximum interval between complete garbage collections of local heap */
+ private final static long gcInterval = // default 1 hour
+ AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.dgc.server.gcInterval", 3600000));
+
+ /**
+ * lock guarding objTable and implTable.
+ * Holders MAY acquire a Target instance's lock or keepAliveLock.
+ */
+ private static final Object tableLock = new Object();
+
+ /** tables mapping to Target, keyed from ObjectEndpoint and impl object */
+ private static final Map<ObjectEndpoint,Target> objTable =
+ new HashMap<ObjectEndpoint,Target>();
+ private static final Map<WeakRef,Target> implTable =
+ new HashMap<WeakRef,Target>();
+
+ /**
+ * lock guarding keepAliveCount, reaper, and gcLatencyRequest.
+ * Holders may NOT acquire a Target instance's lock or tableLock.
+ */
+ private static final Object keepAliveLock = new Object();
+
+ /** count of non-permanent objects in table or still processing calls */
+ private static int keepAliveCount = 0;
+
+ /** thread to collect unreferenced objects from table */
+ private static Thread reaper = null;
+
+ /** queue notified when weak refs in the table are cleared */
+ static final ReferenceQueue reapQueue = new ReferenceQueue();
+
+ /** handle for GC latency request (for future cancellation) */
+ private static GC.LatencyRequest gcLatencyRequest = null;
+
+ /*
+ * Disallow anyone from creating one of these.
+ */
+ private ObjectTable() {}
+
+ /**
+ * Returns the target associated with the object id.
+ */
+ static Target getTarget(ObjectEndpoint oe) {
+ synchronized (tableLock) {
+ return objTable.get(oe);
+ }
+ }
+
+ /**
+ * Returns the target associated with the remote object
+ */
+ public static Target getTarget(Remote impl) {
+ synchronized (tableLock) {
+ return implTable.get(new WeakRef(impl));
+ }
+ }
+
+ /**
+ * Returns the stub for the remote object <b>obj</b> passed
+ * as a parameter. This operation is only valid <i>after</i>
+ * the object has been exported.
+ *
+ * @return the stub for the remote object, <b>obj</b>.
+ * @exception NoSuchObjectException if the stub for the
+ * remote object could not be found.
+ */
+ public static Remote getStub(Remote impl)
+ throws NoSuchObjectException
+ {
+ Target target = getTarget(impl);
+ if (target == null) {
+ throw new NoSuchObjectException("object not exported");
+ } else {
+ return target.getStub();
+ }
+ }
+
+ /**
+ * Remove the remote object, obj, from the RMI runtime. If
+ * successful, the object can no longer accept incoming RMI calls.
+ * If the force parameter is true, the object is forcibly unexported
+ * even if there are pending calls to the remote object or the
+ * remote object still has calls in progress. If the force
+ * parameter is false, the object is only unexported if there are
+ * no pending or in progress calls to the object.
+ *
+ * @param obj the remote object to be unexported
+ * @param force if true, unexports the object even if there are
+ * pending or in-progress calls; if false, only unexports the object
+ * if there are no pending or in-progress calls
+ * @return true if operation is successful, false otherwise
+ * @exception NoSuchObjectException if the remote object is not
+ * currently exported
+ */
+ public static boolean unexportObject(Remote obj, boolean force)
+ throws java.rmi.NoSuchObjectException
+ {
+ synchronized (tableLock) {
+ Target target = getTarget(obj);
+ if (target == null) {
+ throw new NoSuchObjectException("object not exported");
+ } else {
+ if (target.unexport(force)) {
+ removeTarget(target);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Add target to object table. If it is not a permanent entry, then
+ * make sure that reaper thread is running to remove collected entries
+ * and keep VM alive.
+ */
+ static void putTarget(Target target) throws ExportException {
+ ObjectEndpoint oe = target.getObjectEndpoint();
+ WeakRef weakImpl = target.getWeakImpl();
+
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE, "add object " + oe);
+ }
+
+ Remote impl = target.getImpl();
+ if (impl == null) {
+ throw new ExportException(
+ "internal error: attempt to export collected object");
+ }
+
+ synchronized (tableLock) {
+ if (objTable.containsKey(oe)) {
+ throw new ExportException(
+ "internal error: ObjID already in use");
+ } else if (implTable.containsKey(weakImpl)) {
+ throw new ExportException("object already exported");
+ }
+
+ objTable.put(oe, target);
+ implTable.put(weakImpl, target);
+
+ if (!target.isPermanent()) {
+ incrementKeepAliveCount();
+ }
+ }
+ }
+
+ /**
+ * Remove target from object table.
+ *
+ * NOTE: This method must only be invoked while synchronized on
+ * the "tableLock" object, because it does not do so itself.
+ */
+ private static void removeTarget(Target target) {
+ // assert Thread.holdsLock(tableLock);
+
+ ObjectEndpoint oe = target.getObjectEndpoint();
+ WeakRef weakImpl = target.getWeakImpl();
+
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE, "remove object " + oe);
+ }
+
+ objTable.remove(oe);
+ implTable.remove(weakImpl);
+
+ target.markRemoved(); // handles decrementing keep-alive count
+ }
+
+ /**
+ * Process client VM signalling reference for given ObjID: forward to
+ * correspoding Target entry. If ObjID is not found in table,
+ * no action is taken.
+ */
+ static void referenced(ObjID id, long sequenceNum, VMID vmid) {
+ synchronized (tableLock) {
+ ObjectEndpoint oe =
+ new ObjectEndpoint(id, Transport.currentTransport());
+ Target target = objTable.get(oe);
+ if (target != null) {
+ target.referenced(sequenceNum, vmid);
+ }
+ }
+ }
+
+ /**
+ * Process client VM dropping reference for given ObjID: forward to
+ * correspoding Target entry. If ObjID is not found in table,
+ * no action is taken.
+ */
+ static void unreferenced(ObjID id, long sequenceNum, VMID vmid,
+ boolean strong)
+ {
+ synchronized (tableLock) {
+ ObjectEndpoint oe =
+ new ObjectEndpoint(id, Transport.currentTransport());
+ Target target = objTable.get(oe);
+ if (target != null)
+ target.unreferenced(sequenceNum, vmid, strong);
+ }
+ }
+
+ /**
+ * Increments the "keep-alive count".
+ *
+ * The "keep-alive count" is the number of non-permanent remote objects
+ * that are either in the object table or still have calls in progress.
+ * Therefore, this method should be invoked exactly once for every
+ * non-permanent remote object exported (a remote object must be
+ * exported before it can have any calls in progress).
+ *
+ * The VM is "kept alive" while the keep-alive count is greater than
+ * zero; this is accomplished by keeping a non-daemon thread running.
+ *
+ * Because non-permanent objects are those that can be garbage
+ * collected while exported, and thus those for which the "reaper"
+ * thread operates, the reaper thread also serves as the non-daemon
+ * VM keep-alive thread; a new reaper thread is created if necessary.
+ */
+ static void incrementKeepAliveCount() {
+ synchronized (keepAliveLock) {
+ keepAliveCount++;
+
+ if (reaper == null) {
+ reaper = AccessController.doPrivileged(
+ new NewThreadAction(new Reaper(), "Reaper", false));
+ reaper.start();
+ }
+
+ /*
+ * While there are non-"permanent" objects in the object table,
+ * request a maximum latency for inspecting the entire heap
+ * from the local garbage collector, to place an upper bound
+ * on the time to discover remote objects that have become
+ * unreachable (and thus can be removed from the table).
+ */
+ if (gcLatencyRequest == null) {
+ gcLatencyRequest = GC.requestLatency(gcInterval);
+ }
+ }
+ }
+
+ /**
+ * Decrements the "keep-alive count".
+ *
+ * The "keep-alive count" is the number of non-permanent remote objects
+ * that are either in the object table or still have calls in progress.
+ * Therefore, this method should be invoked exactly once for every
+ * previously-exported non-permanent remote object that both has been
+ * removed from the object table and has no calls still in progress.
+ *
+ * If the keep-alive count is decremented to zero, then the current
+ * reaper thread is terminated to cease keeping the VM alive (and
+ * because there are no more non-permanent remote objects to reap).
+ */
+ static void decrementKeepAliveCount() {
+ synchronized (keepAliveLock) {
+ keepAliveCount--;
+
+ if (keepAliveCount == 0) {
+ if (!(reaper != null)) { throw new AssertionError(); }
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
+ public Void run() {
+ reaper.interrupt();
+ return null;
+ }
+ });
+ reaper = null;
+
+ /*
+ * If there are no longer any non-permanent objects in the
+ * object table, we are no longer concerned with the latency
+ * of local garbage collection here.
+ */
+ gcLatencyRequest.cancel();
+ gcLatencyRequest = null;
+ }
+ }
+ }
+
+ /**
+ * The Reaper thread waits for notifications that weak references in the
+ * object table have been cleared. When it receives a notification, it
+ * removes the corresponding entry from the table.
+ *
+ * Since the Reaper is created as a non-daemon thread, it also serves
+ * to keep the VM from exiting while there are objects in the table
+ * (other than permanent entries that should neither be reaped nor
+ * keep the VM alive).
+ */
+ private static class Reaper implements Runnable {
+
+ public void run() {
+ try {
+ do {
+ // wait for next cleared weak reference
+ WeakRef weakImpl = (WeakRef) reapQueue.remove();
+
+ synchronized (tableLock) {
+ Target target = implTable.get(weakImpl);
+ if (target != null) {
+ if (!target.isEmpty()) {
+ throw new Error(
+ "object with known references collected");
+ } else if (target.isPermanent()) {
+ throw new Error("permanent object collected");
+ }
+ removeTarget(target);
+ }
+ }
+ } while (!Thread.interrupted());
+ } catch (InterruptedException e) {
+ // pass away if interrupted
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/StreamRemoteCall.java b/src/share/classes/sun/rmi/transport/StreamRemoteCall.java
new file mode 100644
index 000000000..5a9ef0e1f
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/StreamRemoteCall.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.StreamCorruptedException;
+import java.rmi.RemoteException;
+import java.rmi.MarshalException;
+import java.rmi.UnmarshalException;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteCall;
+import sun.rmi.runtime.Log;
+import sun.rmi.server.UnicastRef;
+import sun.rmi.transport.tcp.TCPEndpoint;
+
+/**
+ * Stream-based implementation of the RemoteCall interface.
+ *
+ * @author Ann Wollrath
+ */
+public class StreamRemoteCall implements RemoteCall {
+ private ConnectionInputStream in = null;
+ private ConnectionOutputStream out = null;
+ private Connection conn;
+ private boolean resultStarted = false;
+ private Exception serverException = null;
+
+ public StreamRemoteCall(Connection c) {
+ conn = c;
+ }
+
+ public StreamRemoteCall(Connection c, ObjID id, int op, long hash)
+ throws RemoteException
+ {
+ try {
+ conn = c;
+ Transport.transportLog.log(Log.VERBOSE,
+ "write remote call header...");
+
+ // write out remote call header info...
+ // call header, part 1 (read by Transport)
+ conn.getOutputStream().write(TransportConstants.Call);
+ getOutputStream(); // creates a MarshalOutputStream
+ id.write(out); // object id (target of call)
+ // call header, part 2 (read by Dispatcher)
+ out.writeInt(op); // method number (operation index)
+ out.writeLong(hash); // stub/skeleton hash
+ } catch (IOException e) {
+ throw new MarshalException("Error marshaling call header", e);
+ }
+ }
+
+ /**
+ * Return the connection associated with this call.
+ */
+ public Connection getConnection() {
+ return conn;
+ }
+
+ /**
+ * Return the output stream the stub/skeleton should put arguments/results
+ * into.
+ */
+ public ObjectOutput getOutputStream() throws IOException {
+ return getOutputStream(false);
+ }
+
+ private ObjectOutput getOutputStream(boolean resultStream)
+ throws IOException
+ {
+ if (out == null) {
+ Transport.transportLog.log(Log.VERBOSE, "getting output stream");
+
+ out = new ConnectionOutputStream(conn, resultStream);
+ }
+ return out;
+ }
+
+ /**
+ * Release the outputStream Currently, will not complain if the
+ * output stream is released more than once.
+ */
+ public void releaseOutputStream() throws IOException {
+ try {
+ if (out != null) {
+ try {
+ out.flush();
+ } finally {
+ out.done(); // always start DGC ack timer
+ }
+ }
+ conn.releaseOutputStream();
+ } finally {
+ out = null;
+ }
+ }
+
+ /**
+ * Get the InputStream the stub/skeleton should get results/arguments
+ * from.
+ */
+ public ObjectInput getInputStream() throws IOException {
+ if (in == null) {
+ Transport.transportLog.log(Log.VERBOSE, "getting input stream");
+
+ in = new ConnectionInputStream(conn.getInputStream());
+ }
+ return in;
+ }
+
+ /**
+ * Release the input stream, this would allow some transports to release
+ * the channel early.
+ */
+ public void releaseInputStream() throws IOException {
+ /* WARNING: Currently, the UnicastRef.java invoke methods rely
+ * upon this method not throwing an IOException.
+ */
+
+ try {
+ if (in != null) {
+ // execute MarshalInputStream "done" callbacks
+ try {
+ in.done();
+ } catch (RuntimeException e) {
+ }
+
+ // add saved references to DGC table
+ in.registerRefs();
+
+ /* WARNING: The connection being passed to done may have
+ * already been freed.
+ */
+ in.done(conn);
+ }
+ conn.releaseInputStream();
+ } finally {
+ in = null;
+ }
+ }
+
+ /**
+ * Returns an output stream (may put out header information
+ * relating to the success of the call).
+ * @param success If true, indicates normal return, else indicates
+ * exceptional return.
+ * @exception StreamCorruptedException If result stream previously
+ * acquired
+ * @exception IOException For any other problem with I/O.
+ */
+ public ObjectOutput getResultStream(boolean success) throws IOException {
+ /* make sure result code only marshaled once. */
+ if (resultStarted)
+ throw new StreamCorruptedException("result already in progress");
+ else
+ resultStarted = true;
+
+ // write out return header
+ // return header, part 1 (read by Transport)
+ DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
+ wr.writeByte(TransportConstants.Return);// transport op
+ getOutputStream(true); // creates a MarshalOutputStream
+ // return header, part 2 (read by client-side RemoteCall)
+ if (success) //
+ out.writeByte(TransportConstants.NormalReturn);
+ else
+ out.writeByte(TransportConstants.ExceptionalReturn);
+ out.writeID(); // write id for gcAck
+ return out;
+ }
+
+ /**
+ * Do whatever it takes to execute the call.
+ */
+ public void executeCall() throws Exception {
+ byte returnType;
+
+ // read result header
+ DGCAckHandler ackHandler = null;
+ try {
+ if (out != null) {
+ ackHandler = out.getDGCAckHandler();
+ }
+ releaseOutputStream();
+ DataInputStream rd = new DataInputStream(conn.getInputStream());
+ byte op = rd.readByte();
+ if (op != TransportConstants.Return) {
+ if (Transport.transportLog.isLoggable(Log.BRIEF)) {
+ Transport.transportLog.log(Log.BRIEF,
+ "transport return code invalid: " + op);
+ }
+ throw new UnmarshalException("Transport return code invalid");
+ }
+ getInputStream();
+ returnType = in.readByte();
+ in.readID(); // id for DGC acknowledgement
+ } catch (UnmarshalException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new UnmarshalException("Error unmarshaling return header",
+ e);
+ } finally {
+ if (ackHandler != null) {
+ ackHandler.release();
+ }
+ }
+
+ // read return value
+ switch (returnType) {
+ case TransportConstants.NormalReturn:
+ break;
+
+ case TransportConstants.ExceptionalReturn:
+ Object ex;
+ try {
+ ex = in.readObject();
+ } catch (Exception e) {
+ throw new UnmarshalException("Error unmarshaling return", e);
+ }
+
+ // An exception should have been received,
+ // if so throw it, else flag error
+ if (ex instanceof Exception) {
+ exceptionReceivedFromServer((Exception) ex);
+ } else {
+ throw new UnmarshalException("Return type not Exception");
+ }
+ default:
+ if (Transport.transportLog.isLoggable(Log.BRIEF)) {
+ Transport.transportLog.log(Log.BRIEF,
+ "return code invalid: " + returnType);
+ }
+ throw new UnmarshalException("Return code invalid");
+ }
+ }
+
+ /**
+ * Routine that causes the stack traces of remote exceptions to be
+ * filled in with the current stack trace on the client. Detail
+ * exceptions are filled in iteratively.
+ */
+ protected void exceptionReceivedFromServer(Exception ex) throws Exception {
+ serverException = ex;
+
+ StackTraceElement[] serverTrace = ex.getStackTrace();
+ StackTraceElement[] clientTrace = (new Throwable()).getStackTrace();
+ StackTraceElement[] combinedTrace =
+ new StackTraceElement[serverTrace.length + clientTrace.length];
+ System.arraycopy(serverTrace, 0, combinedTrace, 0,
+ serverTrace.length);
+ System.arraycopy(clientTrace, 0, combinedTrace, serverTrace.length,
+ clientTrace.length);
+ ex.setStackTrace(combinedTrace);
+
+ /*
+ * Log the details of a server exception thrown as a result of a
+ * remote method invocation.
+ */
+ if (UnicastRef.clientCallLog.isLoggable(Log.BRIEF)) {
+ /* log call exception returned from server before it is rethrown */
+ TCPEndpoint ep = (TCPEndpoint) conn.getChannel().getEndpoint();
+ UnicastRef.clientCallLog.log(Log.BRIEF, "outbound call " +
+ "received exception: [" + ep.getHost() + ":" +
+ ep.getPort() + "] exception: ", ex);
+ }
+
+ throw ex;
+ }
+
+ /*
+ * method to retrieve possible server side exceptions (which will
+ * be throw from exceptionReceivedFromServer(...) )
+ */
+ public Exception getServerException() {
+ return serverException;
+ }
+
+ public void done() throws IOException {
+ /* WARNING: Currently, the UnicastRef.java invoke methods rely
+ * upon this method not throwing an IOException.
+ */
+
+ releaseInputStream();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/Target.java b/src/share/classes/sun/rmi/transport/Target.java
new file mode 100644
index 000000000..01df43800
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/Target.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.rmi.Remote;
+import java.rmi.NoSuchObjectException;
+import java.rmi.dgc.VMID;
+import java.rmi.server.ObjID;
+import java.rmi.server.Unreferenced;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.*;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.server.Dispatcher;
+
+/**
+ * A target contains information pertaining to a remote object that
+ * resides in this address space. Targets are located via the
+ * ObjectTable.
+ */
+public final class Target {
+ /** object id for target */
+ private final ObjID id;
+ /** flag indicating whether target is subject to collection */
+ private final boolean permanent;
+ /** weak reference to remote object implementation */
+ private final WeakRef weakImpl;
+ /** dispatcher for remote object */
+ private volatile Dispatcher disp;
+ /** stub for remote object */
+ private final Remote stub;
+ /** set of clients that hold references to this target */
+ private final Vector refSet = new Vector();
+ /** table that maps client endpoints to sequence numbers */
+ private final Hashtable sequenceTable = new Hashtable(5);
+ /** access control context in which target was created */
+ private final AccessControlContext acc;
+ /** context class loader in which target was created */
+ private final ClassLoader ccl;
+ /** number of pending/executing calls */
+ private int callCount = 0;
+ /** true if this target has been removed from the object table */
+ private boolean removed = false;
+ /**
+ * the transport through which this target was exported and
+ * through which remote calls will be allowed
+ */
+ private volatile Transport exportedTransport = null;
+
+ /** number to identify next callback thread created here */
+ private static int nextThreadNum = 0;
+
+ /**
+ * Construct a Target for a remote object "impl" with
+ * a specific object id.
+ *
+ * If "permanent" is true, then the impl is pinned permanently
+ * (the impl will not be collected via distributed and/or local
+ * GC). If "on" is false, than the impl is subject to
+ * collection. Permanent objects do not keep a server from
+ * exiting.
+ */
+ public Target(Remote impl, Dispatcher disp, Remote stub, ObjID id,
+ boolean permanent)
+ {
+ this.weakImpl = new WeakRef(impl, ObjectTable.reapQueue);
+ this.disp = disp;
+ this.stub = stub;
+ this.id = id;
+ this.acc = AccessController.getContext();
+
+ /*
+ * Fix for 4149366: so that downloaded parameter types unmarshalled
+ * for this impl will be compatible with types known only to the
+ * impl class's class loader (when it's not identical to the
+ * exporting thread's context class loader), mark the impl's class
+ * loader as the loader to use as the context class loader in the
+ * server's dispatch thread while a call to this impl is being
+ * processed (unless this exporting thread's context class loader is
+ * a child of the impl's class loader, such as when a registry is
+ * exported by an application, in which case this thread's context
+ * class loader is preferred).
+ */
+ ClassLoader threadContextLoader =
+ Thread.currentThread().getContextClassLoader();
+ ClassLoader serverLoader = impl.getClass().getClassLoader();
+ if (checkLoaderAncestry(threadContextLoader, serverLoader)) {
+ this.ccl = threadContextLoader;
+ } else {
+ this.ccl = serverLoader;
+ }
+
+ this.permanent = permanent;
+ if (permanent) {
+ pinImpl();
+ }
+ }
+
+ /**
+ * Return true if the first class loader is a child of (or identical
+ * to) the second class loader. Either loader may be "null", which is
+ * considered to be the parent of any non-null class loader.
+ *
+ * (utility method added for the 1.2beta4 fix for 4149366)
+ */
+ private static boolean checkLoaderAncestry(ClassLoader child,
+ ClassLoader ancestor)
+ {
+ if (ancestor == null) {
+ return true;
+ } else if (child == null) {
+ return false;
+ } else {
+ for (ClassLoader parent = child;
+ parent != null;
+ parent = parent.getParent())
+ {
+ if (parent == ancestor) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Get the stub (proxy) object for this target
+ */
+ public Remote getStub() {
+ return stub;
+ }
+
+ /**
+ * Returns the object endpoint for the target.
+ */
+ ObjectEndpoint getObjectEndpoint() {
+ return new ObjectEndpoint(id, exportedTransport);
+ }
+
+ /**
+ * Get the weak reference for the Impl of this target.
+ */
+ WeakRef getWeakImpl() {
+ return weakImpl;
+ }
+
+ /**
+ * Returns the dispatcher for this remote object target.
+ */
+ Dispatcher getDispatcher() {
+ return disp;
+ }
+
+ AccessControlContext getAccessControlContext() {
+ return acc;
+ }
+
+ ClassLoader getContextClassLoader() {
+ return ccl;
+ }
+
+ /**
+ * Get the impl for this target.
+ * Note: this may return null if the impl has been garbage collected.
+ * (currently, there is no need to make this method public)
+ */
+ Remote getImpl() {
+ return (Remote)weakImpl.get();
+ }
+
+ /**
+ * Returns true if the target is permanent.
+ */
+ boolean isPermanent() {
+ return permanent;
+ }
+
+ /**
+ * Pin impl in target. Pin the WeakRef object so it holds a strong
+ * reference to the object to it will not be garbage collected locally.
+ * This way there is a single object responsible for the weak ref
+ * mechanism.
+ */
+ synchronized void pinImpl() {
+ weakImpl.pin();
+ }
+
+ /**
+ * Unpin impl in target. Weaken the reference to impl so that it
+ * can be garbage collected locally. But only if there the refSet
+ * is empty. All of the weak/strong handling is in WeakRef
+ */
+ synchronized void unpinImpl() {
+ /* only unpin if:
+ * a) impl is not permanent, and
+ * b) impl is not already unpinned, and
+ * c) there are no external references (outside this
+ * address space) for the impl
+ */
+ if (!permanent && refSet.isEmpty()) {
+ weakImpl.unpin();
+ }
+ }
+
+ /**
+ * Enable the transport through which remote calls to this target
+ * are allowed to be set if it has not already been set.
+ */
+ void setExportedTransport(Transport exportedTransport) {
+ if (this.exportedTransport == null) {
+ this.exportedTransport = exportedTransport;
+ }
+ }
+
+ /**
+ * Add an endpoint to the remembered set. Also adds a notifier
+ * to call back if the address space associated with the endpoint
+ * dies.
+ */
+ synchronized void referenced(long sequenceNum, VMID vmid) {
+ // check sequence number for vmid
+ SequenceEntry entry = (SequenceEntry) sequenceTable.get(vmid);
+ if (entry == null) {
+ sequenceTable.put(vmid, new SequenceEntry(sequenceNum));
+ } else if (entry.sequenceNum < sequenceNum) {
+ entry.update(sequenceNum);
+ } else {
+ // late dirty call; ignore.
+ return;
+ }
+
+ if (!refSet.contains(vmid)) {
+ /*
+ * A Target must be pinned while its refSet is not empty. It may
+ * have become unpinned if external LiveRefs only existed in
+ * serialized form for some period of time, or if a client failed
+ * to renew its lease due to a transient network failure. So,
+ * make sure that it is pinned here; this fixes bugid 4069644.
+ */
+ pinImpl();
+ if (getImpl() == null) // too late if impl was collected
+ return;
+
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE, "add to dirty set: " + vmid);
+ }
+
+ refSet.addElement(vmid);
+
+ DGCImpl.getDGCImpl().registerTarget(vmid, this);
+ }
+ }
+
+ /**
+ * Remove endpoint from remembered set. If set becomes empty,
+ * remove server from Transport's object table.
+ */
+ synchronized void unreferenced(long sequenceNum, VMID vmid, boolean strong)
+ {
+ // check sequence number for vmid
+ SequenceEntry entry = (SequenceEntry) sequenceTable.get(vmid);
+ if (entry == null || entry.sequenceNum > sequenceNum) {
+ // late clean call; ignore
+ return;
+ } else if (strong) {
+ // strong clean call; retain sequenceNum
+ entry.retain(sequenceNum);
+ } else if (entry.keep == false) {
+ // get rid of sequence number
+ sequenceTable.remove(vmid);
+ }
+
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE, "remove from dirty set: " + vmid);
+ }
+
+ refSetRemove(vmid);
+ }
+
+ /**
+ * Remove endpoint from the reference set.
+ */
+ synchronized private void refSetRemove(VMID vmid) {
+ // remove notification request
+ DGCImpl.getDGCImpl().unregisterTarget(vmid, this);
+
+ if (refSet.removeElement(vmid) && refSet.isEmpty()) {
+ // reference set is empty, so server can be garbage collected.
+ // remove object from table.
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE,
+ "reference set is empty: target = " + this);
+ }
+
+ /*
+ * If the remote object implements the Unreferenced interface,
+ * invoke its unreferenced callback in a separate thread.
+ */
+ Remote obj = getImpl();
+ if (obj instanceof Unreferenced) {
+ final Unreferenced unrefObj = (Unreferenced) obj;
+ final Thread t = (Thread)
+ java.security.AccessController.doPrivileged(
+ new NewThreadAction(new Runnable() {
+ public void run() {
+ unrefObj.unreferenced();
+ }
+ }, "Unreferenced-" + nextThreadNum++, false, true));
+ // REMIND: access to nextThreadNum not synchronized; you care?
+ /*
+ * We must manually set the context class loader appropriately
+ * for threads that may invoke user code (see bugid 4171278).
+ */
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ t.setContextClassLoader(ccl);
+ return null;
+ }
+ });
+
+ t.start();
+ }
+
+ unpinImpl();
+ }
+ }
+
+ /**
+ * Mark this target as not accepting new calls if any of the
+ * following conditions exist: a) the force parameter is true,
+ * b) the target's call count is zero, or c) the object is already
+ * not accepting calls. Returns true if target is marked as not
+ * accepting new calls; returns false otherwise.
+ */
+ synchronized boolean unexport(boolean force) {
+
+ if ((force == true) || (callCount == 0) || (disp == null)) {
+ disp = null;
+ /*
+ * Fix for 4331349: unpin object so that it may be gc'd.
+ * Also, unregister all vmids referencing this target
+ * so target can be gc'd.
+ */
+ unpinImpl();
+ DGCImpl dgc = DGCImpl.getDGCImpl();
+ Enumeration enum_ = refSet.elements();
+ while (enum_.hasMoreElements()) {
+ VMID vmid = (VMID) enum_.nextElement();
+ dgc.unregisterTarget(vmid, this);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Mark this target as having been removed from the object table.
+ */
+ synchronized void markRemoved() {
+ if (!(!removed)) { throw new AssertionError(); }
+
+ removed = true;
+ if (!permanent && callCount == 0) {
+ ObjectTable.decrementKeepAliveCount();
+ }
+
+ if (exportedTransport != null) {
+ exportedTransport.targetUnexported();
+ }
+ }
+
+ /**
+ * Increment call count.
+ */
+ synchronized void incrementCallCount() throws NoSuchObjectException {
+
+ if (disp != null) {
+ callCount ++;
+ } else {
+ throw new NoSuchObjectException("object not accepting new calls");
+ }
+ }
+
+ /**
+ * Decrement call count.
+ */
+ synchronized void decrementCallCount() {
+
+ if (--callCount < 0) {
+ throw new Error("internal error: call count less than zero");
+ }
+
+ /*
+ * The "keep-alive count" is the number of non-permanent remote
+ * objects that are either in the object table or still have calls
+ * in progress. Therefore, this state change may affect the
+ * keep-alive count: if this target is for a non-permanent remote
+ * object that has been removed from the object table and now has a
+ * call count of zero, it needs to be decremented.
+ */
+ if (!permanent && removed && callCount == 0) {
+ ObjectTable.decrementKeepAliveCount();
+ }
+ }
+
+ /**
+ * Returns true if remembered set is empty; otherwise returns
+ * false
+ */
+ boolean isEmpty() {
+ return refSet.isEmpty();
+ }
+
+ /**
+ * This method is called if the address space associated with the
+ * vmid dies. In that case, the vmid should be removed
+ * from the reference set.
+ */
+ synchronized public void vmidDead(VMID vmid) {
+ if (DGCImpl.dgcLog.isLoggable(Log.BRIEF)) {
+ DGCImpl.dgcLog.log(Log.BRIEF, "removing endpoint " +
+ vmid + " from reference set");
+ }
+
+ sequenceTable.remove(vmid);
+ refSetRemove(vmid);
+ }
+}
+
+class SequenceEntry {
+ long sequenceNum;
+ boolean keep;
+
+ SequenceEntry(long sequenceNum) {
+ this.sequenceNum = sequenceNum;
+ keep = false;
+ }
+
+ void retain(long sequenceNum) {
+ this.sequenceNum = sequenceNum;
+ keep = true;
+ }
+
+ void update(long sequenceNum) {
+ this.sequenceNum = sequenceNum;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/Transport.java b/src/share/classes/sun/rmi/transport/Transport.java
new file mode 100644
index 000000000..572a55b2b
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/Transport.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.rmi.MarshalException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.server.LogStream;
+import java.rmi.server.ObjID;
+import java.rmi.server.RemoteCall;
+import java.rmi.server.RemoteServer;
+import java.rmi.server.ServerNotActiveException;
+import java.security.AccessControlContext;
+import sun.rmi.runtime.Log;
+import sun.rmi.server.Dispatcher;
+import sun.rmi.server.UnicastServerRef;
+
+/**
+ * Transport abstraction for enabling communication between different
+ * VMs.
+ *
+ * @author Ann Wollrath
+ */
+public abstract class Transport {
+
+ /** "transport" package log level */
+ static final int logLevel = LogStream.parseLevel(getLogLevel());
+
+ private static String getLogLevel() {
+ return (String) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("sun.rmi.transport.logLevel"));
+ }
+
+ /* transport package log */
+ static final Log transportLog =
+ Log.getLog("sun.rmi.transport.misc", "transport", Transport.logLevel);
+
+ /** References the current transport when a call is being serviced */
+ private static final ThreadLocal currentTransport = new ThreadLocal();
+
+ /** ObjID for DGCImpl */
+ private static final ObjID dgcID = new ObjID(ObjID.DGC_ID);
+
+ /**
+ * Returns a <I>Channel</I> that generates connections to the
+ * endpoint <I>ep</I>. A Channel is an object that creates and
+ * manages connections of a particular type to some particular
+ * address space.
+ * @param ep the endpoint to which connections will be generated.
+ * @return the channel or null if the transport cannot
+ * generate connections to this endpoint
+ */
+ public abstract Channel getChannel(Endpoint ep);
+
+ /**
+ * Removes the <I>Channel</I> that generates connections to the
+ * endpoint <I>ep</I>.
+ */
+ public abstract void free(Endpoint ep);
+
+ /**
+ * Export the object so that it can accept incoming calls.
+ */
+ public void exportObject(Target target) throws RemoteException {
+ target.setExportedTransport(this);
+ ObjectTable.putTarget(target);
+ }
+
+ /**
+ * Invoked when an object that was exported on this transport has
+ * become unexported, either by being garbage collected or by
+ * being explicitly unexported.
+ **/
+ protected void targetUnexported() { }
+
+ /**
+ * Returns the current transport if a call is being serviced, otherwise
+ * returns null.
+ **/
+ static Transport currentTransport() {
+ return (Transport) currentTransport.get();
+ }
+
+ /**
+ * Verify that the current access control context has permission to accept
+ * the connection being dispatched by the current thread. The current
+ * access control context is passed as a parameter to avoid the overhead of
+ * an additional call to AccessController.getContext.
+ */
+ protected abstract void checkAcceptPermission(AccessControlContext acc);
+
+ /**
+ * Service an incoming remote call. When a message arrives on the
+ * connection indicating the beginning of a remote call, the
+ * threads are required to call the <I>serviceCall</I> method of
+ * their transport. The default implementation of this method
+ * locates and calls the dispatcher object. Ordinarily a
+ * transport implementation will not need to override this method.
+ * At the entry to <I>tr.serviceCall(conn)</I>, the connection's
+ * input stream is positioned at the start of the incoming
+ * message. The <I>serviceCall</I> method processes the incoming
+ * remote invocation and sends the result on the connection's
+ * output stream. If it returns "true", then the remote
+ * invocation was processed without error and the transport can
+ * cache the connection. If it returns "false", a protocol error
+ * occurred during the call, and the transport should destroy the
+ * connection.
+ */
+ public boolean serviceCall(final RemoteCall call) {
+ try {
+ /* read object id */
+ final Remote impl;
+ ObjID id;
+
+ try {
+ id = ObjID.read(call.getInputStream());
+ } catch (java.io.IOException e) {
+ throw new MarshalException("unable to read objID", e);
+ }
+
+ /* get the remote object */
+ Transport transport = id.equals(dgcID) ? null : this;
+ Target target =
+ ObjectTable.getTarget(new ObjectEndpoint(id, transport));
+
+ if (target == null || (impl = target.getImpl()) == null) {
+ throw new NoSuchObjectException("no such object in table");
+ }
+
+ final Dispatcher disp = target.getDispatcher();
+ target.incrementCallCount();
+ try {
+ /* call the dispatcher */
+ transportLog.log(Log.VERBOSE, "call dispatcher");
+
+ final AccessControlContext acc =
+ target.getAccessControlContext();
+ ClassLoader ccl = target.getContextClassLoader();
+
+ Thread t = Thread.currentThread();
+ ClassLoader savedCcl = t.getContextClassLoader();
+
+ try {
+ t.setContextClassLoader(ccl);
+ currentTransport.set(this);
+ try {
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedExceptionAction() {
+ public Object run() throws IOException {
+ checkAcceptPermission(acc);
+ disp.dispatch(impl, call);
+ return null;
+ }
+ }, acc);
+ } catch (java.security.PrivilegedActionException pae) {
+ throw (IOException) pae.getException();
+ }
+ } finally {
+ t.setContextClassLoader(savedCcl);
+ currentTransport.set(null);
+ }
+
+ } catch (IOException ex) {
+ transportLog.log(Log.BRIEF,
+ "exception thrown by dispatcher: ", ex);
+ return false;
+ } finally {
+ target.decrementCallCount();
+ }
+
+ } catch (RemoteException e) {
+
+ // if calls are being logged, write out exception
+ if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
+ // include client host name if possible
+ String clientHost = "";
+ try {
+ clientHost = "[" +
+ RemoteServer.getClientHost() + "] ";
+ } catch (ServerNotActiveException ex) {
+ }
+ String message = clientHost + "exception: ";
+ UnicastServerRef.callLog.log(Log.BRIEF, message, e);
+ }
+
+ /* We will get a RemoteException if either a) the objID is
+ * not readable, b) the target is not in the object table, or
+ * c) the object is in the midst of being unexported (note:
+ * NoSuchObjectException is thrown by the incrementCallCount
+ * method if the object is being unexported). Here it is
+ * relatively safe to marshal an exception to the client
+ * since the client will not have seen a return value yet.
+ */
+ try {
+ ObjectOutput out = call.getResultStream(false);
+ UnicastServerRef.clearStackTraces(e);
+ out.writeObject(e);
+ call.releaseOutputStream();
+
+ } catch (IOException ie) {
+ transportLog.log(Log.BRIEF,
+ "exception thrown marshalling exception: ", ie);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/TransportConstants.java b/src/share/classes/sun/rmi/transport/TransportConstants.java
new file mode 100644
index 000000000..534baf873
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/TransportConstants.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport;
+
+public class TransportConstants {
+ /** Transport magic number: "JRMI"*/
+ public static final int Magic = 0x4a524d49;
+ /** Transport version number */
+ public static final short Version = 2;
+
+ /** Connection uses stream protocol */
+ public static final byte StreamProtocol = 0x4b;
+ /** Protocol for single operation per connection; no ack required */
+ public static final byte SingleOpProtocol = 0x4c;
+ /** Connection uses multiplex protocol */
+ public static final byte MultiplexProtocol = 0x4d;
+
+ /** Ack for transport protocol */
+ public static final byte ProtocolAck = 0x4e;
+ /** Negative ack for transport protocol (protocol not supported) */
+ public static final byte ProtocolNack = 0x4f;
+
+ /** RMI call */
+ public static final byte Call = 0x50;
+ /** RMI return */
+ public static final byte Return = 0x51;
+ /** Ping operation */
+ public static final byte Ping = 0x52;
+ /** Acknowledgment for Ping operation */
+ public static final byte PingAck = 0x53;
+ /** Acknowledgment for distributed GC */
+ public static final byte DGCAck = 0x54;
+
+ /** Normal return (with or without return value) */
+ public static final byte NormalReturn = 0x01;
+ /** Exceptional return */
+ public static final byte ExceptionalReturn = 0x02;
+}
diff --git a/src/share/classes/sun/rmi/transport/WeakRef.java b/src/share/classes/sun/rmi/transport/WeakRef.java
new file mode 100644
index 000000000..08b2519ed
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/WeakRef.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport;
+
+import java.lang.ref.*;
+import sun.rmi.runtime.Log;
+
+/**
+ * WeakRef objects are used by the RMI runtime to hold potentially weak
+ * references to exported remote objects in the local object table.
+ *
+ * This class extends the functionality of java.lang.ref.WeakReference in
+ * several ways. The methods pin() and unpin() can be used to set
+ * whether the contained reference is strong or weak (it is weak upon
+ * construction). The hashCode() and equals() methods are overridden so
+ * that WeakRef objects hash and compare to each other according to the
+ * object identity of their referents.
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ */
+class WeakRef extends WeakReference {
+
+ /** value of the referent's "identity" hash code */
+ private int hashValue;
+
+ /** strong reference to the referent, for when this WeakRef is "pinned" */
+ private Object strongRef = null;
+
+ /**
+ * Create a new WeakRef to the given object.
+ */
+ public WeakRef(Object obj) {
+ super(obj);
+ setHashValue(obj); // cache object's "identity" hash code
+ }
+
+ /**
+ * Create a new WeakRef to the given object, registered with a queue.
+ */
+ public WeakRef(Object obj, ReferenceQueue q) {
+ super(obj, q);
+ setHashValue(obj); // cache object's "identity" hash code
+ }
+
+ /**
+ * Pin the contained reference (make this a strong reference).
+ */
+ public synchronized void pin() {
+ if (strongRef == null) {
+ strongRef = get();
+
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE,
+ "strongRef = " + strongRef);
+ }
+ }
+ }
+
+ /**
+ * Unpin the contained reference (make this a weak reference).
+ */
+ public synchronized void unpin() {
+ if (strongRef != null) {
+ if (DGCImpl.dgcLog.isLoggable(Log.VERBOSE)) {
+ DGCImpl.dgcLog.log(Log.VERBOSE,
+ "strongRef = " + strongRef);
+ }
+
+ strongRef = null;
+ }
+ }
+
+ /*
+ * Cache referent's "identity" hash code (so that we still have the
+ * value after the referent gets cleared).
+ *
+ * We cannot use the value from the object's hashCode() method, since
+ * if the object is of a remote class not extended from RemoteObject
+ * and it is trying to implement hashCode() and equals() so that it
+ * can be compared to stub objects, its own hash code could not have
+ * been initialized yet (see bugid 4102938). Also, object table keys
+ * based on server objects are indeed matched on object identity, so
+ * this is the correct hash technique regardless.
+ */
+ private void setHashValue(Object obj) {
+ if (obj != null) {
+ hashValue = System.identityHashCode(obj);
+ } else {
+ hashValue = 0;
+ }
+ }
+
+ /**
+ * Always return the "identity" hash code of the original referent.
+ */
+ public int hashCode() {
+ return hashValue;
+ }
+
+ /**
+ * Return true if "obj" is this identical WeakRef object, or, if the
+ * contained reference has not been cleared, if "obj" is another WeakRef
+ * object with the identical non-null referent. Otherwise, return false.
+ */
+ public boolean equals(Object obj) {
+ if (obj instanceof WeakRef) {
+ if (obj == this)
+ return true;
+
+ Object referent = get();
+ return (referent != null) && (referent == ((WeakRef) obj).get());
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java b/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java
new file mode 100644
index 000000000..3d91ab0d1
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/CGIHandler.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+import java.net.*;
+import java.util.Hashtable;
+
+/**
+ * CGIClientException is thrown when an error is detected
+ * in a client's request.
+ */
+class CGIClientException extends Exception {
+
+ public CGIClientException(String s) {
+ super(s);
+ }
+}
+
+/**
+ * CGIServerException is thrown when an error occurs here on the server.
+ */
+class CGIServerException extends Exception {
+
+ public CGIServerException(String s) {
+ super(s);
+ }
+}
+
+/**
+ * CGICommandHandler is the interface to an object that handles a
+ * particular supported command.
+ */
+interface CGICommandHandler {
+
+ /**
+ * Return the string form of the command
+ * to be recognized in the query string.
+ */
+ public String getName();
+
+ /**
+ * Execute the command with the given string as parameter.
+ */
+ public void execute(String param) throws CGIClientException, CGIServerException;
+}
+
+/**
+ * The CGIHandler class contains methods for executing as a CGI program.
+ * The main function interprets the query string as a command of the form
+ * "<command>=<parameters>".
+ *
+ * This class depends on the CGI 1.0 environment variables being set as
+ * properties of the same name in this Java VM.
+ *
+ * All data and methods of this class are static because they are specific
+ * to this particular CGI process.
+ */
+public final class CGIHandler {
+
+ /* get CGI parameters that we need */
+ static int ContentLength;
+ static String QueryString;
+ static String RequestMethod;
+ static String ServerName;
+ static int ServerPort;
+
+ static {
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction() {
+ public Object run() {
+ ContentLength =
+ Integer.getInteger("CONTENT_LENGTH", 0).intValue();
+ QueryString = System.getProperty("QUERY_STRING", "");
+ RequestMethod = System.getProperty("REQUEST_METHOD", "");
+ ServerName = System.getProperty("SERVER_NAME", "");
+ ServerPort = Integer.getInteger("SERVER_PORT", 0).intValue();
+ return null;
+ }
+ });
+ }
+
+ /* list of handlers for supported commands */
+ private static CGICommandHandler commands[] = {
+ new CGIForwardCommand(),
+ new CGIGethostnameCommand(),
+ new CGIPingCommand(),
+ new CGITryHostnameCommand()
+ };
+
+ /* construct table mapping command strings to handlers */
+ private static Hashtable commandLookup;
+ static {
+ commandLookup = new Hashtable();
+ for (int i = 0; i < commands.length; ++ i)
+ commandLookup.put(commands[i].getName(), commands[i]);
+ }
+
+ /* prevent instantiation of this class */
+ private CGIHandler() {}
+
+ /**
+ * Execute command given in query string on URL. The string before
+ * the first '=' is interpreted as the command name, and the string
+ * after the first '=' is the parameters to the command.
+ */
+ public static void main(String args[])
+ {
+ try {
+ String command, param;
+ int delim = QueryString.indexOf("=");
+ if (delim == -1) {
+ command = QueryString;
+ param = "";
+ }
+ else {
+ command = QueryString.substring(0, delim);
+ param = QueryString.substring(delim + 1);
+ }
+ CGICommandHandler handler =
+ (CGICommandHandler) commandLookup.get(command);
+ if (handler != null)
+ try {
+ handler.execute(param);
+ } catch (CGIClientException e) {
+ returnClientError(e.getMessage());
+ } catch (CGIServerException e) {
+ returnServerError(e.getMessage());
+ }
+ else
+ returnClientError("invalid command: " + command);
+ } catch (Exception e) {
+ returnServerError("internal error: " + e.getMessage());
+ }
+ System.exit(0);
+ }
+
+ /**
+ * Return an HTML error message indicating there was error in
+ * the client's request.
+ */
+ private static void returnClientError(String message)
+ {
+ System.out.println("Status: 400 Bad Request: " + message);
+ System.out.println("Content-type: text/html");
+ System.out.println("");
+ System.out.println("<HTML>" +
+ "<HEAD><TITLE>Java RMI Client Error" +
+ "</TITLE></HEAD>" +
+ "<BODY>");
+ System.out.println("<H1>Java RMI Client Error</H1>");
+ System.out.println("");
+ System.out.println(message);
+ System.out.println("</BODY></HTML>");
+ System.exit(1);
+ }
+
+ /**
+ * Return an HTML error message indicating an error occurred
+ * here on the server.
+ */
+ private static void returnServerError(String message)
+ {
+ System.out.println("Status: 500 Server Error: " + message);
+ System.out.println("Content-type: text/html");
+ System.out.println("");
+ System.out.println("<HTML>" +
+ "<HEAD><TITLE>Java RMI Server Error" +
+ "</TITLE></HEAD>" +
+ "<BODY>");
+ System.out.println("<H1>Java RMI Server Error</H1>");
+ System.out.println("");
+ System.out.println(message);
+ System.out.println("</BODY></HTML>");
+ System.exit(1);
+ }
+}
+
+/**
+ * "forward" command: Forward request body to local port on the server,
+ * and send reponse back to client.
+ */
+final class CGIForwardCommand implements CGICommandHandler {
+
+ public String getName() {
+ return "forward";
+ }
+
+ public void execute(String param) throws CGIClientException, CGIServerException
+ {
+ if (!CGIHandler.RequestMethod.equals("POST"))
+ throw new CGIClientException("can only forward POST requests");
+
+ int port;
+ try {
+ port = Integer.parseInt(param);
+ } catch (NumberFormatException e) {
+ throw new CGIClientException("invalid port number: " + param);
+ }
+ if (port <= 0 || port > 0xFFFF)
+ throw new CGIClientException("invalid port: " + port);
+ if (port < 1024)
+ throw new CGIClientException("permission denied for port: " +
+ port);
+
+ byte buffer[];
+ Socket socket;
+ try {
+ socket = new Socket(InetAddress.getLocalHost(), port);
+ } catch (IOException e) {
+ throw new CGIServerException("could not connect to local port");
+ }
+
+ /*
+ * read client's request body
+ */
+ DataInputStream clientIn = new DataInputStream(System.in);
+ buffer = new byte[CGIHandler.ContentLength];
+ try {
+ clientIn.readFully(buffer);
+ } catch (EOFException e) {
+ throw new CGIClientException("unexpected EOF reading request body");
+ } catch (IOException e) {
+ throw new CGIClientException("error reading request body");
+ }
+
+ /*
+ * send to local server in HTTP
+ */
+ try {
+ DataOutputStream socketOut =
+ new DataOutputStream(socket.getOutputStream());
+ socketOut.writeBytes("POST / HTTP/1.0\r\n");
+ socketOut.writeBytes("Content-length: " +
+ CGIHandler.ContentLength + "\r\n\r\n");
+ socketOut.write(buffer);
+ socketOut.flush();
+ } catch (IOException e) {
+ throw new CGIServerException("error writing to server");
+ }
+
+ /*
+ * read response
+ */
+ DataInputStream socketIn;
+ try {
+ socketIn = new DataInputStream(socket.getInputStream());
+ } catch (IOException e) {
+ throw new CGIServerException("error reading from server");
+ }
+ String key = "Content-length:".toLowerCase();
+ boolean contentLengthFound = false;
+ String line;
+ int responseContentLength = -1;
+ do {
+ try {
+ line = socketIn.readLine();
+ } catch (IOException e) {
+ throw new CGIServerException("error reading from server");
+ }
+ if (line == null)
+ throw new CGIServerException(
+ "unexpected EOF reading server response");
+
+ if (line.toLowerCase().startsWith(key)) {
+ if (contentLengthFound)
+ ; // what would we want to do in this case??
+ responseContentLength =
+ Integer.parseInt(line.substring(key.length()).trim());
+ contentLengthFound = true;
+ }
+ } while ((line.length() != 0) &&
+ (line.charAt(0) != '\r') && (line.charAt(0) != '\n'));
+
+ if (!contentLengthFound || responseContentLength < 0)
+ throw new CGIServerException(
+ "missing or invalid content length in server response");
+ buffer = new byte[responseContentLength];
+ try {
+ socketIn.readFully(buffer);
+ } catch (EOFException e) {
+ throw new CGIServerException(
+ "unexpected EOF reading server response");
+ } catch (IOException e) {
+ throw new CGIServerException("error reading from server");
+ }
+
+ /*
+ * send response back to client
+ */
+ System.out.println("Status: 200 OK");
+ System.out.println("Content-type: application/octet-stream");
+ System.out.println("");
+ try {
+ System.out.write(buffer);
+ } catch (IOException e) {
+ throw new CGIServerException("error writing response");
+ }
+ System.out.flush();
+ }
+}
+
+/**
+ * "gethostname" command: Return the host name of the server as the
+ * response body
+ */
+final class CGIGethostnameCommand implements CGICommandHandler {
+
+ public String getName() {
+ return "gethostname";
+ }
+
+ public void execute(String param)
+ {
+ System.out.println("Status: 200 OK");
+ System.out.println("Content-type: application/octet-stream");
+ System.out.println("Content-length: " +
+ CGIHandler.ServerName.length());
+ System.out.println("");
+ System.out.print(CGIHandler.ServerName);
+ System.out.flush();
+ }
+}
+
+/**
+ * "ping" command: Return an OK status to indicate that connection
+ * was successful.
+ */
+final class CGIPingCommand implements CGICommandHandler {
+
+ public String getName() {
+ return "ping";
+ }
+
+ public void execute(String param)
+ {
+ System.out.println("Status: 200 OK");
+ System.out.println("Content-type: application/octet-stream");
+ System.out.println("Content-length: 0");
+ System.out.println("");
+ }
+}
+
+/**
+ * "tryhostname" command: Return a human readable message describing
+ * what host name is available to local Java VMs.
+ */
+final class CGITryHostnameCommand implements CGICommandHandler {
+
+ public String getName() {
+ return "tryhostname";
+ }
+
+ public void execute(String param)
+ {
+ System.out.println("Status: 200 OK");
+ System.out.println("Content-type: text/html");
+ System.out.println("");
+ System.out.println("<HTML>" +
+ "<HEAD><TITLE>Java RMI Server Hostname Info" +
+ "</TITLE></HEAD>" +
+ "<BODY>");
+ System.out.println("<H1>Java RMI Server Hostname Info</H1>");
+ System.out.println("<H2>Local host name available to Java VM:</H2>");
+ System.out.print("<P>InetAddress.getLocalHost().getHostName()");
+ try {
+ String localHostName = InetAddress.getLocalHost().getHostName();
+
+ System.out.println(" = " + localHostName);
+ } catch (UnknownHostException e) {
+ System.out.println(" threw java.net.UnknownHostException");
+ }
+
+ System.out.println("<H2>Server host information obtained through CGI interface from HTTP server:</H2>");
+ System.out.println("<P>SERVER_NAME = " + CGIHandler.ServerName);
+ System.out.println("<P>SERVER_PORT = " + CGIHandler.ServerPort);
+ System.out.println("</BODY></HTML>");
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java
new file mode 100644
index 000000000..6c168c57a
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpAwareServerSocket.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import sun.rmi.runtime.Log;
+
+/**
+ * The HttpAwareServerSocket class extends the java.net.ServerSocket
+ * class. It behaves like a ServerSocket, except that if
+ * the first four bytes of an accepted socket are the letters "POST",
+ * then it returns an HttpReceiveSocket instead of a java.net.Socket.
+ * This means that the accept method blocks until four bytes have been
+ * read from the new socket's input stream.
+ */
+class HttpAwareServerSocket extends ServerSocket {
+
+ /**
+ * Create a server socket on a specified port.
+ * @param port the port
+ * @exception IOException IO error when opening the socket.
+ */
+ public HttpAwareServerSocket(int port) throws IOException
+ {
+ super(port);
+ }
+
+ /**
+ * Create a server socket, bind it to the specified local port
+ * and listen to it. You can connect to an annonymous port by
+ * specifying the port number to be 0. <i>backlog</i> specifies
+ * how many connection requests the system will queue up while waiting
+ * for the ServerSocket to execute accept().
+ * @param port the specified port
+ * @param backlog the number of queued connect requests pending accept
+ */
+ public HttpAwareServerSocket(int port, int backlog) throws IOException
+ {
+ super(port, backlog);
+ }
+
+ /**
+ * Accept a connection. This method will block until the connection
+ * is made and four bytes can be read from the input stream.
+ * If the first four bytes are "POST", then an HttpReceiveSocket is
+ * returned, which will handle the HTTP protocol wrapping.
+ * Otherwise, a WrappedSocket is returned. The input stream will be
+ * reset to the beginning of the transmission.
+ * In either case, a BufferedInputStream will already be on top of
+ * the underlying socket's input stream.
+ * @exception IOException IO error when waiting for the connection.
+ */
+ public Socket accept() throws IOException
+ {
+ Socket socket = super.accept();
+ BufferedInputStream in =
+ new BufferedInputStream(socket.getInputStream());
+
+ RMIMasterSocketFactory.proxyLog.log(Log.BRIEF,
+ "socket accepted (checking for POST)");
+
+ in.mark(4);
+ boolean isHttp = (in.read() == 'P') &&
+ (in.read() == 'O') &&
+ (in.read() == 'S') &&
+ (in.read() == 'T');
+ in.reset();
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.BRIEF,
+ (isHttp ? "POST found, HTTP socket returned" :
+ "POST not found, direct socket returned"));
+ }
+
+ if (isHttp)
+ return new HttpReceiveSocket(socket, in, null);
+ else
+ return new WrappedSocket(socket, in, null);
+ }
+
+ /**
+ * Return the implementation address and implementation port of
+ * the HttpAwareServerSocket as a String.
+ */
+ public String toString()
+ {
+ return "HttpAware" + super.toString();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java
new file mode 100644
index 000000000..229a2cebd
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpInputStream.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+
+import sun.rmi.runtime.Log;
+
+/**
+ * The HttpInputStream class assists the HttpSendSocket and HttpReceiveSocket
+ * classes by filtering out the header for the message as well as any
+ * data after its proper content length.
+ */
+class HttpInputStream extends FilterInputStream {
+
+ /** bytes remaining to be read from proper content of message */
+ protected int bytesLeft;
+
+ /** bytes remaining to be read at time of last mark */
+ protected int bytesLeftAtMark;
+
+ /**
+ * Create new filter on a given input stream.
+ * @param in the InputStream to filter from
+ */
+ public HttpInputStream(InputStream in) throws IOException
+ {
+ super(in);
+
+ if (in.markSupported())
+ in.mark(0); // prevent resetting back to old marks
+
+ // pull out header, looking for content length
+
+ DataInputStream dis = new DataInputStream(in);
+ String key = "Content-length:".toLowerCase();
+ boolean contentLengthFound = false;
+ String line;
+ do {
+ line = dis.readLine();
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "received header line: \"" + line + "\"");
+ }
+
+ if (line == null)
+ throw new EOFException();
+
+ if (line.toLowerCase().startsWith(key)) {
+ if (contentLengthFound)
+ ; // what would we want to do in this case??
+ bytesLeft =
+ Integer.parseInt(line.substring(key.length()).trim());
+ contentLengthFound = true;
+ }
+
+ // The idea here is to go past the first blank line.
+ // Some DataInputStream.readLine() documentation specifies that
+ // it does include the line-terminating character(s) in the
+ // returned string, but it actually doesn't, so we'll cover
+ // all cases here...
+ } while ((line.length() != 0) &&
+ (line.charAt(0) != '\r') && (line.charAt(0) != '\n'));
+
+ if (!contentLengthFound || bytesLeft < 0) {
+ // This really shouldn't happen, but if it does, shoud we fail??
+ // For now, just give up and let a whole lot of bytes through...
+ bytesLeft = Integer.MAX_VALUE;
+ }
+ bytesLeftAtMark = bytesLeft;
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "content length: " + bytesLeft);
+ }
+ }
+
+ /**
+ * Returns the number of bytes that can be read with blocking.
+ * Make sure that this does not exceed the number of bytes remaining
+ * in the proper content of the message.
+ */
+ public int available() throws IOException
+ {
+ int bytesAvailable = in.available();
+ if (bytesAvailable > bytesLeft)
+ bytesAvailable = bytesLeft;
+
+ return bytesAvailable;
+ }
+
+ /**
+ * Read a byte of data from the stream. Make sure that one is available
+ * from the proper content of the message, else -1 is returned to
+ * indicate to the user that the end of the stream has been reached.
+ */
+ public int read() throws IOException
+ {
+ if (bytesLeft > 0) {
+ int data = in.read();
+ if (data != -1)
+ -- bytesLeft;
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "received byte: '" +
+ ((data & 0x7F) < ' ' ? " " : String.valueOf((char) data)) +
+ "' " + data);
+ }
+
+ return data;
+ }
+ else {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "read past content length");
+
+ return -1;
+ }
+ }
+
+ public int read(byte b[], int off, int len) throws IOException
+ {
+ if (bytesLeft == 0 && len > 0) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "read past content length");
+
+ return -1;
+ }
+ if (len > bytesLeft)
+ len = bytesLeft;
+ int bytesRead = in.read(b, off, len);
+ bytesLeft -= bytesRead;
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "read " + bytesRead + " bytes, " + bytesLeft + " remaining");
+ }
+
+ return bytesRead;
+ }
+
+ /**
+ * Mark the current position in the stream (for future calls to reset).
+ * Remember where we are within the proper content of the message, so
+ * that a reset method call can recreate our state properly.
+ * @param readlimit how many bytes can be read before mark becomes invalid
+ */
+ public void mark(int readlimit)
+ {
+ in.mark(readlimit);
+ if (in.markSupported())
+ bytesLeftAtMark = bytesLeft;
+ }
+
+ /**
+ * Repositions the stream to the last marked position. Make sure to
+ * adjust our position within the proper content accordingly.
+ */
+ public void reset() throws IOException
+ {
+ in.reset();
+ bytesLeft = bytesLeftAtMark;
+ }
+
+ /**
+ * Skips bytes of the stream. Make sure to adjust our
+ * position within the proper content accordingly.
+ * @param n number of bytes to be skipped
+ */
+ public long skip(long n) throws IOException
+ {
+ if (n > bytesLeft)
+ n = bytesLeft;
+ long bytesSkipped = in.skip(n);
+ bytesLeft -= bytesSkipped;
+ return bytesSkipped;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java
new file mode 100644
index 000000000..f7336b432
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpOutputStream.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+
+/**
+ * The HttpOutputStream class assists the HttpSendSocket and HttpReceiveSocket
+ * classes by providing an output stream that buffers its entire input until
+ * closed, and then it sends the complete transmission prefixed by the end of
+ * an HTTP header that specifies the content length.
+ */
+class HttpOutputStream extends ByteArrayOutputStream {
+
+ /** the output stream to send response to */
+ protected OutputStream out;
+
+ /** true if HTTP response has been sent */
+ boolean responseSent = false;
+
+ /**
+ * Begin buffering new HTTP response to be sent to a given stream.
+ * @param out the OutputStream to send response to
+ */
+ public HttpOutputStream(OutputStream out) {
+ super();
+ this.out = out;
+ }
+
+ /**
+ * On close, send HTTP-packaged response.
+ */
+ public synchronized void close() throws IOException {
+ if (!responseSent) {
+ /*
+ * If response would have zero content length, then make it
+ * have some arbitrary data so that certain clients will not
+ * fail because the "document contains no data".
+ */
+ if (size() == 0)
+ write(emptyData);
+
+ DataOutputStream dos = new DataOutputStream(out);
+ dos.writeBytes("Content-type: application/octet-stream\r\n");
+ dos.writeBytes("Content-length: " + size() + "\r\n");
+ dos.writeBytes("\r\n");
+ writeTo(dos);
+ dos.flush();
+ // Do not close the underlying stream here, because that would
+ // close the underlying socket and prevent reading a response.
+ reset(); // reset byte array
+ responseSent = true;
+ }
+ }
+
+ /** data to send if the response would otherwise be empty */
+ private static byte[] emptyData = { 0 };
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java
new file mode 100644
index 000000000..5fc1ddb2b
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpReceiveSocket.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.InetAddress;
+
+/**
+ * The HttpReceiveSocket class extends the WrappedSocket class
+ * by removing the HTTP protocol packaging from the input stream and
+ * formatting the output stream as an HTTP response.
+ *
+ * NOTES:
+ *
+ * The output stream must be explicitly closed for the output to be
+ * sent, since the HttpResponseOutputStream needs to buffer the entire
+ * transmission to be able to fill in the content-length field of
+ * the HTTP header. Closing this socket will do this.
+ *
+ * The constructor blocks until the HTTP protocol header
+ * is received. This could be fixed, but I don't think it should be a
+ * problem because this object would not be created unless the
+ * HttpAwareServerSocket has detected the beginning of the header
+ * anyway, so the rest should be there.
+ *
+ * This socket can only be used to process one POST and reply to it.
+ * Another message would be received on a newly accepted socket anyway.
+ */
+public class HttpReceiveSocket extends WrappedSocket implements RMISocketInfo {
+
+ /** true if the HTTP header has pushed through the output stream yet */
+ private boolean headerSent = false;
+
+ /**
+ * Layer on top of a pre-existing Socket object, and use specified
+ * input and output streams.
+ * @param socket the pre-existing socket to use
+ * @param in the InputStream to use for this socket (can be null)
+ * @param out the OutputStream to use for this socket (can be null)
+ */
+ public HttpReceiveSocket(Socket socket, InputStream in, OutputStream out)
+ throws IOException
+ {
+ super(socket, in, out);
+
+ this.in = new HttpInputStream(in != null ? in :
+ socket.getInputStream());
+ this.out = (out != null ? out :
+ socket.getOutputStream());
+ }
+
+ /**
+ * Indicate that this socket is not reusable.
+ */
+ public boolean isReusable()
+ {
+ return false;
+ }
+
+ /**
+ * Get the address to which this socket is connected. "null" is always
+ * returned (to indicate an unknown address) because the originating
+ * host's IP address cannot be reliably determined: both because the
+ * request probably went through a proxy server, and because if it was
+ * delivered by a local forwarder (CGI script or servlet), we do NOT
+ * want it to appear as if the call is coming from the local host (in
+ * case the remote object makes access control decisions based on the
+ * "client host" of a remote call; see bugid 4399040).
+ */
+ public InetAddress getInetAddress() {
+ return null;
+ }
+
+ /**
+ * Get an OutputStream for this socket.
+ */
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (!headerSent) { // could this be done in constructor??
+ DataOutputStream dos = new DataOutputStream(out);
+ dos.writeBytes("HTTP/1.0 200 OK\r\n");
+ dos.flush();
+ headerSent = true;
+ out = new HttpOutputStream(out);
+ }
+ return out;
+ }
+
+ /**
+ * Close the socket.
+ */
+ public synchronized void close() throws IOException
+ {
+ getOutputStream().close(); // make sure response is sent
+ socket.close();
+ }
+
+ /**
+ * Return string representation of the socket.
+ */
+ public String toString()
+ {
+ return "HttpReceive" + socket.toString();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java
new file mode 100644
index 000000000..48cba46cd
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendInputStream.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+
+/**
+ * The HttpSendInputStream class is used by the HttpSendSocket class as
+ * a layer on the top of the InputStream it returns so that it can be
+ * notified of attempts to read from it. This allows the HttpSendSocket
+ * to know when it should push across its output message.
+ */
+class HttpSendInputStream extends FilterInputStream {
+
+ /** the HttpSendSocket object that is providing this stream */
+ HttpSendSocket owner;
+
+ /**
+ * Create new filter on a given input stream.
+ * @param in the InputStream to filter from
+ * @param owner the HttpSendSocket that is providing this stream
+ */
+ public HttpSendInputStream(InputStream in, HttpSendSocket owner)
+ throws IOException
+ {
+ super(in);
+
+ this.owner = owner;
+ }
+
+ /**
+ * Mark this stream as inactive for its owner socket, so the next time
+ * a read is attempted, the owner will be notified and a new underlying
+ * input stream obtained.
+ */
+ public void deactivate()
+ {
+ in = null;
+ }
+
+ /**
+ * Read a byte of data from the stream.
+ */
+ public int read() throws IOException
+ {
+ if (in == null)
+ in = owner.readNotify();
+ return in.read();
+ }
+
+ /**
+ * Read into an array of bytes.
+ * @param b the buffer into which the data is to be read
+ * @param off the start offset of the data
+ * @param len the maximum number of bytes to read
+ */
+ public int read(byte b[], int off, int len) throws IOException
+ {
+ if (len == 0)
+ return 0;
+ if (in == null)
+ in = owner.readNotify();
+ return in.read(b, off, len);
+ }
+
+ /**
+ * Skip bytes of input.
+ * @param n the number of bytes to be skipped
+ */
+ public long skip(long n) throws IOException
+ {
+ if (n == 0)
+ return 0;
+ if (in == null)
+ in = owner.readNotify();
+ return in.skip(n);
+ }
+
+ /**
+ * Return the number of bytes that can be read without blocking.
+ */
+ public int available() throws IOException
+ {
+ if (in == null)
+ in = owner.readNotify();
+ return in.available();
+ }
+
+ /**
+ * Close the stream.
+ */
+ public void close() throws IOException
+ {
+ owner.close();
+ }
+
+ /**
+ * Mark the current position in the stream.
+ * @param readlimit how many bytes can be read before mark becomes invalid
+ */
+ public synchronized void mark(int readlimit)
+ {
+ if (in == null) {
+ try {
+ in = owner.readNotify();
+ }
+ catch (IOException e) {
+ return;
+ }
+ }
+ in.mark(readlimit);
+ }
+
+ /**
+ * Reposition the stream to the last marked position.
+ */
+ public synchronized void reset() throws IOException
+ {
+ if (in == null)
+ in = owner.readNotify();
+ in.reset();
+ }
+
+ /**
+ * Return true if this stream type supports mark/reset.
+ */
+ public boolean markSupported()
+ {
+ if (in == null) {
+ try {
+ in = owner.readNotify();
+ }
+ catch (IOException e) {
+ return false;
+ }
+ }
+ return in.markSupported();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java
new file mode 100644
index 000000000..b699cdd73
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendOutputStream.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+
+/**
+ * The HttpSendOutputStream class is used by the HttpSendSocket class as
+ * a layer on the top of the OutputStream it returns so that it can be
+ * notified of attempts to write to it. This allows the HttpSendSocket
+ * to know when it should construct a new message.
+ */
+class HttpSendOutputStream extends FilterOutputStream {
+
+ /** the HttpSendSocket object that is providing this stream */
+ HttpSendSocket owner;
+
+ /**
+ * Create new filter on a given output stream.
+ * @param out the OutputStream to filter from
+ * @param owner the HttpSendSocket that is providing this stream
+ */
+ public HttpSendOutputStream(OutputStream out, HttpSendSocket owner)
+ throws IOException
+ {
+ super(out);
+
+ this.owner = owner;
+ }
+
+ /**
+ * Mark this stream as inactive for its owner socket, so the next time
+ * a write is attempted, the owner will be notified and a new underlying
+ * output stream obtained.
+ */
+ public void deactivate()
+ {
+ out = null;
+ }
+
+ /**
+ * Write a byte of data to the stream.
+ */
+ public void write(int b) throws IOException
+ {
+ if (out == null)
+ out = owner.writeNotify();
+ out.write(b);
+ }
+
+ /**
+ * Write a subarray of bytes.
+ * @param b the buffer from which the data is to be written
+ * @param off the start offset of the data
+ * @param len the number of bytes to be written
+ */
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ if (len == 0)
+ return;
+ if (out == null)
+ out = owner.writeNotify();
+ out.write(b, off, len);
+ }
+
+ /**
+ * Flush the stream.
+ */
+ public void flush() throws IOException
+ {
+ if (out != null)
+ out.flush();
+ }
+
+ /**
+ * Close the stream.
+ */
+ public void close() throws IOException
+ {
+ flush();
+ owner.close();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java b/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java
new file mode 100644
index 000000000..7498c9bfd
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/HttpSendSocket.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+import java.net.*;
+
+import sun.rmi.runtime.Log;
+
+/**
+ * The HttpSendSocket class extends the java.net.Socket class
+ * by enclosing the data output stream in, then extracting the input
+ * stream from, an HTTP protocol transmission.
+ *
+ * NOTES:
+ *
+ * Since the length of the output request must be known before the
+ * HTTP header can be completed, all of the output is buffered by
+ * an HttpOutputStream object until either an attempt is made to
+ * read from this socket, or the socket is explicitly closed.
+ *
+ * On the first read attempt to read from this socket, the buffered
+ * output is sent to the destination as the body of an HTTP POST
+ * request. All reads will then acquire data from the body of
+ * the response. A subsequent attempt to write to this socket will
+ * throw an IOException.
+ */
+class HttpSendSocket extends Socket implements RMISocketInfo {
+
+ /** the host to connect to */
+ protected String host;
+
+ /** the port to connect to */
+ protected int port;
+
+ /** the URL to forward through */
+ protected URL url;
+
+ /** the object managing this connection through the URL */
+ protected URLConnection conn = null;
+
+ /** internal input stream for this socket */
+ protected InputStream in = null;
+
+ /** internal output stream for this socket */
+ protected OutputStream out = null;
+
+ /** the notifying input stream returned to users */
+ protected HttpSendInputStream inNotifier;
+
+ /** the notifying output stream returned to users */
+ protected HttpSendOutputStream outNotifier;
+
+ /**
+ * Line separator string. This is the value of the line.separator
+ * property at the moment that the socket was created.
+ */
+ private String lineSeparator =
+ (String) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("line.separator"));
+
+ /**
+ * Create a stream socket and connect it to the specified port on
+ * the specified host.
+ * @param host the host
+ * @param port the port
+ */
+ public HttpSendSocket(String host, int port, URL url) throws IOException
+ {
+ super((SocketImpl)null); // no underlying SocketImpl for this object
+
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.VERBOSE)) {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "host = " + host + ", port = " + port + ", url = " + url);
+ }
+
+ this.host = host;
+ this.port = port;
+ this.url = url;
+
+ inNotifier = new HttpSendInputStream(null, this);
+ outNotifier = new HttpSendOutputStream(writeNotify(), this);
+ }
+
+ /**
+ * Create a stream socket and connect it to the specified port on
+ * the specified host.
+ * @param host the host
+ * @param port the port
+ */
+ public HttpSendSocket(String host, int port) throws IOException
+ {
+ this(host, port, new URL("http", host, port, "/"));
+ }
+
+ /**
+ * Create a stream socket and connect it to the specified address on
+ * the specified port.
+ * @param address the address
+ * @param port the port
+ */
+ public HttpSendSocket(InetAddress address, int port) throws IOException
+ {
+ this(address.getHostName(), port);
+ }
+
+ /**
+ * Indicate that this socket is not reusable.
+ */
+ public boolean isReusable()
+ {
+ return false;
+ }
+
+ /**
+ * Create a new socket connection to host (or proxy), and prepare to
+ * send HTTP transmission.
+ */
+ public synchronized OutputStream writeNotify() throws IOException
+ {
+ if (conn != null) {
+ throw new IOException("attempt to write on HttpSendSocket after " +
+ "request has been sent");
+ }
+
+ conn = url.openConnection();
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ conn.setRequestProperty("Content-type", "application/octet-stream");
+
+ inNotifier.deactivate();
+ in = null;
+
+ return out = conn.getOutputStream();
+ }
+
+ /**
+ * Send HTTP output transmission and prepare to receive response.
+ */
+ public synchronized InputStream readNotify() throws IOException
+ {
+ RMIMasterSocketFactory.proxyLog.log(Log.VERBOSE,
+ "sending request and activating input stream");
+
+ outNotifier.deactivate();
+ out.close();
+ out = null;
+
+ try {
+ in = conn.getInputStream();
+ } catch (IOException e) {
+ RMIMasterSocketFactory.proxyLog.log(Log.BRIEF,
+ "failed to get input stream, exception: ", e);
+
+ throw new IOException("HTTP request failed");
+ }
+
+ /*
+ * If an HTTP error response is returned, sometimes an IOException
+ * is thrown, which is handled above, and other times it isn't, and
+ * the error response body will be available for reading.
+ * As a safety net to catch any such unexpected HTTP behavior, we
+ * verify that the content type of the response is what the
+ * HttpOutputStream generates: "application/octet-stream".
+ * (Servers' error responses will generally be "text/html".)
+ * Any error response body is printed to the log.
+ */
+ String contentType = conn.getContentType();
+ if (contentType == null ||
+ !conn.getContentType().equals("application/octet-stream"))
+ {
+ if (RMIMasterSocketFactory.proxyLog.isLoggable(Log.BRIEF)) {
+ String message;
+ if (contentType == null) {
+ message = "missing content type in response" +
+ lineSeparator;
+ } else {
+ message = "invalid content type in response: " +
+ contentType + lineSeparator;
+ }
+
+ message += "HttpSendSocket.readNotify: response body: ";
+ try {
+ DataInputStream din = new DataInputStream(in);
+ String line;
+ while ((line = din.readLine()) != null)
+ message += line + lineSeparator;
+ } catch (IOException e) {
+ }
+ RMIMasterSocketFactory.proxyLog.log(Log.BRIEF, message);
+ }
+
+ throw new IOException("HTTP request failed");
+ }
+
+ return in;
+ }
+
+ /**
+ * Get the address to which the socket is connected.
+ */
+ public InetAddress getInetAddress()
+ {
+ try {
+ return InetAddress.getByName(host);
+ } catch (UnknownHostException e) {
+ return null; // null if couldn't resolve destination host
+ }
+ }
+
+ /**
+ * Get the local address to which the socket is bound.
+ */
+ public InetAddress getLocalAddress()
+ {
+ try {
+ return InetAddress.getLocalHost();
+ } catch (UnknownHostException e) {
+ return null; // null if couldn't determine local host
+ }
+ }
+
+ /**
+ * Get the remote port to which the socket is connected.
+ */
+ public int getPort()
+ {
+ return port;
+ }
+
+ /**
+ * Get the local port to which the socket is connected.
+ */
+ public int getLocalPort()
+ {
+ return -1; // request not applicable to this socket type
+ }
+
+ /**
+ * Get an InputStream for this socket.
+ */
+ public InputStream getInputStream() throws IOException
+ {
+ return inNotifier;
+ }
+
+ /**
+ * Get an OutputStream for this socket.
+ */
+ public OutputStream getOutputStream() throws IOException
+ {
+ return outNotifier;
+ }
+
+ /**
+ * Enable/disable TCP_NODELAY.
+ * This operation has no effect for an HttpSendSocket.
+ */
+ public void setTcpNoDelay(boolean on) throws SocketException
+ {
+ }
+
+ /**
+ * Retrieve whether TCP_NODELAY is enabled.
+ */
+ public boolean getTcpNoDelay() throws SocketException
+ {
+ return false; // imply option is disabled
+ }
+
+ /**
+ * Enable/disable SO_LINGER with the specified linger time.
+ * This operation has no effect for an HttpSendSocket.
+ */
+ public void setSoLinger(boolean on, int val) throws SocketException
+ {
+ }
+
+ /**
+ * Retrive setting for SO_LINGER.
+ */
+ public int getSoLinger() throws SocketException
+ {
+ return -1; // imply option is disabled
+ }
+
+ /**
+ * Enable/disable SO_TIMEOUT with the specified timeout
+ * This operation has no effect for an HttpSendSocket.
+ */
+ public synchronized void setSoTimeout(int timeout) throws SocketException
+ {
+ }
+
+ /**
+ * Retrive setting for SO_TIMEOUT.
+ */
+ public synchronized int getSoTimeout() throws SocketException
+ {
+ return 0; // imply option is disabled
+ }
+
+ /**
+ * Close the socket.
+ */
+ public synchronized void close() throws IOException
+ {
+ if (out != null) // push out transmission if not done
+ out.close();
+ }
+
+ /**
+ * Return string representation of this pseudo-socket.
+ */
+ public String toString()
+ {
+ return "HttpSendSocket[host=" + host +
+ ",port=" + port +
+ ",url=" + url + "]";
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java
new file mode 100644
index 000000000..e687f7618
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/RMIDirectSocketFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.ServerSocket;
+import java.rmi.server.RMISocketFactory;
+
+/**
+ * RMIDirectSocketFactory creates a direct socket connection to the
+ * specified port on the specified host.
+ */
+public class RMIDirectSocketFactory extends RMISocketFactory {
+
+ public Socket createSocket(String host, int port) throws IOException
+ {
+ return new Socket(host, port);
+ }
+
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ return new ServerSocket(port);
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java
new file mode 100644
index 000000000..b9ed241f2
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToCGISocketFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.ServerSocket;
+import java.net.URL;
+import java.rmi.server.RMISocketFactory;
+
+/**
+ * RMIHttpToCGISocketFactory creates a socket connection to the
+ * specified host that is comminicated within an HTTP request,
+ * forwarded through the default firewall proxy, to the target host's
+ * normal HTTP server, to a CGI program which forwards the request to
+ * the actual specified port on the socket.
+ */
+public class RMIHttpToCGISocketFactory extends RMISocketFactory {
+
+ public Socket createSocket(String host, int port)
+ throws IOException
+ {
+ return new HttpSendSocket(host, port,
+ new URL("http", host,
+ "/cgi-bin/java-rmi.cgi" +
+ "?forward=" + port));
+ }
+
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ return new HttpAwareServerSocket(port);
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java
new file mode 100644
index 000000000..7a6e7a9d5
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/RMIHttpToPortSocketFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.ServerSocket;
+import java.net.URL;
+import java.rmi.server.RMISocketFactory;
+
+/**
+ * RMIHttpToPortSocketFactory creates a socket connection to the
+ * specified host that is communicated within an HTTP request,
+ * forwarded through the default firewall proxy, directly to the
+ * specified port.
+ */
+public class RMIHttpToPortSocketFactory extends RMISocketFactory {
+
+ public Socket createSocket(String host, int port)
+ throws IOException
+ {
+ return new HttpSendSocket(host, port,
+ new URL("http", host, port, "/"));
+ }
+
+ public ServerSocket createServerSocket(int port)
+ throws IOException
+ {
+ return new HttpAwareServerSocket(port);
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java b/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java
new file mode 100644
index 000000000..16735959e
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/RMIMasterSocketFactory.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 1996-2002 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import java.rmi.server.LogStream;
+import java.rmi.server.RMISocketFactory;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetLongAction;
+
+/**
+ * RMIMasterSocketFactory attempts to create a socket connection to the
+ * specified host using successively less efficient mechanisms
+ * until one succeeds. If the host is successfully connected to,
+ * the factory for the successful mechanism is stored in an internal
+ * hash table keyed by the host name, so that future attempts to
+ * connect to the same host will automatically use the same
+ * mechanism.
+ */
+public class RMIMasterSocketFactory extends RMISocketFactory {
+
+ /** "proxy" package log level */
+ static int logLevel = LogStream.parseLevel(getLogLevel());
+
+ private static String getLogLevel() {
+ return (String) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("sun.rmi.transport.proxy.logLevel"));
+ }
+
+ /* proxy package log */
+ static final Log proxyLog =
+ Log.getLog("sun.rmi.transport.tcp.proxy",
+ "transport", RMIMasterSocketFactory.logLevel);
+
+ /** timeout for attemping direct socket connections */
+ private static long connectTimeout = getConnectTimeout();
+
+ private static long getConnectTimeout() {
+ return ((Long) java.security.AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.transport.proxy.connectTimeout",
+ 15000))).longValue(); // default: 15 seconds
+ }
+
+ /** whether to fallback to HTTP on general connect failures */
+ private static final boolean eagerHttpFallback = ((Boolean)
+ java.security.AccessController.doPrivileged(new GetBooleanAction(
+ "sun.rmi.transport.proxy.eagerHttpFallback"))).booleanValue();
+
+ /** table of hosts successfully connected to and the factory used */
+ private Hashtable successTable = new Hashtable();
+
+ /** maximum number of hosts to remember successful connection to */
+ private static final int MaxRememberedHosts = 64;
+
+ /** list of the hosts in successTable in initial connection order */
+ private Vector hostList = new Vector(MaxRememberedHosts);
+
+ /** default factory to initally use for direct socket connection */
+ protected RMISocketFactory initialFactory = new RMIDirectSocketFactory();
+
+ /** ordered list of factories to try as alternate connection
+ * mechanisms if a direct socket connections fails */
+ protected Vector altFactoryList;
+
+ /**
+ * Create a RMIMasterSocketFactory object. Establish order of
+ * connection mechanisms to attempt on createSocket, if a direct
+ * socket connection fails.
+ */
+ public RMIMasterSocketFactory() {
+ altFactoryList = new Vector(2);
+ boolean setFactories = false;
+
+ try {
+ String proxyHost;
+ proxyHost = (String) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("http.proxyHost"));
+
+ if (proxyHost == null)
+ proxyHost=(String)java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("proxyHost"));
+
+ Boolean tmp = (Boolean)java.security.AccessController.doPrivileged(
+ new sun.security.action.GetBooleanAction("java.rmi.server.disableHttp"));
+
+ if (!tmp.booleanValue() &&
+ (proxyHost != null && proxyHost.length() > 0)) {
+ setFactories = true;
+ }
+ } catch (Exception e) {
+ // unable to obtain the properties, so assume default behavior.
+ setFactories = true;
+ }
+
+ if (setFactories) {
+ altFactoryList.addElement(new RMIHttpToPortSocketFactory());
+ altFactoryList.addElement(new RMIHttpToCGISocketFactory());
+ }
+ }
+
+ /**
+ * Create a new client socket. If we remember connecting to this host
+ * successfully before, then use the same factory again. Otherwise,
+ * try using a direct socket connection and then the alternate factories
+ * in the order specified in altFactoryList.
+ */
+ public Socket createSocket(String host, int port)
+ throws IOException
+ {
+ if (proxyLog.isLoggable(Log.BRIEF)) {
+ proxyLog.log(Log.BRIEF, "host: " + host + ", port: " + port);
+ }
+
+ /*
+ * If we don't have any alternate factories to consult, short circuit
+ * the fallback procedure and delegate to the initial factory.
+ */
+ if (altFactoryList.size() == 0) {
+ return initialFactory.createSocket(host, port);
+ }
+
+ RMISocketFactory factory;
+
+ /*
+ * If we remember successfully connecting to this host before,
+ * use the same factory.
+ */
+ factory = (RMISocketFactory) successTable.get(host);
+ if (factory != null) {
+ if (proxyLog.isLoggable(Log.BRIEF)) {
+ proxyLog.log(Log.BRIEF,
+ "previously successful factory found: " + factory);
+ }
+ return factory.createSocket(host, port);
+ }
+
+ /*
+ * Next, try a direct socket connection. Open socket in another
+ * thread and only wait for specified timeout, in case the socket
+ * would otherwise spend minutes trying an unreachable host.
+ */
+ Socket initialSocket = null;
+ Socket fallbackSocket = null;
+ final AsyncConnector connector =
+ new AsyncConnector(initialFactory, host, port,
+ AccessController.getContext());
+ // connection must be attempted with
+ // this thread's access control context
+ IOException initialFailure = null;
+
+ try {
+ synchronized (connector) {
+
+ Thread t = (Thread)
+ java.security.AccessController.doPrivileged(
+ new NewThreadAction(connector, "AsyncConnector",
+ true));
+ t.start();
+
+ try {
+ long now = System.currentTimeMillis();
+ long deadline = now + connectTimeout;
+ do {
+ connector.wait(deadline - now);
+ initialSocket = checkConnector(connector);
+ if (initialSocket != null)
+ break;
+ now = System.currentTimeMillis();
+ } while (now < deadline);
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(
+ "interrupted while waiting for connector");
+ }
+ }
+
+ // assume no route to host (for now) if no connection yet
+ if (initialSocket == null)
+ throw new NoRouteToHostException(
+ "connect timed out: " + host);
+
+ proxyLog.log(Log.BRIEF, "direct socket connection successful");
+
+ return initialSocket;
+
+ } catch (UnknownHostException e) {
+ initialFailure = e;
+ } catch (NoRouteToHostException e) {
+ initialFailure = e;
+ } catch (SocketException e) {
+ if (eagerHttpFallback) {
+ initialFailure = e;
+ } else {
+ throw e;
+ }
+ } finally {
+ if (initialFailure != null) {
+
+ if (proxyLog.isLoggable(Log.BRIEF)) {
+ proxyLog.log(Log.BRIEF,
+ "direct socket connection failed: ", initialFailure);
+ }
+
+ // Finally, try any alternate connection mechanisms.
+ for (int i = 0; i < altFactoryList.size(); ++ i) {
+ factory = (RMISocketFactory) altFactoryList.elementAt(i);
+ try {
+ if (proxyLog.isLoggable(Log.BRIEF)) {
+ proxyLog.log(Log.BRIEF,
+ "trying with factory: " + factory);
+ }
+
+ // For HTTP connections, the output (POST request) must
+ // be sent before we verify a successful connection.
+ // So, sacrifice a socket for the sake of testing...
+ // The following sequence should verify a successful
+ // HTTP connection if no IOException is thrown.
+ Socket testSocket = factory.createSocket(host, port);
+ InputStream in = testSocket.getInputStream();
+ int b = in.read(); // probably -1 for EOF...
+ testSocket.close();
+ } catch (IOException ex) {
+ if (proxyLog.isLoggable(Log.BRIEF)) {
+ proxyLog.log(Log.BRIEF, "factory failed: ", ex);
+ }
+
+ continue;
+ }
+ proxyLog.log(Log.BRIEF, "factory succeeded");
+
+ // factory succeeded, open new socket for caller's use
+ try {
+ fallbackSocket = factory.createSocket(host, port);
+ } catch (IOException ex) { // if it fails 2nd time,
+ } // just give up
+ break;
+ }
+ }
+ }
+
+ synchronized (successTable) {
+ try {
+ // check once again to see if direct connection succeeded
+ synchronized (connector) {
+ initialSocket = checkConnector(connector);
+ }
+ if (initialSocket != null) {
+ // if we had made another one as well, clean it up...
+ if (fallbackSocket != null)
+ fallbackSocket.close();
+ return initialSocket;
+ }
+ // if connector ever does get socket, it won't be used
+ connector.notUsed();
+ } catch (UnknownHostException e) {
+ initialFailure = e;
+ } catch (NoRouteToHostException e) {
+ initialFailure = e;
+ } catch (SocketException e) {
+ if (eagerHttpFallback) {
+ initialFailure = e;
+ } else {
+ throw e;
+ }
+ }
+ // if we had found an alternate mechanism, go and use it
+ if (fallbackSocket != null) {
+ // remember this successful host/factory pair
+ rememberFactory(host, factory);
+ return fallbackSocket;
+ }
+ throw initialFailure;
+ }
+ }
+
+ /**
+ * Remember a successful factory for connecting to host.
+ * Currently, excess hosts are removed from the remembered list
+ * using a Least Recently Created strategy.
+ */
+ void rememberFactory(String host, RMISocketFactory factory) {
+ synchronized (successTable) {
+ while (hostList.size() >= MaxRememberedHosts) {
+ successTable.remove(hostList.elementAt(0));
+ hostList.removeElementAt(0);
+ }
+ hostList.addElement(host);
+ successTable.put(host, factory);
+ }
+ }
+
+ /**
+ * Check if an AsyncConnector succeeded. If not, return socket
+ * given to fall back to.
+ */
+ Socket checkConnector(AsyncConnector connector)
+ throws IOException
+ {
+ Exception e = connector.getException();
+ if (e != null) {
+ e.fillInStackTrace();
+ /*
+ * The AsyncConnector implementation guaranteed that the exception
+ * will be either an IOException or a RuntimeException, and we can
+ * only throw one of those, so convince that compiler that it must
+ * be one of those.
+ */
+ if (e instanceof IOException) {
+ throw (IOException) e;
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new Error("internal error: " +
+ "unexpected checked exception: " + e.toString());
+ }
+ }
+ return connector.getSocket();
+ }
+
+ /**
+ * Create a new server socket.
+ */
+ public ServerSocket createServerSocket(int port) throws IOException {
+ //return new HttpAwareServerSocket(port);
+ return initialFactory.createServerSocket(port);
+ }
+
+
+ /**
+ * AsyncConnector is used by RMIMasterSocketFactory to attempt socket
+ * connections on a separate thread. This allows RMIMasterSocketFactory
+ * to control how long it will wait for the connection to succeed.
+ */
+ private class AsyncConnector implements Runnable {
+
+ /** what factory to use to attempt connection */
+ private RMISocketFactory factory;
+
+ /** the host to connect to */
+ private String host;
+
+ /** the port to connect to */
+ private int port;
+
+ /** access control context to attempt connection within */
+ private AccessControlContext acc;
+
+ /** exception that occurred during connection, if any */
+ private Exception exception = null;
+
+ /** the connected socket, if successful */
+ private Socket socket = null;
+
+ /** socket should be closed after created, if ever */
+ private boolean cleanUp = false;
+
+ /**
+ * Create a new asynchronous connector object.
+ */
+ AsyncConnector(RMISocketFactory factory, String host, int port,
+ AccessControlContext acc)
+ {
+ this.factory = factory;
+ this.host = host;
+ this.port = port;
+ this.acc = acc;
+ SecurityManager security = System.getSecurityManager();
+ if (security != null) {
+ security.checkConnect(host, port);
+ }
+ }
+
+ /**
+ * Attempt socket connection in separate thread. If successful,
+ * notify master waiting,
+ */
+ public void run() {
+ try {
+ /*
+ * Using the privileges of the thread that wants to make the
+ * connection is tempting, but it will fail with applets with
+ * the current applet security manager because the applet
+ * network connection policy is not captured in the permission
+ * framework of the access control context we have.
+ *
+ * java.security.AccessController.beginPrivileged(acc);
+ */
+ try {
+ Socket temp = factory.createSocket(host, port);
+ synchronized (this) {
+ socket = temp;
+ notify();
+ }
+ rememberFactory(host, factory);
+ synchronized (this) {
+ if (cleanUp)
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ } catch (Exception e) {
+ /*
+ * Note that the only exceptions which could actually have
+ * occurred here are IOException or RuntimeException.
+ */
+ synchronized (this) {
+ exception = e;
+ notify();
+ }
+ }
+ } finally {
+ /*
+ * See above comments for matching beginPrivileged() call that
+ * is also commented out.
+ *
+ * java.security.AccessController.endPrivileged();
+ */
+ }
+ }
+
+ /**
+ * Get exception that occurred during connection attempt, if any.
+ * In the current implementation, this is guaranteed to be either
+ * an IOException or a RuntimeException.
+ */
+ private synchronized Exception getException() {
+ return exception;
+ }
+
+ /**
+ * Get successful socket, if any.
+ */
+ private synchronized Socket getSocket() {
+ return socket;
+ }
+
+ /**
+ * Note that this connector's socket, if ever successfully created,
+ * will not be used, so it should be cleaned up quickly
+ */
+ synchronized void notUsed() {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ cleanUp = true;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java b/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java
new file mode 100644
index 000000000..e09cfcd28
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/RMISocketInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+/**
+ * RMISocketInfo is an interface that extensions of the java.net.Socket
+ * class may use to provide more information on its capabilities.
+ */
+public interface RMISocketInfo {
+
+ /**
+ * Return true if this socket can be used for more than one
+ * RMI call. If a socket does not implement this interface, then
+ * it is assumed to be reusable.
+ */
+ public boolean isReusable();
+}
diff --git a/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java b/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java
new file mode 100644
index 000000000..7671a5122
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/proxy/WrappedSocket.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.proxy;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * The WrappedSocket class provides a general wrapper for providing an
+ * extended implementation of java.net.Socket that can be attached to
+ * a pre-existing Socket object. WrappedSocket itself provides a
+ * constructor for specifying alternate input or output streams to be
+ * returned than those of the underlying Socket.
+ */
+class WrappedSocket extends Socket {
+
+ /** the underlying concrete socket */
+ protected Socket socket;
+
+ /** the input stream to return for socket */
+ protected InputStream in = null;
+
+ /** the output stream to return for socket */
+ protected OutputStream out = null;
+
+ /**
+ * Layer on top of a pre-existing Socket object, and use specified
+ * input and output streams. This allows the creator of the
+ * underlying socket to peek at the beginning of the input with a
+ * BufferedInputStream and determine which kind of socket
+ * to create, without consuming the input.
+ * @param socket the pre-existing socket to use
+ * @param in the InputStream to return to users (can be null)
+ * @param out the OutputStream to return to users (can be null)
+ */
+ public WrappedSocket(Socket socket, InputStream in, OutputStream out)
+ throws IOException
+ {
+ super((java.net.SocketImpl)null); // no underlying SocketImpl for this object
+ this.socket = socket;
+ this.in = in;
+ this.out = out;
+ }
+
+ /**
+ * Get the address to which the socket is connected.
+ */
+ public InetAddress getInetAddress()
+ {
+ return socket.getInetAddress();
+ }
+
+ /**
+ * Get the local address to which the socket is bound.
+ */
+ public InetAddress getLocalAddress() {
+ return socket.getLocalAddress();
+ }
+
+ /**
+ * Get the remote port to which the socket is connected.
+ */
+ public int getPort()
+ {
+ return socket.getPort();
+ }
+
+ /**
+ * Get the local port to which the socket is connected.
+ */
+ public int getLocalPort()
+ {
+ return socket.getLocalPort();
+ }
+
+ /**
+ * Get an InputStream for this socket.
+ */
+ public InputStream getInputStream() throws IOException
+ {
+ if (in == null)
+ in = socket.getInputStream();
+ return in;
+ }
+
+ /**
+ * Get an OutputStream for this socket.
+ */
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (out == null)
+ out = socket.getOutputStream();
+ return out;
+ }
+
+ /**
+ * Enable/disable TCP_NODELAY.
+ */
+ public void setTcpNoDelay(boolean on) throws SocketException
+ {
+ socket.setTcpNoDelay(on);
+ }
+
+ /**
+ * Retrieve whether TCP_NODELAY is enabled.
+ */
+ public boolean getTcpNoDelay() throws SocketException
+ {
+ return socket.getTcpNoDelay();
+ }
+
+ /**
+ * Enable/disable SO_LINGER with the specified linger time.
+ */
+ public void setSoLinger(boolean on, int val) throws SocketException
+ {
+ socket.setSoLinger(on, val);
+ }
+
+ /**
+ * Retrive setting for SO_LINGER.
+ */
+ public int getSoLinger() throws SocketException
+ {
+ return socket.getSoLinger();
+ }
+
+ /**
+ * Enable/disable SO_TIMEOUT with the specified timeout
+ */
+ public synchronized void setSoTimeout(int timeout) throws SocketException
+ {
+ socket.setSoTimeout(timeout);
+ }
+
+ /**
+ * Retrive setting for SO_TIMEOUT.
+ */
+ public synchronized int getSoTimeout() throws SocketException
+ {
+ return socket.getSoTimeout();
+ }
+
+ /**
+ * Close the socket.
+ */
+ public synchronized void close() throws IOException
+ {
+ socket.close();
+ }
+
+ /**
+ * Return string representation of the socket.
+ */
+ public String toString()
+ {
+ return "Wrapped" + socket.toString();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java b/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java
new file mode 100644
index 000000000..f756cd34f
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/ConnectionMultiplexer.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright 1996-2003 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.*;
+import java.util.*;
+import java.rmi.server.LogStream;
+
+import sun.rmi.runtime.Log;
+
+/**
+ * ConnectionMultiplexer manages the transparent multiplexing of
+ * multiple virtual connections from one endpoint to another through
+ * one given real connection to that endpoint. The input and output
+ * streams for the the underlying real connection must be supplied.
+ * A callback object is also supplied to be informed of new virtual
+ * connections opened by the remote endpoint. After creation, the
+ * run() method must be called in a thread created for demultiplexing
+ * the connections. The openConnection() method is called to
+ * initiate a virtual connection from this endpoint.
+ *
+ * @author Peter Jones
+ */
+final class ConnectionMultiplexer {
+
+ /** "multiplex" log level */
+ static int logLevel = LogStream.parseLevel(getLogLevel());
+
+ private static String getLogLevel() {
+ return (String) java.security.AccessController.doPrivileged(
+ new sun.security.action.GetPropertyAction("sun.rmi.transport.tcp.multiplex.logLevel"));
+ }
+
+ /* multiplex system log */
+ static final Log multiplexLog =
+ Log.getLog("sun.rmi.transport.tcp.multiplex",
+ "multiplex", ConnectionMultiplexer.logLevel);
+
+ /** multiplexing protocol operation codes */
+ private final static int OPEN = 0xE1;
+ private final static int CLOSE = 0xE2;
+ private final static int CLOSEACK = 0xE3;
+ private final static int REQUEST = 0xE4;
+ private final static int TRANSMIT = 0xE5;
+
+ /** object to notify for new connections from remote endpoint */
+ private TCPChannel channel;
+
+ /** input stream for underlying single connection */
+ private InputStream in;
+
+ /** output stream for underlying single connection */
+ private OutputStream out;
+
+ /** true if underlying connection originated from this endpoint
+ (used for generating unique connection IDs) */
+ private boolean orig;
+
+ /** layered stream for reading formatted data from underlying connection */
+ private DataInputStream dataIn;
+
+ /** layered stream for writing formatted data to underlying connection */
+ private DataOutputStream dataOut;
+
+ /** table holding currently open connection IDs and related info */
+ private Hashtable connectionTable = new Hashtable(7);
+
+ /** number of currently open connections */
+ private int numConnections = 0;
+
+ /** maximum allowed open connections */
+ private final static int maxConnections = 256;
+
+ /** ID of last connection opened */
+ private int lastID = 0x1001;
+
+ /** true if this mechanism is still alive */
+ private boolean alive = true;
+
+ /**
+ * Create a new ConnectionMultiplexer using the given underlying
+ * input/output stream pair. The run method must be called
+ * (possibly on a new thread) to handle the demultiplexing.
+ * @param channel object to notify when new connection is received
+ * @param in input stream of underlying connection
+ * @param out output stream of underlying connection
+ * @param orig true if this endpoint intiated the underlying
+ * connection (needs to be set differently at both ends)
+ */
+ public ConnectionMultiplexer(
+ TCPChannel channel,
+ InputStream in,
+ OutputStream out,
+ boolean orig)
+ {
+ this.channel = channel;
+ this.in = in;
+ this.out = out;
+ this.orig = orig;
+
+ dataIn = new DataInputStream(in);
+ dataOut = new DataOutputStream(out);
+ }
+
+ /**
+ * Process multiplexing protocol received from underlying connection.
+ */
+ public void run() throws IOException
+ {
+ try {
+ int op, id, length;
+ Integer idObj;
+ MultiplexConnectionInfo info;
+
+ while (true) {
+
+ // read next op code from remote endpoint
+ op = dataIn.readUnsignedByte();
+ switch (op) {
+
+ // remote endpoint initiating new connection
+ case OPEN:
+ id = dataIn.readUnsignedShort();
+
+ if (multiplexLog.isLoggable(Log.VERBOSE)) {
+ multiplexLog.log(Log.VERBOSE, "operation OPEN " + id);
+ }
+
+ idObj = new Integer(id);
+ info =
+ (MultiplexConnectionInfo) connectionTable.get(idObj);
+ if (info != null)
+ throw new IOException(
+ "OPEN: Connection ID already exists");
+ info = new MultiplexConnectionInfo(id);
+ info.in = new MultiplexInputStream(this, info, 2048);
+ info.out = new MultiplexOutputStream(this, info, 2048);
+ synchronized (connectionTable) {
+ connectionTable.put(idObj, info);
+ ++ numConnections;
+ }
+ sun.rmi.transport.Connection conn;
+ conn = new TCPConnection(channel, info.in, info.out);
+ channel.acceptMultiplexConnection(conn);
+ break;
+
+ // remote endpoint closing connection
+ case CLOSE:
+ id = dataIn.readUnsignedShort();
+
+ if (multiplexLog.isLoggable(Log.VERBOSE)) {
+ multiplexLog.log(Log.VERBOSE, "operation CLOSE " + id);
+ }
+
+ idObj = new Integer(id);
+ info =
+ (MultiplexConnectionInfo) connectionTable.get(idObj);
+ if (info == null)
+ throw new IOException(
+ "CLOSE: Invalid connection ID");
+ info.in.disconnect();
+ info.out.disconnect();
+ if (!info.closed)
+ sendCloseAck(info);
+ synchronized (connectionTable) {
+ connectionTable.remove(idObj);
+ -- numConnections;
+ }
+ break;
+
+ // remote endpoint acknowledging close of connection
+ case CLOSEACK:
+ id = dataIn.readUnsignedShort();
+
+ if (multiplexLog.isLoggable(Log.VERBOSE)) {
+ multiplexLog.log(Log.VERBOSE,
+ "operation CLOSEACK " + id);
+ }
+
+ idObj = new Integer(id);
+ info =
+ (MultiplexConnectionInfo) connectionTable.get(idObj);
+ if (info == null)
+ throw new IOException(
+ "CLOSEACK: Invalid connection ID");
+ if (!info.closed)
+ throw new IOException(
+ "CLOSEACK: Connection not closed");
+ info.in.disconnect();
+ info.out.disconnect();
+ synchronized (connectionTable) {
+ connectionTable.remove(idObj);
+ -- numConnections;
+ }
+ break;
+
+ // remote endpoint declaring additional bytes receivable
+ case REQUEST:
+ id = dataIn.readUnsignedShort();
+ idObj = new Integer(id);
+ info =
+ (MultiplexConnectionInfo) connectionTable.get(idObj);
+ if (info == null)
+ throw new IOException(
+ "REQUEST: Invalid connection ID");
+ length = dataIn.readInt();
+
+ if (multiplexLog.isLoggable(Log.VERBOSE)) {
+ multiplexLog.log(Log.VERBOSE,
+ "operation REQUEST " + id + ": " + length);
+ }
+
+ info.out.request(length);
+ break;
+
+ // remote endpoint transmitting data packet
+ case TRANSMIT:
+ id = dataIn.readUnsignedShort();
+ idObj = new Integer(id);
+ info =
+ (MultiplexConnectionInfo) connectionTable.get(idObj);
+ if (info == null)
+ throw new IOException("SEND: Invalid connection ID");
+ length = dataIn.readInt();
+
+ if (multiplexLog.isLoggable(Log.VERBOSE)) {
+ multiplexLog.log(Log.VERBOSE,
+ "operation TRANSMIT " + id + ": " + length);
+ }
+
+ info.in.receive(length, dataIn);
+ break;
+
+ default:
+ throw new IOException("Invalid operation: " +
+ Integer.toHexString(op));
+ }
+ }
+ } finally {
+ shutDown();
+ }
+ }
+
+ /**
+ * Initiate a new multiplexed connection through the underlying
+ * connection.
+ */
+ public synchronized TCPConnection openConnection() throws IOException
+ {
+ // generate ID that should not be already used
+ // If all possible 32768 IDs are used,
+ // this method will block searching for a new ID forever.
+ int id;
+ Integer idObj;
+ do {
+ lastID = (++ lastID) & 0x7FFF;
+ id = lastID;
+
+ // The orig flag (copied to the high bit of the ID) is used
+ // to have two distinct ranges to choose IDs from for the
+ // two endpoints.
+ if (orig)
+ id |= 0x8000;
+ idObj = new Integer(id);
+ } while (connectionTable.get(idObj) != null);
+
+ // create multiplexing streams and bookkeeping information
+ MultiplexConnectionInfo info = new MultiplexConnectionInfo(id);
+ info.in = new MultiplexInputStream(this, info, 2048);
+ info.out = new MultiplexOutputStream(this, info, 2048);
+
+ // add to connection table if multiplexer has not died
+ synchronized (connectionTable) {
+ if (!alive)
+ throw new IOException("Multiplexer connection dead");
+ if (numConnections >= maxConnections)
+ throw new IOException("Cannot exceed " + maxConnections +
+ " simultaneous multiplexed connections");
+ connectionTable.put(idObj, info);
+ ++ numConnections;
+ }
+
+ // inform remote endpoint of new connection
+ synchronized (dataOut) {
+ try {
+ dataOut.writeByte(OPEN);
+ dataOut.writeShort(id);
+ dataOut.flush();
+ } catch (IOException e) {
+ multiplexLog.log(Log.BRIEF, "exception: ", e);
+
+ shutDown();
+ throw e;
+ }
+ }
+
+ return new TCPConnection(channel, info.in, info.out);
+ }
+
+ /**
+ * Shut down all connections and clean up.
+ */
+ public void shutDown()
+ {
+ // inform all associated streams
+ synchronized (connectionTable) {
+ // return if multiplexer already officially dead
+ if (!alive)
+ return;
+ alive = false;
+
+ Enumeration enum_ = connectionTable.elements();
+ while (enum_.hasMoreElements()) {
+ MultiplexConnectionInfo info =
+ (MultiplexConnectionInfo) enum_.nextElement();
+ info.in.disconnect();
+ info.out.disconnect();
+ }
+ connectionTable.clear();
+ numConnections = 0;
+ }
+
+ // close underlying connection, if possible (and not already done)
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ try {
+ out.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Send request for more data on connection to remote endpoint.
+ * @param info connection information structure
+ * @param len number of more bytes that can be received
+ */
+ void sendRequest(MultiplexConnectionInfo info, int len) throws IOException
+ {
+ synchronized (dataOut) {
+ if (alive && !info.closed)
+ try {
+ dataOut.writeByte(REQUEST);
+ dataOut.writeShort(info.id);
+ dataOut.writeInt(len);
+ dataOut.flush();
+ } catch (IOException e) {
+ multiplexLog.log(Log.BRIEF, "exception: ", e);
+
+ shutDown();
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Send packet of requested data on connection to remote endpoint.
+ * @param info connection information structure
+ * @param buf array containg bytes to send
+ * @param off offset of first array index of packet
+ * @param len number of bytes in packet to send
+ */
+ void sendTransmit(MultiplexConnectionInfo info,
+ byte buf[], int off, int len) throws IOException
+ {
+ synchronized (dataOut) {
+ if (alive && !info.closed)
+ try {
+ dataOut.writeByte(TRANSMIT);
+ dataOut.writeShort(info.id);
+ dataOut.writeInt(len);
+ dataOut.write(buf, off, len);
+ dataOut.flush();
+ } catch (IOException e) {
+ multiplexLog.log(Log.BRIEF, "exception: ", e);
+
+ shutDown();
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Inform remote endpoint that connection has been closed.
+ * @param info connection information structure
+ */
+ void sendClose(MultiplexConnectionInfo info) throws IOException
+ {
+ info.out.disconnect();
+ synchronized (dataOut) {
+ if (alive && !info.closed)
+ try {
+ dataOut.writeByte(CLOSE);
+ dataOut.writeShort(info.id);
+ dataOut.flush();
+ info.closed = true;
+ } catch (IOException e) {
+ multiplexLog.log(Log.BRIEF, "exception: ", e);
+
+ shutDown();
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Acknowledge remote endpoint's closing of connection.
+ * @param info connection information structure
+ */
+ void sendCloseAck(MultiplexConnectionInfo info) throws IOException
+ {
+ synchronized (dataOut) {
+ if (alive && !info.closed)
+ try {
+ dataOut.writeByte(CLOSEACK);
+ dataOut.writeShort(info.id);
+ dataOut.flush();
+ info.closed = true;
+ } catch (IOException e) {
+ multiplexLog.log(Log.BRIEF, "exception: ", e);
+
+ shutDown();
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Shut down connection upon finalization.
+ */
+ protected void finalize() throws Throwable
+ {
+ super.finalize();
+ shutDown();
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java
new file mode 100644
index 000000000..3bc3efefa
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexConnectionInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+/**
+ * MultiplexConnectionInfo groups related information about a
+ * virtual connection managed by a ConnectionMultiplexer object.
+ *
+ * @author Peter Jones
+ */
+class MultiplexConnectionInfo {
+
+ /** integer that uniquely identifies this connection */
+ int id;
+
+ /** input stream for reading from connection */
+ MultiplexInputStream in = null;
+
+ /** output stream for writing to connection */
+ MultiplexOutputStream out = null;
+
+ /** true if this connection has been closed */
+ boolean closed = false;
+
+ /**
+ * Create information structure for given connection identifier.
+ * @param id connection identifier
+ */
+ MultiplexConnectionInfo(int id)
+ {
+ this.id = id;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java
new file mode 100644
index 000000000..1c302050b
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexInputStream.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 1996-1997 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.*;
+
+/**
+ * MultiplexInputStream manages receiving data over a connection managed
+ * by a ConnectionMultiplexer object. This object is responsible for
+ * requesting more bytes of data as space in its internal buffer becomes
+ * available.
+ *
+ * @author Peter Jones
+ */
+final class MultiplexInputStream extends InputStream {
+
+ /** object managing multiplexed connection */
+ private ConnectionMultiplexer manager;
+
+ /** information about the connection this is the input stream for */
+ private MultiplexConnectionInfo info;
+
+ /** input buffer */
+ private byte buffer[];
+
+ /** number of real data bytes present in buffer */
+ private int present = 0;
+
+ /** current position to read from in input buffer */
+ private int pos = 0;
+
+ /** pending number of bytes this stream has requested */
+ private int requested = 0;
+
+ /** true if this connection has been disconnected */
+ private boolean disconnected = false;
+
+ /**
+ * lock acquired to access shared variables:
+ * buffer, present, pos, requested, & disconnected
+ * WARNING: Any of the methods manager.send*() should not be
+ * invoked while this lock is held, since they could potentially
+ * block if the underlying connection's transport buffers are
+ * full, and the manager may need to acquire this lock to process
+ * and consume data coming over the underlying connection.
+ */
+ private Object lock = new Object();
+
+ /** level at which more data is requested when read past */
+ private int waterMark;
+
+ /** data structure for holding reads of one byte */
+ private byte temp[] = new byte[1];
+
+ /**
+ * Create a new MultiplexInputStream for the given manager.
+ * @param manager object that manages this connection
+ * @param info structure for connection this stream reads from
+ * @param bufferLength length of input buffer
+ */
+ MultiplexInputStream(
+ ConnectionMultiplexer manager,
+ MultiplexConnectionInfo info,
+ int bufferLength)
+ {
+ this.manager = manager;
+ this.info = info;
+
+ buffer = new byte[bufferLength];
+ waterMark = bufferLength / 2;
+ }
+
+ /**
+ * Read a byte from the connection.
+ */
+ public synchronized int read() throws IOException
+ {
+ int n = read(temp, 0, 1);
+ if (n != 1)
+ return -1;
+ return temp[0] & 0xFF;
+ }
+
+ /**
+ * Read a subarray of bytes from connection. This method blocks for
+ * at least one byte, and it returns the number of bytes actually read,
+ * or -1 if the end of the stream was detected.
+ * @param b array to read bytes into
+ * @param off offset of beginning of bytes to read into
+ * @param len number of bytes to read
+ */
+ public synchronized int read(byte b[], int off, int len) throws IOException
+ {
+ if (len <= 0)
+ return 0;
+
+ int moreSpace;
+ synchronized (lock) {
+ if (pos >= present)
+ pos = present = 0;
+ else if (pos >= waterMark) {
+ System.arraycopy(buffer, pos, buffer, 0, present - pos);
+ present -= pos;
+ pos = 0;
+ }
+ int freeSpace = buffer.length - present;
+ moreSpace = Math.max(freeSpace - requested, 0);
+ }
+ if (moreSpace > 0)
+ manager.sendRequest(info, moreSpace);
+ synchronized (lock) {
+ requested += moreSpace;
+ while ((pos >= present) && !disconnected) {
+ try {
+ lock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ if (disconnected && pos >= present)
+ return -1;
+
+ int available = present - pos;
+ if (len < available) {
+ System.arraycopy(buffer, pos, b, off, len);
+ pos += len;
+ return len;
+ }
+ else {
+ System.arraycopy(buffer, pos, b, off, available);
+ pos = present = 0;
+ // could send another request here, if len > available??
+ return available;
+ }
+ }
+ }
+
+ /**
+ * Return the number of bytes immediately available for reading.
+ */
+ public int available() throws IOException
+ {
+ synchronized (lock) {
+ return present - pos;
+ }
+ }
+
+ /**
+ * Close this connection.
+ */
+ public void close() throws IOException
+ {
+ manager.sendClose(info);
+ }
+
+ /**
+ * Receive bytes transmitted from connection at remote endpoint.
+ * @param length number of bytes transmitted
+ * @param in input stream with those bytes ready to be read
+ */
+ void receive(int length, DataInputStream in)
+ throws IOException
+ {
+ /* TO DO: Optimize so that data received from stream can be loaded
+ * directly into user's buffer if there is a pending read().
+ */
+ synchronized (lock) {
+ if ((pos > 0) && ((buffer.length - present) < length)) {
+ System.arraycopy(buffer, pos, buffer, 0, present - pos);
+ present -= pos;
+ pos = 0;
+ }
+ if ((buffer.length - present) < length)
+ throw new IOException("Receive buffer overflow");
+ in.readFully(buffer, present, length);
+ present += length;
+ requested -= length;
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * Disconnect this stream from all connection activity.
+ */
+ void disconnect()
+ {
+ synchronized (lock) {
+ disconnected = true;
+ lock.notifyAll();
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java b/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java
new file mode 100644
index 000000000..d7ef40837
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/MultiplexOutputStream.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 1996 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.*;
+
+/**
+ * MultiplexOutputStream manages sending data over a conection managed
+ * by a ConnectionMultiplexer object. Data written is buffered until the
+ * internal buffer is full or the flush() method is called, at which
+ * point it attempts to push a packet of bytes through to the remote
+ * endpoint. This will never push more bytes than the amount already
+ * requested by the remote endpoint (to prevent receive buffer from
+ * overflowing), so if the write() and flush() methods will block
+ * until their operation can complete if enough bytes cannot be
+ * pushed immediately.
+ *
+ * @author Peter Jones
+ */
+final class MultiplexOutputStream extends OutputStream {
+
+ /** object managing multiplexed connection */
+ private ConnectionMultiplexer manager;
+
+ /** information about the connection this is the output stream for */
+ private MultiplexConnectionInfo info;
+
+ /** output buffer */
+ private byte buffer[];
+
+ /** current position to write to in output buffer */
+ private int pos = 0;
+
+ /** pending number of bytes requested by remote endpoint */
+ private int requested = 0;
+
+ /** true if this connection has been disconnected */
+ private boolean disconnected = false;
+
+ /**
+ * lock acquired to access shared variables:
+ * requested & disconnected
+ * WARNING: Any of the methods manager.send*() should not be
+ * invoked while this lock is held, since they could potentially
+ * block if the underlying connection's transport buffers are
+ * full, and the manager may need to acquire this lock to process
+ * and consume data coming over the underlying connection.
+ */
+ private Object lock = new Object();
+
+ /**
+ * Create a new MultiplexOutputStream for the given manager.
+ * @param manager object that manages this connection
+ * @param info structure for connection this stream writes to
+ * @param bufferLength length of output buffer
+ */
+ MultiplexOutputStream(
+ ConnectionMultiplexer manager,
+ MultiplexConnectionInfo info,
+ int bufferLength)
+ {
+ this.manager = manager;
+ this.info = info;
+
+ buffer = new byte[bufferLength];
+ pos = 0;
+ }
+
+ /**
+ * Write a byte over connection.
+ * @param b byte of data to write
+ */
+ public synchronized void write(int b) throws IOException
+ {
+ while (pos >= buffer.length)
+ push();
+ buffer[pos ++] = (byte) b;
+ }
+
+ /**
+ * Write a subarray of bytes over connection.
+ * @param b array containing bytes to write
+ * @param off offset of beginning of bytes to write
+ * @param len number of bytes to write
+ */
+ public synchronized void write(byte b[], int off, int len)
+ throws IOException
+ {
+ if (len <= 0)
+ return;
+
+ // if enough free space in output buffer, just copy into there
+ int freeSpace = buffer.length - pos;
+ if (len <= freeSpace) {
+ System.arraycopy(b, off, buffer, pos, len);
+ pos += len;
+ return;
+ }
+
+ // else, flush buffer and send rest directly to avoid array copy
+ flush();
+ int local_requested;
+ while (true) {
+ synchronized (lock) {
+ while ((local_requested = requested) < 1 && !disconnected) {
+ try {
+ lock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ if (disconnected)
+ throw new IOException("Connection closed");
+ }
+
+ if (local_requested < len) {
+ manager.sendTransmit(info, b, off, local_requested);
+ off += local_requested;
+ len -= local_requested;
+ synchronized (lock) {
+ requested -= local_requested;
+ }
+ }
+ else {
+ manager.sendTransmit(info, b, off, len);
+ synchronized (lock) {
+ requested -= len;
+ }
+ // len = 0;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Guarantee that all data written to this stream has been pushed
+ * over and made available to the remote endpoint.
+ */
+ public synchronized void flush() throws IOException {
+ while (pos > 0)
+ push();
+ }
+
+ /**
+ * Close this connection.
+ */
+ public void close() throws IOException
+ {
+ manager.sendClose(info);
+ }
+
+ /**
+ * Take note of more bytes requested by conection at remote endpoint.
+ * @param num number of additional bytes requested
+ */
+ void request(int num)
+ {
+ synchronized (lock) {
+ requested += num;
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * Disconnect this stream from all connection activity.
+ */
+ void disconnect()
+ {
+ synchronized (lock) {
+ disconnected = true;
+ lock.notifyAll();
+ }
+ }
+
+ /**
+ * Push bytes in output buffer to connection at remote endpoint.
+ * This method blocks until at least one byte has been pushed across.
+ */
+ private void push() throws IOException
+ {
+ int local_requested;
+ synchronized (lock) {
+ while ((local_requested = requested) < 1 && !disconnected) {
+ try {
+ lock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ if (disconnected)
+ throw new IOException("Connection closed");
+ }
+
+ if (local_requested < pos) {
+ manager.sendTransmit(info, buffer, 0, local_requested);
+ System.arraycopy(buffer, local_requested,
+ buffer, 0, pos - local_requested);
+ pos -= local_requested;
+ synchronized (lock) {
+ requested -= local_requested;
+ }
+ }
+ else {
+ manager.sendTransmit(info, buffer, 0, pos);
+ synchronized (lock) {
+ requested -= pos;
+ }
+ pos = 0;
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java b/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java
new file mode 100644
index 000000000..f426012bf
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/TCPChannel.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.net.Socket;
+import java.rmi.ConnectIOException;
+import java.rmi.RemoteException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.WeakHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.runtime.RuntimeUtil;
+import sun.rmi.transport.Channel;
+import sun.rmi.transport.Connection;
+import sun.rmi.transport.Endpoint;
+import sun.rmi.transport.TransportConstants;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetLongAction;
+
+/**
+ * TCPChannel is the socket-based implementation of the RMI Channel
+ * abstraction.
+ *
+ * @author Ann Wollrath
+ */
+public class TCPChannel implements Channel {
+ /** endpoint for this channel */
+ private final TCPEndpoint ep;
+ /** transport for this channel */
+ private final TCPTransport tr;
+ /** list of cached connections */
+ private final List<TCPConnection> freeList =
+ new ArrayList<TCPConnection>();
+ /** frees cached connections that have expired (guarded by freeList) */
+ private Future<?> reaper = null;
+
+ /** using multiplexer (for bi-directional applet communication */
+ private boolean usingMultiplexer = false;
+ /** connection multiplexer, if used */
+ private ConnectionMultiplexer multiplexer = null;
+ /** connection acceptor (should be in TCPTransport) */
+ private ConnectionAcceptor acceptor;
+
+ /** most recently authorized AccessControlContext */
+ private AccessControlContext okContext;
+
+ /** cache of authorized AccessControlContexts */
+ private WeakHashMap<AccessControlContext,
+ Reference<AccessControlContext>> authcache;
+
+ /** the SecurityManager which authorized okContext and authcache */
+ private SecurityManager cacheSecurityManager = null;
+
+ /** client-side connection idle usage timeout */
+ private static final long idleTimeout = // default 15 seconds
+ AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.transport.connectionTimeout", 15000));
+
+ /** client-side connection handshake read timeout */
+ private static final int handshakeTimeout = // default 1 minute
+ AccessController.doPrivileged(
+ new GetIntegerAction("sun.rmi.transport.tcp.handshakeTimeout",
+ 60000));
+
+ /** client-side connection response read timeout (after handshake) */
+ private static final int responseTimeout = // default infinity
+ AccessController.doPrivileged(
+ new GetIntegerAction("sun.rmi.transport.tcp.responseTimeout", 0));
+
+ /** thread pool for scheduling delayed tasks */
+ private static final ScheduledExecutorService scheduler =
+ AccessController.doPrivileged(
+ new RuntimeUtil.GetInstanceAction()).getScheduler();
+
+ /**
+ * Create channel for endpoint.
+ */
+ TCPChannel(TCPTransport tr, TCPEndpoint ep) {
+ this.tr = tr;
+ this.ep = ep;
+ }
+
+ /**
+ * Return the endpoint for this channel.
+ */
+ public Endpoint getEndpoint() {
+ return ep;
+ }
+
+ /**
+ * Checks if the current caller has sufficient privilege to make
+ * a connection to the remote endpoint.
+ * @exception SecurityException if caller is not allowed to use this
+ * Channel.
+ */
+ private void checkConnectPermission() throws SecurityException {
+ SecurityManager security = System.getSecurityManager();
+ if (security == null)
+ return;
+
+ if (security != cacheSecurityManager) {
+ // The security manager changed: flush the cache
+ okContext = null;
+ authcache = new WeakHashMap<AccessControlContext,
+ Reference<AccessControlContext>>();
+ cacheSecurityManager = security;
+ }
+
+ AccessControlContext ctx = AccessController.getContext();
+
+ // If ctx is the same context as last time, or if it
+ // appears in the cache, bypass the checkConnect.
+ if (okContext == null ||
+ !(okContext.equals(ctx) || authcache.containsKey(ctx)))
+ {
+ security.checkConnect(ep.getHost(), ep.getPort());
+ authcache.put(ctx, new SoftReference<AccessControlContext>(ctx));
+ // A WeakHashMap is transformed into a SoftHashSet by making
+ // each value softly refer to its own key (Peter's idea).
+ }
+ okContext = ctx;
+ }
+
+ /**
+ * Supplies a connection to the endpoint of the address space
+ * for which this is a channel. The returned connection may
+ * be one retrieved from a cache of idle connections.
+ */
+ public Connection newConnection() throws RemoteException {
+ TCPConnection conn;
+
+ // loop until we find a free live connection (in which case
+ // we return) or until we run out of freelist (in which case
+ // the loop exits)
+ do {
+ conn = null;
+ // try to get a free connection
+ synchronized (freeList) {
+ int elementPos = freeList.size()-1;
+
+ if (elementPos >= 0) {
+ // If there is a security manager, make sure
+ // the caller is allowed to connect to the
+ // requested endpoint.
+ checkConnectPermission();
+ conn = freeList.get(elementPos);
+ freeList.remove(elementPos);
+ }
+ }
+
+ // at this point, conn is null iff the freelist is empty,
+ // and nonnull if a free connection of uncertain vitality
+ // has been found.
+
+ if (conn != null) {
+ // check to see if the connection has closed since last use
+ if (!conn.isDead()) {
+ TCPTransport.tcpLog.log(Log.BRIEF, "reuse connection");
+ return conn;
+ }
+
+ // conn is dead, and cannot be reused (reuse => false)
+ this.free(conn, false);
+ }
+ } while (conn != null);
+
+ // none free, so create a new connection
+ return (createConnection());
+ }
+
+ /**
+ * Create a new connection to the remote endpoint of this channel.
+ * The returned connection is new. The caller must already have
+ * passed a security checkConnect or equivalent.
+ */
+ private Connection createConnection() throws RemoteException {
+ Connection conn;
+
+ TCPTransport.tcpLog.log(Log.BRIEF, "create connection");
+
+ if (!usingMultiplexer) {
+ Socket sock = ep.newSocket();
+ conn = new TCPConnection(this, sock);
+
+ try {
+ DataOutputStream out =
+ new DataOutputStream(conn.getOutputStream());
+ writeTransportHeader(out);
+
+ // choose protocol (single op if not reusable socket)
+ if (!conn.isReusable()) {
+ out.writeByte(TransportConstants.SingleOpProtocol);
+ } else {
+ out.writeByte(TransportConstants.StreamProtocol);
+ out.flush();
+
+ /*
+ * Set socket read timeout to configured value for JRMP
+ * connection handshake; this also serves to guard against
+ * non-JRMP servers that do not respond (see 4322806).
+ */
+ int originalSoTimeout = 0;
+ try {
+ originalSoTimeout = sock.getSoTimeout();
+ sock.setSoTimeout(handshakeTimeout);
+ } catch (Exception e) {
+ // if we fail to set this, ignore and proceed anyway
+ }
+
+ DataInputStream in =
+ new DataInputStream(conn.getInputStream());
+ byte ack = in.readByte();
+ if (ack != TransportConstants.ProtocolAck) {
+ throw new ConnectIOException(
+ ack == TransportConstants.ProtocolNack ?
+ "JRMP StreamProtocol not supported by server" :
+ "non-JRMP server at remote endpoint");
+ }
+
+ String suggestedHost = in.readUTF();
+ int suggestedPort = in.readInt();
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "server suggested " + suggestedHost + ":" +
+ suggestedPort);
+ }
+
+ // set local host name, if unknown
+ TCPEndpoint.setLocalHost(suggestedHost);
+ // do NOT set the default port, because we don't
+ // know if we can't listen YET...
+
+ // write out default endpoint to match protocol
+ // (but it serves no purpose)
+ TCPEndpoint localEp =
+ TCPEndpoint.getLocalEndpoint(0, null, null);
+ out.writeUTF(localEp.getHost());
+ out.writeInt(localEp.getPort());
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE, "using " +
+ localEp.getHost() + ":" + localEp.getPort());
+ }
+
+ /*
+ * After JRMP handshake, set socket read timeout to value
+ * configured for the rest of the lifetime of the
+ * connection. NOTE: this timeout, if configured to a
+ * finite duration, places an upper bound on the time
+ * that a remote method call is permitted to execute.
+ */
+ try {
+ /*
+ * If socket factory had set a non-zero timeout on its
+ * own, then restore it instead of using the property-
+ * configured value.
+ */
+ sock.setSoTimeout((originalSoTimeout != 0 ?
+ originalSoTimeout :
+ responseTimeout));
+ } catch (Exception e) {
+ // if we fail to set this, ignore and proceed anyway
+ }
+
+ out.flush();
+ }
+ } catch (IOException e) {
+ if (e instanceof RemoteException)
+ throw (RemoteException) e;
+ else
+ throw new ConnectIOException(
+ "error during JRMP connection establishment", e);
+ }
+ } else {
+ try {
+ conn = multiplexer.openConnection();
+ } catch (IOException e) {
+ synchronized (this) {
+ usingMultiplexer = false;
+ multiplexer = null;
+ }
+ throw new ConnectIOException(
+ "error opening virtual connection " +
+ "over multiplexed connection", e);
+ }
+ }
+ return conn;
+ }
+
+ /**
+ * Free the connection generated by this channel.
+ * @param conn The connection
+ * @param reuse If true, the connection is in a state in which it
+ * can be reused for another method call.
+ */
+ public void free(Connection conn, boolean reuse) {
+ if (conn == null) return;
+
+ if (reuse && conn.isReusable()) {
+ long lastuse = System.currentTimeMillis();
+ TCPConnection tcpConnection = (TCPConnection) conn;
+
+ TCPTransport.tcpLog.log(Log.BRIEF, "reuse connection");
+
+ /*
+ * Cache connection; if reaper task for expired
+ * connections isn't scheduled, then schedule it.
+ */
+ synchronized (freeList) {
+ freeList.add(tcpConnection);
+ if (reaper == null) {
+ TCPTransport.tcpLog.log(Log.BRIEF, "create reaper");
+
+ reaper = scheduler.scheduleWithFixedDelay(
+ new Runnable() {
+ public void run() {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "wake up");
+ freeCachedConnections();
+ }
+ }, idleTimeout, idleTimeout, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ tcpConnection.setLastUseTime(lastuse);
+ tcpConnection.setExpiration(lastuse + idleTimeout);
+ } else {
+ TCPTransport.tcpLog.log(Log.BRIEF, "close connection");
+
+ try {
+ conn.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Send transport header over stream.
+ */
+ private void writeTransportHeader(DataOutputStream out)
+ throws RemoteException
+ {
+ try {
+ // write out transport header
+ DataOutputStream dataOut =
+ new DataOutputStream(out);
+ dataOut.writeInt(TransportConstants.Magic);
+ dataOut.writeShort(TransportConstants.Version);
+ } catch (IOException e) {
+ throw new ConnectIOException(
+ "error writing JRMP transport header", e);
+ }
+ }
+
+ /**
+ * Use given connection multiplexer object to obtain new connections
+ * through this channel.
+ */
+ synchronized void useMultiplexer(ConnectionMultiplexer newMultiplexer) {
+ // for now, always just use the last one given
+ multiplexer = newMultiplexer;
+
+ usingMultiplexer = true;
+ }
+
+ /**
+ * Accept a connection provided over a multiplexed channel.
+ */
+ void acceptMultiplexConnection(Connection conn) {
+ if (acceptor == null) {
+ acceptor = new ConnectionAcceptor(tr);
+ acceptor.startNewAcceptor();
+ }
+ acceptor.accept(conn);
+ }
+
+ /**
+ * Closes all the connections in the cache, whether timed out or not.
+ */
+ public void shedCache() {
+ // Build a list of connections, to avoid holding the freeList
+ // lock during (potentially long-running) close() calls.
+ Connection[] conn;
+ synchronized (freeList) {
+ conn = freeList.toArray(new Connection[freeList.size()]);
+ freeList.clear();
+ }
+
+ // Close all the connections that were free
+ for (int i = conn.length; --i >= 0; ) {
+ Connection c = conn[i];
+ conn[i] = null; // help gc
+ try {
+ c.close();
+ } catch (java.io.IOException e) {
+ // eat exception
+ }
+ }
+ }
+
+ private void freeCachedConnections() {
+ /*
+ * Remove each connection whose time out has expired.
+ */
+ synchronized (freeList) {
+ int size = freeList.size();
+
+ if (size > 0) {
+ long time = System.currentTimeMillis();
+ ListIterator<TCPConnection> iter = freeList.listIterator(size);
+
+ while (iter.hasPrevious()) {
+ TCPConnection conn = iter.previous();
+ if (conn.expired(time)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "connection timeout expired");
+
+ try {
+ conn.close();
+ } catch (java.io.IOException e) {
+ // eat exception
+ }
+ iter.remove();
+ }
+ }
+ }
+
+ if (freeList.isEmpty()) {
+ reaper.cancel(false);
+ reaper = null;
+ }
+ }
+ }
+}
+
+/**
+ * ConnectionAcceptor manages accepting new connections and giving them
+ * to TCPTransport's message handler on new threads.
+ *
+ * Since this object only needs to know which transport to give new
+ * connections to, it doesn't need to be per-channel as currently
+ * implemented.
+ */
+class ConnectionAcceptor implements Runnable {
+
+ /** transport that will handle message on accepted connections */
+ private TCPTransport transport;
+
+ /** queue of connections to be accepted */
+ private List<Connection> queue = new ArrayList<Connection>();
+
+ /** thread ID counter */
+ private static int threadNum = 0;
+
+ /**
+ * Create a new ConnectionAcceptor that will give connections
+ * to the specified transport on a new thread.
+ */
+ public ConnectionAcceptor(TCPTransport transport) {
+ this.transport = transport;
+ }
+
+ /**
+ * Start a new thread to accept connections.
+ */
+ public void startNewAcceptor() {
+ Thread t = AccessController.doPrivileged(
+ new NewThreadAction(ConnectionAcceptor.this,
+ "Multiplex Accept-" + ++ threadNum,
+ true));
+ t.start();
+ }
+
+ /**
+ * Add connection to queue of connections to be accepted.
+ */
+ public void accept(Connection conn) {
+ synchronized (queue) {
+ queue.add(conn);
+ queue.notify();
+ }
+ }
+
+ /**
+ * Give transport next accepted conection, when available.
+ */
+ public void run() {
+ Connection conn;
+
+ synchronized (queue) {
+ while (queue.size() == 0) {
+ try {
+ queue.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ startNewAcceptor();
+ conn = queue.remove(0);
+ }
+
+ transport.handleMessages(conn, true);
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java b/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java
new file mode 100644
index 000000000..ac1971590
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/TCPConnection.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 1996-2001 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.transport.tcp;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.rmi.*;
+import java.rmi.server.RMISocketFactory;
+import sun.rmi.runtime.Log;
+import sun.rmi.transport.*;
+import sun.rmi.transport.proxy.*;
+
+public class TCPConnection implements Connection {
+
+ private Socket socket;
+ private Channel channel;
+ private InputStream in = null;
+ private OutputStream out = null;
+ private long expiration = Long.MAX_VALUE;
+ private long lastuse = Long.MIN_VALUE;
+ private long roundtrip = 5; // round-trip time for ping
+
+ /**
+ * Constructor used for creating a connection to accept call
+ * (an input connection)
+ */
+ TCPConnection(TCPChannel ch, Socket s, InputStream in, OutputStream out)
+ {
+ socket = s;
+ channel = ch;
+ this.in = in;
+ this.out = out;
+ }
+
+ /**
+ * Constructor used by subclass when underlying input and output streams
+ * are already available.
+ */
+ TCPConnection(TCPChannel ch, InputStream in, OutputStream out)
+ {
+ this(ch, null, in, out);
+ }
+
+ /**
+ * Constructor used when socket is available, but not underlying
+ * streams.
+ */
+ TCPConnection(TCPChannel ch, Socket s)
+ {
+ this(ch, s, null, null);
+ }
+
+ /**
+ * Gets the output stream for this connection
+ */
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (out == null)
+ out = new BufferedOutputStream(socket.getOutputStream());
+ return out;
+ }
+
+ /**
+ * Release the output stream for this connection.
+ */
+ public void releaseOutputStream() throws IOException
+ {
+ if (out != null)
+ out.flush();
+ }
+
+ /**
+ * Gets the input stream for this connection.
+ */
+ public InputStream getInputStream() throws IOException
+ {
+ if (in == null)
+ in = new BufferedInputStream(socket.getInputStream());
+ return in;
+ }
+
+
+ /**
+ * Release the input stream for this connection.
+ */
+ public void releaseInputStream()
+ {
+ }
+
+ /**
+ * Determine if this connection can be used for multiple operations.
+ * If the socket implements RMISocketInfo, then we can query it about
+ * this; otherwise, assume that it does provide a full-duplex
+ * persistent connection like java.net.Socket.
+ */
+ public boolean isReusable()
+ {
+ if ((socket != null) && (socket instanceof RMISocketInfo))
+ return ((RMISocketInfo) socket).isReusable();
+ else
+ return true;
+ }
+
+ /**
+ * Set the expiration time of this connection.
+ * @param time The time at which the time out expires.
+ */
+ void setExpiration(long time)
+ {
+ expiration = time;
+ }
+
+ /**
+ * Set the timestamp at which this connection was last used successfully.
+ * The connection will be pinged for liveness if reused long after
+ * this time.
+ * @param time The time at which the connection was last active.
+ */
+ void setLastUseTime(long time)
+ {
+ lastuse = time;
+ }
+
+ /**
+ * Returns true if the timeout has expired on this connection;
+ * otherwise returns false.
+ * @param time The current time.
+ */
+ boolean expired(long time)
+ {
+ return expiration <= time;
+ }
+
+ /**
+ * Probes the connection to see if it still alive and connected to
+ * a responsive server. If the connection has been idle for too
+ * long, the server is pinged. ``Too long'' means ``longer than the
+ * last ping round-trip time''.
+ * <P>
+ * This method may misdiagnose a dead connection as live, but it
+ * will never misdiagnose a live connection as dead.
+ * @return true if the connection and server are recently alive
+ */
+ public boolean isDead()
+ {
+ InputStream i;
+ OutputStream o;
+
+ // skip ping if recently used within 1 RTT
+ long start = System.currentTimeMillis();
+ if ((roundtrip > 0) && (start < lastuse + roundtrip))
+ return (false); // still alive and warm
+
+ // Get the streams
+ try {
+ i = getInputStream();
+ o = getOutputStream();
+ } catch (IOException e) {
+ return (true); // can't even get a stream, must be very dead
+ }
+
+ // Write the ping byte and read the reply byte
+ int response = 0;
+ try {
+ o.write(TransportConstants.Ping);
+ o.flush();
+ response = i.read();
+ } catch (IOException ex) {
+ TCPTransport.tcpLog.log(Log.VERBOSE, "exception: ", ex);
+ TCPTransport.tcpLog.log(Log.BRIEF, "server ping failed");
+
+ return (true); // server failed the ping test
+ }
+
+ if (response == TransportConstants.PingAck) {
+ // save most recent RTT for future use
+ roundtrip = (System.currentTimeMillis() - start) * 2;
+ // clock-correction may make roundtrip < 0; doesn't matter
+ return (false); // it's alive and 5-by-5
+ }
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ (response == -1 ? "server has been deactivated" :
+ "server protocol error: ping response = " + response));
+ }
+ return (true);
+ }
+
+ /**
+ * Close the connection. */
+ public void close() throws IOException
+ {
+ TCPTransport.tcpLog.log(Log.BRIEF, "close connection");
+
+ if (socket != null)
+ socket.close();
+ else {
+ in.close();
+ out.close();
+ }
+ }
+
+ /**
+ * Returns the channel for this connection.
+ */
+ public Channel getChannel()
+ {
+ return channel;
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
new file mode 100644
index 000000000..835a7c7cd
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.ConnectIOException;
+import java.rmi.RemoteException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.security.AccessController;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.transport.Channel;
+import sun.rmi.transport.Endpoint;
+import sun.rmi.transport.Target;
+import sun.rmi.transport.Transport;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * TCPEndpoint represents some communication endpoint for an address
+ * space (VM).
+ *
+ * @author Ann Wollrath
+ */
+public class TCPEndpoint implements Endpoint {
+ /** IP address or host name */
+ private String host;
+ /** port number */
+ private int port;
+ /** custom client socket factory (null if not custom factory) */
+ private final RMIClientSocketFactory csf;
+ /** custom server socket factory (null if not custom factory) */
+ private final RMIServerSocketFactory ssf;
+
+ /** if local, the port number to listen on */
+ private int listenPort = -1;
+ /** if local, the transport object associated with this endpoint */
+ private TCPTransport transport = null;
+
+ /** the local host name */
+ private static String localHost;
+ /** true if real local host name is known yet */
+ private static boolean localHostKnown;
+
+ // this should be a *private* method since it is privileged
+ private static int getInt(String name, int def) {
+ return AccessController.doPrivileged(new GetIntegerAction(name, def));
+ }
+
+ // this should be a *private* method since it is privileged
+ private static boolean getBoolean(String name) {
+ return AccessController.doPrivileged(new GetBooleanAction(name));
+ }
+
+ /**
+ * Returns the value of the java.rmi.server.hostname property.
+ */
+ private static String getHostnameProperty() {
+ return AccessController.doPrivileged(
+ new GetPropertyAction("java.rmi.server.hostname"));
+ }
+
+ /**
+ * Find host name of local machine. Property "java.rmi.server.hostname"
+ * is used if set, so server administrator can compensate for the possible
+ * inablility to get fully qualified host name from VM.
+ */
+ static {
+ localHostKnown = true;
+ localHost = getHostnameProperty();
+
+ // could try querying CGI program here?
+ if (localHost == null) {
+ try {
+ InetAddress localAddr = InetAddress.getLocalHost();
+ byte[] raw = localAddr.getAddress();
+ if ((raw[0] == 127) &&
+ (raw[1] == 0) &&
+ (raw[2] == 0) &&
+ (raw[3] == 1)) {
+ localHostKnown = false;
+ }
+
+ /* if the user wishes to use a fully qualified domain
+ * name then attempt to find one.
+ */
+ if (getBoolean("java.rmi.server.useLocalHostName")) {
+ localHost = FQDN.attemptFQDN(localAddr);
+ } else {
+ /* default to using ip addresses, names will
+ * work across seperate domains.
+ */
+ localHost = localAddr.getHostAddress();
+ }
+ } catch (Exception e) {
+ localHostKnown = false;
+ localHost = null;
+ }
+ }
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "localHostKnown = " + localHostKnown +
+ ", localHost = " + localHost);
+ }
+ }
+
+ /** maps an endpoint key containing custom socket factories to
+ * their own unique endpoint */
+ // TBD: should this be a weak hash table?
+ private static final
+ Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints =
+ new HashMap<TCPEndpoint,LinkedList<TCPEndpoint>>();
+
+ /**
+ * Create an endpoint for a specified host and port.
+ * This should not be used by external classes to create endpoints
+ * for servers in this VM; use getLocalEndpoint instead.
+ */
+ public TCPEndpoint(String host, int port) {
+ this(host, port, null, null);
+ }
+
+ /**
+ * Create a custom socket factory endpoint for a specified host and port.
+ * This should not be used by external classes to create endpoints
+ * for servers in this VM; use getLocalEndpoint instead.
+ */
+ public TCPEndpoint(String host, int port, RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ if (host == null)
+ host = "";
+ this.host = host;
+ this.port = port;
+ this.csf = csf;
+ this.ssf = ssf;
+ }
+
+ /**
+ * Get an endpoint for the local address space on specified port.
+ * If port number is 0, it returns shared default endpoint object
+ * whose host name and port may or may not have been determined.
+ */
+ public static TCPEndpoint getLocalEndpoint(int port) {
+ return getLocalEndpoint(port, null, null);
+ }
+
+ public static TCPEndpoint getLocalEndpoint(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ /*
+ * Find mapping for an endpoint key to the list of local unique
+ * endpoints for this client/server socket factory pair (perhaps
+ * null) for the specific port.
+ */
+ TCPEndpoint ep = null;
+
+ synchronized (localEndpoints) {
+ TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf);
+ LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+ String localHost = resampleLocalHost();
+
+ if (epList == null) {
+ /*
+ * Create new endpoint list.
+ */
+ ep = new TCPEndpoint(localHost, port, csf, ssf);
+ epList = new LinkedList<TCPEndpoint>();
+ epList.add(ep);
+ ep.listenPort = port;
+ ep.transport = new TCPTransport(epList);
+ localEndpoints.put(endpointKey, epList);
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "created local endpoint for socket factory " + ssf +
+ " on port " + port);
+ }
+ } else {
+ synchronized (epList) {
+ ep = epList.getLast();
+ String lastHost = ep.host;
+ int lastPort = ep.port;
+ TCPTransport lastTransport = ep.transport;
+ // assert (localHost == null ^ lastHost != null)
+ if (localHost != null && !localHost.equals(lastHost)) {
+ /*
+ * Hostname has been updated; add updated endpoint
+ * to list.
+ */
+ if (lastPort != 0) {
+ /*
+ * Remove outdated endpoints only if the
+ * port has already been set on those endpoints.
+ */
+ epList.clear();
+ }
+ ep = new TCPEndpoint(localHost, lastPort, csf, ssf);
+ ep.listenPort = port;
+ ep.transport = lastTransport;
+ epList.add(ep);
+ }
+ }
+ }
+ }
+
+ return ep;
+ }
+
+ /**
+ * Resamples the local hostname and returns the possibly-updated
+ * local hostname.
+ */
+ private static String resampleLocalHost() {
+
+ String hostnameProperty = getHostnameProperty();
+
+ synchronized (localEndpoints) {
+ // assert(localHostKnown ^ (localHost == null))
+
+ if (hostnameProperty != null) {
+ if (!localHostKnown) {
+ /*
+ * If the local hostname is unknown, update ALL
+ * existing endpoints with the new hostname.
+ */
+ setLocalHost(hostnameProperty);
+ } else if (!hostnameProperty.equals(localHost)) {
+ /*
+ * Only update the localHost field for reference
+ * in future endpoint creation.
+ */
+ localHost = hostnameProperty;
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "updated local hostname to: " + localHost);
+ }
+ }
+ }
+ return localHost;
+ }
+ }
+
+ /**
+ * Set the local host name, if currently unknown.
+ */
+ static void setLocalHost(String host) {
+ // assert (host != null)
+
+ synchronized (localEndpoints) {
+ /*
+ * If host is not known, change the host field of ALL
+ * the local endpoints.
+ */
+ if (!localHostKnown) {
+ localHost = host;
+ localHostKnown = true;
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "local host set to " + host);
+ }
+ for (LinkedList<TCPEndpoint> epList : localEndpoints.values())
+ {
+ synchronized (epList) {
+ for (TCPEndpoint ep : epList) {
+ ep.host = host;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the port of the (shared) default endpoint object.
+ * When first created, it contains port 0 because the transport
+ * hasn't tried to listen to get assigned a port, or if listening
+ * failed, a port hasn't been assigned from the server.
+ */
+ static void setDefaultPort(int port, RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf);
+
+ synchronized (localEndpoints) {
+ LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+
+ synchronized (epList) {
+ int size = epList.size();
+ TCPEndpoint lastEp = epList.getLast();
+
+ for (TCPEndpoint ep : epList) {
+ ep.port = port;
+ }
+ if (size > 1) {
+ /*
+ * Remove all but the last element of the list
+ * (which contains the most recent hostname).
+ */
+ epList.clear();
+ epList.add(lastEp);
+ }
+ }
+
+ /*
+ * Allow future exports to use the actual bound port
+ * explicitly (see 6269166).
+ */
+ TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, csf, ssf);
+ localEndpoints.put(newEndpointKey, epList);
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "default port for server socket factory " + ssf +
+ " and client socket factory " + csf +
+ " set to " + port);
+ }
+ }
+ }
+
+ /**
+ * Returns transport for making connections to remote endpoints;
+ * (here, the default transport at port 0 is used).
+ */
+ public Transport getOutboundTransport() {
+ TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null);
+ return localEndpoint.transport;
+ }
+
+ /**
+ * Returns the current list of known transports.
+ * The returned list is an unshared collection of Transports,
+ * including all transports which may have channels to remote
+ * endpoints.
+ */
+ private static Collection<TCPTransport> allKnownTransports() {
+ // Loop through local endpoints, getting the transport of each one.
+ Set<TCPTransport> s;
+ synchronized (localEndpoints) {
+ // presize s to number of localEndpoints
+ s = new HashSet<TCPTransport>(localEndpoints.size());
+ for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) {
+ /*
+ * Each local endpoint has its transport added to s.
+ * Note: the transport is the same for all endpoints
+ * in the list, so it is okay to pick any one of them.
+ */
+ TCPEndpoint ep = epList.getFirst();
+ s.add(ep.transport);
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Release idle outbound connections to reduce demand on I/O resources.
+ * All transports are asked to release excess connections.
+ */
+ public static void shedConnectionCaches() {
+ for (TCPTransport transport : allKnownTransports()) {
+ transport.shedConnectionCaches();
+ }
+ }
+
+ /**
+ * Export the object to accept incoming calls.
+ */
+ public void exportObject(Target target) throws RemoteException {
+ transport.exportObject(target);
+ }
+
+ /**
+ * Returns a channel for this (remote) endpoint.
+ */
+ public Channel getChannel() {
+ return getOutboundTransport().getChannel(this);
+ }
+
+ /**
+ * Returns address for endpoint
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Returns the port for this endpoint. If this endpoint was
+ * created as a server endpoint (using getLocalEndpoint) for a
+ * default/anonymous port and its inbound transport has started
+ * listening, this method returns (instead of zero) the actual
+ * bound port suitable for passing to clients.
+ **/
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Returns the port that this endpoint's inbound transport listens
+ * on, if this endpoint was created as a server endpoint (using
+ * getLocalEndpoint). If this endpoint was created for the
+ * default/anonymous port, then this method returns zero even if
+ * the transport has started listening.
+ **/
+ public int getListenPort() {
+ return listenPort;
+ }
+
+ /**
+ * Returns the transport for incoming connections to this
+ * endpoint, if this endpoint was created as a server endpoint
+ * (using getLocalEndpoint).
+ **/
+ public Transport getInboundTransport() {
+ return transport;
+ }
+
+ /**
+ * Get the client socket factory associated with this endpoint.
+ */
+ public RMIClientSocketFactory getClientSocketFactory() {
+ return csf;
+ }
+
+ /**
+ * Get the server socket factory associated with this endpoint.
+ */
+ public RMIServerSocketFactory getServerSocketFactory() {
+ return ssf;
+ }
+
+ /**
+ * Return string representation for endpoint.
+ */
+ public String toString() {
+ return "[" + host + ":" + port +
+ (ssf != null ? "," + ssf : "") +
+ (csf != null ? "," + csf : "") +
+ "]";
+ }
+
+ public int hashCode() {
+ return port;
+ }
+
+ public boolean equals(Object obj) {
+ if ((obj != null) && (obj instanceof TCPEndpoint)) {
+ TCPEndpoint ep = (TCPEndpoint) obj;
+ if (port != ep.port || !host.equals(ep.host))
+ return false;
+ if (((csf == null) ^ (ep.csf == null)) ||
+ ((ssf == null) ^ (ep.ssf == null)))
+ return false;
+ /*
+ * Fix for 4254510: perform socket factory *class* equality check
+ * before socket factory equality check to avoid passing
+ * a potentially naughty socket factory to this endpoint's
+ * {client,server} socket factory equals method.
+ */
+ if ((csf != null) &&
+ !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf)))
+ return false;
+ if ((ssf != null) &&
+ !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf)))
+ return false;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /* codes for the self-describing formats of wire representation */
+ private static final int FORMAT_HOST_PORT = 0;
+ private static final int FORMAT_HOST_PORT_FACTORY = 1;
+
+ /**
+ * Write endpoint to output stream.
+ */
+ public void write(ObjectOutput out) throws IOException {
+ if (csf == null) {
+ out.writeByte(FORMAT_HOST_PORT);
+ out.writeUTF(host);
+ out.writeInt(port);
+ } else {
+ out.writeByte(FORMAT_HOST_PORT_FACTORY);
+ out.writeUTF(host);
+ out.writeInt(port);
+ out.writeObject(csf);
+ }
+ }
+
+ /**
+ * Get the endpoint from the input stream.
+ * @param in the input stream
+ * @exception IOException If id could not be read (due to stream failure)
+ */
+ public static TCPEndpoint read(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ String host;
+ int port;
+ RMIClientSocketFactory csf = null;
+
+ byte format = in.readByte();
+ switch (format) {
+ case FORMAT_HOST_PORT:
+ host = in.readUTF();
+ port = in.readInt();
+ break;
+
+ case FORMAT_HOST_PORT_FACTORY:
+ host = in.readUTF();
+ port = in.readInt();
+ csf = (RMIClientSocketFactory) in.readObject();
+ break;
+
+ default:
+ throw new IOException("invalid endpoint format");
+ }
+ return new TCPEndpoint(host, port, csf, null);
+ }
+
+ /**
+ * Write endpoint to output stream in older format used by
+ * UnicastRef for JDK1.1 compatibility.
+ */
+ public void writeHostPortFormat(DataOutput out) throws IOException {
+ if (csf != null) {
+ throw new InternalError("TCPEndpoint.writeHostPortFormat: " +
+ "called for endpoint with non-null socket factory");
+ }
+ out.writeUTF(host);
+ out.writeInt(port);
+ }
+
+ /**
+ * Create a new endpoint from input stream data.
+ * @param in the input stream
+ */
+ public static TCPEndpoint readHostPortFormat(DataInput in)
+ throws IOException
+ {
+ String host = in.readUTF();
+ int port = in.readInt();
+ return new TCPEndpoint(host, port);
+ }
+
+ private static RMISocketFactory chooseFactory() {
+ RMISocketFactory sf = RMISocketFactory.getSocketFactory();
+ if (sf == null) {
+ sf = TCPTransport.defaultSocketFactory;
+ }
+ return sf;
+ }
+
+ /**
+ * Open and return new client socket connection to endpoint.
+ */
+ Socket newSocket() throws RemoteException {
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "opening socket to " + this);
+ }
+
+ Socket socket;
+
+ try {
+ RMIClientSocketFactory clientFactory = csf;
+ if (clientFactory == null) {
+ clientFactory = chooseFactory();
+ }
+ socket = clientFactory.createSocket(host, port);
+
+ } catch (java.net.UnknownHostException e) {
+ throw new java.rmi.UnknownHostException(
+ "Unknown host: " + host, e);
+ } catch (java.net.ConnectException e) {
+ throw new java.rmi.ConnectException(
+ "Connection refused to host: " + host, e);
+ } catch (IOException e) {
+ // We might have simply run out of file descriptors
+ try {
+ TCPEndpoint.shedConnectionCaches();
+ // REMIND: should we retry createSocket?
+ } catch (OutOfMemoryError mem) {
+ // don't quit if out of memory
+ } catch (Exception ex) {
+ // don't quit if shed fails non-catastrophically
+ }
+
+ throw new ConnectIOException("Exception creating connection to: " +
+ host, e);
+ }
+
+ // set socket to disable Nagle's algorithm (always send immediately)
+ // TBD: should this be left up to socket factory instead?
+ try {
+ socket.setTcpNoDelay(true);
+ } catch (Exception e) {
+ // if we fail to set this, ignore and proceed anyway
+ }
+
+ // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs
+ try {
+ socket.setKeepAlive(true);
+ } catch (Exception e) {
+ // ignore and proceed
+ }
+
+ return socket;
+ }
+
+ /**
+ * Return new server socket to listen for connections on this endpoint.
+ */
+ ServerSocket newServerSocket() throws IOException {
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "creating server socket on " + this);
+ }
+
+ RMIServerSocketFactory serverFactory = ssf;
+ if (serverFactory == null) {
+ serverFactory = chooseFactory();
+ }
+ ServerSocket server = serverFactory.createServerSocket(listenPort);
+
+ // if we listened on an anonymous port, set the default port
+ // (for this socket factory)
+ if (listenPort == 0)
+ setDefaultPort(server.getLocalPort(), csf, ssf);
+
+ return server;
+ }
+
+ /**
+ * The class FQDN encapsulates a routine that makes a best effort
+ * attempt to retrieve the fully qualified domain name of the local
+ * host.
+ *
+ * @author Laird Dornin
+ */
+ private static class FQDN implements Runnable {
+
+ /**
+ * strings in which we can store discovered fqdn
+ */
+ private String reverseLookup;
+
+ private String hostAddress;
+
+ private FQDN(String hostAddress) {
+ this.hostAddress = hostAddress;
+ }
+
+ /**
+ * Do our best to obtain a fully qualified hostname for the local
+ * host. Perform the following steps to get a localhostname:
+ *
+ * 1. InetAddress.getLocalHost().getHostName() - if contains
+ * '.' use as FQDN
+ * 2. if no '.' query name service for FQDN in a thread
+ * Note: We query the name service for an FQDN by creating
+ * an InetAddress via a stringified copy of the local ip
+ * address; this creates an InetAddress with a null hostname.
+ * Asking for the hostname of this InetAddress causes a name
+ * service lookup.
+ *
+ * 3. if name service takes too long to return, use ip address
+ * 4. if name service returns but response contains no '.'
+ * default to ipaddress.
+ */
+ static String attemptFQDN(InetAddress localAddr)
+ throws java.net.UnknownHostException
+ {
+
+ String hostName = localAddr.getHostName();
+
+ if (hostName.indexOf('.') < 0 ) {
+
+ String hostAddress = localAddr.getHostAddress();
+ FQDN f = new FQDN(hostAddress);
+
+ int nameServiceTimeOut =
+ TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut",
+ 10000);
+
+ try {
+ synchronized(f) {
+ f.getFQDN();
+
+ /* wait to obtain an FQDN */
+ f.wait(nameServiceTimeOut);
+ }
+ } catch (InterruptedException e) {
+ /* propagate the exception to the caller */
+ Thread.currentThread().interrupt();
+ }
+ hostName = f.getHost();
+
+ if ((hostName == null) || (hostName.equals(""))
+ || (hostName.indexOf('.') < 0 )) {
+
+ hostName = hostAddress;
+ }
+ }
+ return hostName;
+ }
+
+ /**
+ * Method that that will start a thread to wait to retrieve a
+ * fully qualified domain name from a name service. The spawned
+ * thread may never return but we have marked it as a daemon so the vm
+ * will terminate appropriately.
+ */
+ private void getFQDN() {
+
+ /* FQDN finder will run in RMI threadgroup. */
+ Thread t = AccessController.doPrivileged(
+ new NewThreadAction(FQDN.this, "FQDN Finder", true));
+ t.start();
+ }
+
+ private synchronized String getHost() {
+ return reverseLookup;
+ }
+
+ /**
+ * thread to query a name service for the fqdn of this host.
+ */
+ public void run() {
+
+ String name = null;
+
+ try {
+ name = InetAddress.getByName(hostAddress).getHostName();
+ } catch (java.net.UnknownHostException e) {
+ } finally {
+ synchronized(this) {
+ reverseLookup = name;
+ this.notify();
+ }
+ }
+ }
+ }
+}
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java b/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java
new file mode 100644
index 000000000..8b70ab828
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/TCPTransport.java
@@ -0,0 +1,867 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import java.rmi.server.ExportException;
+import java.rmi.server.LogStream;
+import java.rmi.server.RMIFailureHandler;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.RemoteCall;
+import java.rmi.server.ServerNotActiveException;
+import java.rmi.server.UID;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.transport.Channel;
+import sun.rmi.transport.Connection;
+import sun.rmi.transport.DGCAckHandler;
+import sun.rmi.transport.Endpoint;
+import sun.rmi.transport.StreamRemoteCall;
+import sun.rmi.transport.Target;
+import sun.rmi.transport.Transport;
+import sun.rmi.transport.TransportConstants;
+import sun.rmi.transport.proxy.HttpReceiveSocket;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetLongAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * TCPTransport is the socket-based implementation of the RMI Transport
+ * abstraction.
+ *
+ * @author Ann Wollrath
+ * @author Peter Jones
+ */
+public class TCPTransport extends Transport {
+
+ /* tcp package log */
+ static final Log tcpLog = Log.getLog("sun.rmi.transport.tcp", "tcp",
+ LogStream.parseLevel(AccessController.doPrivileged(
+ new GetPropertyAction("sun.rmi.transport.tcp.logLevel"))));
+
+ /** maximum number of connection handler threads */
+ private static final int maxConnectionThreads = // default no limit
+ AccessController.doPrivileged(
+ new GetIntegerAction("sun.rmi.transport.tcp.maxConnectionThreads",
+ Integer.MAX_VALUE));
+
+ /** keep alive time for idle connection handler threads */
+ private static final long threadKeepAliveTime = // default 1 minute
+ AccessController.doPrivileged(
+ new GetLongAction("sun.rmi.transport.tcp.threadKeepAliveTime",
+ 60000));
+
+ /** thread pool for connection handlers */
+ private static final ExecutorService connectionThreadPool =
+ new ThreadPoolExecutor(0, maxConnectionThreads,
+ threadKeepAliveTime, TimeUnit.MILLISECONDS,
+ new SynchronousQueue<Runnable>(),
+ new ThreadFactory() {
+ public Thread newThread(Runnable runnable) {
+ return AccessController.doPrivileged(new NewThreadAction(
+ runnable, "TCP Connection(idle)", true, true));
+ }
+ });
+
+ /** total connections handled */
+ private static final AtomicInteger connectionCount = new AtomicInteger(0);
+
+ /** client host for the current thread's connection */
+ private static final ThreadLocal<ConnectionHandler>
+ threadConnectionHandler = new ThreadLocal<ConnectionHandler>();
+
+ /** endpoints for this transport */
+ private final LinkedList<TCPEndpoint> epList;
+ /** number of objects exported on this transport */
+ private int exportCount = 0;
+ /** server socket for this transport */
+ private ServerSocket server = null;
+ /** table mapping endpoints to channels */
+ private final Map<TCPEndpoint,Reference<TCPChannel>> channelTable =
+ new WeakHashMap<TCPEndpoint,Reference<TCPChannel>>();
+
+ static final RMISocketFactory defaultSocketFactory =
+ RMISocketFactory.getDefaultSocketFactory();
+
+ /** number of milliseconds in accepted-connection timeout.
+ * Warning: this should be greater than 15 seconds (the client-side
+ * timeout), and defaults to 2 hours.
+ * The maximum representable value is slightly more than 24 days
+ * and 20 hours.
+ */
+ private static final int connectionReadTimeout = // default 2 hours
+ AccessController.doPrivileged(
+ new GetIntegerAction("sun.rmi.transport.tcp.readTimeout",
+ 2 * 3600 * 1000));
+
+ /**
+ * Constructs a TCPTransport.
+ */
+ TCPTransport(LinkedList<TCPEndpoint> epList) {
+ // assert ((epList.size() != null) && (epList.size() >= 1))
+ this.epList = epList;
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "Version = " +
+ TransportConstants.Version + ", ep = " + getEndpoint());
+ }
+ }
+
+ /**
+ * Closes all cached connections in every channel subordinated to this
+ * transport. Currently, this only closes outgoing connections.
+ */
+ public void shedConnectionCaches() {
+ List<TCPChannel> channels;
+ synchronized (channelTable) {
+ channels = new ArrayList<TCPChannel>(channelTable.values().size());
+ for (Reference<TCPChannel> ref : channelTable.values()) {
+ TCPChannel ch = ref.get();
+ if (ch != null) {
+ channels.add(ch);
+ }
+ }
+ }
+ for (TCPChannel channel : channels) {
+ channel.shedCache();
+ }
+ }
+
+ /**
+ * Returns a <I>Channel</I> that generates connections to the
+ * endpoint <I>ep</I>. A Channel is an object that creates and
+ * manages connections of a particular type to some particular
+ * address space.
+ * @param ep the endpoint to which connections will be generated.
+ * @return the channel or null if the transport cannot
+ * generate connections to this endpoint
+ */
+ public TCPChannel getChannel(Endpoint ep) {
+ TCPChannel ch = null;
+ if (ep instanceof TCPEndpoint) {
+ synchronized (channelTable) {
+ Reference<TCPChannel> ref = channelTable.get(ep);
+ if (ref != null) {
+ ch = ref.get();
+ }
+ if (ch == null) {
+ TCPEndpoint tcpEndpoint = (TCPEndpoint) ep;
+ ch = new TCPChannel(this, tcpEndpoint);
+ channelTable.put(tcpEndpoint,
+ new WeakReference<TCPChannel>(ch));
+ }
+ }
+ }
+ return ch;
+ }
+
+ /**
+ * Removes the <I>Channel</I> that generates connections to the
+ * endpoint <I>ep</I>.
+ */
+ public void free(Endpoint ep) {
+ if (ep instanceof TCPEndpoint) {
+ synchronized (channelTable) {
+ Reference<TCPChannel> ref = channelTable.remove(ep);
+ if (ref != null) {
+ TCPChannel channel = ref.get();
+ if (channel != null) {
+ channel.shedCache();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Export the object so that it can accept incoming calls.
+ */
+ public void exportObject(Target target) throws RemoteException {
+ /*
+ * Ensure that a server socket is listening, and count this
+ * export while synchronized to prevent the server socket from
+ * being closed due to concurrent unexports.
+ */
+ synchronized (this) {
+ listen();
+ exportCount++;
+ }
+
+ /*
+ * Try to add the Target to the exported object table; keep
+ * counting this export (to keep server socket open) only if
+ * that succeeds.
+ */
+ boolean ok = false;
+ try {
+ super.exportObject(target);
+ ok = true;
+ } finally {
+ if (!ok) {
+ synchronized (this) {
+ decrementExportCount();
+ }
+ }
+ }
+ }
+
+ protected synchronized void targetUnexported() {
+ decrementExportCount();
+ }
+
+ /**
+ * Decrements the count of exported objects, closing the current
+ * server socket if the count reaches zero.
+ **/
+ private void decrementExportCount() {
+ assert Thread.holdsLock(this);
+ exportCount--;
+ if (exportCount == 0 && getEndpoint().getListenPort() != 0) {
+ ServerSocket ss = server;
+ server = null;
+ try {
+ ss.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Verify that the current access control context has permission to
+ * accept the connection being dispatched by the current thread.
+ */
+ protected void checkAcceptPermission(AccessControlContext acc) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ return;
+ }
+ ConnectionHandler h = threadConnectionHandler.get();
+ if (h == null) {
+ throw new Error(
+ "checkAcceptPermission not in ConnectionHandler thread");
+ }
+ h.checkAcceptPermission(sm, acc);
+ }
+
+ private TCPEndpoint getEndpoint() {
+ synchronized (epList) {
+ return epList.getLast();
+ }
+ }
+
+ /**
+ * Listen on transport's endpoint.
+ */
+ private void listen() throws RemoteException {
+ assert Thread.holdsLock(this);
+ TCPEndpoint ep = getEndpoint();
+ int port = ep.getPort();
+
+ if (server == null) {
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF,
+ "(port " + port + ") create server socket");
+ }
+
+ try {
+ server = ep.newServerSocket();
+ /*
+ * Don't retry ServerSocket if creation fails since
+ * "port in use" will cause export to hang if an
+ * RMIFailureHandler is not installed.
+ */
+ Thread t = AccessController.doPrivileged(
+ new NewThreadAction(new AcceptLoop(server),
+ "TCP Accept-" + port, true));
+ t.start();
+ } catch (java.net.BindException e) {
+ throw new ExportException("Port already in use: " + port, e);
+ } catch (IOException e) {
+ throw new ExportException("Listen failed on port: " + port, e);
+ }
+
+ } else {
+ // otherwise verify security access to existing server socket
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkListen(port);
+ }
+ }
+ }
+
+ /**
+ * Worker for accepting connections from a server socket.
+ **/
+ private class AcceptLoop implements Runnable {
+
+ private final ServerSocket serverSocket;
+
+ // state for throttling loop on exceptions (local to accept thread)
+ private long lastExceptionTime = 0L;
+ private int recentExceptionCount;
+
+ AcceptLoop(ServerSocket serverSocket) {
+ this.serverSocket = serverSocket;
+ }
+
+ public void run() {
+ try {
+ executeAcceptLoop();
+ } finally {
+ try {
+ /*
+ * Only one accept loop is started per server
+ * socket, so after no more connections will be
+ * accepted, ensure that the server socket is no
+ * longer listening.
+ */
+ serverSocket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Accepts connections from the server socket and executes
+ * handlers for them in the thread pool.
+ **/
+ private void executeAcceptLoop() {
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "listening on port " +
+ getEndpoint().getPort());
+ }
+
+ while (true) {
+ Socket socket = null;
+ try {
+ socket = serverSocket.accept();
+
+ /*
+ * Find client host name (or "0.0.0.0" if unknown)
+ */
+ InetAddress clientAddr = socket.getInetAddress();
+ String clientHost = (clientAddr != null
+ ? clientAddr.getHostAddress()
+ : "0.0.0.0");
+
+ /*
+ * Execute connection handler in the thread pool,
+ * which uses non-system threads.
+ */
+ try {
+ connectionThreadPool.execute(
+ new ConnectionHandler(socket, clientHost));
+ } catch (RejectedExecutionException e) {
+ closeSocket(socket);
+ tcpLog.log(Log.BRIEF,
+ "rejected connection from " + clientHost);
+ }
+
+ } catch (Throwable t) {
+ try {
+ /*
+ * If the server socket has been closed, such
+ * as because there are no more exported
+ * objects, then we expect accept to throw an
+ * exception, so just terminate normally.
+ */
+ if (serverSocket.isClosed()) {
+ break;
+ }
+
+ try {
+ if (tcpLog.isLoggable(Level.WARNING)) {
+ tcpLog.log(Level.WARNING,
+ "accept loop for " + serverSocket +
+ " throws", t);
+ }
+ } catch (Throwable tt) {
+ }
+ } finally {
+ /*
+ * Always close the accepted socket (if any)
+ * if an exception occurs, but only after
+ * logging an unexpected exception.
+ */
+ if (socket != null) {
+ closeSocket(socket);
+ }
+ }
+
+ /*
+ * In case we're running out of file descriptors,
+ * release resources held in caches.
+ */
+ if (!(t instanceof SecurityException)) {
+ try {
+ TCPEndpoint.shedConnectionCaches();
+ } catch (Throwable tt) {
+ }
+ }
+
+ /*
+ * A NoClassDefFoundError can occur if no file
+ * descriptors are available, in which case this
+ * loop should not terminate.
+ */
+ if (t instanceof Exception ||
+ t instanceof OutOfMemoryError ||
+ t instanceof NoClassDefFoundError)
+ {
+ if (!continueAfterAcceptFailure(t)) {
+ return;
+ }
+ // continue loop
+ } else {
+ throw (Error) t;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the accept loop should continue after the
+ * specified exception has been caught, or false if the accept
+ * loop should terminate (closing the server socket). If
+ * there is an RMIFailureHandler, this method returns the
+ * result of passing the specified exception to it; otherwise,
+ * this method always returns true, after sleeping to throttle
+ * the accept loop if necessary.
+ **/
+ private boolean continueAfterAcceptFailure(Throwable t) {
+ RMIFailureHandler fh = RMISocketFactory.getFailureHandler();
+ if (fh != null) {
+ return fh.failure(t instanceof Exception ? (Exception) t :
+ new InvocationTargetException(t));
+ } else {
+ throttleLoopOnException();
+ return true;
+ }
+ }
+
+ /**
+ * Throttles the accept loop after an exception has been
+ * caught: if a burst of 10 exceptions in 5 seconds occurs,
+ * then wait for 10 seconds to curb busy CPU usage.
+ **/
+ private void throttleLoopOnException() {
+ long now = System.currentTimeMillis();
+ if (lastExceptionTime == 0L || (now - lastExceptionTime) > 5000) {
+ // last exception was long ago (or this is the first)
+ lastExceptionTime = now;
+ recentExceptionCount = 0;
+ } else {
+ // exception burst window was started recently
+ if (++recentExceptionCount >= 10) {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException ignore) {
+ }
+ }
+ }
+ }
+ }
+
+ /** close socket and eat exception */
+ private static void closeSocket(Socket sock) {
+ try {
+ sock.close();
+ } catch (IOException ex) {
+ // eat exception
+ }
+ }
+
+ /**
+ * handleMessages decodes transport operations and handles messages
+ * appropriately. If an exception occurs during message handling,
+ * the socket is closed.
+ */
+ void handleMessages(Connection conn, boolean persistent) {
+ int port = getEndpoint().getPort();
+
+ try {
+ DataInputStream in = new DataInputStream(conn.getInputStream());
+ do {
+ int op = in.read(); // transport op
+ if (op == -1) {
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "(port " +
+ port + ") connection closed");
+ }
+ break;
+ }
+
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "(port " + port +
+ ") op = " + op);
+ }
+
+ switch (op) {
+ case TransportConstants.Call:
+ // service incoming RMI call
+ RemoteCall call = new StreamRemoteCall(conn);
+ if (serviceCall(call) == false)
+ return;
+ break;
+
+ case TransportConstants.Ping:
+ // send ack for ping
+ DataOutputStream out =
+ new DataOutputStream(conn.getOutputStream());
+ out.writeByte(TransportConstants.PingAck);
+ conn.releaseOutputStream();
+ break;
+
+ case TransportConstants.DGCAck:
+ DGCAckHandler.received(UID.read(in));
+ break;
+
+ default:
+ throw new IOException("unknown transport op " + op);
+ }
+ } while (persistent);
+
+ } catch (IOException e) {
+ // exception during processing causes connection to close (below)
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "(port " + port +
+ ") exception: ", e);
+ }
+ } finally {
+ try {
+ conn.close();
+ } catch (IOException ex) {
+ // eat exception
+ }
+ }
+ }
+
+ /**
+ * Returns the client host for the current thread's connection. Throws
+ * ServerNotActiveException if no connection is active for this thread.
+ */
+ public static String getClientHost() throws ServerNotActiveException {
+ ConnectionHandler h = threadConnectionHandler.get();
+ if (h != null) {
+ return h.getClientHost();
+ } else {
+ throw new ServerNotActiveException("not in a remote call");
+ }
+ }
+
+ /**
+ * Services messages on accepted connection
+ */
+ private class ConnectionHandler implements Runnable {
+
+ /** int value of "POST" in ASCII (Java's specified data formats
+ * make this once-reviled tactic again socially acceptable) */
+ private static final int POST = 0x504f5354;
+
+ /** most recently accept-authorized AccessControlContext */
+ private AccessControlContext okContext;
+ /** cache of accept-authorized AccessControlContexts */
+ private Map<AccessControlContext,
+ Reference<AccessControlContext>> authCache;
+ /** security manager which authorized contexts in authCache */
+ private SecurityManager cacheSecurityManager = null;
+
+ private Socket socket;
+ private String remoteHost;
+
+ ConnectionHandler(Socket socket, String remoteHost) {
+ this.socket = socket;
+ this.remoteHost = remoteHost;
+ }
+
+ String getClientHost() {
+ return remoteHost;
+ }
+
+ /**
+ * Verify that the given AccessControlContext has permission to
+ * accept this connection.
+ */
+ void checkAcceptPermission(SecurityManager sm,
+ AccessControlContext acc)
+ {
+ /*
+ * Note: no need to synchronize on cache-related fields, since this
+ * method only gets called from the ConnectionHandler's thread.
+ */
+ if (sm != cacheSecurityManager) {
+ okContext = null;
+ authCache = new WeakHashMap<AccessControlContext,
+ Reference<AccessControlContext>>();
+ cacheSecurityManager = sm;
+ }
+ if (acc.equals(okContext) || authCache.containsKey(acc)) {
+ return;
+ }
+ InetAddress addr = socket.getInetAddress();
+ String host = (addr != null) ? addr.getHostAddress() : "*";
+
+ sm.checkAccept(host, socket.getPort());
+
+ authCache.put(acc, new SoftReference<AccessControlContext>(acc));
+ okContext = acc;
+ }
+
+ public void run() {
+ Thread t = Thread.currentThread();
+ String name = t.getName();
+ try {
+ t.setName("RMI TCP Connection(" +
+ connectionCount.incrementAndGet() +
+ ")-" + remoteHost);
+ run0();
+ } finally {
+ t.setName(name);
+ }
+ }
+
+ private void run0() {
+ TCPEndpoint endpoint = getEndpoint();
+ int port = endpoint.getPort();
+
+ threadConnectionHandler.set(this);
+
+ // set socket to disable Nagle's algorithm (always send
+ // immediately)
+ // TBD: should this be left up to socket factory instead?
+ try {
+ socket.setTcpNoDelay(true);
+ } catch (Exception e) {
+ // if we fail to set this, ignore and proceed anyway
+ }
+ // set socket to timeout after excessive idle time
+ try {
+ if (connectionReadTimeout > 0)
+ socket.setSoTimeout(connectionReadTimeout);
+ } catch (Exception e) {
+ // too bad, continue anyway
+ }
+
+ try {
+ InputStream sockIn = socket.getInputStream();
+ InputStream bufIn = sockIn.markSupported()
+ ? sockIn
+ : new BufferedInputStream(sockIn);
+
+ // Read magic (or HTTP wrapper)
+ bufIn.mark(4);
+ DataInputStream in = new DataInputStream(bufIn);
+ int magic = in.readInt();
+
+ if (magic == POST) {
+ tcpLog.log(Log.BRIEF, "decoding HTTP-wrapped call");
+
+ // It's really a HTTP-wrapped request. Repackage
+ // the socket in a HttpReceiveSocket, reinitialize
+ // sockIn and in, and reread magic.
+ bufIn.reset(); // unread "POST"
+
+ try {
+ socket = new HttpReceiveSocket(socket, bufIn, null);
+ remoteHost = "0.0.0.0";
+ sockIn = socket.getInputStream();
+ bufIn = new BufferedInputStream(sockIn);
+ in = new DataInputStream(bufIn);
+ magic = in.readInt();
+
+ } catch (IOException e) {
+ throw new RemoteException("Error HTTP-unwrapping call",
+ e);
+ }
+ }
+ // bufIn's mark will invalidate itself when it overflows
+ // so it doesn't have to be turned off
+
+ // read and verify transport header
+ short version = in.readShort();
+ if (magic != TransportConstants.Magic ||
+ version != TransportConstants.Version) {
+ // protocol mismatch detected...
+ // just close socket: this would recurse if we marshal an
+ // exception to the client and the protocol at other end
+ // doesn't match.
+ closeSocket(socket);
+ return;
+ }
+
+ OutputStream sockOut = socket.getOutputStream();
+ BufferedOutputStream bufOut =
+ new BufferedOutputStream(sockOut);
+ DataOutputStream out = new DataOutputStream(bufOut);
+
+ int remotePort = socket.getPort();
+
+ if (tcpLog.isLoggable(Log.BRIEF)) {
+ tcpLog.log(Log.BRIEF, "accepted socket from [" +
+ remoteHost + ":" + remotePort + "]");
+ }
+
+ TCPEndpoint ep;
+ TCPChannel ch;
+ TCPConnection conn;
+
+ // send ack (or nack) for protocol
+ byte protocol = in.readByte();
+ switch (protocol) {
+ case TransportConstants.SingleOpProtocol:
+ // no ack for protocol
+
+ // create dummy channel for receiving messages
+ ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
+ endpoint.getClientSocketFactory(),
+ endpoint.getServerSocketFactory());
+ ch = new TCPChannel(TCPTransport.this, ep);
+ conn = new TCPConnection(ch, socket, bufIn, bufOut);
+
+ // read input messages
+ handleMessages(conn, false);
+ break;
+
+ case TransportConstants.StreamProtocol:
+ // send ack
+ out.writeByte(TransportConstants.ProtocolAck);
+
+ // suggest endpoint (in case client doesn't know host name)
+ if (tcpLog.isLoggable(Log.VERBOSE)) {
+ tcpLog.log(Log.VERBOSE, "(port " + port +
+ ") " + "suggesting " + remoteHost + ":" +
+ remotePort);
+ }
+
+ out.writeUTF(remoteHost);
+ out.writeInt(remotePort);
+ out.flush();
+
+ // read and discard (possibly bogus) endpoint
+ // REMIND: would be faster to read 2 bytes then skip N+4
+ String clientHost = in.readUTF();
+ int clientPort = in.readInt();
+ if (tcpLog.isLoggable(Log.VERBOSE)) {
+ tcpLog.log(Log.VERBOSE, "(port " + port +
+ ") client using " + clientHost + ":" + clientPort);
+ }
+
+ // create dummy channel for receiving messages
+ // (why not use clientHost and clientPort?)
+ ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
+ endpoint.getClientSocketFactory(),
+ endpoint.getServerSocketFactory());
+ ch = new TCPChannel(TCPTransport.this, ep);
+ conn = new TCPConnection(ch, socket, bufIn, bufOut);
+
+ // read input messages
+ handleMessages(conn, true);
+ break;
+
+ case TransportConstants.MultiplexProtocol:
+ if (tcpLog.isLoggable(Log.VERBOSE)) {
+ tcpLog.log(Log.VERBOSE, "(port " + port +
+ ") accepting multiplex protocol");
+ }
+
+ // send ack
+ out.writeByte(TransportConstants.ProtocolAck);
+
+ // suggest endpoint (in case client doesn't already have one)
+ if (tcpLog.isLoggable(Log.VERBOSE)) {
+ tcpLog.log(Log.VERBOSE, "(port " + port +
+ ") suggesting " + remoteHost + ":" + remotePort);
+ }
+
+ out.writeUTF(remoteHost);
+ out.writeInt(remotePort);
+ out.flush();
+
+ // read endpoint client has decided to use
+ ep = new TCPEndpoint(in.readUTF(), in.readInt(),
+ endpoint.getClientSocketFactory(),
+ endpoint.getServerSocketFactory());
+ if (tcpLog.isLoggable(Log.VERBOSE)) {
+ tcpLog.log(Log.VERBOSE, "(port " +
+ port + ") client using " +
+ ep.getHost() + ":" + ep.getPort());
+ }
+
+ ConnectionMultiplexer multiplexer;
+ synchronized (channelTable) {
+ // create or find channel for this endpoint
+ ch = getChannel(ep);
+ multiplexer =
+ new ConnectionMultiplexer(ch, bufIn, sockOut,
+ false);
+ ch.useMultiplexer(multiplexer);
+ }
+ multiplexer.run();
+ break;
+
+ default:
+ // protocol not understood, send nack and close socket
+ out.writeByte(TransportConstants.ProtocolNack);
+ out.flush();
+ break;
+ }
+
+ } catch (IOException e) {
+ // socket in unknown state: destroy socket
+ tcpLog.log(Log.BRIEF, "terminated with exception:", e);
+ } finally {
+ closeSocket(socket);
+ }
+ }
+ }
+}