001package runwar;
002
003import java.awt.AWTException;
004import java.awt.Image;
005import java.awt.MenuItem;
006import java.awt.PopupMenu;
007import java.awt.SystemTray;
008import java.awt.Toolkit;
009import java.awt.TrayIcon;
010import java.awt.event.ActionEvent;
011import java.awt.event.ActionListener;
012import java.awt.event.MouseEvent;
013import java.awt.event.MouseListener;
014import java.io.BufferedInputStream;
015import java.io.BufferedReader;
016import java.io.ByteArrayOutputStream;
017import java.io.File;
018import java.io.FileInputStream;
019import java.io.FileNotFoundException;
020import java.io.FileOutputStream;
021import java.io.FilenameFilter;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.InputStreamReader;
025import java.io.OutputStream;
026import java.io.PrintStream;
027import java.lang.management.ManagementFactory;
028import java.lang.management.RuntimeMXBean;
029import java.lang.reflect.Method;
030import java.net.MalformedURLException;
031import java.net.URISyntaxException;
032import java.net.URL;
033import java.net.URLDecoder;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Set;
039import java.util.Timer;
040import java.util.TimerTask;
041import java.util.jar.JarEntry;
042import java.util.jar.JarInputStream;
043import java.util.jar.JarOutputStream;
044import java.util.jar.Pack200;
045import java.util.zip.GZIPInputStream;
046import java.util.zip.ZipEntry;
047import java.util.zip.ZipFile;
048
049import javax.imageio.ImageIO;
050import javax.swing.JOptionPane;
051
052import net.minidev.json.JSONArray;
053import net.minidev.json.JSONObject;
054import net.minidev.json.JSONValue;
055import runwar.logging.Logger;
056import runwar.options.ServerOptions;
057
058public class LaunchUtil {
059
060    private static TrayIcon trayIcon;
061    private static Logger log = Logger.getLogger("RunwarLogger");
062    private static boolean relaunching;
063    private static final int KB = 1024;
064    public static final Set<String> replicateProps = new HashSet<String>(Arrays.asList(new String[] { "cfml.cli.home",
065            "cfml.server.config.dir", "cfml.web.config.dir", "cfml.server.trayicon", "cfml.server.dockicon" }));
066
067    public static File getJreExecutable() throws FileNotFoundException {
068        String jreDirectory = System.getProperty("java.home");
069        if (jreDirectory == null) {
070            throw new IllegalStateException("java.home");
071        }
072        final String javaPath = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"
073                + (File.separator.equals("\\") ? ".exe" : "");
074        File exe = new File(javaPath);
075        if (!exe.isFile()) {
076            throw new FileNotFoundException(exe.toString());
077        }
078        // if(debug)System.out.println("Java: "+javaPath);
079        return exe;
080    }
081    
082    public static File getJarDir(Class<?> aclass) {
083        URL url;
084        String extURL;
085        try {
086            url = aclass.getProtectionDomain().getCodeSource().getLocation();
087        } catch (SecurityException ex) {
088            url = aclass.getResource(aclass.getSimpleName() + ".class");
089        }
090        extURL = url.toExternalForm();
091        if (extURL.endsWith(".jar"))   // from getCodeSource
092            extURL = extURL.substring(0, extURL.lastIndexOf("/"));
093        else {  // from getResource
094            String suffix = "/"+(aclass.getName()).replace(".", "/")+".class";
095            extURL = extURL.replace(suffix, "");
096            if (extURL.startsWith("jar:") && extURL.endsWith(".jar!"))
097                extURL = extURL.substring(4, extURL.lastIndexOf("/"));
098        }
099        try {
100            url = new URL(extURL);
101        } catch (MalformedURLException mux) {
102        }
103        try {
104            return new File(url.toURI());
105        } catch(URISyntaxException ex) {
106            return new File(url.getPath());
107        }
108    }
109    
110    public static void launch(List<String> cmdarray, int timeout) throws IOException, InterruptedException {
111        // byte[] buffer = new byte[1024];
112
113        ProcessBuilder processBuilder = new ProcessBuilder(cmdarray);
114        processBuilder.redirectErrorStream(true);
115        Process process = processBuilder.start();
116        Thread.sleep(500);
117        InputStream is = process.getInputStream();
118        InputStreamReader isr = new InputStreamReader(is);
119        BufferedReader br = new BufferedReader(isr);
120        log.debug("launching: " + cmdarray.toString());
121        log.debug("timeout of " + timeout / 1000 + " seconds");
122        String line;
123        int exit = -1;
124        long start = System.currentTimeMillis();
125        System.out.print("Starting in background - ");
126        while ((System.currentTimeMillis() - start) < timeout) {
127            if (br.ready() && (line = br.readLine()) != null) {
128                // Outputs your process execution
129                try {
130                    exit = process.exitValue();
131                    if (exit == 0) {
132                        // Process finished
133                        while ((line = br.readLine()) != null) {
134                            log.debug(line);
135                        }
136                        System.exit(0);
137                    } else if (exit == 1) {
138                        System.out.println();
139                        printExceptionLine(line);
140                        while ((line = br.readLine()) != null) {
141                            printExceptionLine(line);
142                        }
143                        System.exit(1);
144                    }
145                } catch (IllegalThreadStateException t) {
146                    // This exceptions means the process has not yet finished.
147                    // decide to continue, exit(0), or exit(1)
148                    processOutout(line, process);
149                }
150            }
151            Thread.sleep(100);
152        }
153        if ((System.currentTimeMillis() - start) > timeout) {
154            process.destroy();
155            System.out.println();
156            System.err.println("ERROR: Startup exceeded timeout of " + timeout / 1000 + " seconds - aborting!");
157            System.exit(1);
158        }
159        System.out.println("Server is up - ");
160        System.exit(0);
161    }
162
163    private static boolean processOutout(String line, Process process) {
164        log.info("processoutput: " + line);
165        if (line.indexOf("Server is up - ") != -1) {
166            // start up was successful, quit out
167            System.out.println(line);
168            System.exit(0);
169        } else if (line.indexOf("Exception in thread \"main\" java.lang.RuntimeException") != -1) {
170            return true;
171        }
172        return false;
173    }
174
175    public static void printExceptionLine(String line) {
176        final String msg = "java.lang.RuntimeException: ";
177        log.debug(line);
178        String formatted = line.contains(msg) ? line.substring(line.indexOf(msg) + msg.length()) : line;
179        formatted = formatted.matches("^\\s+at runwar.Start.*") ? "" : formatted.trim();
180        if (formatted.length() > 0) {
181            System.err.println(formatted);
182        }
183    }
184
185    public static void relaunchAsBackgroundProcess(int timeout, String[] args, String processName) {
186        try {
187            if (relaunching)
188                return;
189            relaunching = true;
190            String path = LaunchUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath();
191            log.info("Starting background " + processName + " from: " + path + " ");
192            String decodedPath = URLDecoder.decode(path, "UTF-8");
193            decodedPath = new File(decodedPath).getPath();
194            List<String> cmdarray = new ArrayList<String>();
195            cmdarray.add(getJreExecutable().toString());
196            List<String> currentVMArgs = getCurrentVMArgs();
197            for (String arg : currentVMArgs) {
198                cmdarray.add(arg);
199            }
200            cmdarray.add("-jar");
201            for (String propertyName : replicateProps) {
202                String property = System.getProperty(propertyName);
203                if (property != null) {
204                    cmdarray.add("-D" + propertyName + "=" + property);
205                }
206            }
207            cmdarray.add(decodedPath);
208            for (String arg : args) {
209                cmdarray.add(arg);
210            }
211            launch(cmdarray, timeout);
212        } catch (Exception e) {
213            throw new RuntimeException(e);
214        }
215    }
216
217    private static void appendToBuffer(List<String> resultBuffer, StringBuffer buf) {
218        if (buf.length() > 0) {
219            resultBuffer.add(buf.toString());
220            buf.setLength(0);
221        }
222    }
223
224    public static List<String> getCurrentVMArgs() {
225        RuntimeMXBean RuntimemxBean = ManagementFactory.getRuntimeMXBean();
226        List<String> arguments = RuntimemxBean.getInputArguments();
227        return arguments;
228    }
229
230    public static String[] tokenizeArgs(String argLine) {
231        List<String> resultBuffer = new java.util.ArrayList<String>();
232
233        if (argLine != null) {
234            int z = argLine.length();
235            boolean insideQuotes = false;
236            StringBuffer buf = new StringBuffer();
237
238            for (int i = 0; i < z; ++i) {
239                char c = argLine.charAt(i);
240                if (c == '"') {
241                    appendToBuffer(resultBuffer, buf);
242                    insideQuotes = !insideQuotes;
243                } else if (c == '\\') {
244                    if ((z > i + 1) && ((argLine.charAt(i + 1) == '"') || (argLine.charAt(i + 1) == '\\'))) {
245                        buf.append(argLine.charAt(i + 1));
246                        ++i;
247                    } else {
248                        buf.append("\\");
249                    }
250                } else {
251                    if (insideQuotes) {
252                        buf.append(c);
253                    } else {
254                        if (Character.isWhitespace(c)) {
255                            appendToBuffer(resultBuffer, buf);
256                        } else {
257                            buf.append(c);
258                        }
259                    }
260                }
261            }
262            appendToBuffer(resultBuffer, buf);
263
264        }
265
266        String[] result = new String[resultBuffer.size()];
267        return resultBuffer.toArray(result);
268    }
269
270    public static void hookTray(Server server) {
271        ServerOptions serverOptions = Server.getServerOptions();
272        String iconImage = serverOptions.getIconImage();
273        String host = serverOptions.getHost();
274        int portNumber = serverOptions.getPortNumber();
275        final int stopSocket = serverOptions.getSocketNumber();
276        String processName = serverOptions.getProcessName();
277        String PID = server.getPID();
278
279        if (SystemTray.isSupported()) {
280            Image image = getIconImage(iconImage);
281            MouseListener mouseListener = new MouseListener() {
282                public void mouseClicked(MouseEvent e) {
283                }
284
285                public void mouseEntered(MouseEvent e) {
286                }
287
288                public void mouseExited(MouseEvent e) {
289                }
290
291                public void mousePressed(MouseEvent e) {
292                }
293
294                public void mouseReleased(MouseEvent e) {
295                }
296            };
297
298            trayIcon = new TrayIcon(image, processName + " server on " + host + ":" + portNumber + " PID:" + PID);
299
300            PopupMenu popup = new PopupMenu();
301            MenuItem item = null;
302            JSONArray menuItems;
303
304            final String defaultMenu = "["
305                    + "{label:\"Stop Server (${runwar.processName})\", action:\"stopserver\"}"
306                    + ",{label:\"Open Browser\", action:\"openbrowser\", url:\"http://${runwar.host}:${runwar.port}/\"}"
307                    + "]";
308
309            if (serverOptions.getTrayConfig() != null) {
310                menuItems = (JSONArray) JSONValue.parse(readFile(serverOptions.getTrayConfig()));
311            } else {
312                menuItems = (JSONArray) JSONValue.parse(getResourceAsString("runwar/taskbar.json"));
313            }
314            if (menuItems == null) {
315                log.error("Could not load taskbar properties");
316                menuItems = (JSONArray) JSONValue.parse(defaultMenu);
317            }
318            for (Object ob : menuItems) {
319                JSONObject itemInfo = (JSONObject) ob;
320                String label = replaceMenuTokens(itemInfo.get("label").toString(), processName, host, portNumber,
321                        stopSocket);
322                String action = itemInfo.get("action").toString();
323                item = new MenuItem(label);
324                if (action.toLowerCase().equals("stopserver")) {
325                    item.addActionListener(new ExitActionListener());
326                } else if (action.toLowerCase().equals("openbrowser")) {
327                    String url = replaceMenuTokens(itemInfo.get("url").toString(), processName, host, portNumber,
328                            stopSocket);
329                    item.addActionListener(new OpenBrowserActionListener(url));
330                } else {
331                    log.error("Unknown menu item action \"" + action + "\" for \"" + label + "\"");
332                }
333                popup.add(item);
334            }
335
336            // MenuItem item = new MenuItem("Stop Server (" + processName +
337            // ")");
338            // item.addActionListener(new
339            // ExitActionListener(trayIcon,host,stopSocket));
340            // popup.add(item);
341            // item = new MenuItem("Open Browser");
342            // item.addActionListener(new
343            // OpenBrowserActionListener(trayIcon,"http://"+host+":"+portNumber
344            // + "/"));
345            // popup.add(item);
346            // item = new MenuItem("Open Admin");
347            // item.addActionListener(new
348            // OpenBrowserActionListener(trayIcon,railoAdminURL));
349            // popup.add(item);
350
351            trayIcon.setPopupMenu(popup);
352            trayIcon.setImageAutoSize(true);
353            trayIcon.addMouseListener(mouseListener);
354
355            try {
356                SystemTray.getSystemTray().add(trayIcon);
357            } catch (AWTException e) {
358                System.err.println("TrayIcon could not be added.");
359            }
360
361        } else {
362            log.warn("System Tray is not supported");
363        }
364    }
365
366    public static void unhookTray() {
367        if (SystemTray.isSupported() && trayIcon != null) {
368            try {
369                log.debug("Removing tray icon");
370                SystemTray.getSystemTray().remove(trayIcon);
371            } catch (Exception e) {
372                e.printStackTrace();
373            }
374        }
375    }
376
377    public static Image getIconImage(String iconImage) {
378        Image image = null;
379        if (iconImage != null && iconImage.length() != 0) {
380            iconImage = iconImage.replaceAll("(^\")|(\"$)", "");
381            log.debug("trying to load icon: " + iconImage);
382            if (iconImage.contains("!")) {
383                String[] zip = iconImage.split("!");
384                try {
385                    ZipFile zipFile = new ZipFile(zip[0]);
386                    ZipEntry zipEntry = zipFile.getEntry(zip[1].replaceFirst("^[\\/]", ""));
387                    InputStream entryStream = zipFile.getInputStream(zipEntry);
388                    image = ImageIO.read(entryStream);
389                    zipFile.close();
390                    log.debug("loaded image from archive: " + zip[0] + zip[1]);
391                } catch (IOException e2) {
392                    log.debug("Could not get zip resource: " + iconImage + "(" + e2.getMessage() + ")");
393                }
394            } else if (new File(iconImage).exists()) {
395                try {
396                    image = ImageIO.read(new File(iconImage));
397                } catch (IOException e1) {
398                    log.debug("Could not get file resource: " + iconImage + "(" + e1.getMessage() + ")");
399                }
400            } else {
401                log.debug("trying parent loader for image: " + iconImage);
402                URL imageURL = LaunchUtil.class.getClassLoader().getParent().getResource(iconImage);
403                if (imageURL == null) {
404                    log.debug("trying loader for image: " + iconImage);
405                    imageURL = LaunchUtil.class.getClassLoader().getResource(iconImage);
406                }
407                if (imageURL != null) {
408                    log.debug("Trying getImage for: " + imageURL);
409                    image = Toolkit.getDefaultToolkit().getImage(imageURL);
410                }
411            }
412        } else {
413            image = Toolkit.getDefaultToolkit().getImage(Start.class.getResource("/runwar/icon.png"));
414        }
415        // if bad image, use default
416        if (image == null) {
417            log.debug("Bad image, using default.");
418            image = Toolkit.getDefaultToolkit().getImage(Start.class.getResource("/runwar/icon.png"));
419        }
420        return image;
421    }
422
423    private static String replaceMenuTokens(String label, String processName, String host, int portNumber,
424            int stopSocket) {
425        label = label.replaceAll("\\$\\{runwar.port\\}", Integer.toString(portNumber))
426                .replaceAll("\\$\\{runwar.processName\\}", processName).replaceAll("\\$\\{runwar.host\\}", host)
427                .replaceAll("\\$\\{runwar.stopsocket\\}", Integer.toString(stopSocket));
428        return label;
429    }
430
431    private static class OpenBrowserActionListener implements ActionListener {
432        private String url;
433
434        public OpenBrowserActionListener(String url) {
435            this.url = url;
436        }
437
438        @Override
439        public void actionPerformed(ActionEvent e) {
440            trayIcon.displayMessage("Browser", "Opening browser", TrayIcon.MessageType.INFO);
441            openURL(url);
442        }
443    }
444
445    private static class ExitActionListener implements ActionListener {
446
447        public ExitActionListener() {
448        }
449
450        @Override
451        public void actionPerformed(ActionEvent e) {
452            try {
453                System.out.println("Exiting...");
454                System.exit(0);
455            } catch (Exception e1) {
456                trayIcon.displayMessage("Error", e1.getMessage(), TrayIcon.MessageType.INFO);
457                try {
458                    Thread.sleep(5000);
459                } catch (InterruptedException e2) {
460                }
461                System.exit(1);
462            }
463        }
464    }
465
466    public static void openURL(String url) {
467        String osName = System.getProperty("os.name");
468        if (url == null) {
469            System.out.println("ERROR: No URL specified to open the browser to!");
470            return;
471        }
472        try {
473            System.out.println(url);
474            if (osName.startsWith("Mac OS")) {
475                Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
476                Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class });
477                openURL.invoke(null, new Object[] { url });
478            } else if (osName.startsWith("Windows"))
479                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
480            else { // assume Unix or Linux
481                String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" };
482                String browser = null;
483                for (int count = 0; count < browsers.length && browser == null; count++)
484                    if (Runtime.getRuntime().exec(new String[] { "which", browsers[count] }).waitFor() == 0)
485                        browser = browsers[count];
486                if (browser == null)
487                    throw new Exception("Could not find web browser");
488                else
489                    Runtime.getRuntime().exec(new String[] { browser, url });
490            }
491        } catch (Exception e) {
492            e.printStackTrace();
493            JOptionPane.showMessageDialog(null, e.getMessage() + ":\n" + e.getLocalizedMessage());
494        }
495    }
496
497    public static String getResourceAsString(String path) {
498        return readStream(LaunchUtil.class.getClassLoader().getResourceAsStream(path));
499    }
500
501    public static void unzipInteralZip(ClassLoader classLoader, String resourcePath, File libDir, boolean debug) {
502        if (debug)
503            System.out.println("Extracting " + resourcePath);
504        libDir.mkdir();
505        URL resource = classLoader.getResource(resourcePath);
506        if (resource == null) {
507            System.err.println("Could not find the " + resourcePath + " on classpath!");
508            System.exit(1);
509        }
510        unzipResource(resource, libDir, debug);
511    }
512
513    public static void unzipResource(URL resource, File libDir, boolean debug) {
514        class PrintDot extends TimerTask {
515            public void run() {
516                System.out.print(".");
517            }
518        }
519        Timer timer = new Timer();
520        PrintDot task = new PrintDot();
521        timer.schedule(task, 0, 2000);
522        
523        try {
524            BufferedInputStream bis = new BufferedInputStream(resource.openStream());
525            JarInputStream jis = new JarInputStream(bis);
526            JarEntry je = null;
527            while ((je = jis.getNextJarEntry()) != null) {
528                java.io.File f = new java.io.File(libDir.toString() + java.io.File.separator + je.getName());
529                if (je.isDirectory()) {
530                    f.mkdirs();
531                    continue;
532                }
533                File parentDir = new File(f.getParent());
534                if (!parentDir.exists()) {
535                    parentDir.mkdirs();
536                }
537                FileOutputStream fileOutStream = new FileOutputStream(f);
538                writeStreamTo(jis, fileOutStream, 8 * KB);
539                if (f.getPath().endsWith("pack.gz")) {
540                    unpack(f);
541                    fileOutStream.close();
542                    f.delete();
543                }
544                fileOutStream.close();
545            }
546            
547        } catch (Exception exc) {
548            task.cancel();
549            exc.printStackTrace();
550        }
551        task.cancel();
552        
553    }
554    
555    public static void cleanUpUnpacked(File libDir) {
556        if (libDir.exists() && libDir.listFiles(new ExtFilter(".gz")).length > 0) {
557            for (File gz : libDir.listFiles(new ExtFilter(".gz"))) {
558                try {
559                    gz.delete();
560                } catch (Exception e) {
561                }
562            }
563        }
564    }
565
566    public static void removePreviousLibs(File libDir) {
567        if (libDir.exists() && libDir.listFiles(new ExtFilter(".jar")).length > 0) {
568            for (File previous : libDir.listFiles(new ExtFilter(".jar"))) {
569                try {
570                    previous.delete();
571                } catch (Exception e) {
572                    System.err.println("Could not delete previous lib: " + previous.getAbsolutePath());
573                }
574            }
575        }
576    }
577
578    public static void unpack(File inFile) {
579        JarOutputStream out = null;
580        InputStream in = null;
581        String inName = inFile.getPath();
582        String outName;
583
584        if (inName.endsWith(".pack.gz")) {
585            outName = inName.substring(0, inName.length() - 8);
586        } else if (inName.endsWith(".pack")) {
587            outName = inName.substring(0, inName.length() - 5);
588        } else {
589            outName = inName + ".unpacked";
590        }
591        try {
592            Pack200.Unpacker unpacker = Pack200.newUnpacker();
593            out = new JarOutputStream(new FileOutputStream(outName));
594            in = new FileInputStream(inName);
595            if (inName.endsWith(".gz"))
596                in = new GZIPInputStream(in);
597            unpacker.unpack(in, out);
598        } catch (IOException ex) {
599            ex.printStackTrace();
600        } finally {
601            if (in != null) {
602                try {
603                    in.close();
604                } catch (IOException ex) {
605                    System.err.println("Error closing file: " + ex.getMessage());
606                }
607            }
608            if (out != null) {
609                try {
610                    out.flush();
611                    out.close();
612                } catch (IOException ex) {
613                    System.err.println("Error closing file: " + ex.getMessage());
614                }
615            }
616        }
617    }
618
619    public static void copyInternalFile(ClassLoader classLoader, String resourcePath, File dest) {
620        URL resource = classLoader.getResource(resourcePath);
621        try {
622            copyStream(resource.openStream(), dest);
623        } catch (IOException e) {
624            log.error(e);
625        }
626
627    }
628
629    public static String readStream(InputStream is) {
630        ByteArrayOutputStream out = new ByteArrayOutputStream();
631        PrintStream outPrint = new PrintStream(out);
632        try {
633            int content;
634            while ((content = is.read()) != -1) {
635                // convert to char and display it
636                outPrint.print((char) content);
637            }
638        } catch (IOException e) {
639            e.printStackTrace();
640        } finally {
641            try {
642                if (is != null)
643                    is.close();
644                if (outPrint != null)
645                    outPrint.close();
646            } catch (IOException ex) {
647                ex.printStackTrace();
648            }
649        }
650        return out.toString();
651    }
652
653    public static String readFile(File source) {
654        try {
655            return readStream(new FileInputStream(source));
656        } catch (FileNotFoundException e) {
657            log.error(e);
658        }
659        return null;
660    }
661
662    public static void copyFile(File source, File dest) {
663        try {
664            copyStream(new FileInputStream(source), dest);
665        } catch (FileNotFoundException e) {
666            log.error(e);
667        }
668    }
669
670    public static void copyStream(InputStream bis, File dest) {
671        try {
672            FileOutputStream output = new FileOutputStream(dest);
673            writeStreamTo(bis, output, 8 * KB);
674            output.close();
675        } catch (FileNotFoundException e) {
676            log.error(e);
677        } catch (IOException e) {
678            log.error(e);
679        }
680
681    }
682
683    public static int writeStreamTo(final InputStream input, final OutputStream output, int bufferSize)
684            throws IOException {
685        int available = Math.min(input.available(), 256 * KB);
686        byte[] buffer = new byte[Math.max(bufferSize, available)];
687        int answer = 0;
688        int count = input.read(buffer);
689        while (count >= 0) {
690            output.write(buffer, 0, count);
691            answer += count;
692            count = input.read(buffer);
693        }
694        return answer;
695    }
696
697    public static void deleteRecursive(File f) throws IOException {
698        if (f.isDirectory()) {
699          for (File c : f.listFiles())
700              deleteRecursive(c);
701        }
702        if (!f.delete())
703            System.err.println("Could not delete file: " + f.getAbsolutePath());
704    }
705
706    public static class ExtFilter implements FilenameFilter {
707        private String ext;
708
709        public ExtFilter(String extension) {
710            ext = extension;
711        }
712
713        public boolean accept(File dir, String name) {
714            return name.toLowerCase().endsWith(ext);
715        }
716    }
717
718    public static class PrefixFilter implements FilenameFilter {
719        private String prefix;
720
721        public PrefixFilter(String prefix) {
722            this.prefix = prefix;
723        }
724
725        public boolean accept(File dir, String name) {
726            return name.toLowerCase().startsWith(prefix);
727        }
728    }
729
730}