package almonds;
import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* The <b>ParseObject</b> is a local representation of data that can be saved
* and retrieved from the Parse cloud.
*
* <p>
* The basic workflow for creating new data is to construct a new ParseObject,
* use put() to fill it with data, and then use save() to persist to the databa
*
* <p>
* The basic workflow for accessing existing data is to use a ParseQuery to
* specify which existing data to retrieve.
*
* @author js
*
*/
public class ParseObject
{
private static final String FIELD_CREATED_AT = "createdAt";
private static final String FIELD_UPDATED_AT = "updatedAt";
/**
* Creates a new ParseObject based upon a class name. If the class name is a
* special type (e.g. for ParseUser), then the appropriate type of
* ParseObject is returned.
*
* @param className
*
* @return
*/
public static ParseObject create(String className)
{
return new ParseObject(className);
}
private String mClassName;
private Hashtable<String, Object> mData;
/**
* Constructs a new ParseObject with no data in it. A ParseObject
* constructed in this way will not have an objectId and will not persist to
* the database until save() is called.
*
* Class names must be alphanumerical plus underscore, and start with a
* letter. It is recommended to name classes in CamelCaseLikeThis.
*
* @param theClassName
* The className for this ParseObject.
*/
public ParseObject(String theClassName)
{
mClassName = theClassName;
mData = new Hashtable<String, Object>();
}
/**
* Used to support a query that returns an object from Parse encoded with
* JSON. This constructor will create itself from the JSON. This is probably
* a poor method, especially if the JSON is mal-formed. A better approach
* would be move the handling to ParseQuery, where it alone would
* understands query responses from Parse.
*
* @param theClassName
* The className for this ParseObject
* @param json
* JSON encoded response from Parse corresponding to a
* ParseObject
*/
ParseObject(String theClassName, JSONObject json)
{
mClassName = theClassName;
mData = new Hashtable<String, Object>();
for (String name : JSONObject.getNames(json))
{
try
{
/*
* Check for data types here. If the 'value' is JSONObject then
* there's additional data type information.
*/
if (json.get(name).getClass().getName().equals("org.json.JSONObject"))
{
JSONObject oType = (JSONObject) json.get(name);
if (oType.get("__type").equals("Pointer"))
{
System.out.println("Found Pointer");
put(name,
new ParsePointer(oType.getString("className"), oType
.getString("objectId")));
}
else if (oType.get("__type").equals("Date"))
{
throw new UnsupportedOperationException();
}
}
else
{
Object value = json.get(name); // the value associated with
// key 'name'
/*
* Check for special fields, createdAt and updatedAt, which
* will be formatted and stored as Dates.
*/
if (name.equals(FIELD_CREATED_AT) || name.equals(FIELD_UPDATED_AT))
{
value = javax.xml.bind.DatatypeConverter.parseDateTime((String) value)
.getTime();
}
put(name, value);
}
}
catch (JSONException e)
{
}
}
}
/**
* Whether this object has a particular key. Same as 'has'.
*
* @param key
* The key to check for
* @return Returns whether this object contains the key
*/
public boolean containsKey(String key)
{
return mData.containsKey(key);
}
/**
* A private helper class to facilitate running a ParseObject delete
* operation in the background.
*
* @author js
*
*/
class DeleteInBackgroundThread extends Thread
{
DeleteCallback mDeleteCallback;
/**
*
* @param callback
* A function object of type DeleteCallback, whose method
* done will be called upon completion
*/
DeleteInBackgroundThread(DeleteCallback callback)
{
mDeleteCallback = callback;
}
public void run()
{
ParseException exception = null;
try
{
delete();
}
catch (ParseException e)
{
exception = e;
}
if (mDeleteCallback != null)
{
mDeleteCallback.done(exception);
}
}
}
/**
* Deletes this object on the server in a background thread. Does nothing in
* particular when the save completes. Use this when you don't care if the
* delete works.
*/
public void deleteInBackground()
{
deleteInBackground(null);
}
/**
* Deletes this object on the server in a background thread. This is
* preferable to using delete(), unless your code is already running from a
* background thread.
*
* @param callback
* callback.done(e) is called when the save completes.
*/
public void deleteInBackground(DeleteCallback callback)
{
DeleteInBackgroundThread thread = new DeleteInBackgroundThread(callback);
thread.run();
}
/**
* Deletes this object on the server. This does not delete or destroy the
* object locally.
*
* @throws ParseException
* Throws an error if the object does not exist or if the
* internet fails.
*
*/
public void delete() throws ParseException
{
try
{
HttpClient httpclient = new DefaultHttpClient();
HttpDelete httpdelete = new HttpDelete(Parse.getParseAPIUrlClasses() + mClassName + "/"
+ getObjectId());
httpdelete.addHeader("X-Parse-Application-Id", Parse.getApplicationId());
httpdelete.addHeader("X-Parse-REST-API-Key", Parse.getRestAPIKey());
HttpResponse httpresponse = httpclient.execute(httpdelete);
HttpEntity entity = httpresponse.getEntity();
ParseResponse response = new ParseResponse(httpresponse);
if (!response.isFailed())
{
// delete was successful
}
else
{
throw response.getException();
}
}
catch (ClientProtocolException e)
{
throw ParseResponse.getConnectionFailedException(e.getMessage());
}
catch (IOException e)
{
throw ParseResponse.getConnectionFailedException(e.getMessage());
}
}
/**
* Accessor to the class name.
*
* @return
*/
public String getClassName()
{
return mClassName;
}
/**
* Accessor to the object id. An object id is assigned as soon as an object
* is saved to the server. The combination of a className and an objectId
* uniquely identifies an object in your application.
*
* @return The object id.
*/
public String getObjectId()
{
return (String) mData.get("objectId");
}
/**
* Setter for the object id. In general you do not need to use this.
* However, in some cases this can be convenient. For example, if you are
* serializing a ParseObject yourself and wish to recreate it, you can use
* this to recreate the ParseObject exactly.
*
* @param objectId
*/
public void setObjectId(String objectId)
{
mData.put("objectId", objectId);
}
public void setCreatedAt(String createdAt)
{
mData.put("createdAt", createdAt);
}
/**
* Access a ParsePointer value.
*
* @param key
* The key to access the value for.
* @return Returns null if there is no such key or if it is not a
* ParsePointer.
*/
public ParsePointer getParsePointer(String key)
{
Object value = get(key);
// test for no such key or not a ParsePointer
if (value == null || value.getClass() != ParsePointer.class)
return null;
return (ParsePointer) value;
}
/**
* Creates and returns a new ParsePointer object that points to the object
* it's called on. Use when other objects need to point to *this* object.
*
* @return A ParsePointer object set to point to this object.
*/
public ParsePointer getPointer()
{
return new ParsePointer(mClassName, getObjectId());
}
/**
* Access a string value.
*
* @param key
* The key to access the value for.
* @return Returns null if there is no such key or if it is not a String.
*/
public String getString(String key)
{
Object value = get(key);
// test for no such key or value not a string
if (value == null || value.getClass() != String.class)
return null;
return (String) value;
}
/**
* Access a boolean value.
*
* @param key
* The key to access the value for.
* @return Returns false if there is no such key or if it is not a boolean.
*/
public Boolean getBoolean(String key)
{
Object value = get(key);
// test for no such key or value not a string
if (value == null || value.getClass() != Boolean.class)
return null;
return (Boolean) value;
}
/**
* Encapsulates access to the HashTable that stores key/value pairs stored
* by this Object
*
* @param key
* The key to access the value for
* @return Returns null if there is no such key
*/
private Object get(String key)
{
return mData.get(key);
}
/**
* Access a Date value.
*
* @param key
* The key to access the value for.
* @return Returns null if there is no such key or if it is not a Date.
*/
public Date getDate(String key)
{
Object value = get(key);
// test for no such key or value not a string
if (value == null || value.getClass() != Date.class)
return null;
return (Date) value;
}
/**
* Access a long value.
*
* @param key
* The key to access the value for.
* @return Returns null if there is no such key or if it is not a long.
*/
public Long getLong(String key)
{
Object value = get(key);
// test for no such key or value not a long
if (value == null || value.getClass() != Long.class)
return null;
return (Long) value;
}
/**
* Add a key-value pair to this object. It is recommended to name keys in
* partialCamelCaseLikeThis.
*
* @param key
* Keys must be alphanumerical plus underscore, and start with a
* letter.
* @param value
* Values may be numerical, String, JSONObject, JSONArray,
* JSONObject.NULL, or other ParseObjects. value may not be null.
*/
public void put(String key, Object value)
{
mData.put(key, value);
}
/**
* Saves this object to the server. Typically, you should use
* saveInBackground(com.parse.SaveCallback) instead of this, unless you are
* managing your own threading.
*
* @throws ParseException
* Throws an exception if the server is inaccessible.
*/
public void save() throws ParseException
{
try
{
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(Parse.getParseAPIUrlClasses() + mClassName);
httppost.addHeader("X-Parse-Application-Id", Parse.getApplicationId());
httppost.addHeader("X-Parse-REST-API-Key", Parse.getRestAPIKey());
httppost.addHeader("Content-Type", "application/json");
httppost.setEntity(new StringEntity(toJSONObject().toString()));
HttpResponse httpresponse = httpclient.execute(httppost);
ParseResponse response = new ParseResponse(httpresponse);
if (!response.isFailed())
{
JSONObject jsonResponse = response.getJsonObject();
if (jsonResponse == null)
{
throw response.getException();
}
try
{
setObjectId(jsonResponse.getString("objectId"));
setCreatedAt(jsonResponse.getString("createdAt"));
}
catch (JSONException e)
{
throw new ParseException(
ParseException.INVALID_JSON,
"Although Parse reports object successfully saved, the response was invalid.",
e);
}
}
else
{
throw response.getException();
}
}
catch (ClientProtocolException e)
{
throw ParseResponse.getConnectionFailedException(e);
}
catch (IOException e)
{
throw ParseResponse.getConnectionFailedException(e);
}
}
/**
* A private helper class to facilitate running a ParseObject save operation
* in the background.
*
* @author js
*
*/
class SaveInBackgroundThread extends Thread
{
SaveCallback mSaveCallback;
/**
*
* @param callback
* A function object of type Savecallback, whose method done
* will be called upon completion
*/
SaveInBackgroundThread(SaveCallback callback)
{
mSaveCallback = callback;
}
public void run()
{
ParseException exception = null;
try
{
save();
}
catch (ParseException e)
{
exception = e;
}
if (mSaveCallback != null)
{
mSaveCallback.done(exception);
}
}
}
/**
* Saves this object to the server in a background thread. This is
* preferable to using save(), unless your code is already running from a
* background thread.
*
* @param callback
* callback.done(e) is called when the save completes.
*/
public void saveInBackground(SaveCallback callback)
{
SaveInBackgroundThread t = new SaveInBackgroundThread(callback);
t.start();
}
/**
* Saves this object to the server in a background thread. Use this when you
* do not have code to run on completion of the push.
*/
public void saveInBackground()
{
saveInBackground(null);
}
private JSONObject toJSONObject()
{
JSONObject jo = new JSONObject();
try
{
for (String key : mData.keySet())
jo.put(key, get(key));
}
catch (JSONException e)
{
}
return jo;
}
/**
* This reports time as the server sees it, so that if you make changes to a
* ParseObject, then wait a while, and then call save(), the updated time
* will be the time of the save() call rather than the time the object was
* changed locally.
*
* @return The last time this object was updated on the server.
*/
public Date getUpdatedAt()
{
return getDate(FIELD_UPDATED_AT);
}
/**
* This reports time as the server sees it, so that if you create a
* ParseObject, then wait a while, and then call save(), the creation time
* will be the time of the first save() call rather than the time the object
* was created locally.
*
* @return The first time this object was saved on the server.
*/
public Date getCreatedAt()
{
return getDate(FIELD_CREATED_AT);
}
/**
* Decides if the calling ParseObject and the parameter ParseObject have the
* same Parse Id.
*
* @param asThisObject
* Parse Object to compare with
* @return True if the Ids of the two objects are equal, false otherwise
*/
public boolean hasSameId(ParseObject asThisObject)
{
return this.getObjectId().equals(asThisObject.getObjectId());
}
}