/**
* JReversePro - Java Decompiler / Disassembler.
* Copyright (C) 2008 Karthik Kumar.
*
* 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 org.jreversepro.gui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
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.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import org.apache.commons.io.IOUtils;
import org.jreversepro.JReverseProContext;
import org.jreversepro.JReverseProContext.OutputType;
import org.jreversepro.jls.JLSConstants;
import org.jreversepro.jvm.JVMConstants;
import org.jreversepro.jvm.TypeInferrer;
import org.jreversepro.parser.ClassParserException;
import org.jreversepro.reflect.ClassInfo;
import org.jreversepro.reflect.Field;
import org.jreversepro.reflect.Import;
import org.jreversepro.reflect.Method;
/**
* Entry point for swing-based GUI
*
* @author Karthik Kumar
*/
@SuppressWarnings("serial")
public class GUIMain extends JFrame implements ActionListener,
GuiConstants {
/**
* Application Context shared between command line and GUI.
*/
private final JReverseProContext context;
/**
* Represents the MenuBar.
*/
private final MainMenu mMbrGen;
/**
* Panel containing the Reverse engineered code.
*/
private final ClassEditPanel pnlEditor;
/**
* Panel containing the status bar.
*/
private final StatusPanel mPnlStatusBar;
/**
* Path to class currently reverse engineered.
*/
private final String mCurrentClass = null;
/**
* JClassInfo class currently reverse engineered.
*/
private ClassInfo mClassInfo;
/**
* Name of the property file.
*/
private final String mPropertyFile;
/**
* Default Directory of a file open/save dialog.
*/
private String mCurDir;
private static final String CURRENT_DIRECTORY = ".";
/**
* No-argument constructor.
*/
public GUIMain(JReverseProContext _context) {
super(TITLE);
mPropertyFile = System.getProperty("user.home")
+ System.getProperty("file.separator") + PROP_FILE;
pnlEditor = new ClassEditPanel();
mPnlStatusBar = new StatusPanel();
pnlEditor.setPreferredSize(new Dimension(500, 200));
mPnlStatusBar.setPreferredSize(new Dimension(500, 20));
getContentPane().setLayout(new BorderLayout());
getContentPane().add(pnlEditor, BorderLayout.CENTER);
getContentPane().add(mPnlStatusBar, BorderLayout.SOUTH);
mMbrGen = new MainMenu(this);
setJMenuBar(mMbrGen);
mCurDir = CURRENT_DIRECTORY;
mMbrGen.onViewCPool.setEnabled(false);
context = _context;
initAppState();
addListeners();
}
/**
* Method containing handlers for events generated by MenuItems.
*
* @param aEvent
* Event generated by GUI.
*/
public void actionPerformed(ActionEvent aEvent) {
if (aEvent.getSource() == mMbrGen.onFileOpen) {
openFile();
} else if (aEvent.getSource() == mMbrGen.onFileSave) {
saveFile();
} else if (aEvent.getSource() == mMbrGen.onFileExit) {
appClose();
} else if (aEvent.getSource() == mMbrGen.onViewCPool) {
viewPool();
} else if (aEvent.getSource() == mMbrGen.onOptFont) {
showFontDialog();
} else if (aEvent.getSource() == mMbrGen.onHelpAbout) {
showAbout();
} else if (aEvent.getSource() == mMbrGen.onEditCut) {
cutText();
} else if (aEvent.getSource() == mMbrGen.onEditCopy) {
copyText();
}
}
/**
* Method to open a file.
*/
public synchronized void openFile() {
CustomFileChooser chooser = new CustomFileChooser(mCurDir, "Class Files",
".class", "Open File");
if (chooser.showChooser(this, "Decompile File") == JFileChooser.APPROVE_OPTION) {
File f = chooser.getSelectedFile();
mCurDir = f.getAbsolutePath();
try {
reverseEngineer(f);
formatTitle(f.getAbsolutePath());
} catch (Exception _ex) {
(new DlgError(this, f.toString(), _ex)).setVisible(true);
}
}
}
/**
* Method to reverse engineer a file.
*
* @param aFile
* Class file to be reverse engineered.
* @throws ClassParserException
* Thrown if class file not in proper format.
* @throws IOException
* Thrown if error occured in reading class file.
* @throws RevEngineException
* Thrown if error occured in reverse engineering file.
*/
private synchronized void reverseEngineer(File aFile)
throws ClassParserException, IOException {
mClassInfo = context.loadResource(aFile.getAbsolutePath());
// Extract code.
// TODO: Select Disassembler / Decompiler based on a variable.
String code = context.print(OutputType.DECOMPILER, mClassInfo);
// Output the code
pnlEditor.writeCode(code);
mMbrGen.onViewCPool.setEnabled(true);
createTree(mClassInfo, mCurrentClass);
}
/**
* Method invoked while saving to a file.
*/
public void saveFile() {
CustomFileChooser chooser = new CustomFileChooser(mCurDir,
"Java Source Files", ".java", "Save File");
if (chooser.showChooser(this, "Save File") == JFileChooser.APPROVE_OPTION) {
try {
pnlEditor.writeToFile(this, chooser.getSelectedFile());
} catch (Exception ex) {
// TODO: Do something about this exception
}
}
}
/**
* Method invoked while closing a file.
*/
public void appClose() {
if (ConfirmCloseDialog.confirmExit(this)) {
saveProperties();
}
}
/**
* Method invoked while text is cut.
*/
public void cutText() {
// System.out.println("Text Cut");
}
/**
* Method invoked while text is copied.
*/
public void copyText() {
// System.out.println("Text Copied");
}
/**
* Method invoked while ConstantPool is viewed.
*/
public void viewPool() {
DlgConstantPool dlgPool = new DlgConstantPool(this, mCurrentClass,
mClassInfo.getConstantPool());
dlgPool.setVisible(true);
dlgPool = null;
}
/**
* Method invoked while System Font is being viewed.
*/
public void showFontDialog() {
DlgFont dlg = new DlgFont(this, "System Fonts");
if (dlg.showFontDialog() == DlgFont.SELECTED) {
pnlEditor.setEditorFont(dlg.getChosenFont());
}
}
/**
* Method invoked to show the About dialog box.
*/
public void showAbout() {
new DlgAbout(this, "About");
}
/**
* Method invoked to initialize the GUI parameters.
*/
private void initAppState() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
FileInputStream fis = null;
try {
Properties pp = new Properties();
fis = new FileInputStream(mPropertyFile);
pp.load(fis);
int x = Integer.parseInt(pp.getProperty(XPOS));
int y = Integer.parseInt(pp.getProperty(YPOS));
int width = Integer.parseInt(pp.getProperty(XSIZE));
int height = Integer.parseInt(pp.getProperty(YSIZE));
mMbrGen.setFlag(pp.getProperty(DECOMPILE_FLAG));
pnlEditor.setEditorFont(new Font(pp.getProperty(FONT), Font.PLAIN,
DlgFont.OPTIMUM_SIZE));
setLocation(x, y);
setSize(width, height);
} catch (FileNotFoundException fnfe) {
setLocation(0, 0);
setSize(800, 550);
pnlEditor.setEditorFont(new Font(ClassEditPanel.DEFAULT_FONT, Font.PLAIN,
DlgFont.OPTIMUM_SIZE));
System.err.println("Failed to load property file");
} catch (IOException ioe) {
System.err.println("Exception while closing a property file ");
} finally {
IOUtils.closeQuietly(fis);
}
}
/**
* Method to add property listeners to MenuItems and the MainFrame.
*/
private void addListeners() {
mMbrGen.onFileOpen.addActionListener(this);
mMbrGen.onFileSave.addActionListener(this);
mMbrGen.onFileExit.addActionListener(this);
mMbrGen.onEditCut.addActionListener(this);
mMbrGen.onEditCopy.addActionListener(this);
mMbrGen.onViewCPool.addActionListener(this);
mMbrGen.onOptFont.addActionListener(this);
mMbrGen.onHelpAbout.addActionListener(this);
addWindowListener(new WindowAdapter() {
/**
* WindowClosing event handler.
*
* @param aEvent
* Event generated.
*/
@Override
public void windowClosing(WindowEvent aEvent) {
appClose();
super.windowClosing(aEvent);
}
});
}
/**
* Formats the title from the string Rhs and sets the title of the Frame.
*
* @param aFileName
* Full Path name to the class being reverse engineered.
*
*/
private void formatTitle(String aFileName) {
int dotIndex = aFileName.indexOf(".");
if (dotIndex != -1) {
String className = aFileName.substring(0, dotIndex);
setTitle(TITLE + " - " + className);
}
}
/**
* Save the state of the GUI as a properties file.
*
*/
private void saveProperties() {
FileOutputStream fos = null;
try {
Properties pp = new Properties();
pp.setProperty(DECOMPILE_FLAG, Boolean.valueOf(
mMbrGen.onDecompiler.isSelected()).toString());
pp.setProperty(XPOS, Integer.valueOf(getLocation().x).toString());
pp.setProperty(YPOS, Integer.valueOf(getLocation().y).toString());
pp.setProperty(XSIZE, Integer.valueOf(getSize().width).toString());
pp.setProperty(YSIZE, Integer.valueOf(getSize().height).toString());
pp.setProperty(FONT, pnlEditor.getEditorFont().getFamily());
fos = new FileOutputStream(mPropertyFile);
pp.store(fos, PROP_HEADING);
} catch (Exception _ex) {
System.err.println("Failed to write Properties" + mPropertyFile);
System.err.println(_ex);
} finally {
IOUtils.closeQuietly(fos);
}
}
/**
* Creates the JTree of the LHS Panel ssociated with the class.
*
* @param aClassInfo
* Information about the class.
* @param aCurrentClass
* Name of the current class being reverse engineered.
*
*/
private void createTree(ClassInfo aClassInfo, String aCurrentClass) {
Import mImports = aClassInfo.getConstantPool().getImportedClasses();
int dotIndex = aCurrentClass.indexOf(".");
if (dotIndex != -1) {
aCurrentClass = aCurrentClass.substring(0, dotIndex);
}
List<Field> listFields = aClassInfo.getFields();
List<Method> listMethods = aClassInfo.getMethods();
List<String> result = new ArrayList<String>();
for (Field field : listFields) {
StringBuilder sb = new StringBuilder("");
String datatype = Import.getClassName(TypeInferrer.getJLSType(field
.getDatatype(), false));
sb.append(field.getQualifier());
sb.append(" " + datatype);
sb.append(" " + field.getName());
result.add(sb.toString());
}
for (Method method : listMethods) {
StringBuilder sb = new StringBuilder("");
String returnType = Import.getClassName(TypeInferrer.getJLSType(method
.getReturnType(), false));
String name = method.getName();
if (name.compareTo(JVMConstants.CLINIT) == 0) {
sb.append(JLSConstants.STATIC);
} else if (name.compareTo(JVMConstants.INIT) == 0) {
sb.append(method.getQualifier());
sb.append(aCurrentClass);
sb.append(writeArgs(method.getArgList(), mImports));
} else {
sb.append(method.getQualifier());
sb.append(returnType);
sb.append(" " + name);
sb.append(writeArgs(method.getArgList(), mImports));
}
result.add(sb.toString());
}
pnlEditor.createModel(this, aCurrentClass, result);
}
/**
* Returns the argument in Java language Class Format.
*
* @param aArgs
* List of arguments of class names in JVM class format.
* @param aImports
* List of imported classes.
* @return A Concatenated list of classes separated by a comma.
*/
private StringBuilder writeArgs(List<String> aArgs, Import aImports) {
StringBuilder result = new StringBuilder("(");
boolean first = true;
for (String str : aArgs) {
if (first) {
first = false;
} else {
result.append(" ,");
}
String argType = Import.getClassName(TypeInferrer.getJLSType(str, false));
result.append(argType);
}
result.append(")");
return result;
}
}