View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit;
16  
17  import java.io.Serializable;
18  import java.util.Arrays;
19  import java.util.LinkedList;
20  import java.util.List;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  /**
26   * This class can be used to print messages to the logger. The first parameter
27   * can be a message-object containing format specifiers such as ("%o", "%s",
28   * "%d", "%i", "%f"). The logging methods are null-safe, so if the number of
29   * format specifiers and the numbers of parameters don't match, no exception is thrown.
30   *
31   * The default logger uses Apache Commons Logging.
32   *
33   * @author Andrea Martino
34   */
35  public class WebConsole implements Serializable {
36  
37      /**
38       * A simple logging interface abstracting logging APIs.
39       */
40      public interface Logger {
41  
42          /**
43           * Is trace logging currently enabled?
44           * <p>
45           * Call this method to prevent having to perform expensive operations
46           * (for example, <code>String</code> concatenation)
47           * when the log level is more than trace.
48           *
49           * @return true if trace is enabled in the underlying logger.
50           */
51          boolean isTraceEnabled();
52  
53          /**
54           * Logs a message with trace log level.
55           *
56           * @param message log this message
57           */
58          void trace(Object message);
59  
60          /**
61           * Is debug logging currently enabled?
62           * <p>
63           * Call this method to prevent having to perform expensive operations
64           * (for example, <code>String</code> concatenation)
65           * when the log level is more than debug.
66           *
67           * @return true if debug is enabled in the underlying logger.
68           */
69          boolean isDebugEnabled();
70  
71          /**
72           * Logs a message with debug log level.
73           *
74           * @param message log this message
75           */
76          void debug(Object message);
77  
78          /**
79           * Is info logging currently enabled?
80           * <p>
81           * Call this method to prevent having to perform expensive operations
82           * (for example, <code>String</code> concatenation)
83           * when the log level is more than info.
84           *
85           * @return true if info is enabled in the underlying logger.
86           */
87          boolean isInfoEnabled();
88  
89          /**
90           * Logs a message with info log level.
91           *
92           * @param message log this message
93           */
94          void info(Object message);
95  
96          /**
97           * Is warn logging currently enabled?
98           * <p>
99           * Call this method to prevent having to perform expensive operations
100          * (for example, <code>String</code> concatenation)
101          * when the log level is more than warn.
102          *
103          * @return true if warn is enabled in the underlying logger.
104          */
105         boolean isWarnEnabled();
106 
107         /**
108          * Logs a message with warn log level.
109          *
110          * @param message log this message
111          */
112         void warn(Object message);
113 
114         /**
115          * Is error logging currently enabled?
116          * <p>
117          * Call this method to prevent having to perform expensive operations
118          * (for example, <code>String</code> concatenation)
119          * when the log level is more than error.
120          *
121          * @return true if error is enabled in the underlying logger.
122          */
123         boolean isErrorEnabled();
124 
125         /**
126          * Logs a message with error log level.
127          *
128          * @param message log this message
129          */
130         void error(Object message);
131     }
132 
133     /**
134      * This interface can be implemented by clients that want to customize
135      * the way parameters and objects are logged.
136      */
137     public interface Formatter {
138         /**
139          * Function that is used to print an object to the console.
140          * @param o object to be printed
141          * @return a string representation of the passed object
142          */
143         String printObject(Object o);
144 
145         /**
146          * Function that is used to print an object as string using
147          * format specifiers.
148          * @param o object to be printed using string format specifiers
149          * @return a string representation of the passed object
150          */
151         String parameterAsString(Object o);
152 
153         /**
154          * Function that is used to print an object as integer using
155          * format specifiers.
156          * @param o object to be printed using integer format specifiers
157          * @return a string representation of the passed object
158          */
159         String parameterAsInteger(Object o);
160 
161         /**
162          * Function that is used to print an object as float using
163          * format specifiers.
164          * @param o object to be printed using float format specifiers
165          * @return a string representation of the passed object
166          */
167         String parameterAsFloat(Object o);
168     }
169 
170     private Formatter formatter_ = new DefaultFormatter();
171     private Logger logger_ = new DefaultLogger();
172 
173     /**
174      * Sets the Formatter.
175      * @param formatter the formatter
176      */
177     public void setFormatter(final Formatter formatter) {
178         formatter_ = formatter;
179     }
180 
181     /**
182      * Returns the current Formatter.
183      * @return the Formatter
184      */
185     public Formatter getFormatter() {
186         return formatter_;
187     }
188 
189     /**
190      * Sets the Logger_.
191      * @param logger the logger
192      */
193     public void setLogger(final Logger logger) {
194         logger_ = logger;
195     }
196 
197     /**
198      * Returns the current Logger.
199      * @return the logger
200      */
201     public Logger getLogger() {
202         return logger_;
203     }
204 
205     /**
206      * Prints the passed objects using logger trace level.
207      * @param args the logging parameters
208      */
209     public void trace(final Object... args) {
210         if (logger_.isTraceEnabled()) {
211             logger_.trace(process(args));
212         }
213     }
214 
215     /**
216      * Prints the passed objects using logger debug level.
217      * @param args the logging parameters
218      */
219     public void debug(final Object... args) {
220         if (logger_.isDebugEnabled()) {
221             logger_.debug(process(args));
222         }
223     }
224 
225     /**
226      * Prints the passed objects using logger info level.
227      * @param args the logging parameters
228      */
229     public void info(final Object... args) {
230         if (logger_.isInfoEnabled()) {
231             logger_.info(process(args));
232         }
233     }
234 
235     /**
236      * Prints the passed objects using logger warn level.
237      * @param args the logging parameters
238      */
239     public void warn(final Object... args) {
240         if (logger_.isWarnEnabled()) {
241             logger_.warn(process(args));
242         }
243     }
244 
245     /**
246      * Prints the passed objects using logger error level.
247      * @param args the logging parameters
248      */
249     public void error(final Object... args) {
250         if (logger_.isErrorEnabled()) {
251             logger_.error(process(args));
252         }
253     }
254 
255     /**
256      * This method is used by all the public method to process the passed
257      * parameters.
258      *
259      * If the last parameter implements the Formatter interface, it will be
260      * used to format the parameters and print the object.
261      * @param objs the logging parameters
262      * @return a String to be printed using the logger
263      */
264     private String process(final Object[] objs) {
265         if (objs == null) {
266             return "null";
267         }
268 
269         final StringBuilder sb = new StringBuilder();
270         final LinkedList<Object> args = new LinkedList<>(Arrays.asList(objs));
271 
272         final Formatter formatter = getFormatter();
273 
274         if (args.size() > 1 && args.get(0) instanceof String) {
275             final StringBuilder msg = new StringBuilder((String) args.remove(0));
276             int startPos = msg.indexOf("%");
277 
278             while (startPos > -1 && startPos < msg.length() - 1 && args.size() > 0) {
279                 if (startPos != 0 && msg.charAt(startPos - 1) == '%') {
280                     // double %
281                     msg.replace(startPos, startPos + 1, "");
282                 }
283                 else {
284                     final char type = msg.charAt(startPos + 1);
285                     String replacement = null;
286                     switch (type) {
287                         case 'o':
288                         case 's':
289                             replacement = formatter.parameterAsString(pop(args));
290                             break;
291                         case 'd':
292                         case 'i':
293                             replacement = formatter.parameterAsInteger(pop(args));
294                             break;
295                         case 'f':
296                             replacement = formatter.parameterAsFloat(pop(args));
297                             break;
298                         default:
299                             break;
300                     }
301                     if (replacement != null) {
302                         msg.replace(startPos, startPos + 2, replacement);
303                         startPos = startPos + replacement.length();
304                     }
305                     else {
306                         startPos++;
307                     }
308                 }
309                 startPos = msg.indexOf("%", startPos);
310             }
311             sb.append(msg);
312         }
313 
314         for (final Object o : args) {
315             if (sb.length() != 0) {
316                 sb.append(' ');
317             }
318             sb.append(formatter.printObject(o));
319         }
320         return sb.toString();
321     }
322 
323     /**
324      * This method removes and returns the first (i.e. at index 0) element in
325      * the passed list if it exists. Otherwise, null is returned.
326      * @param list the
327      * @return the first object in the list or null
328      */
329     private static Object pop(final List<Object> list) {
330         return list.isEmpty() ? null : list.remove(0);
331     }
332 
333     /**
334      * This class is the default formatter used by WebConsole.
335      */
336     private static class DefaultFormatter implements Formatter, Serializable {
337 
338         @Override
339         public String printObject(final Object o) {
340             return parameterAsString(o);
341         }
342 
343         @Override
344         public String parameterAsString(final Object o) {
345             if (o != null) {
346                 return o.toString();
347             }
348             return "null";
349         }
350 
351         @Override
352         public String parameterAsInteger(final Object o) {
353             if (o instanceof Number) {
354                 return Integer.toString(((Number) o).intValue());
355             }
356             else if (o instanceof String) {
357                 try {
358                     return Integer.toString(Integer.parseInt((String) o));
359                 }
360                 catch (final NumberFormatException e) {
361                     // Swallow the exception and return below
362                 }
363             }
364             return "NaN";
365         }
366 
367         @Override
368         public String parameterAsFloat(final Object o) {
369             if (o instanceof Number) {
370                 return Float.toString(((Number) o).floatValue());
371             }
372             else if (o instanceof String) {
373                 try {
374                     return Float.toString(Float.parseFloat((String) o));
375                 }
376                 catch (final NumberFormatException e) {
377                     // Swallow the exception and return below
378                 }
379             }
380             return "NaN";
381         }
382     }
383 
384     /**
385      * This class is the default logger used by WebConsole.
386      */
387     private static class DefaultLogger implements Logger, Serializable {
388         /** Logging support. */
389         private static final Log LOG = LogFactory.getLog(WebConsole.class);
390 
391         @Override
392         public boolean isTraceEnabled() {
393             return LOG.isTraceEnabled();
394         }
395 
396         @Override
397         public void trace(final Object message) {
398             if (LOG.isTraceEnabled()) {
399                 LOG.trace(message);
400             }
401         }
402 
403         @Override
404         public boolean isDebugEnabled() {
405             return LOG.isDebugEnabled();
406         }
407 
408         @Override
409         public void debug(final Object message) {
410             if (LOG.isDebugEnabled()) {
411                 LOG.debug(message);
412             }
413         }
414 
415         @Override
416         public boolean isInfoEnabled() {
417             return LOG.isInfoEnabled();
418         }
419 
420         @Override
421         public void info(final Object message) {
422             LOG.info(message);
423         }
424 
425         @Override
426         public boolean isWarnEnabled() {
427             return LOG.isWarnEnabled();
428         }
429 
430         @Override
431         public void warn(final Object message) {
432             LOG.warn(message);
433         }
434 
435         @Override
436         public boolean isErrorEnabled() {
437             return LOG.isErrorEnabled();
438         }
439 
440         @Override
441         public void error(final Object message) {
442             LOG.error(message);
443         }
444     }
445 }