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    }