001package com.box.sdk;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.OutputStream;
007import java.net.MalformedURLException;
008import java.net.URL;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Collection;
012import java.util.Date;
013import java.util.EnumSet;
014import java.util.HashSet;
015import java.util.List;
016import java.util.Map;
017import java.util.Set;
018import java.util.concurrent.TimeUnit;
019
020import com.box.sdk.internal.utils.Parsers;
021import com.eclipsesource.json.JsonArray;
022import com.eclipsesource.json.JsonObject;
023import com.eclipsesource.json.JsonValue;
024
025
026/**
027 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
028 * perform other common file operations (move, copy, delete, etc.).
029 *
030 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
031 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
032 * handling for errors related to the Box REST API, you should capture this exception explicitly.
033 */
034@BoxResourceType("file")
035public class BoxFile extends BoxItem {
036
037    /**
038     * An array of all possible file fields that can be requested when calling {@link #getInfo()}.
039     */
040    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
041                                               "description", "size", "path_collection", "created_at", "modified_at",
042                                               "trashed_at", "purged_at", "content_created_at", "content_modified_at",
043                                               "created_by", "modified_by", "owned_by", "shared_link", "parent",
044                                               "item_status", "version_number", "comment_count", "permissions", "tags",
045                                               "lock", "extension", "is_package", "file_version", "collections",
046                                               "watermark_info", "metadata", "representations"};
047
048    /**
049     * Used to specify what filetype to request for a file thumbnail.
050     */
051    public enum ThumbnailFileType {
052        /**
053         * PNG image format.
054         */
055        PNG,
056
057        /**
058         * JPG image format.
059         */
060        JPG
061    }
062
063    /**
064     * File URL Template.
065     */
066    public static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
067    /**
068     * Content URL Template.
069     */
070    public static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
071    /**
072     * Versions URL Template.
073     */
074    public static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
075    /**
076     * Copy URL Template.
077     */
078    public static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
079    /**
080     * Add Comment URL Template.
081     */
082    public static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
083    /**
084     * Get Comments URL Template.
085     */
086    public static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
087    /**
088     * Metadata URL Template.
089     */
090    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s/%s");
091    /**
092     * Add Task URL Template.
093     */
094    public static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
095    /**
096     * Get Tasks URL Template.
097     */
098    public static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
099    /**
100     * Get Thumbnail PNG Template.
101     */
102    public static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png");
103    /**
104     * Get Thumbnail JPG Template.
105     */
106    public static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg");
107    /**
108     * Upload Session URL Template.
109     */
110    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload_sessions");
111    /**
112     * Upload Session Status URL Template.
113     */
114    public static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate(
115            "files/upload_sessions/%s/status");
116    /**
117     * Abort Upload Session URL Template.
118     */
119    public static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions/%s");
120    /**
121     * Add Collaborations URL Template.
122     */
123    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
124    /**
125     * Get All File Collaborations URL Template.
126     */
127    public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations");
128    private static final int BUFFER_SIZE = 8192;
129    private static final int GET_COLLABORATORS_PAGE_SIZE = 1000;
130
131    /**
132     * Constructs a BoxFile for a file with a given ID.
133     *
134     * @param api the API connection to be used by the file.
135     * @param id  the ID of the file.
136     */
137    public BoxFile(BoxAPIConnection api, String id) {
138        super(api, id);
139    }
140
141    /**
142     * {@inheritDoc}
143     */
144    @Override
145    protected URL getItemURL() {
146        return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
147    }
148
149    @Override
150    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
151                                          BoxSharedLink.Permissions permissions) {
152
153        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
154        Info info = new Info();
155        info.setSharedLink(sharedLink);
156
157        this.updateInfo(info);
158        return info.getSharedLink();
159    }
160
161    /**
162     * Adds new {@link BoxWebHook} to this {@link BoxFile}.
163     *
164     * @param address  {@link BoxWebHook.Info#getAddress()}
165     * @param triggers {@link BoxWebHook.Info#getTriggers()}
166     * @return created {@link BoxWebHook.Info}
167     */
168    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
169        return BoxWebHook.create(this, address, triggers);
170    }
171
172    /**
173     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
174     * within the message, where userid and username are the ID and username of the person being mentioned.
175     *
176     * @param message the comment's message.
177     * @return information about the newly added comment.
178     * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
179     * for including @mentions.</a>
180     */
181    public BoxComment.Info addComment(String message) {
182        JsonObject itemJSON = new JsonObject();
183        itemJSON.add("type", "file");
184        itemJSON.add("id", this.getID());
185
186        JsonObject requestJSON = new JsonObject();
187        requestJSON.add("item", itemJSON);
188        if (BoxComment.messageContainsMention(message)) {
189            requestJSON.add("tagged_message", message);
190        } else {
191            requestJSON.add("message", message);
192        }
193
194        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
195        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
196        request.setBody(requestJSON.toString());
197        BoxJSONResponse response = (BoxJSONResponse) request.send();
198        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
199
200        BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
201        return addedComment.new Info(responseJSON);
202    }
203
204    /**
205     * Adds a new task to this file. The task can have an optional message to include, and a due date.
206     *
207     * @param action  the action the task assignee will be prompted to do.
208     * @param message an optional message to include with the task.
209     * @param dueAt   the day at which this task is due.
210     * @return information about the newly added task.
211     */
212    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
213        JsonObject itemJSON = new JsonObject();
214        itemJSON.add("type", "file");
215        itemJSON.add("id", this.getID());
216
217        JsonObject requestJSON = new JsonObject();
218        requestJSON.add("item", itemJSON);
219        requestJSON.add("action", action.toJSONString());
220
221        if (message != null && !message.isEmpty()) {
222            requestJSON.add("message", message);
223        }
224
225        if (dueAt != null) {
226            requestJSON.add("due_at", BoxDateFormat.format(dueAt));
227        }
228
229        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
230        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
231        request.setBody(requestJSON.toString());
232        BoxJSONResponse response = (BoxJSONResponse) request.send();
233        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
234
235        BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
236        return addedTask.new Info(responseJSON);
237    }
238
239    /**
240     * Gets an expiring URL for downloading a file directly from Box. This can be user,
241     * for example, for sending as a redirect to a browser to cause the browser
242     * to download the file directly from Box.
243     *
244     * @return the temporary download URL
245     */
246    public URL getDownloadURL() {
247        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
248        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
249        request.setFollowRedirects(false);
250
251        BoxRedirectResponse response = (BoxRedirectResponse) request.send();
252
253        return response.getRedirectURL();
254    }
255
256    /**
257     * Downloads the contents of this file to a given OutputStream.
258     *
259     * @param output the stream to where the file will be written.
260     */
261    public void download(OutputStream output) {
262        this.download(output, null);
263    }
264
265    /**
266     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
267     *
268     * @param output   the stream to where the file will be written.
269     * @param listener a listener for monitoring the download's progress.
270     */
271    public void download(OutputStream output, ProgressListener listener) {
272        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
273        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
274        BoxAPIResponse response = request.send();
275        InputStream input = response.getBody(listener);
276
277        byte[] buffer = new byte[BUFFER_SIZE];
278        try {
279            int n = input.read(buffer);
280            while (n != -1) {
281                output.write(buffer, 0, n);
282                n = input.read(buffer);
283            }
284        } catch (IOException e) {
285            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
286        } finally {
287            response.disconnect();
288        }
289    }
290
291    /**
292     * Downloads a part of this file's contents, starting at specified byte offset.
293     *
294     * @param output the stream to where the file will be written.
295     * @param offset the byte offset at which to start the download.
296     */
297    public void downloadRange(OutputStream output, long offset) {
298        this.downloadRange(output, offset, -1);
299    }
300
301    /**
302     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
303     *
304     * @param output     the stream to where the file will be written.
305     * @param rangeStart the byte offset at which to start the download.
306     * @param rangeEnd   the byte offset at which to stop the download.
307     */
308    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
309        this.downloadRange(output, rangeStart, rangeEnd, null);
310    }
311
312    /**
313     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
314     * progress to a ProgressListener.
315     *
316     * @param output     the stream to where the file will be written.
317     * @param rangeStart the byte offset at which to start the download.
318     * @param rangeEnd   the byte offset at which to stop the download.
319     * @param listener   a listener for monitoring the download's progress.
320     */
321    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
322        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
323        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
324        if (rangeEnd > 0) {
325            request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart),
326                    Long.toString(rangeEnd)));
327        } else {
328            request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart)));
329        }
330
331        BoxAPIResponse response = request.send();
332        InputStream input = response.getBody(listener);
333
334        byte[] buffer = new byte[BUFFER_SIZE];
335        try {
336            int n = input.read(buffer);
337            while (n != -1) {
338                output.write(buffer, 0, n);
339                n = input.read(buffer);
340            }
341        } catch (IOException e) {
342            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
343        } finally {
344            response.disconnect();
345        }
346    }
347
348    @Override
349    public BoxFile.Info copy(BoxFolder destination) {
350        return this.copy(destination, null);
351    }
352
353    @Override
354    public BoxFile.Info copy(BoxFolder destination, String newName) {
355        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
356
357        JsonObject parent = new JsonObject();
358        parent.add("id", destination.getID());
359
360        JsonObject copyInfo = new JsonObject();
361        copyInfo.add("parent", parent);
362        if (newName != null) {
363            copyInfo.add("name", newName);
364        }
365
366        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
367        request.setBody(copyInfo.toString());
368        BoxJSONResponse response = (BoxJSONResponse) request.send();
369        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
370        BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
371        return copiedFile.new Info(responseJSON);
372    }
373
374    /**
375     * Deletes this file by moving it to the trash.
376     */
377    public void delete() {
378        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
379        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
380        BoxAPIResponse response = request.send();
381        response.disconnect();
382    }
383
384    @Override
385    public BoxItem.Info move(BoxFolder destination) {
386        return this.move(destination, null);
387    }
388
389    @Override
390    public BoxItem.Info move(BoxFolder destination, String newName) {
391        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
392        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
393
394        JsonObject parent = new JsonObject();
395        parent.add("id", destination.getID());
396
397        JsonObject updateInfo = new JsonObject();
398        updateInfo.add("parent", parent);
399        if (newName != null) {
400            updateInfo.add("name", newName);
401        }
402
403        request.setBody(updateInfo.toString());
404        BoxJSONResponse response = (BoxJSONResponse) request.send();
405        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
406        BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
407        return movedFile.new Info(responseJSON);
408    }
409
410    /**
411     * Renames this file.
412     *
413     * @param newName the new name of the file.
414     */
415    public void rename(String newName) {
416        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
417        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
418
419        JsonObject updateInfo = new JsonObject();
420        updateInfo.add("name", newName);
421
422        request.setBody(updateInfo.toString());
423        BoxAPIResponse response = request.send();
424        response.disconnect();
425    }
426
427    @Override
428    public BoxFile.Info getInfo() {
429        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
430        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
431        BoxJSONResponse response = (BoxJSONResponse) request.send();
432        return new Info(response.getJSON());
433    }
434
435    @Override
436    public BoxFile.Info getInfo(String... fields) {
437        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
438        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
439
440        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
441        BoxJSONResponse response = (BoxJSONResponse) request.send();
442        return new Info(response.getJSON());
443    }
444
445    /**
446     * Gets information about this item including a specified set of representations.
447     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
448     *
449     * @param representationHints hints for representations to be retrieved
450     * @param fields the fields to retrieve.
451     * @return info about this item containing only the specified fields, including representations.
452     */
453    public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) {
454        if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) {
455            //Since the user intends to get representations, add it to fields, even if user has missed it
456            Set<String> fieldsSet = new HashSet<String>(Arrays.asList(fields));
457            fieldsSet.add("representations");
458            String queryString = new QueryStringBuilder().appendParam("fields",
459                fieldsSet.toArray(new String[fieldsSet.size()])).toString();
460            URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
461
462            BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
463            request.addHeader("X-Rep-Hints", representationHints);
464            BoxJSONResponse response = (BoxJSONResponse) request.send();
465            return new Info(response.getJSON());
466        } else {
467            throw new BoxAPIException("Represention hints is not valid."
468                + " Refer documention on how to construct X-Rep-Hints Header");
469        }
470    }
471
472    /**
473     * Updates the information about this file with any info fields that have been modified locally.
474     *
475     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
476     * code won't update any information (or even send a network request) since none of the info's fields were
477     * changed:</p>
478     *
479     * <pre>BoxFile file = new File(api, id);
480     * BoxFile.Info info = file.getInfo();
481     * file.updateInfo(info);</pre>
482     *
483     * @param info the updated info.
484     */
485    public void updateInfo(BoxFile.Info info) {
486        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
487        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
488        request.setBody(info.getPendingChanges());
489        BoxJSONResponse response = (BoxJSONResponse) request.send();
490        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
491        info.update(jsonObject);
492    }
493
494    /**
495     * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve
496     * previous versions of their files.
497     *
498     * @return a list of previous file versions.
499     */
500    public Collection<BoxFileVersion> getVersions() {
501        URL url = VERSIONS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
502        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
503        BoxJSONResponse response = (BoxJSONResponse) request.send();
504
505        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
506        JsonArray entries = jsonObject.get("entries").asArray();
507        Collection<BoxFileVersion> versions = new ArrayList<BoxFileVersion>();
508        for (JsonValue entry : entries) {
509            versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
510        }
511
512        return versions;
513    }
514
515    /**
516     * Checks if the file can be successfully uploaded by using the preflight check.
517     *
518     * @param name     the name to give the uploaded file or null to use existing name.
519     * @param fileSize the size of the file used for account capacity calculations.
520     * @param parentID the ID of the parent folder that the new version is being uploaded to.
521     */
522    public void canUploadVersion(String name, long fileSize, String parentID) {
523        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
524        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
525
526        JsonObject parent = new JsonObject();
527        parent.add("id", parentID);
528
529        JsonObject preflightInfo = new JsonObject();
530        preflightInfo.add("parent", parent);
531        if (name != null) {
532            preflightInfo.add("name", name);
533        }
534
535        preflightInfo.add("size", fileSize);
536
537        request.setBody(preflightInfo.toString());
538        BoxAPIResponse response = request.send();
539        response.disconnect();
540    }
541
542    /**
543     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
544     * will be able to view and recover previous versions of the file.
545     *
546     * @param fileContent a stream containing the new file contents.
547     */
548    public void uploadVersion(InputStream fileContent) {
549        this.uploadVersion(fileContent, null);
550    }
551
552    /**
553     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
554     * will be able to view and recover previous versions of the file.
555     *
556     * @param fileContent     a stream containing the new file contents.
557     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
558     */
559    public void uploadVersion(InputStream fileContent, String fileContentSHA1) {
560        this.uploadVersion(fileContent, fileContentSHA1, null);
561    }
562
563    /**
564     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
565     * will be able to view and recover previous versions of the file.
566     *
567     * @param fileContent     a stream containing the new file contents.
568     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
569     * @param modified        the date that the new version was modified.
570     */
571    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
572        this.uploadVersion(fileContent, fileContentSHA1, modified, 0, null);
573    }
574
575    /**
576     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
577     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
578     * of the file.
579     *
580     * @param fileContent a stream containing the new file contents.
581     * @param modified    the date that the new version was modified.
582     * @param fileSize    the size of the file used for determining the progress of the upload.
583     * @param listener    a listener for monitoring the upload's progress.
584     */
585    public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) {
586        this.uploadVersion(fileContent, null, modified, fileSize, listener);
587    }
588
589    /**
590     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
591     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
592     * of the file.
593     *
594     * @param fileContent     a stream containing the new file contents.
595     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
596     * @param modified        the date that the new version was modified.
597     * @param fileSize        the size of the file used for determining the progress of the upload.
598     * @param listener        a listener for monitoring the upload's progress.
599     */
600    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
601                              ProgressListener listener) {
602        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
603        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
604
605        if (fileSize > 0) {
606            request.setFile(fileContent, "", fileSize);
607        } else {
608            request.setFile(fileContent, "");
609        }
610
611        if (fileContentSHA1 != null) {
612            request.setContentSHA1(fileContentSHA1);
613        }
614
615        if (modified != null) {
616            request.putField("content_modified_at", modified);
617        }
618
619        BoxJSONResponse response;
620        if (listener == null) {
621            response = (BoxJSONResponse) request.send();
622        } else {
623            response = (BoxJSONResponse) request.send(listener);
624        }
625        response.getJSON();
626    }
627
628    /**
629     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
630     * preview session will expire after 60 minutes.
631     *
632     * @return the expiring preview link
633     */
634    public URL getPreviewLink() {
635        BoxFile.Info info = this.getInfo("expiring_embed_link");
636
637        return info.getPreviewLink();
638    }
639
640
641    /**
642     * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128,
643     * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned
644     * in the .jpg format.
645     *
646     * @param fileType  either PNG of JPG
647     * @param minWidth  minimum width
648     * @param minHeight minimum height
649     * @param maxWidth  maximum width
650     * @param maxHeight maximum height
651     * @return the byte array of the thumbnail image
652     */
653    public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) {
654        QueryStringBuilder builder = new QueryStringBuilder();
655        builder.appendParam("min_width", minWidth);
656        builder.appendParam("min_height", minHeight);
657        builder.appendParam("max_width", maxWidth);
658        builder.appendParam("max_height", maxHeight);
659
660        URLTemplate template;
661        if (fileType == ThumbnailFileType.PNG) {
662            template = GET_THUMBNAIL_PNG_TEMPLATE;
663        } else if (fileType == ThumbnailFileType.JPG) {
664            template = GET_THUMBNAIL_JPG_TEMPLATE;
665        } else {
666            throw new BoxAPIException("Unsupported thumbnail file type");
667        }
668        URL url = template.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
669
670        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
671        BoxAPIResponse response = request.send();
672
673        ByteArrayOutputStream thumbOut = new ByteArrayOutputStream();
674        InputStream body = response.getBody();
675        byte[] buffer = new byte[BUFFER_SIZE];
676        try {
677            int n = body.read(buffer);
678            while (n != -1) {
679                thumbOut.write(buffer, 0, n);
680                n = body.read(buffer);
681            }
682        } catch (IOException e) {
683            throw new BoxAPIException("Error reading thumbnail bytes from response body", e);
684        } finally {
685            response.disconnect();
686        }
687
688        return thumbOut.toByteArray();
689    }
690
691    /**
692     * Gets a list of any comments on this file.
693     *
694     * @return a list of comments on this file.
695     */
696    public List<BoxComment.Info> getComments() {
697        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
698        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
699        BoxJSONResponse response = (BoxJSONResponse) request.send();
700        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
701
702        int totalCount = responseJSON.get("total_count").asInt();
703        List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount);
704        JsonArray entries = responseJSON.get("entries").asArray();
705        for (JsonValue value : entries) {
706            JsonObject commentJSON = value.asObject();
707            BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
708            BoxComment.Info info = comment.new Info(commentJSON);
709            comments.add(info);
710        }
711
712        return comments;
713    }
714
715    /**
716     * Gets a list of any tasks on this file.
717     *
718     * @return a list of tasks on this file.
719     */
720    public List<BoxTask.Info> getTasks() {
721        URL url = GET_TASKS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
722        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
723        BoxJSONResponse response = (BoxJSONResponse) request.send();
724        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
725
726        int totalCount = responseJSON.get("total_count").asInt();
727        List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount);
728        JsonArray entries = responseJSON.get("entries").asArray();
729        for (JsonValue value : entries) {
730            JsonObject taskJSON = value.asObject();
731            BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
732            BoxTask.Info info = task.new Info(taskJSON);
733            tasks.add(info);
734        }
735
736        return tasks;
737    }
738
739    /**
740     * Creates metadata on this file in the global properties template.
741     *
742     * @param metadata The new metadata values.
743     * @return the metadata returned from the server.
744     */
745    public Metadata createMetadata(Metadata metadata) {
746        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
747    }
748
749    /**
750     * Creates metadata on this file in the specified template type.
751     *
752     * @param typeName the metadata template type name.
753     * @param metadata the new metadata values.
754     * @return the metadata returned from the server.
755     */
756    public Metadata createMetadata(String typeName, Metadata metadata) {
757        String scope = Metadata.scopeBasedOnType(typeName);
758        return this.createMetadata(typeName, scope, metadata);
759    }
760
761    /**
762     * Creates metadata on this file in the specified template type.
763     *
764     * @param typeName the metadata template type name.
765     * @param scope    the metadata scope (global or enterprise).
766     * @param metadata the new metadata values.
767     * @return the metadata returned from the server.
768     */
769    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
770        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
771        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
772        request.addHeader("Content-Type", "application/json");
773        request.setBody(metadata.toString());
774        BoxJSONResponse response = (BoxJSONResponse) request.send();
775        return new Metadata(JsonObject.readFrom(response.getJSON()));
776    }
777
778    /**
779     * Locks a file.
780     *
781     * @return the lock returned from the server.
782     */
783    public BoxLock lock() {
784        return this.lock(null, false);
785    }
786
787    /**
788     * Locks a file.
789     *
790     * @param isDownloadPrevented is downloading of file prevented when locked.
791     * @return the lock returned from the server.
792     */
793    public BoxLock lock(boolean isDownloadPrevented) {
794        return this.lock(null, isDownloadPrevented);
795    }
796
797    /**
798     * Locks a file.
799     *
800     * @param expiresAt expiration date of the lock.
801     * @return the lock returned from the server.
802     */
803    public BoxLock lock(Date expiresAt) {
804        return this.lock(expiresAt, false);
805    }
806
807    /**
808     * Locks a file.
809     *
810     * @param expiresAt           expiration date of the lock.
811     * @param isDownloadPrevented is downloading of file prevented when locked.
812     * @return the lock returned from the server.
813     */
814    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
815        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
816        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
817        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
818
819        JsonObject lockConfig = new JsonObject();
820        lockConfig.add("type", "lock");
821        if (expiresAt != null) {
822            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
823        }
824        lockConfig.add("is_download_prevented", isDownloadPrevented);
825
826        JsonObject requestJSON = new JsonObject();
827        requestJSON.add("lock", lockConfig);
828        request.setBody(requestJSON.toString());
829
830        BoxJSONResponse response = (BoxJSONResponse) request.send();
831
832        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
833        JsonValue lockValue = responseJSON.get("lock");
834        JsonObject lockJSON = JsonObject.readFrom(lockValue.toString());
835
836        return new BoxLock(lockJSON, this.getAPI());
837    }
838
839    /**
840     * Unlocks a file.
841     */
842    public void unlock() {
843        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
844        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
845        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
846
847        JsonObject lockObject = new JsonObject();
848        lockObject.add("lock", JsonObject.NULL);
849
850        request.setBody(lockObject.toString());
851        request.send();
852    }
853
854    /**
855     * Used to retrieve all metadata associated with the file.
856     *
857     * @param fields the optional fields to retrieve.
858     * @return An iterable of metadata instances associated with the file.
859     */
860    public Iterable<Metadata> getAllMetadata(String... fields) {
861        return Metadata.getAllMetadata(this, fields);
862    }
863
864    /**
865     * Gets the file properties metadata.
866     *
867     * @return the metadata returned from the server.
868     */
869    public Metadata getMetadata() {
870        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
871    }
872
873    /**
874     * Gets the file metadata of specified template type.
875     *
876     * @param typeName the metadata template type name.
877     * @return the metadata returned from the server.
878     */
879    public Metadata getMetadata(String typeName) {
880        String scope = Metadata.scopeBasedOnType(typeName);
881        return this.getMetadata(typeName, scope);
882    }
883
884    /**
885     * Gets the file metadata of specified template type.
886     *
887     * @param typeName the metadata template type name.
888     * @param scope    the metadata scope (global or enterprise).
889     * @return the metadata returned from the server.
890     */
891    public Metadata getMetadata(String typeName, String scope) {
892        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
893        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
894        BoxJSONResponse response = (BoxJSONResponse) request.send();
895        return new Metadata(JsonObject.readFrom(response.getJSON()));
896    }
897
898    /**
899     * Updates the file metadata.
900     *
901     * @param metadata the new metadata values.
902     * @return the metadata returned from the server.
903     */
904    public Metadata updateMetadata(Metadata metadata) {
905        String scope;
906        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
907            scope = Metadata.GLOBAL_METADATA_SCOPE;
908        } else {
909            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
910        }
911
912        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(),
913                scope, metadata.getTemplateName());
914        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
915        request.addHeader("Content-Type", "application/json-patch+json");
916        request.setBody(metadata.getPatch());
917        BoxJSONResponse response = (BoxJSONResponse) request.send();
918        return new Metadata(JsonObject.readFrom(response.getJSON()));
919    }
920
921    /**
922     * Deletes the file properties metadata.
923     */
924    public void deleteMetadata() {
925        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
926    }
927
928    /**
929     * Deletes the file metadata of specified template type.
930     *
931     * @param typeName the metadata template type name.
932     */
933    public void deleteMetadata(String typeName) {
934        String scope = Metadata.scopeBasedOnType(typeName);
935        this.deleteMetadata(typeName, scope);
936    }
937
938    /**
939     * Deletes the file metadata of specified template type.
940     *
941     * @param typeName the metadata template type name.
942     * @param scope    the metadata scope (global or enterprise).
943     */
944    public void deleteMetadata(String typeName, String scope) {
945        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
946        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
947        request.send();
948    }
949
950    /**
951     * Used to retrieve the watermark for the file.
952     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
953     *
954     * @param fields the fields to retrieve.
955     * @return the watermark associated with the file.
956     */
957    public BoxWatermark getWatermark(String... fields) {
958        return this.getWatermark(FILE_URL_TEMPLATE, fields);
959    }
960
961    /**
962     * Used to apply or update the watermark for the file.
963     *
964     * @return the watermark associated with the file.
965     */
966    public BoxWatermark applyWatermark() {
967        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
968    }
969
970    /**
971     * Removes a watermark from the file.
972     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
973     */
974    public void removeWatermark() {
975        this.removeWatermark(FILE_URL_TEMPLATE);
976    }
977
978    /**
979     * {@inheritDoc}
980     */
981    @Override
982    public BoxFile.Info setCollections(BoxCollection... collections) {
983        JsonArray jsonArray = new JsonArray();
984        for (BoxCollection collection : collections) {
985            JsonObject collectionJSON = new JsonObject();
986            collectionJSON.add("id", collection.getID());
987            jsonArray.add(collectionJSON);
988        }
989        JsonObject infoJSON = new JsonObject();
990        infoJSON.add("collections", jsonArray);
991
992        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
993        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
994        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
995        request.setBody(infoJSON.toString());
996        BoxJSONResponse response = (BoxJSONResponse) request.send();
997        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
998        return new Info(jsonObject);
999    }
1000
1001    /**
1002     * Creates an upload session to create a new version of a file in chunks.
1003     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1004     * @param fileSize the size of the file that will be uploaded.
1005     * @return the created upload session instance.
1006     */
1007    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1008        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1009
1010        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1011        request.addHeader("Content-Type", "application/json");
1012
1013        JsonObject body = new JsonObject();
1014        body.add("file_size", fileSize);
1015        request.setBody(body.toString());
1016
1017        BoxJSONResponse response = (BoxJSONResponse) request.send();
1018        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1019
1020        String sessionId = jsonObject.get("id").asString();
1021        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1022        return session.new Info(jsonObject);
1023    }
1024
1025    /**
1026     * Creates a new version of a file.
1027     * @param inputStream the stream instance that contains the data.
1028     * @param fileSize the size of the file that will be uploaded.
1029     * @return the created file instance.
1030     * @throws InterruptedException when a thread execution is interrupted.
1031     * @throws IOException when reading a stream throws exception.
1032     */
1033    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1034        throws InterruptedException, IOException {
1035        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1036        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
1037    }
1038
1039    /**
1040     * Creates a new version of a file using specified number of parallel http connections.
1041     * @param inputStream the stream instance that contains the data.
1042     * @param fileSize the size of the file that will be uploaded.
1043     * @param nParallelConnections number of parallel http connections to use
1044     * @param timeOut time to wait before killing the job
1045     * @param unit time unit for the time wait value
1046     * @return the created file instance.
1047     * @throws InterruptedException when a thread execution is interrupted.
1048     * @throws IOException when reading a stream throws exception.
1049     */
1050    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1051                                        int nParallelConnections, long timeOut, TimeUnit unit)
1052        throws InterruptedException, IOException {
1053        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1054        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1055            .upload(this.getAPI(), inputStream, url, fileSize);
1056    }
1057
1058    /**
1059     * Contains information about a BoxFile.
1060     */
1061    public class Info extends BoxItem.Info {
1062        private String sha1;
1063        private String versionNumber;
1064        private long commentCount;
1065        private EnumSet<Permission> permissions;
1066        private String extension;
1067        private boolean isPackage;
1068        private BoxFileVersion version;
1069        private URL previewLink;
1070        private BoxLock lock;
1071        private boolean isWatermarked;
1072        private JsonObject metadata;
1073        private Map<String, Map<String, Metadata>> metadataMap;
1074        private List<Representation> representations;
1075
1076        /**
1077         * Constructs an empty Info object.
1078         */
1079        public Info() {
1080            super();
1081        }
1082
1083        /**
1084         * Constructs an Info object by parsing information from a JSON string.
1085         *
1086         * @param json the JSON string to parse.
1087         */
1088        public Info(String json) {
1089            super(json);
1090        }
1091
1092        /**
1093         * Constructs an Info object using an already parsed JSON object.
1094         *
1095         * @param jsonObject the parsed JSON object.
1096         */
1097        public Info(JsonObject jsonObject) {
1098            super(jsonObject);
1099        }
1100
1101        @Override
1102        public BoxFile getResource() {
1103            return BoxFile.this;
1104        }
1105
1106        /**
1107         * Gets the SHA1 hash of the file.
1108         *
1109         * @return the SHA1 hash of the file.
1110         */
1111        public String getSha1() {
1112            return this.sha1;
1113        }
1114
1115        /**
1116         * Gets the lock of the file.
1117         *
1118         * @return the lock of the file.
1119         */
1120        public BoxLock getLock() {
1121            return this.lock;
1122        }
1123
1124        /**
1125         * Gets the current version number of the file.
1126         *
1127         * @return the current version number of the file.
1128         */
1129        public String getVersionNumber() {
1130            return this.versionNumber;
1131        }
1132
1133        /**
1134         * Gets the number of comments on the file.
1135         *
1136         * @return the number of comments on the file.
1137         */
1138        public long getCommentCount() {
1139            return this.commentCount;
1140        }
1141
1142        /**
1143         * Gets the permissions that the current user has on the file.
1144         *
1145         * @return the permissions that the current user has on the file.
1146         */
1147        public EnumSet<Permission> getPermissions() {
1148            return this.permissions;
1149        }
1150
1151        /**
1152         * Gets the extension suffix of the file, excluding the dot.
1153         *
1154         * @return the extension of the file.
1155         */
1156        public String getExtension() {
1157            return this.extension;
1158        }
1159
1160        /**
1161         * Gets whether or not the file is an OSX package.
1162         *
1163         * @return true if the file is an OSX package; otherwise false.
1164         */
1165        public boolean getIsPackage() {
1166            return this.isPackage;
1167        }
1168
1169        /**
1170         * Gets the current version details of the file.
1171         *
1172         * @return the current version details of the file.
1173         */
1174        public BoxFileVersion getVersion() {
1175            return this.version;
1176        }
1177
1178        /**
1179         * Gets the current expiring preview link.
1180         *
1181         * @return the expiring preview link
1182         */
1183        public URL getPreviewLink() {
1184            return this.previewLink;
1185        }
1186
1187        /**
1188         * Gets flag indicating whether this file is Watermarked.
1189         *
1190         * @return whether the file is watermarked or not
1191         */
1192        public boolean getIsWatermarked() {
1193            return this.isWatermarked;
1194        }
1195
1196        /**
1197         * Gets the metadata on this file associated with a specified scope and template.
1198         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found
1199         * then makes an API call to get metadata
1200         * @param   templateName    the metadata template type name.
1201         * @param   scope           the scope of the template (usually "global" or "enterprise").
1202         * @return                  the metadata returned from the server.
1203         */
1204        public Metadata getMetadata(String templateName, String scope) {
1205            try {
1206                return this.metadataMap.get(scope).get(templateName);
1207            } catch (NullPointerException e) {
1208                return null;
1209            }
1210        }
1211
1212        /**
1213         * Get file's representations.
1214         * @return list of representations
1215         */
1216        public List<Representation> getRepresentations() {
1217            return this.representations;
1218        }
1219
1220        @Override
1221        protected void parseJSONMember(JsonObject.Member member) {
1222            super.parseJSONMember(member);
1223
1224            String memberName = member.getName();
1225            JsonValue value = member.getValue();
1226            if (memberName.equals("sha1")) {
1227                this.sha1 = value.asString();
1228            } else if (memberName.equals("version_number")) {
1229                this.versionNumber = value.asString();
1230            } else if (memberName.equals("comment_count")) {
1231                this.commentCount = value.asLong();
1232            } else if (memberName.equals("permissions")) {
1233                this.permissions = this.parsePermissions(value.asObject());
1234            } else if (memberName.equals("extension")) {
1235                this.extension = value.asString();
1236            } else if (memberName.equals("is_package")) {
1237                this.isPackage = value.asBoolean();
1238            } else if (memberName.equals("file_version")) {
1239                this.version = this.parseFileVersion(value.asObject());
1240            } else if (memberName.equals("expiring_embed_link")) {
1241                try {
1242                    String urlString = member.getValue().asObject().get("url").asString();
1243                    this.previewLink = new URL(urlString);
1244                } catch (MalformedURLException e) {
1245                    throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
1246                }
1247            } else if (memberName.equals("lock")) {
1248                if (value.isNull()) {
1249                    this.lock = null;
1250                } else {
1251                    this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
1252                }
1253            } else if (memberName.equals("watermark_info")) {
1254                JsonObject jsonObject = value.asObject();
1255                this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1256            } else if (memberName.equals("metadata")) {
1257                JsonObject jsonObject = value.asObject();
1258                this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1259            } else if (memberName.equals("representations")) {
1260                JsonObject jsonObject = value.asObject();
1261                this.representations = Parsers.parseRepresentations(jsonObject);
1262            }
1263        }
1264
1265        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1266            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1267            for (JsonObject.Member member : jsonObject) {
1268                JsonValue value = member.getValue();
1269                if (value.isNull() || !value.asBoolean()) {
1270                    continue;
1271                }
1272
1273                String memberName = member.getName();
1274                if (memberName.equals("can_download")) {
1275                    permissions.add(Permission.CAN_DOWNLOAD);
1276                } else if (memberName.equals("can_upload")) {
1277                    permissions.add(Permission.CAN_UPLOAD);
1278                } else if (memberName.equals("can_rename")) {
1279                    permissions.add(Permission.CAN_RENAME);
1280                } else if (memberName.equals("can_delete")) {
1281                    permissions.add(Permission.CAN_DELETE);
1282                } else if (memberName.equals("can_share")) {
1283                    permissions.add(Permission.CAN_SHARE);
1284                } else if (memberName.equals("can_set_share_access")) {
1285                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1286                } else if (memberName.equals("can_preview")) {
1287                    permissions.add(Permission.CAN_PREVIEW);
1288                } else if (memberName.equals("can_comment")) {
1289                    permissions.add(Permission.CAN_COMMENT);
1290                }
1291            }
1292
1293            return permissions;
1294        }
1295
1296        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
1297            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
1298        }
1299    }
1300
1301    /**
1302     * Enumerates the possible permissions that a user can have on a file.
1303     */
1304    public enum Permission {
1305        /**
1306         * The user can download the file.
1307         */
1308        CAN_DOWNLOAD("can_download"),
1309
1310        /**
1311         * The user can upload new versions of the file.
1312         */
1313        CAN_UPLOAD("can_upload"),
1314
1315        /**
1316         * The user can rename the file.
1317         */
1318        CAN_RENAME("can_rename"),
1319
1320        /**
1321         * The user can delete the file.
1322         */
1323        CAN_DELETE("can_delete"),
1324
1325        /**
1326         * The user can share the file.
1327         */
1328        CAN_SHARE("can_share"),
1329
1330        /**
1331         * The user can set the access level for shared links to the file.
1332         */
1333        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1334
1335        /**
1336         * The user can preview the file.
1337         */
1338        CAN_PREVIEW("can_preview"),
1339
1340        /**
1341         * The user can comment on the file.
1342         */
1343        CAN_COMMENT("can_comment");
1344
1345        private final String jsonValue;
1346
1347        private Permission(String jsonValue) {
1348            this.jsonValue = jsonValue;
1349        }
1350
1351        static Permission fromJSONValue(String jsonValue) {
1352            return Permission.valueOf(jsonValue.toUpperCase());
1353        }
1354
1355        String toJSONValue() {
1356            return this.jsonValue;
1357        }
1358    }
1359
1360    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1361                                              Boolean notify, Boolean canViewPath) {
1362        BoxAPIConnection api = this.getAPI();
1363        URL url = ADD_COLLABORATION_URL.build(api.getBaseURL());
1364
1365        JsonObject itemField = new JsonObject();
1366        itemField.add("id", this.getID());
1367        itemField.add("type", "file");
1368
1369        JsonObject requestJSON = new JsonObject();
1370        requestJSON.add("item", itemField);
1371        requestJSON.add("accessible_by", accessibleByField);
1372        requestJSON.add("role", role.toJSONString());
1373        if (canViewPath != null) {
1374            requestJSON.add("can_view_path", canViewPath.booleanValue());
1375        }
1376
1377        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
1378        if (notify != null) {
1379            request.addHeader("notify", notify.toString());
1380        }
1381
1382        request.setBody(requestJSON.toString());
1383        BoxJSONResponse response = (BoxJSONResponse) request.send();
1384        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
1385
1386        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
1387        BoxCollaboration.Info info = newCollaboration.new Info(responseJSON);
1388        return info;
1389    }
1390
1391    /**
1392     * Adds a collaborator to this file.
1393     *
1394     * @param collaborator the collaborator to add.
1395     * @param role         the role of the collaborator.
1396     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1397     * @param canViewPath  whether view path collaboration feature is enabled or not.
1398     * @return info about the new collaboration.
1399     */
1400    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1401                                             Boolean notify, Boolean canViewPath) {
1402        JsonObject accessibleByField = new JsonObject();
1403        accessibleByField.add("id", collaborator.getID());
1404
1405        if (collaborator instanceof BoxUser) {
1406            accessibleByField.add("type", "user");
1407        } else if (collaborator instanceof BoxGroup) {
1408            accessibleByField.add("type", "group");
1409        } else {
1410            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
1411        }
1412        return this.collaborate(accessibleByField, role, notify, canViewPath);
1413    }
1414
1415
1416    /**
1417     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1418     * account.
1419     *
1420     * @param email the email address of the collaborator to add.
1421     * @param role  the role of the collaborator.
1422     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1423     * @param canViewPath  whether view path collaboration feature is enabled or not.
1424     * @return info about the new collaboration.
1425     */
1426    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1427                                             Boolean notify, Boolean canViewPath) {
1428        JsonObject accessibleByField = new JsonObject();
1429        accessibleByField.add("login", email);
1430        accessibleByField.add("type", "user");
1431
1432        return this.collaborate(accessibleByField, role, notify, canViewPath);
1433    }
1434
1435    /**
1436     * Used to retrieve all collaborations associated with the item.
1437     *
1438     * @param fields the optional fields to retrieve.
1439     * @return An iterable of metadata instances associated with the item.
1440     */
1441    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1442        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
1443                GET_COLLABORATORS_PAGE_SIZE, fields);
1444
1445    }
1446}