blob: e5318e8f742c91b3a0b857a3c7299777dcc7cc10 [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.core.startup;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkEvent.EventType;
import javax.swing.event.HyperlinkListener;
import org.netbeans.Events;
import org.netbeans.Module;
import org.netbeans.Util;
import static org.netbeans.core.startup.Bundle.*;
import org.openide.modules.SpecificationVersion;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbCollections;
import org.openide.util.RequestProcessor;
/** Report events to the performance logger, status text/splash screen,
* console, and so on.
* @author Jesse Glick
*/
final class NbEvents extends Events {
private static final Logger logger = Logger.getLogger(NbEvents.class.getName());
private int moduleCount;
private int counter;
/** Handle a logged event.
* CAREFUL that this is called synchronously, usually within a write
* mutex or other sensitive environment. So do not call anything
* blocking (like TM.notify) directly. TM.setStatusText and printing
* to console are fine, as well as performance logging.
*/
@Messages({
"MSG_start_load_boot_modules=Loading core...",
"MSG_finish_load_boot_modules=Loading core...done.",
"MSG_start_auto_restore=Loading modules...",
"MSG_finish_auto_restore=Done loading modules.",
"MSG_start_enable_modules=Turning on modules...",
"TEXT_finish_enable_modules=Turning on modules:",
"# {0} = startlevel",
"MSG_startlevelinfo=, startlevel={0}",
"MSG_finish_enable_modules=Turning on modules...done.",
"MSG_start_disable_modules=Turning off modules...",
"TEXT_finish_disable_modules=Turning off modules:",
"MSG_finish_disable_modules=Turning off modules...done.",
"# {0} - file name ", "TEXT_start_deploy_test_module=Deploying test module in {0} ...",
"TEXT_finish_deploy_test_module=Finished deploying test module.",
"MSG_failed_install_new=Warning - could not install some modules:",
"# {0} - module display name", "MSG_failed_install_new_unexpected=Warning - could not install module {0}",
"MSG_start_read=Reading module storage...",
"MSG_finish_read=Reading module storage...done.",
"MSG_restore=Starting modules...",
"# #13806: might have been installed before and uninstalled.", "# {0} - module display name", "MSG_install=Installing {0}",
"# {0} - module display name", "TEXT_install=Installing new module: {0}",
"# {0} - module display name", "MSG_update=Starting a new version of {0}",
"# {0} - module display name", "TEXT_update=Updating module: {0}",
"# {0} - module display name", "MSG_uninstall=Stopping {0}",
"MSG_load_section=Loading modules...",
"MSG_load_layers=Loading module services...",
"# {0} - path to expected module JAR", "TEXT_missing_jar_file=Warning: the module {0} could not be found, ignoring...",
"# {0} - module display name", "TEXT_cant_delete_enabled_autoload=The module {0} could not be deleted as it was an autoload or eager module and still in use.",
"# {0} - module display name", "# {1} - property name in XML", "# {2} - value on disk in XML", "# {3} - actual value", "TEXT_misc_prop_mismatch=An attempt was made to change the property {1} of the module {0} in the system/Modules folder.\nThe actual value is \"{3}\" but it was changed on disk to read \"{2}\".\nThis property cannot be changed while the IDE is running, so this attempt had no effect.",
"# {0} - JAR file name", "TEXT_patch=Module patch or custom extension: {0}"
})
@Override protected void logged(final String message, Object[] args) {
if (message.equals(PERF_TICK)) {
StartLog.logProgress( (String)args[0]);
} else if (message.equals(PERF_START)) {
StartLog.logStart( (String)args[0]);
} else if (message.equals(PERF_END)) {
StartLog.logEnd( (String)args[0]);
} else if (message.equals(START_CREATE_BOOT_MODULE)) {
org.netbeans.core.startup.Splash.getInstance().increment(1);
} else if (message.equals(START_LOAD_BOOT_MODULES)) {
setStatusText(
MSG_start_load_boot_modules());
StartLog.logStart("ModuleSystem.loadBootModules"); // NOI18N
} else if (message.equals(START_LOAD)) {
StartLog.logStart("NbInstaller.load"); // NOI18N
} else if (message.equals(FINISH_LOAD_BOOT_MODULES)) {
setStatusText(
MSG_finish_load_boot_modules());
StartLog.logEnd( "ModuleSystem.loadBootModules" ); // NOI18N
} else if (message.equals(FINISH_LOAD)) {
StartLog.logEnd("NbInstaller.load"); // NOI18N
} else if (message.equals(START_AUTO_RESTORE)) {
Set<?> modules = (Set) args[0];
if (! modules.isEmpty()) {
setStatusText(
MSG_start_auto_restore());
}
} else if (message.equals(FINISH_AUTO_RESTORE)) {
setStatusText(
MSG_finish_auto_restore());
} else if (message.equals(START_ENABLE_MODULES)) {
setStatusText(
MSG_start_enable_modules());
} else if (message.equals(FINISH_ENABLE_MODULES)) {
List<Module> modules = NbCollections.checkedListByCopy((List) args[0], Module.class, true);
if (! modules.isEmpty()) {
logger.log(Level.INFO, TEXT_finish_enable_modules());
dumpModulesList(modules);
}
setStatusText(
MSG_finish_enable_modules());
StartLog.logEnd("ModuleManager.enable"); // NOI18N
} else if (message.equals(START_DISABLE_MODULES)) {
setStatusText(
MSG_start_disable_modules());
} else if (message.equals(FINISH_DISABLE_MODULES)) {
List<Module> modules = NbCollections.checkedListByCopy((List<?>) args[0], Module.class, true);
if (! modules.isEmpty()) {
logger.log(Level.INFO, TEXT_finish_disable_modules());
dumpModulesList(modules);
}
setStatusText(
MSG_finish_disable_modules());
} else if (message.equals(START_DEPLOY_TEST_MODULE)) {
// No need to print anything. ModuleSystem.deployTestModule prints
// its own stuff (it needs to be printed synchronously to console
// in order to appear in the output window). But status text is OK.
// Fix for IZ#81566 - I18N: need to localize status messages for module dev
String msg = TEXT_start_deploy_test_module((File) args[0]);
setStatusText( msg );
} else if (message.equals(FINISH_DEPLOY_TEST_MODULE)) {
// Fix for IZ#81566 - I18N: need to localize status messages for module dev
setStatusText(
TEXT_finish_deploy_test_module());
} else if (message.equals(FAILED_INSTALL_NEW)) {
Set<Module> modules = NbCollections.checkedSetByCopy((Set) args[0], Module.class, true);
{
StringBuilder buf = new StringBuilder(MSG_failed_install_new());
NbProblemDisplayer.problemMessagesForModules(buf, modules, false);
buf.append('\n'); // #123669
logger.log(Level.INFO, buf.toString());
}
{
StringBuilder buf = new StringBuilder(MSG_failed_install_new());
NbProblemDisplayer.problemMessagesForModules(buf, modules, true);
String msg = buf.toString();
notify(msg, true);
}
setStatusText("");
} else if (message.equals(FAILED_INSTALL_NEW_UNEXPECTED)) {
Module m = (Module)args[0];
List<Module> modules = new ArrayList<Module> ();
modules.add (m);
modules.addAll (NbCollections.checkedSetByCopy((Set) args[1], Module.class, true));
// ignore args[2]: InvalidException
{
StringBuilder buf = new StringBuilder(MSG_failed_install_new_unexpected(m.getDisplayName()));
NbProblemDisplayer.problemMessagesForModules(buf, modules, false);
buf.append('\n');
logger.log(Level.INFO, buf.toString());
}
{
notify(NbProblemDisplayer.messageForProblem (m, m.getProblems ().iterator ().next (), true), true);
}
setStatusText("");
} else if (message.equals(START_READ)) {
setStatusText(
MSG_start_read());
StartLog.logStart("ModuleList.readInitial"); // NOI18N
} else if (message.equals(MODULES_FILE_SCANNED)) {
moduleCount = (Integer)args[0];
Splash.getInstance().addToMaxSteps(Math.max(moduleCount + moduleCount/2 - 100, 0));
} else if (message.equals(MODULES_FILE_PROCESSED)) {
Splash.getInstance().increment(1);
if (StartLog.willLog()) {
StartLog.logProgress("module " + args[0] + " processed"); // NOI18N
}
} else if (message.equals(FINISH_READ)) {
if (moduleCount < 100) {
Splash.getInstance().increment(moduleCount - 100);
}
setStatusText(
MSG_finish_read());
StartLog.logEnd("ModuleList.readInitial"); // NOI18N
} else if (message.equals(RESTORE)) {
// Don't look for display name. Just takes too long.
setStatusText(
MSG_restore(/*, ((Module)args[0]).getDisplayName()*/));
if (++counter < moduleCount / 2) {
Splash.getInstance().increment(1);
}
} else if (message.equals(INSTALL)) {
// Nice to see the real title; not that common, after all.
setStatusText(
MSG_install(((Module)args[0]).getDisplayName()));
logger.log(Level.INFO, TEXT_install(((Module)args[0]).getDisplayName()));
} else if (message.equals(UPDATE)) {
setStatusText(
MSG_update(((Module)args[0]).getDisplayName()));
logger.log(Level.INFO, TEXT_update(((Module)args[0]).getDisplayName()));
} else if (message.equals(UNINSTALL)) {
setStatusText(
MSG_uninstall(((Module)args[0]).getDisplayName()));
} else if (message.equals(LOAD_SECTION)) {
// Again avoid finding display name now.
setStatusText(
MSG_load_section(/*, ((Module)args[0]).getDisplayName()*/));
if (++counter < moduleCount / 4) {
Splash.getInstance().increment(1);
}
} else if (message.equals(LOAD_LAYERS)) {
setStatusText(
MSG_load_layers());
} else if (message.equals(WRONG_CLASS_LOADER)) {
if (! Boolean.getBoolean("netbeans.moduleitem.dontverifyclassloader") && Util.err.isLoggable(Level.WARNING)) { // NOI18N
Class<?> clazz = (Class) args[1];
// Message for developers, no need for I18N.
StringBuilder b = new StringBuilder();
b.append("The module ").append(((Module) args[0]).getDisplayName()).append(" loaded the class ").append(clazz.getName()).append("\n"); // NOI18N
b.append("from the wrong classloader. The expected classloader was ").append(args[2]).append("\n"); // NOI18N
b.append("whereas it was actually loaded from ").append(clazz.getClassLoader()).append("\n"); // NOI18N
b.append("Usually this means that some classes were in the startup classpath.\n"); // NOI18N
b.append("To suppress this message, run with: -J-Dnetbeans.moduleitem.dontverifyclassloader=true"); // NOI18N
Util.err.warning(b.toString());
}
} else if (message.equals(EXTENSION_MULTIPLY_LOADED)) {
// Developer-oriented message, no need for I18N.
logger.log(Level.WARNING, "The extension {0} may be multiply loaded by modules: {1}; see: http://www.netbeans.org/download/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/classpath.html#class-path", new Object[] {(File) args[0], (Set<?/*File*/>) args[1]});
} else if (message.equals(MISSING_JAR_FILE)) {
File jar = (File)args[0];
Level level = Boolean.FALSE.equals(args[1]) ? Level.FINE : Level.INFO;
logger.log(level, TEXT_missing_jar_file(jar.getAbsolutePath()));
} else if (message.equals(CANT_DELETE_ENABLED_AUTOLOAD)) {
Module m = (Module)args[0];
logger.log(Level.INFO, TEXT_cant_delete_enabled_autoload(m.getDisplayName()));
} else if (message.equals(MISC_PROP_MISMATCH)) {
// XXX does this really need to be logged to the user?
// Or should it just be sent quietly to the log file?
Module m = (Module)args[0];
String prop = (String)args[1];
Object onDisk = args[2];
Object inMem = args[3];
logger.log(Level.INFO, TEXT_misc_prop_mismatch(m.getDisplayName(), prop, onDisk, inMem));
} else if (message.equals(PATCH)) {
File f = (File)args[0];
logger.log(Level.INFO, TEXT_patch(f.getAbsolutePath()));
}
// XXX other messages?
}
/** Print a nonempty list of modules to console (= log file).
* @param modules the modules
*/
private void dumpModulesList(Collection<Module> modules) {
if (modules.isEmpty()) {
throw new IllegalArgumentException();
}
StringBuilder buf = new StringBuilder(modules.size() * 100 + 1);
String lineSep = System.getProperty("line.separator");
for (Module m : modules) {
buf.append('\t'); // NOI18N
buf.append(m.getCodeName());
buf.append(" ["); // NOI18N
SpecificationVersion sv = m.getSpecificationVersion();
if (sv != null) {
buf.append(sv);
}
String iv = m.getImplementationVersion();
if (iv != null) {
buf.append(' '); // NOI18N
buf.append(iv);
}
String bv = m.getBuildVersion();
if (bv != null && !bv.equals (iv)) {
buf.append(' '); // NOI18N
buf.append(bv);
}
buf.append(']'); // NOI18N
int startlevel = m.getStartLevel();
if (startlevel != -1) {
buf.append(MSG_startlevelinfo(startlevel));
}
// #32331: use platform-specific newlines
buf.append(lineSep);
}
logger.log(Level.INFO, buf.toString());
}
private void notify(String text, boolean warn) {
if (GraphicsEnvironment.isHeadless() || Boolean.getBoolean("netbeans.full.hack")) { // NOI18N
// #21773: interferes with automated GUI testing.
logger.log(Level.WARNING, "{0}\n", text);
} else {
// Normal - display dialog.
new Notifier(text, warn).show();
}
}
private static final class Notifier implements Runnable {
private static boolean showDialog = true;
private boolean warn;
private String text;
private static RequestProcessor RP = new RequestProcessor("Notify About Module System"); // NOI18N
public Notifier(String text, boolean type) {
this.warn = type;
this.text = text;
}
void show() {
if (showDialog) {
showDialog = false;
if (EventQueue.isDispatchThread()) {
run();
} else {
RP.post(this, 0, Thread.MIN_PRIORITY).waitFinished ();
}
}
}
@Messages({
"MSG_warning=Warning",
"MSG_info=Information",
"MSG_continue=Disable Modules and Continue",
"MSG_exit=Exit"
})
@Override public void run() {
int type = warn ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE;
String msg = warn ? MSG_warning() : MSG_info();
Splash out = Splash.getInstance();
final Component c = out.getComponent() == null ? null : out.getComponent();
try {
UIManager.setLookAndFeel (UIManager.getSystemLookAndFeelClassName ());
} catch (ClassNotFoundException ex) {
logger.log(Level.INFO, null, ex);
} catch (InstantiationException ex) {
logger.log(Level.INFO, null, ex);
} catch (IllegalAccessException ex) {
logger.log(Level.INFO, null, ex);
} catch (UnsupportedLookAndFeelException ex) {
logger.log(Level.INFO, null, ex);
}
JTextPane tp = new JTextPane ();
tp.setContentType("text/html"); // NOI18N
text = text.replace ("\n", "<br>"); // NOI18N
tp.setEditable(false);
tp.setOpaque (false);
tp.setEnabled(true);
tp.addHyperlinkListener(new HyperlinkListener() {
@Override public void hyperlinkUpdate(HyperlinkEvent hlevt) {
if (EventType.ACTIVATED == hlevt.getEventType()) {
assert hlevt.getURL() != null;
try {
Desktop.getDesktop().browse(hlevt.getURL().toURI());
} catch (Exception ex) {
logger.log(Level.INFO, null, ex);
}
}
}
});
tp.setText (text);
JComponent sp;
if (tp.getPreferredSize ().width > 600 || tp.getPreferredSize ().height > 400) {
tp.setPreferredSize (new Dimension (600, 400));
sp = new JScrollPane (tp);
} else {
sp = tp;
}
final JOptionPane op = new JOptionPane (sp, type, JOptionPane.YES_NO_OPTION, null);
JButton continueButton = new JButton(MSG_continue());
continueButton.setDisplayedMnemonicIndex (0);
continueButton.addActionListener (new ActionListener () {
@Override public void actionPerformed (ActionEvent e) {
op.setValue (0);
}
});
JButton exitButton = new JButton(MSG_exit());
exitButton.addActionListener (new ActionListener () {
@Override public void actionPerformed (ActionEvent e) {
op.setValue (1);
}
});
Object [] options = new JButton [] {continueButton, exitButton};
op.setOptions (options);
op.setInitialValue (options [1]);
JDialog d = op.createDialog (c, msg);
d.setResizable (true);
d.pack();
d.setVisible (true);
Object res = op.getValue ();
if (res instanceof Integer) {
int ret = (Integer) res;
if (ret == 1 || ret == -1) { // exit or close
TopLogging.exit(1);
}
}
}
}
private static void setStatusText (String msg) {
Main.setStatusText (msg);
}
}