Commits

Colin Gordon committed 70376f3

adding essentially finished project

Comments (0)

Files changed (67)

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="lib" path="jars/aws-java-sdk-1.2.15.jar"/>
+	<classpathentry kind="lib" path="jars/commons-cli-1.2.jar"/>
+	<classpathentry kind="lib" path="jars/commons-codec-1.3.jar"/>
+	<classpathentry kind="lib" path="jars/commons-io-1.4.jar"/>
+	<classpathentry kind="lib" path="jars/commons-lang-2.4.jar"/>
+	<classpathentry kind="lib" path="jars/forms-1.0.6.jar"/>
+	<classpathentry kind="lib" path="jars/httpclient-4.1.1.jar"/>
+	<classpathentry kind="lib" path="jars/httpcore-4.1.jar"/>
+	<classpathentry kind="lib" path="jars/jcl-over-slf4j-1.6.1.jar"/>
+	<classpathentry kind="lib" path="jars/joda-time-1.5.2.jar"/>
+	<classpathentry kind="lib" path="jars/looks-2.0.1.jar"/>
+	<classpathentry kind="lib" path="jars/slf4j-api-1.6.1.jar"/>
+	<classpathentry kind="lib" path="jars/slf4j-simple-1.6.1.jar"/>
+	<classpathentry kind="lib" path="/homes/gws/csgordon/research/guitypes/checker/dist/lib/guitypes-20120509.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>S3dropbox</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

.settings/org.eclipse.jdt.core.prefs

+#Wed Jan 11 20:17:04 PST 2012
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+#!/bin/bash
+export PATH=~/research/guitypes/checker/binary:$PATH
+
+
+# For some reason half the jars in the jar dir are unused.  Maybe some are used at runtime as
+# transitive dependencies of the linked JARs.
+JARS="/homes/gws/csgordon/research/experiments/S3dropbox/jars/aws-java-sdk-1.2.15.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-cli-1.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-io-1.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-lang-2.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/forms-1.0.6.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/joda-time-1.5.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-api-1.6.1.jar"
+#JARS="/homes/gws/csgordon/research/experiments/S3dropbox/jars/aws-java-sdk-1.2.15.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-cli-1.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-codec-1.3.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-io-1.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-lang-2.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/forms-1.0.6.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/httpclient-4.1.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/httpcore-4.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/jcl-over-slf4j-1.6.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/joda-time-1.5.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/looks-2.0.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-api-1.6.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-simple-1.6.1.jar"
+#CFJARS="/homes/gws/csgordon/research/guitypes/checker/checkers.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jdk.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+#CFJARS="/homes/gws/csgordon/research/guitypes/checker/checkers.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+CFJARS="/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+CHECKER=/homes/gws/csgordon/research/guitypes/checker/dist/lib/guitypes-`date "+%Y%m%d"`.jar
+DEBUG= #"-AprintErrorStack -Afilenames -Ashowchecks" #-Alint=debugSpew"
+
+#COMMAND="javac -J-Xbootclasspath/p:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar -J-Xms48m -J-cp -J$CFJARS -Xmaxerrs 999 -cp $CHECKER:$CFJARS:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+#COMMAND="javac -J-Xbootclasspath/p:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+#COMMAND="javac -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+COMMAND="javac -J-Xbootclasspath/p:$CFJARS -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+
+echo $COMMAND
+
+find . -name '*.java' | xargs $COMMAND
+
+
+#

jars/aws-java-sdk-1.2.15.jar

Binary file added.

jars/commons-cli-1.2.jar

Binary file added.

jars/commons-codec-1.3.jar

Binary file added.

jars/commons-io-1.4.jar

Binary file added.

jars/commons-lang-2.4.jar

Binary file added.

jars/forms-1.0.6.jar

Binary file added.

jars/httpclient-4.1.1.jar

Binary file added.

jars/httpcore-4.1.jar

Binary file added.

jars/jcl-over-slf4j-1.6.1.jar

Binary file added.

jars/joda-time-1.5.2.jar

Binary file added.

jars/looks-2.0.1.jar

Binary file added.

jars/slf4j-api-1.6.1.jar

Binary file added.

jars/slf4j-simple-1.6.1.jar

Binary file added.
+#!/bin/bash
+export PATH=~/research/guitypes/checker/binary:$PATH
+
+
+# For some reason half the jars in the jar dir are unused.  Maybe some are used at runtime as
+# transitive dependencies of the linked JARs.
+JARS="/homes/gws/csgordon/research/experiments/S3dropbox/jars/aws-java-sdk-1.2.15.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-cli-1.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-io-1.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-lang-2.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/forms-1.0.6.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/joda-time-1.5.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-api-1.6.1.jar"
+#JARS="/homes/gws/csgordon/research/experiments/S3dropbox/jars/aws-java-sdk-1.2.15.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-cli-1.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-codec-1.3.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-io-1.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/commons-lang-2.4.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/forms-1.0.6.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/httpclient-4.1.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/httpcore-4.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/jcl-over-slf4j-1.6.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/joda-time-1.5.2.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/looks-2.0.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-api-1.6.1.jar:/homes/gws/csgordon/research/experiments/S3dropbox/jars/slf4j-simple-1.6.1.jar"
+#CFJARS="/homes/gws/csgordon/research/guitypes/checker/checkers.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jdk.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+#CFJARS="/homes/gws/csgordon/research/guitypes/checker/checkers.jar:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+CFJARS="/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar"
+CHECKER=/homes/gws/csgordon/research/guitypes/checker/dist/lib/guitypes-`date "+%Y%m%d"`.jar
+DEBUG= #"-AprintErrorStack -Afilenames -Ashowchecks" #-Alint=debugSpew"
+
+#COMMAND="javac -J-Xbootclasspath/p:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar -J-Xms48m -J-cp -J$CFJARS -Xmaxerrs 999 -cp $CHECKER:$CFJARS:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+#COMMAND="javac -J-Xbootclasspath/p:/homes/gws/csgordon/research/guitypes/checker/binary/jsr308-all.jar -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+#COMMAND="javac -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+COMMAND="javac -J-Xbootclasspath/p:$CFJARS -Xmaxerrs 999 -cp $CHECKER:$JARS -processor guitypes.checkers.GUIEffectsChecker $DEBUG"
+
+echo $COMMAND
+
+find . -name '*.java' | xargs $COMMAND
+
+
+#

src/com/tomczarniecki/s3/CliMain.java

+/*
+ * Copyright (c) 2011, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+import com.tomczarniecki.s3.rest.Configuration;
+import com.tomczarniecki.s3.rest.ConfigurationFactory;
+import com.tomczarniecki.s3.rest.WebClientService;
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.File;
+import java.util.List;
+
+public class CliMain {
+
+    private final String[] args;
+
+    public CliMain(String[] args) {
+        this.args = args;
+    }
+
+    public void main() {
+        boolean success = mainWithoutExit();
+        System.exit(success ? 0 : 1);
+    }
+
+    public boolean mainWithoutExit() {
+        Options options = createOptions();
+        Service service = null;
+        try {
+            CommandLineParser parser = new BasicParser();
+            CommandLine cmd = parser.parse(options, args);
+
+            if (cmd.hasOption("help")) {
+                showHelp(options);
+            } else {
+                service = new WebClientService(loadConfiguration(cmd));
+                invokeService(service, cmd);
+            }
+            return true;
+
+        } catch (ParseException e) {
+            System.out.println(e.getMessage());
+            showHelp(options);
+            return false;
+
+        } finally {
+            if (service != null) {
+                service.close();
+            }
+        }
+    }
+
+    private void invokeService(Service service, CommandLine cmd) throws ParseException {
+        if (cmd.hasOption("list")) {
+            listAction(service, cmd);
+        } else {
+            fileAction(service, cmd);
+        }
+    }
+
+    private void listAction(Service service, CommandLine cmd) {
+        String bucketName = cmd.getOptionValue("bucket", "*");
+        if (bucketName.equals("*")) {
+            List<S3Bucket> buckets = service.listAllMyBuckets();
+            System.out.println("\nListing " + buckets.size() + " buckets:");
+            for (S3Bucket bucket : buckets) {
+                System.out.println(bucket.getName());
+            }
+        } else {
+            FileSize size = new FileSize();
+            List<S3Object> objects = service.listObjectsInBucket(bucketName);
+            System.out.println("\nListing " + objects.size() + " objects in " + bucketName + ":");
+            for (S3Object object : objects) {
+                String objectSize = StringUtils.rightPad(size.format(object.getSize()), 10);
+                System.out.println(objectSize + " " + object.getKey());
+            }
+        }
+    }
+
+    private void fileAction(Service service, CommandLine cmd) throws ParseException {
+        String bucket = getRequiredOption(cmd, "bucket");
+        String object = getRequiredOption(cmd, "object");
+        File file = new File(getRequiredOption(cmd, "file"));
+
+        if (cmd.hasOption("get")) {
+            System.out.printf("Downloading %s from bucket %s to %s ...\n", object, bucket, file);
+            service.downloadObject(bucket, object, file, new NullProgressListener());
+
+        } else if (cmd.hasOption("put")) {
+            System.out.printf("Uploading %s to bucket %s as %s ...\n", file, bucket, object);
+            service.createObject(bucket, object, file, new NullProgressListener());
+        }
+        System.out.println("... Done");
+    }
+
+    private String getRequiredOption(CommandLine cmd, String key) throws ParseException {
+        String value = cmd.getOptionValue(key);
+        if (value == null) {
+            throw new ParseException(key + " is required!");
+        }
+        return value;
+    }
+
+    private Configuration loadConfiguration(CommandLine cmd) {
+        File confFile = cmd.hasOption("conf") ? new File(cmd.getOptionValue("conf")) : null;
+        ConfigurationFactory factory = new ConfigurationFactory(confFile);
+        return factory.load();
+    }
+
+    private void showHelp(Options options) {
+        HelpFormatter formatter = new HelpFormatter();
+        formatter.printHelp("s3dropbox", options);
+    }
+
+    private Options createOptions() {
+        Option bucket = new Option("bucket", true, "Name of S3 bucket");
+        bucket.setArgName("NAME");
+
+        Option object = new Option("object", true, "Name of S3 object");
+        object.setArgName("NAME");
+
+        Option file = new Option("file", true, "File to upload or download");
+        file.setArgName("FILE");
+
+        Option config = new Option("conf", true, "Configuration properties file");
+        config.setArgName("FILE");
+
+        OptionGroup group = new OptionGroup();
+        group.addOption(new Option("put", "Upload file to S3"));
+        group.addOption(new Option("get", "Download file from S3"));
+        group.addOption(new Option("list", "List buckets or objects in a bucket"));
+        group.setRequired(true);
+
+        Options options = new Options();
+        options.addOption("help", false, "Print this message");
+        options.addOption("cli", false, "Run in command line mode");
+        options.addOptionGroup(group);
+        options.addOption(config);
+        options.addOption(bucket);
+        options.addOption(object);
+        options.addOption(file);
+        return options;
+    }
+
+    private static class NullProgressListener implements ProgressListener {
+        public void processed(long count, long length) {
+        }
+    }
+}

src/com/tomczarniecki/s3/FileSize.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+import org.apache.commons.io.FileUtils;
+
+import java.text.DecimalFormat;
+import java.util.List;
+
+import static com.tomczarniecki.s3.Generics.newArrayList;
+import static com.tomczarniecki.s3.Pair.pair;
+
+public class FileSize {
+
+    private final List<Pair<String, Long>> sizes;
+    private final DecimalFormat format;
+
+    public FileSize() {
+        sizes = newArrayList();
+        sizes.add(pair("GB", FileUtils.ONE_GB));
+        sizes.add(pair("MB", FileUtils.ONE_MB));
+        sizes.add(pair("KB", FileUtils.ONE_KB));
+        format = new DecimalFormat(",##0.0");
+    }
+
+    public String format(long size) {
+        for (Pair<String, Long> entry : sizes) {
+            if (size / entry.getValue() > 0) {
+                double value = ((double) size) / ((double) entry.getValue());
+                return format.format(value) + " " + entry.getKey();
+            }
+        }
+        return size + " bytes";
+    }
+}

src/com/tomczarniecki/s3/Generics.java

+/*
+ * Copyright (c) 2011, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Generics {
+
+    public static <T> List<T> newArrayList() {
+        return new ArrayList<T>();
+    }
+
+    public static <K, V> Map<K, V> newHashMap() {
+        return new HashMap<K, V>();
+    }
+}

src/com/tomczarniecki/s3/GuiMain.java

+/*
+ * Copyright (c) 2011, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+import com.tomczarniecki.s3.gui.CredentialsDialog;
+import com.tomczarniecki.s3.gui.DropBox;
+import com.tomczarniecki.s3.rest.Configuration;
+import com.tomczarniecki.s3.rest.ConfigurationFactory;
+import com.tomczarniecki.s3.rest.WebClientService;
+import org.apache.commons.lang.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import guitypes.checkers.quals.*;
+
+@UIType @UI public class GuiMain implements @UI Runnable {
+
+    private final Logger logger = LoggerFactory.getLogger(Main.class);
+
+    private final String[] args;
+
+    public GuiMain(String[] args) {
+        this.args = args;
+    }
+
+    @SafeEffect public void main() {
+        setupLookAndFeel();
+        SwingUtilities.invokeLater(this);
+    }
+
+    public void run() {
+        File file = (args.length > 0) ? new File(args[0]) : null;
+        Configuration credentials = getCredentials(file);
+        DropBox box = new DropBox(new WebClientService(credentials));
+        box.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        box.addWindowListener(new WindowCloseListener());
+        box.showBuckets();
+    }
+
+    @SafeEffect private void setupLookAndFeel() {
+        if (SystemUtils.IS_OS_WINDOWS) {
+            try {
+                UIManager.setLookAndFeel("com.jgoodies.looks.plastic.PlasticXPLookAndFeel");
+            } catch (Exception e) {
+                // not to worry, can still use platform default L&F
+            }
+        }
+    }
+
+    private Configuration getCredentials(File file) {
+        ConfigurationFactory factory = new ConfigurationFactory(file);
+        try {
+            return factory.load();
+        } catch (Exception e) {
+            logger.warn("Cannot load credentials", e);
+            return createCredentials(factory);
+        }
+    }
+
+    private Configuration createCredentials(ConfigurationFactory factory) {
+        CredentialsDialog dialog = new CredentialsDialog();
+        Configuration credentials = dialog.getCredentials();
+        if (credentials == null) {
+            System.exit(1);
+        }
+        factory.save(credentials);
+        return credentials;
+    }
+
+    @UIType private static class WindowCloseListener extends WindowAdapter {
+
+        @Override
+        public void windowClosing(WindowEvent windowEvent) {
+            System.exit(0);
+        }
+    }
+}

src/com/tomczarniecki/s3/Main.java

+/*
+ * Copyright (c) 2009, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+import org.apache.commons.lang.ArrayUtils;
+
+public class Main {
+
+    public static void main(final String... args) {
+        if (ArrayUtils.contains(args, "-cli")) {
+            new CliMain(args).main();
+        } else {
+            new GuiMain(args).main();
+        }
+    }
+}

src/com/tomczarniecki/s3/Pair.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+public class Pair<K, V> {
+
+    private final K key;
+    private final V value;
+
+    public Pair(K key, V value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public static <K, V> Pair<K, V> pair(K key, V value) {
+        return new Pair<K, V>(key, value);
+    }
+
+    public K getKey() {
+        return key;
+    }
+
+    public V getValue() {
+        return value;
+    }
+}
+

src/com/tomczarniecki/s3/ProgressListener.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *  Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ * the names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3;
+
+public interface ProgressListener {
+    void processed(long count, long length);
+}

src/com/tomczarniecki/s3/S3Bucket.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3;
+
+public class S3Bucket implements Comparable<S3Bucket> {
+
+    private final String name;
+
+    public S3Bucket(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int compareTo(S3Bucket other) {
+        return this.name.compareToIgnoreCase(other.name);
+    }
+
+    public String toString() {
+        return String.format("Bucket[%s]", name);
+    }
+}

src/com/tomczarniecki/s3/S3Object.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3;
+
+import org.joda.time.LocalDateTime;
+
+public class S3Object implements Comparable<S3Object> {
+
+    private final String key;
+    private final long size;
+    private final LocalDateTime lastModified;
+
+    public S3Object(String key, long size, LocalDateTime lastModified) {
+        this.key = key;
+        this.size = size;
+        this.lastModified = lastModified;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public LocalDateTime getLastModified() {
+        return lastModified;
+    }
+
+    public int compareTo(S3Object other) {
+        return this.key.compareToIgnoreCase(other.key);
+    }
+
+    public String toString() {
+        return String.format("S3Object[%s,%d,%s]", key, size, lastModified.toString());
+    }
+}

src/com/tomczarniecki/s3/Service.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3;
+
+import org.joda.time.DateTime;
+
+import java.io.File;
+import java.util.List;
+
+public interface Service {
+
+    List<String> bucketRegions();
+
+    List<S3Bucket> listAllMyBuckets();
+
+    boolean bucketExists(String bucketName);
+
+    void createBucket(String bucketName, String region);
+
+    void deleteBucket(String bucketName);
+
+    List<S3Object> listObjectsInBucket(String bucketName);
+
+    boolean objectExists(String bucketName, String objectKey);
+
+    String getPublicUrl(String bucketName, String objectKey, DateTime expires);
+
+    void createObject(String bucketName, String objectKey, File source, ProgressListener listener);
+
+    void downloadObject(String bucketName, String objectKey, File target, ProgressListener listener);
+
+    void deleteObject(String bucketName, String objectKey);
+
+    void removeFailedUploads(String bucketName);
+
+    void close();
+}

src/com/tomczarniecki/s3/gui/Announcer.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+
+import static com.tomczarniecki.s3.Generics.newArrayList;
+
+class Announcer<T> {
+
+    private final List<T> targets = newArrayList();
+    private final T proxy;
+
+    private Announcer(Class<T> type) {
+        this.proxy = proxyFor(type);
+    }
+
+    public static <T> Announcer<T> createFor(Class<T> type) {
+        return new Announcer<T>(type);
+    }
+
+    public void add(T target) {
+        targets.add(target);
+    }
+
+    public T announce() {
+        return proxy;
+    }
+
+    private T proxyFor(Class<T> type) {
+        Class[] proxyInterfaces = {type};
+        ClassLoader classLoader = type.getClassLoader();
+        return type.cast(Proxy.newProxyInstance(classLoader, proxyInterfaces, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                for (T target : targets) {
+                    method.invoke(target, args);
+                }
+                return null;
+            }
+        }));
+    }
+}

src/com/tomczarniecki/s3/gui/BucketNameValidator.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.amazonaws.services.s3.internal.BucketNameUtils;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.regex.Pattern;
+
+class BucketNameValidator {
+
+    private final Controller controller;
+    private final Pattern validNamePattern;
+    private final Pattern ipAddressPattern;
+    private final BucketNameUtils names;
+
+    public BucketNameValidator(Controller controller) {
+        this.validNamePattern = Pattern.compile("^[a-z0-9][a-z0-9\\.\\-]+?[a-z0-9]$");
+        this.ipAddressPattern = Pattern.compile("^\\d+\\.\\d+\\.\\d+.\\d+$");
+        this.names = new BucketNameUtils();
+        this.controller = controller;
+    }
+
+    public String validate(String bucketName) {
+        if (!validNamePattern.matcher(bucketName).matches()) {
+            return "Folder name can only contain lowercase letters, digits, periods\n" +
+                    "and dashes, and must start and end with a letter or a digit.";
+        }
+        if (ipAddressPattern.matcher(bucketName).matches()) {
+            return "Folder name cannot be in an IP address format.";
+        }
+        try {
+            names.validateBucketName(bucketName);
+        } catch (IllegalArgumentException e) {
+            return StringUtils.replace(e.getMessage(), "Bucket", "Folder");
+        }
+        if (controller.bucketExists(bucketName)) {
+            return "You cannot use this folder name since someone else is already using it.";
+        }
+        return null;
+    }
+}

src/com/tomczarniecki/s3/gui/BusyCursorExecutor.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3.gui;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.Executor;
+import guitypes.checkers.quals.*;
+
+class BusyCursorExecutor implements Executor {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Display display;
+    private final Worker worker;
+
+    public BusyCursorExecutor(Display display, Worker worker) {
+        this.display = display;
+        this.worker = worker;
+    }
+
+    public void execute(final Runnable command) {
+        worker.executeInBackground(new Runnable() {
+            public void run() {
+                String error = null;
+                setCursor(true);
+                try {
+                    command.run();
+
+                } catch (Exception e) {
+                    logger.warn("Task failed", e);
+                    error = e.toString();
+
+                } finally {
+                    setCursor(false);
+                }
+                if (error != null) {
+                    showError(error);
+                }
+            }
+        });
+    }
+
+    private void setCursor(final boolean busy) {
+        worker.executeOnEventLoop(new @UI Runnable() {
+            public void run() {
+                if (busy) {
+                    display.showBusyCursor();
+                } else {
+                    display.showNormalCursor();
+                }
+            }
+        });
+    }
+
+    private void showError(final String error) {
+        worker.executeOnEventLoop(new @UI Runnable() {
+            public void run() {
+                display.showErrorMessage("Oops", "Something bad has happended.\n" +
+                        "Any saves, updates or deletes may be incomplete.\n" +
+                        "Error is: " + error + "\n" +
+                        "Please wait a bit and try again.\n");
+            }
+        });
+    }
+}

src/com/tomczarniecki/s3/gui/Constants.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+public class Constants {
+
+    public static final String FOLDER_NAME = "S3 Drop Box [%s]";
+    public static final String ALL_FOLDERS = "all my folders";
+    public static final String BACK_LINK = "..";
+
+    public static final String MAIN_WINDOW_NAME = "dropBoxMainWindow";
+    public static final String MAIN_TABLE_NAME = "dropBoxMainTable";
+}

src/com/tomczarniecki/s3/gui/Controller.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.tomczarniecki.s3.ProgressListener;
+import com.tomczarniecki.s3.S3Bucket;
+import com.tomczarniecki.s3.S3Object;
+import com.tomczarniecki.s3.Service;
+import org.joda.time.DateTime;
+
+import java.io.File;
+import java.util.List;
+
+class Controller {
+
+    private final Announcer<ControllerListener> announcer;
+    private final Service service;
+
+    private boolean showingObjects;
+    private String selectedBucketName;
+    private String selectedObjectKey;
+
+    public Controller(Service service) {
+        this.announcer = Announcer.createFor(ControllerListener.class);
+        this.service = service;
+    }
+
+    public void addListener(ControllerListener listener) {
+        announcer.add(listener);
+    }
+
+    public void showBuckets() {
+        List<S3Bucket> buckets = service.listAllMyBuckets();
+        selectedBucketName = null;
+        selectedObjectKey = null;
+        showingObjects = false;
+        announcer.announce().updatedBuckets(buckets);
+    }
+
+    public void showObjects() {
+        List<S3Object> objects = service.listObjectsInBucket(selectedBucketName);
+        selectedObjectKey = null;
+        showingObjects = true;
+        announcer.announce().updatedObjects(selectedBucketName, objects);
+    }
+
+    public void updateSelectedName(String name) {
+        if (showingObjects) {
+            selectedObjectKey = name;
+        } else {
+            selectedBucketName = name;
+        }
+    }
+
+    public void selectBucket(String bucketName) {
+        selectedBucketName = bucketName;
+    }
+
+    public String getSelectedBucketName() {
+        return selectedBucketName;
+    }
+
+    public String getSelectedObjectKey() {
+        return selectedObjectKey;
+    }
+
+    public List<String> bucketRegions() {
+        return service.bucketRegions();
+    }
+
+    public boolean bucketExists(String bucketName) {
+        return service.bucketExists(bucketName);
+    }
+
+    public void createBucket(String bucketName, String region) {
+        service.createBucket(bucketName, region);
+        showBuckets();
+    }
+
+    public void deleteCurrentBucket() {
+        service.deleteBucket(selectedBucketName);
+        showBuckets();
+    }
+
+    public boolean objectExists(String bucketName, String objectKey) {
+        return service.objectExists(bucketName, objectKey);
+    }
+
+    public void createObject(String bucketName, String objectKey, File source, ProgressListener listener) {
+        service.createObject(bucketName, objectKey, source, listener);
+        showObjects();
+    }
+
+    public void deleteCurrentObject() {
+        service.deleteObject(selectedBucketName, selectedObjectKey);
+        showObjects();
+    }
+
+    public void downloadCurrentObject(File target, ProgressListener listener) {
+        service.downloadObject(selectedBucketName, selectedObjectKey, target, listener);
+    }
+
+    public String getPublicUrlForCurrentObject(DateTime expires) {
+        return service.getPublicUrl(selectedBucketName, selectedObjectKey, expires);
+    }
+
+    public boolean isShowingObjects() {
+        return showingObjects;
+    }
+
+    public boolean isBucketSelected() {
+        return selectedBucketName != null;
+    }
+
+    public boolean isObjectSelected() {
+        return (selectedObjectKey != null) && !isBackLinkSelected();
+    }
+
+    public boolean canShowBuckets() {
+        return showingObjects && isBackLinkSelected();
+    }
+
+    public boolean canShowObjects() {
+        return !showingObjects && isBucketSelected();
+    }
+
+    private boolean isBackLinkSelected() {
+        return Constants.BACK_LINK.equals(selectedObjectKey);
+    }
+
+    public List<S3Bucket> listAllMyBuckets() {
+        return service.listAllMyBuckets();
+    }
+
+    public void removeFailedUploads(String bucketName) {
+        service.removeFailedUploads(bucketName);
+    }
+}

src/com/tomczarniecki/s3/gui/ControllerListener.java

+/*
+ * Copyright (c) 2010, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.tomczarniecki.s3.S3Bucket;
+import com.tomczarniecki.s3.S3Object;
+
+import java.util.List;
+
+interface ControllerListener {
+
+    void updatedBuckets(List<S3Bucket> buckets);
+
+    void updatedObjects(String bucketName, List<S3Object> objects);
+}

src/com/tomczarniecki/s3/gui/CreateBucketAction.java

+/*
+ * Copyright (c) 2011, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.tomczarniecki.s3.Pair;
+
+import javax.swing.AbstractAction;
+import java.awt.event.ActionEvent;
+import java.util.concurrent.Executor;
+import guitypes.checkers.quals.*;
+
+@UIType class CreateBucketAction extends AbstractAction {
+
+    private final BucketNameValidator validator;
+    private final CreateBucketDialog dialog;
+    private final Controller controller;
+    private final Executor executor;
+    private final Display display;
+
+    public CreateBucketAction(Controller controller, Display display, Executor executor) {
+        super("Create Folder");
+        this.dialog = display.createBucketDialog(controller.bucketRegions());
+        this.validator = new BucketNameValidator(controller);
+        this.controller = controller;
+        this.executor = executor;
+        this.display = display;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        Pair<String, String> bucketNameAndRegion = getBucketNameAndRegion();
+        if (bucketNameAndRegion != null) {
+            createBucket(bucketNameAndRegion.getKey(), bucketNameAndRegion.getValue());
+        }
+    }
+
+    private Pair<String, String> getBucketNameAndRegion() {
+        Pair<String, String> bucketNameAndRegion = null;
+        while (true) {
+            bucketNameAndRegion = dialog.get(bucketNameAndRegion);
+            if (bucketNameAndRegion == null) {
+                return null;
+            }
+            String errorMessage = validator.validate(bucketNameAndRegion.getKey());
+            if (errorMessage == null) {
+                return bucketNameAndRegion;
+            }
+            if (!display.confirmMessage("Oops", errorMessage + "\nDo you want to try again?")) {
+                return null;
+            }
+        }
+    }
+
+    private void createBucket(final String bucketName, final String region) {
+        executor.execute(new Runnable() {
+            public void run() {
+                controller.createBucket(bucketName, region);
+            }
+        });
+    }
+}

src/com/tomczarniecki/s3/gui/CreateBucketDialog.java

+/*
+ * Copyright (c) 2011, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.jgoodies.forms.builder.ButtonBarBuilder;
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+import com.tomczarniecki.s3.Pair;
+import org.apache.commons.lang.StringUtils;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+import static com.tomczarniecki.s3.Pair.pair;
+
+import guitypes.checkers.quals.*;
+
+@UIType
+public class CreateBucketDialog extends JDialog {
+
+    private final JTextField bucketName;
+    private final JComboBox bucketRegion;
+
+    private boolean createBucket;
+
+    public CreateBucketDialog(List<String> regions) {
+        setTitle("Create Folder");
+        setModal(true);
+
+        bucketName = new JTextField();
+        bucketRegion = new JComboBox(regions.toArray());
+
+        getContentPane().add(createDisplayPanel());
+        setResizable(false);
+        pack();
+    }
+
+    public Pair<String, String> get(Pair<String, String> bucketNameAndRegion) {
+        setValues(bucketNameAndRegion);
+        setLocationRelativeTo(null);
+        setVisible(true);
+        return result();
+    }
+
+    private JPanel createDisplayPanel() {
+        CellConstraints cc = new CellConstraints();
+        String cols = "pref,5dlu,100dlu";
+        String rows = "pref,5dlu,pref,5dlu,pref";
+
+        PanelBuilder builder = new PanelBuilder(new FormLayout(cols, rows));
+        builder.setDefaultDialogBorder();
+        builder.addLabel("Folder Name", cc.xy(1, 1));
+        builder.add(bucketName, cc.xy(3, 1));
+        builder.addLabel("S3 Region", cc.xy(1, 3));
+        builder.add(bucketRegion, cc.xy(3, 3));
+        builder.add(createButtonBar(), cc.xyw(1, 5, 3));
+
+        return builder.getPanel();
+    }
+
+    private JPanel createButtonBar() {
+        JButton createButton = new JButton("Create");
+        createButton.addActionListener(new CreateAction());
+
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(new CancelAction());
+
+        ButtonBarBuilder buttonBar = ButtonBarBuilder.createLeftToRightBuilder();
+        buttonBar.addGlue();
+        buttonBar.addGridded(createButton);
+        buttonBar.addRelatedGap();
+        buttonBar.addGridded(cancelButton);
+        return buttonBar.getPanel();
+    }
+
+    private void setValues(Pair<String, String> bucketNameAndRegion) {
+        createBucket = false;
+        if (bucketNameAndRegion != null) {
+            this.bucketName.setText(bucketNameAndRegion.getKey());
+            this.bucketRegion.setSelectedItem(bucketNameAndRegion.getValue());
+        }
+    }
+
+    private Pair<String, String> result() {
+        if (createBucket) {
+            String name = StringUtils.trimToEmpty(bucketName.getText());
+            String region = (String) bucketRegion.getSelectedItem();
+            return pair(name, region);
+        }
+        return null;
+    }
+
+    @UIType private class CreateAction implements ActionListener {
+        public void actionPerformed(ActionEvent e) {
+            createBucket = true;
+            setVisible(false);
+        }
+    }
+
+    @UIType private class CancelAction implements ActionListener {
+        public void actionPerformed(ActionEvent e) {
+            createBucket = false;
+            setVisible(false);
+        }
+    }
+}

src/com/tomczarniecki/s3/gui/CreatePublicLinkAction.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3.gui;
+
+import javax.swing.AbstractAction;
+import java.awt.event.ActionEvent;
+import guitypes.checkers.quals.*;
+
+@UIType class CreatePublicLinkAction extends AbstractAction {
+
+    private final PublicLinkDialog dialog;
+    private final Controller controller;
+
+    public CreatePublicLinkAction(Controller controller, Display display) {
+        super("Create Public Link");
+        this.dialog = display.createPublicLinkDialog(controller);
+        this.controller = controller;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        if (controller.isObjectSelected()) {
+            dialog.display();
+        }
+    }
+}

src/com/tomczarniecki/s3/gui/CredentialsDialog.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3.gui;
+
+import com.jgoodies.forms.builder.ButtonBarBuilder;
+import com.jgoodies.forms.builder.PanelBuilder;
+import com.jgoodies.forms.layout.CellConstraints;
+import com.jgoodies.forms.layout.FormLayout;
+import com.tomczarniecki.s3.rest.Configuration;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+import static org.apache.commons.lang.StringUtils.trimToEmpty;
+import guitypes.checkers.quals.*;
+
+@UIType public class CredentialsDialog extends JDialog {
+
+    private final JTextField accessKeyId;
+    private final JTextField secretAccessKey;
+
+    private Configuration credentials;
+
+    public CredentialsDialog() {
+        setTitle("Amazon S3 Credentials");
+        setModal(true);
+
+        accessKeyId = new JTextField();
+        secretAccessKey = new JTextField();
+
+        getContentPane().add(createDisplayPanel());
+        setResizable(false);
+        pack();
+    }
+
+    public Configuration getCredentials() {
+        setLocationRelativeTo(null);
+        secretAccessKey.setText("");
+        accessKeyId.setText("");
+        credentials = null;
+        setVisible(true);
+        return credentials;
+    }
+
+    private JPanel createDisplayPanel() {
+        CellConstraints cc = new CellConstraints();
+        String cols = "pref,5dlu,200dlu";
+        String rows = "pref,5dlu,pref,5dlu,pref";
+
+        PanelBuilder builder = new PanelBuilder(new FormLayout(cols, rows));
+        builder.setDefaultDialogBorder();
+        builder.addLabel("Access Key ID", cc.xy(1, 1));
+        builder.add(accessKeyId, cc.xy(3, 1));
+        builder.addLabel("Secret Access Key", cc.xy(1, 3));
+        builder.add(secretAccessKey, cc.xy(3, 3));
+        builder.add(createButtonBar(), cc.xyw(1, 5, 3));
+
+        return builder.getPanel();
+    }
+
+    private JPanel createButtonBar() {
+        JButton createButton = new JButton("Save");
+        createButton.addActionListener(new SaveAction());
+
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(new CancelAction());
+
+        ButtonBarBuilder buttonBar = ButtonBarBuilder.createLeftToRightBuilder();
+        buttonBar.addGlue();
+        buttonBar.addGridded(createButton);
+        buttonBar.addRelatedGap();
+        buttonBar.addGridded(cancelButton);
+        return buttonBar.getPanel();
+    }
+
+    private void createCredentials() {
+        String key = trimToEmpty(accessKeyId.getText());
+        String secret = trimToEmpty(secretAccessKey.getText());
+        if (isNotEmpty(key) && isNotEmpty(secret)) {
+            credentials = new Configuration(key, secret);
+        }
+    }
+
+    @UIType private class SaveAction implements ActionListener {
+        public void actionPerformed(ActionEvent e) {
+            createCredentials();
+            setVisible(false);
+        }
+    }
+
+    @UIType private class CancelAction implements ActionListener {
+        public void actionPerformed(ActionEvent e) {
+            setVisible(false);
+        }
+    }
+}

src/com/tomczarniecki/s3/gui/DeleteBucketAction.java

+/* ===================================================================================
+ * Copyright (c) 2008, Thomas Czarniecki
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *  * Neither the name of S3DropBox, Thomas Czarniecki, tomczarniecki.com nor
+ *    the names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ===================================================================================
+ */
+package com.tomczarniecki.s3.gui;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.AbstractAction;
+import java.awt.event.ActionEvent;
+import java.util.concurrent.Executor;
+import guitypes.checkers.quals.*;
+
+@UIType class DeleteBucketAction extends AbstractAction {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final Controller controller;
+    private final Executor executor;
+    private final Display display;
+
+    public DeleteBucketAction(Controller controller, Display display, Executor executor) {
+        super("Delete Folder");
+        this.controller = controller;
+        this.executor = executor;
+        this.display = display;
+    }
+
+    public void actionPerformed(ActionEvent evt) {
+        if (controller.isBucketSelected() && confirmDeletion()) {
+            deleteBucket();
+        }
+    }
+
+    private void deleteBucket() {
+        executor.execute(new Runnable() {
+            public void run() {
+                try {
+                    controller.deleteCurrentBucket();
+                } catch (Exception e) {
+                    logger.info("Delete failed", e);
+                    deleteError(); // Colin Gordon: directly access GUI, but this is run on a fresh thread!
+                }
+            }
+        });
+    }
+
+    private boolean confirmDeletion() {
+        String text = "Are you sure that you want to delete folder %s?\nYou will not be able to undo this action.";
+        return display.confirmMessage("Just Checking", String.format(text, controller.getSelectedBucketName()));
+    }
+
+    private void deleteError() {
+        String text = "Cannot delete folder %s.\nPlease make sure that it is empty and try again.";
+        display.showErrorMessage("Delete failed", String.format(text, controller.getSelectedBucketName()));
+    }
+}