001package com.box.sdk;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.OutputStream;
006import java.net.MalformedURLException;
007import java.net.URL;
008import java.util.ArrayList;
009import java.util.Collection;
010import java.util.Date;
011import java.util.EnumSet;
012import java.util.List;
013
014import com.eclipsesource.json.JsonArray;
015import com.eclipsesource.json.JsonObject;
016import com.eclipsesource.json.JsonValue;
017
018
019/**
020 * Represents an individual file on Box. This class can be used to download a file's contents, upload new versions, and
021 * perform other common file operations (move, copy, delete, etc.).
022 *
023 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
024 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
025 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
026 */
027public class BoxFile extends BoxItem {
028
029    /**
030     * An array of all possible file fields that can be requested when calling {@link #getInfo()}.
031     */
032    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "sha1", "name",
033        "description", "size", "path_collection", "created_at", "modified_at", "trashed_at", "purged_at",
034        "content_created_at", "content_modified_at", "created_by", "modified_by", "owned_by", "shared_link", "parent",
035        "item_status", "version_number", "comment_count", "permissions", "tags", "lock", "extension", "is_package",
036        "file_version", "expiring_embed_link"};
037
038    private static final URLTemplate FILE_URL_TEMPLATE = new URLTemplate("files/%s");
039    private static final URLTemplate CONTENT_URL_TEMPLATE = new URLTemplate("files/%s/content");
040    private static final URLTemplate VERSIONS_URL_TEMPLATE = new URLTemplate("files/%s/versions");
041    private static final URLTemplate COPY_URL_TEMPLATE = new URLTemplate("files/%s/copy");
042    private static final URLTemplate ADD_COMMENT_URL_TEMPLATE = new URLTemplate("comments");
043    private static final URLTemplate GET_COMMENTS_URL_TEMPLATE = new URLTemplate("files/%s/comments");
044    private static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("files/%s/metadata/%s");
045    private static final URLTemplate ADD_TASK_URL_TEMPLATE = new URLTemplate("tasks");
046    private static final URLTemplate GET_TASKS_URL_TEMPLATE = new URLTemplate("files/%s/tasks");
047    private static final String DEFAULT_METADATA_TYPE = "properties";
048    private static final int BUFFER_SIZE = 8192;
049
050
051    /**
052     * Constructs a BoxFile for a file with a given ID.
053     * @param  api the API connection to be used by the file.
054     * @param  id  the ID of the file.
055     */
056    public BoxFile(BoxAPIConnection api, String id) {
057        super(api, id);
058    }
059
060    @Override
061    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
062        BoxSharedLink.Permissions permissions) {
063
064        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
065        Info info = new Info();
066        info.setSharedLink(sharedLink);
067
068        this.updateInfo(info);
069        return info.getSharedLink();
070    }
071
072    /**
073     * Adds a comment to this file. The message can contain @mentions by using the string @[userid:username] anywhere
074     * within the message, where userid and username are the ID and username of the person being mentioned.
075     * @see    <a href="https://developers.box.com/docs/#comments-add-a-comment-to-an-item">the tagged_message field
076     *         for including @mentions.</a>
077     * @param  message the comment's message.
078     * @return information about the newly added comment.
079     */
080    public BoxComment.Info addComment(String message) {
081        JsonObject itemJSON = new JsonObject();
082        itemJSON.add("type", "file");
083        itemJSON.add("id", this.getID());
084
085        JsonObject requestJSON = new JsonObject();
086        requestJSON.add("item", itemJSON);
087        if (BoxComment.messageContainsMention(message)) {
088            requestJSON.add("tagged_message", message);
089        } else {
090            requestJSON.add("message", message);
091        }
092
093        URL url = ADD_COMMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL());
094        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
095        request.setBody(requestJSON.toString());
096        BoxJSONResponse response = (BoxJSONResponse) request.send();
097        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
098
099        BoxComment addedComment = new BoxComment(this.getAPI(), responseJSON.get("id").asString());
100        return addedComment.new Info(responseJSON);
101    }
102
103    /**
104     * Adds a new task to this file. The task can have an optional message to include, and a due date.
105     * @param action the action the task assignee will be prompted to do.
106     * @param message an optional message to include with the task.
107     * @param dueAt the day at which this task is due.
108     * @return information about the newly added task.
109     */
110    public BoxTask.Info addTask(BoxTask.Action action, String message, Date dueAt) {
111        JsonObject itemJSON = new JsonObject();
112        itemJSON.add("type", "file");
113        itemJSON.add("id", this.getID());
114
115        JsonObject requestJSON = new JsonObject();
116        requestJSON.add("item", itemJSON);
117        requestJSON.add("action", action.toJSONString());
118
119        if (message != null && !message.isEmpty()) {
120            requestJSON.add("message", message);
121        }
122
123        if (dueAt != null) {
124            requestJSON.add("due_at", dueAt.toString());
125        }
126
127        URL url = ADD_TASK_URL_TEMPLATE.build(this.getAPI().getBaseURL());
128        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
129        request.setBody(requestJSON.toString());
130        BoxJSONResponse response = (BoxJSONResponse) request.send();
131        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
132
133        BoxTask addedTask = new BoxTask(this.getAPI(), responseJSON.get("id").asString());
134        return addedTask.new Info(responseJSON);
135    }
136
137    /**
138     * Downloads the contents of this file to a given OutputStream.
139     * @param output the stream to where the file will be written.
140     */
141    public void download(OutputStream output) {
142        this.download(output, null);
143    }
144
145    /**
146     * Downloads the contents of this file to a given OutputStream while reporting the progress to a ProgressListener.
147     * @param output   the stream to where the file will be written.
148     * @param listener a listener for monitoring the download's progress.
149     */
150    public void download(OutputStream output, ProgressListener listener) {
151        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
152        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
153        BoxAPIResponse response = request.send();
154        InputStream input = response.getBody(listener);
155
156        byte[] buffer = new byte[BUFFER_SIZE];
157        try {
158            int n = input.read(buffer);
159            while (n != -1) {
160                output.write(buffer, 0, n);
161                n = input.read(buffer);
162            }
163        } catch (IOException e) {
164            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
165        } finally {
166            response.disconnect();
167        }
168    }
169
170    /**
171     * Downloads a part of this file's contents, starting at specified byte offset.
172     * @param output the stream to where the file will be written.
173     * @param offset the byte offset at which to start the download.
174     */
175    public void downloadRange(OutputStream output, long offset) {
176        this.downloadRange(output, offset, -1);
177    }
178
179    /**
180     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd.
181     * @param output     the stream to where the file will be written.
182     * @param rangeStart the byte offset at which to start the download.
183     * @param rangeEnd   the byte offset at which to stop the download.
184     */
185    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd) {
186        this.downloadRange(output, rangeStart, rangeEnd, null);
187    }
188
189    /**
190     * Downloads a part of this file's contents, starting at rangeStart and stopping at rangeEnd, while reporting the
191     * progress to a ProgressListener.
192     * @param output     the stream to where the file will be written.
193     * @param rangeStart the byte offset at which to start the download.
194     * @param rangeEnd   the byte offset at which to stop the download.
195     * @param listener   a listener for monitoring the download's progress.
196     */
197    public void downloadRange(OutputStream output, long rangeStart, long rangeEnd, ProgressListener listener) {
198        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
199        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
200        if (rangeEnd > 0) {
201            request.addHeader("Range", String.format("bytes=%s-%s", Long.toString(rangeStart),
202                Long.toString(rangeEnd)));
203        } else {
204            request.addHeader("Range", String.format("bytes=%s-", Long.toString(rangeStart)));
205        }
206
207        BoxAPIResponse response = request.send();
208        InputStream input = response.getBody(listener);
209
210        byte[] buffer = new byte[BUFFER_SIZE];
211        try {
212            int n = input.read(buffer);
213            while (n != -1) {
214                output.write(buffer, 0, n);
215                n = input.read(buffer);
216            }
217        } catch (IOException e) {
218            throw new BoxAPIException("Couldn't connect to the Box API due to a network error.", e);
219        } finally {
220            response.disconnect();
221        }
222    }
223
224    @Override
225    public BoxFile.Info copy(BoxFolder destination) {
226        return this.copy(destination, null);
227    }
228
229    @Override
230    public BoxFile.Info copy(BoxFolder destination, String newName) {
231        URL url = COPY_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
232
233        JsonObject parent = new JsonObject();
234        parent.add("id", destination.getID());
235
236        JsonObject copyInfo = new JsonObject();
237        copyInfo.add("parent", parent);
238        if (newName != null) {
239            copyInfo.add("name", newName);
240        }
241
242        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
243        request.setBody(copyInfo.toString());
244        BoxJSONResponse response = (BoxJSONResponse) request.send();
245        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
246        BoxFile copiedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
247        return copiedFile.new Info(responseJSON);
248    }
249
250    /**
251     * Deletes this file by moving it to the trash.
252     */
253    public void delete() {
254        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
255        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
256        BoxAPIResponse response = request.send();
257        response.disconnect();
258    }
259
260    @Override
261    public BoxItem.Info move(BoxFolder destination) {
262        return this.move(destination, null);
263    }
264
265    @Override
266    public BoxItem.Info move(BoxFolder destination, String newName) {
267        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
268        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
269
270        JsonObject parent = new JsonObject();
271        parent.add("id", destination.getID());
272
273        JsonObject updateInfo = new JsonObject();
274        updateInfo.add("parent", parent);
275        if (newName != null) {
276            updateInfo.add("name", newName);
277        }
278
279        request.setBody(updateInfo.toString());
280        BoxJSONResponse response = (BoxJSONResponse) request.send();
281        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
282        BoxFile movedFile = new BoxFile(this.getAPI(), responseJSON.get("id").asString());
283        return movedFile.new Info(responseJSON);
284    }
285
286    /**
287     * Renames this file.
288     * @param newName the new name of the file.
289     */
290    public void rename(String newName) {
291        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
292        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
293
294        JsonObject updateInfo = new JsonObject();
295        updateInfo.add("name", newName);
296
297        request.setBody(updateInfo.toString());
298        BoxAPIResponse response = request.send();
299        response.disconnect();
300    }
301
302    @Override
303    public BoxFile.Info getInfo() {
304        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
305        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
306        BoxJSONResponse response = (BoxJSONResponse) request.send();
307        return new Info(response.getJSON());
308    }
309
310    @Override
311    public BoxFile.Info getInfo(String... fields) {
312        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
313        URL url = FILE_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
314
315        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
316        BoxJSONResponse response = (BoxJSONResponse) request.send();
317        return new Info(response.getJSON());
318    }
319
320    /**
321     * Updates the information about this file with any info fields that have been modified locally.
322     *
323     * <p>The only fields that will be updated are the ones that have been modified locally. For example, the following
324     * code won't update any information (or even send a network request) since none of the info's fields were
325     * changed:</p>
326     *
327     * <pre>BoxFile file = new File(api, id);
328     *BoxFile.Info info = file.getInfo();
329     *file.updateInfo(info);</pre>
330     *
331     * @param info the updated info.
332     */
333    public void updateInfo(BoxFile.Info info) {
334        URL url = FILE_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
335        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
336        request.setBody(info.getPendingChanges());
337        BoxJSONResponse response = (BoxJSONResponse) request.send();
338        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
339        info.update(jsonObject);
340    }
341
342    /**
343     * Gets any previous versions of this file. Note that only users with premium accounts will be able to retrieve
344     * previous versions of their files.
345     * @return a list of previous file versions.
346     */
347    public Collection<BoxFileVersion> getVersions() {
348        URL url = VERSIONS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
349        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
350        BoxJSONResponse response = (BoxJSONResponse) request.send();
351
352        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
353        JsonArray entries = jsonObject.get("entries").asArray();
354        Collection<BoxFileVersion> versions = new ArrayList<BoxFileVersion>();
355        for (JsonValue entry : entries) {
356            versions.add(new BoxFileVersion(this.getAPI(), entry.asObject(), this.getID()));
357        }
358
359        return versions;
360    }
361
362    /**
363     * Checks if the file can be successfully uploaded by using the preflight check.
364     * @param  name        the name to give the uploaded file or null to use existing name.
365     * @param  fileSize    the size of the file used for account capacity calculations.
366     * @param  parentID    the ID of the parent folder that the new version is being uploaded to.
367     */
368    public void canUploadVersion(String name, long fileSize, String parentID) {
369        URL url = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
370        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
371
372        JsonObject parent = new JsonObject();
373        parent.add("id", parentID);
374
375        JsonObject preflightInfo = new JsonObject();
376        preflightInfo.add("parent", parent);
377        if (name != null) {
378            preflightInfo.add("name", name);
379        }
380
381        preflightInfo.add("size", fileSize);
382
383        request.setBody(preflightInfo.toString());
384        BoxAPIResponse response = request.send();
385        response.disconnect();
386    }
387
388    /**
389     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
390     * will be able to view and recover previous versions of the file.
391     * @param fileContent a stream containing the new file contents.
392     */
393    public void uploadVersion(InputStream fileContent) {
394        this.uploadVersion(fileContent, null);
395    }
396
397    /**
398     * Uploads a new version of this file, replacing the current version. Note that only users with premium accounts
399     * will be able to view and recover previous versions of the file.
400     * @param fileContent a stream containing the new file contents.
401     * @param modified    the date that the new version was modified.
402     */
403    public void uploadVersion(InputStream fileContent, Date modified) {
404        this.uploadVersion(fileContent, modified, 0, null);
405    }
406
407    /**
408     * Uploads a new version of this file, replacing the current version, while reporting the progress to a
409     * ProgressListener. Note that only users with premium accounts will be able to view and recover previous versions
410     * of the file.
411     * @param fileContent a stream containing the new file contents.
412     * @param modified    the date that the new version was modified.
413     * @param fileSize    the size of the file used for determining the progress of the upload.
414     * @param listener    a listener for monitoring the upload's progress.
415     */
416    public void uploadVersion(InputStream fileContent, Date modified, long fileSize, ProgressListener listener) {
417        URL uploadURL = CONTENT_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL(), this.getID());
418        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
419        if (fileSize > 0) {
420            request.setFile(fileContent, "", fileSize);
421        } else {
422            request.setFile(fileContent, "");
423        }
424
425        if (modified != null) {
426            request.putField("content_modified_at", modified);
427        }
428
429        BoxAPIResponse response;
430        if (listener == null) {
431            response = request.send();
432        } else {
433            response = request.send(listener);
434        }
435        response.disconnect();
436    }
437
438    /**
439     * Gets an expiring URL for creating an embedded preview session. The URL will expire after 60 seconds and the
440     * preview session will expire after 60 minutes.
441     * @return the expiring preview link
442     */
443    public URL getPreviewLink() {
444        BoxFile.Info info = this.getInfo("expiring_embed_link");
445
446        return info.getPreviewLink();
447    }
448
449    /**
450     * Gets a list of any comments on this file.
451     * @return a list of comments on this file.
452     */
453    public List<BoxComment.Info> getComments() {
454        URL url = GET_COMMENTS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
455        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
456        BoxJSONResponse response = (BoxJSONResponse) request.send();
457        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
458
459        int totalCount = responseJSON.get("total_count").asInt();
460        List<BoxComment.Info> comments = new ArrayList<BoxComment.Info>(totalCount);
461        JsonArray entries = responseJSON.get("entries").asArray();
462        for (JsonValue value : entries) {
463            JsonObject commentJSON = value.asObject();
464            BoxComment comment = new BoxComment(this.getAPI(), commentJSON.get("id").asString());
465            BoxComment.Info info = comment.new Info(commentJSON);
466            comments.add(info);
467        }
468
469        return comments;
470    }
471
472    /**
473     * Gets a list of any tasks on this file.
474     * @return a list of tasks on this file.
475     */
476    public List<BoxTask.Info> getTasks() {
477        URL url = GET_TASKS_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
478        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
479        BoxJSONResponse response = (BoxJSONResponse) request.send();
480        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
481
482        int totalCount = responseJSON.get("total_count").asInt();
483        List<BoxTask.Info> tasks = new ArrayList<BoxTask.Info>(totalCount);
484        JsonArray entries = responseJSON.get("entries").asArray();
485        for (JsonValue value : entries) {
486            JsonObject taskJSON = value.asObject();
487            BoxTask task = new BoxTask(this.getAPI(), taskJSON.get("id").asString());
488            BoxTask.Info info = task.new Info(taskJSON);
489            tasks.add(info);
490        }
491
492        return tasks;
493    }
494
495    /**
496     * Creates metadata on this file.
497     * @param metadata The new metadata values.
498     * @return the metadata returned from the server.
499     */
500    public Metadata createMetadata(Metadata metadata) {
501        return this.createMetadata(DEFAULT_METADATA_TYPE, metadata);
502    }
503
504    /**
505     * Creates the metadata of specified type.
506     * @param typeName the metadata type name.
507     * @param metadata the new metadata values.
508     * @return the metadata returned from the server.
509     */
510    public Metadata createMetadata(String typeName, Metadata metadata) {
511        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
512        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
513        request.addHeader("Content-Type", "application/json");
514        request.setBody(metadata.toString());
515        BoxJSONResponse response = (BoxJSONResponse) request.send();
516        return new Metadata(JsonObject.readFrom(response.getJSON()));
517    }
518
519    /**
520     * Gets the file properties metadata.
521     * @return the metadata returned from the server.
522     */
523    public Metadata getMetadata() {
524        return this.getMetadata(DEFAULT_METADATA_TYPE);
525    }
526
527    /**
528     * Gets the file metadata of specified type.
529     * @param typeName the metadata type name.
530     * @return the metadata returned from the server.
531     */
532    public Metadata getMetadata(String typeName) {
533        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
534        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
535        BoxJSONResponse response = (BoxJSONResponse) request.send();
536        return new Metadata(JsonObject.readFrom(response.getJSON()));
537    }
538
539    /**
540     * Updates the file metadata.
541     * @param metadata the new metadata values.
542     * @return the metadata returned from the server.
543     */
544    public Metadata updateMetadata(Metadata metadata) {
545        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), metadata.getTypeName());
546        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
547        request.addHeader("Content-Type", "application/json-patch+json");
548        request.setBody(metadata.getPatch());
549        BoxJSONResponse response = (BoxJSONResponse) request.send();
550        return new Metadata(JsonObject.readFrom(response.getJSON()));
551    }
552
553    /**
554     * Deletes the file properties metadata.
555     */
556    public void deleteMetadata() {
557        this.deleteMetadata(DEFAULT_METADATA_TYPE);
558    }
559
560    /**
561     * Deletes the file metadata of specified type.
562     * @param typeName the metadata type name.
563     */
564    public void deleteMetadata(String typeName) {
565        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), typeName);
566        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
567        request.send();
568    }
569
570    /**
571     * Contains information about a BoxFile.
572     */
573    public class Info extends BoxItem.Info {
574        private String sha1;
575        private String versionNumber;
576        private long commentCount;
577        private EnumSet<Permission> permissions;
578        private String extension;
579        private boolean isPackage;
580        private BoxFileVersion version;
581        private URL previewLink;
582
583        /**
584         * Constructs an empty Info object.
585         */
586        public Info() {
587            super();
588        }
589
590        /**
591         * Constructs an Info object by parsing information from a JSON string.
592         * @param  json the JSON string to parse.
593         */
594        public Info(String json) {
595            super(json);
596        }
597
598        /**
599         * Constructs an Info object using an already parsed JSON object.
600         * @param  jsonObject the parsed JSON object.
601         */
602        Info(JsonObject jsonObject) {
603            super(jsonObject);
604        }
605
606        @Override
607        public BoxFile getResource() {
608            return BoxFile.this;
609        }
610
611        /**
612         * Gets the SHA1 hash of the file.
613         * @return the SHA1 hash of the file.
614         */
615        public String getSha1() {
616            return this.sha1;
617        }
618
619        /**
620         * Gets the current version number of the file.
621         * @return the current version number of the file.
622         */
623        public String getVersionNumber() {
624            return this.versionNumber;
625        }
626
627        /**
628         * Gets the number of comments on the file.
629         * @return the number of comments on the file.
630         */
631        public long getCommentCount() {
632            return this.commentCount;
633        }
634
635        /**
636         * Gets the permissions that the current user has on the file.
637         * @return the permissions that the current user has on the file.
638         */
639        public EnumSet<Permission> getPermissions() {
640            return this.permissions;
641        }
642
643        /**
644         * Gets the extension suffix of the file, excluding the dot.
645         * @return the extension of the file.
646         */
647        public String getExtension() {
648            return this.extension;
649        }
650
651        /**
652         * Gets whether or not the file is an OSX package.
653         * @return true if the file is an OSX package; otherwise false.
654         */
655        public boolean getIsPackage() {
656            return this.isPackage;
657        }
658
659        /**
660         * Gets the current version details of the file.
661         * @return the current version details of the file.
662         */
663        public BoxFileVersion getVersion() {
664            return this.version;
665        }
666
667        /**
668         * Gets the current expiring preview link.
669         * @return the expiring preview link
670         */
671        public URL getPreviewLink() {
672            return this.previewLink;
673        }
674
675        @Override
676        protected void parseJSONMember(JsonObject.Member member) {
677            super.parseJSONMember(member);
678
679            String memberName = member.getName();
680            JsonValue value = member.getValue();
681            if (memberName.equals("sha1")) {
682                this.sha1 = value.asString();
683            } else if (memberName.equals("version_number")) {
684                this.versionNumber = value.asString();
685            } else if (memberName.equals("comment_count")) {
686                this.commentCount = value.asLong();
687            } else if (memberName.equals("permissions")) {
688                this.permissions = this.parsePermissions(value.asObject());
689            } else if (memberName.equals("extension")) {
690                this.extension = value.asString();
691            } else if (memberName.equals("is_package")) {
692                this.isPackage = value.asBoolean();
693            } else if (memberName.equals("file_version")) {
694                this.version = this.parseFileVersion(value.asObject());
695            } else if (memberName.equals("expiring_embed_link")) {
696                try {
697                    String urlString = member.getValue().asObject().get("url").asString();
698                    this.previewLink = new URL(urlString);
699                } catch (MalformedURLException e) {
700                    throw new BoxAPIException("Couldn't parse expiring_embed_link/url for file", e);
701                }
702            }
703        }
704
705        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
706            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
707            for (JsonObject.Member member : jsonObject) {
708                JsonValue value = member.getValue();
709                if (value.isNull() || !value.asBoolean()) {
710                    continue;
711                }
712
713                String memberName = member.getName();
714                if (memberName.equals("can_download")) {
715                    permissions.add(Permission.CAN_DOWNLOAD);
716                } else if (memberName.equals("can_upload")) {
717                    permissions.add(Permission.CAN_UPLOAD);
718                } else if (memberName.equals("can_rename")) {
719                    permissions.add(Permission.CAN_RENAME);
720                } else if (memberName.equals("can_delete")) {
721                    permissions.add(Permission.CAN_DELETE);
722                } else if (memberName.equals("can_share")) {
723                    permissions.add(Permission.CAN_SHARE);
724                } else if (memberName.equals("can_set_share_access")) {
725                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
726                } else if (memberName.equals("can_preview")) {
727                    permissions.add(Permission.CAN_PREVIEW);
728                } else if (memberName.equals("can_comment")) {
729                    permissions.add(Permission.CAN_COMMENT);
730                }
731            }
732
733            return permissions;
734        }
735
736        private BoxFileVersion parseFileVersion(JsonObject jsonObject) {
737            return new BoxFileVersion(BoxFile.this.getAPI(), jsonObject, BoxFile.this.getID());
738        }
739    }
740
741    /**
742     * Enumerates the possible permissions that a user can have on a file.
743     */
744    public enum Permission {
745        /**
746         * The user can download the file.
747         */
748        CAN_DOWNLOAD ("can_download"),
749
750        /**
751         * The user can upload new versions of the file.
752         */
753        CAN_UPLOAD ("can_upload"),
754
755        /**
756         * The user can rename the file.
757         */
758        CAN_RENAME ("can_rename"),
759
760        /**
761         * The user can delete the file.
762         */
763        CAN_DELETE ("can_delete"),
764
765        /**
766         * The user can share the file.
767         */
768        CAN_SHARE ("can_share"),
769
770        /**
771         * The user can set the access level for shared links to the file.
772         */
773        CAN_SET_SHARE_ACCESS ("can_set_share_access"),
774
775        /**
776         * The user can preview the file.
777         */
778        CAN_PREVIEW ("can_preview"),
779
780        /**
781         * The user can comment on the file.
782         */
783        CAN_COMMENT ("can_comment");
784
785        private final String jsonValue;
786
787        private Permission(String jsonValue) {
788            this.jsonValue = jsonValue;
789        }
790
791        static Permission fromJSONValue(String jsonValue) {
792            return Permission.valueOf(jsonValue.toUpperCase());
793        }
794
795        String toJSONValue() {
796            return this.jsonValue;
797        }
798    }
799}