Skip to content

Commit fafc29d

Browse files
Ian OrtonKannan Goundan
authored andcommitted
Add a common builder interface for upload- and download-style methods.
1 parent dbbcdfa commit fafc29d

7 files changed

Lines changed: 324 additions & 25 deletions

File tree

examples/upload-file/src/com/dropbox/core/examples/upload_file/Main.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import com.dropbox.core.DbxRequestConfig;
66
import com.dropbox.core.DbxWebAuth;
77
import com.dropbox.core.json.JsonReader;
8-
import com.dropbox.core.util.IOUtil;
98
import com.dropbox.core.v2.DbxClientV2;
109
import com.dropbox.core.v2.DbxPathV2;
1110
import com.dropbox.core.v2.Files;
@@ -84,11 +83,13 @@ private static int _main(String[] args)
8483

8584
// Make the API call to upload the file.
8685
Files.FileMetadata metadata;
87-
InputStream in = new FileInputStream(localPath);
8886
try {
89-
Files.UploadUploader uploader = dbxClient.files.upload(dropboxPath);
90-
IOUtil.copyStreamToStream(in, uploader.getBody());
91-
metadata = uploader.finish();
87+
InputStream in = new FileInputStream(localPath);
88+
try {
89+
metadata = dbxClient.files.uploadBuilder(dropboxPath).run(in);
90+
} finally {
91+
in.close();
92+
}
9293
}
9394
catch (Files.UploadException ex) {
9495
System.out.println("Error uploading to Dropbox: " + ex.getMessage());
@@ -102,9 +103,6 @@ private static int _main(String[] args)
102103
System.out.println("Error reading from file \"" + localPath + "\": " + ex.getMessage());
103104
return 1;
104105
}
105-
finally {
106-
IOUtil.closeInput(in);
107-
}
108106

109107
System.out.print(metadata.toStringMultiline());
110108
return 0;

examples/web-file-browser/src/com/dropbox/core/examples/web_file_browser/DropboxBrowse.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,7 @@ public void doUpload(HttpServletRequest request, HttpServletResponse response)
264264
String fullTargetPath = targetFolder + "/" + fileName;
265265
Files.FileMetadata metadata;
266266
try {
267-
Files.UploadUploader uploader = dbxClient.files.upload(fullTargetPath);
268-
IOUtil.copyStreamToStream(filePart.getInputStream(), uploader.getBody());
269-
metadata = uploader.finish();
267+
metadata = dbxClient.files.uploadBuilder(fullTargetPath).run(filePart.getInputStream());
270268
}
271269
catch (DbxException ex) {
272270
common.handleDbxException(response, user, ex, "upload(" + jq(fullTargetPath) + ", ...)");

generator/java.babelg.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,29 @@ def generate_builder(self, namespace, route, rtype, ret, outer):
504504
out = self.emit
505505
method_name = camelcase(route.name)
506506
exc_name = classname(route.name + '_exception')
507-
self.generate_doc('The builder object for {@link #%s}' % method_name)
508507
builder_name = classname(method_name + 'Builder')
509-
out('public final class %s' % builder_name)
508+
style = route.attrs.get('style', 'rpc')
509+
if style == 'upload':
510+
self.generate_doc(
511+
'The {@link com.dropbox.core.v2.DbxUploadStyleBuilder} '
512+
'returned by {@link #%s}.' % builder_name)
513+
result_name = maptype(namespace, route.response_data_type)
514+
error_name = maptype(namespace, route.error_data_type)
515+
resname = 'Object' if result_name == 'void' else result_name
516+
errname = 'Object' if error_name == 'void' else error_name
517+
out('public final class %s extends DbxUploadStyleBuilder<%s,%s,%s>' %
518+
(builder_name, resname, errname, exc_name))
519+
elif style == 'download':
520+
self.generate_doc(
521+
'The {@link com.dropbox.core.v2.DbxDownloadStyleBuilder} '
522+
'returned by {@link #%s}.' % builder_name)
523+
result_name = maptype(namespace, route.response_data_type)
524+
resname = 'Object' if result_name == 'void' else result_name
525+
out('public final class %s extends DbxDownloadStyleBuilder<%s>' %
526+
(builder_name, resname))
527+
else:
528+
self.generate_doc('The builder object returned by {@link #%s}' % builder_name)
529+
out('public final class %s' % builder_name)
510530
with self.block():
511531
# Generate a field for every argument.
512532
all_args = [
@@ -538,8 +558,8 @@ def generate_builder(self, namespace, route, rtype, ret, outer):
538558
with self.block():
539559
out('this.%s = %s;' % (arg_name, arg_name))
540560
out('return this;')
541-
# Create a run() method to use the builder.
542-
out('public %s run() throws %s, DbxException' % (rtype, exc_name))
561+
# Create a start() method to use the builder.
562+
out('public %s start() throws %s, DbxException' % (rtype, exc_name))
543563
with self.block():
544564
packed_class = maptype(namespace, route.request_data_type)
545565
prefix = '%s.this.' % outer
@@ -562,22 +582,24 @@ def generate_route_stuff(self, namespace, route, outer):
562582
if style == 'upload':
563583
rtype = classname(route.name + '_uploader')
564584
ret = 'return '
585+
self.generate_builder(namespace, route, rtype, ret, outer)
565586
elif style == 'download':
566587
rtype = 'com.dropbox.core.DbxDownloader<%s>' % result_name
567588
ret = 'return '
589+
self.generate_builder(namespace, route, rtype, ret, outer)
568590
else:
569591
rtype = result_name
570592
ret = '' if rtype == 'void' else 'return '
571-
# Generate a shortcut with required args.
572-
self.generate_unpacked_method(namespace, route, rtype, ret, required_only=True)
573-
# Generate a builder if there are two or more optional args.
574-
# If there's only 1 optional argument then we might as well
575-
# just offer two overloaded methods.
576-
n_optional = len(route.request_data_type.all_optional_fields)
577-
if n_optional == 1:
578-
self.generate_unpacked_method(namespace, route, rtype, ret)
579-
elif n_optional > 1:
580-
self.generate_builder(namespace, route, rtype, ret, outer)
593+
# Generate a shortcut with required args.
594+
self.generate_unpacked_method(namespace, route, rtype, ret, required_only=True)
595+
# Generate a builder if there are two or more optional args.
596+
# If there's only 1 optional argument then we might as well
597+
# just offer two overloaded methods.
598+
n_optional = len(route.request_data_type.all_optional_fields)
599+
if n_optional == 1:
600+
self.generate_unpacked_method(namespace, route, rtype, ret)
601+
elif n_optional > 1:
602+
self.generate_builder(namespace, route, rtype, ret, outer)
581603

582604
def generate_field_assignment(self, namespace, field):
583605
out = self.emit
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.dropbox.core;
2+
3+
import com.dropbox.core.util.IOUtil;
4+
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.io.OutputStream;
8+
9+
/**
10+
* A callback for streaming data from an {@link InputStream}.
11+
*
12+
* @param <E>
13+
* The type of exception that your {@link #read} implementation might throw
14+
* If your {@code read} implementation won't throw any checked exceptions,
15+
* you should use {@link RuntimeException} for this parameter.
16+
*/
17+
public abstract class DbxStreamReader<E extends Throwable>
18+
{
19+
/**
20+
* Write all the data you plan to write to {@code in}. Do not
21+
* call {@link InputStream#close close()} on the stream (the stream will
22+
* be closed automatically).
23+
*/
24+
public abstract void read(NoThrowInputStream in) throws E;
25+
26+
/**
27+
* A {@link DbxStreamReader} that gets its source data from the given {@code OutputStream}.
28+
* The {@code OutputStream} will be closed automatically.
29+
*/
30+
public static final class OutputStreamCopier extends DbxStreamReader<IOException>
31+
{
32+
private final OutputStream dest;
33+
34+
public OutputStreamCopier(OutputStream dest)
35+
{
36+
this.dest = dest;
37+
}
38+
39+
public void read(NoThrowInputStream source) throws IOException
40+
{
41+
IOUtil.copyStreamToStream(source, dest);
42+
}
43+
}
44+
45+
public static final class ByteArrayCopier extends DbxStreamReader<RuntimeException>
46+
{
47+
private final byte[] data;
48+
private final int offset;
49+
private final int length;
50+
51+
public ByteArrayCopier(byte[] data, int offset, int length)
52+
{
53+
if (data == null) throw new IllegalArgumentException("'data' can't be null");
54+
if (offset < 0 || offset >= data.length) throw new IllegalArgumentException("'offset' is out of bounds");
55+
if ((offset + length) < offset || (offset + length) > data.length) throw new IllegalArgumentException("'offset+length' is out of bounds");
56+
this.data = data;
57+
this.offset = offset;
58+
this.length = length;
59+
}
60+
61+
public ByteArrayCopier(byte[] data)
62+
{
63+
this(data, 0, data.length);
64+
}
65+
66+
@Override
67+
public void read(NoThrowInputStream in)
68+
{
69+
in.read(this.data, this.offset, this.length);
70+
}
71+
}
72+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.dropbox.core;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.OutputStream;
6+
7+
/**
8+
* Wraps an existing input stream, converting all the underlying stream's {@code IOException}s
9+
* to our own {@link HiddenException}, which is a subclass of {@code RuntimeException}. This
10+
* means that the methods don't have {@code IOException} in their {@code throws} clauses.
11+
*
12+
* <p>
13+
* NOTE: The name is slightly misleading in that these methods still might throw an
14+
* {@code IOException} wrapped in a {@code HiddenException}, just not an
15+
* {@code IOException} directly.
16+
* </p>
17+
*
18+
* <p>
19+
* Also, it does have {@code IOException} in the {@code throws} clause of {@link #close},
20+
* but you're not supposed to call that method.
21+
* </p>
22+
*/
23+
public final class NoThrowInputStream extends InputStream
24+
{
25+
private final InputStream underlying;
26+
private long bytesRead = 0;
27+
28+
public NoThrowInputStream(InputStream underlying)
29+
{
30+
this.underlying = underlying;
31+
}
32+
33+
@Override
34+
public void close()
35+
{
36+
throw new UnsupportedOperationException("don't call close()");
37+
}
38+
39+
@Override
40+
public int read()
41+
{
42+
try {
43+
bytesRead += 1;
44+
return underlying.read();
45+
}
46+
catch (IOException ex) {
47+
throw new HiddenException(ex);
48+
}
49+
}
50+
51+
@Override
52+
public int read(byte[] b, int off, int len)
53+
{
54+
try {
55+
int bytesReadNow = underlying.read(b, off, len);
56+
this.bytesRead += bytesReadNow;
57+
return bytesReadNow;
58+
}
59+
catch (IOException ex) {
60+
throw new HiddenException(ex);
61+
}
62+
}
63+
64+
@Override
65+
public int read(byte[] b)
66+
{
67+
try {
68+
int bytesReadNow = underlying.read(b);
69+
this.bytesRead += bytesReadNow;
70+
return bytesReadNow;
71+
}
72+
catch (IOException ex) {
73+
throw new HiddenException(ex);
74+
}
75+
}
76+
77+
public static final class HiddenException extends RuntimeException
78+
{
79+
public final IOException underlying;
80+
81+
public HiddenException(IOException underlying)
82+
{
83+
super(underlying);
84+
this.underlying = underlying;
85+
}
86+
87+
public static final long serialVersionUID = 0;
88+
}
89+
90+
public long getBytesRead()
91+
{
92+
return bytesRead;
93+
}
94+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.dropbox.core.v2;
2+
3+
import com.dropbox.core.DbxException;
4+
import com.dropbox.core.DbxDownloader;
5+
import com.dropbox.core.DbxStreamReader;
6+
import com.dropbox.core.NoThrowInputStream;
7+
import com.dropbox.core.util.IOUtil;
8+
9+
import java.io.IOException;
10+
import java.io.OutputStream;
11+
12+
/**
13+
* The common interface for all builders associated with upload style
14+
* methods. Use the {@link #start} method to get a {@link DbxDownloader}
15+
* object, use {@link #run(DbxStreamReader)} for a callback style or
16+
* use {@link #run(OutputStream)} to directly copy the downloaded data
17+
* to an output stream.
18+
*
19+
* @param <R> The return type of the {@link DbxDownloader}
20+
*/
21+
public abstract class DbxDownloadStyleBuilder<R>
22+
{
23+
24+
public abstract DbxDownloader<R> start() throws DbxException;
25+
26+
public R run(OutputStream out) throws DbxException, IOException
27+
{
28+
DbxDownloader<R> downloader = null;
29+
try {
30+
downloader = start();
31+
IOUtil.copyStreamToStream(downloader.body, out);
32+
return downloader.result;
33+
} finally {
34+
if (downloader != null) {
35+
downloader.close();
36+
}
37+
}
38+
}
39+
40+
public<X extends Throwable> R run(DbxStreamReader<X> handler) throws DbxException, X, IOException
41+
{
42+
DbxDownloader<R> downloader = null;
43+
try {
44+
downloader = start();
45+
NoThrowInputStream noThrow = new NoThrowInputStream(downloader.body);
46+
handler.read(noThrow);
47+
return downloader.result;
48+
} catch (NoThrowInputStream.HiddenException ex) {
49+
throw new DbxException.NetworkIO(ex.underlying);
50+
} finally {
51+
if (downloader != null) {
52+
downloader.close();
53+
}
54+
}
55+
}
56+
57+
}

0 commit comments

Comments
 (0)