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 }