content: /**
   * 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;
    }
  
  }
  