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