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.http.HttpMethod;
021import com.box.sdk.internal.utils.Parsers;
022import com.eclipsesource.json.JsonArray;
023import com.eclipsesource.json.JsonObject;
024import com.eclipsesource.json.JsonValue;
025
026
027/**
028 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
029 * perform other common file operations (move, copy, delete, etc.).
030 *
031 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
032 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
033 * handling for errors related to the Box REST API, you should capture this exception explicitly.
034 */
035@BoxResourceType("file")
036public class BoxFile extends BoxItem {
037
038    /**
039     * An array of all possible file fields that can be requested when calling {@link #getInfo()}.
040     */
041    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
042                                               "description", "size", "path_collection", "created_at", "modified_at",
043                                               "trashed_at", "purged_at", "content_created_at", "content_modified_at",
044                                               "created_by", "modified_by", "owned_by", "shared_link", "parent",
045                                               "item_status", "version_number", "comment_count", "permissions", "tags",
046                                               "lock", "extension", "is_package", "file_version", "collections",
047                                               "watermark_info", "metadata", "representations",
048                                               "is_external_only", "expiring_embed_link", "allowed_invitee_roles",
049                                               "has_collaborations"};
050
051    /**
052     * Used to specify what filetype to request for a file thumbnail.
053     */
054    public enum ThumbnailFileType {
055        /**
056         * PNG image format.
057         */
058        PNG,
059
060        /**
061         * JPG image format.
062         */
063        JPG
064    }
065
066    /**
067     * File URL Template.
068     */
069    public static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
070    /**
071     * Content URL Template.
072     */
073    public static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
074    /**
075     * Versions URL Template.
076     */
077    public static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
078    /**
079     * Copy URL Template.
080     */
081    public static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
082    /**
083     * Add Comment URL Template.
084     */
085    public static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
086    /**
087     * Get Comments URL Template.
088     */
089    public static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
090    /**
091     * Metadata URL Template.
092     */
093    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s/%s");
094    /**
095     * Add Task URL Template.
096     */
097    public static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
098    /**
099     * Get Tasks URL Template.
100     */
101    public static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
102    /**
103     * Get Thumbnail PNG Template.
104     */
105    public static final URLTemplate GET_THUMBNAIL_PNG_TEMPLATE = new URLTemplate("files/%s/thumbnail.png");
106    /**
107     * Get Thumbnail JPG Template.
108     */
109    public static final URLTemplate GET_THUMBNAIL_JPG_TEMPLATE = new URLTemplate("files/%s/thumbnail.jpg");
110    /**
111     * Upload Session URL Template.
112     */
113    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/%s/upload_sessions");
114    /**
115     * Upload Session Status URL Template.
116     */
117    public static final URLTemplate UPLOAD_SESSION_STATUS_URL_TEMPLATE = new URLTemplate(
118            "files/upload_sessions/%s/status");
119    /**
120     * Abort Upload Session URL Template.
121     */
122    public static final URLTemplate ABORT_UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions/%s");
123    /**
124     * Add Collaborations URL Template.
125     */
126    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
127    /**
128     * Get All File Collaborations URL Template.
129     */
130    public static final URLTemplate GET_ALL_FILE_COLLABORATIONS_URL = new URLTemplate("files/%s/collaborations");
131    private static final int BUFFER_SIZE = 8192;
132    private static final int GET_COLLABORATORS_PAGE_SIZE = 1000;
133
134    /**
135     * Constructs a BoxFile for a file with a given ID.
136     *
137     * @param api the API connection to be used by the file.
138     * @param id  the ID of the file.
139     */
140    public BoxFile(BoxAPIConnection api, String id) {
141        super(api, id);
142    }
143
144    /**
145     * {@inheritDoc}
146     */
147    @Override
148    protected URL getItemURL() {
149        return FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
150    }
151
152    @Override
153    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
154                                          BoxSharedLink.Permissions permissions) {
155
156        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
157        Info info = new Info();
158        info.setSharedLink(sharedLink);
159
160        this.updateInfo(info);
161        return info.getSharedLink();
162    }
163
164    /**
165     * Creates new SharedLink for a BoxFile with a password.
166     *
167     * @param access        The access level of the shared link.
168     * @param unshareDate   A specified date to unshare the Box file.
169     * @param permissions   The permissions to set on the shared link for the Box file.
170     * @param password      Password set on the shared link to give access to the Box file.
171     * @return information about the newly created shared link.
172     */
173    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
174                                          BoxSharedLink.Permissions permissions, String password) {
175
176        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions, password);
177        Info info = new Info();
178        info.setSharedLink(sharedLink);
179
180        this.updateInfo(info);
181        return info.getSharedLink();
182    }
183
184    /**
185     * Adds new {@link BoxWebHook} to this {@link BoxFile}.
186     *
187     * @param address  {@link BoxWebHook.Info#getAddress()}
188     * @param triggers {@link BoxWebHook.Info#getTriggers()}
189     * @return created {@link BoxWebHook.Info}
190     */
191    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
192        return BoxWebHook.create(this, address, triggers);
193    }
194
195    /**
196     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
197     * within the message, where userid and username are the ID and username of the person being mentioned.
198     *
199     * @param message the comment's message.
200     * @return information about the newly added comment.
201     * @see <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
202     * for including @mentions.</a>
203     */
204    public BoxComment.Info addComment(String message) {
205        JsonObject itemJSON = new JsonObject();
206        itemJSON.add("type", "file");
207        itemJSON.add("id", this.getID());
208
209        JsonObject requestJSON = new JsonObject();
210        requestJSON.add("item", itemJSON);
211        if (BoxComment.messageContainsMention(message)) {
212            requestJSON.add("tagged_message", message);
213        } else {
214            requestJSON.add("message", message);
215        }
216
217        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
218        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
219        request.setBody(requestJSON.toString());
220        BoxJSONResponse response = (BoxJSONResponse) request.send();
221        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
222
223        BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
224        return addedComment.new Info(responseJSON);
225    }
226
227    /**
228     * Adds a new task to this file. The task can have an optional message to include, and a due date.
229     *
230     * @param action  the action the task assignee will be prompted to do.
231     * @param message an optional message to include with the task.
232     * @param dueAt   the day at which this task is due.
233     * @return information about the newly added task.
234     */
235    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
236        return this.addTask(action, message, dueAt, null);
237    }
238
239    /**
240     * Adds a new task to this file. The task can have an optional message to include, due date,
241     * and task completion rule.
242     *
243     * @param action  the action the task assignee will be prompted to do.
244     * @param message an optional message to include with the task.
245     * @param dueAt   the day at which this task is due.
246     * @param completionRule the rule for completing the task.
247     * @return information about the newly added task.
248     */
249    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt,
250                                BoxTask.CompletionRule completionRule) {
251        JsonObject itemJSON = new JsonObject();
252        itemJSON.add("type", "file");
253        itemJSON.add("id", this.getID());
254
255        JsonObject requestJSON = new JsonObject();
256        requestJSON.add("item", itemJSON);
257        requestJSON.add("action", action.toJSONString());
258
259        if (message != null && !message.isEmpty()) {
260            requestJSON.add("message", message);
261        }
262
263        if (dueAt != null) {
264            requestJSON.add("due_at", BoxDateFormat.format(dueAt));
265        }
266
267        if (completionRule != null) {
268            requestJSON.add("completion_rule", completionRule.toJSONString());
269        }
270
271        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
272        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
273        request.setBody(requestJSON.toString());
274        BoxJSONResponse response = (BoxJSONResponse) request.send();
275        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
276
277        BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
278        return addedTask.new Info(responseJSON);
279    }
280
281    /**
282     * Gets an expiring URL for downloading a file directly from Box. This can be user,
283     * for example, for sending as a redirect to a browser to cause the browser
284     * to download the file directly from Box.
285     *
286     * @return the temporary download URL
287     */
288    public URL getDownloadURL() {
289        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
290        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
291        request.setFollowRedirects(false);
292
293        BoxRedirectResponse response = (BoxRedirectResponse) request.send();
294
295        return response.getRedirectURL();
296    }
297
298    /**
299     * Downloads the contents of this file to a given OutputStream.
300     *
301     * @param output the stream to where the file will be written.
302     */
303    public void download(OutputStream output) {
304        this.download(output, null);
305    }
306
307    /**
308     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
309     *
310     * @param output   the stream to where the file will be written.
311     * @param listener a listener for monitoring the download's progress.
312     */
313    public void download(OutputStream output, ProgressListener listener) {
314        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
315        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
316        BoxAPIResponse response = request.send();
317        InputStream input = response.getBody(listener);
318
319        byte[] buffer = new byte[BUFFER_SIZE];
320        try {
321            int n = input.read(buffer);
322            while (n != -1) {
323                output.write(buffer, 0, n);
324                n = input.read(buffer);
325            }
326        } catch (IOException e) {
327            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
328        } finally {
329            response.disconnect();
330        }
331    }
332
333    /**
334     * Downloads a part of this file's contents, starting at specified byte offset.
335     *
336     * @param output the stream to where the file will be written.
337     * @param offset the byte offset at which to start the download.
338     */
339    public void downloadRange(OutputStream output, long offset) {
340        this.downloadRange(output, offset, -1);
341    }
342
343    /**
344     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
345     *
346     * @param output     the stream to where the file will be written.
347     * @param rangeStart the byte offset at which to start the download.
348     * @param rangeEnd   the byte offset at which to stop the download.
349     */
350    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
351        this.downloadRange(output, rangeStart, rangeEnd, null);
352    }
353
354    /**
355     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
356     * progress to a ProgressListener.
357     *
358     * @param output     the stream to where the file will be written.
359     * @param rangeStart the byte offset at which to start the download.
360     * @param rangeEnd   the byte offset at which to stop the download.
361     * @param listener   a listener for monitoring the download's progress.
362     */
363    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
364        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
365        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
366        if (rangeEnd > 0) {
367            request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart),
368                    Long.toString(rangeEnd)));
369        } else {
370            request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart)));
371        }
372
373        BoxAPIResponse response = request.send();
374        InputStream input = response.getBody(listener);
375
376        byte[] buffer = new byte[BUFFER_SIZE];
377        try {
378            int n = input.read(buffer);
379            while (n != -1) {
380                output.write(buffer, 0, n);
381                n = input.read(buffer);
382            }
383        } catch (IOException e) {
384            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
385        } finally {
386            response.disconnect();
387        }
388    }
389
390    @Override
391    public BoxFile.Info copy(BoxFolder destination) {
392        return this.copy(destination, null);
393    }
394
395    @Override
396    public BoxFile.Info copy(BoxFolder destination, String newName) {
397        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
398
399        JsonObject parent = new JsonObject();
400        parent.add("id", destination.getID());
401
402        JsonObject copyInfo = new JsonObject();
403        copyInfo.add("parent", parent);
404        if (newName != null) {
405            copyInfo.add("name", newName);
406        }
407
408        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
409        request.setBody(copyInfo.toString());
410        BoxJSONResponse response = (BoxJSONResponse) request.send();
411        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
412        BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
413        return copiedFile.new Info(responseJSON);
414    }
415
416    /**
417     * Deletes this file by moving it to the trash.
418     */
419    public void delete() {
420        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
421        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
422        BoxAPIResponse response = request.send();
423        response.disconnect();
424    }
425
426    @Override
427    public BoxItem.Info move(BoxFolder destination) {
428        return this.move(destination, null);
429    }
430
431    @Override
432    public BoxItem.Info move(BoxFolder destination, String newName) {
433        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
434        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
435
436        JsonObject parent = new JsonObject();
437        parent.add("id", destination.getID());
438
439        JsonObject updateInfo = new JsonObject();
440        updateInfo.add("parent", parent);
441        if (newName != null) {
442            updateInfo.add("name", newName);
443        }
444
445        request.setBody(updateInfo.toString());
446        BoxJSONResponse response = (BoxJSONResponse) request.send();
447        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
448        BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
449        return movedFile.new Info(responseJSON);
450    }
451
452    /**
453     * Renames this file.
454     *
455     * @param newName the new name of the file.
456     */
457    public void rename(String newName) {
458        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
459        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
460
461        JsonObject updateInfo = new JsonObject();
462        updateInfo.add("name", newName);
463
464        request.setBody(updateInfo.toString());
465        BoxJSONResponse response = (BoxJSONResponse) request.send();
466        response.getJSON();
467    }
468
469    @Override
470    public BoxFile.Info getInfo() {
471        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
472        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
473        BoxJSONResponse response = (BoxJSONResponse) request.send();
474        return new Info(response.getJSON());
475    }
476
477    @Override
478    public BoxFile.Info getInfo(String... fields) {
479        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
480        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
481
482        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
483        BoxJSONResponse response = (BoxJSONResponse) request.send();
484        return new Info(response.getJSON());
485    }
486
487    /**
488     * Gets information about this item including a specified set of representations.
489     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
490     *
491     * @param representationHints hints for representations to be retrieved
492     * @param fields the fields to retrieve.
493     * @return info about this item containing only the specified fields, including representations.
494     */
495    public BoxFile.Info getInfoWithRepresentations(String representationHints, String... fields) {
496        if (representationHints.matches(Representation.X_REP_HINTS_PATTERN)) {
497            //Since the user intends to get representations, add it to fields, even if user has missed it
498            Set<String> fieldsSet = new HashSet<String>(Arrays.asList(fields));
499            fieldsSet.add("representations");
500            String queryString = new QueryStringBuilder().appendParam("fields",
501                fieldsSet.toArray(new String[fieldsSet.size()])).toString();
502            URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
503
504            BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
505            request.addHeader("X-Rep-Hints", representationHints);
506            BoxJSONResponse response = (BoxJSONResponse) request.send();
507            return new Info(response.getJSON());
508        } else {
509            throw new BoxAPIException("Represention hints is not valid."
510                + " Refer documention on how to construct X-Rep-Hints Header");
511        }
512    }
513
514    /**
515     * Fetches the contents of a file representation and writes them to the provided output stream.
516     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
517     * @param representationHint the X-Rep-Hints query for the representation to fetch.
518     * @param output the output stream to write the contents to.
519     */
520    public void getRepresentationContent(String representationHint, OutputStream output) {
521
522        this.getRepresentationContent(representationHint, "", output);
523    }
524
525    /**
526     * Fetches the contents of a file representation with asset path and writes them to the provided output stream.
527     * @see <a href=https://developer.box.com/reference#section-x-rep-hints-header>X-Rep-Hints Header</a>
528     * @param representationHint the X-Rep-Hints query for the representation to fetch.
529     * @param assetPath the path of the asset for representations containing multiple files.
530     * @param output the output stream to write the contents to.
531     */
532    public void getRepresentationContent(String representationHint, String assetPath, OutputStream output) {
533
534        List<Representation> reps = this.getInfoWithRepresentations(representationHint).getRepresentations();
535        if (reps.size() < 1) {
536            throw new BoxAPIException("No matching representations found");
537        }
538        Representation representation = reps.get(0);
539        String repState = representation.getStatus().getState();
540
541        if (repState.equals("viewable") || repState.equals("success")) {
542
543            this.makeRepresentationContentRequest(representation.getContent().getUrlTemplate(),
544                    assetPath, output);
545            return;
546        } else if (repState.equals("pending") || repState.equals("none")) {
547
548            String repContentURLString = null;
549            while (repContentURLString == null) {
550                repContentURLString = this.pollRepInfo(representation.getInfo().getUrl());
551            }
552
553            this.makeRepresentationContentRequest(repContentURLString, assetPath, output);
554            return;
555
556        } else if (repState.equals("error")) {
557
558            throw new BoxAPIException("Representation had error status");
559        } else {
560
561            throw new BoxAPIException("Representation had unknown status");
562        }
563
564    }
565
566    private String pollRepInfo(URL infoURL) {
567
568        BoxAPIRequest infoRequest = new BoxAPIRequest(this.getAPI(), infoURL, HttpMethod.GET);
569        BoxJSONResponse infoResponse = (BoxJSONResponse) infoRequest.send();
570        JsonObject response = infoResponse.getJsonObject();
571
572        Representation rep = new Representation(response);
573
574        String repState = rep.getStatus().getState();
575
576        if (repState.equals("viewable") || repState.equals("success")) {
577
578            return rep.getContent().getUrlTemplate();
579        } else if (repState.equals("pending") || repState.equals("none")) {
580
581            return null;
582
583        } else if (repState.equals("error")) {
584
585            throw new BoxAPIException("Representation had error status");
586        } else {
587
588            throw new BoxAPIException("Representation had unknown status");
589        }
590    }
591
592    private void makeRepresentationContentRequest(String representationURLTemplate, String assetPath,
593                                                  OutputStream output) {
594
595        try {
596
597            URL repURL = new URL(representationURLTemplate.replace("{+asset_path}", assetPath));
598            BoxAPIRequest repContentReq = new BoxAPIRequest(this.getAPI(), repURL, HttpMethod.GET);
599
600            BoxAPIResponse contentResponse = repContentReq.send();
601
602            InputStream input = contentResponse.getBody();
603
604            byte[] buffer = new byte[BUFFER_SIZE];
605            try {
606                int n = input.read(buffer);
607                while (n != -1) {
608                    output.write(buffer, 0, n);
609                    n = input.read(buffer);
610                }
611            } catch (IOException e) {
612                throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
613            } finally {
614                contentResponse.disconnect();
615            }
616        } catch (MalformedURLException ex) {
617
618            throw new BoxAPIException("Could not generate representation content URL");
619        }
620    }
621
622    /**
623     * Updates the information about this file with any info fields that have been modified locally.
624     *
625     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
626     * code won't update any information (or even send a network request) since none of the info's fields were
627     * changed:</p>
628     *
629     * <pre>BoxFile file = new File(api, id);
630     * BoxFile.Info info = file.getInfo();
631     * file.updateInfo(info);</pre>
632     *
633     * @param info the updated info.
634     */
635    public void updateInfo(BoxFile.Info info) {
636        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
637        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
638        request.setBody(info.getPendingChanges());
639        BoxJSONResponse response = (BoxJSONResponse) request.send();
640        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
641        info.update(jsonObject);
642    }
643
644    /**
645     * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve
646     * previous versions of their files.
647     *
648     * @return a list of previous file versions.
649     */
650    public Collection<BoxFileVersion> getVersions() {
651        URL url = VERSIONS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
652        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
653        BoxJSONResponse response = (BoxJSONResponse) request.send();
654
655        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
656        JsonArray entries = jsonObject.get("entries").asArray();
657        Collection<BoxFileVersion> versions = new ArrayList<BoxFileVersion>();
658        for (JsonValue entry : entries) {
659            versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
660        }
661
662        return versions;
663    }
664
665    /**
666     * Checks if the file can be successfully uploaded by using the preflight check.
667     *
668     * @param name     the name to give the uploaded file or null to use existing name.
669     * @param fileSize the size of the file used for account capacity calculations.
670     * @param parentID the ID of the parent folder that the new version is being uploaded to.
671     * @deprecated This method will be removed in future versions of the SDK; use canUploadVersion(String, long) instead
672     */
673    @Deprecated
674    public void canUploadVersion(String name, long fileSize, String parentID) {
675        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
676        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
677
678        JsonObject parent = new JsonObject();
679        parent.add("id", parentID);
680
681        JsonObject preflightInfo = new JsonObject();
682        preflightInfo.add("parent", parent);
683        if (name != null) {
684            preflightInfo.add("name", name);
685        }
686
687        preflightInfo.add("size", fileSize);
688
689        request.setBody(preflightInfo.toString());
690        BoxAPIResponse response = request.send();
691        response.disconnect();
692    }
693
694    /**
695     * Checks if a new version of the file can be uploaded with the specified name.
696     * @param name the new name for the file.
697     * @return whether or not the file version can be uploaded.
698     */
699    public boolean canUploadVersion(String name) {
700        return this.canUploadVersion(name, 0);
701    }
702
703    /**
704     * Checks if a new version of the file can be uploaded with the specified name and size.
705     * @param name the new name for the file.
706     * @param fileSize the size of the new version content in bytes.
707     * @return whether or not the file version can be uploaded.
708     */
709    public boolean canUploadVersion(String name, long fileSize) {
710
711        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
712        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
713
714        JsonObject preflightInfo = new JsonObject();
715        if (name != null) {
716            preflightInfo.add("name", name);
717        }
718
719        preflightInfo.add("size", fileSize);
720
721        request.setBody(preflightInfo.toString());
722        try {
723            BoxAPIResponse response = request.send();
724
725            return response.getResponseCode() == 200;
726        } catch (BoxAPIException ex) {
727
728            if (ex.getResponseCode() >= 400 && ex.getResponseCode() < 500) {
729                // This looks like an error response, meaning the upload would fail
730                return false;
731            } else {
732                // This looks like a network error or server error, rethrow exception
733                throw ex;
734            }
735        }
736    }
737
738    /**
739     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
740     * will be able to view and recover previous versions of the file.
741     *
742     * @param fileContent a stream containing the new file contents.
743     * @deprecated use uploadNewVersion() instead.
744     */
745    @Deprecated
746    public void uploadVersion(InputStream fileContent) {
747        this.uploadVersion(fileContent, null);
748    }
749
750    /**
751     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
752     * will be able to view and recover previous versions of the file.
753     *
754     * @param fileContent     a stream containing the new file contents.
755     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
756     * @deprecated use uploadNewVersion() instead.
757     */
758    @Deprecated
759    public void uploadVersion(InputStream fileContent, String fileContentSHA1) {
760        this.uploadVersion(fileContent, fileContentSHA1, null);
761    }
762
763    /**
764     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
765     * will be able to view and recover previous versions of the file.
766     *
767     * @param fileContent     a stream containing the new file contents.
768     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
769     * @param modified        the date that the new version was modified.
770     * @deprecated use uploadNewVersion() instead.
771     */
772    @Deprecated
773    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
774        this.uploadVersion(fileContent, fileContentSHA1, modified, 0, null);
775    }
776
777    /**
778     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
779     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
780     * of the file.
781     *
782     * @param fileContent a stream containing the new file contents.
783     * @param modified    the date that the new version was modified.
784     * @param fileSize    the size of the file used for determining the progress of the upload.
785     * @param listener    a listener for monitoring the upload's progress.
786     * @deprecated use uploadNewVersion() instead.
787     */
788    @Deprecated
789    public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) {
790        this.uploadVersion(fileContent, null, modified, fileSize, listener);
791    }
792
793    /**
794     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
795     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
796     * of the file.
797     *
798     * @param fileContent     a stream containing the new file contents.
799     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
800     * @param modified        the date that the new version was modified.
801     * @param fileSize        the size of the file used for determining the progress of the upload.
802     * @param listener        a listener for monitoring the upload's progress.
803     * @deprecated use uploadNewVersion() instead.
804     */
805    @Deprecated
806    public void uploadVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
807                              ProgressListener listener) {
808        this.uploadNewVersion(fileContent, fileContentSHA1, modified, fileSize, listener);
809        return;
810    }
811
812    /**
813     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
814     * will be able to view and recover previous versions of the file.
815     *
816     * @param fileContent a stream containing the new file contents.
817     * @return the uploaded file version.
818     */
819    public BoxFile.Info uploadNewVersion(InputStream fileContent) {
820        return this.uploadNewVersion(fileContent, null);
821    }
822
823    /**
824     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
825     * will be able to view and recover previous versions of the file.
826     *
827     * @param fileContent     a stream containing the new file contents.
828     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
829     * @return the uploaded file version.
830     */
831    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1) {
832        return this.uploadNewVersion(fileContent, fileContentSHA1, null);
833    }
834
835    /**
836     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
837     * will be able to view and recover previous versions of the file.
838     *
839     * @param fileContent     a stream containing the new file contents.
840     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
841     * @param modified        the date that the new version was modified.
842     * @return the uploaded file version.
843     */
844    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified) {
845        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, 0, null);
846    }
847
848    /**
849     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
850     * will be able to view and recover previous versions of the file.
851     *
852     * @param fileContent     a stream containing the new file contents.
853     * @param fileContentSHA1 a string containing the SHA1 hash of the new file contents.
854     * @param modified        the date that the new version was modified.
855     * @param name            the new name for the file
856     * @return the uploaded file version.
857     */
858    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name) {
859        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, name, 0, null);
860    }
861
862    /**
863     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
864     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
865     * of the file.
866     *
867     * @param fileContent a stream containing the new file contents.
868     * @param modified    the date that the new version was modified.
869     * @param fileSize    the size of the file used for determining the progress of the upload.
870     * @param listener    a listener for monitoring the upload's progress.
871     * @return the uploaded file version.
872     */
873    public BoxFile.Info uploadNewVersion(InputStream fileContent, Date modified, long fileSize,
874                                         ProgressListener listener) {
875        return this.uploadNewVersion(fileContent, null, modified, fileSize, listener);
876    }
877
878    /**
879     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
880     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
881     * of the file.
882     *
883     * @param fileContent     a stream containing the new file contents.
884     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
885     * @param modified        the date that the new version was modified.
886     * @param fileSize        the size of the file used for determining the progress of the upload.
887     * @param listener        a listener for monitoring the upload's progress.
888     * @return the uploaded file version.
889     */
890    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, long fileSize,
891                              ProgressListener listener) {
892        return this.uploadNewVersion(fileContent, fileContentSHA1, modified, null, fileSize, listener);
893    }
894
895    /**
896     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
897     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
898     * of the file.
899     *
900     * @param fileContent     a stream containing the new file contents.
901     * @param fileContentSHA1 the SHA1 hash of the file contents. will be sent along in the Content-MD5 header
902     * @param modified        the date that the new version was modified.
903     * @param name            the new name for the file
904     * @param fileSize        the size of the file used for determining the progress of the upload.
905     * @param listener        a listener for monitoring the upload's progress.
906     * @return the uploaded file version.
907     */
908    public BoxFile.Info uploadNewVersion(InputStream fileContent, String fileContentSHA1, Date modified, String name,
909                                         long fileSize, ProgressListener listener) {
910        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
911        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
912
913        if (fileSize > 0) {
914            request.setFile(fileContent, "", fileSize);
915        } else {
916            request.setFile(fileContent, "");
917        }
918
919        if (fileContentSHA1 != null) {
920            request.setContentSHA1(fileContentSHA1);
921        }
922
923        JsonObject attributesJSON = new JsonObject();
924        if (modified != null) {
925            attributesJSON.add("content_modified_at", BoxDateFormat.format(modified));
926        }
927
928        if (name != null) {
929            attributesJSON.add("name", name);
930        }
931
932        request.putField("attributes", attributesJSON.toString());
933
934        BoxJSONResponse response;
935        if (listener == null) {
936            response = (BoxJSONResponse) request.send();
937        } else {
938            response = (BoxJSONResponse) request.send(listener);
939        }
940
941        String fileJSON = response.getJsonObject().get("entries").asArray().get(0).toString();
942
943        return new BoxFile.Info(fileJSON);
944    }
945
946    /**
947     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
948     * preview session will expire after 60 minutes.
949     *
950     * @return the expiring preview link
951     */
952    public URL getPreviewLink() {
953        BoxFile.Info info = this.getInfo("expiring_embed_link");
954
955        return info.getPreviewLink();
956    }
957
958
959    /**
960     * Retrieves a thumbnail, or smaller image representation, of this file. Sizes of 32x32, 64x64, 128x128,
961     * and 256x256 can be returned in the .png format and sizes of 32x32, 94x94, 160x160, and 320x320 can be returned
962     * in the .jpg format.
963     *
964     * @param fileType  either PNG of JPG
965     * @param minWidth  minimum width
966     * @param minHeight minimum height
967     * @param maxWidth  maximum width
968     * @param maxHeight maximum height
969     * @return the byte array of the thumbnail image
970     * @deprecated use getRepresentationContent() instead
971     */
972    @Deprecated
973    public byte[] getThumbnail(ThumbnailFileType fileType, int minWidth, int minHeight, int maxWidth, int maxHeight) {
974        QueryStringBuilder builder = new QueryStringBuilder();
975        builder.appendParam("min_width", minWidth);
976        builder.appendParam("min_height", minHeight);
977        builder.appendParam("max_width", maxWidth);
978        builder.appendParam("max_height", maxHeight);
979
980        URLTemplate template;
981        if (fileType == ThumbnailFileType.PNG) {
982            template = GET_THUMBNAIL_PNG_TEMPLATE;
983        } else if (fileType == ThumbnailFileType.JPG) {
984            template = GET_THUMBNAIL_JPG_TEMPLATE;
985        } else {
986            throw new BoxAPIException("Unsupported thumbnail file type");
987        }
988        URL url = template.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
989
990        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
991        BoxAPIResponse response = request.send();
992
993        ByteArrayOutputStream thumbOut = new ByteArrayOutputStream();
994        InputStream body = response.getBody();
995        byte[] buffer = new byte[BUFFER_SIZE];
996        try {
997            int n = body.read(buffer);
998            while (n != -1) {
999                thumbOut.write(buffer, 0, n);
1000                n = body.read(buffer);
1001            }
1002        } catch (IOException e) {
1003            throw new BoxAPIException("Error reading thumbnail bytes from response body", e);
1004        } finally {
1005            response.disconnect();
1006        }
1007
1008        return thumbOut.toByteArray();
1009    }
1010
1011    /**
1012     * Gets a list of any comments on this file.
1013     *
1014     * @return a list of comments on this file.
1015     */
1016    public List<BoxComment.Info> getComments() {
1017        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
1018        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
1019        BoxJSONResponse response = (BoxJSONResponse) request.send();
1020        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
1021
1022        int totalCount = responseJSON.get("total_count").asInt();
1023        List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount);
1024        JsonArray entries = responseJSON.get("entries").asArray();
1025        for (JsonValue value : entries) {
1026            JsonObject commentJSON = value.asObject();
1027            BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
1028            BoxComment.Info info = comment.new Info(commentJSON);
1029            comments.add(info);
1030        }
1031
1032        return comments;
1033    }
1034
1035    /**
1036     * Gets a list of any tasks on this file with requested fields.
1037     *
1038     * @param fields optional fields to retrieve for this task.
1039     * @return a list of tasks on this file.
1040     */
1041    public List<BoxTask.Info> getTasks(String... fields) {
1042        QueryStringBuilder builder = new QueryStringBuilder();
1043        if (fields.length > 0) {
1044            builder.appendParam("fields", fields).toString();
1045        }
1046        URL url = GET_TASKS_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), builder.toString(), this.getID());
1047        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
1048        BoxJSONResponse response = (BoxJSONResponse) request.send();
1049        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
1050
1051        int totalCount = responseJSON.get("total_count").asInt();
1052        List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount);
1053        JsonArray entries = responseJSON.get("entries").asArray();
1054        for (JsonValue value : entries) {
1055            JsonObject taskJSON = value.asObject();
1056            BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
1057            BoxTask.Info info = task.new Info(taskJSON);
1058            tasks.add(info);
1059        }
1060
1061        return tasks;
1062    }
1063
1064    /**
1065     * Creates metadata on this file in the global properties template.
1066     *
1067     * @param metadata The new metadata values.
1068     * @return the metadata returned from the server.
1069     */
1070    public Metadata createMetadata(Metadata metadata) {
1071        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
1072    }
1073
1074    /**
1075     * Creates metadata on this file in the specified template type.
1076     *
1077     * @param typeName the metadata template type name.
1078     * @param metadata the new metadata values.
1079     * @return the metadata returned from the server.
1080     */
1081    public Metadata createMetadata(String typeName, Metadata metadata) {
1082        String scope = Metadata.scopeBasedOnType(typeName);
1083        return this.createMetadata(typeName, scope, metadata);
1084    }
1085
1086    /**
1087     * Creates metadata on this file in the specified template type.
1088     *
1089     * @param typeName the metadata template type name.
1090     * @param scope    the metadata scope (global or enterprise).
1091     * @param metadata the new metadata values.
1092     * @return the metadata returned from the server.
1093     */
1094    public Metadata createMetadata(String typeName, String scope, Metadata metadata) {
1095        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1096        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
1097        request.addHeader("Content-Type", "application/json");
1098        request.setBody(metadata.toString());
1099        BoxJSONResponse response = (BoxJSONResponse) request.send();
1100        return new Metadata(JsonObject.readFrom(response.getJSON()));
1101    }
1102
1103    /**
1104     * Sets the provided metadata on the file, overwriting any existing metadata keys already present.
1105     *
1106     * @param templateName the name of the metadata template.
1107     * @param scope        the scope of the template (usually "global" or "enterprise").
1108     * @param metadata     the new metadata values.
1109     * @return the metadata returned from the server.
1110     */
1111    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
1112        Metadata metadataValue = null;
1113
1114        try {
1115            metadataValue = this.createMetadata(templateName, scope, metadata);
1116        } catch (BoxAPIException e) {
1117            if (e.getResponseCode() == 409) {
1118                Metadata metadataToUpdate = new Metadata(scope, templateName);
1119                for (JsonValue value : metadata.getOperations()) {
1120                    if (value.asObject().get("value").isNumber()) {
1121                        metadataToUpdate.add(value.asObject().get("path").asString(),
1122                                value.asObject().get("value").asDouble());
1123                    } else if (value.asObject().get("value").isString()) {
1124                        metadataToUpdate.add(value.asObject().get("path").asString(),
1125                                value.asObject().get("value").asString());
1126                    } else if (value.asObject().get("value").isArray()) {
1127                        ArrayList<String> list = new ArrayList<String>();
1128                        for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
1129                            list.add(jsonValue.asString());
1130                        }
1131                        metadataToUpdate.add(value.asObject().get("path").asString(), list);
1132                    }
1133                }
1134                metadataValue = this.updateMetadata(metadataToUpdate);
1135            } else {
1136                throw e;
1137            }
1138        }
1139
1140        return metadataValue;
1141    }
1142
1143    /**
1144     * Adds a metadata classification to the specified file.
1145     *
1146     * @param classificationType the metadata classification type.
1147     * @return the metadata classification type added to the file.
1148     */
1149    public String addClassification(String classificationType) {
1150        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1151        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1152                "enterprise", metadata);
1153
1154        return classification.getString(Metadata.CLASSIFICATION_KEY);
1155    }
1156
1157    /**
1158     * Updates a metadata classification on the specified file.
1159     *
1160     * @param classificationType the metadata classification type.
1161     * @return the new metadata classification type updated on the file.
1162     */
1163    public String updateClassification(String classificationType) {
1164        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1165        metadata.add("/Box__Security__Classification__Key", classificationType);
1166        Metadata classification = this.updateMetadata(metadata);
1167
1168        return classification.getString(Metadata.CLASSIFICATION_KEY);
1169    }
1170
1171    /**
1172     * Attempts to add classification to a file. If classification already exists then do update.
1173     *
1174     * @param classificationType the metadata classification type.
1175     * @return the metadata classification type on the file.
1176     */
1177    public String setClassification(String classificationType) {
1178        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1179        Metadata classification = null;
1180
1181        try {
1182            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
1183        } catch (BoxAPIException e) {
1184            if (e.getResponseCode() == 409) {
1185                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1186                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1187                classification = this.updateMetadata(metadata);
1188            } else {
1189                throw e;
1190            }
1191        }
1192
1193        return classification.getString(Metadata.CLASSIFICATION_KEY);
1194    }
1195
1196    /**
1197     * Gets the classification type for the specified file.
1198     *
1199     * @return the metadata classification type on the file.
1200     */
1201    public String getClassification() {
1202        Metadata metadata;
1203        try {
1204            metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1205
1206        } catch (BoxAPIException e) {
1207            JsonObject responseObject = JsonObject.readFrom(e.getResponse());
1208            String code = responseObject.get("code").asString();
1209
1210            if (e.getResponseCode() == 404 && code.equals("instance_not_found")) {
1211                return null;
1212            } else {
1213                throw e;
1214            }
1215        }
1216
1217        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1218    }
1219
1220    /**
1221     * Deletes the classification on the file.
1222     */
1223    public void deleteClassification() {
1224        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1225    }
1226
1227    /**
1228     * Locks a file.
1229     *
1230     * @return the lock returned from the server.
1231     */
1232    public BoxLock lock() {
1233        return this.lock(null, false);
1234    }
1235
1236    /**
1237     * Locks a file.
1238     *
1239     * @param isDownloadPrevented is downloading of file prevented when locked.
1240     * @return the lock returned from the server.
1241     */
1242    public BoxLock lock(boolean isDownloadPrevented) {
1243        return this.lock(null, isDownloadPrevented);
1244    }
1245
1246    /**
1247     * Locks a file.
1248     *
1249     * @param expiresAt expiration date of the lock.
1250     * @return the lock returned from the server.
1251     */
1252    public BoxLock lock(Date expiresAt) {
1253        return this.lock(expiresAt, false);
1254    }
1255
1256    /**
1257     * Locks a file.
1258     *
1259     * @param expiresAt           expiration date of the lock.
1260     * @param isDownloadPrevented is downloading of file prevented when locked.
1261     * @return the lock returned from the server.
1262     */
1263    public BoxLock lock(Date expiresAt, boolean isDownloadPrevented) {
1264        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1265        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1266        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1267
1268        JsonObject lockConfig = new JsonObject();
1269        lockConfig.add("type", "lock");
1270        if (expiresAt != null) {
1271            lockConfig.add("expires_at", BoxDateFormat.format(expiresAt));
1272        }
1273        lockConfig.add("is_download_prevented", isDownloadPrevented);
1274
1275        JsonObject requestJSON = new JsonObject();
1276        requestJSON.add("lock", lockConfig);
1277        request.setBody(requestJSON.toString());
1278
1279        BoxJSONResponse response = (BoxJSONResponse) request.send();
1280
1281        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
1282        JsonValue lockValue = responseJSON.get("lock");
1283        JsonObject lockJSON = JsonObject.readFrom(lockValue.toString());
1284
1285        return new BoxLock(lockJSON, this.getAPI());
1286    }
1287
1288    /**
1289     * Unlocks a file.
1290     */
1291    public void unlock() {
1292        String queryString = new QueryStringBuilder().appendParam("fields", "lock").toString();
1293        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1294        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1295
1296        JsonObject lockObject = new JsonObject();
1297        lockObject.add("lock", JsonObject.NULL);
1298
1299        request.setBody(lockObject.toString());
1300        request.send();
1301    }
1302
1303    /**
1304     * Used to retrieve all metadata associated with the file.
1305     *
1306     * @param fields the optional fields to retrieve.
1307     * @return An iterable of metadata instances associated with the file.
1308     */
1309    public Iterable<Metadata> getAllMetadata(String... fields) {
1310        return Metadata.getAllMetadata(this, fields);
1311    }
1312
1313    /**
1314     * Gets the file properties metadata.
1315     *
1316     * @return the metadata returned from the server.
1317     */
1318    public Metadata getMetadata() {
1319        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
1320    }
1321
1322    /**
1323     * Gets the file metadata of specified template type.
1324     *
1325     * @param typeName the metadata template type name.
1326     * @return the metadata returned from the server.
1327     */
1328    public Metadata getMetadata(String typeName) {
1329        String scope = Metadata.scopeBasedOnType(typeName);
1330        return this.getMetadata(typeName, scope);
1331    }
1332
1333    /**
1334     * Gets the file metadata of specified template type.
1335     *
1336     * @param typeName the metadata template type name.
1337     * @param scope    the metadata scope (global or enterprise).
1338     * @return the metadata returned from the server.
1339     */
1340    public Metadata getMetadata(String typeName, String scope) {
1341        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1342        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
1343        BoxJSONResponse response = (BoxJSONResponse) request.send();
1344        return new Metadata(JsonObject.readFrom(response.getJSON()));
1345    }
1346
1347    /**
1348     * Updates the file metadata.
1349     *
1350     * @param metadata the new metadata values.
1351     * @return the metadata returned from the server.
1352     */
1353    public Metadata updateMetadata(Metadata metadata) {
1354        String scope;
1355        if (metadata.getScope().equals(Metadata.GLOBAL_METADATA_SCOPE)) {
1356            scope = Metadata.GLOBAL_METADATA_SCOPE;
1357        } else if (metadata.getScope().startsWith(Metadata.ENTERPRISE_METADATA_SCOPE)) {
1358            scope = metadata.getScope();
1359        } else {
1360            scope = Metadata.ENTERPRISE_METADATA_SCOPE;
1361        }
1362
1363        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(),
1364                scope, metadata.getTemplateName());
1365        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
1366        request.addHeader("Content-Type", "application/json-patch+json");
1367        request.setBody(metadata.getPatch());
1368        BoxJSONResponse response = (BoxJSONResponse) request.send();
1369        return new Metadata(JsonObject.readFrom(response.getJSON()));
1370    }
1371
1372    /**
1373     * Deletes the file properties metadata.
1374     */
1375    public void deleteMetadata() {
1376        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1377    }
1378
1379    /**
1380     * Deletes the file metadata of specified template type.
1381     *
1382     * @param typeName the metadata template type name.
1383     */
1384    public void deleteMetadata(String typeName) {
1385        String scope = Metadata.scopeBasedOnType(typeName);
1386        this.deleteMetadata(typeName, scope);
1387    }
1388
1389    /**
1390     * Deletes the file metadata of specified template type.
1391     *
1392     * @param typeName the metadata template type name.
1393     * @param scope    the metadata scope (global or enterprise).
1394     */
1395    public void deleteMetadata(String typeName, String scope) {
1396        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, typeName);
1397        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1398        request.send();
1399    }
1400
1401    /**
1402     * Used to retrieve the watermark for the file.
1403     * If the file does not have a watermark applied to it, a 404 Not Found will be returned by API.
1404     *
1405     * @param fields the fields to retrieve.
1406     * @return the watermark associated with the file.
1407     */
1408    public BoxWatermark getWatermark(String... fields) {
1409        return this.getWatermark(FILE_URL_TEMPLATE, fields);
1410    }
1411
1412    /**
1413     * Used to apply or update the watermark for the file.
1414     *
1415     * @return the watermark associated with the file.
1416     */
1417    public BoxWatermark applyWatermark() {
1418        return this.applyWatermark(FILE_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
1419    }
1420
1421    /**
1422     * Removes a watermark from the file.
1423     * If the file did not have a watermark applied to it, a 404 Not Found will be returned by API.
1424     */
1425    public void removeWatermark() {
1426        this.removeWatermark(FILE_URL_TEMPLATE);
1427    }
1428
1429    /**
1430     * {@inheritDoc}
1431     */
1432    @Override
1433    public BoxFile.Info setCollections(BoxCollection... collections) {
1434        JsonArray jsonArray = new JsonArray();
1435        for (BoxCollection collection : collections) {
1436            JsonObject collectionJSON = new JsonObject();
1437            collectionJSON.add("id", collection.getID());
1438            jsonArray.add(collectionJSON);
1439        }
1440        JsonObject infoJSON = new JsonObject();
1441        infoJSON.add("collections", jsonArray);
1442
1443        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
1444        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
1445        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
1446        request.setBody(infoJSON.toString());
1447        BoxJSONResponse response = (BoxJSONResponse) request.send();
1448        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1449        return new Info(jsonObject);
1450    }
1451
1452    /**
1453     * Creates an upload session to create a new version of a file in chunks.
1454     * This will first verify that the version can be created and then open a session for uploading pieces of the file.
1455     * @param fileSize the size of the file that will be uploaded.
1456     * @return the created upload session instance.
1457     */
1458    public BoxFileUploadSession.Info createUploadSession(long fileSize) {
1459        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1460
1461        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1462        request.addHeader("Content-Type", "application/json");
1463
1464        JsonObject body = new JsonObject();
1465        body.add("file_size", fileSize);
1466        request.setBody(body.toString());
1467
1468        BoxJSONResponse response = (BoxJSONResponse) request.send();
1469        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1470
1471        String sessionId = jsonObject.get("id").asString();
1472        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1473        return session.new Info(jsonObject);
1474    }
1475
1476    /**
1477     * Creates a new version of a file.
1478     * @param inputStream the stream instance that contains the data.
1479     * @param fileSize the size of the file that will be uploaded.
1480     * @return the created file instance.
1481     * @throws InterruptedException when a thread execution is interrupted.
1482     * @throws IOException when reading a stream throws exception.
1483     */
1484    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize)
1485        throws InterruptedException, IOException {
1486        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1487        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize);
1488    }
1489
1490    /**
1491     * Creates a new version of a file.  Also sets file attributes.
1492     * @param inputStream the stream instance that contains the data.
1493     * @param fileSize the size of the file that will be uploaded.
1494     * @param fileAttributes file attributes to set
1495     * @return the created file instance.
1496     * @throws InterruptedException when a thread execution is interrupted.
1497     * @throws IOException when reading a stream throws exception.
1498     */
1499    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize, Map<String, String> fileAttributes)
1500        throws InterruptedException, IOException {
1501        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1502        return new LargeFileUpload().upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1503    }
1504
1505    /**
1506     * Creates a new version of a file using specified number of parallel http connections.
1507     * @param inputStream the stream instance that contains the data.
1508     * @param fileSize the size of the file that will be uploaded.
1509     * @param nParallelConnections number of parallel http connections to use
1510     * @param timeOut time to wait before killing the job
1511     * @param unit time unit for the time wait value
1512     * @return the created file instance.
1513     * @throws InterruptedException when a thread execution is interrupted.
1514     * @throws IOException when reading a stream throws exception.
1515     */
1516    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1517                                        int nParallelConnections, long timeOut, TimeUnit unit)
1518        throws InterruptedException, IOException {
1519        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1520        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1521            .upload(this.getAPI(), inputStream, url, fileSize);
1522    }
1523
1524    /**
1525     * Creates a new version of a file using specified number of parallel http connections.  Also sets file attributes.
1526     * @param inputStream the stream instance that contains the data.
1527     * @param fileSize the size of the file that will be uploaded.
1528     * @param nParallelConnections number of parallel http connections to use
1529     * @param timeOut time to wait before killing the job
1530     * @param unit time unit for the time wait value
1531     * @param fileAttributes file attributes to set
1532     * @return the created file instance.
1533     * @throws InterruptedException when a thread execution is interrupted.
1534     * @throws IOException when reading a stream throws exception.
1535     */
1536    public BoxFile.Info uploadLargeFile(InputStream inputStream, long fileSize,
1537                                        int nParallelConnections, long timeOut, TimeUnit unit,
1538                                        Map<String, String> fileAttributes)
1539        throws InterruptedException, IOException {
1540        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
1541        return new LargeFileUpload(nParallelConnections, timeOut, unit)
1542            .upload(this.getAPI(), inputStream, url, fileSize, fileAttributes);
1543    }
1544
1545    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
1546                                              Boolean notify, Boolean canViewPath) {
1547
1548        JsonObject itemField = new JsonObject();
1549        itemField.add("id", this.getID());
1550        itemField.add("type", "file");
1551
1552        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath);
1553    }
1554
1555    /**
1556     * Adds a collaborator to this file.
1557     *
1558     * @param collaborator the collaborator to add.
1559     * @param role         the role of the collaborator.
1560     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1561     * @param canViewPath  whether view path collaboration feature is enabled or not.
1562     * @return info about the new collaboration.
1563     */
1564    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
1565                                             Boolean notify, Boolean canViewPath) {
1566        JsonObject accessibleByField = new JsonObject();
1567        accessibleByField.add("id", collaborator.getID());
1568
1569        if (collaborator instanceof BoxUser) {
1570            accessibleByField.add("type", "user");
1571        } else if (collaborator instanceof BoxGroup) {
1572            accessibleByField.add("type", "group");
1573        } else {
1574            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
1575        }
1576        return this.collaborate(accessibleByField, role, notify, canViewPath);
1577    }
1578
1579
1580    /**
1581     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
1582     * account.
1583     *
1584     * @param email the email address of the collaborator to add.
1585     * @param role  the role of the collaborator.
1586     * @param notify       determines if the user (or all the users in the group) will receive email notifications.
1587     * @param canViewPath  whether view path collaboration feature is enabled or not.
1588     * @return info about the new collaboration.
1589     */
1590    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
1591                                             Boolean notify, Boolean canViewPath) {
1592        JsonObject accessibleByField = new JsonObject();
1593        accessibleByField.add("login", email);
1594        accessibleByField.add("type", "user");
1595
1596        return this.collaborate(accessibleByField, role, notify, canViewPath);
1597    }
1598
1599    /**
1600     * Used to retrieve all collaborations associated with the item.
1601     *
1602     * @param fields the optional fields to retrieve.
1603     * @return An iterable of metadata instances associated with the item.
1604     */
1605    public BoxResourceIterable<BoxCollaboration.Info> getAllFileCollaborations(String... fields) {
1606        return BoxCollaboration.getAllFileCollaborations(this.getAPI(), this.getID(),
1607                GET_COLLABORATORS_PAGE_SIZE, fields);
1608
1609    }
1610
1611    /**
1612     * Contains information about a BoxFile.
1613     */
1614    public class Info extends BoxItem.Info {
1615        private String sha1;
1616        private String versionNumber;
1617        private long commentCount;
1618        private EnumSet<Permission> permissions;
1619        private String extension;
1620        private boolean isPackage;
1621        private BoxFileVersion version;
1622        private URL previewLink;
1623        private BoxLock lock;
1624        private boolean isWatermarked;
1625        private boolean isExternallyOwned;
1626        private JsonObject metadata;
1627        private Map<String, Map<String, Metadata>> metadataMap;
1628        private List<Representation> representations;
1629        private List<String> allowedInviteeRoles;
1630        private Boolean hasCollaborations;
1631        private String uploaderDisplayName;
1632        private BoxClassification classification;
1633
1634        /**
1635         * Constructs an empty Info object.
1636         */
1637        public Info() {
1638            super();
1639        }
1640
1641        /**
1642         * Constructs an Info object by parsing information from a JSON string.
1643         *
1644         * @param json the JSON string to parse.
1645         */
1646        public Info(String json) {
1647            super(json);
1648        }
1649
1650        /**
1651         * Constructs an Info object using an already parsed JSON object.
1652         *
1653         * @param jsonObject the parsed JSON object.
1654         */
1655        public Info(JsonObject jsonObject) {
1656            super(jsonObject);
1657        }
1658
1659        @Override
1660        public BoxFile getResource() {
1661            return BoxFile.this;
1662        }
1663
1664        /**
1665         * Gets the SHA1 hash of the file.
1666         *
1667         * @return the SHA1 hash of the file.
1668         */
1669        public String getSha1() {
1670            return this.sha1;
1671        }
1672
1673        /**
1674         * Gets the lock of the file.
1675         *
1676         * @return the lock of the file.
1677         */
1678        public BoxLock getLock() {
1679            return this.lock;
1680        }
1681
1682        /**
1683         * Gets the current version number of the file.
1684         *
1685         * @return the current version number of the file.
1686         */
1687        public String getVersionNumber() {
1688            return this.versionNumber;
1689        }
1690
1691        /**
1692         * Gets the number of comments on the file.
1693         *
1694         * @return the number of comments on the file.
1695         */
1696        public long getCommentCount() {
1697            return this.commentCount;
1698        }
1699
1700        /**
1701         * Gets the permissions that the current user has on the file.
1702         *
1703         * @return the permissions that the current user has on the file.
1704         */
1705        public EnumSet<Permission> getPermissions() {
1706            return this.permissions;
1707        }
1708
1709        /**
1710         * Gets the extension suffix of the file, excluding the dot.
1711         *
1712         * @return the extension of the file.
1713         */
1714        public String getExtension() {
1715            return this.extension;
1716        }
1717
1718        /**
1719         * Gets whether or not the file is an OSX package.
1720         *
1721         * @return true if the file is an OSX package; otherwise false.
1722         */
1723        public boolean getIsPackage() {
1724            return this.isPackage;
1725        }
1726
1727        /**
1728         * Gets the current version details of the file.
1729         *
1730         * @return the current version details of the file.
1731         */
1732        public BoxFileVersion getVersion() {
1733            return this.version;
1734        }
1735
1736        /**
1737         * Gets the current expiring preview link.
1738         *
1739         * @return the expiring preview link
1740         */
1741        public URL getPreviewLink() {
1742            return this.previewLink;
1743        }
1744
1745        /**
1746         * Gets flag indicating whether this file is Watermarked.
1747         *
1748         * @return whether the file is watermarked or not
1749         */
1750        public boolean getIsWatermarked() {
1751            return this.isWatermarked;
1752        }
1753
1754        /**
1755         * Returns the allowed invitee roles for this file item.
1756         *
1757         * @return the list of roles allowed for invited collaborators.
1758         */
1759        public List<String> getAllowedInviteeRoles() {
1760            return this.allowedInviteeRoles;
1761        }
1762
1763        /**
1764         * Returns the indicator for whether this file item has collaborations.
1765         *
1766         * @return indicator for whether this file item has collaborations.
1767         */
1768        public Boolean getHasCollaborations() {
1769            return this.hasCollaborations;
1770        }
1771
1772        /**
1773         * Gets the metadata on this file associated with a specified scope and template.
1774         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method.
1775         * @param   templateName    the metadata template type name.
1776         * @param   scope           the scope of the template (usually "global" or "enterprise").
1777         * @return                  the metadata returned from the server.
1778         */
1779        public Metadata getMetadata(String templateName, String scope) {
1780            try {
1781                return this.metadataMap.get(scope).get(templateName);
1782            } catch (NullPointerException e) {
1783                return null;
1784            }
1785        }
1786
1787        /**
1788         * Returns the field for indicating whether a file is owned by a user outside the enterprise.
1789         * @return indicator for whether or not the file is owned by a user outside the enterprise.
1790         */
1791        public boolean getIsExternallyOwned() {
1792            return this.isExternallyOwned;
1793        }
1794
1795        /**
1796         * Get file's representations.
1797         * @return list of representations
1798         */
1799        public List<Representation> getRepresentations() {
1800            return this.representations;
1801        }
1802
1803        /**
1804         * Returns user's name at the time of upload.
1805         *
1806         * @return user's name at the time of upload
1807         */
1808        public String getUploaderDisplayName() {
1809            return this.uploaderDisplayName;
1810        }
1811
1812        /**
1813         * Gets the metadata classification type of this file.
1814         * @return the metadata classification type of this file.
1815         */
1816        public BoxClassification getClassification() {
1817            return this.classification;
1818        }
1819
1820        @Override
1821        protected void parseJSONMember(JsonObject.Member member) {
1822            super.parseJSONMember(member);
1823
1824            String memberName = member.getName();
1825            JsonValue value = member.getValue();
1826            try {
1827                if (memberName.equals("sha1")) {
1828                    this.sha1 = value.asString();
1829                } else if (memberName.equals("version_number")) {
1830                    this.versionNumber = value.asString();
1831                } else if (memberName.equals("comment_count")) {
1832                    this.commentCount = value.asLong();
1833                } else if (memberName.equals("permissions")) {
1834                    this.permissions = this.parsePermissions(value.asObject());
1835                } else if (memberName.equals("extension")) {
1836                    this.extension = value.asString();
1837                } else if (memberName.equals("is_package")) {
1838                    this.isPackage = value.asBoolean();
1839                } else if (memberName.equals("has_collaborations")) {
1840                    this.hasCollaborations = value.asBoolean();
1841                } else if (memberName.equals("is_externally_owned")) {
1842                    this.isExternallyOwned = value.asBoolean();
1843                } else if (memberName.equals("file_version")) {
1844                    this.version = this.parseFileVersion(value.asObject());
1845                } else if (memberName.equals("allowed_invitee_roles")) {
1846                    this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1847                } else if (memberName.equals("expiring_embed_link")) {
1848                    try {
1849                        String urlString = member.getValue().asObject().get("url").asString();
1850                        this.previewLink = new URL(urlString);
1851                    } catch (MalformedURLException e) {
1852                        throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
1853                    }
1854                } else if (memberName.equals("lock")) {
1855                    if (value.isNull()) {
1856                        this.lock = null;
1857                    } else {
1858                        this.lock = new BoxLock(value.asObject(), BoxFile.this.getAPI());
1859                    }
1860                } else if (memberName.equals("watermark_info")) {
1861                    JsonObject jsonObject = value.asObject();
1862                    this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1863                } else if (memberName.equals("metadata")) {
1864                    JsonObject jsonObject = value.asObject();
1865                    this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1866                } else if (memberName.equals("representations")) {
1867                    JsonObject jsonObject = value.asObject();
1868                    this.representations = Parsers.parseRepresentations(jsonObject);
1869                } else if (memberName.equals("uploader_display_name")) {
1870                    this.uploaderDisplayName = value.asString();
1871                } else if (memberName.equals("classification")) {
1872                    if (value.isNull()) {
1873                        this.classification = null;
1874                    } else {
1875                        this.classification = new BoxClassification(value.asObject());
1876                    }
1877                }
1878            } catch (Exception e) {
1879                throw new BoxDeserializationException(memberName, value.toString(), e);
1880            }
1881        }
1882
1883        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1884            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1885            for (JsonObject.Member member : jsonObject) {
1886                JsonValue value = member.getValue();
1887                if (value.isNull() || !value.asBoolean()) {
1888                    continue;
1889                }
1890
1891                String memberName = member.getName();
1892                if (memberName.equals("can_download")) {
1893                    permissions.add(Permission.CAN_DOWNLOAD);
1894                } else if (memberName.equals("can_upload")) {
1895                    permissions.add(Permission.CAN_UPLOAD);
1896                } else if (memberName.equals("can_rename")) {
1897                    permissions.add(Permission.CAN_RENAME);
1898                } else if (memberName.equals("can_delete")) {
1899                    permissions.add(Permission.CAN_DELETE);
1900                } else if (memberName.equals("can_share")) {
1901                    permissions.add(Permission.CAN_SHARE);
1902                } else if (memberName.equals("can_set_share_access")) {
1903                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1904                } else if (memberName.equals("can_preview")) {
1905                    permissions.add(Permission.CAN_PREVIEW);
1906                } else if (memberName.equals("can_comment")) {
1907                    permissions.add(Permission.CAN_COMMENT);
1908                }
1909            }
1910
1911            return permissions;
1912        }
1913
1914        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
1915            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
1916        }
1917
1918        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
1919            List<String> roles = new ArrayList<String>(jsonArray.size());
1920            for (JsonValue value : jsonArray) {
1921                roles.add(value.asString());
1922            }
1923
1924            return roles;
1925        }
1926    }
1927
1928    /**
1929     * Enumerates the possible permissions that a user can have on a file.
1930     */
1931    public enum Permission {
1932        /**
1933         * The user can download the file.
1934         */
1935        CAN_DOWNLOAD("can_download"),
1936
1937        /**
1938         * The user can upload new versions of the file.
1939         */
1940        CAN_UPLOAD("can_upload"),
1941
1942        /**
1943         * The user can rename the file.
1944         */
1945        CAN_RENAME("can_rename"),
1946
1947        /**
1948         * The user can delete the file.
1949         */
1950        CAN_DELETE("can_delete"),
1951
1952        /**
1953         * The user can share the file.
1954         */
1955        CAN_SHARE("can_share"),
1956
1957        /**
1958         * The user can set the access level for shared links to the file.
1959         */
1960        CAN_SET_SHARE_ACCESS("can_set_share_access"),
1961
1962        /**
1963         * The user can preview the file.
1964         */
1965        CAN_PREVIEW("can_preview"),
1966
1967        /**
1968         * The user can comment on the file.
1969         */
1970        CAN_COMMENT("can_comment"),
1971
1972        /**
1973         * The user can place annotations on this file.
1974         */
1975        CAN_ANNOTATE("can_annotate"),
1976
1977        /**
1978         * The current user can invite new users to collaborate on this item, and the user can update the role of a
1979         * user already collaborated on this item.
1980         */
1981        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1982
1983        /**
1984         * The user can view all annotations placed on this file.
1985         */
1986        CAN_VIEW_ANNOTATIONS_ALL("can_view_annotations_all"),
1987
1988        /**
1989         * The user can view annotations placed by themselves on this file.
1990         */
1991        CAN_VIEW_ANNOTATIONS_SELF("can_view_annotations_self");
1992
1993        private final String jsonValue;
1994
1995        private Permission(String jsonValue) {
1996            this.jsonValue = jsonValue;
1997        }
1998
1999        static Permission fromJSONValue(String jsonValue) {
2000            return Permission.valueOf(jsonValue.toUpperCase());
2001        }
2002
2003        String toJSONValue() {
2004            return this.jsonValue;
2005        }
2006    }
2007
2008}