/* * JarSearch.java * * Copyright (C) 2018 JDotSoft. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jdotsoft.jarsearch; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.LineNumberReader; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; /** * Simple GUI application to search classes in JARs. */ public class JarSearch extends JFrame { private static final String CLASS_EXT = ".class"; private final static String KEY_DIR = "dir"; private final static String KEY_FILE = "file"; private final static String KEY_CLASS = "class"; private final static String KEY_RESOURCE = "resource"; private final static String KEY_RADIO = "radio"; private final static String KEY_WIDTH = "width"; private final static String KEY_HEIGHT = "height"; private final static String KEY_VIEW = "view"; private final static String VAL_RADIO_FILE = "file"; private final static String VAL_RADIO_CLASS = "class"; private final static String VAL_RADIO_RESOURCE = "resource"; private final static Font FONT_CONSOLE = new Font(Font.MONOSPACED, Font.PLAIN, 12); private final static int APP_WIDTH = 800; private final static int APP_HEIGHT = 700; private final static int MAX_DIR_LRU = 20; private Properties propDyn; private JComboBox cbSearchDir; private DefaultComboBoxModel modelCbSearchDir; private String searchDir; private JTextField tfFile; private JTextField tfClass; private JTextField tfResource; private JRadioButton radioFile; private JRadioButton radioClass; private JRadioButton radioResource; private JTextArea taConsole; private JComboBox cbView; private JButton btnBrowseFile; private JButton btnSearch; private List lstProgress; private Map> mapJar2Class; private Map> mapClass2Jar; private enum ViewType { PROGRESS("Progress..."), ALL_JARS("All JARs"), REQUIRED_JARS("Required JARs"), ALL_CLASSES("All classes"), JAR_TO_CLASS("JAR to class"), CLASS_TO_JAR("Class to JAR"), CLASS_NOT_FOUND("Not found classes"), CLASS_DUPLICATE("Duplicate classes"), ; String title; ViewType(String title) { this.title = title; } @Override public String toString() { return title; } } public JarSearch() { super("JARs search \u00A9 http://www.jdotsoft.com"); printBuildTimestamp(); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { // Ignore: ClassNotFoundException, InstantiationException // IllegalAccessException, UnsupportedLookAndFeelException } loadProps(); int nWidth, nHeight; try { nWidth = Integer.parseInt(propDyn.getProperty(KEY_WIDTH)); nHeight = Integer.parseInt(propDyn.getProperty(KEY_HEIGHT)); } catch (NumberFormatException e) { nWidth = APP_WIDTH; nHeight = APP_HEIGHT; } setSize(nWidth, nHeight); Dimension dmScreen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension dmWindow = getSize(); int nW = Math.min(dmWindow.width, dmScreen.width); int nH = Math.min(dmWindow.height, dmScreen.height - 20); // 20 for taskbar int nX = (dmScreen.width - nW) / 2; int nY = (dmScreen.height - nH) / 2; setBounds(nX, nY, nW, nH); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { doClose(); } }); searchDir = ""; } private void printBuildTimestamp() { try { Enumeration resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"); while (resources.hasMoreElements()) { Manifest manifest = new Manifest(resources.nextElement().openStream()); Attributes attr = manifest.getMainAttributes(); String s = attr.getValue(Attributes.Name.MAIN_CLASS); if (JarSearch.class.getName().equals(s)) { System.out.println("Build: " + attr.getValue("Build-Date")); } } } catch (IOException e1) { } } static int ______GUI; // Eclipse section header private void initGUI() { JPanel panelMain = new JPanel(); panelMain.setLayout(new BoxLayout(panelMain, BoxLayout.Y_AXIS)); panelMain.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); // T,L,B,R // Search dir addLabel(panelMain, "Directory to search"); JPanel panelBrowseDir = new JPanel(); panelBrowseDir.setLayout(new BoxLayout(panelBrowseDir, BoxLayout.X_AXIS)); panelMain.add(panelBrowseDir); modelCbSearchDir = new DefaultComboBoxModel<>(); cbSearchDir = new JComboBox<>(modelCbSearchDir); cbSearchDir.setEditable(true); cbSearchDir.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { searchDir = (String)cbSearchDir.getSelectedItem(); if (searchDir == null) { searchDir = ""; // make it safe } searchDir = searchDir.trim(); if (searchDir.isEmpty()) { return; } String sel = modelCbSearchDir.getElementAt(0); if (searchDir.equals(sel)) { return; // no action required } modelCbSearchDir.removeElement(searchDir); modelCbSearchDir.insertElementAt(searchDir, 0); cbSearchDir.setSelectedIndex(0); } }); for (int i = 0; i < MAX_DIR_LRU; i++) { String dir = propDyn.getProperty(KEY_DIR + i); if (dir == null) { break; // stop processing - no more entries } if (dir.trim().isEmpty() || !new File(dir).exists()) { continue; // skip } modelCbSearchDir.addElement(dir); // first added item sets selected index 0 -> ActionEvent } //if (modelCbSearchDir.getSize() == 0) { // modelCbSearchDir.addElement(System.getProperty("user.home")); //} panelBrowseDir.add(cbSearchDir); panelBrowseDir.add(Box.createHorizontalStrut(10)); JButton btnBrowseDir = new JButton("\u2026"); // ellipsis panelBrowseDir.add(btnBrowseDir); btnBrowseDir.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseDir(); } }); // File with classes addLabel(panelMain, "File with classes list"); JPanel panelBrowseFile = new JPanel(); panelBrowseFile.setLayout(new BoxLayout(panelBrowseFile, BoxLayout.X_AXIS)); panelMain.add(panelBrowseFile); radioFile = new JRadioButton(); radioFile.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateEnable(); } }); panelBrowseFile.add(radioFile); panelBrowseFile.add(Box.createHorizontalStrut(10)); tfFile = new JTextField(propDyn.getProperty(KEY_FILE, "")); panelBrowseFile.add(tfFile); panelBrowseFile.add(Box.createHorizontalStrut(10)); btnBrowseFile = new JButton("\u2026"); // ellipsis panelBrowseFile.add(btnBrowseFile); btnBrowseFile.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { browseFile(); } }); // Class name addLabel(panelMain, "Class name"); JPanel panelClass = new JPanel(); panelClass.setLayout(new BoxLayout(panelClass, BoxLayout.X_AXIS)); radioClass = new JRadioButton(); radioClass.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateEnable(); } }); panelClass.add(radioClass); panelClass.add(Box.createHorizontalStrut(10)); tfClass = new JTextField(propDyn.getProperty(KEY_CLASS, "")); panelClass.add(tfClass); panelMain.add(panelClass); // Resource name addLabel(panelMain, "Resource name (substring)"); JPanel panelResource = new JPanel(); panelResource.setLayout(new BoxLayout(panelResource, BoxLayout.X_AXIS)); radioResource = new JRadioButton(); radioResource.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { updateEnable(); } }); panelResource.add(radioResource); panelResource.add(Box.createHorizontalStrut(10)); tfResource = new JTextField(propDyn.getProperty(KEY_RESOURCE, "")); panelResource.add(tfResource); panelMain.add(panelResource); // Radio ButtonGroup group = new ButtonGroup(); group.add(radioFile); group.add(radioClass); group.add(radioResource); radioFile.setSelected(VAL_RADIO_FILE.equals(propDyn.getProperty(KEY_RADIO))); radioClass.setSelected(VAL_RADIO_CLASS.equals(propDyn.getProperty(KEY_RADIO))); radioResource.setSelected(VAL_RADIO_RESOURCE.equals(propDyn.getProperty(KEY_RADIO))); if (group.getSelection() == null) { radioClass.setSelected(true); } // Console addLabel(panelMain, "Search result"); taConsole = new JTextArea(); taConsole.setEditable(false); taConsole.setBackground(new Color(255, 255, 230)); taConsole.setFont(FONT_CONSOLE); JScrollPane sp = new JScrollPane(taConsole, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); sp.setPreferredSize(new Dimension(1000, 1000)); panelMain.add(sp); // Buttons JPanel panelBtn = new JPanel(); JPanel panelBtnAct = new JPanel(); JPanel panelBtnView = new JPanel(); panelBtn.setLayout(new BorderLayout()); panelBtn.add(panelBtnAct, BorderLayout.CENTER); panelBtn.add(panelBtnView, BorderLayout.SOUTH); // Buttons ACT btnSearch = new JButton("Search"); btnSearch.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doSearch(); } }); panelBtnAct.add(btnSearch); JButton btnClear = new JButton("Clear"); btnClear.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doClear(); } }); panelBtnAct.add(btnClear); JButton btnClose = new JButton("Close"); btnClose.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doClose(); } }); panelBtnAct.add(btnClose); // Buttons VIEW cbView = new JComboBox<>(); for (ViewType vt : ViewType.values()) { cbView.addItem(vt); } panelBtnView.add(cbView); int nView = ViewType.CLASS_TO_JAR.ordinal(); try { nView = Integer.parseInt(propDyn.getProperty(KEY_VIEW)); } catch (NumberFormatException e) { } cbView.setSelectedIndex(nView); cbView.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doView(); } }); // Finally: set content Container cont = this.getContentPane(); cont.setLayout(new BorderLayout()); cont.add(panelMain, BorderLayout.CENTER); cont.add(panelBtn, BorderLayout.SOUTH); updateEnable(); } private String getPropFilename() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName() + "_dynamic.properties"); return sb.toString(); } private void loadProps() { propDyn = new Properties(); try { InputStream is = new FileInputStream(getPropFilename()); propDyn.load(is); is.close(); } catch (IOException e) { // Error is OK. File may not exist or disk read-only. } } private static void addLabel(JPanel panel, String sLabel) { panel.add(Box.createVerticalStrut(6)); JPanel p = new JPanel(new BorderLayout()); JLabel label = new JLabel(sLabel); p.add(label, BorderLayout.WEST); panel.add(p); } static int ______BUTTONS; // Eclipse section header private void browseDir() { JFileChooser fc = new JFileChooser(); fc.setDialogTitle("Directory to search"); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); File fileDir = new File(searchDir); fc.setCurrentDirectory(fileDir); int nRet = fc.showOpenDialog(null); if (nRet != JFileChooser.APPROVE_OPTION) { return; } File file = fc.getSelectedFile(); if (file != null) { String dir = file.getAbsolutePath(); modelCbSearchDir.removeElement(dir); // no duplicates cbSearchDir.insertItemAt(dir, 0); cbSearchDir.setSelectedIndex(0); while (modelCbSearchDir.getSize() > MAX_DIR_LRU) { modelCbSearchDir.removeElementAt(modelCbSearchDir.getSize() - 1); } } } private void browseFile() { JFileChooser fc = new JFileChooser(); fc.setDialogTitle("File with classes list"); fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); File fileDir = new File(tfFile.getText()); fc.setCurrentDirectory(fileDir.getParentFile()); fc.setSelectedFile(fileDir); int nRet = fc.showOpenDialog(null); if (nRet != JFileChooser.APPROVE_OPTION) { return; } File file = fc.getSelectedFile(); if (file != null) { tfFile.setText(file.getAbsolutePath()); } } private void doSearch() { clearConsole(); taConsole.setText("Progress:"); // Execute in a separate thread to see progress in a console: Thread t = new Thread("CountThread") { @Override public void run() { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); executeSearch(new File(tfFile.getText()), 0); setCursor(Cursor.getDefaultCursor()); } }; t.start(); } private void doClear() { clearConsole(); lstProgress = null; mapJar2Class = null; mapClass2Jar = null; updateEnable(); } private void doClose() { // Save dynamic props for (int i = 0; i < modelCbSearchDir.getSize(); i++) { propDyn.setProperty(KEY_DIR + i, cbSearchDir.getItemAt(i)); } propDyn.setProperty(KEY_FILE, tfFile.getText()); propDyn.setProperty(KEY_CLASS, tfClass.getText()); propDyn.setProperty(KEY_RESOURCE, tfResource.getText()); if (radioFile.isSelected()) { propDyn.setProperty(KEY_RADIO, VAL_RADIO_FILE); } if (radioClass.isSelected()) { propDyn.setProperty(KEY_RADIO, VAL_RADIO_CLASS); } if (radioResource.isSelected()) { propDyn.setProperty(KEY_RADIO, VAL_RADIO_RESOURCE); } propDyn.setProperty(KEY_WIDTH, Integer.toString(getWidth())); propDyn.setProperty(KEY_HEIGHT, Integer.toString(getHeight())); propDyn.setProperty(KEY_VIEW, Integer.toString(cbView.getSelectedIndex())); try { OutputStream os = new FileOutputStream(getPropFilename()); propDyn.store(os, "Dynamic properties"); os.close(); } catch (IOException e) { // Ignore. Disk or folder may have ReadOnly access. } System.exit(0); } private void doView() { clearConsole(); if (lstProgress == null) { return; } switch ((ViewType)cbView.getSelectedItem()) { case PROGRESS: viewProgress(); break; case ALL_JARS: viewAllJars(); break; case REQUIRED_JARS: viewRequiredJars(); break; case ALL_CLASSES: viewAllClasses(); break; case JAR_TO_CLASS: viewJar2Class(); break; case CLASS_TO_JAR: viewClass2Jar(); break; case CLASS_NOT_FOUND: viewClassNotFound(); break; case CLASS_DUPLICATE: viewClassDuplicate(); break; } } static int ______SEARCH; // Eclipse section header private void executeSearch(File fDir, int nLevel) { doClear(); lstProgress = new ArrayList<>(); mapJar2Class = new TreeMap<>(); mapClass2Jar = new TreeMap<>(); if (searchDir.isEmpty()) { addProgressLine("Please enter directory to search."); return; } File dirSearch = new File(searchDir); if (!dirSearch.exists()) { addProgressLine("Directory '" + searchDir + "' does not exist."); return; } try { loadJars(dirSearch); addProgressLine("Loaded JARs: " + mapJar2Class.size()); if (!loadEntriesToSearch()) { return; } } catch (IOException e) { addProgressLine(e.toString()); return; } addProgressLine("Loaded classes to search: " + mapClass2Jar.size()); for (Map.Entry> entry : mapJar2Class.entrySet()) { File file = entry.getKey(); try { searchJar(file); } catch (IOException e) { addProgressLine("ERROR: " + e.toString() + " -> " + file.getAbsolutePath()); } } addProgressLine("Done."); updateEnable(); doView(); } private void searchJar(File file) throws IOException { addProgressLine("Processing " + file.getAbsolutePath()); JarFile jar = new JarFile(file); for (Map.Entry> entry : mapClass2Jar.entrySet()) { String clazz = entry.getKey(); if (radioResource.isSelected()) { clazz = clazz.replace('\\', '/'); // normalize resource name } Enumeration en = jar.entries(); while (en.hasMoreElements()) { JarEntry elem = en.nextElement(); if (elem.isDirectory()) { continue; } // entry.getName() like "com/bea/adaptive/harvester/utils/HarvesterUtilsLogLocalizer.properties" String entryName = elem.getName(); if (radioResource.isSelected()) { // Work with resources if (entryName.indexOf(clazz) >= 0) { entry.getValue().add(file); // register JAR in mapClass2Jar Set s = mapJar2Class.get(file); if (s != null) { s.add(entryName); // register CLASS in mapJar2Class } } } else { // Work with classes if (entryName.endsWith(CLASS_EXT)) { entryName = entryName.substring(0, entryName.length() - CLASS_EXT.length()); entryName = entryName.replace('/', '.'); } //addProgressLine("Compare " + clazz + " and " + entryName); if (clazz.equals(entryName)) { entry.getValue().add(file); // register JAR in mapClass2Jar Set s = mapJar2Class.get(file); if (s != null) { s.add(clazz); // register CLASS in mapJar2Class } } } } } jar.close(); } private void addProgressLine(String str) { lstProgress.add(str); addConsoleLine(str); } static int ______VIEWS; // Eclipse section header private void viewProgress() { for (String s : lstProgress) { addConsoleLine(s); } } private void viewAllJars() { taConsole.setText("=== JARs in " + searchDir + " ==="); for (Map.Entry> entry : mapJar2Class.entrySet()) { addConsoleLine(entry.getKey().getAbsolutePath()); } addConsoleLine("TOTAL: " + mapJar2Class.size()); } private void viewRequiredJars() { taConsole.setText("=== REQUIRED JARs in " + searchDir + " ==="); int count = 0; for (Map.Entry> entry : mapJar2Class.entrySet()) { if (entry.getValue().size() > 0) { addConsoleLine(entry.getKey().getAbsolutePath()); count++; } } addConsoleLine("TOTAL: " + count); } private void viewAllClasses() { taConsole.setText("=== All classes ==="); for (Map.Entry> entry : mapClass2Jar.entrySet()) { addConsoleLine(entry.getKey()); } addConsoleLine("TOTAL: " + mapClass2Jar.size()); } private void viewJar2Class() { taConsole.setText("=== JAR-to-classes (only required JARs) ==="); int count = 0; for (Map.Entry> entry : mapJar2Class.entrySet()) { Set lst = entry.getValue(); if (lst.size() == 0) continue; count++; File f = entry.getKey(); addConsoleLine(f.getAbsolutePath() + " used for classes:"); for (String s : lst) { addConsoleLine(" " + s); } } addConsoleLine("TOTAL JARs: " + count); } private void viewClass2Jar() { taConsole.setText("=== Class-to-JAR ==="); int count0 = 0; int count1 = 0; int countN = 0; for (Map.Entry> entry : mapClass2Jar.entrySet()) { String clazz = entry.getKey(); Set lst = entry.getValue(); switch (lst.size()) { case 0: count0++; addConsoleLine(clazz + " not found"); break; case 1: count1++; Iterator it = lst.iterator(); addConsoleLine(clazz + " loaded from " + it.next()); break; default: countN++; addConsoleLine(clazz + " in following JARs:"); for (File f : lst) { addConsoleLine(" " + f.getAbsolutePath()); } break; } } addConsoleLine(String.format( "TOTAL: SingleImport=%d NotFound=%d Duplicates=%d", count1, count0, countN)); } private void viewClassNotFound() { taConsole.setText("=== Not found classes ==="); int count = 0; for (Map.Entry> entry : mapClass2Jar.entrySet()) { String clazz = entry.getKey(); Set lst = entry.getValue(); if (lst.size() == 0) { addConsoleLine(clazz); count++; } } addConsoleLine("TOTAL: " + count); } private void viewClassDuplicate() { taConsole.setText("=== Duplicate classes ==="); int count = 0; for (Map.Entry> entry : mapClass2Jar.entrySet()) { String clazz = entry.getKey(); Set lst = entry.getValue(); if (lst.size() > 1) { addConsoleLine(clazz + " in following JARs:"); for (File f : lst) { addConsoleLine(" " + f.getAbsolutePath()); } count++; } } addConsoleLine("TOTAL: " + count); } static int ______HELPERS; // Eclipse section header private void loadJars(File fDir) throws IOException { File[] a_f = fDir.listFiles(new ThisJarFilter()); if (!fDir.exists() || a_f == null) { JOptionPane.showMessageDialog( this, String.format("Failed to scan '%s'\nDoes not exist or IOException", fDir.getAbsoluteFile()), "Error", JOptionPane.ERROR_MESSAGE); return; } for (File f : a_f) { if (f.isDirectory()) { loadJars(f); } else { mapJar2Class.put(f, new TreeSet()); } } } private boolean loadEntriesToSearch() throws IOException { if (radioFile.isSelected()) { File f = new File(tfFile.getText()); try { LineNumberReader reader = new LineNumberReader(new FileReader(f)); while (true) { String s = reader.readLine(); if (s == null) { break; } s = s.trim(); if (s.isEmpty() || s.startsWith("#")) { continue; } addEntryToSearch(s); } reader.close(); } catch (IOException e) { addProgressLine("File " + f.getAbsolutePath()); throw e; } } if (radioClass.isSelected()) { String sClass = tfClass.getText(); if (sClass.trim().length() == 0) { addProgressLine("Failed to load class to search."); return false; } addEntryToSearch(sClass); } if (radioResource.isSelected()) { String sResource = tfResource.getText(); if (sResource.trim().length() == 0) { addProgressLine("Failed to load resource to search."); return false; } addEntryToSearch(sResource); } return true; } private void addEntryToSearch(String clazz) { mapClass2Jar.put(clazz, new TreeSet()); } private void updateEnable() { boolean enableFile = radioFile.isSelected(); boolean enableClass = radioClass.isSelected(); boolean enableResource = radioResource.isSelected(); btnBrowseFile.setEnabled(enableFile); tfFile.setEnabled(enableFile); tfClass.setEnabled(enableClass); tfResource.setEnabled(enableResource); } private void clearConsole() { taConsole.setText(""); } private void addConsoleLine(String s) { taConsole.append("\n"); taConsole.append(s); } public static void main(String[] args) { JarSearch app = new JarSearch(); app.initGUI(); app.toFront(); app.setVisible(true); } private class ThisJarFilter implements FileFilter { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String filename = f.getName().toLowerCase(); return filename.endsWith(".jar") || filename.endsWith(".zip"); } } } // class JarSearch