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     * @deprecated This method will be removed in future versions of the SDK; use canUploadVersion(String, long) instead
522     */
523    @Deprecated
524    public void canUploadVersion(String name, long fileSize, String parentID) {
525        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
526        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
527
528        JsonObject parent = new JsonObject();
529        parent.add("id", parentID);
530
531        JsonObject preflightInfo = new JsonObject();
532        preflightInfo.add("parent", parent);
533        if (name != null) {
534            preflightInfo.add("name", name);
535        }
536
537        preflightInfo.add("size", fileSize);
538
539        request.setBody(preflightInfo.toString());
540        BoxAPIResponse response = request.send();
541        response.disconnect();
542    }
543
544    /**
545     * Checks if a new version of the file can be uploaded with the specified name.
546     * @param name the new name for the file.
547     * @return whether or not the file version can be uploaded.
548     */
549    public boolean canUploadVersion(String name) {
550        return this.canUploadVersion(name, 0);
551    }
552
553    /**
554     * Checks if a new version of the file can be uploaded with the specified name and size.
555     * @param name the new name for the file.
556     * @param fileSize the size of the new version content in bytes.
557     * @return whether or not the file version can be uploaded.
558     */
559    public boolean canUploadVersion(String name, long fileSize) {
560
561        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
562        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
563
564        JsonObject preflightInfo = new JsonObject();
565        if (name != null) {
566            preflightInfo.add("name", name);
567        }
568
569        preflightInfo.add("size", fileSize);
570
571        request.setBody(preflightInfo.toString());
572        try {
573            BoxAPIResponse response = request.send();
574
575            return response.getResponseCode() == 200;
576        } catch (BoxAPIException ex) {
577
578            if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) {
579                // This looks like an error response, menaing the upload would fail
580                return false;
581            } else {
582                // This looks like a network error or server error, rethrow exception
583                throw ex;
584            }
585        }
586    }
587
588    /**
589     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
590     * will be able to view and recover previous versions of the file.
591     *
592     * @param fileContent a stream containing the new file contents.
593     * @deprecated use uploadNewVersion() instead.
594     */
595    @Deprecated
596    public void uploadVersion(InputStream fileContent) {
597        this.uploadVersion(fileContent, null);
598    }
599
600    /**
601     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
602     * will be able to view and recover previous versions of the file.
603     *
604     * @param fileContent     a stream containing the new file contents.
605     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
606     * @deprecated use uploadNewVersion() instead.
607     */
608    @Deprecated
609    public void uploadVersion(InputStream fileContent, String fileContentSHA1) {
610        this.uploadVersion(fileContent, fileContentSHA1, null);
611    }
612
613    /**
614     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
615     * will be able to view and recover previous versions of the file.
616     *
617     * @param fileContent     a stream containing the new file contents.
618     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
619     * @param modified        the date that the new version was modified.
620     * @deprecated use uploadNewVersion() instead.
621     */
622    @Deprecated
623    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
624        this.uploadVersion(fileContent, fileContentSHA1, modified, 0, null);
625    }
626
627    /**
628     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
629     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
630     * of the file.
631     *
632     * @param fileContent a stream containing the new file contents.
633     * @param modified    the date that the new version was modified.
634     * @param fileSize    the size of the file used for determining the progress of the upload.
635     * @param listener    a listener for monitoring the upload's progress.
636     * @deprecated use uploadNewVersion() instead.
637     */
638    @Deprecated
639    public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) {
640        this.uploadVersion(fileContent, null, modified, fileSize, listener);
641    }
642
643    /**
644     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
645     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
646     * of the file.
647     *
648     * @param fileContent     a stream containing the new file contents.
649     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
650     * @param modified        the date that the new version was modified.
651     * @param fileSize        the size of the file used for determining the progress of the upload.
652     * @param listener        a listener for monitoring the upload's progress.
653     * @deprecated use uploadNewVersion() instead.
654     */
655    @Deprecated
656    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
657                              ProgressListener listener) {
658        this.uploadNewVersion(fileContent, fileContentSHA1, modified, fileSize, listener);
659        return;
660    }
661
662    /**
663     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
664     * will be able to view and recover previous versions of the file.
665     *
666     * @param fileContent a stream containing the new file contents.
667     * @return the uploaded file version.
668     */
669    public BoxFile.Info uploadNewVersion(InputStream fileContent) {
670        return this.uploadNewVersion(fileContent, null);
671    }
672
673    /**
674     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
675     * will be able to view and recover previous versions of the file.
676     *
677     * @param fileContent     a stream containing the new file contents.
678     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
679     * @return the uploaded file version.
680     */
681    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) {
682        return this.uploadNewVersion(fileContent, fileContentSHA1, null);
683    }
684
685    /**
686     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
687     * will be able to view and recover previous versions of the file.
688     *
689     * @param fileContent     a stream containing the new file contents.
690     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
691     * @param modified        the date that the new version was modified.
692     * @return the uploaded file version.
693     */
694    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
695        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null);
696    }
697
698    /**
699     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
700     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
701     * of the file.
702     *
703     * @param fileContent a stream containing the new file contents.
704     * @param modified    the date that the new version was modified.
705     * @param fileSize    the size of the file used for determining the progress of the upload.
706     * @param listener    a listener for monitoring the upload's progress.
707     * @return the uploaded file version.
708     */
709    public BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize,
710                                         ProgressListener listener) {
711        return this.uploadNewVersion(fileContent, null, modified, fileSize, listener);
712    }
713
714    /**
715     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
716     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
717     * of the file.
718     *
719     * @param fileContent     a stream containing the new file contents.
720     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
721     * @param modified        the date that the new version was modified.
722     * @param fileSize        the size of the file used for determining the progress of the upload.
723     * @param listener        a listener for monitoring the upload's progress.
724     * @return the uploaded file version.
725     */
726    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
727                              ProgressListener listener) {
728        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
729        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
730
731        if (fileSize > 0) {
732            request.setFile(fileContent, "", fileSize);
733        } else {
734            request.setFile(fileContent, "");
735        }
736
737        if (fileContentSHA1 != null) {
738            request.setContentSHA1(fileContentSHA1);
739        }
740
741        if (modified != null) {
742            request.putField("content_modified_at", modified);
743        }
744
745        BoxJSONResponse response;
746        if (listener == null) {
747            response = (BoxJSONResponse) request.send();
748        } else {
749            response = (BoxJSONResponse) request.send(listener);
750        }
751
752        return new BoxFile.Info(response.getJSON());
753    }
754
755    /**
756     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
757     * preview session will expire after 60 minutes.
758     *
759     * @return the expiring preview link
760     */
761    public URL getPreviewLink() {
762        BoxFile.Info info = this.getInfo("expiring_embed_link");
763
764        return info.getPreviewLink();
765    }
766
767
768    /**
769     * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128,
770     * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned
771     * in the .jpg format.
772     *
773     * @param fileType  either PNG of JPG
774     * @param minWidth  minimum width
775     * @param minHeight minimum height
776     * @param maxWidth  maximum width
777     * @param maxHeight maximum height
778     * @return the byte array of the thumbnail image
779     */
780    public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) {
781        QueryStringBuilder builder = new QueryStringBuilder();
782        builder.appendParam("min_width", minWidth);
783        builder.appendParam("min_height", minHeight);
784        builder.appendParam("max_width", maxWidth);
785        builder.appendParam("max_height", maxHeight);
786
787        URLTemplate template;
788        if (fileType == ThumbnailFileType.PNG) {
789            template = GET_THUMBNAIL_PNG_TEMPLATE;
790        } else if (fileType == ThumbnailFileType.JPG) {
791            template = GET_THUMBNAIL_JPG_TEMPLATE;
792        } else {
793            throw new BoxAPIException("Unsupported thumbnail file type");
794        }
795        URL url = template.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
796
797        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
798        BoxAPIResponse response = request.send();
799
800        ByteArrayOutputStream thumbOut = new ByteArrayOutputStream();
801        InputStream body = response.getBody();
802        byte[] buffer = new byte[BUFFER_SIZE];
803        try {
804            int n = body.read(buffer);
805            while (n != -1) {
806                thumbOut.write(buffer, 0, n);
807                n = body.read(buffer);
808            }
809        } catch (IOException e) {
810            throw new BoxAPIException("Error reading thumbnail bytes from response body", e);
811        } finally {
812            response.disconnect();
813        }
814
815        return thumbOut.toByteArray();
816    }
817
818    /**
819     * Gets a list of any comments on this file.
820     *
821     * @return a list of comments on this file.
822     */
823    public List<BoxComment.Info> getComments() {
824        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
825        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
826        BoxJSONResponse response = (BoxJSONResponse) request.send();
827        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
828
829        int totalCount = responseJSON.get("total_count").asInt();
830        List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount);
831        JsonArray entries = responseJSON.get("entries").asArray();
832        for (JsonValue value : entries) {
833            JsonObject commentJSON = value.asObject();
834            BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
835            BoxComment.Info info = comment.new Info(commentJSON);
836            comments.add(info);
837        }
838
839        return comments;
840    }
841
842    /**
843     * Gets a list of any tasks on this file.
844     *
845     * @return a list of tasks on this file.
846     */
847    public List<BoxTask.Info> getTasks() {
848        URL url = GET_TASKS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
849        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
850        BoxJSONResponse response = (BoxJSONResponse) request.send();
851        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
852
853        int totalCount = responseJSON.get("total_count").asInt();
854        List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount);
855        JsonArray entries = responseJSON.get("entries").asArray();
856        for (JsonValue value : entries) {
857            JsonObject taskJSON = value.asObject();
858            BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
859            BoxTask.Info info = task.new Info(taskJSON);
860            tasks.add(info);
861        }
862
863        return tasks;
864    }
865
866    /**
867     * Creates metadata on this file in the global properties template.
868     *
869     * @param metadata The new metadata values.
870     * @return the metadata returned from the server.
871     */
872    public Metadata createMetadata(Metadata metadata) {
873        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
874    }
875
876    /**
877     * Creates metadata on this file in the specified template type.
878     *
879     * @param typeName the metadata template type name.
880     * @param metadata the new metadata values.
881     * @return the metadata returned from the server.
882     */
883    public Metadata createMetadata(String typeName, Metadata metadata) {
884        String scope = Metadata.scopeBasedOnType(typeName);
885        return this.createMetadata(typeName, scope, metadata);
886    }
887
888    /**
889     * Creates metadata on this file in the specified template type.
890     *
891     * @param typeName the metadata template type name.
892     * @param scope    the metadata scope (global or enterprise).
893     * @param metadata the new metadata values.
894     * @return the metadata returned from the server.
895     */
896    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
897        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
898        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
899        request.addHeader("Content-Type", "application/json");
900        request.setBody(metadata.toString());
901        BoxJSONResponse response = (BoxJSONResponse) request.send();
902        return new Metadata(JsonObject.readFrom(response.getJSON()));
903    }
904
905    /**
906     * Locks a file.
907     *
908     * @return the lock returned from the server.
909     */
910    public BoxLock lock() {
911        return this.lock(null, false);
912    }
913
914    /**
915     * Locks a file.
916     *
917     * @param isDownloadPrevented is downloading of file prevented when locked.
918     * @return the lock returned from the server.
919     */
920    public BoxLock lock(boolean isDownloadPrevented) {
921        return this.lock(null, isDownloadPrevented);
922    }
923
924    /**
925     * Locks a file.
926     *
927     * @param expiresAt expiration date of the lock.
928     * @return the lock returned from the server.
929     */
930    public BoxLock lock(Date expiresAt) {
931        return this.lock(expiresAt, false);
932    }
933
934    /**
935     * Locks a file.
936     *
937     * @param expiresAt           expiration date of the lock.
938     * @param isDownloadPrevented is downloading of file prevented when locked.
939     * @return the lock returned from the server.
940     */
941    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
942        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
943        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
944        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
945
946        JsonObject lockConfig = new JsonObject();
947        lockConfig.add("type", "lock");
948        if (expiresAt != null) {
949            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
950        }
951        lockConfig.add("is_download_prevented", isDownloadPrevented);
952
953        JsonObject requestJSON = new JsonObject();
954        requestJSON.add("lock", lockConfig);
955        request.setBody(requestJSON.toString());
956
957        BoxJSONResponse response = (BoxJSONResponse) request.send();
958
959        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
960        JsonValue lockValue = responseJSON.get("lock");
961        JsonObject lockJSON = JsonObject.readFrom(lockValue.toString());
962
963        return new BoxLock(lockJSON, this.getAPI());
964    }
965
966    /**
967     * Unlocks a file.
968     */
969    public void unlock() {
970        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
971        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
972        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
973
974        JsonObject lockObject = new JsonObject();
975        lockObject.add("lock", JsonObject.NULL);
976
977        request.setBody(lockObject.toString());
978        request.send();
979    }
980
981    /**
982     * Used to retrieve all metadata associated with the file.
983     *
984     * @param fields the optional fields to retrieve.
985     * @return An iterable of metadata instances associated with the file.
986     */
987    public Iterable<Metadata> getAllMetadata(String... fields) {
988        return Metadata.getAllMetadata(this, fields);
989    }
990
991    /**
992     * Gets the file properties metadata.
993     *
994     * @return the metadata returned from the server.
995     */
996    public Metadata getMetadata() {
997        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
998    }
999
1000    /**
1001     * Gets the file metadata of specified template type.
1002     *
1003     * @param typeName the metadata template type name.
1004     * @return the metadata returned from the server.
1005     */
1006    public Metadata getMetadata(String typeName) {
1007        String scope = Metadata.scopeBasedOnType(typeName);
1008        return this.getMetadata(typeName, scope);
1009    }
1010
1011    /**
1012     * Gets the file metadata of specified template type.
1013     *
1014     * @param typeName the metadata template type name.
1015     * @param scope    the metadata scope (global or enterprise).
1016     * @return the metadata returned from the server.
1017     */
1018    public Metadata getMetadata(String typeName, String scope) {
1019        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1020        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
1021        BoxJSONResponse response = (BoxJSONResponse) request.send();
1022        return new Metadata(JsonObject.readFrom(response.getJSON()));
1023    }
1024
1025    /**
1026     * Updates the file metadata.
1027     *
1028     * @param metadata the new metadata values.
1029     * @return the metadata returned from the server.
1030     */
1031    public Metadata updateMetadata(Metadata metadata) {
1032        String scope;
1033        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
1034            scope = Metadata.GLOBAL_METADATA_SCOPE;
1035        } else {
1036            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
1037        }
1038
1039        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(),
1040                scope, metadata.getTemplateName());
1041        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1042        request.addHeader("Content-Type", "application/json-patch+json");
1043        request.setBody(metadata.getPatch());
1044        BoxJSONResponse response = (BoxJSONResponse) request.send();
1045        return new Metadata(JsonObject.readFrom(response.getJSON()));
1046    }
1047
1048    /**
1049     * Deletes the file properties metadata.
1050     */
1051    public void deleteMetadata() {
1052        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1053    }
1054
1055    /**
1056     * Deletes the file metadata of specified template type.
1057     *
1058     * @param typeName the metadata template type name.
1059     */
1060    public void deleteMetadata(String typeName) {
1061        String scope = Metadata.scopeBasedOnType(typeName);
1062        this.deleteMetadata(typeName, scope);
1063    }
1064
1065    /**
1066     * Deletes the file metadata of specified template type.
1067     *
1068     * @param typeName the metadata template type name.
1069     * @param scope    the metadata scope (global or enterprise).
1070     */
1071    public void deleteMetadata(String typeName, String scope) {
1072        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1073        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1074        request.send();
1075    }
1076
1077    /**
1078     * Used to retrieve the watermark for the file.
1079     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
1080     *
1081     * @param fields the fields to retrieve.
1082     * @return the watermark associated with the file.
1083     */
1084    public BoxWatermark getWatermark(String... fields) {
1085        return this.getWatermark(FILE_URL_TEMPLATE, fields);
1086    }
1087
1088    /**
1089     * Used to apply or update the watermark for the file.
1090     *
1091     * @return the watermark associated with the file.
1092     */
1093    public BoxWatermark applyWatermark() {
1094        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
1095    }
1096
1097    /**
1098     * Removes a watermark from the file.
1099     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
1100     */
1101    public void removeWatermark() {
1102        this.removeWatermark(FILE_URL_TEMPLATE);
1103    }
1104
1105    /**
1106     * {@inheritDoc}
1107     */
1108    @Override
1109    public BoxFile.Info setCollections(BoxCollection... collections) {
1110        JsonArray jsonArray = new JsonArray();
1111        for (BoxCollection collection : collections) {
1112            JsonObject collectionJSON = new JsonObject();
1113            collectionJSON.add("id", collection.getID());
1114            jsonArray.add(collectionJSON);
1115        }
1116        JsonObject infoJSON = new JsonObject();
1117        infoJSON.add("collections", jsonArray);
1118
1119        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
1120        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1121        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1122        request.setBody(infoJSON.toString());
1123        BoxJSONResponse response = (BoxJSONResponse) request.send();
1124        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1125        return new Info(jsonObject);
1126    }
1127
1128    /**
1129     * Creates an upload session to create a new version of a file in chunks.
1130     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1131     * @param fileSize the size of the file that will be uploaded.
1132     * @return the created upload session instance.
1133     */
1134    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1135        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1136
1137        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1138        request.addHeader("Content-Type", "application/json");
1139
1140        JsonObject body = new JsonObject();
1141        body.add("file_size", fileSize);
1142        request.setBody(body.toString());
1143
1144        BoxJSONResponse response = (BoxJSONResponse) request.send();
1145        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1146
1147        String sessionId = jsonObject.get("id").asString();
1148        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1149        return session.new Info(jsonObject);
1150    }
1151
1152    /**
1153     * Creates a new version of a file.
1154     * @param inputStream the stream instance that contains the data.
1155     * @param fileSize the size of the file that will be uploaded.
1156     * @return the created file instance.
1157     * @throws InterruptedException when a thread execution is interrupted.
1158     * @throws IOException when reading a stream throws exception.
1159     */
1160    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1161        throws InterruptedException, IOException {
1162        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1163        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
1164    }
1165
1166    /**
1167     * Creates a new version of a file using specified number of parallel http connections.
1168     * @param inputStream the stream instance that contains the data.
1169     * @param fileSize the size of the file that will be uploaded.
1170     * @param nParallelConnections number of parallel http connections to use
1171     * @param timeOut time to wait before killing the job
1172     * @param unit time unit for the time wait value
1173     * @return the created file instance.
1174     * @throws InterruptedException when a thread execution is interrupted.
1175     * @throws IOException when reading a stream throws exception.
1176     */
1177    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1178                                        int nParallelConnections, long timeOut, TimeUnit unit)
1179        throws InterruptedException, IOException {
1180        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1181        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1182            .upload(this.getAPI(), inputStream, url, fileSize);
1183    }
1184
1185    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1186                                              Boolean notify, Boolean canViewPath) {
1187
1188        JsonObject itemField = new JsonObject();
1189        itemField.add("id", this.getID());
1190        itemField.add("type", "file");
1191
1192        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath);
1193    }
1194
1195    /**
1196     * Adds a collaborator to this file.
1197     *
1198     * @param collaborator the collaborator to add.
1199     * @param role         the role of the collaborator.
1200     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1201     * @param canViewPath  whether view path collaboration feature is enabled or not.
1202     * @return info about the new collaboration.
1203     */
1204    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1205                                             Boolean notify, Boolean canViewPath) {
1206        JsonObject accessibleByField = new JsonObject();
1207        accessibleByField.add("id", collaborator.getID());
1208
1209        if (collaborator instanceof BoxUser) {
1210            accessibleByField.add("type", "user");
1211        } else if (collaborator instanceof BoxGroup) {
1212            accessibleByField.add("type", "group");
1213        } else {
1214            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
1215        }
1216        return this.collaborate(accessibleByField, role, notify, canViewPath);
1217    }
1218
1219
1220    /**
1221     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1222     * account.
1223     *
1224     * @param email the email address of the collaborator to add.
1225     * @param role  the role of the collaborator.
1226     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1227     * @param canViewPath  whether view path collaboration feature is enabled or not.
1228     * @return info about the new collaboration.
1229     */
1230    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1231                                             Boolean notify, Boolean canViewPath) {
1232        JsonObject accessibleByField = new JsonObject();
1233        accessibleByField.add("login", email);
1234        accessibleByField.add("type", "user");
1235
1236        return this.collaborate(accessibleByField, role, notify, canViewPath);
1237    }
1238
1239    /**
1240     * Used to retrieve all collaborations associated with the item.
1241     *
1242     * @param fields the optional fields to retrieve.
1243     * @return An iterable of metadata instances associated with the item.
1244     */
1245    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1246        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
1247                GET_COLLABORATORS_PAGE_SIZE, fields);
1248
1249    }
1250
1251    /**
1252     * Contains information about a BoxFile.
1253     */
1254    public class Info extends BoxItem.Info {
1255        private String sha1;
1256        private String versionNumber;
1257        private long commentCount;
1258        private EnumSet<Permission> permissions;
1259        private String extension;
1260        private boolean isPackage;
1261        private BoxFileVersion version;
1262        private URL previewLink;
1263        private BoxLock lock;
1264        private boolean isWatermarked;
1265        private JsonObject metadata;
1266        private Map<String, Map<String, Metadata>> metadataMap;
1267        private List<Representation> representations;
1268
1269        /**
1270         * Constructs an empty Info object.
1271         */
1272        public Info() {
1273            super();
1274        }
1275
1276        /**
1277         * Constructs an Info object by parsing information from a JSON string.
1278         *
1279         * @param json the JSON string to parse.
1280         */
1281        public Info(String json) {
1282            super(json);
1283        }
1284
1285        /**
1286         * Constructs an Info object using an already parsed JSON object.
1287         *
1288         * @param jsonObject the parsed JSON object.
1289         */
1290        public Info(JsonObject jsonObject) {
1291            super(jsonObject);
1292        }
1293
1294        @Override
1295        public BoxFile getResource() {
1296            return BoxFile.this;
1297        }
1298
1299        /**
1300         * Gets the SHA1 hash of the file.
1301         *
1302         * @return the SHA1 hash of the file.
1303         */
1304        public String getSha1() {
1305            return this.sha1;
1306        }
1307
1308        /**
1309         * Gets the lock of the file.
1310         *
1311         * @return the lock of the file.
1312         */
1313        public BoxLock getLock() {
1314            return this.lock;
1315        }
1316
1317        /**
1318         * Gets the current version number of the file.
1319         *
1320         * @return the current version number of the file.
1321         */
1322        public String getVersionNumber() {
1323            return this.versionNumber;
1324        }
1325
1326        /**
1327         * Gets the number of comments on the file.
1328         *
1329         * @return the number of comments on the file.
1330         */
1331        public long getCommentCount() {
1332            return this.commentCount;
1333        }
1334
1335        /**
1336         * Gets the permissions that the current user has on the file.
1337         *
1338         * @return the permissions that the current user has on the file.
1339         */
1340        public EnumSet<Permission> getPermissions() {
1341            return this.permissions;
1342        }
1343
1344        /**
1345         * Gets the extension suffix of the file, excluding the dot.
1346         *
1347         * @return the extension of the file.
1348         */
1349        public String getExtension() {
1350            return this.extension;
1351        }
1352
1353        /**
1354         * Gets whether or not the file is an OSX package.
1355         *
1356         * @return true if the file is an OSX package; otherwise false.
1357         */
1358        public boolean getIsPackage() {
1359            return this.isPackage;
1360        }
1361
1362        /**
1363         * Gets the current version details of the file.
1364         *
1365         * @return the current version details of the file.
1366         */
1367        public BoxFileVersion getVersion() {
1368            return this.version;
1369        }
1370
1371        /**
1372         * Gets the current expiring preview link.
1373         *
1374         * @return the expiring preview link
1375         */
1376        public URL getPreviewLink() {
1377            return this.previewLink;
1378        }
1379
1380        /**
1381         * Gets flag indicating whether this file is Watermarked.
1382         *
1383         * @return whether the file is watermarked or not
1384         */
1385        public boolean getIsWatermarked() {
1386            return this.isWatermarked;
1387        }
1388
1389        /**
1390         * Gets the metadata on this file associated with a specified scope and template.
1391         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found
1392         * then makes an API call to get metadata
1393         * @param   templateName    the metadata template type name.
1394         * @param   scope           the scope of the template (usually "global" or "enterprise").
1395         * @return                  the metadata returned from the server.
1396         */
1397        public Metadata getMetadata(String templateName, String scope) {
1398            try {
1399                return this.metadataMap.get(scope).get(templateName);
1400            } catch (NullPointerException e) {
1401                return null;
1402            }
1403        }
1404
1405        /**
1406         * Get file's representations.
1407         * @return list of representations
1408         */
1409        public List<Representation> getRepresentations() {
1410            return this.representations;
1411        }
1412
1413        @Override
1414        protected void parseJSONMember(JsonObject.Member member) {
1415            super.parseJSONMember(member);
1416
1417            String memberName = member.getName();
1418            JsonValue value = member.getValue();
1419            if (memberName.equals("sha1")) {
1420                this.sha1 = value.asString();
1421            } else if (memberName.equals("version_number")) {
1422                this.versionNumber = value.asString();
1423            } else if (memberName.equals("comment_count")) {
1424                this.commentCount = value.asLong();
1425            } else if (memberName.equals("permissions")) {
1426                this.permissions = this.parsePermissions(value.asObject());
1427            } else if (memberName.equals("extension")) {
1428                this.extension = value.asString();
1429            } else if (memberName.equals("is_package")) {
1430                this.isPackage = value.asBoolean();
1431            } else if (memberName.equals("file_version")) {
1432                this.version = this.parseFileVersion(value.asObject());
1433            } else if (memberName.equals("expiring_embed_link")) {
1434                try {
1435                    String urlString = member.getValue().asObject().get("url").asString();
1436                    this.previewLink = new URL(urlString);
1437                } catch (MalformedURLException e) {
1438                    throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
1439                }
1440            } else if (memberName.equals("lock")) {
1441                if (value.isNull()) {
1442                    this.lock = null;
1443                } else {
1444                    this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
1445                }
1446            } else if (memberName.equals("watermark_info")) {
1447                JsonObject jsonObject = value.asObject();
1448                this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1449            } else if (memberName.equals("metadata")) {
1450                JsonObject jsonObject = value.asObject();
1451                this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1452            } else if (memberName.equals("representations")) {
1453                JsonObject jsonObject = value.asObject();
1454                this.representations = Parsers.parseRepresentations(jsonObject);
1455            }
1456        }
1457
1458        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1459            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1460            for (JsonObject.Member member : jsonObject) {
1461                JsonValue value = member.getValue();
1462                if (value.isNull() || !value.asBoolean()) {
1463                    continue;
1464                }
1465
1466                String memberName = member.getName();
1467                if (memberName.equals("can_download")) {
1468                    permissions.add(Permission.CAN_DOWNLOAD);
1469                } else if (memberName.equals("can_upload")) {
1470                    permissions.add(Permission.CAN_UPLOAD);
1471                } else if (memberName.equals("can_rename")) {
1472                    permissions.add(Permission.CAN_RENAME);
1473                } else if (memberName.equals("can_delete")) {
1474                    permissions.add(Permission.CAN_DELETE);
1475                } else if (memberName.equals("can_share")) {
1476                    permissions.add(Permission.CAN_SHARE);
1477                } else if (memberName.equals("can_set_share_access")) {
1478                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1479                } else if (memberName.equals("can_preview")) {
1480                    permissions.add(Permission.CAN_PREVIEW);
1481                } else if (memberName.equals("can_comment")) {
1482                    permissions.add(Permission.CAN_COMMENT);
1483                }
1484            }
1485
1486            return permissions;
1487        }
1488
1489        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
1490            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
1491        }
1492    }
1493
1494    /**
1495     * Enumerates the possible permissions that a user can have on a file.
1496     */
1497    public enum Permission {
1498        /**
1499         * The user can download the file.
1500         */
1501        CAN_DOWNLOAD("can_download"),
1502
1503        /**
1504         * The user can upload new versions of the file.
1505         */
1506        CAN_UPLOAD("can_upload"),
1507
1508        /**
1509         * The user can rename the file.
1510         */
1511        CAN_RENAME("can_rename"),
1512
1513        /**
1514         * The user can delete the file.
1515         */
1516        CAN_DELETE("can_delete"),
1517
1518        /**
1519         * The user can share the file.
1520         */
1521        CAN_SHARE("can_share"),
1522
1523        /**
1524         * The user can set the access level for shared links to the file.
1525         */
1526        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1527
1528        /**
1529         * The user can preview the file.
1530         */
1531        CAN_PREVIEW("can_preview"),
1532
1533        /**
1534         * The user can comment on the file.
1535         */
1536        CAN_COMMENT("can_comment");
1537
1538        private final String jsonValue;
1539
1540        private Permission(String jsonValue) {
1541            this.jsonValue = jsonValue;
1542        }
1543
1544        static Permission fromJSONValue(String jsonValue) {
1545            return Permission.valueOf(jsonValue.toUpperCase());
1546        }
1547
1548        String toJSONValue() {
1549            return this.jsonValue;
1550        }
1551    }
1552
1553}