/* * Copyright 2015 Google, Inc. * * 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.google.classyshark.gui.panel; import com.google.classyshark.gui.GuiMode; import com.google.classyshark.gui.panel.chart.RingChartPanel; import com.google.classyshark.gui.panel.displayarea.DisplayArea; import com.google.classyshark.gui.panel.displayarea.IDisplayArea; import com.google.classyshark.gui.panel.io.CurrentFolderConfig; import com.google.classyshark.gui.panel.io.FileChooserUtils; import com.google.classyshark.gui.panel.io.RecentArchivesConfig; import com.google.classyshark.gui.panel.methodscount.MethodsCountPanel; import com.google.classyshark.gui.panel.toolbar.KeyUtils; import com.google.classyshark.gui.panel.toolbar.Toolbar; import com.google.classyshark.gui.panel.toolbar.ToolbarController; import com.google.classyshark.gui.panel.tree.FilesTree; import com.google.classyshark.gui.settings.SettingsFrame; import com.google.classyshark.gui.theme.Theme; import com.google.classyshark.silverghost.SilverGhost; import com.google.classyshark.silverghost.TokensMapper; import com.google.classyshark.silverghost.exporter.Exporter; import com.google.classyshark.silverghost.methodscounter.ClassNode; import com.google.classyshark.silverghost.translator.Translator; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.File; import java.util.ArrayList; import java.util.List; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileFilter; /** * App controller, general app structure MVM ==> Model - View - Mediator (this class) */ public class ClassySharkPanel extends JPanel implements ToolbarController, ViewerController, KeyListener { private static final boolean IS_CLASSNAME_FROM_MOUSE_CLICK = true; private static final boolean VIEW_TOP_CLASS = true; public static final String ANDROID_MANIFEST_XML_SEARCH = "AndroidManifest.xml - "; private JFrame parentFrame; private Toolbar toolbar; private JSplitPane jSplitPane; private MethodsCountPanel methodsCountPanel; private int dividerLocation = 0; private IDisplayArea displayArea; private FilesTree filesTree; private RingChartPanel ringChartPanel; private boolean isDataLoaded = false; private final Theme theme = GuiMode.getTheme(); private SilverGhost silverGhost = new SilverGhost(); public ClassySharkPanel(JFrame frame, File archive, String fullClassName) { this(frame); silverGhost.setBinaryArchive(archive); updateUiAfterArchiveReadAndLoadClass(fullClassName); } public ClassySharkPanel(JFrame frame, File archive) { this(frame); silverGhost.setBinaryArchive(archive); displayArchive(silverGhost.getBinaryArchive()); } public ClassySharkPanel(JFrame frame) { super(false); buildUI(); parentFrame = frame; toolbar.setText(""); theme.applyTo(this); } @Override public void onSelectedTypeClassFromMouseClick(String selectedClass) { for (String clazz : silverGhost.getImportsForCurrentClass()) { if (clazz.contains(selectedClass)) { onSelectedImportFromMouseClick(clazz); return; } } for (String clazz : silverGhost.getAllClassNames()) { if (clazz.contains(selectedClass)) { onSelectedImportFromMouseClick(clazz); return; } } } @Override public void onSelectedImportFromMouseClick(String className) { if (silverGhost.getAllClassNames().contains(className)) { onSelectedClassName(className); } } @Override public void onSelectedClassName(String className) { fillDisplayArea(className, VIEW_TOP_CLASS, IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void openArchive() { final JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return FileChooserUtils.acceptFile(f); } @Override public String getDescription() { return FileChooserUtils.getFileChooserDescription(); } }); fc.setCurrentDirectory(CurrentFolderConfig.INSTANCE.getCurrentDirectory()); int returnVal = fc.showOpenDialog(this); toolbar.setText(""); if (returnVal == JFileChooser.APPROVE_OPTION) { File resultFile = fc.getSelectedFile(); CurrentFolderConfig.INSTANCE.setCurrentDirectory(fc.getCurrentDirectory()); RecentArchivesConfig.INSTANCE.addArchive(resultFile.getName(), fc.getCurrentDirectory()); displayArchive(resultFile); } } @Override public void onGoBackPressed() { toolbar.setText(""); displayArea.displayClassNames(silverGhost.getAllClassNames(), ""); silverGhost.initClassNameFiltering(); } @Override public void onViewTopClassPressed() { final String textFromTypingArea = toolbar.getText(); fillDisplayArea(textFromTypingArea, VIEW_TOP_CLASS, !IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void onChangedTextFromTypingArea(String selectedLine) { fillDisplayArea(selectedLine, !VIEW_TOP_CLASS, !IS_CLASSNAME_FROM_MOUSE_CLICK); } @Override public void onMappingsButtonPressed() { final JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { return true; } @Override public String getDescription() { return ""; } }); fc.setCurrentDirectory(CurrentFolderConfig.INSTANCE.getCurrentDirectory()); int returnVal = fc.showOpenDialog(this); toolbar.setText(""); if (returnVal == JFileChooser.APPROVE_OPTION) { File resultFile = fc.getSelectedFile(); readMappingFile(resultFile); } } @Override public void onExportButtonPressed() { SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { Exporter.writeCurrentClass(silverGhost.getCurrentClassName(), silverGhost.getCurrentClassContent()); Exporter.writeArchive(silverGhost.getBinaryArchive(), silverGhost.getAllClassNames()); return null; } protected void done() { } }; worker.execute(); } @Override public void onChangeLeftPaneVisibility(boolean visible) { if (visible) { jSplitPane.setDividerLocation(dividerLocation); } else { dividerLocation = jSplitPane.getDividerLocation(); } jSplitPane.getLeftComponent().setVisible(visible); jSplitPane.updateUI(); } @Override public void onSettingsButtonPressed() { SettingsFrame frame = new SettingsFrame(); } @Override public void displayArchive(File binaryArchive) { silverGhost.setBinaryArchive(binaryArchive); if (parentFrame != null) { parentFrame.setTitle(silverGhost.getBinaryArchive().getName()); } readArchiveAndFillDisplayArea(null); toolbar.activateNavigationButtons(); filesTree.setVisibleRoot(); methodsCountPanel.loadFile(silverGhost.getBinaryArchive()); } @Override public void onSelectedMethodCount(ClassNode rootNode) { ringChartPanel.setRootNode(rootNode); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (!isDataLoaded) { openArchive(); return; } if (KeyUtils.isLeftArrowPressed(e)) { openArchive(); return; } final String textFromTypingArea = processKeyPressWithTypedText(e, toolbar.getText()); final boolean isViewTopClassKeyPressed = KeyUtils.isRightArrowPressed(e) || KeyUtils.isCommandKeyPressed(e); fillDisplayArea(textFromTypingArea, isViewTopClassKeyPressed, !ClassySharkPanel.IS_CLASSNAME_FROM_MOUSE_CLICK); } private static String processKeyPressWithTypedText(KeyEvent e, String text) { String result = text; if (KeyUtils.isDeletePressed(e)) { if (!text.isEmpty()) { result = text.substring(0, text.length() - 1); return result; } } if (KeyUtils.isLetterOrDigit(e)) { result += e.getKeyChar(); } return result; } @Override public void keyReleased(KeyEvent e) { } private void buildUI() { BorderLayout borderLayout = new BorderLayout(); setLayout(borderLayout); ringChartPanel = new RingChartPanel(this); toolbar = new Toolbar(this); add(toolbar, BorderLayout.NORTH); toolbar.addKeyListenerToTypingArea(this); displayArea = new DisplayArea(this); final JScrollPane rightScrollPane = new JScrollPane(displayArea.onAddComponentToPane()); theme.applyTo(rightScrollPane); filesTree = new FilesTree(this); JTabbedPane jTabbedPane = new JTabbedPane(); JScrollPane leftScrollPane = new JScrollPane(filesTree.getJTree()); theme.applyTo(leftScrollPane); jTabbedPane.addTab("Classes", leftScrollPane); methodsCountPanel = new MethodsCountPanel(this); jTabbedPane.addTab("Methods count", methodsCountPanel); theme.applyTo(jTabbedPane); jTabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int dividerLocation1 = jSplitPane.getDividerLocation(); JTabbedPane jTabbedPane1 = (JTabbedPane) e.getSource(); if (jTabbedPane1.getSelectedIndex() == 0) { jSplitPane.setRightComponent(rightScrollPane); } else { jSplitPane.setRightComponent(ringChartPanel); } jSplitPane.setDividerLocation(dividerLocation1); } }); jSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); jSplitPane.setDividerSize(3); jSplitPane.setPreferredSize(new Dimension(1000, 700)); jSplitPane.add(jTabbedPane, JSplitPane.LEFT); jSplitPane.add(rightScrollPane, JSplitPane.RIGHT); jSplitPane.getLeftComponent().setVisible(true); jSplitPane.setDividerLocation(300); theme.applyTo(jSplitPane); add(jSplitPane, BorderLayout.CENTER); } private void updateUiAfterArchiveReadAndLoadClass(String className) { if (parentFrame != null) { parentFrame.setTitle(silverGhost.getBinaryArchive().getName()); } readArchiveAndFillDisplayArea(className); toolbar.activateNavigationButtons(); filesTree.setVisibleRoot(); methodsCountPanel.loadFile(silverGhost.getBinaryArchive()); } private void readArchiveAndFillDisplayArea(final String className) { SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { silverGhost.readContents(); return null; } @Override protected void done() { if (silverGhost.isArchiveError()) { filesTree.fillArchive(new File("ERROR"), new ArrayList<String>(), silverGhost.getComponents()); displayArea.displayError(); return; } filesTree.fillArchive(silverGhost.getBinaryArchive(), silverGhost.getAllClassNames(), silverGhost.getComponents()); if (className != null) { onSelectedClassName(className); } else { displayArea.displaySharkey(); } isDataLoaded = true; } }; worker.execute(); } private void readMappingFile(final File resultFile) { SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { private TokensMapper reverseMappings; @Override protected Void doInBackground() throws Exception { reverseMappings = silverGhost.readMappingFile(resultFile); return null; } protected void done() { silverGhost.addMappings(reverseMappings); } }; worker.execute(); } private void fillDisplayArea(final String textFromTypingArea, final boolean viewTopClass, final boolean viewMouseClickedClass) { toolbar.setTypingAreaCaret(); SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { private List<Translator.ELEMENT> displayedClassTokens = new ArrayList<>(); private List<Translator.ELEMENT> manifestSearchResultsTokens = new ArrayList<>() ; private List<String> filteredClassNames = new ArrayList<>(); private String className = ""; @Override protected Void doInBackground() throws Exception { if (viewMouseClickedClass) { className = textFromTypingArea; convertToManifestIfNeeded(); silverGhost.translateArchiveElement(className); displayedClassTokens = silverGhost.getArchiveElementTokens(); } else if (viewTopClass) { className = silverGhost.getAutoCompleteClassName(); silverGhost.translateArchiveElement(className); displayedClassTokens = silverGhost.getArchiveElementTokens(); } else { className = textFromTypingArea; convertToManifestIfNeeded(); filteredClassNames = silverGhost.filter(className); manifestSearchResultsTokens = silverGhost.getManifestMatches(textFromTypingArea); checkIfOneClassAndPrepareTokens(); } return null; } @Override protected void done() { if(className.isEmpty()) return; if (isUserClickedOnSearchResult()) { if (clickedOnClass()) { toolbar.setText(className); displayArea.displayClass(displayedClassTokens, textFromTypingArea); } else { toolbar.setText("AndroidManifest.xml"); displayManifestWithSpecificLine(); } } else { if (noResults()) { displayArea.displayError(); } else if (oneResult()) { displayArea.displayClass(displayedClassTokens, ""); } else { displayArea.displaySearchResults(filteredClassNames, manifestSearchResultsTokens, textFromTypingArea); } } } private boolean isUserClickedOnSearchResult() { return viewTopClass || viewMouseClickedClass; } private boolean clickedOnClass() { return !displayedClassTokens.isEmpty(); } private void displayManifestWithSpecificLine() { silverGhost.translateArchiveElement("AndroidManifest.xml"); displayedClassTokens = silverGhost.getArchiveElementTokens(); displayArea.displayClass(displayedClassTokens, className); } private void checkIfOneClassAndPrepareTokens() { if (oneResult()) { String topClassName = filteredClassNames.get(0); silverGhost.translateArchiveElement(topClassName); displayedClassTokens = silverGhost.getArchiveElementTokens(); } } private boolean noResults() { return ((filteredClassNames.size() == 0) && (manifestSearchResultsTokens.size() == 0)); } private boolean oneResult() { return filteredClassNames.size() == 1; } private void convertToManifestIfNeeded() { if (textFromTypingArea.startsWith(ANDROID_MANIFEST_XML_SEARCH)) { className = "AndroidManifest.xml"; } } }; worker.execute(); } }