src/main/java/com/github/kskelm/baringo/model/gallery/GalleryAlbum.java
public class GalleryAlbum extends GalleryItem {

	/**
	 * A GalleryAlbum has a privacy level. See <a href="https://help.imgur.com/hc/en-us/articles/201746817-Image-and-album-privacy-explained-">Imgur's documentation</a>.
	 */
	public enum Privacy {
		/**
		 * An album will be visible to public
		 */
		@SerializedName("public") Public,
		/**
		 * An album will be visible to public, but it won't show up in galleries
		 */
		@SerializedName("hidden") Hidden,
		/**
		 * An album will be invisible to everyone but its owner
		 */
		@SerializedName("secret") Secret
	}

	/**
	 * A GalleryAlbum has a layout. <a href="https://help.imgur.com/hc/en-us/articles/201746817-Image-and-album-privacy-explained-">Imgur's documentation</a>.
	 */
	public enum Layout {
		/**
		 * An album will be rendered in blog format
		 */
		@SerializedName("blog") Blog,
		/**
		 * An album will be rendered as a grid of images
		 */
		@SerializedName("grid") Grid,
		/**
		 * An album will be rendered with images side-by-side
		 */
		@SerializedName("horizontal") Horizontal,
		/**
		 * An album will be rendered vertically
		 */
		@SerializedName("vertical") Vertical
	}
	
	
	/**
	 * The image ID of the cover image for this album
	 * @return the cover
	 */
	public String getCoverId() {
		return coverId;
	}

	/**
	 * The width in pixels of the cover image
	 * @return the coverWidth
	 */
	public int getCoverWidth() {
		return coverWidth;
	}

	/**
	 * The height in pixels of the cover image
	 * @return the coverHeight
	 */
	public int getCoverHeight() {
		return coverHeight;
	}

	/**
	 * The privacy level of the album.  If an authenticated account
	 * is logged into the client, the public view cannot be accessed.
	 * Log out to see it.
	 * @return the privacy
	 */
	public Privacy getPrivacy() {
		return privacy;
	}

	/**
	 * The view layout of the album
	 * @return the layout
	 */
	public Layout getLayout() {
		return layout;
	}
	
	/**
	 * The total number of images in the album
	 * @return the imageCount
	 */
	public int getImageCount() {
		return imageCount;
	}

	/**
	 * Returns the list of Image objects
	 * in the GalleryAlbum.
	 * <p>
	 * NOTE: This will only exist if the
	 * gallery album was requested
	 * directly and not via a listing.
	 * @return the images
	 */
	public List<Image> getImages() {
		return images;
	}

	
	public String toString() {
		HashMap<String,Object> fields = Utils.toHashMap( this, GalleryAlbum.class );
		fields.putAll( Utils.toHashMap( this, GalleryItem.class ) );
		return Utils.toString( fields, GalleryAlbum.class.getSimpleName() );
	} // toString
	
	// =========================================================

	/**
	 * Internal only
	 */
	public GalleryAlbum() { }

	/**
	 * Internal only
	 * @param p proxy object
	 */
	public GalleryAlbum( GalleryItemProxy p ) {
		super( p );
		copyFrom( p, GalleryAlbum.class );
	} // lame copy constructor
	
	protected String coverId;
	protected int coverWidth;
	protected int coverHeight;
	protected Privacy privacy;
	protected Layout layout;
	@SerializedName("images_count")
	protected int imageCount;
	protected List<Image> images;

} // GalleryAlbum
src/main/java/com/github/kskelm/baringo/model/Votes.java
public class Votes {

	/**
	 * The number of upvotes the item has received
	 * @return the upVotes
	 */
	public int getUpVotes() {
		return upVotes;
	}

	/**
	 * The number of downvotes the item has received
	 * @return the downVotes
	 */
	public int getDownVotes() {
		return downVotes;
	}

	// ===================================================
	
	protected Votes() {}
	
	@SerializedName("ups")
	private int upVotes;
	@SerializedName("downs")
	private int downVotes;
	
	@Override
	public String toString() {
		return Utils.toString( this );
	}

} // class AccountSettings
src/main/java/com/github/kskelm/baringo/model/ReportReason.java
public enum ReportReason {

	/**
	 * Ambiguously rejected
	 */
	@SerializedName("1") DoesntBelongOnImgur,
	/**
	 * The image is advertising spam
	 */
	@SerializedName("2") Spam,
	/**
	 * The image includes abusive or hateful content
	 */
	@SerializedName("3") Abusive,
	/**
	 * Not safe for work, but not marked that way
	 */
	@SerializedName("4") ShouldBeMarkedNsfw,
	/**
	 * Ambiguously rejected
	 */
	@SerializedName("5") Pornography,
}
src/main/java/com/github/kskelm/baringo/util/BaringoApiException.java
public class BaringoApiException extends Exception {

	/**
	 * Behaves just like every other exception constructor
	 * @param msg The message text for the exception
	 */
	public BaringoApiException( String msg ) {
		super( msg );
	}
	
	/**
	 * Adds support to include HTTP codes in the exception
	 * @param msg The message text for the exception
	 * @param httpCode The HTTP code associated with the exception
	 */
	public BaringoApiException( String msg, int httpCode ) {
		super( msg );
		this.httpCode = httpCode;
	}

	/**
	 * Returns the HTTP code of the exception, or 0 if none
	 * @return the httpCode
	 */
	public int getHttpCode() {
		return this.httpCode;
	}
	
	public String toString() {
		if( httpCode == 0 ) {
			return super.toString();
		} // if
		return super.toString() + " (http code " + httpCode + ")";
	}
	
	private int httpCode = 0;
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -3049907337921263740L;


}
src/main/java/com/github/kskelm/baringo/model/gallery/GalleryItem.java
public abstract class GalleryItem {

	/**
	 * When requesting a gallery, this is the section
	 * 
	 */
	public enum Section {
		/**
		 * Used for returning galleries marked as hot, meaning they're trending.  This is the default in searches.
		 */
		@SerializedName("hot") Hot,  // default
		/**
		 * Used for returning galleries that are top rated
		 */
		@SerializedName("top") Top,
		/**
		 * Used for returning galleries associated with a specific user
		 */
		@SerializedName("user") User;

	}
	
	/**
	 * When requesting content, this is the sort to use
	 */
	public enum Sort {
		/**
		 * Viral means lots of people are visiting this gallery
		 */
		@SerializedName("viral") Viral,
		/**
		 * Top means lots of people like this gallery
		 */
		@SerializedName("top") Top,
		/**
		 * Time means galleries associated with current events
		 */
		@SerializedName("time") Time,
		/**
		 * Within the user section, galleries that are gaining popularity
		 */
		@SerializedName("rising") Rising,
		/**
		 * Best first... used only for comments
		 */
		@SerializedName("best") Best
	}
	
	/**
	 * When requesting the section type "top",
	 * this changes the date range of the request.
	 * The default is Day.
	 */
	public enum Window {
		/**
		 * Return items in the last day.
		 */
		@SerializedName("day") Day,
		/**
		 * Return items in the last week
		 */
		@SerializedName("week") Week,
		/**
		 * Return items in the last month
		 */
		@SerializedName("month") Month,
		/**
		 * Return items in the last year
		 */
		@SerializedName("year") Year,
		/**
		 * Return all of them (not available for all methods)
		 */
		@SerializedName("day") All;
		
		@Override public String toString() {
			return this.toString().toLowerCase();
		}
	}
	
	/**
	 * Returns the ID of the item
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * Returns the title of the item
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * Returns the description of the item
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Returns the date/time on which the item was uploaded
	 * @return the uploadDate
	 */
	public Date getUploadDate() {
		return uploadDate;
	}

	/**
	 * Returns the number of times the item has been viewed.
	 * @return the views
	 */
	public int getViews() {
		return views;
	}

	/**
	 * Returns a link to directly view the item
	 * @return the link
	 */
	public String getLink() {
		return link;
	}

	/**
	 * If the account currently has a client logged in and the user has voted on this item, this will be the vote value.
	 * @return the vote
	 */
	public String getVote() {
		return vote;
	}

	/**
	 * Returns the number of upvotes this item has gotten
	 * @return the ups
	 */
	public int getUps() {
		return ups;
	}

	/**
	 * Returns the number of downvotes this item has gotten
	 * @return the downs
	 */
	public int getDowns() {
		return downs;
	}

	/**
	 * upvotes minus downvotes because math
	 * @return the points
	 */
	public int getPoints() {
		return points;
	}

	/**
	 * Popularity score
	 * @return the score
	 */
	public int getScore() {
		return score;
	}

	/**
	 * Returns true if the account currently has a client logged in and the user favorited this item.
	 * @return the favorite
	 */
	public boolean isFavorite() {
		return favorite;
	}

	/**
	 * Returns whether this item has been marked as Not Safe For Work
	 * @return the nsfw
	 */
	public boolean isNsfw() {
		return nsfw;
	}

	/**
	 * Returns the total number of comments on the item
	 * @return the commentCount
	 */
	public int getCommentCount() {
		return commentCount;
	}

	/**
	 * Returns a preview of the first 10 comments on the item.  Not all fields are filled in.
	 * @return the commentPreview
	 */
	public List<Comment> getCommentPreview() {
		return commentPreview;
	}

	/**
	 * Returns the topic of the item
	 * @return the topic
	 */
	public String getTopic() {
		return topic;
	}

	/**
	 * Returns the ID of the topic of the item
	 * @return the topicId
	 */
	public int getTopicId() {
		return topicId;
	}

	/**
	 * Returns the userName of the user that uploaded the item
	 * @return the userName
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Returns the account id of the user that uploaded the item
	 * @return the userId
	 */
	public int getUserId() {
		return userId;
	}

	/**
	 * Returns whether or not this item is an album.  If true, it
	 * can be cast to GalleryAlbum.  If false, it can be cast to
	 * GalleryImage.
	 * @return the album
	 */
	public boolean isAlbum() {
		return album;
	}

	
	// =========================================================
	
	public GalleryItem() {
// TODO: WAT
System.err.println("wat");
	}

	protected GalleryItem( GalleryItemProxy p ) {
		copyFrom( p, GalleryItem.class );
	} // lame copy constructor
	
	protected String id;
	protected String title;
	protected String description;
	@SerializedName("datetime")
	protected Date uploadDate;
	protected int views;
	protected String link;
	protected String vote;
	protected int ups;
	protected int downs;
	protected int points;
	protected int score;
	protected boolean favorite;
	protected boolean nsfw;
	@SerializedName("comment_count")
	protected int commentCount;
	@SerializedName("comment_preview")
	protected List<Comment> commentPreview;
	protected String topic;
	@SerializedName("topic_id")
	protected int topicId;
	@SerializedName("account_url")
	protected String userName;
	@SerializedName("account_id")
	protected int userId;
	@SerializedName("is_album")
	protected boolean album;
	
	public String toString() {
		return Utils.toString( this );
	} // toString

//	/**
//	 * (INTERNAL) Shallow copy the fields from one object to another, given
//	 * named fields in a specific level of the inheritance.
//	 * This makes me sad.
//	 * @param from destination object
//	 * @param cls the class to enumerate fields on
//	 */
	public void copyFrom( GalleryItemProxy from, @SuppressWarnings("rawtypes") Class cls ) {
		for( Field toField : cls.getDeclaredFields() ) {
			try {
				Field fromField = from.getClass().getField( toField.getName() );
				if( fromField == null ) { // doesn't exist in the source
					continue;
				} // if
				Object val = fromField.get( from );
				toField.set( this, val );
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (NoSuchFieldException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			}
		} // for
	} // copyFrom
}
src/main/java/com/github/kskelm/baringo/model/Vote.java
public enum Vote {
	/**
	 * This represents an upvote
	 */
	@SerializedName("up") Up,
	/**
	 * This represents a downvote
	 */
	@SerializedName("down") Down
}
src/main/java/com/github/kskelm/baringo/model/search/SearchQuery.java
public class SearchQuery {

	/**
	 * This allows specific kinds of items to come back
	 */
	public enum ItemType {
		/**
		 * No specific item type, allow any
		 */
		any,
		/**
		 * Return only image/jpeg
		 */
		jpg,
		/**
		 * Return only image/png
		 */
		png,
		/**
		 * Return only image/gif
		 */
		gif,
		/**
		 * Return only image/gif, but specifically animated gifs
		 */
		anigif,
		/**
		 * Return only albums
		 */
		album
	}
	
	/**
	 * This allows specific file size ranges to come back
	 */
	public enum SizeRange {
		/**
		 * No restriction, any size
		 */
		any,
		/**
		 * Small, 500px square or less
		 */
		small,
		/**
		 * Medium, 500-2000px square
		 */
		med,
		/**
		 * Big, 2000-5000px square
		 */
		big,
		/**
		 * Large, 5000-10000px square
		 */
		lrg,
		/**
		 * Huge, 10000px square and above
		 */
		huge
	}

	/**
	 * Returns the list of words that are part of an "or" search
	 * @return the anyWords
	 */
	public String getAnyWords() {
		return anyWords;
	}


	/**
	 * Set the "or" query string; If a result matches ANY of
	 * the words present, it will be returned. For more complex
	 * queries, see {@link CompoundSearchQuery}. Only one of the
	 * query string setters may be called for a given SearchQuery.
	 * @param anyWords the query to set
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery anyWords(String anyWords) {
		if( alreadySetQuery ) {
			log.warning( "SearchQuery string already set, overriding with compound query");
		} // if
		alreadySetQuery = true;

		this.anyWords = anyWords;
		return this;
	}


	/**
	 * Returns the list of words that are part of an "and" search
	 * @return the allWords
	 */
	public String getAllWords() {
		return allWords;
	}


	/**
	 * Set the "and" query string; If a result matches ALL of
	 * the words present, it will be returned. For more complex queries,
	 * see {@link CompoundSearchQuery}. Only one of the
	 * query string setters may be called for a given SearchQuery.
	 * @param allWords the query to set
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery allWords(String allWords) {
		if( alreadySetQuery ) {
			log.warning( "SearchQuery string already set, overriding with compound query");
		} // if
		alreadySetQuery = true;

		this.allWords = allWords;
		return this;
	}


	/**
	 * Returns the specific phrase to be searched.
	 * @return the thisPhrase
	 */
	public String getThisPhrase() {
		return thisPhrase;
	}


	/**
	 * Set the exact match query string; If a result matches the
	 * specific phrase given, it will be returned. Any item with
	 * this as a substring will be returned. Only one of the
	 * query string setters may be called for a given SearchQuery.
	 * For more complex queries, see {@link CompoundSearchQuery}.
	 * 
	 * @param thisPhrase a phrase to include in results
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery thisPhrase(String thisPhrase) {
		if( alreadySetQuery ) {
			log.warning( "SearchQuery string already set, overriding with compound query");
		} // if
		alreadySetQuery = true;

		this.thisPhrase = thisPhrase;
		return this;
	}


	/**
	 * Returns the phrase to be excluded from the search
	 * @return the notPhrase
	 */
	public String getNotPhrase() {
		return notThisPhrase;
	}


	/**
	 * Set the exclusion query string; If a result matches the
	 * specific phrase given, it will NOT be returned. For more
	 * complex queries, see {@link CompoundSearchQuery}. This MAY
	 * be used in conjunction with the other search strings.
	 * @param notThisPhrase a phrase to declare as unwanted
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery notThisPhrase(String notThisPhrase) {
		this.notThisPhrase = notThisPhrase;
		return this;
	}


	/**
	 * Returns the item type this search is to return
	 * @return the itemType
	 */
	public ItemType getItemType() {
		return itemType;
	}


	/**
	 * Sets the type of item this search must return.
	 * For more complex queries, see {@link CompoundSearchQuery}.
	 * @param itemType the itemType to set
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery itemType(ItemType itemType) {
		this.itemType = itemType;
		return this;
	}


	/**
	 * Returns the size range this search is to return
	 * @return the sizeRange
	 */
	public SizeRange getSizeRange() {
		return sizeRange;
	}


	/**
	 * Sets the range of image sizes this search must
	 * return. For more complex queries, see
	 * See {@link CompoundSearchQuery}.
	 * @param sizeRange the sizeRange to set
	 * @return The SearchQuery itself, for functional chaining
	 */
	public SearchQuery sizeRange(SizeRange sizeRange) {
		this.sizeRange = sizeRange;
		return this;
	}


	// ===================================================
	private String anyWords;  // or
	private String allWords;  // and
	private String thisPhrase; // exact match
	private String notThisPhrase; // not this phrase
	private ItemType itemType = ItemType.any;
	private SizeRange sizeRange = SizeRange.any;
	private boolean alreadySetQuery = false;
	
	private static final Logger log = Logger.getLogger( BaringoClient.LOG_NAME );
	
	@Override
	public String toString() {
		return Utils.toString( this );
	}

}
src/main/java/com/github/kskelm/baringo/model/TagGallery.java
public class TagGallery {

	
	/**
	 * Returns the name of the tag gallery (this will be the tag itself)
	 * @return the name
	 */
	public String getName() {
		return name;
	}
	/**
	 * This is the number of users following this tag
	 * @return the followerCount
	 */
	public int getFollowerCount() {
		return followerCount;
	}
	/**
	 * This is the number of items tagged with this word
	 * @return the itemCount
	 */
	public int getItemCount() {
		return itemCount;
	}
	/**
	 * If there currently an authenticated user, this will indicate
	 * whether they are following this tag or not.  Instances of
	 * this object created before authenticating a user or before
	 * following the tag in CustomGallery will not match current
	 * server state.
	 * @return the following
	 */
	public boolean isFollowing() {
		return following;
	}
	/**
	 * Returns the current page of results for the gallery.
	 * @return the items
	 */
	public List<GalleryItem> getItems() {
		return _convertedItems;
	}
	@Override
	public String toString() {
		return Utils.toString( this );
	}


	// ==========================================
	
	/**
	 * INTERNAL ONLY
	 * @param items converts proxy objects to real GalleryItem objects
	 */
	public void setConvertedItems(List<GalleryItem> items ) {
		this._convertedItems = items;
	}
	
	/**
	 * INTERNAL ONLY
	 * @return list of proxy objects that you shouldn't use for anything
	 */
	public List<GalleryItemProxy> getInternalItems() {
		return _internalItems;
	}
	

	private String name;
	private int followerCount;
	@SerializedName("total_items")
	private int itemCount;
	private boolean following;
	private List<GalleryItem> _convertedItems;
	@SerializedName("items")
	private List<GalleryItemProxy> _internalItems;
	
}
src/main/java/com/github/kskelm/baringo/model/gallery/GalleryItemProxy.java
@SuppressWarnings("unused")
public class GalleryItemProxy {

// TODO: SWITCH THIS OVER TO THE JSONDESERIALIZER APPROACH USED IN COMMENTSERVICE
	public boolean isAlbum() {
		return album;
	}
	
	public String id;
	public String title;
	public String description;
	@SerializedName("datetime")
	public Date uploadDate;
	public int views;
	public String link;
	public String vote;
	public int ups;
	public int downs;
	public int points;
	public int score;
	public boolean favorite;
	public boolean nsfw;
	@SerializedName("comment_count")
	public int commentCount;
	@SerializedName("comment_preview")
	public List<Comment> commentPreview;
	public String topic;
	@SerializedName("topic_id")
	public int topicId;
	@SerializedName("account_url")
	public String userName;
	@SerializedName("account_id")
	public int userId;
	@SerializedName("type")
	public String mimeType;
	public boolean animated;
	public int width;
	public int height;
	public int size;
	public long bandwidth;
	@SerializedName("deletehash")
	public String deleteHash;
	public String gifvLink;
	public String mp4Link;
	public String webmLink;
	public boolean looping;
	public GalleryItem.Section section;
	public String coverId;
	public int coverWidth;
	public int coverHeight;
	public Privacy privacy;
	public Layout layout;
	@SerializedName("images_count")
	public int imageCount;
	public List<Image> images;
	@SerializedName("is_album")
	public boolean album;
	// only for meme subclasses
// UPDATE: apparently Imgur doesn't really support this anymore at least
// as of 6/24/15.  https://groups.google.com/forum/#!msg/imgur/BEyZryAhGi0/yfOFyixuPy4J
//	@SerializedName("is_album")
//	protected MemeMetaData memeMetaData;

	// only for subreddit classes
// UPDATE: apparently Imgur doesn't really support this anymore
//	@SerializedName("reddit_comments")
//	protected String redditCommentsUrl;
	
}
src/main/java/com/github/kskelm/baringo/model/TagVote.java
public class TagVote {

	@Override
	public String toString() {
		return Utils.toString( this );
	}

	
	/**
	 * Returns the tag string
	 * @return the tag
	 */
	public String getTag() {
		return tag;
	}
	
	/**
	 * Returns the userName of the user
	 * that first created the tag
	 * @return the userName
	 */
	public String getUserName() {
		return userName;
	}
	/**
	 * Returns the total number of up votes for the tag
	 * @return the ups
	 */
	public int getUps() {
		return ups;
	}
	/**
	 * Returns the total number of down votes for the tag
	 * @return the downs
	 */
	public int getDowns() {
		return downs;
	}

	
	// =============================================
	
	@SerializedName("name")
	private String tag;
	@SerializedName("author")
	private String userName;
	private int ups;
	private int downs;

}
src/main/java/com/github/kskelm/baringo/model/gallery/GalleryImage.java
public class GalleryImage extends GalleryItem {

	
	/**
	 * Return the mimeType of the image
	 * @return the mimeType
	 */
	public String getMimeType() {
		return mimeType;
	}

	/**
	 * Returns true if the image is animated
	 * @return the animated
	 */
	public boolean isAnimated() {
		return animated;
	}

	/**
	 * Returns the width of the image in pixels
	 * @return the width
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Returns the height of the image in pixels
	 * @return the height
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * Returns the size of the image in bytes
	 * @return the size
	 */
	public int getSize() {
		return size;
	}

	/**
	 * Returns the total number of bytes of Internet bandwidth this image has wasted
	 * @return the bandwidth
	 */
	public long getBandwidth() {
		return bandwidth;
	}

	/**
	 * If the client is currently logged into a user and that user owns this image, this is the hash that can be used to delete the image, else null.
	 * @return the deleteHash
	 */
	public String getDeleteHash() {
		return deleteHash;
	}

	/**
	 * If the image is animated, this is a link to a gifv formatted version
	 * @return the gifvLink
	 */
	public String getGifvLink() {
		return gifvLink;
	}

	/**
	 * If the image is animated, this is a link to an mp4 formatted version
	 * @return the mp4Link
	 */
	public String getMp4Link() {
		return mp4Link;
	}

	/**
	 * If the image is animated, this is a link to a webm formatted version
	 * @return the webmLink
	 */
	public String getWebmLink() {
		return webmLink;
	}

	/**
	 * Returns true if the image is animated and it loops continuously
	 * @return the looping
	 */
	public boolean isLooping() {
		return looping;
	}

	/**
	 * If the backend has classified this image into a section like cat, wtf, etc, that value is here.
	 * @return the section
	 */
	public GalleryItem.Section getSection() {
		return section;
	}

	
	public String toString() {
		HashMap<String,Object> fields = Utils.toHashMap( this, GalleryImage.class );
		fields.putAll( Utils.toHashMap( this, GalleryItem.class ) );
		return Utils.toString( fields, GalleryImage.class.getSimpleName() );
	} // toString
	
	// ======================================================
	
	/**
	 * Internal only
	 */
	public GalleryImage() { }
	
	/**
	 * Internal only
	 * @param p proxy object
	 */
	public GalleryImage( GalleryItemProxy p ) {
		super( p );
		copyFrom( p, GalleryImage.class );
	} // lame copy constructor
	
	@SerializedName("type")
	protected String mimeType;
	protected boolean animated;
	protected int width;
	protected int height;
	protected int size;
	protected long bandwidth;
	@SerializedName("deletehash")
	protected String deleteHash;
	protected String gifvLink;
	protected String mp4Link;
	protected String webmLink;
	protected boolean looping;
	protected GalleryItem.Section section;


} // GalleryImage
src/main/java/com/github/kskelm/baringo/model/Image.java
public class Image {
	
	
	/**
	 * A list of the available thumbnail sizes for images
	 */
	public enum ThumbnailType {
		/**
		 * Small square 90x90 (not proportional)
		 */
		SmallSquare,
		/**
		 * Big square 160x160 (not proportional)
		 */
		BigSquare,
		/**
		 * Small 160x160
		 */
		Small,
		/**
		 * Medium 320x320
		 */
		Medium,
		/**
		 * Large 640x640
		 */
		Large,
		/**
		 * Huge 1024x1024
		 */
		Huge
	}
	
	/**
	 * Given a thumbnail size, returns the link for the
	 * corresponding thumbnail file.
	 * @param tt the thumbnail time
	 * @return thumbnail link
	 */
	public String getThumbnailLink( ThumbnailType tt ) {
		int dotAt = link.lastIndexOf( '.' );
		String suffix = suffixes.get(tt);
		return link.substring( 0, dotAt ) + suffix + link.substring( dotAt );
	} // getThumbnailLink
	
	/**
	 * The ID for the image
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * The title of the image.
	 * @return the title
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * Description of the image.
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Date/time of upload
	 * @return the uploadDate
	 */
	public Date getUploadDate() {
		return uploadDate;
	}

	/**
	 *  	Image MIME type.
	 *  @return the mimeType
	 */
	public String getMimeType() {
		return mimeType;
	}

	/**
	 *  	is the image animated
	 *  @return the animated
	 */
	public boolean isAnimated() {
		return animated;
	}

	/**
	 *  	The width of the image in pixels
	 *  @return the width
	 */
	public int getWidth() {
		return width;
	}

	/**
	 *  	The height of the image in pixels
	 *  @return the height
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * The size of the image in bytes
	 * @return the size
	 */
	public int getSize() {
		return size;
	}

	/**
	 * The number of image views
	 * @return the views
	 */
	public int getViewCount() {
		return viewCount;
	}

	/**
	 * Total internet bandwidth wasted by the image in bytes
	 * @return the bandwidth
	 */
	public long getBandwidth() {
		return bandwidth;
	}

	/**
	 *  	The deletehash for an image is a means of modifying/deleting
	 *  it without necessarily owning it. if you're logged in as the
	 *  image owner then you can delete it by the delete hash or via
	 *  the image id.  If it's an anonymous image not owned by anyone,
	 *  it is deletable only with this value.
	 *  <p>
	 *  If the image is "anonymous," you'll never see this value again
	 *  unless you keep a copy of it yourself after image creation.
	 * @return the deletehash
	 */
	public String getDeleteHash() {
		return deleteHash;
	}

	/**
	 *  The original filename.  This will only be filled in if you're
	 *  logged in as the owner (assuming it was set at upload)
	 *  @return the name
	 */
	public String getFileName() {
		return name;
	}

	/**
	 * If the image has been categorized by the backend then this will contain the section the image belongs in. (funny, cats, adviceanimals, wtf, etc) link
	 * @return the section
	 */
	public String getSection() {
		return section;
	}

	/**
	 * The direct link to the the image.
	 * <p>
	 * NOTE: if fetching an animated GIF that was over 20MB in original size, a .gif thumbnail will be returned.
	 * @return the link
	 */
	public String getLink() {
		return link;
	}

	/**
	 * The .gifv link. Only available if the image is animated and type is 'image/gif'.
	 * @return the gifv
	 */
	public String getGifv() {
		return gifv;
	}

	/**
	 * The direct link to the .mp4. Only available if the image is animated and type is 'image/gif'.
	 * @return the mp4
	 */
	public String getMp4() {
		return mp4;
	}

	/**
	 *  The direct link to the .webm. Only available if the image is animated and type is 'image/gif'.
	 *  @return the webm
	 */
	public String getWebm() {
		return webm;
	}

	/**
	 * Whether the image has a looping animation. Only available if the image is animated and type is 'image/gif'.
	 * @return the looping
	 */
	public boolean isLooping() {
		return looping;
	}

	/**
	 * Indicates if the image has been marked as nsfw or not. Defaults to false if information is not available.
	 * @return the nsfw
	 */
	public boolean isNsfw() {
		return nsfw;
	}

	/**
	 * Indicates if the current user favorited the image. False if not signed in.
	 * @return the favorite
	 */
	public boolean isFavorite() {
		return favorite;
	}

	/**
	 * Sets the favorite status on the image.
	 * <p>
	 * <b>DO NOT USE THIS...</b>
	 * this is used by the ImageService.  Instead call ImageService's
	 * favoriteImage() and unfavoriteImage() and the image object
	 * will be updated accordingly.
	 * @param favorite true if favorited
	 */
	public void setFavorite(boolean favorite) {
		this.favorite = favorite;
	}
	

	/**
	 *  	The current user's vote on the album. null if not signed in, if the user hasn't voted on it, or if not submitted to the gallery.
	 *  @return the vote
	 */
	public String getVote() {
		return vote;
	}

	// =====================================================
	
	private String id;
	private String title;
	private String description;
	@SerializedName("datetime")
	private Date uploadDate;
	@SerializedName("type")
	private String mimeType;
	private boolean animated;
	private int width;
	private int height;
	private int size;
	@SerializedName("views")
	private int viewCount;
	private long bandwidth;
	@SerializedName("deletehash")
	private String deleteHash;
	private String name;
	private String section;
	private String link;
	private String gifv;
	private String mp4;
	private String webm;
	private boolean looping;
	private boolean nsfw;
	private boolean favorite;
	private String vote;

	private static HashMap<ThumbnailType,String> suffixes = new HashMap<>();
	static {
		suffixes.put( ThumbnailType.SmallSquare, "s" );
		suffixes.put( ThumbnailType.BigSquare, "b" );
		suffixes.put( ThumbnailType.Small, "t" );
		suffixes.put( ThumbnailType.Medium, "m" );
		suffixes.put( ThumbnailType.Large, "l" );
		suffixes.put( ThumbnailType.Huge, "h" );
	}
	

	public String toString() {
		return Utils.toString( this );
	} // toString
}
src/main/java/com/github/kskelm/baringo/model/ImgurResponseWrapper.java
public class ImgurResponseWrapper<T> {

	/**
	 * Actual payload
	 */
	private T data;
	/**
	 * Success status
	 */
	private boolean success;
	/**
	 * HTTP status
	 */
	private int status;
	
	
	/**
	 * @return the data
	 */
	public T getData() {
		return data;
	}


	/**
	 * @param data the data to set
	 */
	public void setData(T data) {
		this.data = data;
	}


	/**
	 * @return the success
	 */
	public boolean isSuccess() {
		return success;
	}


	/**
	 * @param success the success to set
	 */
	public void setSuccess(boolean success) {
		this.success = success;
	}


	/**
	 * @return the status
	 */
	public int getStatus() {
		return status;
	}


	/**
	 * @param status the status to set
	 */
	public void setStatus(int status) {
		this.status = status;
	}


	@Override
	public String toString() {
		return String.format(
				"ImgurResponseWrapper[success=%b, status=%d, data=%s]",
				success, status, data );
	} // toString

} // class ImgurResponseWrapper
src/main/java/com/github/kskelm/baringo/model/Comment.java
public class Comment {

	
	/**
	 * When requesting a list of comments this is the order.
	 * Note that loading comments for an image/album is done
	 * from those services, not this one.
	 */
	public enum Sort {
		/**
		 * Sort comments with the newest ones first.
		 */
		@SerializedName("newest") Newest,
		/**
		 * Sort comments with the oldest first
		 */
		@SerializedName("oldest") Oldest,
		/**
		 * Sort comments with the top-rated ones first
		 */
		@SerializedName("best") Best,
		/**
		 * Sort comments with the worst-rated ones first
		 */
		@SerializedName("worst") Worst;
	}
	
	/**
	 * The ID of the comment
	 * @return the id
	 */
	public long getId() {
		return id;
	}


	/**
	 * The ID of the image the comment is for
	 * @return the imageId
	 */
	public String getImageId() {
		return imageId;
	}


	/**
	 * The comment itself
	 * @return the comment
	 */
	public String getComment() {
		return comment;
	}


	/**
	 * The userName of the author
	 * @return the author
	 */
	public String getAuthorName() {
		return authorName;
	}


	/**
	 * The account ID of the author
	 * @return the authorId
	 */
	public int getAuthorId() {
		return authorId;
	}


	/**
	 * True if the comment was made on an album
	 * @return the onAlbum
	 */
	public boolean isOnAlbum() {
		return onAlbum;
	}


	/**
	 * The ID of the album cover image to display with this comment, if any
	 * @return the albumCover
	 */
	public String getAlbumCover() {
		return albumCover;
	}


	/**
	 * Number of upvotes for the comment
	 * @return the ups
	 */
	public int getUps() {
		return ups;
	}


	/**
	 * Number of downvotes for the comment
	 * @return the downs
	 */
	public int getDowns() {
		return downs;
	}


	/**
	 * Number of upvotes-downvotes
	 * @return the points
	 */
	public int getPoints() {
		return points;
	}


	/**
	 * Date/time the comment was created
	 * @return the createdAt
	 */
	public Date getCreatedAt() {
		return createdAt;
	}


	/**
	 * If this is a reply, the comment ID it is replying to
	 * @return the parentId, or 0 if none
	 */
	public long getParentId() {
		return parentId;
	}


	/**
	 * True if this comment has been deleted
	 * @return the deleted
	 */
	public boolean isDeleted() {
		return deleted;
	}


	/**
	 * The current authenticated user's vote on this comment.
	 * Null if not currently signed in or if the user hasn't offered
	 * an opinion yet.
	 * @return the vote
	 */
	public Vote getVote() {
		return vote;
	}


	/**
	 * Get all of the replies for this comment.  If none, this
	 * will be an empty list rather than a null.
	 * @return the children
	 */
	public List<Comment> getChildren() {
		return children;
	}

	public String toString() {
		return Utils.toString( this );
	} // toString
	
	
	// ================================================
	private long id;
	@SerializedName("image_id")
	private String imageId;
	private String comment;
	@SerializedName("author")
	private String authorName;
	@SerializedName("author_id")
	private int authorId;
	@SerializedName("on_album")
	private boolean onAlbum;
	@SerializedName("album_cover")
	private String albumCover;
	private int ups;
	private int downs;
	private int points;
	@SerializedName("datetime")
	private Date createdAt;
	@SerializedName("parent_id")
	private long parentId;
	private boolean deleted;
	private Vote vote;
	private List<Comment> children = new ArrayList<>();

}
src/main/java/com/github/kskelm/baringo/model/TagVoteList.java
public class TagVoteList {

	/**
	 * Returns the tag votes
	 * @return the tag
	 */
	public List<TagVote> getList() {
		return tagVotes;
	}

	@Override
	public String toString() {
		return Utils.toString( this );
	}

	// =============================================
	
	@SerializedName("tags")
	private List<TagVote> tagVotes;

}
src/main/java/com/github/kskelm/baringo/model/search/CompoundSearchQuery.java
public class CompoundSearchQuery  {

	public CompoundSearchQuery() {
		user = new QueryElement( "user", this );
		title = new QueryElement( "title", this );
		extension = new QueryElement( "ext", this );
		subReddit = new QueryElement( "subreddit", this );
		album = new QueryElement( "album", this );
		meme = new QueryElement( "meme", this );
		lastElement = null;
	}

	/**
	 * Sets the user element for adding new userNames to the query
	 * @param userName the userName to add
	 * @return user element
	 * @throws BaringoApiException a rule was broken 
	 */
	public CompoundSearchQuery user( String userName ) throws BaringoApiException {
		lastElement = user;
		return user.set( userName );
	}

	/**
	 * Sets the title element for adding new title search words to the query
	 * @param word the word to add
	 * @return title element
	 * @throws BaringoApiException a rule was broken 
	 */
	public CompoundSearchQuery title( String word ) throws BaringoApiException {
		lastElement = title;
		return title.set( word );
	}

	/**
	 * Sets the extension element for adding new file extensions to the query
	 * @param ext the file extension to add
	 * @return extension element
	 * @throws BaringoApiException a rule was broken
	 */
	public CompoundSearchQuery extension( String ext ) throws BaringoApiException {
		lastElement = extension;
		return extension.set( ext );
	}

	/**
	 * Sets the subreddit element for adding new subreddit names to the query
	 * @param subreddit the name of the subreddit to add
	 * @return subreddit element
	 * @throws BaringoApiException a rule was broken
	 */
	public CompoundSearchQuery subreddit( String subreddit ) throws BaringoApiException {
		lastElement = subReddit;
		return subReddit.set( subreddit );
	}

	/**
	 * Sets the album element for adding new album search words to the query
	 * @param word the word to add
	 * @return album element
	 * @throws BaringoApiException a rule was broken 
	 */
	public CompoundSearchQuery album( String word ) throws BaringoApiException {
		lastElement = album;
		return album.set( word );
	}

	/**
	 * Sets the meme element for adding new meme search words to the query
	 * @param word the word to add
	 * @return meme element
	 * @throws BaringoApiException a rule was broken 
	 */
	public CompoundSearchQuery meme( String word ) throws BaringoApiException {
		lastElement = meme;
		return meme.set( word );
	}

	/**
	 * Adds an AND word to the current element such as user(...) or title(...)
	 * @param word the word to add
	 * @return this object for further query refinement
	 * @throws BaringoApiException a rule was broken
	 */
	public CompoundSearchQuery and( String word ) throws BaringoApiException {
		if( lastElement == null ) {
			throw new BaringoApiException( "Cannot AND without a preceding"
					+ " search element such as user(...) or title(...)" );
		} // if
		return lastElement.and( word );
	}
	
	/**
	 * Adds an OR word to the current element such as user(...) or title(...)
	 * @param word the word to or
	 * @return this object for further query refinement
	 * @throws BaringoApiException a rule was broken
	 */
	public CompoundSearchQuery or( String word ) throws BaringoApiException {
		if( lastElement == null ) {
			throw new BaringoApiException( "Cannot OR without a preceding"
					+ " search element such as user(...) or title(...)" );
		} // if
		return lastElement.or( word );
	}
	
	
	public String toString() {
		StringBuffer buf = new StringBuffer();
		buf.append( user )
				.append( title )
				.append( extension )
				.append( subReddit )
				.append( album )
				.append( meme );
		
		return buf.toString().trim();
	}
	
	private QueryElement user;
	private QueryElement title;
	private QueryElement extension;
	private QueryElement subReddit;
	private QueryElement album;
	private QueryElement meme;
	private QueryElement lastElement = null;

	// ===================================================

	/**
	 * This is used to keep track of SET/AND/OR entries inside
	 * the element-- internal
	 * @author Kevin Kelm (triggur@gmail.com)
	 */
	public class QueryElement {
		// internal
		public QueryElement( String name, CompoundSearchQuery parent ) {
			this.name = name;
			this.parent = parent;
		}

		/**
		 * Start out a query element with a match word
		 * @param entry the match word
		 * @return the QueryElement object itself
		 * @throws BaringoApiException a rule was broken
		 */
		public CompoundSearchQuery set( String entry ) throws BaringoApiException {
			if( !entries.isEmpty() ) {
				throw new BaringoApiException( "Cannot add SET entries to a"
						+ " complex query element once other elements have been added" );
			} // if
			entries.add( entry );
			return parent;
		}

		/**
		 * Add an AND element. It's okay if this is the first entry.
		 * @param entry a word to add to the and clause
		 * @return the search query in its current state, for functional chaining
		 * @throws BaringoApiException a rule was broken
		 */
		public CompoundSearchQuery and( String entry ) throws BaringoApiException {
			if( oring ) {
				throw new BaringoApiException( "Cannot add AND entries to a"
						+ " complex query element once OR elements have been added" );
			} // if
			anding = true;
			entries.add( entry );
			return parent;
		}

		/**
		 * Add an OR element. It's okay if this is the first entry.
		 * @param entry a word to add to the or clause
		 * @return the search query in its current state, for functional chaining
		 * @throws BaringoApiException a rule was broken
		 */
		public CompoundSearchQuery or( String entry ) throws BaringoApiException {
			if( anding ) {
				throw new BaringoApiException( "Cannot add OR entries to a"
						+ " complex query element once AND elements have been added" );
			} // if
			oring = true;
			entries.add( entry );
			return parent;
		}
		
		// examples of elements include variations on title search:
		// title: dog
		// title: dog AND cat
		// title: dog OR cat
		public String toString() {
			if( entries.isEmpty() || (!anding && !oring)) { // nothing to do
				return "";
			} // if
			StringBuffer buf = new StringBuffer();
			buf.append( " " )
					.append( name )
					.append( ":");
			int count = 0;
			for( String entry : entries ) {
				if( entry == null ) {
					continue;
				} // if
				if( count++ == 0 ) { // no expression [yet]
					buf.append( " " );
				} else if( anding ) {
					buf.append( " AND " );
				} else if( oring ) {
					buf.append( " OR " );
				} // if-else
				buf.append( entry );
			} // for
			return buf.toString();
		}


		// ===================================================


		private String name;
		private boolean anding; // have we already starting anding entries?
		private boolean oring; // have we already started oring entries?
		private List<String> entries = new ArrayList<>();
		private CompoundSearchQuery parent;
	}
}


Question: import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.github.kskelm.baringo.model.ImgurResponseWrapper;
import com.github.kskelm.baringo.model.Comment;
import com.github.kskelm.baringo.model.Image;
import com.github.kskelm.baringo.model.ReportReason;
import com.github.kskelm.baringo.model.TagGallery;
import com.github.kskelm.baringo.model.TagVote;
import com.github.kskelm.baringo.model.TagVoteList;
import com.github.kskelm.baringo.model.Vote;
import com.github.kskelm.baringo.model.Votes;
import com.github.kskelm.baringo.model.gallery.GalleryAlbum;
import com.github.kskelm.baringo.model.gallery.GalleryImage;
import com.github.kskelm.baringo.model.gallery.GalleryItem;
import com.github.kskelm.baringo.model.gallery.GalleryItemProxy;
import com.github.kskelm.baringo.model.search.CompoundSearchQuery;
import com.github.kskelm.baringo.model.search.SearchQuery;
import com.github.kskelm.baringo.util.BaringoApiException;
import com.google.gson.GsonBuilder;
import retrofit.Call;
import retrofit.Response;
		} 		
	}

	/**
	 * Returns the upvote/downvote counts for an image or album
	 * <p>
        * <b>ACCESS: ANONYMOUS</b>
	 * @param itemId the item to get votes for
	 * @return its vote counts
	 * @throws BaringoApiException Imgur voted us off the island I guess
	 */
	public Votes getItemVotes( String itemId ) throws BaringoApiException {

		Call<ImgurResponseWrapper<Votes>> call =
				client.getApi().getGalleryItemVotes( itemId );

		try {
			Response<ImgurResponseWrapper<Votes>> res = call.execute();
			ImgurResponseWrapper<Votes> out = res.body();

			client.throwOnWrapperError( res );

			return out.getData();
		} catch (IOException e) {
			throw new BaringoApiException( e.getMessage() );
		} 
	}

	/**
	 * Returns the list of comments on a gallery image or album
	 * <p>
        * <b>ACCESS: ANONYMOUS</b>
	 * @param itemId the id of the item to get comments for
	 * @param sort the sort direction - Best | Top | New
	 * @return a list of comments
	 * @throws BaringoApiException something went south
	 */
	public List<Comment> getItemComments(
			String itemId,
			Comment.Sort sort ) throws BaringoApiException {

		String sortStr = sort.name().toLowerCase();
		Call<ImgurResponseWrapper<List<Comment>>> call =
				client.getApi().getGalleryItemComments( itemId, sortStr );

		try {
			Response<ImgurResponseWrapper<List<Comment>>> res = call.execute();
			ImgurResponseWrapper<List<Comment>> out = res.body();

			client.throwOnWrapperError( res );

			return out.getData();
		} catch (IOException e) {
			throw new BaringoApiException( e.getMessage() );
		} 
	}

	/**
	 * Returns the list of comment Ids on a gallery image or album
	 * <p>
        * <b>ACCESS: ANONYMOUS</b>
	 * @param itemId the id of the item to get comment ids for
	 * @return a list of longs of the comment Ids
	 * @throws BaringoApiException something went south
	 */
	public List<Long> getItemCommentIds(
			String itemId) throws BaringoApiException {

		Call<ImgurResponseWrapper<List<Long>>> call =
				client.getApi().getGalleryItemCommentIds( itemId );

		try {
			Response<ImgurResponseWrapper<List<Long>>> res = call.execute();
			ImgurResponseWrapper<List<Long>> out = res.body();

			client.throwOnWrapperError( res );

			return out.getData();
		} catch (IOException e) {
			throw new BaringoApiException( e.getMessage() );
		} 
	}

	/**
	 * Returns the total number of comments for a gallery image or album.
	 * <p>
        * <b>ACCESS: ANONYMOUS</b>
	 * @param itemId the id of the item to get comment ids for
	 * @return the total number of comments
	 * @throws BaringoApiException something went south
	 */
	public int getItemCommentCount(
			String itemId) throws BaringoApiException {

		Call<ImgurResponseWrapper<Integer>> call =
				client.getApi().getGalleryItemCommentCount( itemId );

		try {
			Response<ImgurResponseWrapper<Integer>> res = call.execute();
			ImgurResponseWrapper<Integer> out = res.body();

			client.throwOnWrapperError( res );

			return out.getData();
		} catch (IOException e) {
			throw new BaringoApiException( e.getMessage() );
		} 
	}


	// ================================================

	// this approach feels filthy. Convert lame proxy objects to
	// type-safe GalleryItem derivatives.  
	protected List<GalleryItem> convertToGalleryItems( List<GalleryItemProxy> list ) {
		ArrayList<GalleryItem> items = new ArrayList<>();

		for( GalleryItemProxy proxy : list ) {
			GalleryItem item = null;
			if( proxy.isAlbum() ) {
Answer: 				item = new GalleryAlbum( proxy );