1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 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.File;
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("jarExtensions"),
62                                    new String[] { ".apk", ".ap_", ".jar", ".aar", ".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.setApkFilter(firstEntry.getApkFilter());
271        filterDialog.setJarFilter(firstEntry.getJarFilter());
272        filterDialog.setAarFilter(firstEntry.getAarFilter());
273        filterDialog.setWarFilter(firstEntry.getWarFilter());
274        filterDialog.setEarFilter(firstEntry.getEarFilter());
275        filterDialog.setZipFilter(firstEntry.getZipFilter());
276    }
277
278
279    /**
280     * Applies the entered filter to the specified class path entries.
281     * Any previously set filters are discarded.
282     */
283    private void setFiltersAt(int[] indices)
284    {
285        for (int index = indices.length - 1; index >= 0; index--)
286        {
287            ClassPathEntry entry = (ClassPathEntry)listModel.get(indices[index]);
288            entry.setFilter(filterDialog.getFilter());
289            entry.setApkFilter(filterDialog.getApkFilter());
290            entry.setJarFilter(filterDialog.getJarFilter());
291            entry.setAarFilter(filterDialog.getAarFilter());
292            entry.setWarFilter(filterDialog.getWarFilter());
293            entry.setEarFilter(filterDialog.getEarFilter());
294            entry.setZipFilter(filterDialog.getZipFilter());
295        }
296
297        // Make sure they are selected and thus repainted.
298        list.setSelectedIndices(indices);
299    }
300
301
302    /**
303     * Attaches the tool tip from the GUI resources that corresponds to the
304     * given key, to the given component.
305     */
306    private static JComponent tip(JComponent component, String messageKey)
307    {
308        component.setToolTipText(msg(messageKey));
309
310        return component;
311    }
312
313
314    /**
315     * Returns the message from the GUI resources that corresponds to the given
316     * key.
317     */
318    private static String msg(String messageKey)
319    {
320         return GUIResources.getMessage(messageKey);
321    }
322
323
324    /**
325     * This ListCellRenderer renders ClassPathEntry objects.
326     */
327    private class MyListCellRenderer implements ListCellRenderer
328    {
329        private static final String ARROW_IMAGE_FILE = "arrow.gif";
330
331        private final JPanel cellPanel    = new JPanel(new GridBagLayout());
332        private final JLabel iconLabel    = new JLabel("", JLabel.RIGHT);
333        private final JLabel jarNameLabel = new JLabel("", JLabel.RIGHT);
334        private final JLabel filterLabel  = new JLabel("", JLabel.RIGHT);
335
336        private final Icon arrowIcon;
337
338
339        public MyListCellRenderer()
340        {
341            GridBagConstraints jarNameLabelConstraints = new GridBagConstraints();
342            jarNameLabelConstraints.anchor             = GridBagConstraints.WEST;
343            jarNameLabelConstraints.insets             = new Insets(1, 2, 1, 2);
344
345            GridBagConstraints filterLabelConstraints  = new GridBagConstraints();
346            filterLabelConstraints.gridwidth           = GridBagConstraints.REMAINDER;
347            filterLabelConstraints.fill                = GridBagConstraints.HORIZONTAL;
348            filterLabelConstraints.weightx             = 1.0;
349            filterLabelConstraints.anchor              = GridBagConstraints.EAST;
350            filterLabelConstraints.insets              = jarNameLabelConstraints.insets;
351
352            arrowIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(this.getClass().getResource(ARROW_IMAGE_FILE)));
353
354            cellPanel.add(iconLabel,    jarNameLabelConstraints);
355            cellPanel.add(jarNameLabel, jarNameLabelConstraints);
356            cellPanel.add(filterLabel,  filterLabelConstraints);
357        }
358
359
360        // Implementations for ListCellRenderer.
361
362        public Component getListCellRendererComponent(JList   list,
363                                                      Object  value,
364                                                      int     index,
365                                                      boolean isSelected,
366                                                      boolean cellHasFocus)
367        {
368            ClassPathEntry entry = (ClassPathEntry)value;
369
370            // Prepend an arrow to the output entries.
371            if (inputAndOutput && entry.isOutput())
372            {
373                iconLabel.setIcon(arrowIcon);
374            }
375            else
376            {
377                iconLabel.setIcon(null);
378            }
379
380            // Set the entry name text.
381            jarNameLabel.setText(entry.getName());
382
383            // Set the filter text.
384            StringBuffer filter = null;
385            filter = appendFilter(filter, entry.getZipFilter());
386            filter = appendFilter(filter, entry.getEarFilter());
387            filter = appendFilter(filter, entry.getWarFilter());
388            filter = appendFilter(filter, entry.getAarFilter());
389            filter = appendFilter(filter, entry.getJarFilter());
390            filter = appendFilter(filter, entry.getApkFilter());
391            filter = appendFilter(filter, entry.getFilter());
392
393            if (filter != null)
394            {
395                filter.append(')');
396            }
397
398            filterLabel.setText(filter != null ? filter.toString() : "");
399
400            // Set the colors.
401            if (isSelected)
402            {
403                cellPanel.setBackground(list.getSelectionBackground());
404                jarNameLabel.setForeground(list.getSelectionForeground());
405                filterLabel.setForeground(list.getSelectionForeground());
406            }
407            else
408            {
409                cellPanel.setBackground(list.getBackground());
410                jarNameLabel.setForeground(list.getForeground());
411                filterLabel.setForeground(list.getForeground());
412            }
413
414            // Make the font color red if this is an input file that can't be read.
415            if (!(inputAndOutput && entry.isOutput()) &&
416                !entry.getFile().canRead())
417            {
418                jarNameLabel.setForeground(Color.red);
419            }
420
421            cellPanel.setOpaque(true);
422
423            return cellPanel;
424        }
425
426
427        private StringBuffer appendFilter(StringBuffer filter, List additionalFilter)
428        {
429            if (filter != null)
430            {
431                filter.append(';');
432            }
433
434            if (additionalFilter != null)
435            {
436                if (filter == null)
437                {
438                    filter = new StringBuffer().append('(');
439                }
440
441                filter.append(ListUtil.commaSeparatedString(additionalFilter, true));
442            }
443
444            return filter;
445        }
446    }
447}
448