001 package edu.wisc.ssec.mcidasv.startupmanager.options; 002 003 import java.awt.event.ItemEvent; 004 import java.awt.event.ItemListener; 005 006 import javax.swing.JComboBox; 007 import javax.swing.SwingUtilities; 008 009 import org.slf4j.LoggerFactory; 010 011 import ch.qos.logback.classic.Level; 012 import ch.qos.logback.classic.Logger; 013 014 import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.OptionPlatform; 015 import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Type; 016 import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster.Visibility; 017 import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 018 019 /** 020 * Representation of a choice allowing the user to select the global McIDAS-V 021 * logging level. 022 */ 023 public class LoggerLevelOption extends AbstractOption { 024 025 /** 026 * {@code String} representation of Logback's {@literal "TRACE"} logging 027 * level. 028 */ 029 private static final String TRACE = Level.TRACE.toString(); 030 031 /** 032 * {@code String} representation of Logback's {@literal "DEBUG"} logging 033 * level. 034 */ 035 private static final String DEBUG = Level.DEBUG.toString(); 036 037 /** 038 * {@code String} representation of Logback's {@literal "INFO"} logging 039 * level. 040 */ 041 private static final String INFO = Level.INFO.toString(); 042 043 /** 044 * {@code String} representation of Logback's {@literal "WARN"} logging 045 * level. 046 */ 047 private static final String WARN = Level.WARN.toString(); 048 049 /** 050 * {@code String} representation of Logback's {@literal "ERROR"} logging 051 * level. 052 */ 053 private static final String ERROR = Level.ERROR.toString(); 054 055 /** 056 * {@code String} representation of Logback's {@literal "OFF"} logging 057 * level. 058 */ 059 private static final String OFF = Level.OFF.toString(); 060 061 /** 062 * {@code JComboBox} that will eventually contain logging levels to 063 * select. May be {@code null}. 064 */ 065 private JComboBox comboBox; 066 067 /** 068 * {@code String} representation of the user's selection, or the default 069 * value provided to the constructor. 070 */ 071 private String currentChoice; 072 073 /** 074 * Create a startup option that allows the user to manipulate the global 075 * McIDAS-V logging level. <B>NOTE:</b> {@code null} is not a permitted 076 * value for any of this constructor's parameters. 077 * 078 * @param id Identifier for this startup option. 079 * @param label Brief description suitable for a GUI label. 080 * @param defaultValue Default value for this startup option. 081 * @param optionPlatform Platforms where this option may be applied. 082 * @param optionVisibility Whether or not the option is presented via the GUI. 083 * 084 * @throws IllegalArgumentException if {@code defaultValue} failed {@link #isValidValue(String)}. 085 */ 086 public LoggerLevelOption(String id, String label, String defaultValue, 087 OptionPlatform optionPlatform, Visibility optionVisibility) { 088 super(id, label, Type.LOGLEVEL, optionPlatform, optionVisibility); 089 if (!isValidValue(defaultValue)) { 090 throw new IllegalArgumentException("Default value '"+defaultValue+"' is not one of: TRACE, DEBUG, INFO, WARN, ERROR, or OFF."); 091 } 092 currentChoice = defaultValue; 093 } 094 095 /** 096 * Builds a {@link JComboBox} containing the logging levels to select. Defaults to the {@code String} specified 097 * in the constructor. 098 * 099 * @return {@code JComboBox} to present to the user. 100 */ 101 public JComboBox getComponent() { 102 comboBox = new JComboBox(new String[] { TRACE, DEBUG, INFO, WARN, ERROR, OFF }); 103 comboBox.setSelectedItem(currentChoice); 104 comboBox.addItemListener(new ItemListener() { 105 public void itemStateChanged(ItemEvent e) { 106 setValue(comboBox.getSelectedItem().toString()); 107 } 108 }); 109 110 McVGuiUtils.setComponentWidth(comboBox, McVGuiUtils.Width.ONEHALF); 111 return comboBox; 112 } 113 114 /** 115 * Returns the user's current selection (or the default value). 116 * 117 * @return Current selection or default value. 118 */ 119 public String getValue() { 120 return currentChoice; 121 } 122 123 /** 124 * Stores the user's selected logging level. Note that this can be called from third-party or the GUI! If the call 125 * originates from the GUI, an infinite loop is avoided by using the {@link JComboBox#setSelectedItem(Object)} 126 * behavior that does <b>not</b> generate {@link ItemEvent ItemEvents} if the selection did not actually change. 127 * 128 * @param value {@code String} representation of the desired logging 129 * level. Should not be {@code null}. 130 * 131 * @throws IllegalArgumentException if {@code value} failed {@link #isValidValue(String)}. 132 */ 133 public void setValue(String value) { 134 if (!isValidValue(value)) { 135 throw new IllegalArgumentException("Value '"+value+"' is not one of: TRACE, DEBUG, INFO, WARN, ERROR, or OFF."); 136 } 137 currentChoice = value; 138 Logger rootLogger = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); 139 rootLogger.setLevel(stringToLogback(value)); 140 SwingUtilities.invokeLater(new Runnable() { 141 public void run() { 142 if (comboBox != null) { 143 comboBox.setSelectedItem(currentChoice); 144 } 145 } 146 }); 147 } 148 149 /** 150 * Converts a {@code String} value to the corresponding logging level. 151 * 152 * <p>This functionality is similar to 153 * {@link Level#toLevel(String, Level)}, but for this use case it is 154 * preferable to know if an invalid {@code value} was provided.</p> 155 * 156 * @param value Value to convert. 157 * 158 * @return Logging level. 159 * 160 * @throws IllegalArgumentException if {@code value} did not have a 161 * corresponding logging level. 162 */ 163 private static Level stringToLogback(final String value) { 164 Level level; 165 if (TRACE.equalsIgnoreCase(value)) { 166 level = Level.TRACE; 167 } else if (DEBUG.equalsIgnoreCase(value)) { 168 level = Level.DEBUG; 169 } else if (INFO.equalsIgnoreCase(value)) { 170 level = Level.INFO; 171 } else if (WARN.equalsIgnoreCase(value)) { 172 level = Level.WARN; 173 } else if (ERROR.equalsIgnoreCase(value)) { 174 level = Level.ERROR; 175 } else if (OFF.equalsIgnoreCase(value)) { 176 level = Level.OFF; 177 } else { 178 throw new IllegalArgumentException(); 179 } 180 return level; 181 } 182 183 /** 184 * Tests a {@code String} value to see if it has a corresponding logging 185 * level. 186 * 187 * @param value Value to test. 188 * 189 * @return {@code true} if-and-only-if passes a 190 * {@link String#equalsIgnoreCase(String)} check against {@link #TRACE}, 191 * {@link #DEBUG}, {@link #INFO}, {@link #WARN}, {@link #ERROR}, or 192 * {@link #OFF}. 193 */ 194 private static boolean isValidValue(final String value) { 195 return (TRACE.equalsIgnoreCase(value) || 196 DEBUG.equalsIgnoreCase(value) || 197 INFO.equalsIgnoreCase(value) || 198 WARN.equalsIgnoreCase(value) || 199 ERROR.equalsIgnoreCase(value) || 200 OFF.equalsIgnoreCase(value)); 201 } 202 203 /** 204 * {@code String} representation of the user's logging level selection. 205 * 206 * @return {@code String} that looks something like 207 * {@literal "[LoggerLevel@7825114a: currentChoice=INFO]"}. 208 */ 209 public String toString() { 210 return String.format("[LoggerLevelOption@%x: currentChoice=%s]", 211 hashCode(), currentChoice); 212 } 213 }