001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.satsa.jcrmic;
028:
029: import java.util.*;
030: import java.io.*;
031:
032: import com.sun.satsa.jcrmic.classfile.*;
033: import com.sun.satsa.jcrmic.utils.*;
034:
035: /**
036: * Main "jcrmic" program.
037: */
038:
039: public class Main {
040:
041: /**
042: * The list of interface names specified by user.
043: */
044: private Vector classNames;
045:
046: /**
047: * Class loader.
048: */
049: private Loader loader;
050:
051: /**
052: * Errors/warnings notifier.
053: */
054: private Notifier notifier;
055:
056: /**
057: * Output directory for java and class files.
058: */
059: private File destDir;
060:
061: /**
062: * Class path specified by user.
063: */
064: private String classPath;
065:
066: /**
067: * 'Compile generated source files' flag.
068: */
069: private boolean compile = true;
070:
071: /**
072: * 'Keep generated source files' flag.
073: */
074: private boolean keepGenerated = false;
075:
076: /**
077: * Constructor.
078: * @param out output stream for messages.
079: */
080: public Main(OutputStream out) {
081:
082: notifier = new Notifier(out);
083: classNames = new Vector();
084: loader = new Loader(notifier);
085: }
086:
087: /**
088: * Main program.
089: * @param argv command line arguments given by user.
090: */
091: public static void main(String argv[]) {
092:
093: Main compiler = new Main(System.out);
094: System.exit(compiler.compile(argv) ? 0 : 1);
095: }
096:
097: /**
098: * Run the compiler.
099: * @param argv command line arguments given by user.
100: * @return true if compilation was successful
101: */
102: public boolean compile(String argv[]) {
103:
104: if (!parseArgs(argv)) {
105: usage();
106: return false;
107: }
108:
109: return doCompile();
110: }
111:
112: /**
113: * Parse the arguments for compile.
114: * @param argv command line arguments given by user.
115: * @return true if command line is parsed without errors
116: */
117: public boolean parseArgs(String argv[]) {
118:
119: // Parse arguments
120: for (int i = 0; i < argv.length; i++) {
121:
122: if (argv[i].equals("-classpath")) {
123: if ((i + 1) < argv.length) {
124: if (!loader.setClassPath(argv[++i]))
125: return false;
126: classPath = argv[i];
127: } else {
128: notifier.error("rmic.option.requires.argument",
129: "-classpath");
130: return false;
131: }
132: } else if (argv[i].equals("-d")) {
133: if ((i + 1) < argv.length) {
134: if (destDir != null) {
135: notifier
136: .error("rmic.option.already.seen", "-d");
137: return false;
138: }
139: destDir = new File(argv[++i]);
140: if (!destDir.exists()) {
141: notifier.error("rmic.no.such.directory",
142: destDir.getPath());
143: return false;
144: }
145: }
146: } else if (argv[i].equals("-nocompile")) {
147: compile = false;
148: } else if (argv[i].equals("-keep")) {
149: keepGenerated = true;
150: } else if (argv[i].startsWith("-")) {
151: notifier.error("rmic.no.such.option", argv[i]);
152: return false;
153: } else {
154: classNames.addElement(argv[i]);
155: }
156: }
157:
158: if (classNames.size() == 0) {
159: return false;
160: }
161:
162: return true;
163: }
164:
165: /**
166: * Do the compile with the switches and files already supplied.
167: * @return true if compilation was successful
168: */
169: public boolean doCompile() {
170:
171: if (!(loader.loadClass("java/rmi/Remote")
172: && loader.loadClass("java/rmi/RemoteException") && loader
173: .loadClass("java/lang/RuntimeException"))) {
174: return false;
175: }
176:
177: // load classes
178:
179: for (int i = 0; i < classNames.size(); i++) {
180: String name = (String) classNames.elementAt(i);
181: if (!loader.loadClass(name)) {
182: return false;
183: }
184: }
185:
186: // verify classes
187:
188: for (int i = 0; i < classNames.size(); i++) {
189:
190: String name = (String) classNames.elementAt(i);
191:
192: if (!verify(name)) {
193: return false;
194: }
195: }
196:
197: // create stubs
198:
199: Vector fileNames = new Vector();
200:
201: for (int i = 0; i < classNames.size(); i++) {
202:
203: String name = (String) classNames.elementAt(i);
204:
205: String filePath = name.replace('.', File.separatorChar);
206: String packagePath = "";
207:
208: int index = filePath.lastIndexOf(File.separatorChar);
209:
210: if (index != -1) {
211: packagePath = filePath.substring(0, index);
212: filePath = filePath.substring(index + 1);
213: }
214:
215: filePath = filePath + "_Stub.java";
216:
217: File stubFile;
218:
219: if (destDir != null) {
220: File packageDir = new File(destDir, packagePath);
221: /*
222: * Make sure that the directory for this package exists.
223: * We assume that the caller has verified that the top-
224: * level destination directory exists, so we don't have
225: * to worry about creating it unintentionally.
226: */
227: if (!packageDir.exists()) {
228: packageDir.mkdirs();
229: }
230: stubFile = new File(packageDir, filePath);
231: } else {
232: /*
233: * If a top-level destination directory is not specified
234: * (with the "-d" option), we just put the generated files
235: * in the current working directory, which was the behavior
236: * of rmic in JDK 1.1. This feels less than ideal, but
237: * there is no easy alternative.
238: */
239: stubFile = new File(System.getProperty("user.dir"),
240: filePath);
241: }
242:
243: try {
244: stubFile.createNewFile();
245:
246: fileNames.add(stubFile.getCanonicalPath());
247:
248: IndentingWriter out = new IndentingWriter(
249: new OutputStreamWriter(new FileOutputStream(
250: stubFile)));
251:
252: writeStub(name, out);
253:
254: out.close();
255:
256: } catch (IOException e) {
257: notifier.error("rmic.ioerror.writing", stubFile
258: .getPath());
259: return false;
260: }
261: }
262:
263: if (!compile) {
264: return true;
265: }
266:
267: // compile the stubs
268:
269: String command = System.getProperty("JAVAC_PATH");
270:
271: if (command == null || !new File(command).exists()) {
272: notifier.error("rmic.compiler.not.found");
273: return false;
274: }
275:
276: command += " -classpath " + classPath;
277:
278: if (destDir != null) {
279: command += " -d " + destDir.getPath();
280: }
281:
282: for (int i = 0; i < fileNames.size(); i++) {
283: command += " " + (String) fileNames.elementAt(i);
284: }
285:
286: Process p;
287: try {
288: p = Runtime.getRuntime().exec(command);
289: } catch (IOException e) {
290: notifier.error("rmic.compilation.error");
291: return false;
292: }
293:
294: (new StreamReader(p.getInputStream())).start();
295: (new StreamReader(p.getErrorStream(), notifier)).start();
296:
297: try {
298: p.waitFor();
299: } catch (InterruptedException e) {
300: notifier.error("rmic.compilation.error");
301: return false;
302: }
303:
304: if (!keepGenerated) {
305: for (int i = 0; i < fileNames.size(); i++) {
306: new File((String) fileNames.elementAt(i)).delete();
307: }
308: }
309:
310: if (p.exitValue() != 0) {
311: notifier.error("rmic.compilation.failed");
312: return false;
313: }
314:
315: return true;
316: }
317:
318: /**
319: * Prints usage message.
320: */
321: public void usage() {
322: notifier.error("rmic.usage");
323: }
324:
325: /**
326: * Verify that interface specified in command line complies with
327: * JCRMI limitations.
328: * @param name interface name
329: * @return verification result
330: */
331: public boolean verify(String name) {
332:
333: JClass c = loader.getClass(name);
334:
335: if (!c.isInterface()) {
336: notifier.error("rmic.cant.make.stubs.for.class", name);
337: return false;
338: }
339:
340: if (!c.isRemote()) {
341: notifier.error("rmic.must.implement.remote", name);
342: return false;
343: }
344:
345: // Verify this interface and all interfaces extended by this
346: // interface
347: Vector remoteInterfaces = new Vector();
348:
349: addInterface(c, remoteInterfaces);
350:
351: if (!verifyInterfaces(remoteInterfaces)) {
352: return false;
353: }
354:
355: return true;
356: }
357:
358: /**
359: * Verify all the interfaces in vector for compliance with JCRMI
360: * limitations.
361: * @param interfaces the list of interfaces
362: * @return verification result
363: */
364: private boolean verifyInterfaces(Vector interfaces) {
365:
366: for (int i = 0; i < interfaces.size(); i++) {
367:
368: if (!verifyInterface((JClass) interfaces.elementAt(i))) {
369: return false;
370: }
371: }
372: return true;
373: }
374:
375: /**
376: * Verify that interface complies with JCRMI limitations.
377: * @param cl interface to be verified
378: * @return verification result
379: */
380: private boolean verifyInterface(JClass cl) {
381:
382: if (cl.isVerified()) {
383: return true;
384: }
385:
386: cl.setVerified();
387:
388: JMethod[] methods = cl.getMethods();
389:
390: for (int i = 0; i < methods.length; i++) {
391:
392: if (!verifyRemoteMethod(cl.getClassName(), methods[i]))
393: return false;
394: }
395:
396: return true;
397: }
398:
399: /**
400: * The list of exceptions that can be thrown by remote methods.
401: */
402: private static String[] JCExceptions = { "java/lang/Throwable",
403: "java/lang/ArithmeticException",
404: "java/lang/ArrayIndexOutOfBoundsException",
405: "java/lang/ArrayStoreException",
406: "java/lang/ClassCastException", "java/lang/Exception",
407: "java/lang/IndexOutOfBoundsException",
408: "java/lang/NegativeArraySizeException",
409: "java/lang/NullPointerException",
410: "java/lang/RuntimeException",
411: "java/lang/SecurityException", "java/io/IOException",
412: "java/rmi/RemoteException",
413: "javacard/framework/APDUException",
414: "javacard/framework/CardException",
415: "javacard/framework/CardRuntimeException",
416: "javacard/framework/ISOException",
417: "javacard/framework/PINException",
418: "javacard/framework/SystemException",
419: "javacard/framework/TransactionException",
420: "javacard/framework/UserException",
421: "javacard/security/CryptoException",
422: "javacard/framework/service/ServiceException" };
423:
424: /**
425: * Verify that remote method complies with JCRMI limitations.
426: * @param class_name the name of class that defines the method
427: * @param m the method to be verified
428: * @return verification result
429: */
430: private boolean verifyRemoteMethod(String class_name, JMethod m) {
431:
432: // check signature
433:
434: String descriptor = m.getMethodDescriptor();
435:
436: Vector v = RemoteMethod.parseDescriptor(descriptor);
437:
438: boolean ok = (v != null);
439:
440: if (ok) {
441:
442: String parameter = (String) v.elementAt(v.size() - 1);
443:
444: if (parameter.startsWith("L")) {
445:
446: parameter = parameter.substring(1,
447: parameter.length() - 1);
448:
449: if (!loader.loadClass(parameter)) {
450: return false;
451: }
452:
453: JClass ret = loader.getClass(parameter);
454:
455: ok = ok && ret.isInterface() && ret.isRemote();
456:
457: if (ok) {
458:
459: Vector z = new Vector();
460:
461: addInterface(ret, z);
462:
463: if (!verifyInterfaces(z)) {
464: return false;
465: }
466: }
467: }
468: }
469:
470: if (!ok) {
471: notifier.error("rmic.incorrect.method.signature",
472: class_name, m.getMethodName() + descriptor);
473: return false;
474: }
475:
476: // check that all exceptions are defined in Java Card API
477: // and that method throws RemoteException
478:
479: JClass jrRemoteException = loader
480: .getClass("java/rmi/RemoteException");
481: boolean RemoteThrown = false;
482:
483: String[] exceptions = m.getExceptionsThrown();
484:
485: if (exceptions != null) {
486:
487: for (int i = 0; i < exceptions.length; i++) {
488:
489: boolean found = false;
490:
491: for (int j = 0; j < JCExceptions.length; j++) {
492:
493: if (exceptions[i].equals(JCExceptions[j])) {
494: found = true;
495: break;
496: }
497: }
498:
499: if (!found) {
500: notifier.error(
501: "rmic.method.throws.invalid.exception",
502: class_name, m.getMethodName() + descriptor,
503: exceptions[i]);
504: return false;
505: }
506:
507: JClass ex = loader.getClass(exceptions[i]);
508:
509: RemoteThrown = RemoteThrown
510: || jrRemoteException.isSubclass(ex);
511: }
512:
513: }
514:
515: if (!RemoteThrown) {
516: notifier.error("rmic.must.throw.remoteexception",
517: class_name, m.getMethodName() + descriptor);
518: return false;
519: }
520:
521: return true;
522: }
523:
524: /**
525: * Writes the stub for remote interface into the stream.
526: * @param className interface name
527: * @param p output stream
528: * @throws IOException if I/O error occurs
529: */
530: public void writeStub(String className, IndentingWriter p)
531: throws IOException {
532:
533: JClass c = loader.getClass(className);
534:
535: // find all remote interfaces
536:
537: Vector remoteInterfaces = new Vector();
538: addInterface(c, remoteInterfaces);
539:
540: // find all remote methods
541:
542: Hashtable remoteMethods = new Hashtable();
543:
544: for (int i = 0; i < remoteInterfaces.size(); i++) {
545:
546: JClass cl = (JClass) remoteInterfaces.elementAt(i);
547: JMethod[] methods = cl.getMethods();
548:
549: for (int j = 0; j < methods.length; j++) {
550:
551: String m_name = methods[j].getMethodName();
552: String m_descriptor = methods[j].getMethodDescriptor();
553:
554: RemoteMethod m = new RemoteMethod(m_name, m_descriptor,
555: loader);
556:
557: String[] exceptions = methods[j].getExceptionsThrown();
558:
559: if (exceptions != null) {
560: for (int k = 0; k < exceptions.length; k++) {
561: m.addException(loader.getClass(exceptions[k]));
562: }
563: }
564:
565: String key = m_name + m_descriptor;
566:
567: RemoteMethod m_old = (RemoteMethod) remoteMethods
568: .get(key);
569:
570: if (m_old == null) {
571: remoteMethods.put(key, m);
572: } else {
573: m_old.merge(m);
574: }
575: }
576: }
577:
578: p.pln("// Stub class generated by jcrmic, do not edit.");
579: p.pln("// Contents subject to change without notice.");
580: p.pln();
581:
582: String name = c.getClassName().replace('/', '.');
583: String pname = "";
584: int index = name.lastIndexOf('.');
585:
586: if (index != -1) {
587:
588: pname = name.substring(0, index);
589: name = name.substring(index + 1);
590: }
591:
592: name = name + "_Stub";
593:
594: /*
595: * If remote implementation class was in a particular package,
596: * declare the stub class to be in the same package.
597: */
598: if (!pname.equals("")) {
599: p.pln("package " + pname + ";");
600: p.pln();
601: }
602:
603: /*
604: * Declare the class; implement all remote interfaces.
605: */
606:
607: String s = "";
608:
609: for (int i = 0; i < remoteInterfaces.size(); i++) {
610:
611: JClass cl = (JClass) remoteInterfaces.elementAt(i);
612:
613: if (cl.isRemote()) {
614:
615: if (!s.equals("")) {
616: s = s + ", ";
617: }
618:
619: s = s + cl.getClassName().replace('/', '.');
620: }
621: }
622:
623: p.plnI("public final class " + name);
624: p.pln("extends javax.microedition.jcrmi.RemoteStub");
625: p.pln("implements " + s + " {");
626:
627: p.pln("");
628:
629: p.pln("// constructor");
630:
631: p.plnI("public " + name + "() {");
632: p.pln("super();");
633: p.pOln("}");
634:
635: for (Enumeration methods = remoteMethods.elements(); methods
636: .hasMoreElements();) {
637:
638: ((RemoteMethod) methods.nextElement()).write(p);
639: }
640:
641: p.pOln("}");
642: }
643:
644: /**
645: * Put this interface and all interfaces extended by this interface
646: * into vector.
647: * @param cl an interface
648: * @param v target vector
649: */
650: public static void addInterface(JClass cl, Vector v) {
651:
652: /* !!! debug */
653: if (!cl.isInterface()) {
654: System.out.println("error - addInterface");
655: System.exit(1);
656: }
657:
658: if (!v.contains(cl)) {
659:
660: v.add(cl);
661:
662: JClass[] interfaces = cl.getInterfaces();
663:
664: for (int i = 0; i < interfaces.length; i++) {
665: addInterface(interfaces[i], v);
666: }
667: }
668: }
669: }
|