/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
/*
* Created on Oct 26, 2005 by mschilli
*/
package alma.acs.vmtools;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Filter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
/**
* Allows a User to configure Loggers at run-time.
*
* For a logger L there are:
* <ul>
* <li> Logger Level: L's level (which can be null, i.e. unset)
* <li> Active Level: L's level if it is non-null, otherwise the closest ancestor's level
* <li> ForceFilter Level: The level of L's forcefilter if L has a forcefilter
* <li> Effective Level: The stricter one out of {L's active level, L's forcefilter level}
* </ul>
*
* @author mschilli
*/
public class LogManagerGui extends JPanel {
//
// ============= Stand-Alone Launch==================
//
public static JFrame openFrame (final LogManagerGui inst) {
// frame
final JFrame f = new JFrame(LogManagerGui.class.getName());
f.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
f.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent evt){
int answer = JOptionPane.showConfirmDialog(f, "Really close?", "Close Window", JOptionPane.YES_NO_OPTION);
if (answer == JOptionPane.YES_OPTION) {
f.setVisible(false);
f.dispose();
}
}
});
f.getContentPane().add(inst);
f.pack();
f.setVisible(true);
return f;
}
//
// ============= Instance Implementation ==================
//
static final Color BG_NORMAL = Color.white;
static final Color BG_SELECT = Color.lightGray;
static final HashMap<Level, Color> level2color = new HashMap<Level, Color>();
static {
level2color.put(null, Color.magenta);
level2color.put(Level.OFF, Color.gray);
level2color.put(Level.SEVERE, Color.red);
level2color.put(Level.WARNING, Color.orange);
level2color.put(Level.INFO, Color.green);
level2color.put(Level.FINE, Color.green.brighter());
level2color.put(Level.FINER, Color.green.brighter().brighter());
level2color.put(Level.FINEST, Color.green.brighter().brighter().brighter());
level2color.put(Level.CONFIG, Color.pink);
level2color.put(Level.ALL, Color.blue);
}
TreeM model;
JTree tree;
LoggerEditor editor;
QuickLoggerEditor quickEditor;
JSplitPane splitpane;
Controls controls;
/**
*
*/
public LogManagerGui() {
super(new BorderLayout());
model = new TreeM(null); // root will be set in populateModel()
tree = new JTree(model);
tree.addTreeSelectionListener(new TreeS());
tree.setCellRenderer(new TreeR());
quickEditor = new QuickLoggerEditor();
tree.addMouseListener(new TreeL());
editor = new LoggerEditor();
tree.setBackground(Color.white);
controls = new Controls();
JPanel left = new JPanel(new BorderLayout());
left.add(new JScrollPane(tree), BorderLayout.CENTER);
left.add(controls, BorderLayout.SOUTH);
splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitpane.setLeftComponent(left);
splitpane.setRightComponent(editor);
add(splitpane);
}
protected void message(String msg) {
String me = LogManagerGui.class.getName();
me = me.substring(me.lastIndexOf('.')+1);
System.out.println(me+": "+msg);
}
////////////////////////////////////////////////////////
/// ------------------- API ------------------------ ///
////////////////////////////////////////////////////////
public void setFilter (String string) {
controls.txtNameFilter.setText(string);
}
@Override
public void setVisible (boolean b) {
if (b == true) {
populateModel();
}
splitpane.setDividerLocation(0.66);
super.setVisible(b);
}
public void populateModel () {
populateModel(new String[]{});
}
public void populateModel (String[] nameFilter) {
HashMap<Logger, TreeN> reverse = new HashMap<Logger, TreeN>();
if (model.root() != null) {
model.root().removeAllChildren();
}
// iterate over all loggers and create nodes
Enumeration<String> en = LogManager.getLogManager().getLoggerNames();
while (en.hasMoreElements()) {
String name = en.nextElement();
if (passesThroughFilter(nameFilter, name)) {
Logger elem = LogManager.getLogManager().getLogger(name);
TreeN node = new TreeN(elem);
reverse.put(elem, node);
}
}
// iterate over nodes and compose them as tree
Iterator<Logger> it = reverse.keySet().iterator();
while (it.hasNext()) {
Logger elem = it.next();
TreeN node = (TreeN) reverse.get(elem);
TreeN pnode = (TreeN) reverse.get(elem.getParent());
if (pnode == null) {
// logger has no parent
model.setRoot(node);
continue;
}
pnode.add(node);
}
model.nodeStructureChanged(model.root());
}
private boolean passesThroughFilter(String[] nameFilter, String name) {
for (int i = 0; i < nameFilter.length; i++) {
if (name.startsWith(nameFilter[i])) {
return false;
}
}
return true;
}
////////////////////////////////////////////////////////
/// ----------------- Internal --------------------- ///
////////////////////////////////////////////////////////
Color colorFromLevel (Level x) {
Color ret = (Color) level2color.get(x);
if (ret == null) {
ret = Color.magenta;
message("no color defined for log-level " + x);
}
return ret;
}
void editLogger (final Logger x) {
String level = String.valueOf(x.getLevel());
if (x.getFilter() instanceof ForceFilter) {
level += " (filter-level: " + ((ForceFilter) x.getFilter()).level + ")";
}
editor.text2.setText(x.toString() //
+ "\nname=" + x.getName() //
+ "\nlogger-level=" + level //
+ "\nactive-level=" + activeLevel(x)
+ "\neffective-level=" + effectiveLevel(x) //
+ "\nhandlers=" + Arrays.toString(x.getHandlers()) //
+ "\nusesParentHandler (i.e. sends output to parent):" + x.getUseParentHandlers() //
+ "\nparent=" + x.getParent());
editor.comboBoxActionListener.x = null;
if (x.getLevel() == null) {
editor.text.setSelectedItem("(inherited)");
} else {
editor.text.setSelectedItem(x.getLevel());
}
editor.comboBoxActionListener.x = x;
}
void quickEditLogger (Point p, final Logger x) {
quickEditor.use(x);
quickEditor.show(tree, p.x, p.y);
}
/**
* @param level Something that can be recognized as (or made) a level
*/
Level decodeLevel (Object level) {
Level toSet;
if ("(inherited)".equals(level)) {
toSet = null;
} else {
if (LEVELS.contains(level)) {
toSet = (Level) level;
} else {
String s = level.toString();
try {
toSet = Level.parse(s);
/* Level.parse() will create a custom-level if an integer is passed in */
} catch (Exception exc) {
message("cannot decode level '" + level + "', using ALL instead");
toSet = Level.ALL;
}
}
}
return toSet;
}
/**
*
* @param x
*/
void doSetLevel (Logger x, Level toSet) {
if (toSet == null && x.getParent() == null) {
// leniently ignore attempts to set "(inherited)" on the rootlogger
return;
}
message("setting logger '" + x.getName() + "' to level '" + toSet + "'");
// PENDING: allow chains of filters instead of overwriting a previous filter here
if (toSet != null) {
x.setFilter(new ForceFilter(toSet));
} else {
x.setFilter(null);
// PENDING: might make forcefilters smart enough to understand inheritance, too
}
x.setLevel(toSet);
//populateModel();
tree.repaint();
}
////////////////////////////////////////////////////////
/// ---------------- Inner Types ------------------- ///
////////////////////////////////////////////////////////
static final Vector<Level> LEVELS = new Vector<Level>(Arrays.asList(new Level[]{
//
Level.OFF,//
Level.SEVERE,//
Level.WARNING,//
Level.INFO, //
Level.CONFIG,//
Level.FINE,//
Level.FINER,//
Level.FINEST,//
Level.ALL//
}));
class QuickLoggerEditor extends JPopupMenu {
Logger x;
JMenu handlermenu;
QuickLoggerEditor() {
JMenuItem item;
LoggerLevelAction loggerAction = new LoggerLevelAction();
item = new JMenuItem("(inherited)");
item.setActionCommand("(inherited)");
item.addActionListener(loggerAction);
super.add(item);
for (int i = 0; i < LEVELS.size(); i++) {
String levelName = ((Level) LEVELS.get(i)).getName();
item = new JMenuItem(levelName);
item.setActionCommand(levelName);
item.addActionListener(loggerAction);
super.add(item);
}
HandlerLevelAction handlerAction = new HandlerLevelAction();
handlermenu = new JMenu("Handlers");
for (int i = 0; i < LEVELS.size(); i++) {
String levelName = ((Level) LEVELS.get(i)).getName();
item = new JMenuItem(levelName);
item.setActionCommand(levelName);
item.addActionListener(handlerAction);
handlermenu.add(item);
}
super.add(handlermenu);
}
void use(Logger x) {
this.x = x;
handlermenu.setEnabled(x.getHandlers().length>0);
}
class LoggerLevelAction implements ActionListener {
public void actionPerformed (ActionEvent e) {
if (x != null)
doSetLevel(x, decodeLevel(e.getActionCommand()));
}
}
class HandlerLevelAction implements ActionListener {
public void actionPerformed (ActionEvent e) {
if (x != null)
for (Handler h : x.getHandlers())
h.setLevel(decodeLevel(e.getActionCommand()));
}
}
}
class LoggerEditor extends JPanel {
JTextArea text2;
JComboBox text;
ActionLi comboBoxActionListener;
LoggerEditor() {
super(new BorderLayout());
Vector<Object> cmbBoxContents = new Vector<Object>(LEVELS);
cmbBoxContents.insertElementAt("(inherited)", 0);
text = new JComboBox(cmbBoxContents);
this.add(text, BorderLayout.NORTH);
text2 = new JTextArea();
this.add(text2, BorderLayout.CENTER);
comboBoxActionListener = new ActionLi();
text.addActionListener(comboBoxActionListener);
text.setEditable(true);
}
class ActionLi implements ActionListener {
Logger x;
public void actionPerformed (ActionEvent evt) {
if (x == null) {
return;
}
Object sel = editor.text.getSelectedItem();
if (sel == null) {
return;
}
doSetLevel(x, decodeLevel(sel));
}
}
}
class Controls extends JPanel {
JTextField txtNameFilter;
Controls() {
super(new BorderLayout());
this.add(new JLabel("Don't show: "), BorderLayout.WEST);
this.add(txtNameFilter = new JTextField(), BorderLayout.CENTER);
JButton r = new JButton("Refresh");
r.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent evt) {
StringTokenizer t = new StringTokenizer(txtNameFilter.getText(), ";, ", false);
String[] nameFilter = (String[])Collections.list(t).toArray(new String[]{});
populateModel(nameFilter);
}
});
this.add(r, BorderLayout.EAST);
txtNameFilter.setText("sun., java., javax.");
}
}
// yields the logger's level, considering inheritance
Level activeLevel (Logger x) {
if (x == null) {
return null;
}
if (x.getLevel() == null) {
return activeLevel(x.getParent());
}
return x.getLevel();
}
// yields the logger's level, considering inheritance and force-filters
Level effectiveLevel (Logger x) {
Level ret = activeLevel(x);
Filter f = x.getFilter();
if (f != null && f instanceof ForceFilter) {
ForceFilter ff = (ForceFilter) f;
if (ff.level.intValue() > ret.intValue())
ret = ff.level;
}
return ret;
}
// There really is a Logger (namely "alma.component") that obviously is reset
// to Level ALL on each invokation. This filter tries to make up for this annoyance.
class ForceFilter implements Filter {
public Level level;
int threshold;
ForceFilter(Level level) {
this.level = level;
this.threshold = level.intValue();
}
public boolean isLoggable (LogRecord record) {
return threshold <= record.getLevel().intValue();
}
}
class TreeM extends DefaultTreeModel {
TreeM(TreeN root) {
super(root);
}
TreeN root () {
return (TreeN) root;
}
}
class TreeN extends DefaultMutableTreeNode {
TreeN(Logger x) {
super(x);
}
Logger logger () {
return (Logger) userObject;
}
}
class TreeR implements TreeCellRenderer {
JLabel cell = new JLabel();
{
cell.setBackground(BG_SELECT);
}
public Component getTreeCellRendererComponent (JTree tree, Object value, boolean selected, boolean expanded, boolean leaf,
int row, boolean hasFocus) {
TreeN node = (TreeN) value;
Logger logger = node.logger();
String text = logger.getName();
if (text.equals("")) {
text = "<Root Logger>";
}
// if no own level show parent level
Level activeLevel = activeLevel(logger);
if (logger.getLevel() != null) {
text += ": " + logger.getLevel().getName();
} else {
text += ": " + activeLevel + " (inherited)";
}
Level effectiveLevel = effectiveLevel(logger);
if (effectiveLevel.intValue() != activeLevel.intValue()) {
text += ", " + effectiveLevel + " (filtered)";
}
if (selected) {
cell.setOpaque(true);
} else {
cell.setOpaque(false);
}
Color fg = colorFromLevel(effectiveLevel);
cell.setForeground(fg);
cell.setText(text);
return cell;
}
}
class TreeS implements TreeSelectionListener {
public void valueChanged (TreeSelectionEvent e) {
TreeN node = (TreeN) e.getPath().getLastPathComponent();
editLogger(node.logger());
}
}
class TreeL extends MouseAdapter {
@Override
public void mouseClicked (MouseEvent e) {
if (!SwingUtilities.isRightMouseButton(e))
return;
Point p = e.getPoint();
TreePath tp = tree.getClosestPathForLocation(p.x, p.y);
tree.setSelectionPath(tp);
TreeN n = (TreeN) tp.getLastPathComponent();
quickEditLogger(p, n.logger());
}
}
}
//
//
//
//
//
//
//
//
//
//
//
//