1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2013 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.gui;
22
23import proguard.*;
24import proguard.util.ListUtil;
25
26import javax.swing.*;
27import java.awt.*;
28import java.awt.event.*;
29import java.io.*;
30import java.util.List;
31
32/**
33 * This <code>ListPanel</code> allows the user to add, edit, filter, move, and
34 * remove ClassPathEntry objects in a ClassPath object.
35 *
36 * @author Eric Lafortune
37 */
38class ClassPathPanel extends ListPanel
39{
40    private final JFrame       owner;
41    private final boolean      inputAndOutput;
42    private final JFileChooser chooser;
43    private final FilterDialog filterDialog;
44
45
46    public ClassPathPanel(JFrame owner, boolean inputAndOutput)
47    {
48        super();
49
50        super.firstSelectionButton = inputAndOutput ? 3 : 2;
51
52        this.owner          = owner;
53        this.inputAndOutput = inputAndOutput;
54
55        list.setCellRenderer(new MyListCellRenderer());
56
57        chooser = new JFileChooser("");
58        chooser.setMultiSelectionEnabled(true);
59        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
60        chooser.addChoosableFileFilter(
61            new ExtensionFileFilter(msg("jarWarEarZipExtensions"),
62                                    new String[] { ".jar", ".war", ".ear", ".zip" }));
63        chooser.setApproveButtonText(msg("ok"));
64
65        filterDialog = new FilterDialog(owner, msg("enterFilter"));
66
67        addAddButton(inputAndOutput, false);
68        if (inputAndOutput)
69        {
70            addAddButton(inputAndOutput, true);
71        }
72        addEditButton();
73        addFilterButton();
74        addRemoveButton();
75        addUpButton();
76        addDownButton();
77
78        enableSelectionButtons();
79    }
80
81
82    protected void addAddButton(boolean       inputAndOutput,
83                                final boolean isOutput)
84    {
85        JButton addButton = new JButton(msg(inputAndOutput ?
86                                            isOutput       ? "addOutput" :
87                                                             "addInput" :
88                                                             "add"));
89        addButton.addActionListener(new ActionListener()
90        {
91            public void actionPerformed(ActionEvent e)
92            {
93                chooser.setDialogTitle(msg("addJars"));
94                chooser.setSelectedFile(null);
95                chooser.setSelectedFiles(null);
96
97                int returnValue = chooser.showOpenDialog(owner);
98                if (returnValue == JFileChooser.APPROVE_OPTION)
99                {
100                    File[] selectedFiles = chooser.getSelectedFiles();
101                    ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput);
102
103                    // Add the new elements.
104                    addElements(entries);
105                }
106            }
107        });
108
109        addButton(tip(addButton, inputAndOutput ?
110                                 isOutput       ? "addOutputTip" :
111                                                  "addInputTip" :
112                                                  "addTip"));
113    }
114
115
116    protected void addEditButton()
117    {
118        JButton editButton = new JButton(msg("edit"));
119        editButton.addActionListener(new ActionListener()
120        {
121            public void actionPerformed(ActionEvent e)
122            {
123                boolean isOutput = false;
124
125                int[] selectedIndices = list.getSelectedIndices();
126
127                // Copy the Object array into a File array.
128                File[] selectedFiles = new File[selectedIndices.length];
129                for (int index = 0; index < selectedFiles.length; index++)
130                {
131                    ClassPathEntry entry =
132                        (ClassPathEntry)listModel.getElementAt(selectedIndices[index]);
133
134                    isOutput = entry.isOutput();
135
136                    selectedFiles[index] = entry.getFile();
137                }
138
139                chooser.setDialogTitle(msg("chooseJars"));
140
141                // Up to JDK 1.3.1, setSelectedFiles doesn't show in the file
142                // chooser, so we just use setSelectedFile first. It also sets
143                // the current directory.
144                chooser.setSelectedFile(selectedFiles[0].getAbsoluteFile());
145                chooser.setSelectedFiles(selectedFiles);
146
147                int returnValue = chooser.showOpenDialog(owner);
148                if (returnValue == JFileChooser.APPROVE_OPTION)
149                {
150                    selectedFiles = chooser.getSelectedFiles();
151                    ClassPathEntry[] entries = classPathEntries(selectedFiles, isOutput);
152
153                    // If there are the same number of files selected now as
154                    // there were before, we can just replace the old ones.
155                    if (selectedIndices.length == selectedFiles.length)
156                    {
157                        // Replace the old elements.
158                        setElementsAt(entries, selectedIndices);
159                    }
160                    else
161                    {
162                        // Remove the old elements.
163                        removeElementsAt(selectedIndices);
164
165                        // Add the new elements.
166                        addElements(entries);
167                    }
168                }
169            }
170        });
171
172        addButton(tip(editButton, "editTip"));
173    }
174
175
176    protected void addFilterButton()
177    {
178        JButton filterButton = new JButton(msg("filter"));
179        filterButton.addActionListener(new ActionListener()
180        {
181            public void actionPerformed(ActionEvent e)
182            {
183                if (!list.isSelectionEmpty())
184                {
185                    int[] selectedIndices = list.getSelectedIndices();
186
187                    // Put the filters of the first selected entry in the dialog.
188                    getFiltersFrom(selectedIndices[0]);
189
190                    int returnValue = filterDialog.showDialog();
191                    if (returnValue == FilterDialog.APPROVE_OPTION)
192                    {
193                        // Apply the entered filters to all selected entries.
194                        setFiltersAt(selectedIndices);
195                    }
196                }
197            }
198        });
199
200        addButton(tip(filterButton, "filterTip"));
201    }
202
203
204    /**
205     * Sets the ClassPath to be represented in this panel.
206     */
207    public void setClassPath(ClassPath classPath)
208    {
209        listModel.clear();
210
211        if (classPath != null)
212        {
213            for (int index = 0; index < classPath.size(); index++)
214            {
215                listModel.addElement(classPath.get(index));
216            }
217        }
218
219        // Make sure the selection buttons are properly enabled,
220        // since the clear method doesn't seem to notify the listener.
221        enableSelectionButtons();
222    }
223
224
225    /**
226     * Returns the ClassPath currently represented in this panel.
227     */
228    public ClassPath getClassPath()
229    {
230        int size = listModel.size();
231        if (size == 0)
232        {
233            return null;
234        }
235
236        ClassPath classPath = new ClassPath();
237        for (int index = 0; index < size; index++)
238        {
239            classPath.add((ClassPathEntry)listModel.get(index));
240        }
241
242        return classPath;
243    }
244
245
246    /**
247     * Converts the given array of File objects into a corresponding array of
248     * ClassPathEntry objects.
249     */
250    private ClassPathEntry[] classPathEntries(File[] files, boolean isOutput)
251    {
252        ClassPathEntry[] entries = new ClassPathEntry[files.length];
253        for (int index = 0; index < entries.length; index++)
254        {
255            entries[index] = new ClassPathEntry(files[index], isOutput);
256        }
257        return entries;
258    }
259
260
261    /**
262     * Sets up the filter dialog with the filters from the specified class path
263     * entry.
264     */
265    private void getFiltersFrom(int index)
266    {
267        ClassPathEntry firstEntry = (ClassPathEntry)listModel.get(index);
268
269        filterDialog.setFilter(firstEntry.getFilter());
270        filterDialog.setJarFilter(firstEntry.getJarFilter());
271        filterDialog.setWarFilter(firstEntry.getWarFilter());
272        filterDialog.setEarFilter(firstEntry.getEarFilter());
273        filterDialog.setZipFilter(firstEntry.getZipFilter());
274    }
275
276
277    /**
278     * Applies the entered filter to the specified class path entries.
279     * Any previously set filters are discarded.
280     */
281    private void setFiltersAt(int[] indices)
282    {
283        for (int index = indices.length - 1; index >= 0; index--)
284        {
285            ClassPathEntry entry = (ClassPathEntry)listModel.get(indices[index]);
286            entry.setFilter(filterDialog.getFilter());
287            entry.setJarFilter(filterDialog.getJarFilter());
288            entry.setWarFilter(filterDialog.getWarFilter());
289            entry.setEarFilter(filterDialog.getEarFilter());
290            entry.setZipFilter(filterDialog.getZipFilter());
291        }
292
293        // Make sure they are selected and thus repainted.
294        list.setSelectedIndices(indices);
295    }
296
297
298    /**
299     * Attaches the tool tip from the GUI resources that corresponds to the
300     * given key, to the given component.
301     */
302    private static JComponent tip(JComponent component, String messageKey)
303    {
304        component.setToolTipText(msg(messageKey));
305
306        return component;
307    }
308
309
310    /**
311     * Returns the message from the GUI resources that corresponds to the given
312     * key.
313     */
314    private static String msg(String messageKey)
315    {
316         return GUIResources.getMessage(messageKey);
317    }
318
319
320    /**
321     * This ListCellRenderer renders ClassPathEntry objects.
322     */
323    private class MyListCellRenderer implements ListCellRenderer
324    {
325        private static final String ARROW_IMAGE_FILE = "arrow.gif";
326
327        private final JPanel cellPanel    = new JPanel(new GridBagLayout());
328        private final JLabel iconLabel    = new JLabel("", JLabel.RIGHT);
329        private final JLabel jarNameLabel = new JLabel("", JLabel.RIGHT);
330        private final JLabel filterLabel  = new JLabel("", JLabel.RIGHT);
331
332        private final Icon arrowIcon;
333
334
335        public MyListCellRenderer()
336        {
337            GridBagConstraints jarNameLabelConstraints = new GridBagConstraints();
338            jarNameLabelConstraints.anchor             = GridBagConstraints.WEST;
339            jarNameLabelConstraints.insets             = new Insets(1, 2, 1, 2);
340
341            GridBagConstraints filterLabelConstraints  = new GridBagConstraints();
342            filterLabelConstraints.gridwidth           = GridBagConstraints.REMAINDER;
343            filterLabelConstraints.fill                = GridBagConstraints.HORIZONTAL;
344            filterLabelConstraints.weightx             = 1.0;
345            filterLabelConstraints.anchor              = GridBagConstraints.EAST;
346            filterLabelConstraints.insets              = jarNameLabelConstraints.insets;
347
348            arrowIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(this.getClass().getResource(ARROW_IMAGE_FILE)));
349
350            cellPanel.add(iconLabel,    jarNameLabelConstraints);
351            cellPanel.add(jarNameLabel, jarNameLabelConstraints);
352            cellPanel.add(filterLabel,  filterLabelConstraints);
353        }
354
355
356        // Implementations for ListCellRenderer.
357
358        public Component getListCellRendererComponent(JList   list,
359                                                      Object  value,
360                                                      int     index,
361                                                      boolean isSelected,
362                                                      boolean cellHasFocus)
363        {
364            ClassPathEntry entry = (ClassPathEntry)value;
365
366            // Prepend an arrow to the output entries.
367            if (inputAndOutput && entry.isOutput())
368            {
369                iconLabel.setIcon(arrowIcon);
370            }
371            else
372            {
373                iconLabel.setIcon(null);
374            }
375
376            // Set the entry name text.
377            jarNameLabel.setText(entry.getName());
378
379            // Set the filter text.
380            StringBuffer filter = null;
381            filter = appendFilter(filter, entry.getZipFilter());
382            filter = appendFilter(filter, entry.getEarFilter());
383            filter = appendFilter(filter, entry.getWarFilter());
384            filter = appendFilter(filter, entry.getJarFilter());
385            filter = appendFilter(filter, entry.getFilter());
386
387            if (filter != null)
388            {
389                filter.append(')');
390            }
391
392            filterLabel.setText(filter != null ? filter.toString() : "");
393
394            // Set the colors.
395            if (isSelected)
396            {
397                cellPanel.setBackground(list.getSelectionBackground());
398                jarNameLabel.setForeground(list.getSelectionForeground());
399                filterLabel.setForeground(list.getSelectionForeground());
400            }
401            else
402            {
403                cellPanel.setBackground(list.getBackground());
404                jarNameLabel.setForeground(list.getForeground());
405                filterLabel.setForeground(list.getForeground());
406            }
407
408            // Make the font color red if this is an input file that can't be read.
409            if (!(inputAndOutput && entry.isOutput()) &&
410                !entry.getFile().canRead())
411            {
412                jarNameLabel.setForeground(Color.red);
413            }
414
415            cellPanel.setOpaque(true);
416
417            return cellPanel;
418        }
419
420
421        private StringBuffer appendFilter(StringBuffer filter, List additionalFilter)
422        {
423            if (filter != null)
424            {
425                filter.append(';');
426            }
427
428            if (additionalFilter != null)
429            {
430                if (filter == null)
431                {
432                    filter = new StringBuffer().append('(');
433                }
434
435                filter.append(ListUtil.commaSeparatedString(additionalFilter, true));
436            }
437
438            return filter;
439        }
440    }
441}
442