package us.deathmarine.luyten; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Theme; import org.fife.ui.rtextarea.RTextScrollPane; import com.strobel.assembler.InputTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; import com.strobel.assembler.metadata.JarTypeLoader; import com.strobel.assembler.metadata.MetadataSystem; import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.assembler.metadata.TypeReference; import com.strobel.core.StringUtilities; import com.strobel.core.VerifyArgument; import com.strobel.decompiler.DecompilationOptions; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; /** * Jar-level model */ public class Model extends JSplitPane { private static final long serialVersionUID = 6896857630400910200L; private static final long MAX_JAR_FILE_SIZE_BYTES = 10_000_000_000L; private static final long MAX_UNPACKED_FILE_SIZE_BYTES = 10_000_000L; private static LuytenTypeLoader typeLoader = new LuytenTypeLoader(); public static MetadataSystem metadataSystem = new MetadataSystem(typeLoader); private JTree tree; public JTabbedPane house; private File file; private DecompilerSettings settings; private DecompilationOptions decompilationOptions; private Theme theme; private MainWindow mainWindow; private JProgressBar bar; private JLabel label; private HashSet<OpenFile> hmap = new HashSet<OpenFile>(); private Set<String> treeExpansionState; private boolean open = false; private State state; private ConfigSaver configSaver; private LuytenPreferences luytenPrefs; public Model(MainWindow mainWindow) { this.mainWindow = mainWindow; this.bar = mainWindow.getBar(); this.setLabel(mainWindow.getLabel()); configSaver = ConfigSaver.getLoadedInstance(); settings = configSaver.getDecompilerSettings(); luytenPrefs = configSaver.getLuytenPreferences(); try { String themeXml = luytenPrefs.getThemeXml(); setTheme(Theme.load(getClass().getResourceAsStream(LuytenPreferences.THEME_XML_PATH + themeXml))); } catch (Exception e1) { try { Luyten.showExceptionDialog("Exception!", e1); String themeXml = LuytenPreferences.DEFAULT_THEME_XML; luytenPrefs.setThemeXml(themeXml); setTheme(Theme.load(getClass().getResourceAsStream(LuytenPreferences.THEME_XML_PATH + themeXml))); } catch (Exception e2) { Luyten.showExceptionDialog("Exception!", e2); } } tree = new JTree(); tree.setModel(new DefaultTreeModel(null)); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setCellRenderer(new CellRenderer()); TreeListener tl = new TreeListener(); tree.addMouseListener(tl); tree.addTreeExpansionListener(new FurtherExpandingTreeExpansionListener()); tree.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { openEntryByTreePath(tree.getSelectionPath()); } } }); JPanel panel2 = new JPanel(); panel2.setLayout(new BoxLayout(panel2, 1)); panel2.setBorder(BorderFactory.createTitledBorder("Structure")); panel2.add(new JScrollPane(tree)); house = new JTabbedPane(); house.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); house.addChangeListener(new TabChangeListener()); house.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (SwingUtilities.isMiddleMouseButton(e)) { closeOpenTab(house.getSelectedIndex()); } } }); KeyStroke sfuncF4 = KeyStroke.getKeyStroke(KeyEvent.VK_F4, Keymap.ctrlDownModifier(), false); mainWindow.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sfuncF4, "CloseTab"); mainWindow.getRootPane().getActionMap().put("CloseTab", new AbstractAction() { private static final long serialVersionUID = -885398399200419492L; @Override public void actionPerformed(ActionEvent e) { closeOpenTab(house.getSelectedIndex()); } }); JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, 1)); panel.setBorder(BorderFactory.createTitledBorder("Code")); panel.add(house); this.setOrientation(JSplitPane.HORIZONTAL_SPLIT); this.setDividerLocation(250 % mainWindow.getWidth()); this.setLeftComponent(panel2); this.setRightComponent(panel); decompilationOptions = new DecompilationOptions(); decompilationOptions.setSettings(settings); decompilationOptions.setFullDecompilation(true); } public void showLegal(String legalStr) { show("Legal", legalStr); } public void show(String name, String contents) { OpenFile open = new OpenFile(name, "*/" + name, getTheme(), mainWindow); open.setContent(contents); hmap.add(open); addOrSwitchToTab(open); } private void addOrSwitchToTab(final OpenFile open) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { final String title = open.name; RTextScrollPane rTextScrollPane = open.scrollPane; int index = house.indexOfTab(title); if (index > -1 && house.getTabComponentAt(index) != open.scrollPane) { index = -1; for (int i = 0; i < house.getTabCount(); i++) { if (house.getComponentAt(i) == open.scrollPane) { index = i; break; } } } if (index < 0) { house.addTab(title, rTextScrollPane); index = house.indexOfComponent(rTextScrollPane); house.setSelectedIndex(index); Tab ct = new Tab(title); ct.getButton().addMouseListener(new CloseTab(title)); house.setTabComponentAt(index, ct); } else { house.setSelectedIndex(index); } open.onAddedToScreen(); } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } }); } public void closeOpenTab(int index) { RTextScrollPane co = (RTextScrollPane) house.getComponentAt(index); RSyntaxTextArea pane = (RSyntaxTextArea) co.getViewport().getView(); OpenFile open = null; for (OpenFile file : hmap) if (pane.equals(file.textArea)) open = file; if (open != null && hmap.contains(open)) hmap.remove(open); house.remove(co); if (open != null) open.close(); } private String getName(String path) { if (path == null) return ""; int i = path.lastIndexOf("/"); if (i == -1) i = path.lastIndexOf("\\"); if (i != -1) return path.substring(i + 1); return path; } private class TreeListener extends MouseAdapter { @Override public void mousePressed(MouseEvent event) { boolean isClickCountMatches = (event.getClickCount() == 1 && luytenPrefs.isSingleClickOpenEnabled()) || (event.getClickCount() == 2 && !luytenPrefs.isSingleClickOpenEnabled()); if (!isClickCountMatches) return; if (!SwingUtilities.isLeftMouseButton(event)) return; final TreePath trp = tree.getPathForLocation(event.getX(), event.getY()); if (trp == null) return; Object lastPathComponent = trp.getLastPathComponent(); boolean isLeaf = (lastPathComponent instanceof TreeNode && ((TreeNode) lastPathComponent).isLeaf()); if (!isLeaf) return; new Thread() { public void run() { openEntryByTreePath(trp); } }.start(); } } private class FurtherExpandingTreeExpansionListener implements TreeExpansionListener { @Override public void treeExpanded(final TreeExpansionEvent event) { final TreePath treePath = event.getPath(); final Object expandedTreePathObject = treePath.getLastPathComponent(); if (!(expandedTreePathObject instanceof TreeNode)) { return; } final TreeNode expandedTreeNode = (TreeNode) expandedTreePathObject; if (expandedTreeNode.getChildCount() == 1) { final TreeNode descendantTreeNode = expandedTreeNode.getChildAt(0); if (descendantTreeNode.isLeaf()) { return; } final TreePath nextTreePath = treePath.pathByAddingChild(descendantTreeNode); tree.expandPath(nextTreePath); } } @Override public void treeCollapsed(final TreeExpansionEvent event) { } } public void openEntryByTreePath(TreePath trp) { String name = ""; String path = ""; try { bar.setVisible(true); if (trp.getPathCount() > 1) { for (int i = 1; i < trp.getPathCount(); i++) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) trp.getPathComponent(i); TreeNodeUserObject userObject = (TreeNodeUserObject) node.getUserObject(); if (i == trp.getPathCount() - 1) { name = userObject.getOriginalName(); } else { path = path + userObject.getOriginalName() + "/"; } } path = path + name; if (file.getName().endsWith(".jar") || file.getName().endsWith(".zip")) { if (state == null) { JarFile jfile = new JarFile(file); ITypeLoader jarLoader = new JarTypeLoader(jfile); typeLoader.getTypeLoaders().add(jarLoader); state = new State(file.getCanonicalPath(), file, jfile, jarLoader); } JarEntry entry = state.jarFile.getJarEntry(path); if (entry == null) { throw new FileEntryNotFoundException(); } if (entry.getSize() > MAX_UNPACKED_FILE_SIZE_BYTES) { throw new TooLargeFileException(entry.getSize()); } String entryName = entry.getName(); if (entryName.endsWith(".class")) { getLabel().setText("Extracting: " + name); String internalName = StringUtilities.removeRight(entryName, ".class"); TypeReference type = metadataSystem.lookupType(internalName); extractClassToTextPane(type, name, path, null); } else { getLabel().setText("Opening: " + name); try (InputStream in = state.jarFile.getInputStream(entry);) { extractSimpleFileEntryToTextPane(in, name, path); } } } } else { name = file.getName(); path = file.getPath().replaceAll("\\\\", "/"); if (file.length() > MAX_UNPACKED_FILE_SIZE_BYTES) { throw new TooLargeFileException(file.length()); } if (name.endsWith(".class")) { getLabel().setText("Extracting: " + name); TypeReference type = metadataSystem.lookupType(path); extractClassToTextPane(type, name, path, null); } else { getLabel().setText("Opening: " + name); try (InputStream in = new FileInputStream(file);) { extractSimpleFileEntryToTextPane(in, name, path); } } } getLabel().setText("Complete"); } catch (FileEntryNotFoundException e) { getLabel().setText("File not found: " + name); } catch (FileIsBinaryException e) { getLabel().setText("Binary resource: " + name); } catch (TooLargeFileException e) { getLabel().setText("File is too large: " + name + " - size: " + e.getReadableFileSize()); } catch (Exception e) { getLabel().setText("Cannot open: " + name); Luyten.showExceptionDialog("Unable to open file!", e); } finally { bar.setVisible(false); } } void extractClassToTextPane(TypeReference type, String tabTitle, String path, String navigatonLink) throws Exception { if (tabTitle == null || tabTitle.trim().length() < 1 || path == null) { throw new FileEntryNotFoundException(); } OpenFile sameTitledOpen = null; for (OpenFile nextOpen : hmap) { if (tabTitle.equals(nextOpen.name) && path.equals(nextOpen.path) && type.equals(nextOpen.getType())) { sameTitledOpen = nextOpen; break; } } if (sameTitledOpen != null && sameTitledOpen.isContentValid()) { sameTitledOpen.setInitialNavigationLink(navigatonLink); addOrSwitchToTab(sameTitledOpen); return; } // resolve TypeDefinition TypeDefinition resolvedType = null; if (type == null || ((resolvedType = type.resolve()) == null)) { throw new Exception("Unable to resolve type."); } // open tab, store type information, start decompilation if (sameTitledOpen != null) { sameTitledOpen.path = path; sameTitledOpen.invalidateContent(); sameTitledOpen.setDecompilerReferences(metadataSystem, settings, decompilationOptions); sameTitledOpen.setType(resolvedType); sameTitledOpen.setInitialNavigationLink(navigatonLink); sameTitledOpen.resetScrollPosition(); sameTitledOpen.decompile(); addOrSwitchToTab(sameTitledOpen); } else { OpenFile open = new OpenFile(tabTitle, path, getTheme(), mainWindow); open.setDecompilerReferences(metadataSystem, settings, decompilationOptions); open.setType(resolvedType); open.setInitialNavigationLink(navigatonLink); open.decompile(); hmap.add(open); addOrSwitchToTab(open); } } public void extractSimpleFileEntryToTextPane(InputStream inputStream, String tabTitle, String path) throws Exception { if (inputStream == null || tabTitle == null || tabTitle.trim().length() < 1 || path == null) { throw new FileEntryNotFoundException(); } OpenFile sameTitledOpen = null; for (OpenFile nextOpen : hmap) { if (tabTitle.equals(nextOpen.name) && path.equals(nextOpen.path)) { sameTitledOpen = nextOpen; break; } } if (sameTitledOpen != null) { addOrSwitchToTab(sameTitledOpen); return; } // build tab content StringBuilder sb = new StringBuilder(); long nonprintableCharactersCount = 0; try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader reader = new BufferedReader(inputStreamReader);) { String line; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); for (byte nextByte : line.getBytes()) { if (nextByte <= 0) { nonprintableCharactersCount++; } } } } // guess binary or text String extension = "." + tabTitle.replaceAll("^[^\\.]*$", "").replaceAll("[^\\.]*\\.", ""); boolean isTextFile = (OpenFile.WELL_KNOWN_TEXT_FILE_EXTENSIONS.contains(extension) || nonprintableCharactersCount < sb.length() / 5); if (!isTextFile) { throw new FileIsBinaryException(); } // open tab if (sameTitledOpen != null) { sameTitledOpen.path = path; sameTitledOpen.setDecompilerReferences(metadataSystem, settings, decompilationOptions); sameTitledOpen.resetScrollPosition(); sameTitledOpen.setContent(sb.toString()); addOrSwitchToTab(sameTitledOpen); } else { OpenFile open = new OpenFile(tabTitle, path, getTheme(), mainWindow); open.setDecompilerReferences(metadataSystem, settings, decompilationOptions); open.setContent(sb.toString()); hmap.add(open); addOrSwitchToTab(open); } } private class TabChangeListener implements ChangeListener { @Override public void stateChanged(ChangeEvent e) { int selectedIndex = house.getSelectedIndex(); if (selectedIndex < 0) { return; } for (OpenFile open : hmap) { if (house.indexOfTab(open.name) == selectedIndex) { if (open.getType() != null && !open.isContentValid()) { updateOpenClass(open); break; } } } } } public void updateOpenClasses() { // invalidate all open classes (update will hapen at tab change) for (OpenFile open : hmap) { if (open.getType() != null) { open.invalidateContent(); } } // update the current open tab - if it is a class for (OpenFile open : hmap) { if (open.getType() != null && isTabInForeground(open)) { updateOpenClass(open); break; } } } private void updateOpenClass(final OpenFile open) { if (open.getType() == null) { return; } new Thread(new Runnable() { @Override public void run() { try { bar.setVisible(true); getLabel().setText("Extracting: " + open.name); open.invalidateContent(); open.decompile(); getLabel().setText("Complete"); } catch (Exception e) { getLabel().setText("Error, cannot update: " + open.name); } finally { bar.setVisible(false); } } }).start(); } private boolean isTabInForeground(OpenFile open) { String title = open.name; int selectedIndex = house.getSelectedIndex(); return (selectedIndex >= 0 && selectedIndex == house.indexOfTab(title)); } final class State implements AutoCloseable { private final String key; private final File file; final JarFile jarFile; final ITypeLoader typeLoader; private State(String key, File file, JarFile jarFile, ITypeLoader typeLoader) { this.key = VerifyArgument.notNull(key, "key"); this.file = VerifyArgument.notNull(file, "file"); this.jarFile = jarFile; this.typeLoader = typeLoader; } @Override public void close() { if (typeLoader != null) { Model.typeLoader.getTypeLoaders().remove(typeLoader); } Closer.tryClose(jarFile); } public File getFile() { return file; } public String getKey() { return key; } } private class Tab extends JPanel { private static final long serialVersionUID = -514663009333644974L; private JLabel closeButton = new JLabel(new ImageIcon( Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/resources/icon_close.png")))); private JLabel tabTitle = new JLabel(); private String title = ""; public Tab(String t) { super(new GridBagLayout()); this.setOpaque(false); this.title = t; this.tabTitle = new JLabel(title); this.createTab(); } public JLabel getButton() { return this.closeButton; } public void createTab() { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; this.add(tabTitle, gbc); gbc.gridx++; gbc.insets = new Insets(0, 5, 0, 0); gbc.anchor = GridBagConstraints.EAST; this.add(closeButton, gbc); } } private class CloseTab extends MouseAdapter { String title; public CloseTab(String title) { this.title = title; } @Override public void mouseClicked(MouseEvent e) { int index = house.indexOfTab(title); closeOpenTab(index); } } public DefaultMutableTreeNode loadNodesByNames(DefaultMutableTreeNode node, List<String> originalNames) { List<TreeNodeUserObject> args = new ArrayList<>(); for (String originalName : originalNames) { args.add(new TreeNodeUserObject(originalName)); } return loadNodesByUserObj(node, args); } public DefaultMutableTreeNode loadNodesByUserObj(DefaultMutableTreeNode node, List<TreeNodeUserObject> args) { if (args.size() > 0) { TreeNodeUserObject name = args.remove(0); DefaultMutableTreeNode nod = getChild(node, name); if (nod == null) nod = new DefaultMutableTreeNode(name); node.add(loadNodesByUserObj(nod, args)); } return node; } @SuppressWarnings("unchecked") public DefaultMutableTreeNode getChild(DefaultMutableTreeNode node, TreeNodeUserObject name) { Enumeration<DefaultMutableTreeNode> entry = node.children(); while (entry.hasMoreElements()) { DefaultMutableTreeNode nods = entry.nextElement(); if (((TreeNodeUserObject) nods.getUserObject()).getOriginalName().equals(name.getOriginalName())) { return nods; } } return null; } public void loadFile(File file) { if (open) closeFile(); this.file = file; RecentFiles.add(file.getAbsolutePath()); mainWindow.mainMenuBar.updateRecentFiles(); loadTree(); } public void updateTree() { TreeUtil treeUtil = new TreeUtil(tree); treeExpansionState = treeUtil.getExpansionState(); loadTree(); } public void loadTree() { new Thread(new Runnable() { @Override public void run() { try { if (file == null) { return; } tree.setModel(new DefaultTreeModel(null)); if (file.length() > MAX_JAR_FILE_SIZE_BYTES) { throw new TooLargeFileException(file.length()); } if (file.getName().endsWith(".zip") || file.getName().endsWith(".jar")) { JarFile jfile; jfile = new JarFile(file); getLabel().setText("Loading: " + jfile.getName()); bar.setVisible(true); JarEntryFilter jarEntryFilter = new JarEntryFilter(jfile); List<String> mass = null; if (luytenPrefs.isFilterOutInnerClassEntries()) { mass = jarEntryFilter.getEntriesWithoutInnerClasses(); } else { mass = jarEntryFilter.getAllEntriesFromJar(); } buildTreeFromMass(mass); if (state == null) { ITypeLoader jarLoader = new JarTypeLoader(jfile); typeLoader.getTypeLoaders().add(jarLoader); state = new State(file.getCanonicalPath(), file, jfile, jarLoader); } open = true; getLabel().setText("Complete"); } else { TreeNodeUserObject topNodeUserObject = new TreeNodeUserObject(getName(file.getName())); final DefaultMutableTreeNode top = new DefaultMutableTreeNode(topNodeUserObject); tree.setModel(new DefaultTreeModel(top)); settings.setTypeLoader(new InputTypeLoader()); open = true; getLabel().setText("Complete"); // open it automatically new Thread() { public void run() { TreePath trp = new TreePath(top.getPath()); openEntryByTreePath(trp); }; }.start(); } if (treeExpansionState != null) { try { TreeUtil treeUtil = new TreeUtil(tree); treeUtil.restoreExpanstionState(treeExpansionState); } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } } catch (TooLargeFileException e) { getLabel().setText("File is too large: " + file.getName() + " - size: " + e.getReadableFileSize()); closeFile(); } catch (Exception e1) { Luyten.showExceptionDialog("Cannot open " + file.getName() + "!", e1); getLabel().setText("Cannot open: " + file.getName()); closeFile(); } finally { mainWindow.onFileLoadEnded(file, open); bar.setVisible(false); } } }).start(); } private void buildTreeFromMass(List<String> mass) { if (luytenPrefs.isPackageExplorerStyle()) { buildFlatTreeFromMass(mass); } else { buildDirectoryTreeFromMass(mass); } } private void buildDirectoryTreeFromMass(List<String> mass) { TreeNodeUserObject topNodeUserObject = new TreeNodeUserObject(getName(file.getName())); DefaultMutableTreeNode top = new DefaultMutableTreeNode(topNodeUserObject); List<String> sort = new ArrayList<String>(); Collections.sort(mass, String.CASE_INSENSITIVE_ORDER); for (String m : mass) if (m.contains("META-INF") && !sort.contains(m)) sort.add(m); Set<String> set = new HashSet<String>(); for (String m : mass) { if (m.contains("/")) { set.add(m.substring(0, m.lastIndexOf("/") + 1)); } } List<String> packs = Arrays.asList(set.toArray(new String[] {})); Collections.sort(packs, String.CASE_INSENSITIVE_ORDER); Collections.sort(packs, new Comparator<String>() { public int compare(String o1, String o2) { return o2.split("/").length - o1.split("/").length; } }); for (String pack : packs) for (String m : mass) if (!m.contains("META-INF") && m.contains(pack) && !m.replace(pack, "").contains("/")) sort.add(m); for (String m : mass) if (!m.contains("META-INF") && !m.contains("/") && !sort.contains(m)) sort.add(m); for (String pack : sort) { LinkedList<String> list = new LinkedList<String>(Arrays.asList(pack.split("/"))); loadNodesByNames(top, list); } tree.setModel(new DefaultTreeModel(top)); } private void buildFlatTreeFromMass(List<String> mass) { TreeNodeUserObject topNodeUserObject = new TreeNodeUserObject(getName(file.getName())); DefaultMutableTreeNode top = new DefaultMutableTreeNode(topNodeUserObject); TreeMap<String, TreeSet<String>> packages = new TreeMap<>(); HashSet<String> classContainingPackageRoots = new HashSet<>(); Comparator<String> sortByFileExtensionsComparator = new Comparator<String>() { // (assertion: mass does not contain null elements) @Override public int compare(String o1, String o2) { int comp = o1.replaceAll("[^\\.]*\\.", "").compareTo(o2.replaceAll("[^\\.]*\\.", "")); if (comp != 0) return comp; return o1.compareTo(o2); } }; for (String entry : mass) { String packagePath = ""; String packageRoot = ""; if (entry.contains("/")) { packagePath = entry.replaceAll("/[^/]*$", ""); packageRoot = entry.replaceAll("/.*$", ""); } String packageEntry = entry.replace(packagePath + "/", ""); if (!packages.containsKey(packagePath)) { packages.put(packagePath, new TreeSet<String>(sortByFileExtensionsComparator)); } packages.get(packagePath).add(packageEntry); if (!entry.startsWith("META-INF") && packageRoot.trim().length() > 0 && entry.matches(".*\\.(class|java|prop|properties)$")) { classContainingPackageRoots.add(packageRoot); } } // META-INF comes first -> not flat for (String packagePath : packages.keySet()) { if (packagePath.startsWith("META-INF")) { List<String> packagePathElements = Arrays.asList(packagePath.split("/")); for (String entry : packages.get(packagePath)) { ArrayList<String> list = new ArrayList<>(packagePathElements); list.add(entry); loadNodesByNames(top, list); } } } // real packages: path starts with a classContainingPackageRoot -> flat for (String packagePath : packages.keySet()) { String packageRoot = packagePath.replaceAll("/.*$", ""); if (classContainingPackageRoots.contains(packageRoot)) { for (String entry : packages.get(packagePath)) { ArrayList<TreeNodeUserObject> list = new ArrayList<>(); list.add(new TreeNodeUserObject(packagePath, packagePath.replaceAll("/", "."))); list.add(new TreeNodeUserObject(entry)); loadNodesByUserObj(top, list); } } } // the rest, not real packages but directories -> not flat for (String packagePath : packages.keySet()) { String packageRoot = packagePath.replaceAll("/.*$", ""); if (!classContainingPackageRoots.contains(packageRoot) && !packagePath.startsWith("META-INF") && packagePath.length() > 0) { List<String> packagePathElements = Arrays.asList(packagePath.split("/")); for (String entry : packages.get(packagePath)) { ArrayList<String> list = new ArrayList<>(packagePathElements); list.add(entry); loadNodesByNames(top, list); } } } // the default package -> not flat String packagePath = ""; if (packages.containsKey(packagePath)) { for (String entry : packages.get(packagePath)) { ArrayList<String> list = new ArrayList<>(); list.add(entry); loadNodesByNames(top, list); } } tree.setModel(new DefaultTreeModel(top)); } public void closeFile() { for (OpenFile co : hmap) { int pos = house.indexOfTab(co.name); if (pos >= 0) house.remove(pos); co.close(); } final State oldState = state; Model.this.state = null; if (oldState != null) { Closer.tryClose(oldState); } hmap.clear(); tree.setModel(new DefaultTreeModel(null)); metadataSystem = new MetadataSystem(typeLoader); file = null; treeExpansionState = null; open = false; mainWindow.onFileLoadEnded(file, open); } public void changeTheme(String xml) { InputStream in = getClass().getResourceAsStream(LuytenPreferences.THEME_XML_PATH + xml); try { if (in != null) { setTheme(Theme.load(in)); for (OpenFile f : hmap) { getTheme().apply(f.textArea); } } } catch (Exception e1) { Luyten.showExceptionDialog("Exception!", e1); } } public File getOpenedFile() { File openedFile = null; if (file != null && open) { openedFile = file; } if (openedFile == null) { getLabel().setText("No open file"); } return openedFile; } public String getCurrentTabTitle() { String tabTitle = null; try { int pos = house.getSelectedIndex(); if (pos >= 0) { tabTitle = house.getTitleAt(pos); } } catch (Exception e1) { Luyten.showExceptionDialog("Exception!", e1); } if (tabTitle == null) { getLabel().setText("No open tab"); } return tabTitle; } public RSyntaxTextArea getCurrentTextArea() { RSyntaxTextArea currentTextArea = null; try { int pos = house.getSelectedIndex(); System.out.println(pos); if (pos >= 0) { RTextScrollPane co = (RTextScrollPane) house.getComponentAt(pos); currentTextArea = (RSyntaxTextArea) co.getViewport().getView(); } } catch (Exception e1) { Luyten.showExceptionDialog("Exception!", e1); } if (currentTextArea == null) { getLabel().setText("No open tab"); } return currentTextArea; } public void startWarmUpThread() { new Thread() { public void run() { try { Thread.sleep(500); String internalName = FindBox.class.getName(); TypeReference type = metadataSystem.lookupType(internalName); TypeDefinition resolvedType = null; if ((type == null) || ((resolvedType = type.resolve()) == null)) { return; } StringWriter stringwriter = new StringWriter(); PlainTextOutput plainTextOutput = new PlainTextOutput(stringwriter); plainTextOutput .setUnicodeOutputEnabled(decompilationOptions.getSettings().isUnicodeOutputEnabled()); settings.getLanguage().decompileType(resolvedType, plainTextOutput, decompilationOptions); String decompiledSource = stringwriter.toString(); OpenFile open = new OpenFile(internalName, "*/" + internalName, getTheme(), mainWindow); open.setContent(decompiledSource); JTabbedPane pane = new JTabbedPane(); pane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); pane.addTab("title", open.scrollPane); pane.setSelectedIndex(pane.indexOfTab("title")); } catch (Exception e) { Luyten.showExceptionDialog("Exception!", e); } } }.start(); } public void navigateTo(final String uniqueStr) { new Thread(new Runnable() { @Override public void run() { if (uniqueStr == null) return; String[] linkParts = uniqueStr.split("\\|"); if (linkParts.length <= 1) return; String destinationTypeStr = linkParts[1]; try { bar.setVisible(true); getLabel().setText("Navigating: " + destinationTypeStr.replaceAll("/", ".")); TypeReference type = metadataSystem.lookupType(destinationTypeStr); if (type == null) throw new RuntimeException("Cannot lookup type: " + destinationTypeStr); TypeDefinition typeDef = type.resolve(); if (typeDef == null) throw new RuntimeException("Cannot resolve type: " + destinationTypeStr); String tabTitle = typeDef.getName() + ".class"; extractClassToTextPane(typeDef, tabTitle, destinationTypeStr, uniqueStr); getLabel().setText("Complete"); } catch (Exception e) { getLabel().setText("Cannot navigate: " + destinationTypeStr.replaceAll("/", ".")); Luyten.showExceptionDialog("Cannot Navigate!", e); } finally { bar.setVisible(false); } } }).start(); } public JLabel getLabel() { return label; } public void setLabel(JLabel label) { this.label = label; } public State getState() { return state; } public Theme getTheme() { return theme; } public void setTheme(Theme theme) { this.theme = theme; } }