View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe;
12  
13  import com.google.common.base.Strings;
14  
15  import java.io.BufferedInputStream;
16  import java.io.BufferedReader;
17  import java.io.ByteArrayInputStream;
18  import java.io.ByteArrayOutputStream;
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.OutputStream;
24  import java.io.RandomAccessFile;
25  import java.io.Reader;
26  import java.lang.management.ManagementFactory;
27  import java.nio.charset.Charset;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.Locale;
33  import java.util.Scanner;
34  import java.util.Set;
35  import java.util.zip.ZipEntry;
36  import java.util.zip.ZipOutputStream;
37  
38  import javax.management.MBeanServer;
39  import javax.management.MalformedObjectNameException;
40  import javax.management.ObjectInstance;
41  import javax.management.ObjectName;
42  import javax.servlet.http.HttpServletRequest;
43  import javax.servlet.http.HttpServletResponse;
44  
45  import org.codelibs.jhighlight.renderer.Renderer;
46  import org.codelibs.jhighlight.renderer.XhtmlRendererFactory;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import psiprobe.tokenizer.StringTokenizer;
51  import psiprobe.tokenizer.Token;
52  import psiprobe.tokenizer.Tokenizer;
53  import psiprobe.tokenizer.TokenizerSymbol;
54  
55  /**
56   * Misc. static helper methods.
57   */
58  public final class Utils {
59  
60    /** The Constant logger. */
61    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
62  
63    /**
64     * Prevent Instantiation.
65     */
66    private Utils() {
67      // Prevent Instantiation
68    }
69  
70    /**
71     * Reads a file on disk. The method uses default file encoding (see: file.encoding system
72     * property)
73     *
74     * @param file to be read
75     * @param charsetName the charset name
76     *
77     * @return String representation of the file contents
78     *
79     * @throws IOException Signals that an I/O exception has occurred.
80     */
81    public static String readFile(File file, String charsetName) throws IOException {
82      try (InputStream fis = Files.newInputStream(file.toPath())) {
83        return readStream(fis, charsetName);
84      }
85    }
86  
87    /**
88     * Reads strings from the intput stream using the given charset. This method closes the input
89     * stream after it has been consumed.
90     *
91     * <p>
92     * This method uses the system's default charset if the given one is unsupported.
93     * </p>
94     *
95     * @param is the stream from which to read
96     * @param charsetName the charset to use when reading the stream
97     *
98     * @return the contents of the given input stream
99     *
100    * @throws IOException if reading from the stream fails spectacularly
101    */
102   public static String readStream(InputStream is, String charsetName) throws IOException {
103     Charset charset;
104     if (Charset.isSupported(charsetName)) {
105       charset = Charset.forName(charsetName);
106     } else {
107       // use system's default encoding if the passed encoding is unsupported
108       charset = Charset.defaultCharset();
109     }
110 
111     StringBuilder out = new StringBuilder();
112     try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset), 4096)) {
113       String line;
114       while ((line = reader.readLine()) != null) {
115         out.append(line).append('\n');
116       }
117     }
118 
119     return out.toString();
120   }
121 
122   /**
123    * Delete.
124    *
125    * @param file the file
126    */
127   public static void delete(File file) {
128     if (file != null && file.exists()) {
129       if (file.isDirectory()) {
130         for (File child : file.listFiles()) {
131           delete(child);
132         }
133       }
134       try {
135         Files.delete(file.toPath());
136       } catch (IOException e) {
137         logger.debug("Cannot delete '{}'", file.getAbsolutePath(), e);
138       }
139     } else {
140       logger.debug("'{}' does not exist", file);
141     }
142   }
143 
144   /**
145    * To int.
146    *
147    * @param num the num
148    * @param defaultValue the default value
149    *
150    * @return the int
151    */
152   public static int toInt(String num, int defaultValue) {
153     if (num != null && !num.contains(" ")) {
154       try (Scanner scanner = new Scanner(num)) {
155         if (scanner.hasNextInt()) {
156           return scanner.nextInt();
157         }
158       }
159     }
160     return defaultValue;
161   }
162 
163   /**
164    * To int hex.
165    *
166    * @param num the num
167    * @param defaultValue the default value
168    *
169    * @return the int
170    */
171   public static int toIntHex(String num, int defaultValue) {
172     if (num != null && !num.contains(" ")) {
173       if (num.startsWith("#")) {
174         num = num.substring(1);
175       }
176       try (Scanner scanner = new Scanner(num)) {
177         if (scanner.hasNextInt()) {
178           return scanner.nextInt(16);
179         }
180       }
181     }
182     return defaultValue;
183   }
184 
185   /**
186    * To long.
187    *
188    * @param num the num
189    * @param defaultValue the default value
190    *
191    * @return the long
192    */
193   public static long toLong(String num, long defaultValue) {
194     if (num != null && !num.contains(" ")) {
195       try (Scanner scanner = new Scanner(num)) {
196         if (scanner.hasNextLong()) {
197           return scanner.nextLong();
198         }
199       }
200     }
201     return defaultValue;
202   }
203 
204   /**
205    * To long.
206    *
207    * @param num the num
208    * @param defaultValue the default value
209    *
210    * @return the long
211    */
212   public static long toLong(Long num, long defaultValue) {
213     return num == null ? defaultValue : num;
214   }
215 
216   /**
217    * To float.
218    *
219    * @param num the num
220    * @param defaultValue the default value
221    *
222    * @return the float
223    */
224   public static float toFloat(String num, float defaultValue) {
225     if (num != null && !num.contains(" ")) {
226       try (Scanner scanner = new Scanner(num)) {
227         if (scanner.hasNextFloat()) {
228           return scanner.nextFloat();
229         }
230       }
231     }
232     return defaultValue;
233   }
234 
235   /**
236    * Gets the jsp encoding.
237    *
238    * @param is the is
239    *
240    * @return the jsp encoding
241    *
242    * @throws IOException Signals that an I/O exception has occurred.
243    */
244   public static String getJspEncoding(InputStream is) throws IOException {
245 
246     String encoding = null;
247     String contentType = null;
248 
249     Tokenizer jspTokenizer = new Tokenizer();
250     jspTokenizer.addSymbol("\n", true);
251     jspTokenizer.addSymbol(" ", true);
252     jspTokenizer.addSymbol("\t", true);
253     jspTokenizer.addSymbol(new TokenizerSymbol("dir", "<%@", "%>", false, false, true, false));
254 
255     StringTokenizer directiveTokenizer = new StringTokenizer();
256     directiveTokenizer.addSymbol("\n", true);
257     directiveTokenizer.addSymbol(" ", true);
258     directiveTokenizer.addSymbol("\t", true);
259     directiveTokenizer.addSymbol("=");
260     directiveTokenizer.addSymbol("\"", "\"", false);
261     directiveTokenizer.addSymbol("'", "'", false);
262 
263     StringTokenizer contentTypeTokenizer = new StringTokenizer();
264     contentTypeTokenizer.addSymbol(" ", true);
265     contentTypeTokenizer.addSymbol(";", true);
266 
267     try (Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
268       jspTokenizer.setReader(reader);
269       while (jspTokenizer.hasMore()) {
270         Token token = jspTokenizer.nextToken();
271         if ("dir".equals(token.getName())) {
272           directiveTokenizer.setString(token.getInnerText());
273           if (directiveTokenizer.hasMore()
274               && "page".equals(directiveTokenizer.nextToken().getText())) {
275             while (directiveTokenizer.hasMore()) {
276               Token directiveToken = directiveTokenizer.nextToken();
277               if ("pageEncoding".equals(directiveToken.getText())) {
278                 if (directiveTokenizer.hasMore()
279                     && "=".equals(directiveTokenizer.nextToken().getText())
280                     && directiveTokenizer.hasMore()) {
281                   encoding = directiveTokenizer.nextToken().getInnerText();
282                   break;
283                 }
284               } else if ("contentType".equals(directiveToken.getText())
285                   && directiveTokenizer.hasMore()
286                   && "=".equals(directiveTokenizer.nextToken().getText())
287                   && directiveTokenizer.hasMore()) {
288                 contentType = directiveTokenizer.nextToken().getInnerText();
289               }
290             }
291           }
292         }
293       }
294     }
295 
296     if (encoding == null && contentType != null) {
297       contentTypeTokenizer.setString(contentType);
298       while (contentTypeTokenizer.hasMore()) {
299         String token = contentTypeTokenizer.nextToken().getText();
300         if (token.startsWith("charset=")) {
301           encoding = token.substring("charset=".length());
302           break;
303         }
304       }
305     }
306 
307     return encoding != null ? encoding : StandardCharsets.UTF_8.name();
308   }
309 
310   /**
311    * Send file.
312    *
313    * @param request the request
314    * @param response the response
315    * @param file the file
316    *
317    * @throws IOException Signals that an I/O exception has occurred.
318    */
319   public static void sendFile(HttpServletRequest request, HttpServletResponse response, File file)
320       throws IOException {
321 
322     try (OutputStream out = response.getOutputStream();
323         RandomAccessFile raf = new RandomAccessFile(file, "r")) {
324       long fileSize = raf.length();
325       long rangeStart = 0;
326       long rangeFinish = fileSize - 1;
327 
328       // accept attempts to resume download (if any)
329       String range = request.getHeader("Range");
330       if (range != null && range.startsWith("bytes=")) {
331         String pureRange = range.replace("bytes=", "");
332         int rangeSep = pureRange.indexOf('-');
333 
334         try {
335           rangeStart = Long.parseLong(pureRange.substring(0, rangeSep));
336           if (rangeStart > fileSize || rangeStart < 0) {
337             rangeStart = 0;
338           }
339         } catch (NumberFormatException e) {
340           // keep rangeStart unchanged
341           logger.trace("", e);
342         }
343 
344         if (rangeSep < pureRange.length() - 1) {
345           try {
346             rangeFinish = Long.parseLong(pureRange.substring(rangeSep + 1));
347             if (rangeFinish < 0 || rangeFinish >= fileSize) {
348               rangeFinish = fileSize - 1;
349             }
350           } catch (NumberFormatException e) {
351             logger.trace("", e);
352           }
353         }
354       }
355 
356       // set some headers
357       response.setContentType("application/x-download");
358       response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
359       response.setHeader("Accept-Ranges", "bytes");
360       response.setHeader("Content-Length", Long.toString(rangeFinish - rangeStart + 1));
361       response.setHeader("Content-Range",
362           "bytes " + rangeStart + "-" + rangeFinish + "/" + fileSize);
363 
364       // seek to the requested offset
365       raf.seek(rangeStart);
366 
367       // send the file
368       byte[] buffer = new byte[4096];
369 
370       long len;
371       int totalRead = 0;
372       boolean nomore = false;
373       while (true) {
374         len = raf.read(buffer);
375         if (len > 0 && totalRead + len > rangeFinish - rangeStart + 1) {
376           // read more then required?
377           // adjust the length
378           len = rangeFinish - rangeStart + 1 - totalRead;
379           nomore = true;
380         }
381 
382         if (len <= 0) {
383           break;
384         }
385         out.write(buffer, 0, (int) len);
386         totalRead = totalRead + (int) len;
387         if (nomore) {
388           break;
389         }
390       }
391     }
392   }
393 
394   /**
395    * Gets the thread by name.
396    *
397    * @param name the name
398    *
399    * @return the thread by name
400    */
401   public static Thread getThreadByName(String name) {
402     if (name != null) {
403       // get top ThreadGroup
404       ThreadGroup masterGroup = Thread.currentThread().getThreadGroup();
405       while (masterGroup.getParent() != null) {
406         masterGroup = masterGroup.getParent();
407       }
408 
409       Thread[] threads = new Thread[masterGroup.activeCount()];
410       masterGroup.enumerate(threads);
411 
412       for (Thread thread : threads) {
413         if (thread != null && name.equals(thread.getName())) {
414           return thread;
415         }
416       }
417     }
418     return null;
419   }
420 
421   /**
422    * Highlight stream.
423    *
424    * @param name the name
425    * @param input the input
426    * @param rendererName the renderer name
427    * @param encoding the encoding
428    *
429    * @return the string
430    *
431    * @throws IOException Signals that an I/O exception has occurred.
432    */
433   public static String highlightStream(String name, InputStream input, String rendererName,
434       String encoding) throws IOException {
435 
436     Renderer jspRenderer = XhtmlRendererFactory.getRenderer(rendererName);
437     if (jspRenderer == null) {
438       return null;
439     }
440 
441     ByteArrayOutputStream bos = new ByteArrayOutputStream();
442     jspRenderer.highlight(name, input, bos, encoding, true);
443 
444     ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
445 
446     Tokenizer tokenizer = new Tokenizer(new InputStreamReader(bis, Charset.forName(encoding)));
447     tokenizer.addSymbol(new TokenizerSymbol("EOL", "\n", null, false, false, true, false));
448     tokenizer.addSymbol(new TokenizerSymbol("EOL", "\r\n", null, false, false, true, false));
449 
450     //
451     // JHighlight adds HTML comment as the first line, so if
452     // we number the lines we could end up with a line number and no line
453     // to avoid that we just ignore the first line all together.
454     //
455     StringBuilder buffer = new StringBuilder();
456     long counter = 0;
457     while (tokenizer.hasMore()) {
458       Token tk = tokenizer.nextToken();
459       if ("EOL".equals(tk.getName())) {
460         counter++;
461         buffer.append(tk.getText());
462       } else if (counter > 0) {
463         buffer.append("<span class=\"codeline\">");
464         buffer.append("<span class=\"linenum\">");
465         buffer.append(leftPad(Long.toString(counter), 6, " ").replace(" ", "&nbsp;"));
466         buffer.append("</span>");
467         buffer.append(tk.getText());
468         buffer.append("</span>");
469       }
470     }
471     return buffer.toString();
472   }
473 
474   /**
475    * Send compressed file.
476    *
477    * @param response the response
478    * @param file the file
479    *
480    * @throws IOException Signals that an I/O exception has occurred.
481    */
482   public static void sendCompressedFile(HttpServletResponse response, File file)
483       throws IOException {
484     try (ZipOutputStream zip = new ZipOutputStream(response.getOutputStream());
485         InputStream fileInput = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
486 
487       String fileName = file.getName();
488 
489       // set some headers
490       response.setContentType("application/zip");
491       response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".zip");
492 
493       zip.putNextEntry(new ZipEntry(fileName));
494 
495       // send the file
496       byte[] buffer = new byte[4096];
497       long len;
498 
499       while ((len = fileInput.read(buffer)) > 0) {
500         zip.write(buffer, 0, (int) len);
501       }
502       zip.closeEntry();
503     }
504   }
505 
506   /**
507    * Left pad.
508    *
509    * @param str the str
510    * @param len the len
511    * @param fill the fill
512    *
513    * @return the string
514    */
515   static String leftPad(final String str, final int len, final String fill) {
516     if (str != null && str.length() < len) {
517       return Strings.padStart(str, len, fill.charAt(0));
518     }
519     return str == null ? "" : str;
520   }
521 
522   /**
523    * Gets the names for locale.
524    *
525    * @param baseName the base name
526    * @param locale the locale
527    *
528    * @return the names for locale
529    */
530   public static List<String> getNamesForLocale(String baseName, Locale locale) {
531     List<String> result = new ArrayList<>(3);
532     String language = locale.getLanguage();
533     String country = locale.getCountry();
534     String variant = locale.getVariant();
535     StringBuilder temp = new StringBuilder(baseName);
536 
537     if (language.length() > 0) {
538       temp.append('_').append(language);
539       result.add(0, temp.toString());
540     }
541 
542     if (country.length() > 0) {
543       temp.append('_').append(country);
544       result.add(0, temp.toString());
545     }
546 
547     if (variant.length() > 0) {
548       temp.append('_').append(variant);
549       result.add(0, temp.toString());
550     }
551 
552     return result;
553   }
554 
555   /**
556    * Checks if is threading enabled.
557    *
558    * @return true, if is threading enabled
559    */
560   public static boolean isThreadingEnabled() {
561     try {
562       MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
563       ObjectName objectNameThreading = new ObjectName("java.lang:type=Threading");
564       Set<ObjectInstance> threading = mbeanServer.queryMBeans(objectNameThreading, null);
565       return threading != null && !threading.isEmpty();
566     } catch (MalformedObjectNameException e) {
567       logger.trace("", e);
568       return false;
569     }
570   }
571 }