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.MemberSpecification;
24import proguard.classfile.*;
25import proguard.classfile.util.ClassUtil;
26import proguard.util.ListUtil;
27
28import javax.swing.*;
29import javax.swing.border.*;
30import java.awt.*;
31import java.awt.event.*;
32
33/**
34 * This <code>JDialog</code> allows the user to enter a String.
35 *
36 * @author Eric Lafortune
37 */
38final class MemberSpecificationDialog extends JDialog
39{
40    /**
41     * Return value if the dialog is canceled (with the Cancel button or by
42     * closing the dialog window).
43     */
44    public static final int CANCEL_OPTION = 1;
45
46    /**
47     * Return value if the dialog is approved (with the Ok button).
48     */
49    public static final int APPROVE_OPTION = 0;
50
51
52    private final boolean isField;
53
54    private final JRadioButton[] publicRadioButtons;
55    private final JRadioButton[] privateRadioButtons;
56    private final JRadioButton[] protectedRadioButtons;
57    private final JRadioButton[] staticRadioButtons;
58    private final JRadioButton[] finalRadioButtons;
59    private final JRadioButton[] syntheticRadioButtons;
60
61    private JRadioButton[] volatileRadioButtons;
62    private JRadioButton[] transientRadioButtons;
63
64    private JRadioButton[] synchronizedRadioButtons;
65    private JRadioButton[] nativeRadioButtons;
66    private JRadioButton[] abstractRadioButtons;
67    private JRadioButton[] strictRadioButtons;
68    private JRadioButton[] bridgeRadioButtons;
69    private JRadioButton[] varargsRadioButtons;
70
71    private final JTextField annotationTypeTextField = new JTextField(20);
72    private final JTextField nameTextField           = new JTextField(20);
73    private final JTextField typeTextField           = new JTextField(20);
74    private final JTextField argumentTypesTextField  = new JTextField(20);
75
76    private int returnValue;
77
78
79    public MemberSpecificationDialog(JDialog owner, boolean isField)
80    {
81        super(owner, msg(isField ? "specifyFields" : "specifyMethods"), true);
82        setResizable(true);
83
84        // Create some constraints that can be reused.
85        GridBagConstraints constraints = new GridBagConstraints();
86        constraints.anchor = GridBagConstraints.WEST;
87        constraints.insets = new Insets(1, 2, 1, 2);
88
89        GridBagConstraints constraintsStretch = new GridBagConstraints();
90        constraintsStretch.fill    = GridBagConstraints.HORIZONTAL;
91        constraintsStretch.weightx = 1.0;
92        constraintsStretch.anchor  = GridBagConstraints.WEST;
93        constraintsStretch.insets  = constraints.insets;
94
95        GridBagConstraints constraintsLast = new GridBagConstraints();
96        constraintsLast.gridwidth = GridBagConstraints.REMAINDER;
97        constraintsLast.anchor    = GridBagConstraints.WEST;
98        constraintsLast.insets    = constraints.insets;
99
100        GridBagConstraints constraintsLastStretch = new GridBagConstraints();
101        constraintsLastStretch.gridwidth = GridBagConstraints.REMAINDER;
102        constraintsLastStretch.fill      = GridBagConstraints.HORIZONTAL;
103        constraintsLastStretch.weightx   = 1.0;
104        constraintsLastStretch.anchor    = GridBagConstraints.WEST;
105        constraintsLastStretch.insets    = constraints.insets;
106
107        GridBagConstraints panelConstraints = new GridBagConstraints();
108        panelConstraints.gridwidth = GridBagConstraints.REMAINDER;
109        panelConstraints.fill      = GridBagConstraints.HORIZONTAL;
110        panelConstraints.weightx   = 1.0;
111        panelConstraints.weighty   = 0.0;
112        panelConstraints.anchor    = GridBagConstraints.NORTHWEST;
113        panelConstraints.insets    = constraints.insets;
114
115        GridBagConstraints stretchPanelConstraints = new GridBagConstraints();
116        stretchPanelConstraints.gridwidth = GridBagConstraints.REMAINDER;
117        stretchPanelConstraints.fill      = GridBagConstraints.BOTH;
118        stretchPanelConstraints.weightx   = 1.0;
119        stretchPanelConstraints.weighty   = 1.0;
120        stretchPanelConstraints.anchor    = GridBagConstraints.NORTHWEST;
121        stretchPanelConstraints.insets    = constraints.insets;
122
123        GridBagConstraints labelConstraints = new GridBagConstraints();
124        labelConstraints.anchor = GridBagConstraints.CENTER;
125        labelConstraints.insets = new Insets(2, 10, 2, 10);
126
127        GridBagConstraints lastLabelConstraints = new GridBagConstraints();
128        lastLabelConstraints.gridwidth = GridBagConstraints.REMAINDER;
129        lastLabelConstraints.anchor    = GridBagConstraints.CENTER;
130        lastLabelConstraints.insets    = labelConstraints.insets;
131
132        GridBagConstraints advancedButtonConstraints = new GridBagConstraints();
133        advancedButtonConstraints.weightx = 1.0;
134        advancedButtonConstraints.weighty = 1.0;
135        advancedButtonConstraints.anchor  = GridBagConstraints.SOUTHWEST;
136        advancedButtonConstraints.insets  = new Insets(4, 4, 8, 4);
137
138        GridBagConstraints okButtonConstraints = new GridBagConstraints();
139        okButtonConstraints.weightx = 1.0;
140        okButtonConstraints.weighty = 1.0;
141        okButtonConstraints.anchor  = GridBagConstraints.SOUTHEAST;
142        okButtonConstraints.insets  = advancedButtonConstraints.insets;
143
144        GridBagConstraints cancelButtonConstraints = new GridBagConstraints();
145        cancelButtonConstraints.gridwidth = GridBagConstraints.REMAINDER;
146        cancelButtonConstraints.weighty   = 1.0;
147        cancelButtonConstraints.anchor    = GridBagConstraints.SOUTHEAST;
148        cancelButtonConstraints.insets    = okButtonConstraints.insets;
149
150        GridBagLayout layout = new GridBagLayout();
151
152        Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
153
154        this.isField = isField;
155
156        // Create the access panel.
157        JPanel accessPanel = new JPanel(layout);
158        accessPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
159                                                               msg("access")));
160
161        accessPanel.add(Box.createGlue(),                                labelConstraints);
162        accessPanel.add(tip(new JLabel(msg("required")), "requiredTip"), labelConstraints);
163        accessPanel.add(tip(new JLabel(msg("not")),      "notTip"),      labelConstraints);
164        accessPanel.add(tip(new JLabel(msg("dontCare")), "dontCareTip"), labelConstraints);
165        accessPanel.add(Box.createGlue(),                                constraintsLastStretch);
166
167        publicRadioButtons    = addRadioButtonTriplet("Public",    accessPanel);
168        privateRadioButtons   = addRadioButtonTriplet("Private",   accessPanel);
169        protectedRadioButtons = addRadioButtonTriplet("Protected", accessPanel);
170        staticRadioButtons    = addRadioButtonTriplet("Static",    accessPanel);
171        finalRadioButtons     = addRadioButtonTriplet("Final",     accessPanel);
172        syntheticRadioButtons = addRadioButtonTriplet("Synthetic", accessPanel);
173
174        if (isField)
175        {
176            volatileRadioButtons  = addRadioButtonTriplet("Volatile",  accessPanel);
177            transientRadioButtons = addRadioButtonTriplet("Transient", accessPanel);
178        }
179        else
180        {
181            synchronizedRadioButtons = addRadioButtonTriplet("Synchronized", accessPanel);
182            nativeRadioButtons       = addRadioButtonTriplet("Native",       accessPanel);
183            abstractRadioButtons     = addRadioButtonTriplet("Abstract",     accessPanel);
184            strictRadioButtons       = addRadioButtonTriplet("Strict",       accessPanel);
185            bridgeRadioButtons       = addRadioButtonTriplet("Bridge",       accessPanel);
186            varargsRadioButtons      = addRadioButtonTriplet("Varargs",      accessPanel);
187        }
188
189        // Create the type panel.
190        JPanel typePanel = new JPanel(layout);
191        typePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
192                                                             msg(isField ? "fieldType" :
193                                                                           "returnType")));
194
195        typePanel.add(tip(typeTextField, "typeTip"), constraintsLastStretch);
196
197        // Create the annotation type panel.
198        final JPanel annotationTypePanel = new JPanel(layout);
199        annotationTypePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
200                                                                       msg("annotation")));
201
202        annotationTypePanel.add(tip(annotationTypeTextField, "classNameTip"), constraintsLastStretch);
203
204        // Create the name panel.
205        JPanel namePanel = new JPanel(layout);
206        namePanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
207                                                             msg("name")));
208
209        namePanel.add(tip(nameTextField, isField ? "fieldNameTip" :
210                                                   "methodNameTip"), constraintsLastStretch);
211
212        // Create the arguments panel.
213        JPanel argumentsPanel = new JPanel(layout);
214        argumentsPanel.setBorder(BorderFactory.createTitledBorder(etchedBorder,
215                                                                  msg("argumentTypes")));
216
217        argumentsPanel.add(tip(argumentTypesTextField, "argumentTypes2Tip"), constraintsLastStretch);
218
219        // Create the Advanced button.
220        final JButton advancedButton = new JButton(msg("basic"));
221        advancedButton.addActionListener(new ActionListener()
222        {
223            public void actionPerformed(ActionEvent e)
224            {
225                boolean visible = !annotationTypePanel.isVisible();
226
227                annotationTypePanel.setVisible(visible);
228
229                advancedButton.setText(msg(visible ? "basic" : "advanced"));
230
231                pack();
232            }
233        });
234        advancedButton.doClick();
235
236        // Create the Ok button.
237        JButton okButton = new JButton(msg("ok"));
238        okButton.addActionListener(new ActionListener()
239        {
240            public void actionPerformed(ActionEvent e)
241            {
242                returnValue = APPROVE_OPTION;
243                hide();
244            }
245        });
246
247        // Create the Cancel button.
248        JButton cancelButton = new JButton(msg("cancel"));
249        cancelButton.addActionListener(new ActionListener()
250        {
251            public void actionPerformed(ActionEvent e)
252            {
253                hide();
254            }
255        });
256
257        // Add all panels to the main panel.
258        JPanel mainPanel = new JPanel(layout);
259        mainPanel.add(tip(accessPanel,         "accessTip"),       panelConstraints);
260        mainPanel.add(tip(annotationTypePanel, "annotationTip"),   panelConstraints);
261        mainPanel.add(tip(typePanel, isField ? "fieldTypeTip" :
262                                               "returnTypeTip"),   panelConstraints);
263        mainPanel.add(tip(namePanel,           "nameTip"),         panelConstraints);
264
265        if (!isField)
266        {
267            mainPanel.add(tip(argumentsPanel, "argumentTypesTip"), panelConstraints);
268        }
269
270        mainPanel.add(tip(advancedButton, "advancedTip"), advancedButtonConstraints);
271        mainPanel.add(okButton,                           okButtonConstraints);
272        mainPanel.add(cancelButton,                       cancelButtonConstraints);
273
274        getContentPane().add(new JScrollPane(mainPanel));
275    }
276
277
278    /**
279     * Adds a JLabel and three JRadioButton instances in a ButtonGroup to the
280     * given panel with a GridBagLayout, and returns the buttons in an array.
281     */
282    private JRadioButton[] addRadioButtonTriplet(String labelText,
283                                                 JPanel panel)
284    {
285        GridBagConstraints labelConstraints = new GridBagConstraints();
286        labelConstraints.anchor = GridBagConstraints.WEST;
287        labelConstraints.insets = new Insets(2, 10, 2, 10);
288
289        GridBagConstraints buttonConstraints = new GridBagConstraints();
290        buttonConstraints.insets = labelConstraints.insets;
291
292        GridBagConstraints lastGlueConstraints = new GridBagConstraints();
293        lastGlueConstraints.gridwidth = GridBagConstraints.REMAINDER;
294        lastGlueConstraints.weightx   = 1.0;
295
296        // Create the radio buttons.
297        JRadioButton radioButton0 = new JRadioButton();
298        JRadioButton radioButton1 = new JRadioButton();
299        JRadioButton radioButton2 = new JRadioButton();
300
301        // Put them in a button group.
302        ButtonGroup buttonGroup = new ButtonGroup();
303        buttonGroup.add(radioButton0);
304        buttonGroup.add(radioButton1);
305        buttonGroup.add(radioButton2);
306
307        // Add the label and the buttons to the panel.
308        panel.add(new JLabel(labelText), labelConstraints);
309        panel.add(radioButton0,          buttonConstraints);
310        panel.add(radioButton1,          buttonConstraints);
311        panel.add(radioButton2,          buttonConstraints);
312        panel.add(Box.createGlue(),      lastGlueConstraints);
313
314        return new JRadioButton[]
315        {
316             radioButton0,
317             radioButton1,
318             radioButton2
319        };
320    }
321
322
323    /**
324     * Sets the MemberSpecification to be represented in this dialog.
325     */
326    public void setMemberSpecification(MemberSpecification memberSpecification)
327    {
328        String annotationType = memberSpecification.annotationType;
329        String name           = memberSpecification.name;
330        String descriptor     = memberSpecification.descriptor;
331
332        // Set the class name text fields.
333        annotationTypeTextField.setText(annotationType == null ? "" : ClassUtil.externalType(annotationType));
334
335        // Set the access radio buttons.
336        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PUBLIC,       publicRadioButtons);
337        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PRIVATE,      privateRadioButtons);
338        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PROTECTED,    protectedRadioButtons);
339        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_STATIC,       staticRadioButtons);
340        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_FINAL,        finalRadioButtons);
341        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_SYNTHETIC,    syntheticRadioButtons);
342        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_VOLATILE,     volatileRadioButtons);
343        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_TRANSIENT,    transientRadioButtons);
344        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_SYNCHRONIZED, synchronizedRadioButtons);
345        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_NATIVE,       nativeRadioButtons);
346        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_ABSTRACT,     abstractRadioButtons);
347        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_STRICT,       strictRadioButtons);
348        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_BRIDGE,       bridgeRadioButtons);
349        setMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_VARARGS,      varargsRadioButtons);
350
351        // Set the class name text fields.
352        nameTextField.setText(name == null ? "*" : name);
353
354        if (isField)
355        {
356            typeTextField         .setText(descriptor == null ? "***" : ClassUtil.externalType(descriptor));
357        }
358        else
359        {
360            typeTextField         .setText(descriptor == null ? "***" : ClassUtil.externalMethodReturnType(descriptor));
361            argumentTypesTextField.setText(descriptor == null ? "..." : ClassUtil.externalMethodArguments(descriptor));
362        }
363    }
364
365
366    /**
367     * Returns the MemberSpecification currently represented in this dialog.
368     */
369    public MemberSpecification getMemberSpecification()
370    {
371        String annotationType = annotationTypeTextField.getText();
372        String name           = nameTextField.getText();
373        String type           = typeTextField.getText();
374        String arguments      = argumentTypesTextField.getText();
375
376        // Convert all class member specifications into the internal format.
377        annotationType =
378            annotationType.equals("") ||
379            annotationType.equals("***") ? null : ClassUtil.internalType(annotationType);
380
381        if (name.equals("") ||
382            name.equals("*"))
383        {
384            name = null;
385        }
386
387        if (isField)
388        {
389            type =
390                type.equals("") ||
391                type.equals("***") ? null : ClassUtil.internalType(type);
392        }
393        else
394        {
395            if (type.equals(""))
396            {
397                type = JavaConstants.TYPE_VOID;
398            }
399
400            type =
401                type     .equals("***") &&
402                arguments.equals("...") ? null :
403                    ClassUtil.internalMethodDescriptor(type, ListUtil.commaSeparatedList(arguments));
404        }
405
406        MemberSpecification memberSpecification =
407            new MemberSpecification(0, 0, annotationType, name, type);
408
409        // Also get the access radio button settings.
410        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PUBLIC,       publicRadioButtons);
411        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PRIVATE,      privateRadioButtons);
412        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_PROTECTED,    protectedRadioButtons);
413        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_STATIC,       staticRadioButtons);
414        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_FINAL,        finalRadioButtons);
415        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_SYNTHETIC,    syntheticRadioButtons);
416        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_VOLATILE,     volatileRadioButtons);
417        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_TRANSIENT,    transientRadioButtons);
418        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_SYNCHRONIZED, synchronizedRadioButtons);
419        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_NATIVE,       nativeRadioButtons);
420        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_ABSTRACT,     abstractRadioButtons);
421        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_STRICT,       strictRadioButtons);
422        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_BRIDGE,       bridgeRadioButtons);
423        getMemberSpecificationRadioButtons(memberSpecification, ClassConstants.ACC_VARARGS,      varargsRadioButtons);
424
425        return memberSpecification;
426    }
427
428
429    /**
430     * Shows this dialog. This method only returns when the dialog is closed.
431     *
432     * @return <code>CANCEL_OPTION</code> or <code>APPROVE_OPTION</code>,
433     *         depending on the choice of the user.
434     */
435    public int showDialog()
436    {
437        returnValue = CANCEL_OPTION;
438
439        // Open the dialog in the right place, then wait for it to be closed,
440        // one way or another.
441        pack();
442        setLocationRelativeTo(getOwner());
443        show();
444
445        return returnValue;
446    }
447
448
449    /**
450     * Sets the appropriate radio button of a given triplet, based on the access
451     * flags of the given keep option.
452     */
453    private void setMemberSpecificationRadioButtons(MemberSpecification memberSpecification,
454                                                    int                 flag,
455                                                    JRadioButton[]      radioButtons)
456    {
457        if (radioButtons != null)
458        {
459            int index = (memberSpecification.requiredSetAccessFlags   & flag) != 0 ? 0 :
460                        (memberSpecification.requiredUnsetAccessFlags & flag) != 0 ? 1 :
461                                                                                       2;
462            radioButtons[index].setSelected(true);
463        }
464    }
465
466
467    /**
468     * Updates the access flag of the given keep option, based on the given radio
469     * button triplet.
470     */
471    private void getMemberSpecificationRadioButtons(MemberSpecification memberSpecification,
472                                                    int                 flag,
473                                                    JRadioButton[]      radioButtons)
474    {
475        if (radioButtons != null)
476        {
477            if      (radioButtons[0].isSelected())
478            {
479                memberSpecification.requiredSetAccessFlags   |= flag;
480            }
481            else if (radioButtons[1].isSelected())
482            {
483                memberSpecification.requiredUnsetAccessFlags |= flag;
484            }
485        }
486    }
487
488
489    /**
490     * Attaches the tool tip from the GUI resources that corresponds to the
491     * given key, to the given component.
492     */
493    private static JComponent tip(JComponent component, String messageKey)
494    {
495        component.setToolTipText(msg(messageKey));
496
497        return component;
498    }
499
500
501    /**
502     * Returns the message from the GUI resources that corresponds to the given
503     * key.
504     */
505    private static String msg(String messageKey)
506    {
507         return GUIResources.getMessage(messageKey);
508    }
509}
510