Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 754   Methods: 39
NCLOC: 559   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
GenerateCustomDrJavaJarFrame.java 0% 0% 0% 0%
coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.ui;
 38   
 39    import java.awt.event.*;
 40    import java.awt.*;
 41    import java.io.*;
 42    import java.util.zip.*;
 43    import java.util.*;
 44    import javax.swing.*;
 45    import javax.swing.event.*;
 46    import javax.swing.border.EmptyBorder;
 47   
 48    import edu.rice.cs.util.*;
 49    import edu.rice.cs.util.swing.FileSelectorComponent;
 50    import edu.rice.cs.drjava.ui.config.VectorFileOptionComponent;
 51    import edu.rice.cs.util.swing.SwingFrame;
 52    import edu.rice.cs.util.swing.Utilities;
 53    import edu.rice.cs.drjava.config.OptionConstants;
 54   
 55    import edu.rice.cs.plt.lambda.Runnable1;
 56    import edu.rice.cs.plt.lambda.LambdaUtil;
 57    import edu.rice.cs.plt.lambda.Predicate;
 58    import edu.rice.cs.plt.io.IOUtil;
 59   
 60    import javax.swing.filechooser.FileFilter;
 61   
 62    /** A frame for generating a custom drjava.jar. */
 63    public class GenerateCustomDrJavaJarFrame extends SwingFrame {
 64   
 65    private static final int FRAME_WIDTH = 503;
 66    private static final int FRAME_HEIGHT = 500;
 67   
 68    // size of information dialogs that may be displayed
 69    private static final int INFO_DIALOG_WIDTH = 850;
 70    private static final int INFO_DIALOG_HEIGHT = 550;
 71   
 72    private MainFrame _mainFrame;
 73   
 74    private final JButton _generateButton;
 75    private final JButton _checkButton;
 76    private final JButton _closeButton;
 77    private JPanel _mainPanel;
 78   
 79    /** The file with the current DrJava executable. */
 80    private final File _drjavaFile = FileOps.getDrJavaFile();
 81   
 82    /** File selector for the jar output file. */
 83    private FileSelectorComponent _jarFileSelector;
 84   
 85    /** List with additional sources. */
 86    private VectorFileOptionComponent _sourcesList;
 87   
 88    /** Constructs a frame to generate a custom drjava.jar. */
 89  0 public GenerateCustomDrJavaJarFrame(MainFrame mf) {
 90  0 super("Generate Custom drjava.jar File");
 91   
 92  0 _mainFrame = mf;
 93  0 _mainPanel= new JPanel();
 94   
 95  0 Action generateAction = new AbstractAction("Generate") {
 96  0 public void actionPerformed(ActionEvent e) { generate(); }
 97    };
 98  0 _generateButton = new JButton(generateAction);
 99   
 100  0 Action checkAction = new AbstractAction("Check Conflicts") {
 101  0 public void actionPerformed(ActionEvent e) { checkConflicts(); }
 102    };
 103  0 _checkButton = new JButton(checkAction);
 104   
 105  0 Action closeAction = new AbstractAction("Close") {
 106  0 public void actionPerformed(ActionEvent e) { close(); }
 107    };
 108  0 _closeButton = new JButton(closeAction);
 109   
 110  0 init();
 111  0 initDone(); // call mandated by SwingFrame contract
 112    }
 113   
 114    /** Initializes the components in this frame. */
 115  0 private void init() {
 116  0 _setupPanel(_mainPanel);
 117  0 JScrollPane scrollPane = new JScrollPane(_mainPanel);
 118  0 Container cp = getContentPane();
 119   
 120  0 GridBagLayout cpLayout = new GridBagLayout();
 121  0 GridBagConstraints c = new GridBagConstraints();
 122  0 cp.setLayout(cpLayout);
 123   
 124  0 c.fill = GridBagConstraints.BOTH;
 125  0 c.anchor = GridBagConstraints.NORTH;
 126  0 c.gridwidth = GridBagConstraints.REMAINDER;
 127  0 c.gridheight = GridBagConstraints.RELATIVE;
 128  0 c.weightx = 1.0;
 129  0 c.weighty = 1.0;
 130  0 cpLayout.setConstraints(scrollPane, c);
 131  0 cp.add(scrollPane);
 132   
 133    // Add buttons
 134  0 JPanel bottom = new JPanel();
 135  0 bottom.setBorder(new EmptyBorder(5,5,5,5));
 136  0 bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS));
 137  0 bottom.add(Box.createHorizontalGlue());
 138  0 bottom.add(_checkButton);
 139  0 bottom.add(_generateButton);
 140  0 bottom.add(_closeButton);
 141  0 bottom.add(Box.createHorizontalGlue());
 142   
 143  0 c.fill = GridBagConstraints.NONE;
 144  0 c.anchor = GridBagConstraints.SOUTH;
 145  0 c.gridheight = GridBagConstraints.REMAINDER;
 146  0 c.weighty = 0.0;
 147  0 cpLayout.setConstraints(bottom, c);
 148  0 cp.add(bottom);
 149   
 150    // Set all dimensions ----
 151  0 Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
 152  0 if (dim.width>FRAME_WIDTH) { dim.width = FRAME_WIDTH; }
 153  0 else { dim.width -= 80; }
 154  0 if (dim.height>FRAME_HEIGHT) { dim.height = FRAME_HEIGHT; }
 155  0 else { dim.height -= 80; }
 156  0 setSize(dim);
 157  0 Utilities.setPopupLoc(this, _mainFrame);
 158   
 159  0 reset();
 160    }
 161   
 162    /** Create a file selector to select the output jar file
 163    * @return The JPanel that contains the selector
 164    */
 165  0 private JPanel _makeJarFileSelector() {
 166  0 JFileChooser fileChooser = new JFileChooser();
 167  0 fileChooser.setDialogTitle("Select Jar Output File");
 168  0 fileChooser.setApproveButtonText("Select");
 169  0 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
 170  0 fileChooser.setMultiSelectionEnabled(false);
 171  0 fileChooser.setDialogType(JFileChooser.SAVE_DIALOG);
 172   
 173  0 _jarFileSelector = new FileSelectorComponent(this, fileChooser, 20, 12f, false) {
 174    /** Opens the file chooser to select a file, putting the result in the file field. */
 175  0 protected void _chooseFile() {
 176  0 _mainFrame.removeModalWindowAdapter(GenerateCustomDrJavaJarFrame.this);
 177  0 super._chooseFile();
 178  0 validateTextField();
 179  0 _mainFrame.installModalWindowAdapter(GenerateCustomDrJavaJarFrame.this, LambdaUtil.NO_OP, CLOSE);
 180    }
 181  0 public boolean validateTextField() {
 182  0 String newValue = _fileField.getText().trim();
 183  0 if ((newValue.length()>0) && !newValue.toLowerCase().endsWith(".jar")) {
 184  0 _fileField.setText(newValue+".jar");
 185    }
 186  0 return super.validateTextField();
 187    }
 188    };
 189  0 _jarFileSelector.setFileFilter(new FileFilter() {
 190  0 public boolean accept(File f) { return f.getName().endsWith(".jar") || f.isDirectory(); }
 191  0 public String getDescription() { return "Java Archive Files (*.jar)"; }
 192    });
 193   
 194  0 return _jarFileSelector;
 195    }
 196   
 197    /** Resets the frame and hides it. */
 198  0 public void close() {
 199  0 setVisible(false);
 200  0 reset();
 201    }
 202   
 203    /** Reset the frame. */
 204  0 public void reset() {
 205  0 ArrayList<File> jars = new ArrayList<File>();
 206  0 _sourcesList.setValue(jars);
 207    }
 208   
 209    /** Generate the custom drjava.jar file, reset the frame and hide it. */
 210  0 public void generate() {
 211  0 final File jarOut = _jarFileSelector.getFileFromField();
 212  0 if ((jarOut == null) || (jarOut.equals(FileOps.NULL_FILE))) {
 213  0 JOptionPane.showMessageDialog(GenerateCustomDrJavaJarFrame.this,
 214    "You must specify an output file",
 215    "Error: No File Specified",
 216    JOptionPane.ERROR_MESSAGE);
 217  0 return;
 218    }
 219  0 else if (jarOut.exists()) {
 220  0 if (jarOut.equals(_drjavaFile)) {
 221  0 JOptionPane.showMessageDialog(GenerateCustomDrJavaJarFrame.this,
 222    "You cannot specify this DrJava executable as output file.\n"+
 223    "Please choose a different file.",
 224    "Error: Cannot Overwrite",
 225    JOptionPane.ERROR_MESSAGE);
 226  0 return;
 227    }
 228   
 229  0 if (JOptionPane.showConfirmDialog(GenerateCustomDrJavaJarFrame.this,
 230    "Are you sure you want to overwrite the file '" + jarOut.getPath() + "'?",
 231    "Overwrite file?",
 232    JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
 233    // I want to focus back to the dialog
 234  0 return;
 235    }
 236    }
 237   
 238  0 final Container prevContentPane = getContentPane();
 239   
 240  0 JPanel cp = new JPanel(new BorderLayout(5,5));
 241  0 cp.setBorder(new EmptyBorder(5,5,5,5));
 242  0 setContentPane(cp);
 243  0 validate();
 244   
 245  0 new Thread() {
 246  0 public void run() {
 247  0 final StringBuilder sb = new StringBuilder();
 248  0 final Runnable yesRunnable = new Runnable() {
 249  0 public void run() {
 250    // generate
 251  0 sb.setLength(0);
 252  0 new Thread() {
 253  0 public void run() {
 254  0 boolean result = true;
 255  0 try {
 256  0 final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(jarOut));
 257  0 final Runnable noRunnable = new Runnable() {
 258  0 public void run() {
 259  0 try { zos.close(); }
 260    catch(IOException ioe) { /* ignore, this failed anyway */ }
 261  0 setContentPane(prevContentPane);
 262  0 validate();
 263  0 new DrJavaScrollableDialog(GenerateCustomDrJavaJarFrame.this,
 264    "Generation Failed",
 265    "Custom drjava.jar file generation failed.",
 266    sb.toString(),
 267    INFO_DIALOG_WIDTH,
 268    INFO_DIALOG_HEIGHT,
 269    true).show();
 270    }
 271    };
 272   
 273  0 checkConflictFree(sb, zos, "Writing file ...", new Runnable() {
 274  0 public void run() {
 275  0 try {
 276    // add new options.properties file
 277  0 addOptionsPropertiesFile(zos);
 278   
 279  0 zos.close();
 280  0 setContentPane(prevContentPane);
 281  0 validate();
 282  0 JOptionPane.showMessageDialog(GenerateCustomDrJavaJarFrame.this,
 283    "Custom drjava.jar file generated successfully.",
 284    "Generation Successful",
 285    JOptionPane.INFORMATION_MESSAGE);
 286    }
 287  0 catch(IOException ioe) { noRunnable.run(); }
 288    }
 289    }, noRunnable);
 290    }
 291    catch(IOException ioe) {
 292  0 setContentPane(prevContentPane);
 293  0 validate();
 294  0 new DrJavaScrollableDialog(GenerateCustomDrJavaJarFrame.this,
 295    "Generation Failed",
 296    "Custom drjava.jar file generation failed.",
 297    sb.toString(),
 298    INFO_DIALOG_WIDTH,
 299    INFO_DIALOG_HEIGHT,
 300    true).show();
 301    }
 302    }
 303    }.start();
 304    }
 305    };
 306  0 Runnable noRunnable = new Runnable() {
 307  0 public void run() {
 308  0 if (askGenerateAnyway(sb.toString())) {
 309  0 yesRunnable.run();
 310    }
 311    else {
 312  0 setContentPane(prevContentPane);
 313  0 validate();
 314    }
 315    }
 316    };
 317  0 checkConflictFree(sb, null, "Checking for conflicts ...", yesRunnable, noRunnable);
 318    }
 319    }.start();
 320    }
 321   
 322    /** Ask if the user wants to generate the file anyway.
 323    * @return true if the user wants to generate.
 324    */
 325  0 public boolean askGenerateAnyway(String text) {
 326  0 final boolean[] result = new boolean[] { false };
 327  0 new DrJavaScrollableDialog(this,
 328    "Additional Files Conflict",
 329    "The files you want to add create conflicts. As a result,\n"+
 330    "the generated file may not work.",
 331    text,
 332    INFO_DIALOG_WIDTH,
 333    INFO_DIALOG_HEIGHT,
 334    true) {
 335  0 protected void _addButtons() {
 336  0 _buttonPanel.add(new JButton(new AbstractAction("Generate anyway") {
 337  0 public void actionPerformed(ActionEvent e) {
 338  0 result[0] = true;
 339  0 _dialog.dispose();
 340    }
 341    }));
 342  0 _buttonPanel.add(new JButton(new AbstractAction("Go back") {
 343  0 public void actionPerformed(ActionEvent e) {
 344  0 result[0] = false;
 345  0 _dialog.dispose();
 346    }
 347    }));
 348    }
 349    }.show();
 350  0 return result[0];
 351    }
 352   
 353    /** Verify that the jar files don't conflict.
 354    * @param sb StringBuilder to contain a description of the conflicts (output)
 355    * @param zos ZipOutputStream (or null if not wanted)
 356    * @param message message to display while working
 357    * @param yesRunnable code to execute if the jar files are conflict free
 358    * @param noRunnable code to execute if the jar files are NOT conflict free
 359    */
 360  0 public void checkConflictFree(StringBuilder sb, ZipOutputStream zos,
 361    String message,
 362    Runnable yesRunnable, Runnable noRunnable) {
 363  0 final Container prevContentPane = getContentPane();
 364   
 365  0 JPanel cp = new JPanel(new BorderLayout(5,5));
 366  0 cp.setBorder(new EmptyBorder(5,5,5,5));
 367  0 setContentPane(cp);
 368  0 cp.add(new JOptionPane(message,JOptionPane.INFORMATION_MESSAGE,
 369    JOptionPane.DEFAULT_OPTION,null,
 370    new Object[0]), BorderLayout.CENTER);
 371  0 JProgressBar pb = new JProgressBar(0,100);
 372  0 pb.setIndeterminate(true);
 373  0 cp.add(pb, BorderLayout.SOUTH);
 374  0 validate();
 375   
 376  0 MD5ChecksumProperties p = new MD5ChecksumProperties();
 377   
 378  0 sb.append("Conflict summary:\n");
 379  0 boolean result = true;
 380   
 381  0 try {
 382  0 if (!addZipFile(_drjavaFile, p, sb, zos, NOT_OPTIONS_PROPERTIES)) {
 383  0 result = false;
 384    }
 385    }
 386    catch(IOException ioe) {
 387  0 sb.append("Error: "+_drjavaFile.getPath()+" could not be processed.");
 388  0 result = false;
 389    }
 390   
 391  0 for(File f: _sourcesList.getValue()) {
 392  0 try {
 393  0 if (!f.exists()) {
 394  0 sb.append("Error: "+f.getPath()+" not found.");
 395  0 result = false;
 396  0 continue;
 397    }
 398  0 if (f.isDirectory()) {
 399  0 if (!addDirectory(f, p, sb, zos, NOT_MANIFEST)) {
 400  0 result = false;
 401    }
 402    }
 403    else {
 404  0 if (!addZipFile(f, p, sb, zos, NOT_MANIFEST)) {
 405  0 result = false;
 406    }
 407    }
 408    }
 409    catch(IOException ioe) {
 410  0 sb.append("Error: "+f.getPath()+" could not be processed.");
 411  0 result = false;
 412    }
 413    }
 414   
 415  0 setContentPane(prevContentPane);
 416  0 validate();
 417   
 418  0 if (result) {
 419  0 Utilities.invokeLater(yesRunnable);
 420    }
 421    else {
 422  0 Utilities.invokeLater(noRunnable);
 423    }
 424    }
 425   
 426    /**
 427    * Recursively add all files in the specified directory. Update the
 428    * MD5 checksums in the property. Log output to the StringBuilder,
 429    * and copy the files into the ZipOutputStream (if not null).
 430    * @param f the directory whose files should be recursively added
 431    * @param p MD5 checksums
 432    * @param sb StringBuilder for log output
 433    * @param zos ZipOutputStream (or null if not wanted)
 434    * @param processFile a predicate returning true if the file should be processed
 435    * @return false if there was a conflict
 436    */
 437  0 public boolean addDirectory(File f,
 438    MD5ChecksumProperties p,
 439    StringBuilder sb,
 440    ZipOutputStream zos,
 441    Predicate<String> processFile) throws IOException {
 442  0 sb.append("Adding "+f+":\n");
 443  0 boolean result = true;
 444  0 for(File de: IOUtil.listFilesRecursively(f)) {
 445  0 if (!de.isDirectory()) {
 446  0 String key = FileOps.stringMakeRelativeTo(de, f).replace('\\','/');
 447  0 if (processFile.contains(key)) {
 448  0 if (zos!=null) {
 449    // writing to zip file
 450  0 if (p.containsKey(key)) {
 451    // already exists
 452  0 sb.append("Warning: skipped "+key+", already exists\n");
 453    }
 454    else {
 455    // writing, does not exist
 456  0 zos.putNextEntry(new ZipEntry(key));
 457  0 if (!p.addMD5(key, de, zos)) {
 458    // MD5 existed and didn't match
 459  0 result = false;
 460  0 sb.append("Warning: a different "+key+" already exists\n");
 461    }
 462    }
 463    }
 464    else {
 465    // not writing
 466  0 if (!p.addMD5(key, de, zos)) {
 467    // MD5 existed and didn't match
 468  0 result = false;
 469  0 sb.append("Warning: a different "+key+" already exists\n");
 470    }
 471    }
 472    }
 473    }
 474    }
 475  0 return result;
 476    }
 477   
 478    /**
 479    * Recursively add all files in the specified zip file (or jar file,
 480    * or EXE containing a zip file). Update the MD5 checksums in the property.
 481    * Log output to the StringBuilder, and copy the files into the
 482    * ZipOutputStream (if not null).
 483    * @param f the zip file whose files should be added
 484    * @param p MD5 checksums
 485    * @param sb StringBuilder for log output
 486    * @param zos ZipOutputStream (or null if not wanted)
 487    * @param processFile a predicate returning true if the file should be processed
 488    * @return false if there was a conflict
 489    */
 490  0 public boolean addZipFile(File f,
 491    MD5ChecksumProperties p,
 492    StringBuilder sb,
 493    ZipOutputStream zos,
 494    Predicate<String> processFile) throws IOException {
 495  0 sb.append("Adding "+f+":\n");
 496  0 ZipFile zf = new ZipFile(f);
 497  0 Enumeration<? extends ZipEntry> entries = zf.entries();
 498  0 boolean result = true;
 499  0 while(entries.hasMoreElements()) {
 500  0 ZipEntry ze = entries.nextElement();
 501  0 if (!ze.isDirectory()) {
 502  0 String key = ze.getName().replace('\\','/');
 503  0 if (processFile.contains(key)) {
 504  0 if (zos!=null) {
 505    // writing to zip file
 506  0 if (p.containsKey(key)) {
 507    // already exists
 508  0 sb.append("Warning: skipped "+key+", already exists\n");
 509    }
 510    else {
 511    // writing, does not exist
 512  0 zos.putNextEntry(new ZipEntry(key));
 513  0 if (!p.addMD5(key, zf.getInputStream(ze), zos)) {
 514    // MD5 existed and didn't match
 515  0 result = false;
 516  0 sb.append("Warning: a different "+key+" already exists\n");
 517    }
 518    }
 519    }
 520    else {
 521    // not writing
 522  0 if (!p.addMD5(key, zf.getInputStream(ze), zos)) {
 523    // MD5 existed and didn't match
 524  0 result = false;
 525  0 sb.append("Warning: a different "+key+" already exists\n");
 526    }
 527    }
 528    }
 529    }
 530    }
 531  0 return result;
 532    }
 533   
 534    /** Verify that the jar files don't conflict. */
 535  0 public void checkConflicts() {
 536  0 new Thread() {
 537  0 public void run() {
 538  0 final StringBuilder sb = new StringBuilder();
 539  0 checkConflictFree(sb, null, "Checking for conflicts ...", new Runnable() {
 540  0 public void run() {
 541  0 JOptionPane.showMessageDialog(GenerateCustomDrJavaJarFrame.this,
 542    "There were no conflicts.",
 543    "Additional Files",
 544    JOptionPane.INFORMATION_MESSAGE);
 545    }
 546    }, new Runnable() {
 547  0 public void run() {
 548  0 new DrJavaScrollableDialog(GenerateCustomDrJavaJarFrame.this,
 549    "Additional Files Conflict",
 550    "The files you want to add create conflicts. As a result,\n"+
 551    "the generated file may not work.",
 552    sb.toString(),
 553    INFO_DIALOG_WIDTH,
 554    INFO_DIALOG_HEIGHT,
 555    true).show();
 556    }
 557    });
 558    }
 559    }.start();
 560    }
 561   
 562    /** Set up the panel with the explanation, the output file selector,
 563    * and the table with the additional sources.
 564    * @param panel to which the components get added */
 565  0 private void _setupPanel(JPanel panel) {
 566  0 GridBagLayout gridbag = new GridBagLayout();
 567  0 GridBagConstraints c = new GridBagConstraints();
 568  0 panel.setLayout(gridbag);
 569  0 c.fill = GridBagConstraints.HORIZONTAL;
 570  0 Insets labelInsets = new Insets(5, 10, 0, 0);
 571  0 Insets compInsets = new Insets(5, 5, 0, 10);
 572   
 573  0 c.weightx = 0.0;
 574  0 c.gridwidth = GridBagConstraints.REMAINDER;
 575  0 c.insets = labelInsets;
 576   
 577  0 JTextArea helpText = new JTextArea();
 578  0 helpText.setEditable(false);
 579  0 helpText.setText("This dialog lets you generate a custom drjava.jar file based on "+
 580    "the currently running version of DrJava, and you can include "+
 581    "additional jar files, zip files or directories. These additional "+
 582    "files are added to the drjava.jar and are immediately available "+
 583    "in the generated DrJava application without having to set up "+
 584    "extra classpaths.\n"+
 585    "\n"+
 586    "If a file is contained in more than one source, the file " +
 587    "contained in the first source will be included; conflicting " +
 588    "files from sources further down the list will be skipped. " +
 589    "Files belonging to DrJava always take precedence.\n" +
 590    "Note: This implies that DrJava's manifest file will be used.\n" +
 591    "\n"+
 592    "Please note that the added files may produce a copy of DrJava "+
 593    "does not work as intended, and that it will be more difficult "+
 594    "for us to help you with these problems. YOU ARE USING THE "+
 595    "CUSTOM DRJAVA.JAR FILE AT YOUR OWN RISK.");
 596  0 helpText.setLineWrap(true);
 597  0 helpText.setWrapStyleWord(true);
 598   
 599  0 gridbag.setConstraints(helpText, c);
 600  0 panel.add(helpText);
 601   
 602    // Output file
 603  0 c.weightx = 0.0;
 604  0 c.gridwidth = 1;
 605  0 c.weighty = 0.0;
 606  0 c.gridheight = 1;
 607  0 c.insets = labelInsets;
 608  0 JLabel label = new JLabel("Output Jar File");
 609  0 label.setToolTipText("The file that the custom drjava.jar should be written to.");
 610  0 gridbag.setConstraints(label, c);
 611  0 panel.add(label);
 612   
 613  0 c.weightx = 1.0;
 614  0 c.gridwidth = GridBagConstraints.REMAINDER;
 615  0 c.insets = compInsets;
 616   
 617  0 JPanel jarFilePanel = _makeJarFileSelector();
 618  0 gridbag.setConstraints(jarFilePanel, c);
 619  0 panel.add(jarFilePanel);
 620   
 621    // Additional sources
 622  0 c.weightx = 0.0;
 623  0 c.gridwidth = 1;
 624  0 c.weighty = 1.0;
 625  0 c.gridheight = GridBagConstraints.REMAINDER;
 626  0 c.fill = GridBagConstraints.BOTH;
 627  0 c.insets = labelInsets;
 628   
 629  0 JLabel jarLabel = new JLabel("<html>Additional Sources</html>");
 630  0 jarLabel.setToolTipText("<html>The list of additional jar or zip files or<br>" +
 631    "directories that should be added to the custom drjava.jar<br>" +
 632    "file. If a file is contained in more than one source,<br>" +
 633    "the file contained in the first source will be included;<br>" +
 634    "conflicting files from sources further down the list<br>" +
 635    "will be skipped. Files belonging to DrJava always<br>" +
 636    "take precedence.</html>");
 637  0 gridbag.setConstraints(jarLabel, c);
 638  0 panel.add(jarLabel);
 639   
 640  0 c.weightx = 1.0;
 641  0 c.gridwidth = GridBagConstraints.REMAINDER;
 642  0 c.insets = compInsets;
 643   
 644  0 Component sourcesComponent = _sourcesComponent();
 645  0 gridbag.setConstraints(sourcesComponent, c);
 646  0 panel.add(sourcesComponent);
 647    }
 648   
 649    /** Create the component that maintains the table of additional sources.
 650    * Side effect: The option component will be assigned to the _sourcesList field.
 651    * @return GUI component with the table */
 652  0 public Component _sourcesComponent() {
 653  0 _sourcesList = new VectorFileOptionComponent(null, "Additional Sources", this, null, true) {
 654  0 protected Action _getAddAction() {
 655  0 final Action a = super._getAddAction();
 656  0 return new AbstractAction("Add") {
 657  0 public void actionPerformed(ActionEvent ae) {
 658  0 _mainFrame.removeModalWindowAdapter(GenerateCustomDrJavaJarFrame.this);
 659  0 a.actionPerformed(ae);
 660  0 _mainFrame.installModalWindowAdapter(GenerateCustomDrJavaJarFrame.this, LambdaUtil.NO_OP, CLOSE);
 661    }
 662    };
 663    }
 664    };
 665  0 _sourcesList.setRows(5,5);
 666  0 return _sourcesList.getComponent();
 667    }
 668   
 669    /** Runnable that calls _close. */
 670    protected final Runnable1<WindowEvent> CLOSE = new Runnable1<WindowEvent>() {
 671  0 public void run(WindowEvent e) { close(); }
 672    };
 673   
 674    /** Validates before changing visibility. Only runs in the event thread.
 675    * @param vis true if frame should be shown, false if it should be hidden.
 676    */
 677  0 public void setVisible(boolean vis) {
 678  0 assert EventQueue.isDispatchThread();
 679  0 validate();
 680  0 if (vis) {
 681  0 _mainFrame.hourglassOn();
 682  0 _mainFrame.installModalWindowAdapter(this, LambdaUtil.NO_OP, CLOSE);
 683  0 toFront();
 684    }
 685    else {
 686  0 _mainFrame.removeModalWindowAdapter(this);
 687  0 _mainFrame.hourglassOff();
 688  0 _mainFrame.toFront();
 689    }
 690  0 super.setVisible(vis);
 691    }
 692   
 693    /** Add an updated options.properties file to the ZIP file.
 694    * @param zos output stream for the ZIP file */
 695  0 public void addOptionsPropertiesFile(ZipOutputStream zos) throws IOException {
 696  0 Properties optionsProperties = new Properties();
 697  0 ResourceBundle bundle = ResourceBundle .getBundle(edu.rice.cs.drjava.DrJava.RESOURCE_BUNDLE_NAME);
 698  0 String customDrJavaJarVersionSuffix = "";
 699   
 700  0 Enumeration<String> keyEn = bundle.getKeys();
 701  0 while(keyEn.hasMoreElements()) {
 702  0 String key = keyEn.nextElement();
 703  0 String value = bundle.getString(key);
 704  0 if (key.equals(OptionConstants.CUSTOM_DRJAVA_JAR_VERSION_SUFFIX.getName())) {
 705    // store value and skip
 706  0 customDrJavaJarVersionSuffix = value;
 707    }
 708  0 else if (key.equals(OptionConstants.NEW_VERSION_NOTIFICATION.getName())) {
 709    // skip
 710    }
 711  0 else if (key.equals(OptionConstants.NEW_VERSION_ALLOWED.getName())) {
 712    // skip
 713    }
 714    else {
 715  0 optionsProperties.setProperty(key, value);
 716    }
 717    }
 718   
 719    // update customDrJavaJarVersionSuffix
 720  0 StringBuilder sb = new StringBuilder(customDrJavaJarVersionSuffix);
 721  0 for(File f: _sourcesList.getValue()) {
 722  0 if (sb.length()>0) { sb.append(", "); }
 723  0 sb.append(f.getName());
 724    }
 725  0 optionsProperties.setProperty(OptionConstants.CUSTOM_DRJAVA_JAR_VERSION_SUFFIX.getName(), sb.toString());
 726   
 727    // disable automatic updates
 728  0 optionsProperties.setProperty(OptionConstants.NEW_VERSION_ALLOWED.getName(), "false");
 729  0 optionsProperties.setProperty(OptionConstants.NEW_VERSION_NOTIFICATION.getName(),
 730    OptionConstants.VersionNotificationChoices.DISABLED);
 731   
 732    // and write updated options.properties into zip file
 733  0 zos.putNextEntry(new ZipEntry(OPTIONS_PROPERTIES_FILENAME));
 734  0 optionsProperties.store(zos, "Custom drjava.jar file generated "+new Date());
 735    }
 736   
 737    /** Name of the options.properties file. */
 738    public static final String OPTIONS_PROPERTIES_FILENAME =
 739    edu.rice.cs.drjava.DrJava.RESOURCE_BUNDLE_NAME.replace('.','/')+".properties";
 740   
 741    /** A predicate that skips the options.properties file. */
 742    public static final Predicate<String> NOT_OPTIONS_PROPERTIES = new Predicate<String>() {
 743  0 public boolean contains(String key) {
 744  0 return !key.equals(OPTIONS_PROPERTIES_FILENAME);
 745    }
 746    };
 747   
 748    /** A predicate that skips the manifest file. */
 749    public static final Predicate<String> NOT_MANIFEST = new Predicate<String>() {
 750  0 public boolean contains(String key) {
 751  0 return !key.equals("META-INF/MANIFEST.MF");
 752    }
 753    };
 754    }