001package com.box.sdk;
002
003import java.lang.reflect.Constructor;
004import java.lang.reflect.InvocationTargetException;
005import java.util.Collections;
006import java.util.Map;
007import java.util.concurrent.ConcurrentHashMap;
008
009import com.eclipsesource.json.JsonObject;
010
011/**
012 * The abstract base class for all resource types (files, folders, comments, collaborations, etc.) used by the API.
013 *
014 * <p>Every API resource has an ID and a {@link BoxAPIConnection} that it uses to communicate with the API. Some
015 * resources also have an associated {@link Info} class that contains information about the resource.</p>
016 */
017public abstract class BoxResource {
018
019    /**
020     * @see #initResourceClassByType()
021     */
022    private static final Map<String, Class<? extends BoxResource>> RESOURCE_CLASS_BY_TYPE = initResourceClassByType();
023
024    private final BoxAPIConnection api;
025    private final String id;
026
027    /**
028     * Constructs a BoxResource for a resource with a given ID.
029     *
030     * @param  api the API connection to be used by the resource.
031     * @param  id  the ID of the resource.
032     */
033    public BoxResource(BoxAPIConnection api, String id) {
034        this.api = api;
035        this.id = id;
036    }
037
038    /**
039     * @return Builds {@link Map} between String {@link #getResourceType(Class)} and {@link BoxResource} type.
040     */
041    private static Map<String, Class<? extends BoxResource>> initResourceClassByType() {
042        Map<String, Class<? extends BoxResource>> result =
043                new ConcurrentHashMap<String, Class<? extends BoxResource>>();
044        result.put(getResourceType(BoxFolder.class), BoxFolder.class);
045        result.put(getResourceType(BoxFile.class), BoxFile.class);
046        result.put(getResourceType(BoxComment.class), BoxComment.class);
047        result.put(getResourceType(BoxCollaboration.class), BoxCollaboration.class);
048        result.put(getResourceType(BoxTask.class), BoxTask.class);
049        result.put(getResourceType(BoxTaskAssignment.class), BoxTaskAssignment.class);
050        result.put(getResourceType(BoxUser.class), BoxUser.class);
051        result.put(getResourceType(BoxGroup.class), BoxGroup.class);
052        result.put(getResourceType(BoxGroupMembership.class), BoxGroupMembership.class);
053        result.put(getResourceType(BoxEvent.class), BoxEvent.class);
054        result.put(getResourceType(BoxWebHook.class), BoxWebHook.class);
055        result.put(getResourceType(BoxCollection.class), BoxCollection.class);
056        result.put(getResourceType(BoxDevicePin.class), BoxDevicePin.class);
057        result.put(getResourceType(BoxRetentionPolicy.class), BoxRetentionPolicy.class);
058        result.put(getResourceType(BoxRetentionPolicyAssignment.class), BoxRetentionPolicyAssignment.class);
059        result.put(getResourceType(BoxFileVersionRetention.class), BoxFileVersionRetention.class);
060        result.put(getResourceType(BoxLegalHoldPolicy.class), BoxLegalHoldPolicy.class);
061        result.put(getResourceType(BoxLegalHoldAssignment.class), BoxLegalHoldAssignment.class);
062        result.put(getResourceType(BoxFileVersionLegalHold.class), BoxFileVersionLegalHold.class);
063        return Collections.unmodifiableMap(result);
064    }
065
066    /**
067     * Resolves {@link BoxResourceType} for a provided {@link BoxResource} {@link Class}.
068     *
069     * @param clazz
070     *            {@link BoxResource} type
071     * @return resolved {@link BoxResourceType#value()}
072     */
073    public static String getResourceType(Class<? extends BoxResource> clazz) {
074        BoxResourceType resource = clazz.getAnnotation(BoxResourceType.class);
075        if (resource == null) {
076            throw new IllegalArgumentException("Provided BoxResource type does not have @BoxResourceType annotation.");
077        }
078        return resource.value();
079    }
080
081    static BoxResource.Info parseInfo(BoxAPIConnection api, JsonObject jsonObject) {
082        String type = jsonObject.get("type").asString();
083        String id = jsonObject.get("id").asString();
084
085        try {
086            Class<? extends BoxResource> resourceClass = RESOURCE_CLASS_BY_TYPE.get(type);
087            Constructor<? extends BoxResource> resourceConstructor =
088                    resourceClass.getConstructor(BoxAPIConnection.class, String.class);
089
090            Class<?> infoClass = resourceClass.getClassLoader().loadClass(resourceClass.getCanonicalName() + "$Info");
091            Constructor<?> infoConstructor = infoClass.getDeclaredConstructor(resourceClass, JsonObject.class);
092
093            BoxResource resource = resourceConstructor.newInstance(api, id);
094            return (BoxResource.Info) infoConstructor.newInstance(resource, jsonObject);
095
096        } catch (ClassNotFoundException e) {
097            return null;
098        } catch (NoSuchMethodException e) {
099            return null;
100        } catch (IllegalAccessException e) {
101            throw new BoxAPIException("Can not create BoxResource.Info instance:", e);
102        } catch (InvocationTargetException e) {
103            throw new BoxAPIException("Can not create BoxResource.Info instance:", e);
104        } catch (InstantiationException e) {
105            throw new BoxAPIException("Can not create BoxResource.Info instance:", e);
106        }
107    }
108
109    /**
110     * Gets the API connection used by this resource.
111     * @return the API connection used by this resource.
112     */
113    public BoxAPIConnection getAPI() {
114        return this.api;
115    }
116
117    /**
118     * Gets the ID of this resource.
119     * @return the ID of this resource.
120     */
121    public String getID() {
122        return this.id;
123    }
124
125    /**
126     * Indicates whether this BoxResource is equal to another BoxResource. Two BoxResources are equal if they have the
127     * same type and ID.
128     * @param  other the other BoxResource to compare.
129     * @return       true if the type and IDs of the two resources are equal; otherwise false.
130     */
131    @Override
132    public boolean equals(Object other) {
133        if (other == null) {
134            return false;
135        }
136
137        if (this.getClass().equals(other.getClass())) {
138            BoxResource otherResource = (BoxResource) other;
139            return this.getID().equals(otherResource.getID());
140        }
141
142        return false;
143    }
144
145    /**
146     * Returns a hash code value for this BoxResource.
147     * @return a hash code value for this BoxResource.
148     */
149    @Override
150    public int hashCode() {
151        return this.getID().hashCode();
152    }
153
154    /**
155     * Contains information about a BoxResource.
156     */
157    public abstract class Info extends BoxJSONObject {
158        /**
159         * Constructs an empty Info object.
160         */
161        public Info() {
162            super();
163        }
164
165        /**
166         * Constructs an Info object by parsing information from a JSON string.
167         * @param  json the JSON string to parse.
168         */
169        public Info(String json) {
170            super(json);
171        }
172
173        /**
174         * Constructs an Info object using an already parsed JSON object.
175         * @param  jsonObject the parsed JSON object.
176         */
177        Info(JsonObject jsonObject) {
178            super(jsonObject);
179        }
180
181        /**
182         * Gets the ID of the resource associated with this Info.
183         * @return the ID of the associated resource.
184         */
185        public String getID() {
186            return BoxResource.this.getID();
187        }
188
189        /**
190         * Gets the resource associated with this Info.
191         * @return the associated resource.
192         */
193        public abstract BoxResource getResource();
194    }
195}