? X.diff ? manifest-subst.mf ? netbeans ? test/X.diff Index: bootstrap/src/org/netbeans/Bundle.properties =================================================================== RCS file: bootstrap/src/org/netbeans/Bundle.properties diff -N bootstrap/src/org/netbeans/Bundle.properties --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ bootstrap/src/org/netbeans/Bundle.properties 2 Dec 2003 09:22:46 -0000 @@ -0,0 +1,14 @@ +# Sun Public License Notice +# +# The contents of this file are subject to the Sun Public License +# Version 1.0 (the "License"). You may not use this file except in +# compliance with the License. A copy of the License is available at +# http://www.sun.com/ +# +# The Original Code is NetBeans. The Initial Developer of the Original +# Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun +# Microsystems, Inc. All Rights Reserved. + +#Question to show user to find out whether start second instance of NetBeans or not +MSG_AlreadyRunning=Another version of the program seems to be running. Continue anyway? +MSG_AlreadyRunningTitle=Warning Index: bootstrap/src/org/netbeans/CLIHandler.java =================================================================== RCS file: bootstrap/src/org/netbeans/CLIHandler.java diff -N bootstrap/src/org/netbeans/CLIHandler.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ bootstrap/src/org/netbeans/CLIHandler.java 2 Dec 2003 09:22:46 -0000 @@ -0,0 +1,811 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans; + +import java.io.*; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; + +/** + * Command Line Interface and User Directory Locker support class. + * Subclasses may be registered into the system to handle special command-line options. + * To be registered, use META-INF/services/org.netbeans.CLIHandler + * in a JAR file in the startup or dynamic class path (e.g. lib/ext/ + * or lib/). + * @author Jaroslav Tulach + * @since org.netbeans.core/1 1.18 + * @see "#32054" + * @see Specification + */ +public abstract class CLIHandler extends Object { + /** lenght of the key used for connecting */ + private static final int KEY_LENGTH = 10; + /** ok reply */ + private static final int REPLY_OK = 1; + /** sends exit code */ + private static final int REPLY_EXIT = 2; + /** fail reply */ + private static final int REPLY_FAIL = 0; + + /** request to read from input stream */ + private static final int REPLY_READ = 10; + /** request to write */ + private static final int REPLY_WRITE = 11; + /** request to find out how much data is available */ + private static final int REPLY_AVAILABLE = 12; + + /** + * Used during bootstrap sequence. Should only be used by core, not modules. + */ + public static final int WHEN_BOOT = 1; + /** + * Used during later initialization or while NetBeans is up and running. + */ + public static final int WHEN_INIT = 2; + + private int when; + + /** + * Create a CLI handler and indicate its preferred timing. + * @param when when to run the handler: {@link #WHEN_BOOT} or {@link #WHEN_INIT} + */ + protected CLIHandler(int when) { + this.when = when; + } + + /** + * Process some set of command-line arguments. + * Unrecognized or null arguments should be ignored. + * Recognized arguments should be nulled out. + * @param args arguments + * @return error value or 0 if everything is all right + */ + protected abstract int cli(Args args); + + private static void showHelp(PrintWriter w, List handlers) { + w.println("-?"); + w.println("-help"); + w.println(" Show this help information."); + Iterator it = handlers.iterator(); + while (it.hasNext()) { + ((CLIHandler)it.next()).usage(w); + } + } + + /** + * Print usage information for this handler. + * @param w a writer to print to + */ + protected abstract void usage(PrintWriter w); + + /** For testing purposes we can block the + * algorithm in any place in the initialize method. + */ + private static void enterState(int state, Integer block) { + if (block == null) return; + + // for easier debugging of CLIHandlerTest + //System.err.println("state: " + state + " block: " + block + " thread: " + Thread.currentThread()); + + synchronized (block) { + if (state == block.intValue()) { + block.notifyAll(); + try { + block.wait(); + } catch (InterruptedException ex) { + throw new IllegalStateException(); + } + } + } + } + + /** Notification of available handlers. + * @return non-zero if one of the handlers fails + */ + private static int notifyHandlers(Args args, List handlers, int when, boolean failOnUnknownOptions, boolean consume) { + try { + //System.err.println("notifyHandlers: handlers=" + handlers + " when=" + when + " args=" + Arrays.asList(args.getArguments())); + if (failOnUnknownOptions) { + String[] argv = args.getArguments(); + for (int i = 0; i < argv.length; i++) { + if (argv[i].equals("-?") || argv[i].equals("-help")) { // NOI18N + PrintWriter w = new PrintWriter(args.getOutputStream()); + showHelp(w, handlers); + w.flush(); + return 2; + } + } + } + int r = 0; + Iterator it = handlers.iterator(); + while (it.hasNext()) { + CLIHandler h = (CLIHandler)it.next(); + if (h.when != when) continue; + + r = h.cli(args); + //System.err.println("notifyHandlers: exit code " + r + " from " + h); + if (r != 0) { + return r; + } + } + if (failOnUnknownOptions) { + String[] argv = args.getArguments(); + for (int i = 0; i < argv.length; i++) { + if (argv[i] != null) { + // Unhandled option. + PrintWriter w = new PrintWriter(args.getOutputStream()); + w.println("Unknown option: " + argv[i]); // NOI18N + showHelp(w, handlers); + w.flush(); + return 2; + } + } + } + return 0; + } finally { + args.reset(consume); + } + } + + /** + * Represents result of initialization. + * @see #initialize(String[], ClassLoader) + * @see #initialize(Args, Integer, List) + */ + static final class Status { + public static final int CANNOT_CONNECT = -255; + + private final File lockFile; + private final int port; + private final int exitCode; + /** + * General failure. + */ + Status() { + this(0); + } + /** + * Failure due to a parse problem. + * @param c bad status code (not 0) + * @see #cli(Args) + */ + Status(int c) { + this(null, 0, c); + } + /** + * Some measure of success. + * @param l the lock file (not null) + * @param p the server port (not 0) + * @param c a status code (0 or not) + */ + Status(File l, int p, int c) { + lockFile = l; + port = p; + exitCode = c; + } + /** + * Get the lock file, if available. + * @return the lock file, or null if there is none + */ + public File getLockFile() { + return lockFile; + } + /** + * Get the server port, if available. + * @return a port number for the server, or 0 if there is no port open + */ + public int getServerPort() { + return port; + } + /** + * Get the CLI parse status. + * @return 0 for success, some other value for error conditions + */ + public int getExitCode() { + return exitCode; + } + } + + + /** Initializes the system by creating lock file. + * + * @param args the command line arguments to recognize + * @param classloader to find command CLIHandlers in + * @param doAllInit if true, run all WHEN_INIT handlers now; else wait for {@link #finishInitialization} + * @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help) + * @param cleanLockFile removes lock file if it appears to be dead + * @return the file to be used as lock file or null parsing of args failed + */ + static Status initialize(String[] args, ClassLoader loader, boolean doAllInit, boolean failOnUnknownOptions, boolean cleanLockFile) { + return initialize(new Args(args, System.in, System.err, System.getProperty ("user.dir")), (Integer)null, allCLIs(loader), doAllInit, failOnUnknownOptions, cleanLockFile); + } + + /** + * What to do later when {@link #finishInitialization} is called. + * May remain null. + */ + private static Runnable doLater = null; + + /** Initializes the system by creating lock file. + * + * @param args the command line arguments to recognize + * @param block the state we want to block in + * @param handlers all handlers to use + * @param doAllInit if true, run all WHEN_INIT handlers now; else wait for {@link #finishInitialization} + * @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help) + * @param cleanLockFile removes lock file if it appears to be dead + * @return a status summary + */ + static Status initialize(final Args args, Integer block, final List handlers, boolean doAllInit, final boolean failOnUnknownOptions, boolean cleanLockFile) { + // initial parsing of args + { + int r = notifyHandlers(args, handlers, WHEN_BOOT, false, failOnUnknownOptions); + if (r != 0) { + return new Status(r); + } + } + + // get the value + String home = System.getProperty("netbeans.user"); // NOI18N + if (home == null) { + home = System.getProperty("user.home"); // NOI18N + } + File lockFile = new File(home, "lock"); // NOI18N + + for (int i = 0; i < 5; i++) { + // try few times to succeed + try { + if (lockFile.exists()) { + enterState(5, block); + throw new IOException("EXISTS"); // NOI18N + } + lockFile.getParentFile().mkdirs(); + lockFile.createNewFile(); + lockFile.deleteOnExit(); + try { + // try to make it only user-readable (on Unix) + // since people are likely to leave a+r on their userdir + File chmod = new File("/bin/chmod"); // NOI18N + if (!chmod.isFile()) { + // Linux uses /bin, Solaris /usr/bin, others hopefully one of those + chmod = new File("/usr/bin/chmod"); // NOI18N + } + if (chmod.isFile()) { + int chmoded = Runtime.getRuntime().exec(new String[] { + chmod.getAbsolutePath(), + "go-rwx", // NOI18N + lockFile.getAbsolutePath() + }).waitFor(); + if (chmoded != 0) { + throw new IOException("could not run " + chmod + " go-rwx " + lockFile); // NOI18N + } + } + } catch (InterruptedException e) { + throw (IOException)new IOException(e.toString()).initCause(e); + } + + enterState(10, block); + + byte[] arr = new byte[KEY_LENGTH]; + try { + SecureRandom.getInstance("SHA1PRNG").nextBytes(arr); // NOI18N + } catch (NoSuchAlgorithmException e) { + // #36966: IBM JDK doesn't have it. + try { + SecureRandom.getInstance("IBMSecureRandom").nextBytes(arr); // NOI18N + } catch (NoSuchAlgorithmException e2) { + // OK, disable server... + System.err.println("WARNING: remote IDE automation features cannot be cryptographically secured, so disabling; please reopen http://www.netbeans.org/issues/show_bug.cgi?id=36966"); // NOI18N + e.printStackTrace(); + return new Status(); + } + } + + Server server = new Server(arr, block, handlers, failOnUnknownOptions); + + DataOutputStream os = new DataOutputStream(new FileOutputStream(lockFile)); + int p = server.getLocalPort(); + os.writeInt(p); + + enterState(20, block); + + os.write(arr); + os.close(); + + int exitCode; + if (doAllInit) { + exitCode = notifyHandlers(args, handlers, WHEN_INIT, failOnUnknownOptions, failOnUnknownOptions); + } else { + doLater = new Runnable() { + public void run() { + int r = notifyHandlers(args, handlers, WHEN_INIT, failOnUnknownOptions, failOnUnknownOptions); + if (r != 0) { + // Not much to do about it. + System.err.println("Post-initialization command-line options could not be run."); // NOI18N + //System.err.println("r=" + r + " args=" + java.util.Arrays.asList(args.getArguments())); + } + } + }; + exitCode = 0; + } + + enterState(0, block); + return new Status(lockFile, server.getLocalPort(), exitCode); + + } catch (IOException ex) { + if (!"EXISTS".equals(ex.getMessage())) { // NOI18N + ex.printStackTrace(); + } + // already exists, try to read + byte[] key = null; + int port = -1; + try { + DataInputStream is = new DataInputStream(new FileInputStream(lockFile)); + port = is.readInt(); + key = new byte[KEY_LENGTH]; + is.readFully(key); + is.close(); + } catch (IOException ex2) { + // ok, try to read it once more + } + + if (key != null && port != -1) { + try { + // ok, try to connect + enterState(28, block); + Socket socket = new Socket(InetAddress.getLocalHost(), port); + // wait max of 1s for reply + socket.setSoTimeout(5000); + DataOutputStream os = new DataOutputStream(socket.getOutputStream()); + os.write(key); + os.flush(); + + enterState(30, block); + + DataInputStream replyStream = new DataInputStream(socket.getInputStream()); + COMMUNICATION: for (;;) { + enterState(32, block); + int reply = replyStream.read(); + //System.err.println("reply=" + reply); + enterState(34, block); + + switch (reply) { + case REPLY_FAIL: + enterState(36, block); + break COMMUNICATION; + case REPLY_OK: + enterState(38, block); + // write the arguments + String[] arr = args.getArguments(); + os.writeInt(arr.length); + for (int a = 0; a < arr.length; a++) { + os.writeUTF(arr[a]); + } + os.writeUTF (args.getCurrentDirectory().toString()); + os.flush(); + break; + case REPLY_EXIT: + int exitCode = replyStream.readInt(); + if (exitCode == 0) { + // to signal end of the world + exitCode = -1; + } + + os.close(); + replyStream.close(); + + enterState(0, block); + return new Status(lockFile, port, exitCode); + case REPLY_READ: { + enterState(42, block); + int howMuch = replyStream.readInt(); + byte[] byteArr = new byte[howMuch]; + args.getInputStream().read(byteArr); + os.write(byteArr); + os.flush(); + break; + } + case REPLY_WRITE: { + enterState(44, block); + int howMuch = replyStream.readInt(); + byte[] byteArr = new byte[howMuch]; + replyStream.read(byteArr); + args.getOutputStream().write(byteArr); + break; + } + case REPLY_AVAILABLE: + enterState(46, block); + os.writeInt(args.getInputStream().available()); + os.flush(); + break; + case -1: + // EOF. Why does this happen? + break; + default: + throw new IllegalStateException ("Reply: " + reply); // NOI18N + } + } + + // connection ok, but secret key not recognized + // delete the lock file + } catch (java.net.SocketTimeoutException ex2) { + // connection failed, the port is dead + enterState(33, block); + } catch (IOException ex2) { + // some strange exception + ex2.printStackTrace(); + enterState(33, block); + } + if (cleanLockFile) { + // remove the file and try once more + lockFile.delete(); + } else { + return new Status (Status.CANNOT_CONNECT); + } + } + } + + try { + enterState(83, block); + Thread.sleep((int)(Math.random() * 1000.00)); + enterState(85, block); + } catch (InterruptedException ex) { + // means nothing + } + } + + // failure + return new Status(); + } + + /** + * Run any {@link #WHEN_INIT} handlers that were passed to the original command line. + * Should be called when the system is up and ready. + * Cancels any existing actions, in case it is called twice. + */ + static void finishInitialization() { + if (doLater != null) { + doLater.run(); + doLater = null; + } + } + + /** For a given classloader finds all registered CLIHandlers. + */ + private static List allCLIs(ClassLoader loader) { + /* should be, but we cannot use it yet, as openide is not separated: + return new ArrayList(Lookups.metaInfServices(loader).lookup(new Lookup.Template(CLIHandler.class)).allInstances()); + */ + List res = new ArrayList(); + Enumeration en; + try { + en = loader.getResources("META-INF/services/org.netbeans.CLIHandler"); // NOI18N + } catch (IOException ex) { + ex.printStackTrace(); + return Collections.EMPTY_LIST; + } + while (en.hasMoreElements()) { + URL url = (URL)en.nextElement(); + try { + InputStream is = url.openStream(); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); // NOI18N + while (true) { + String line = reader.readLine(); + if (line == null) break; + + // Ignore blank lines and comments. + line = line.trim(); + if (line.length() == 0) continue; + + boolean remove = false; + if (line.charAt(0) == '#') { + if (line.length() == 1 || line.charAt(1) != '-') { + continue; + } + + // line starting with #- is a sign to remove that class from lookup + remove = true; + line = line.substring(2); + } + Class inst = Class.forName(line, false, loader); + + Object obj = inst.newInstance(); + res.add((CLIHandler)obj); + } + } finally { + is.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return res; + } + + /** Class that represents available arguments to the CLI + * handlers. + */ + public static final class Args extends Object { + private String[] args; + private final String[] argsBackup; + private InputStream is; + private OutputStream os; + private File currentDir; + + Args(String[] args, InputStream is, OutputStream os, String currentDir) { + argsBackup = args; + reset(false); + this.is = is; + this.os = os; + this.currentDir = new File (currentDir); + } + + /** + * Restore the arguments list to a clean state. + * If not consuming arguments, it is just set to the original list. + * If consuming arguments, any nulled-out arguments are removed from the list. + */ + void reset(boolean consume) { + if (consume) { + String[] a = args; + if (a == null) { + a = argsBackup; + } + List l = new ArrayList(Arrays.asList(a)); + l.removeAll(Collections.singleton(null)); + args = (String[])l.toArray(new String[l.size()]); + } else { + args = (String[])argsBackup.clone(); + } + } + + /** + * Get the command-line arguments. + * You may not modify the returned array except to set some elements + * to null as you recognize them. + * @return array of string arguments, may contain nulls + */ + public String[] getArguments() { + return args; + } + + /** + * Get an output stream to which data may be sent. + * @return stream to write to + */ + public OutputStream getOutputStream() { + return os; + } + + public File getCurrentDirectory () { + return currentDir; + } + + /** + * Get an input stream that may supply additional data. + * @return stream to read from + */ + public InputStream getInputStream() { + return is; + } + } // end of Args + + /** Server that creates local socket and communicates with it. + */ + private static final class Server extends Thread { + private byte[] key; + private ServerSocket socket; + private Integer block; + private List handlers; + private Socket work; + private static volatile int counter; + private final boolean failOnUnknownOptions; + + public Server(byte[] key, Integer block, List handlers, boolean failOnUnknownOptions) throws IOException { + super("CLI Requests Server"); // NOI18N + this.key = key; + this.setDaemon(true); + this.block = block; + this.handlers = handlers; + this.failOnUnknownOptions = failOnUnknownOptions; + + socket = new ServerSocket(0); + + start(); + } + + public Server(Socket request, byte[] key, Integer block, List handlers, boolean failOnUnknownOptions) throws IOException { + super("CLI Handler Thread Handler: " + ++counter); // NOI18N + this.key = key; + this.setDaemon(true); + this.block = block; + this.handlers = handlers; + this.work = request; + this.failOnUnknownOptions = failOnUnknownOptions; + + start(); + } + + public int getLocalPort() { + return socket.getLocalPort(); + } + + public void run() { + if (work != null) { + // I am a worker not listener server + try { + handleConnect(work); + } catch (IOException ex) { + ex.printStackTrace(); + } + return; + } + + + while (true) { + try { + enterState(65, block); + Socket s = socket.accept(); + + // spans new request handler + new Server(s, key, block, handlers, failOnUnknownOptions); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + + private void handleConnect(Socket s) throws IOException { + + byte[] check = new byte[key.length]; + DataInputStream is = new DataInputStream(s.getInputStream()); + + enterState(70, block); + + is.readFully(check); + + enterState(90, block); + + DataOutputStream os = new DataOutputStream(s.getOutputStream()); + + if (Arrays.equals(check, key)) { + enterState(93, block); + os.write(REPLY_OK); + os.flush(); + + // continue with arguments + int numberOfArguments = is.readInt(); + String[] args = new String[numberOfArguments]; + for (int i = 0; i < args.length; i++) { + args[i] = is.readUTF(); + } + String currentDir = is.readUTF (); + + Args arguments = new Args(args, new IS(is, os), new OS(is, os), currentDir); + int res = notifyHandlers(arguments, handlers, WHEN_INIT, failOnUnknownOptions, false); + + if (res == 0) { + enterState(98, block); + } else { + enterState(99, block); + } + + os.write(REPLY_EXIT); + os.writeInt(res); + } else { + enterState(103, block); + os.write(REPLY_FAIL); + } + + + enterState(120, block); + + os.close(); + is.close(); + } + + private static final class IS extends InputStream { + private DataInputStream is; + private DataOutputStream os; + + public IS(DataInputStream is, DataOutputStream os) { + this.is = is; + this.os = os; + } + + public int read() throws IOException { + byte[] arr = new byte[1]; + if (read(arr) == 1) { + return arr[0]; + } else { + return -1; + } + } + + public void close() throws IOException { + super.close(); + } + + public int available() throws IOException { + // ask for data + os.write(REPLY_AVAILABLE); + os.flush(); + // read provided data + return is.readInt(); + } + + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException { + // ask for data + os.write(REPLY_READ); + os.writeInt(len); + os.flush(); + // read provided data + return is.read(b, off, len); + } + + } // end of IS + + private static final class OS extends OutputStream { + private DataOutputStream os; + + public OS(DataInputStream is, DataOutputStream os) { + this.os = os; + } + + public void write(int b) throws IOException { + byte[] arr = { (byte)b }; + write(arr); + } + + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + public void close() throws IOException { + super.close(); + } + + public void flush() throws IOException { + os.flush(); + } + + public void write(byte[] b, int off, int len) throws IOException { + os.write(REPLY_WRITE); + os.writeInt(len); + os.write(b, off, len); + } + + } // end of OS + + } // end of Server + + +} Index: bootstrap/src/org/netbeans/Main.java =================================================================== RCS file: /cvs/core/bootstrap/src/org/netbeans/Main.java,v retrieving revision 1.8.2.1.16.1 diff -u -r1.8.2.1.16.1 Main.java --- bootstrap/src/org/netbeans/Main.java 8 Jul 2003 15:18:55 -0000 1.8.2.1.16.1 +++ bootstrap/src/org/netbeans/Main.java 2 Dec 2003 09:22:46 -0000 @@ -16,18 +16,19 @@ import java.io.File; import java.util.*; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; import java.util.jar.JarFile; import java.security.*; +import java.net.*; /** Bootstrap main class. * @author Jaroslav Tulach, Jesse Glick */ public class Main extends Object { + /** Starts the IDE. - * @param args the command line arguments - */ + * @param args the command line arguments + * @throws Exception for lots of reasons + */ public static void main (String args[]) throws Exception { ArrayList list = new ArrayList (); @@ -92,8 +93,45 @@ // Note that ModuleManager.updateContextClassLoaders will later change // the loader on this and other threads to be MM.SystemClassLoader anyway. Thread.currentThread().setContextClassLoader (loader); + + + // + // Evaluate command line interfaces and lock the user directory + // + + CLIHandler.Status result; + result = CLIHandler.initialize(args, loader, false, true, false); + if (result.getExitCode () == CLIHandler.Status.CANNOT_CONNECT) { + int value = javax.swing.JOptionPane.showConfirmDialog ( + null, + java.util.ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunning"), + java.util.ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunningTitle"), + javax.swing.JOptionPane.OK_CANCEL_OPTION, + javax.swing.JOptionPane.WARNING_MESSAGE + ); + if (value == javax.swing.JOptionPane.OK_OPTION) { + result = CLIHandler.initialize(args, loader, false, true, true); + } + } + + int res = result.getExitCode(); + if (res == -1) { + // Connected to another running NB instance and succeeded in making a call. + System.exit(0); + } else if (res != 0) { + // Some CLIHandler refused the invocation + System.exit(res); + } m.invoke (null, new Object[] { args }); + } + + /** + * Call when the system is up and running, to complete handling of + * delayed command-line options like -open FILE. + */ + public static void finishInitialization() { + CLIHandler.finishInitialization(); } private static final class BootClassLoader extends JarClassLoader { Index: src/META-INF/services/org.netbeans.CLIHandler =================================================================== RCS file: src/META-INF/services/org.netbeans.CLIHandler diff -N src/META-INF/services/org.netbeans.CLIHandler --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/META-INF/services/org.netbeans.CLIHandler 2 Dec 2003 09:22:46 -0000 @@ -0,0 +1 @@ +org.netbeans.core.CLIOptions Index: src/org/netbeans/core/Bundle.properties =================================================================== RCS file: /cvs/core/src/org/netbeans/core/Bundle.properties,v retrieving revision 1.358.4.8.4.2 diff -u -r1.358.4.8.4.2 Bundle.properties --- src/org/netbeans/core/Bundle.properties 14 Nov 2003 13:18:57 -0000 1.358.4.8.4.2 +++ src/org/netbeans/core/Bundle.properties 2 Dec 2003 09:22:46 -0000 @@ -171,7 +171,6 @@ ERR_UIError=An error occured when setting the specified UI, using default UI ... ERR_FontSizeExpected=Font size expected, using default font size... ERR_BadFontSize=Bad format of the font size, using default font size... -ERR_UnknownOption=Unknown option # Notify system # {0} - class name of exception Index: src/org/netbeans/core/CLIOptions.java =================================================================== RCS file: src/org/netbeans/core/CLIOptions.java diff -N src/org/netbeans/core/CLIOptions.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/netbeans/core/CLIOptions.java 2 Dec 2003 09:22:46 -0000 @@ -0,0 +1,173 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.core; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import java.io.*; +import java.security.*; +import java.util.Locale; +import javax.swing.*; +import javax.swing.border.*; + +import org.netbeans.CLIHandler; + +import org.netbeans.core.perftool.StartLog; +import org.openide.util.NbBundle; + +/** + * Handler for core options. + * @author Jaroslav Tulach + */ +public class CLIOptions extends CLIHandler { + + private static boolean specifiedBranding = false; + + /** + * Create a default handler. + */ + public CLIOptions() { + super(WHEN_BOOT); + } + + protected int cli(Args arguments) { + return cli(arguments.getArguments()); + } + + final int cli(String[] args) { + // let's go through the command line + for (int i = 0; i < args.length; i++) { + if (args[i] == null) { + continue; + } + boolean used = true; + if (args[i].equalsIgnoreCase("-nogui")) { // NOI18N + System.getProperties().put("org.openide.TopManager", "org.netbeans.core.NonGui"); // NOI18N + } else if (args[i].equalsIgnoreCase("-nosplash")) { // NOI18N + NonGui.noSplash = true; + } else if (args[i].equalsIgnoreCase("-noinfo")) { // NOI18N + // obsolete switch, ignore + } else if (args[i].equalsIgnoreCase("-nologging")) { // NOI18N + NonGui.noLogging = true; + } else if (args[i].equalsIgnoreCase("-ui")) { // NOI18N + args[i] = null; + try { + NonGui.uiClass = Class.forName(args[++i]); + } catch(ArrayIndexOutOfBoundsException e) { + System.err.println(NonGui.getString("ERR_UIExpected")); + return 2; + } catch (ClassNotFoundException e2) { + System.err.println(NonGui.getString("ERR_UINotFound")); + return 1; + } + } else if (args[i].equalsIgnoreCase("-fontsize")) { // NOI18N + args[i] = null; + try { + NonGui.uiFontSize = Integer.parseInt(args[++i]); + } catch(ArrayIndexOutOfBoundsException e) { + System.err.println(NonGui.getString("ERR_FontSizeExpected")); + return 2; + } catch (NumberFormatException e2) { + System.err.println(NonGui.getString("ERR_BadFontSize")); + return 1; + } + } else if (args[i].equalsIgnoreCase("-locale")) { // NOI18N + args[i] = null; + String localeParam = args[++i]; + String language; + String country = ""; // NOI18N + String variant = ""; // NOI18N + int index1 = localeParam.indexOf(":"); // NOI18N + if (index1 == -1) + language = localeParam; + else { + language = localeParam.substring(0, index1); + int index2 = localeParam.indexOf(":", index1+1); // NOI18N + if (index2 != -1) { + country = localeParam.substring(index1+1, index2); + variant = localeParam.substring(index2+1); + } + else + country = localeParam.substring(index1+1); + } + Locale.setDefault(new Locale(language, country, variant)); + } else if (args[i].equalsIgnoreCase("-branding")) { // NOI18N + args[i] = null; + String branding = args[++i]; + if (branding.equals("-")) branding = null; // NOI18N + try { + NbBundle.setBranding(branding); + } catch (IllegalArgumentException iae) { + iae.printStackTrace(); + return 1; + } + specifiedBranding = true; + } else { + used = false; + } + args[i] = null; + } + + return 0; + } + + /** Initializes logging etc. + */ + public static void initialize() { + if (! specifiedBranding) { + // Read default branding from file "lib/branding" in installation. + File branding = new File(new File(NonGui.getHomeDir(), "lib"), "branding"); // NOI18N + if (branding.exists()) { + try { + InputStream is = new FileInputStream(branding); + try { + BufferedReader rd = new BufferedReader(new InputStreamReader(is)); + String line = rd.readLine(); + if (line == null || line.equals("")) // NOI18N + throw new IOException("empty branding file"); // NOI18N + if (rd.readLine() != null) + throw new IOException("branding file more than one line"); // NOI18N + line = line.trim(); + if (line.equals("-")) line = null; // NOI18N + try { + org.openide.util.NbBundle.setBranding(line); + } catch (IllegalArgumentException iae) { + iae.printStackTrace(); + } + } finally { + is.close(); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } + + if (!NonGui.noLogging) { + try { + NonGui.logger = new TopLogging(NonGui.getSystemDir()); + } catch (IOException e) { + System.err.println("Cannot create log file. Logging disabled."); // NOI18N + e.printStackTrace(); + } + } + StartLog.logProgress("TopLogging initialized"); // NOI18N + } + + protected void usage(PrintWriter w) { + w.println(NonGui.getString("TEXT_help")); + } + +} Index: src/org/netbeans/core/Main.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/Main.java,v retrieving revision 1.159.2.4.4.1 diff -u -r1.159.2.4.4.1 Main.java --- src/org/netbeans/core/Main.java 14 Nov 2003 13:18:59 -0000 1.159.2.4.4.1 +++ src/org/netbeans/core/Main.java 2 Dec 2003 09:22:46 -0000 @@ -414,7 +414,7 @@ // the loger // org.netbeans.lib.logger.TraceLogger lgr = org.netbeans.lib.logger.TraceLogger.getTraceLogger(); - NonGui.parseCommandLine (args); + CLIOptions.initialize(); StartLog.logProgress ("Command line parsed"); // NOI18N checkDoubleBuffering(); Index: src/org/netbeans/core/NonGui.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/NonGui.java,v retrieving revision 1.96 diff -u -r1.96 NonGui.java --- src/org/netbeans/core/NonGui.java 27 Feb 2003 22:09:53 -0000 1.96 +++ src/org/netbeans/core/NonGui.java 2 Dec 2003 09:22:46 -0000 @@ -23,8 +23,7 @@ import javax.swing.*; import javax.swing.border.*; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URLClassLoader; +import java.net.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -35,14 +34,13 @@ import org.openide.loaders.*; import org.openide.actions.*; import org.openide.filesystems.*; -import org.openide.filesystems.FileSystem; import org.openide.windows.*; import org.openide.explorer.*; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.SharedClassObject; -import org.openide.util.Utilities; import org.openide.util.io.*; +import org.openide.util.Utilities; import org.openide.nodes.*; import org.netbeans.TopSecurityManager; @@ -97,13 +95,13 @@ /** The flag whether to create the log - can be set via -nologging * command line option */ - private static boolean noLogging = false; + protected static boolean noLogging = false; /** The flag whether to show the Splash screen on the startup */ - protected static boolean noSplash = false; + protected static boolean noSplash = false; /** The Class that logs the IDE events to a log file */ - private static TopLogging logger; + protected static TopLogging logger; /** Getter for home directory. */ protected static String getHomeDir () { @@ -564,6 +562,9 @@ java.net.Authenticator.setDefault (new NbAuthenticator ()); StartLog.logProgress ("Security managers installed"); // NOI18N Main.incrementSplashProgressBar(); + + org.netbeans.Main.finishInitialization(); + StartLog.logProgress("Ran any delayed command-line options"); // NOI18N } Index: src/org/netbeans/core/NonGuiMain.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/NonGuiMain.java,v retrieving revision 1.16 diff -u -r1.16 NonGuiMain.java --- src/org/netbeans/core/NonGuiMain.java 27 Feb 2003 23:33:28 -0000 1.16 +++ src/org/netbeans/core/NonGuiMain.java 2 Dec 2003 09:22:47 -0000 @@ -93,7 +93,7 @@ /** Prints help to the System.out. */ public static void showHelp2() { - showHelp(); + // XXX how to implement? CLIOptions.showHelp(); System.out.println(getStringX("CTL_INPUT_option")); // NOI18N System.out.println(getStringX("CTL_OUTPUT_option")); System.out.println(getStringX("CTL_INFO_option")); @@ -338,10 +338,12 @@ // if we are starting the GUI (e.g. for running Jemmy tests), // pass the args to Main.main, rather than NonGui.main. // - if (startGui) + if (startGui) { startGui(tmpArgs); - else - NonGui.parseCommandLine(tmpArgs); + } else { + new CLIOptions().cli(tmpArgs); + CLIOptions.initialize(); + } if (showInfo) { info(); Index: test/unit/src/org/netbeans/CLIHandlerTest.java =================================================================== RCS file: test/unit/src/org/netbeans/CLIHandlerTest.java diff -N test/unit/src/org/netbeans/CLIHandlerTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/unit/src/org/netbeans/CLIHandlerTest.java 2 Dec 2003 09:22:47 -0000 @@ -0,0 +1,479 @@ +/* + * Sun Public License Notice + * + * The contents of this file are subject to the Sun Public License + * Version 1.0 (the "License"). You may not use this file except in + * compliance with the License. A copy of the License is available at + * http://www.sun.com/ + * + * The Original Code is NetBeans. The Initial Developer of the Original + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans; + +import java.io.*; +import junit.textui.TestRunner; +import org.netbeans.junit.*; +import java.util.*; +import org.openide.util.RequestProcessor; + +/** + * Test the command-line-interface handler. + * @author Jaroslav Tulach + */ +public class CLIHandlerTest extends NbTestCase { + + private static ByteArrayInputStream nullInput = new ByteArrayInputStream(new byte[0]); + private static ByteArrayOutputStream nullOutput = new ByteArrayOutputStream(); + + public CLIHandlerTest(String name) { + super(name); + } + + public static void main(String[] args) { + TestRunner.run(new NbTestSuite(CLIHandlerTest.class)); + } + + protected void setUp() throws Exception { + super.setUp(); + + // setups a temporary file + String tmp = System.getProperty("java.io.tmpdir"); + assertNotNull(tmp); + System.getProperties().put("netbeans.user", tmp); + + File f = new File(tmp, "lock"); + if (f.exists()) { + assertTrue("Clean up previous mess", f.delete()); + assertTrue(!f.exists()); + } + } + + public void testFileExistsButItCannotBeRead() throws Exception { + // just creates the file and blocks + InitializeRunner runner = new InitializeRunner(10); + + // blocks when operation fails + InitializeRunner second = new InitializeRunner(85); + + for (int i = 0; i < 3; i++) { + second.next(); + } + + // finishes the code + runner.next(); + + assertNotNull("Runner succeeded to allocate the file", runner.resultFile()); + + // let the second code go on as well + second.next(); + + assertEquals("The same file has been allocated", runner.resultFile(), second.resultFile()); + } + + public void testFileExistsButTheServerCannotBeContacted() throws Exception { + // start the server and block + InitializeRunner runner = new InitializeRunner(65); + + assertNotNull("File created", runner.resultFile()); + assertTrue("Port allocated", runner.resultPort() != 0); + + // blocks when operation fails + InitializeRunner second = new InitializeRunner(85); + // second try should be ok + second.next(); + + assertNotNull("Previous file deleted and new one created", second.resultFile()); + assertTrue("Another port allocated", second.resultPort() != runner.resultPort()); + + + } + + public void testFileExistsButTheServerCannotBeContactedAndWeDoNotWantToCleanTheFile() throws Exception { + // start the server and block + InitializeRunner runner = new InitializeRunner(65); + + assertNotNull("File created", runner.resultFile()); + assertTrue("Port allocated", runner.resultPort() != 0); + + CLIHandler.Status res = CLIHandler.initialize( + new CLIHandler.Args(new String[0], nullInput, nullOutput, ""), + null, Collections.EMPTY_LIST, true, false, false + ); + + assertEquals ("Cannot connect", CLIHandler.Status.CANNOT_CONNECT, res.getExitCode()); + } + + public void testFileExistsButTheKeyIsNotRecognized() throws Exception { + // start the server be notified when it accepts connection + InitializeRunner runner = new InitializeRunner(65); + + assertNotNull("File created", runner.resultFile()); + assertTrue("Port allocated", runner.resultPort() != 0); + + int s = (int)runner.resultFile().length(); + byte[] copy = new byte[s]; + FileInputStream is = new FileInputStream(runner.resultFile()); + assertEquals("Read fully", s, is.read(copy)); + is.close(); + + // change the key + copy[s - 1]++; + + FileOutputStream os = new FileOutputStream(runner.resultFile()); + os.write(copy); + os.close(); + + // try to connect to previous server be notified as soon as it + // sends request + InitializeRunner second = new InitializeRunner(30); + + // handle the request, say NO + runner.next(); + + // read the reply and allocate new port + second.next(); + + assertNotNull("Previous file deleted and new one created", second.resultFile()); + assertTrue("Another port allocated", second.resultPort() != runner.resultPort()); + } + + public void testCLIHandlersCanChangeLocationOfLockFile() throws Exception { + File f = File.createTempFile("suffix", "tmp").getParentFile(); + final File dir = new File(f, "subdir"); + dir.delete(); + assertTrue(dir.exists() || dir.mkdir()); + + class UserDir extends CLIHandler { + private int cnt; + + public UserDir() { + super(WHEN_BOOT); + } + + protected int cli(Args args) { + cnt++; + System.setProperty("netbeans.user", dir.toString()); + return 0; + } + + protected void usage(PrintWriter w) {} + } + UserDir ud = new UserDir(); + + CLIHandler.Status res = cliInitialize(new String[0], ud, nullInput, nullOutput, null); + + assertEquals("Our command line handler is called once", 1, ud.cnt); + assertEquals("Lock file is created in dir", dir, res.getLockFile().getParentFile()); + + dir.delete(); + } + + public void testCLIHandlerCanStopEvaluation() throws Exception { + class H extends CLIHandler { + private int cnt; + + public H() { + super(WHEN_INIT); + } + + protected int cli(Args args) { + cnt++; + return 1; + } + + protected void usage(PrintWriter w) {} + } + H h1 = new H(); + H h2 = new H(); + + + CLIHandler.Status res = cliInitialize(new String[0], new H[] { + h1, h2 + }, nullInput, nullOutput); + + assertEquals("CLI evaluation failed with return code of h1", 1, res.getExitCode()); + assertEquals("First one executed", 1, h1.cnt); + assertEquals("Not the second one", 0, h2.cnt); + } + + public void testWhenInvokedTwiceParamsGoToTheFirstHandler() throws Exception { + final String[] template = { "Ahoj", "Hello" }; + final String currentDir = "MyDir"; + + class H extends CLIHandler { + private int cnt; + + public H() { + super(WHEN_INIT); + } + + protected int cli(Args args) { + String[] a = args.getArguments(); + String[] t = template; + + assertEquals("Same length", t.length, a.length); + assertEquals("First is same", t[0], a[0]); + assertEquals("Second is same", t[1], a[1]); + assertEquals("Current dir is fine", currentDir, args.getCurrentDirectory().toString()); + return ++cnt; + } + + protected void usage(PrintWriter w) {} + } + H h1 = new H(); + + + CLIHandler.Status res = cliInitialize(template, h1, nullInput, nullOutput, null, currentDir); + + assertEquals("First one executed", 1, h1.cnt); + assertEquals("CLI evaluation failed with return code of h1", 1, res.getExitCode()); + + res = cliInitialize(template, java.util.Collections.EMPTY_LIST, nullInput, nullOutput, null, currentDir); + assertEquals("But again executed h1", 2, h1.cnt); + assertEquals("Now the result is 2 as cnt++ was increased", 2, res.getExitCode()); + + } + + public void testServerIsNotBlockedByLongRequests() throws Exception { + class H extends CLIHandler { + private int cnt = -1; + public int toReturn; + + public H() { + super(CLIHandler.WHEN_INIT); + } + + protected synchronized int cli(Args args) { + notifyAll(); + cnt++; + return toReturn; + } + + protected void usage(PrintWriter w) {} + } + H h = new H(); + + h.toReturn = 7; + final Integer blockOn = new Integer(99); + CLIHandler.Status res = cliInitialize(new String[0], h, nullInput, nullOutput, blockOn); + assertEquals("Called once, increased -1 to 0", 0, h.cnt); + assertEquals("Result is provided by H", 7, res.getExitCode()); + + // blocks after connection established, before returning the result + class R implements Runnable { + CLIHandler.Status res; + public void run() { + res = cliInitialize(new String[0], Collections.EMPTY_LIST, nullInput, nullOutput, blockOn); + } + } + R r = new R(); + RequestProcessor.Task task; + synchronized (h) { + h.toReturn = 5; + task = new org.openide.util.RequestProcessor("Blocking request").post(r); + h.wait(); + assertEquals("Connects to the h", 1, h.cnt); + assertEquals("But is not finished", null, r.res); + } + + // while R is blocked, run another task + h.toReturn = 0; + res = cliInitialize(new String[0], Collections.EMPTY_LIST, nullInput, nullOutput, null); + assertEquals("Called once, increased to 2", 2, h.cnt); + assertEquals("Result is provided by H, H gives 0, changes into -1 right now", -1, res.getExitCode()); + + synchronized (blockOn) { + // let the R task go on + blockOn.notifyAll(); + } + task.waitFinished(); + assertNotNull("Now it is finished", r.res); + assertEquals("Result is -1, if this fails: this usually means that the server is blocked by some work and the task R started new server to handle its request", + 5, r.res.getExitCode()); + assertEquals("H called three times (but counting from -1)", 2, h.cnt); + } + + public void testReadingOfInputWorksInHandler() throws Exception { + final byte[] template = { 1, 2, 3, 4 }; + + class H extends CLIHandler { + private byte[] arr; + + public H() { + super(WHEN_INIT); + } + + protected int cli(Args args) { + try { + InputStream is = args.getInputStream(); + arr = new byte[is.available() / 2]; + if (arr.length > 0) { + assertEquals("Read amount is the same", arr.length, is.read(arr)); + } + is.close(); + } catch (IOException ex) { + fail("There is an exception: " + ex); + } + return 0; + } + + protected void usage(PrintWriter w) {} + } + H h1 = new H(); + H h2 = new H(); + + // why twice? first attempt is direct, second thru the socket server + for (int i = 0; i < 2; i++) { + CLIHandler.Status res = cliInitialize( + new String[0], new H[] { h1, h2 }, new ByteArrayInputStream(template), nullOutput); + + assertNotNull("Attempt " + i + ": " + "Can be read", h1.arr); + assertEquals("Attempt " + i + ": " + "Read two bytes", 2, h1.arr.length); + assertEquals("Attempt " + i + ": " + "First is same", template[0], h1.arr[0]); + assertEquals("Attempt " + i + ": " + "Second is same", template[1], h1.arr[1]); + + assertNotNull("Attempt " + i + ": " + "Can read as well", h2.arr); + assertEquals("Attempt " + i + ": " + "Just one char", 1, h2.arr.length); + assertEquals("Attempt " + i + ": " + "And is the right one", template[2], h2.arr[0]); + + h1.arr = null; + h2.arr = null; + } + } + + public void testWritingToOutputIsFine() throws Exception { + final byte[] template = { 1, 2, 3, 4 }; + + class H extends CLIHandler { + public H() { + super(WHEN_INIT); + } + + protected int cli(Args args) { + try { + OutputStream os = args.getOutputStream(); + os.write(template); + os.close(); + } catch (IOException ex) { + fail("There is an exception: " + ex); + } + return 0; + } + + protected void usage(PrintWriter w) {} + } + H h1 = new H(); + H h2 = new H(); + + // why twice? first attempt is direct, second thru the socket server + for (int i = 0; i < 2; i++) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + CLIHandler.Status res = cliInitialize( + new String[0], new H[] { h1, h2 }, nullInput, os); + + byte[] arr = os.toByteArray(); + assertEquals("Double size of template", template.length * 2, arr.length); + + for (int pos = 0; pos < arr.length; pos++) { + assertEquals(pos + ". is the same", template[pos % template.length], arr[pos]); + } + } + } + + // + // Utility methods + // + + private static CLIHandler.Status cliInitialize(String[] args, CLIHandler handler, InputStream is, OutputStream os, Integer lock) { + return cliInitialize(args, handler, is, os, lock, System.getProperty ("user.dir")); + } + private static CLIHandler.Status cliInitialize(String[] args, CLIHandler handler, InputStream is, OutputStream os, Integer lock, String currentDir) { + return cliInitialize(args, Collections.nCopies(1, handler), is, os, lock, currentDir); + } + private static CLIHandler.Status cliInitialize(String[] args, CLIHandler[] arr, InputStream is, OutputStream os) { + return cliInitialize(args, Arrays.asList(arr), is, os, null); + } + private static CLIHandler.Status cliInitialize(String[] args, List coll, InputStream is, OutputStream os, Integer lock) { + return cliInitialize (args, coll, is, os, lock, System.getProperty ("user.dir")); + } + private static CLIHandler.Status cliInitialize(String[] args, List coll, InputStream is, OutputStream os, Integer lock, String currentDir) { + return CLIHandler.initialize(new CLIHandler.Args(args, is, os, currentDir), lock, coll, true, false, true); + } + + + private static final class InitializeRunner extends Object implements Runnable { + private Integer block; + private String[] args; + private CLIHandler handler; + private CLIHandler.Status result; + + public InitializeRunner(int till) throws InterruptedException { + this(new String[0], null, till); + } + + public InitializeRunner(String[] args, CLIHandler h, int till) throws InterruptedException { + this(args, h, new Integer(till)); + } + public InitializeRunner(String[] args, CLIHandler h, Integer till) throws InterruptedException { + this.args = args; + this.block = till; + this.handler = h; + + synchronized (block) { + RequestProcessor.getDefault().post(this); + block.wait(); + } + } + + public void run() { + synchronized (block) { + result = CLIHandler.initialize( + new CLIHandler.Args(args, nullInput, nullOutput, ""), + block, + handler == null ? java.util.Collections.EMPTY_LIST : java.util.Collections.nCopies(1, handler), + true, + false, + true); + // we are finished, wake up guys in next() if any + block.notifyAll(); + } + } + + /** Executes the code to next invocation */ + public void next() throws InterruptedException { + synchronized (block) { + block.notify(); + block.wait(); + } + } + + /** Has already the resutl? + */ + public boolean hasResult() { + return result != null; + } + + /** Gets the resultFile, if there is some, + */ + public File resultFile() { + if (result == null) { + fail("No result produced"); + } + return result.getLockFile(); + } + + /** Gets the port, if there is some, + */ + public int resultPort() { + if (result == null) { + fail("No result produced"); + } + return result.getServerPort(); + } + } // end of InitializeRunner + +}