/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program 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.
*
* This program 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 at /legal/license.txt).
*
* 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 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.satsa.jcrmic;
import java.util.*;
import java.io.*;
import com.sun.satsa.jcrmic.classfile.*;
import com.sun.satsa.jcrmic.utils.*;
/**
* Main "jcrmic" program.
*/
public class Main {
/**
* The list of interface names specified by user.
*/
private Vector classNames;
/**
* Class loader.
*/
private Loader loader;
/**
* Errors/warnings notifier.
*/
private Notifier notifier;
/**
* Output directory for java and class files.
*/
private File destDir;
/**
* Class path specified by user.
*/
private String classPath;
/**
* 'Compile generated source files' flag.
*/
private boolean compile = true;
/**
* 'Keep generated source files' flag.
*/
private boolean keepGenerated = false;
/**
* Constructor.
* @param out output stream for messages.
*/
public Main(OutputStream out) {
notifier = new Notifier(out);
classNames = new Vector();
loader = new Loader(notifier);
}
/**
* Main program.
* @param argv command line arguments given by user.
*/
public static void main(String argv[]) {
Main compiler = new Main(System.out);
System.exit(compiler.compile(argv) ? 0 : 1);
}
/**
* Run the compiler.
* @param argv command line arguments given by user.
* @return true if compilation was successful
*/
public boolean compile(String argv[]) {
if (!parseArgs(argv)) {
usage();
return false;
}
return doCompile();
}
/**
* Parse the arguments for compile.
* @param argv command line arguments given by user.
* @return true if command line is parsed without errors
*/
public boolean parseArgs(String argv[]) {
// Parse arguments
for (int i = 0; i < argv.length; i++) {
if (argv[i].equals("-classpath")) {
if ((i + 1) < argv.length) {
if (!loader.setClassPath(argv[++i]))
return false;
classPath = argv[i];
} else {
notifier.error("rmic.option.requires.argument",
"-classpath");
return false;
}
} else if (argv[i].equals("-d")) {
if ((i + 1) < argv.length) {
if (destDir != null) {
notifier.error("rmic.option.already.seen", "-d");
return false;
}
destDir = new File(argv[++i]);
if (!destDir.exists()) {
notifier.error("rmic.no.such.directory",
destDir.getPath());
return false;
}
}
} else if (argv[i].equals("-nocompile")) {
compile = false;
} else if (argv[i].equals("-keep")) {
keepGenerated = true;
} else if (argv[i].startsWith("-")) {
notifier.error("rmic.no.such.option", argv[i]);
return false;
} else {
classNames.addElement(argv[i]);
}
}
if (classNames.size() == 0) {
return false;
}
return true;
}
/**
* Do the compile with the switches and files already supplied.
* @return true if compilation was successful
*/
public boolean doCompile() {
if (!(loader.loadClass("java/rmi/Remote") &&
loader.loadClass("java/rmi/RemoteException") &&
loader.loadClass("java/lang/RuntimeException"))) {
return false;
}
// load classes
for (int i = 0; i < classNames.size(); i++) {
String name = (String) classNames.elementAt(i);
if (!loader.loadClass(name)) {
return false;
}
}
// verify classes
for (int i = 0; i < classNames.size(); i++) {
String name = (String) classNames.elementAt(i);
if (! verify(name)) {
return false;
}
}
// create stubs
Vector fileNames = new Vector();
for (int i = 0; i < classNames.size(); i++) {
String name = (String) classNames.elementAt(i);
String filePath = name.replace('.', File.separatorChar);
String packagePath = "";
int index = filePath.lastIndexOf(File.separatorChar);
if (index != -1) {
packagePath = filePath.substring(0, index);
filePath = filePath.substring(index + 1);
}
filePath = filePath + "_Stub.java";
File stubFile;
if (destDir != null) {
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 don't have
* to worry about creating it unintentionally.
*/
if (!packageDir.exists()) {
packageDir.mkdirs();
}
stubFile = new File(packageDir, filePath);
} else {
/*
* If a top-level destination directory is not specified
* (with the "-d" option), we just put the generated files
* in the current working directory, which was the behavior
* of rmic in JDK 1.1. This feels less than ideal, but
* there is no easy alternative.
*/
stubFile = new File(System.getProperty("user.dir"), filePath);
}
try {
stubFile.createNewFile();
fileNames.add(stubFile.getCanonicalPath());
IndentingWriter out = new IndentingWriter(
new OutputStreamWriter(new FileOutputStream(stubFile)));
writeStub(name, out);
out.close();
} catch (IOException e) {
notifier.error("rmic.ioerror.writing", stubFile.getPath());
return false;
}
}
if (! compile) {
return true;
}
// compile the stubs
String command = System.getProperty("JAVAC_PATH");
if (command == null || ! new File(command).exists()) {
notifier.error("rmic.compiler.not.found");
return false;
}
command += " -classpath " + classPath;
if (destDir != null) {
command += " -d " + destDir.getPath();
}
for (int i = 0; i < fileNames.size(); i++) {
command += " " + (String) fileNames.elementAt(i);
}
Process p;
try {
p = Runtime.getRuntime().exec(command);
} catch (IOException e) {
notifier.error("rmic.compilation.error");
return false;
}
(new StreamReader(p.getInputStream())).start();
(new StreamReader(p.getErrorStream(), notifier)).start();
try {
p.waitFor();
} catch (InterruptedException e) {
notifier.error("rmic.compilation.error");
return false;
}
if (! keepGenerated) {
for (int i = 0; i < fileNames.size(); i++) {
new File((String) fileNames.elementAt(i)).delete();
}
}
if (p.exitValue() != 0) {
notifier.error("rmic.compilation.failed");
return false;
}
return true;
}
/**
* Prints usage message.
*/
public void usage() {
notifier.error("rmic.usage");
}
/**
* Verify that interface specified in command line complies with
* JCRMI limitations.
* @param name interface name
* @return verification result
*/
public boolean verify(String name) {
JClass c = loader.getClass(name);
if (!c.isInterface()) {
notifier.error("rmic.cant.make.stubs.for.class", name);
return false;
}
if (!c.isRemote()) {
notifier.error("rmic.must.implement.remote", name);
return false;
}
// Verify this interface and all interfaces extended by this
// interface
Vector remoteInterfaces = new Vector();
addInterface(c, remoteInterfaces);
if (!verifyInterfaces(remoteInterfaces)) {
return false;
}
return true;
}
/**
* Verify all the interfaces in vector for compliance with JCRMI
* limitations.
* @param interfaces the list of interfaces
* @return verification result
*/
private boolean verifyInterfaces(Vector interfaces) {
for (int i = 0; i < interfaces.size(); i++) {
if (!verifyInterface((JClass) interfaces.elementAt(i))) {
return false;
}
}
return true;
}
/**
* Verify that interface complies with JCRMI limitations.
* @param cl interface to be verified
* @return verification result
*/
private boolean verifyInterface(JClass cl) {
if (cl.isVerified()) {
return true;
}
cl.setVerified();
JMethod[] methods = cl.getMethods();
for (int i = 0; i < methods.length; i++) {
if (!verifyRemoteMethod(cl.getClassName(), methods[i]))
return false;
}
return true;
}
/**
* The list of exceptions that can be thrown by remote methods.
*/
private static String[] JCExceptions = {
"java/lang/Throwable",
"java/lang/ArithmeticException",
"java/lang/ArrayIndexOutOfBoundsException",
"java/lang/ArrayStoreException",
"java/lang/ClassCastException",
"java/lang/Exception",
"java/lang/IndexOutOfBoundsException",
"java/lang/NegativeArraySizeException",
"java/lang/NullPointerException",
"java/lang/RuntimeException",
"java/lang/SecurityException",
"java/io/IOException",
"java/rmi/RemoteException",
"javacard/framework/APDUException",
"javacard/framework/CardException",
"javacard/framework/CardRuntimeException",
"javacard/framework/ISOException",
"javacard/framework/PINException",
"javacard/framework/SystemException",
"javacard/framework/TransactionException",
"javacard/framework/UserException",
"javacard/security/CryptoException",
"javacard/framework/service/ServiceException"};
/**
* Verify that remote method complies with JCRMI limitations.
* @param class_name the name of class that defines the method
* @param m the method to be verified
* @return verification result
*/
private boolean verifyRemoteMethod(String class_name, JMethod m) {
// check signature
String descriptor = m.getMethodDescriptor();
Vector v = RemoteMethod.parseDescriptor(descriptor);
boolean ok = (v != null);
if (ok) {
String parameter = (String) v.elementAt(v.size() - 1);
if (parameter.startsWith("L")) {
parameter = parameter.substring(1, parameter.length() - 1);
if (!loader.loadClass(parameter)) {
return false;
}
JClass ret = loader.getClass(parameter);
ok = ok && ret.isInterface() && ret.isRemote();
if (ok) {
Vector z = new Vector();
addInterface(ret, z);
if (!verifyInterfaces(z)) {
return false;
}
}
}
}
if (!ok) {
notifier.error("rmic.incorrect.method.signature",
class_name, m.getMethodName() + descriptor);
return false;
}
// check that all exceptions are defined in Java Card API
// and that method throws RemoteException
JClass jrRemoteException = loader.getClass("java/rmi/RemoteException");
boolean RemoteThrown = false;
String[] exceptions = m.getExceptionsThrown();
if (exceptions != null) {
for (int i = 0; i < exceptions.length; i++) {
boolean found = false;
for (int j = 0; j < JCExceptions.length; j++) {
if (exceptions[i].equals(JCExceptions[j])) {
found = true;
break;
}
}
if (!found) {
notifier.error("rmic.method.throws.invalid.exception",
class_name, m.getMethodName() + descriptor,
exceptions[i]);
return false;
}
JClass ex = loader.getClass(exceptions[i]);
RemoteThrown = RemoteThrown ||
jrRemoteException.isSubclass(ex);
}
}
if (!RemoteThrown) {
notifier.error("rmic.must.throw.remoteexception",
class_name, m.getMethodName() + descriptor);
return false;
}
return true;
}
/**
* Writes the stub for remote interface into the stream.
* @param className interface name
* @param p output stream
* @throws IOException if I/O error occurs
*/
public void writeStub(String className, IndentingWriter p)
throws IOException {
JClass c = loader.getClass(className);
// find all remote interfaces
Vector remoteInterfaces = new Vector();
addInterface(c, remoteInterfaces);
// find all remote methods
Hashtable remoteMethods = new Hashtable();
for (int i = 0; i < remoteInterfaces.size(); i++) {
JClass cl = (JClass) remoteInterfaces.elementAt(i);
JMethod[] methods = cl.getMethods();
for (int j = 0; j < methods.length; j++) {
String m_name = methods[j].getMethodName();
String m_descriptor = methods[j].getMethodDescriptor();
RemoteMethod m = new RemoteMethod(m_name, m_descriptor, loader);
String[] exceptions = methods[j].getExceptionsThrown();
if (exceptions != null) {
for (int k = 0; k < exceptions.length; k++) {
m.addException(loader.getClass(exceptions[k]));
}
}
String key = m_name + m_descriptor;
RemoteMethod m_old = (RemoteMethod) remoteMethods.get(key);
if (m_old == null) {
remoteMethods.put(key, m);
} else {
m_old.merge(m);
}
}
}
p.pln("// Stub class generated by jcrmic, do not edit.");
p.pln("// Contents subject to change without notice.");
p.pln();
String name = c.getClassName().replace('/', '.');
String pname = "";
int index = name.lastIndexOf('.');
if (index != -1) {
pname = name.substring(0, index);
name = name.substring(index + 1);
}
name = name + "_Stub";
/*
* If remote implementation class was in a particular package,
* declare the stub class to be in the same package.
*/
if (!pname.equals("")) {
p.pln("package " + pname + ";");
p.pln();
}
/*
* Declare the class; implement all remote interfaces.
*/
String s = "";
for (int i = 0; i < remoteInterfaces.size(); i++) {
JClass cl = (JClass) remoteInterfaces.elementAt(i);
if (cl.isRemote()) {
if (! s.equals("")) {
s = s + ", ";
}
s = s + cl.getClassName().replace('/', '.');
}
}
p.plnI("public final class " + name);
p.pln("extends javax.microedition.jcrmi.RemoteStub");
p.pln("implements " + s + " {");
p.pln("");
p.pln("// constructor");
p.plnI("public " + name + "() {");
p.pln("super();");
p.pOln("}");
for (Enumeration methods = remoteMethods.elements();
methods.hasMoreElements();) {
((RemoteMethod) methods.nextElement()).write(p);
}
p.pOln("}");
}
/**
* Put this interface and all interfaces extended by this interface
* into vector.
* @param cl an interface
* @param v target vector
*/
public static void addInterface(JClass cl, Vector v) {
/* !!! debug */
if (!cl.isInterface()) {
System.out.println("error - addInterface");
System.exit(1);
}
if (!v.contains(cl)) {
v.add(cl);
JClass[] interfaces = cl.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
addInterface(interfaces[i], v);
}
}
}
}
|