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