/* $Id: Import.java 17870 2010-01-12 20:49:32Z linus $ ***************************************************************************** * Copyright (c) 2009 Contributors - see below * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * linus ***************************************************************************** * * Some portions of this file was previously release using the BSD License: */ // Copyright (c) 1996-2008 The Regents of the University of California. All // Rights Reserved. Permission to use, copy, modify, and distribute this // software and its documentation without fee, and without a written // agreement is hereby granted, provided that the above copyright notice // and this paragraph appear in all copies. This software program and // documentation are copyrighted by The Regents of the University of // California. The software program and documentation are supplied "AS // IS", without any accompanying services from The Regents. The Regents // does not warrant that the operation of the program will be // uninterrupted or error-free. The end-user understands that the program // was developed for research purposes and is advised not to rely // exclusively on the program for any reason. IN NO EVENT SHALL THE // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. package org.argouml.uml.reveng; import java.awt.BorderLayout; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.io.File; import java.nio.charset.Charset; import java.util.List; import java.util.StringTokenizer; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.filechooser.FileSystemView; import org.argouml.application.api.Argo; import org.argouml.configuration.Configuration; import org.argouml.i18n.Translator; import org.argouml.moduleloader.ModuleInterface; import org.argouml.uml.reveng.SettingsTypes.BooleanSelection2; import org.argouml.uml.reveng.SettingsTypes.PathListSelection; import org.argouml.uml.reveng.SettingsTypes.PathSelection; import org.argouml.uml.reveng.SettingsTypes.Setting; import org.argouml.uml.reveng.SettingsTypes.UniqueSelection2; import org.argouml.uml.reveng.SettingsTypes.UserString2; import org.argouml.uml.reveng.ui.ImportClasspathDialog; import org.argouml.uml.reveng.ui.ImportStatusScreen; import org.argouml.util.SuffixFilter; import org.argouml.util.UIUtils; import org.tigris.gef.base.Globals; import org.tigris.swidgets.GridLayout2; /** * This is the main class for the Swing importer framework. It extends * ImportCommon which contains all the GUI independent pieces of the import * framework. * <p> * The Service Providers Interface (SPI) to the individual language importers * is defined in such a way that they can be completely GUI independent as * well, receiving lists of source files and settings for the import and * reporting progress via progress monitor API. * * It provides JPanels for tailoring the import run in the FileChooser. * <p> * * The Import run is started by calling doFile(Project, File) * <p> * * Supports recursive search in folder for source files with matching * extensions. * <p> * * There are three levels of detail for import: * <p> * * <ol> * <li> 0 - classifiers only * <li> 1 - classifiers plus feature specifications * <li> 2 - full import, feature detail (ie. operations with methods) * </ol> * * @author Andreas Rueckert a_rueckert@gmx.net * @author Tom Morris <tfmorris@gmail.com> */ public class Import extends ImportCommon implements ImportSettings { private JComponent configPanel; private JCheckBox descend; private JCheckBox changedOnly; private JCheckBox createDiagrams; private JCheckBox minimiseFigs; private JCheckBox layoutDiagrams; // level 0 import detail private JRadioButton classOnly; // level 1 import detail private JRadioButton classAndFeatures; // level 2 import detail private JRadioButton fullImport; private JComboBox sourceEncoding; private JDialog dialog; private ImportStatusScreen iss; private Frame myFrame; /** * Creates dialog window with chooser and configuration panel. * * @param frame the ui frame to display dialogs on */ public Import(Frame frame) { super(); myFrame = frame; // TODO: this needs to be improved // even for nongui calling, the config panel needs to be initialized: getConfigPanel(); if (frame != null) { JComponent chooser = getChooser(); dialog = new JDialog(frame, Translator.localize("action.import-sources"), true); dialog.getContentPane().add(chooser, BorderLayout.CENTER); dialog.getContentPane().add(getConfigPanel(), BorderLayout.EAST); dialog.pack(); int x = (frame.getSize().width - dialog.getSize().width) / 2; int y = (frame.getSize().height - dialog.getSize().height) / 2; dialog.setLocation(x > 0 ? x : 0, y > 0 ? y : 0); UIUtils.loadCommonKeyMap(dialog); dialog.setVisible(true); } } /* * @see org.argouml.uml.reveng.ImportSettings#getInputSourceEncoding() */ public String getInputSourceEncoding() { return (String) sourceEncoding.getSelectedItem(); } /** * Close dialog window. */ private void disposeDialog() { StringBuffer flags = new StringBuffer(30); flags.append(isDescendSelected()).append(","); flags.append(isChangedOnlySelected()).append(","); flags.append(isCreateDiagramsSelected()).append(","); flags.append(isMinimizeFigsSelected()).append(","); flags.append(isDiagramLayoutSelected()); Configuration.setString(Argo.KEY_IMPORT_GENERAL_SETTINGS_FLAGS, flags .toString()); Configuration.setString(Argo.KEY_IMPORT_GENERAL_DETAIL_LEVEL, String .valueOf(getImportLevel())); Configuration.setString(Argo.KEY_INPUT_SOURCE_ENCODING, getInputSourceEncoding()); dialog.setVisible(false); dialog.dispose(); } /** * Get the panel that lets the user set reverse engineering parameters. * * @param importInstance the instance of the import * @return the panel This is an internal method. Use the accessors in * {@link ImportSettings} to determine the current settings. */ private JComponent getConfigPanel() { final JTabbedPane tab = new JTabbedPane(); // build the configPanel: if (configPanel == null) { JPanel general = new JPanel(); general.setLayout(new GridLayout2(20, 1, 0, 0, GridLayout2.NONE)); general.add(new JLabel(Translator .localize("action.import-select-lang"))); JComboBox selectedLanguage = new JComboBox(getModules().keySet() .toArray()); selectedLanguage .addActionListener(new SelectedLanguageListener(tab)); general.add(selectedLanguage); addConfigCheckboxes(general); addDetailLevelButtons(general); addSourceEncoding(general); tab.add(general, Translator.localize("action.import-general")); ImportInterface current = getCurrentModule(); if (current != null) { tab.add(getConfigPanelExtension(), current.getName()); } configPanel = tab; } return configPanel; } private void addConfigCheckboxes(JPanel panel) { boolean desc = true; boolean chan = true; boolean crea = true; boolean mini = true; boolean layo = true; String flags = Configuration .getString(Argo.KEY_IMPORT_GENERAL_SETTINGS_FLAGS); if (flags != null && flags.length() > 0) { StringTokenizer st = new StringTokenizer(flags, ","); if (st.hasMoreTokens() && st.nextToken().equals("false")) { desc = false; } if (st.hasMoreTokens() && st.nextToken().equals("false")) { chan = false; } if (st.hasMoreTokens() && st.nextToken().equals("false")) { crea = false; } if (st.hasMoreTokens() && st.nextToken().equals("false")) { mini = false; } if (st.hasMoreTokens() && st.nextToken().equals("false")) { layo = false; } } descend = new JCheckBox(Translator .localize("action.import-option-descend-dir-recur"), desc); panel.add(descend); changedOnly = new JCheckBox(Translator .localize("action.import-option-changed_new"), chan); panel.add(changedOnly); createDiagrams = new JCheckBox(Translator .localize("action.import-option-create-diagram"), crea); panel.add(createDiagrams); minimiseFigs = new JCheckBox(Translator .localize("action.import-option-min-class-icon"), mini); panel.add(minimiseFigs); layoutDiagrams = new JCheckBox(Translator.localize( "action.import-option-perform-auto-diagram-layout"), layo); panel.add(layoutDiagrams); // de-selects the fig minimising & layout // if we are not creating diagrams createDiagrams.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { if (!createDiagrams.isSelected()) { minimiseFigs.setSelected(false); layoutDiagrams.setSelected(false); } } }); } private void addDetailLevelButtons(JPanel panel) { // select the level of import // 0 - classifiers only // 1 - classifiers plus feature specifications // 2 - full import, feature detail JLabel importDetailLabel = new JLabel(Translator .localize("action.import-level-of-import-detail")); ButtonGroup detailButtonGroup = new ButtonGroup(); classOnly = new JRadioButton(Translator .localize("action.import-option-classifiers")); detailButtonGroup.add(classOnly); classAndFeatures = new JRadioButton(Translator .localize("action.import-option-classifiers-plus-specs")); detailButtonGroup.add(classAndFeatures); fullImport = new JRadioButton(Translator .localize("action.import-option-full-import")); String detaillevel = Configuration .getString(Argo.KEY_IMPORT_GENERAL_DETAIL_LEVEL); if ("0".equals(detaillevel)) { classOnly.setSelected(true); } else if ("1".equals(detaillevel)) { classAndFeatures.setSelected(true); } else { fullImport.setSelected(true); } detailButtonGroup.add(fullImport); panel.add(importDetailLabel); panel.add(classOnly); panel.add(classAndFeatures); panel.add(fullImport); } private void addSourceEncoding(JPanel panel) { panel.add(new JLabel( Translator.localize("action.import-file-encoding"))); String enc = Configuration.getString(Argo.KEY_INPUT_SOURCE_ENCODING); if (enc == null || enc.trim().equals("")) { enc = System.getProperty("file.encoding"); } // cp1252 is often the default, but windows-1252 is the name listed // by Charset.availableCharsets if (enc.startsWith("cp")) { enc = "windows-" + enc.substring(2); } sourceEncoding = new JComboBox(Charset .availableCharsets().keySet().toArray()); sourceEncoding.setSelectedItem(enc); panel.add(sourceEncoding); } /* * Get the extension panel for the configuration settings. */ private JComponent getConfigPanelExtension() { List<Setting> settings = null; ImportInterface current = getCurrentModule(); if (current != null) { settings = current.getImportSettings(); } return new ConfigPanelExtension(settings); } private class SelectedLanguageListener implements ActionListener { /** * The pane. */ private JTabbedPane tab; /** * The constructor. * * @param i The current import. * @param t The pane. */ SelectedLanguageListener(JTabbedPane t) { tab = t; } /* * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent e) { JComboBox cb = (JComboBox) e.getSource(); String selected = (String) cb.getSelectedItem(); ImportInterface oldModule = getCurrentModule(); setCurrentModule(getModules().get(selected)); updateFilters((JFileChooser) dialog.getContentPane() .getComponent(0), oldModule.getSuffixFilters(), getCurrentModule().getSuffixFilters()); updateTabbedPane(); } private void updateTabbedPane() { String name = ((ModuleInterface) getCurrentModule()).getName(); if (tab.indexOfTab(name) < 0) { tab.add(getConfigPanelExtension(), name); } } } /** * Parse all selected files in a separate thread. It calls the actual * parser methods depending on the type of the file. */ public void doFile() { iss = new ImportStatusScreen(myFrame, "Importing", "Splash"); Thread t = new Thread(new Runnable() { public void run() { doImport(iss); // ExplorerEventAdaptor.getInstance().structureChanged(); // ProjectBrowser.getInstance().getStatusBar().showProgress(0); } }, "Import Thread"); t.start(); } /* * @see org.argouml.uml.reveng.ImportCommon#getImportLevel() */ public int getImportLevel() { if (classOnly != null && classOnly.isSelected()) { return ImportSettings.DETAIL_CLASSIFIER; } else if (classAndFeatures != null && classAndFeatures.isSelected()) { return ImportSettings.DETAIL_CLASSIFIER_FEATURE; } else if (fullImport != null && fullImport.isSelected()) { return ImportSettings.DETAIL_FULL; } else { return ImportSettings.DETAIL_CLASSIFIER; } } /* * @see org.argouml.uml.reveng.ImportCommon#isCreateDiagramsChecked() */ public boolean isCreateDiagramsSelected() { if (createDiagrams != null) { return createDiagrams.isSelected(); } return true; } /* * @see org.argouml.uml.reveng.ImportCommon#isMinimiseFigsChecked() */ public boolean isMinimizeFigsSelected() { if (minimiseFigs != null) { return minimiseFigs.isSelected(); } return false; } /* * @see org.argouml.uml.reveng.ImportCommon#isDiagramLayoutSelected() */ public boolean isDiagramLayoutSelected() { if (layoutDiagrams != null) { return layoutDiagrams.isSelected(); } return false; } /* * @see org.argouml.uml.reveng.ImportCommon#isDescendSelected() */ public boolean isDescendSelected() { if (descend != null) { return descend.isSelected(); } return true; } /* * @see org.argouml.uml.reveng.ImportCommon#isChangedOnlySelected() */ public boolean isChangedOnlySelected() { if (changedOnly != null) { return changedOnly.isSelected(); } return false; } /* * Create chooser for objects we are to import. Old style modules get to * provide their own (although I don't believe any of them do), while new * style modules get the a chooser provided by us (which matches what the * abstract class FileImportSupport used to provide). */ private JComponent getChooser() { String directory = Globals.getLastDirectory(); final JFileChooser chooser = new ImportFileChooser(this, directory); chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); ImportInterface current = getCurrentModule(); if (current != null) { updateFilters(chooser, null, current.getSuffixFilters()); } return chooser; } private static void updateFilters(JFileChooser chooser, SuffixFilter[] oldFilters, SuffixFilter[] newFilters) { if (oldFilters != null) { for (int i = 0; i < oldFilters.length; i++) { chooser.removeChoosableFileFilter(oldFilters[i]); } } if (newFilters != null) { for (int i = 0; i < newFilters.length; i++) { chooser.addChoosableFileFilter(newFilters[i]); } if (newFilters.length > 0) { chooser.setFileFilter(newFilters[0]); } } } private static class ImportFileChooser extends JFileChooser { private Import theImport; /** * Constructs a new ImportFileChooser opened to the given directory. * * @param imp the import manager * @param currentDirectoryPath the directory path * @see javax.swing.JFileChooser#JFileChooser(String) */ public ImportFileChooser(Import imp, String currentDirectoryPath) { super(currentDirectoryPath); theImport = imp; initChooser(); } /** * Constructs a JFileChooser using the given current directory path and * FileSystemView. * * @param imp the import manager * @param currentDirectoryPath the directory path * @param fsv the file system view * @see javax.swing.JFileChooser#JFileChooser(String, FileSystemView) */ public ImportFileChooser(Import imp, String currentDirectoryPath, FileSystemView fsv) { super(currentDirectoryPath, fsv); theImport = imp; initChooser(); } /** * Constructs a new default ImportFileChooser. * * @param imp the import manager * @see javax.swing.JFileChooser#JFileChooser() */ public ImportFileChooser(Import imp) { super(); theImport = imp; initChooser(); } /** * Constructs a JFileChooser using the given FileSystemView. * * @param imp the import manager * @param fsv the file system view * @see javax.swing.JFileChooser#JFileChooser(FileSystemView) */ public ImportFileChooser(Import imp, FileSystemView fsv) { super(fsv); theImport = imp; initChooser(); } private void initChooser() { setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); setMultiSelectionEnabled(true); setSelectedFile(getCurrentDirectory()); } /* * @see javax.swing.JFileChooser#approveSelection() */ @Override public void approveSelection() { File[] files = getSelectedFiles(); File dir = getCurrentDirectory(); if (files.length == 0) { files = new File[] {dir}; } if (files.length == 1) { File file = files[0]; if (file != null && file.isDirectory()) { dir = file; } else { dir = file.getParentFile(); } } theImport.setSelectedFiles(files); try { theImport.setSelectedSuffixFilter( (SuffixFilter) getFileFilter()); } catch (Exception e) { // this is because of the (senseless?) "All files" FileFilter theImport.setSelectedSuffixFilter(null); } Globals.setLastDirectory(dir.getPath()); theImport.disposeDialog(); theImport.doFile(); } /* * @see javax.swing.JFileChooser#cancelSelection() */ @Override public void cancelSelection() { theImport.disposeDialog(); } } /** * Extended configuration panel for file import. Built based on settings * requested by the specific language importer. */ class ConfigPanelExtension extends JPanel { /** * Construct the configuration extension panel. * @param settings A list of settings requested by the language importer */ public ConfigPanelExtension(final List<Setting> settings) { setLayout(new GridBagLayout()); if (settings == null || settings.size() == 0) { JLabel label = new JLabel("No settings for this importer"); add(label, createGridBagConstraints(true, false, false)); add(new JPanel(), createGridBagConstraintsFinal()); return; } for (Setting setting : settings) { if (setting instanceof UniqueSelection2) { JLabel label = new JLabel(setting.getLabel()); add(label, createGridBagConstraints(true, false, false)); final UniqueSelection2 us = (UniqueSelection2) setting; ButtonGroup group = new ButtonGroup(); int count = 0; for (String option : us.getOptions()) { JRadioButton button = new JRadioButton(option); final int index = count++; if (us.getDefaultSelection() == index) { button.setSelected(true); } group.add(button); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { us.setSelection(index); } }); add(button, createGridBagConstraints(false, false, false)); } } else if (setting instanceof UserString2) { JLabel label = new JLabel(setting.getLabel()); add(label, createGridBagConstraints(true, false, false)); final UserString2 us = (UserString2) setting; final JTextField text = new JTextField(us.getDefaultString()); text.addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { } public void focusLost(FocusEvent e) { us.setUserString(text.getText()); } }); add(text, createGridBagConstraints(true, false, false)); } else if (setting instanceof BooleanSelection2) { final BooleanSelection2 bs = (BooleanSelection2) setting; final JCheckBox button = new JCheckBox(setting.getLabel()); button.setEnabled(bs.isSelected()); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { bs.setSelected(button.isSelected()); } }); add(button, createGridBagConstraints(true, false, false)); } else if (setting instanceof PathSelection) { JLabel label = new JLabel(setting.getLabel()); add(label, createGridBagConstraints(true, false, false)); PathSelection ps = (PathSelection) setting; // TODO: Need to add FileChooser JTextField text = new JTextField(ps.getDefaultPath()); add(text, createGridBagConstraints(true, false, false)); // TODO: Update setting } else if (setting instanceof PathListSelection) { PathListSelection pls = (PathListSelection) setting; add(new ImportClasspathDialog(pls), createGridBagConstraints(true, false, false)); } else { throw new RuntimeException("Unknown setting type requested " + setting); } } add(new JPanel(), createGridBagConstraintsFinal()); } /** * Create a GridBagConstraints object to use with the layout. * * @param topInset true to use a top inset * @param bottomInset true to use a bottom inset * @param fill true to fill (horizontally) * @return the grid bag constraints */ private GridBagConstraints createGridBagConstraints(boolean topInset, boolean bottomInset, boolean fill) { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = GridBagConstraints.RELATIVE; gbc.gridy = GridBagConstraints.RELATIVE; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.gridheight = 1; gbc.weightx = 1.0; gbc.weighty = 0.0; gbc.anchor = GridBagConstraints.NORTHWEST; gbc.fill = fill ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE; gbc.insets = new Insets( topInset ? 5 : 0, 5, bottomInset ? 5 : 0, 5); gbc.ipadx = 0; gbc.ipady = 0; return gbc; } /** * A GridBagConstraints for the last item to take up the rest of the * space. * * @return the GridBagConstraints object */ private GridBagConstraints createGridBagConstraintsFinal() { GridBagConstraints gbc = createGridBagConstraints(false, true, false); gbc.gridheight = GridBagConstraints.REMAINDER; gbc.weighty = 1.0; return gbc; } } }