blob: 577c392d2f463cbf0b76fa8956c9e92076f35db5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans;
import java.awt.GraphicsEnvironment;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JOptionPane;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
/** Bootstrap main class.
* @author Jaroslav Tulach, Jesse Glick
*/
final class MainImpl extends Object {
/** Starts the IDE.
* @param args the command line arguments
* @throws Exception for lots of reasons
*/
public static void main (String args[]) throws Exception {
AtomicReference<Method> m = new AtomicReference<Method>();
int res = execute (args, System.in, System.out, System.err, m);
if (res == -1) {
// Connected to another running NB instance and succeeded in making a call.
return;
} else if (res != 0) {
// Some CLIHandler refused the invocation
if (res == Integer.MIN_VALUE) {
res = 0;
}
System.exit(res);
}
m.get().invoke(null, new Object[] {args});
}
/** Constructs the correct ClassLoader, finds main method to execute
* and invokes all registered CLIHandlers.
*
* @param args the arguments to pass to the handlers
* @param reader the input stream reader for the handlers
* @param writer the output stream for the handlers
* @param methodToCall null, or cell that will be set to
* a method that shall be executed as the main application
*/
static int execute (
String[] args,
java.io.InputStream reader,
java.io.OutputStream writer,
java.io.OutputStream error,
AtomicReference<Method> methodToCall
) throws Exception {
// #42431: turn off jar: caches, they are evil
// Note that setDefaultUseCaches changes a static field
// yet for some reason it is an instance method!
new URLConnection(MainImpl.class.getResource("Main.class")) { // NOI18N
@Override
public void connect() throws IOException {}
}.setDefaultUseCaches(false);
ArrayList<File> list = new ArrayList<File>();
HashSet<File> processedDirs = new HashSet<File> ();
HashSet<String> processedPaths = new HashSet<String> ();
List<String> argsL = Arrays.asList (args);
// only nbexec.exe puts userdir into netbeans.user
String user = System.getProperty ("netbeans.user"); // NOI18N
if (user == null) {
// read userdir from args (for unix nbexec)
int idx = argsL.indexOf ("--userdir"); // NOI18N
if (idx != -1 && argsL.size () > idx + 1) {
user = argsL.get (idx + 1);
}
}
if (user != null) {
build_cp (new File (user), list, processedDirs, processedPaths);
}
String home = System.getProperty ("netbeans.home"); // NOI18N
if (home != null) {
build_cp (new File (home), list, processedDirs, processedPaths);
}
// #34069: need to do the same for nbdirs.
String nbdirs = System.getProperty("netbeans.dirs"); // NOI18N
if (nbdirs != null) {
StringTokenizer tok = new StringTokenizer(nbdirs, File.pathSeparator);
while (tok.hasMoreTokens()) {
// passing false as last argument as we need to initialize openfile-cli.jar
build_cp(new File(tok.nextToken()).getAbsoluteFile(), list, processedDirs, processedPaths);
}
}
//
// prepend classpath
//
String prepend = System.getProperty("netbeans.classpath"); // NOI18N
if (prepend != null) {
StringTokenizer tok = new StringTokenizer (prepend, File.pathSeparator);
while (tok.hasMoreElements()) {
File f = new File(tok.nextToken());
list.add(0, f);
}
}
// Compute effective dynamic classpath (mostly lib/*.jar) for TopLogging, NbInstaller:
StringBuilder buf = new StringBuilder(1000);
for (File o : list) {
String f = o.getAbsolutePath();
if (buf.length() > 0) {
buf.append(File.pathSeparatorChar);
}
buf.append(f);
}
System.setProperty("netbeans.dynamic.classpath", buf.toString());
BootClassLoader loader = new BootClassLoader(list, new ClassLoader[] {
MainImpl.class.getClassLoader()
});
// Needed for Lookup.getDefault to find MainLookup.
// 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, reader, writer, error, loader, true, false, loader);
if (result.getExitCode () == CLIHandler.Status.CANNOT_CONNECT) {
int value = JOptionPane.CLOSED_OPTION;
if (!GraphicsEnvironment.isHeadless()) {
value = JOptionPane.showConfirmDialog (
null,
ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunning"),
ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunningTitle"),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE
);
}
if (value == JOptionPane.OK_OPTION) {
result = CLIHandler.initialize(args, reader, writer, error, loader, true, true, loader);
} else {
return result.getExitCode();
}
}
if (result.getExitCode () == CLIHandler.Status.CANNOT_WRITE) {
int value = JOptionPane.CLOSED_OPTION;
if (!GraphicsEnvironment.isHeadless()) {
value = JOptionPane.showConfirmDialog (
null,
MessageFormat.format(ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_CannotWrite"), user),
ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_CannotWriteTitle"),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE
);
}
if (value == JOptionPane.OK_OPTION) {
result = CLIHandler.initialize(args, reader, writer, error, loader, true, true, loader);
} else {
return result.getExitCode();
}
}
if (result.getExitCode () == CLIHandler.Status.ALREADY_RUNNING) {
if (!GraphicsEnvironment.isHeadless()) {
JOptionPane.showMessageDialog(null,
MessageFormat.format(ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunning"), user),
ResourceBundle.getBundle("org/netbeans/Bundle").getString("MSG_AlreadyRunningTitle"),
JOptionPane.OK_OPTION
);
}
return result.getExitCode();
}
if (methodToCall != null) {
String className = System.getProperty("netbeans.mainclass", "org.netbeans.core.startup.Main"); // NOI18N
Class<?> c = loader.loadClass(className);
methodToCall.set(c.getMethod("main", String[].class)); // NOI18N
}
return result.getExitCode ();
}
/**
* Call when the system is up and running, to complete handling of
* delayed command-line options like -open FILE.
*/
public static void finishInitialization() {
int r = CLIHandler.finishInitialization (false);
if (r != 0) {
if (r == Integer.MIN_VALUE) {
r = 0;
}
TopSecurityManager.exit(r);
}
}
static final class BootClassLoader extends JarClassLoader
implements Runnable {
private Lookup metaInf;
private List<CLIHandler> handlers;
public BootClassLoader(List<File> cp, ClassLoader[] parents) {
super(cp, parents);
metaInf = Lookups.metaInfServices(this);
String value = null;
try {
if (cp.isEmpty ()) {
value = searchBuildNumber(this.getResources("META-INF/MANIFEST.MF"));
} else {
value = searchBuildNumber(this.findResources("META-INF/MANIFEST.MF"));
}
} catch (IOException ex) {
ex.printStackTrace();
}
if (value == null) {
System.err.println("Cannot set netbeans.buildnumber property no OpenIDE-Module-Build-Version found"); // NOI18N
} else {
System.setProperty ("netbeans.buildnumber", value); // NOI18N
}
}
@Override // #154417: work around JAXP #6723276, at least within tests for now
public InputStream getResourceAsStream(String name) {
if (name.equals("META-INF/services/javax.xml.stream.XMLInputFactory")) { // NOI18N
return super.getResourceAsStream(name);
} else if (Boolean.getBoolean("org.netbeans.MainImpl.154417") && name.startsWith("META-INF/services/javax.xml.")) { // NOI18N
return new ByteArrayInputStream(new byte[0]);
} else {
return super.getResourceAsStream(name);
}
}
/** @param en enumeration of URLs */
private static String searchBuildNumber(Enumeration<URL> en) {
String value = null;
try {
java.util.jar.Manifest mf;
URL u = null;
while(en.hasMoreElements()) {
u = en.nextElement();
InputStream is = u.openStream();
mf = new java.util.jar.Manifest(is);
is.close();
// #251035: core-base now allows impl dependencies, with manually added impl version. Prefer Build-Version.
value = mf.getMainAttributes().getValue("OpenIDE-Module-Build-Version"); // NOI18N
if (value == null) {
value = mf.getMainAttributes().getValue("OpenIDE-Module-Implementation-Version"); // NOI18N
}
if (value != null) {
break;
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
return value;
}
private boolean onlyRunRunOnce;
/** Checks for new JARs in netbeans.user */
@Override
public void run () {
// do not call this method twice
if (onlyRunRunOnce) return;
onlyRunRunOnce = true;
ArrayList<File> toAdd = new ArrayList<File> ();
String user = System.getProperty ("netbeans.user"); // NOI18N
try {
if (user != null) {
JarClassLoader.initializeCache();
build_cp (new File (user), toAdd, new HashSet<File> (), new HashSet<String> ());
}
if (!toAdd.isEmpty ()) {
// source were already added in MainImpl.execute() method while processing userdir
metaInf = Lookups.metaInfServices(this);
if (handlers != null) {
handlers.clear();
handlers.addAll(metaInf.lookupAll(CLIHandler.class));
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
/** #27226: startup optimization. */
@Override
protected PermissionCollection getPermissions(CodeSource cs) {
return getAllPermission();
}
private static PermissionCollection modulePermissions;
private static synchronized PermissionCollection getAllPermission() {
if (modulePermissions == null) {
modulePermissions = new Permissions();
modulePermissions.add(new AllPermission());
modulePermissions.setReadOnly();
}
return modulePermissions;
}
/** For a given classloader finds all registered CLIHandlers.
*/
public final Collection<? extends CLIHandler> allCLIs () {
if (handlers == null) {
handlers = new ArrayList<CLIHandler>(metaInf.lookupAll(CLIHandler.class));
}
return handlers;
}
} // end of BootClassLoader
private static void append_jars_to_cp (File base, String pathToDir, Collection<File> toAdd, Set<String> processedPaths) throws IOException {
File dir = new File (base, pathToDir);
if (!dir.isDirectory()) return;
File[] arr = dir.listFiles();
for (int i = 0; i < arr.length; i++) {
String n = arr[i].getName ();
/*
if (n.equals("updater.jar") || // NOI18N
(dir.getName().equals("locale") && n.startsWith("updater_") && n.endsWith(".jar"))) { // NOI18N
// Used by launcher, not by us.
continue;
}
*/
if (n.endsWith("jar") || n.endsWith ("zip")) { // NOI18N
if (processedPaths.add (pathToDir + '/' + n)) { // NOI18N
toAdd.add(arr[i]);
}
}
}
}
private static void build_cp(File base, Collection<File> toAdd, Set<File> processedDirs, Set<String> processedPaths)
throws java.io.IOException {
if (!processedDirs.add (base)) {
// already processed
return;
}
append_jars_to_cp(base, "core/patches", toAdd, processedPaths); // NOI18N
append_jars_to_cp(base, "core", toAdd, processedPaths); // NOI18N
// XXX a minor optimization: exclude any unused locale JARs
// For example, lib/locale/ might contain:
// core_ja.jar
// core_f4j.jar
// core_f4j_ja.jar
// core_f4j_ce.jar
// core_f4j_ce_ja.jar
// core_ru.jar
// core_fr.jar
// [etc.]
// Only some of these will apply to the current session, based on the
// current values of Locale.default and NbBundle.branding.
append_jars_to_cp(base, "core/locale", toAdd, processedPaths); // NOI18N
}
}