Added Java client

This commit is contained in:
Justin Kaufman 2015-08-18 15:55:35 -05:00
parent 956d57a9dd
commit 20cad274e6
13 changed files with 715 additions and 1 deletions

2
.gitignore vendored
View File

@ -59,5 +59,5 @@ docs/_build/
# PyBuilder
target/
ponyapi
# ponyapi
nimcache

View File

@ -35,6 +35,7 @@ Clients
- [Go](https://godoc.org/github.com/Xe/PonyAPI/client/go)
- [Nim](https://github.com/Xe/PonyAPI/blob/master/client/nim/ponyapi.nim) [Docs](http://ponyapi.apps.xeserv.us/static/nim.html)
- [Python](https://github.com/Xe/PonyAPI/blob/master/client/python/ponyapi.py)
- [Java](https://github.com/Xe/PonyAPI/client/java)
Routes
------

62
client/java/README.md Normal file
View File

@ -0,0 +1,62 @@
PonyAPI Java Client
===================
A Java client for accessing PonyAPI. Requires Java 8.
Example Usage
-------------
```java
package us.xeserv.examples;
import us.xeserv.ponyapi.Episode;
import us.xeserv.ponyapi.PonyApiClient;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
public class PonyApiExample {
public static void main(String[] args) throws IOException {
// Initialize a client with a custom API host
PonyApiClient client = new PonyApiClient("some.fqdn.here"); // defaults to port 80
client = new PonyApiClient("some.fqdn.here", 8080); // with a custom port number
// Initialize a client using http://ponyapi.apps.xeserv.us/ as the API ohst
client = new PonyApiClient();
// Get a list of all the episodes
List<Episode> allEpisodes = client.all();
// Get the newest episode
Episode newestEpisode = client.newest();
// Get information about an episode
String name = newestEpisode.name;
Instant airDate = newestEpisode.airDate;
int seasonNumber = newestEpisode.season;
int episodeNumber = newestEpisode.episode;
boolean isMovie = newestEpisode.isMovie;
// Get all episodes in a season
List<Episode> season = client.getSeason(1);
// Get all movies
List<Episode> movies = client.getMovies();
// Get a specific episode by season and episode number
Episode specificEpisode = client.getEpisode(1, 13);
// Get a specific movie by movie number
Episode specificMovie = client.getMovie(1);
// Get a random movie or movie number
Episode random = client.random();
// Get a list of all episodes and movies matching a query
List<Episode> queryResults = client.search("Owl's Well"); // returns Owl's Well That Ends Well
}
}
```

19
client/java/build.gradle Normal file
View File

@ -0,0 +1,19 @@
group 'us.xeserv'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "com.google.code.gson:gson:+"
compile "org.apache.httpcomponents:httpclient:+"
compile "org.apache.httpcomponents:fluent-hc:+"
compile "com.google.guava:guava:+"
testCompile "junit:junit:+"
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Aug 18 15:13:15 CDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip

164
client/java/gradlew vendored Normal file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@ -0,0 +1,84 @@
package us.xeserv.ponyapi;
import java.time.Instant;
import java.util.Objects;
/**
* The {@code Episode} class represents My Little Pony episode or movie information returned by PonyAPI.
* <p>
* Episodes are immutable; they represent data returned from the API and cannot be changed after they are created.
* <p>
* Movie data stored in an {@code Episode} object are referred to by the season number 99. Their episode number refers
* to the order of their release starting with 1.
*
* @author Justin Kaufman
* @since 1.0
*/
public class Episode {
/**
* The name of the episode or movie.
*/
public final String name;
/**
* The season of the episode, or 99 if it is a movie.
*/
public final int season;
/**
* The episode or movie number.
*/
public final int episode;
/**
* The air date.
*/
public final Instant airDate;
/**
* True if this episode represents a movie, false otherwise.
*/
public final boolean isMovie;
protected Episode(String name, int season, int episode, long airDate, boolean isMovie) {
this.name = name;
this.season = season;
this.episode = episode;
this.airDate = Instant.ofEpochSecond(airDate);
this.isMovie = isMovie;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Episode rhs = (Episode) obj;
return Objects.equals(name, rhs.name)
&& Objects.equals(season, rhs.season)
&& Objects.equals(episode, rhs.episode)
&& Objects.equals(airDate, rhs.airDate)
&& Objects.equals(isMovie, rhs.isMovie);
}
@Override
public int hashCode() {
return Objects.hash(name, season, episode, airDate, isMovie);
}
@Override
public String toString() {
return "{\n"
+ "\t\"name\": \"" + name + "\",\n"
+ "\t\"air_date\": " + airDate.getEpochSecond() + ",\n"
+ "\t\"season\": " + season + ",\n"
+ "\t\"episode\": " + episode + ",\n"
+ "\t\"is_movie\": " + isMovie + "\n"
+ "}";
}
}

View File

@ -0,0 +1,42 @@
package us.xeserv.ponyapi;
import com.google.gson.*;
import java.util.ArrayList;
import java.util.List;
class JsonDecoder {
private static final Gson gson;
static {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Episode.class, (JsonDeserializer<Episode>) (json, typeOfT, context) -> {
JsonObject jsonObject = json.getAsJsonObject();
return new Episode(
jsonObject.get("name").getAsString(),
jsonObject.get("season").getAsInt(),
jsonObject.get("episode").getAsInt(),
jsonObject.get("air_date").getAsLong(),
jsonObject.get("is_movie").getAsBoolean()
);
});
gson = gsonBuilder.create();
}
protected static Episode fromJson(String json) {
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonObject payload = wrapper.get("episode").getAsJsonObject();
return gson.fromJson(payload, Episode.class);
}
protected static List<Episode> listFromJson(String json) {
List<Episode> list = new ArrayList<>();
JsonObject wrapper = (JsonObject) new JsonParser().parse(json);
JsonArray payload = wrapper.get("episodes").getAsJsonArray();
for (JsonElement episode : payload) {
list.add(gson.fromJson(episode, Episode.class));
}
return list;
}
}

View File

@ -0,0 +1,158 @@
package us.xeserv.ponyapi;
import com.google.common.base.Strings;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
/**
* The {@code PonyApiClient} class provides an interface between Java code that uses this library and PonyAPI.
* <p>
* This client is able to retrieve information about the show My Little Pony: Friendship is Magic.
*
* @author Justin Kaufman
* @since 1.0
*/
public class PonyApiClient {
private final String host;
/**
* The default constructor initializes a client instance that uses http://ponyapi.apps.xeserv.us/ as the API source.
*/
public PonyApiClient() {
this("ponyapi.apps.xeserv.us");
}
/**
* Accepts a FQDN (and optionally port) running the PonyAPI service.
* @param host a FQDN running the PonyAPI service
*/
public PonyApiClient(String host) {
this.host = host;
}
/**
* Returns a list of all episodes and movies.
* @return a list of all episodes and movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> all() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/all")));
}
/**
* Returns the newest episode or movie aired.
* @return the newest episode or movie aired
* @throws IOException if there is an error accessing the service
*/
public Episode newest() throws IOException {
return JsonDecoder.fromJson(asJson(get("/newest")));
}
/**
* Returns the episode or movie that most recently aired.
* @return the episode or movie that most recently aired
* @throws IOException if there is an error accessing the service
*/
public Episode lastAired() throws IOException {
return JsonDecoder.fromJson(asJson(get("/last_aired")));
}
/**
* Returns all information about episodes in a given season number.
* @param season the season number
* @return all information about episodes in a given season number
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getSeason(int season) throws IOException {
HttpResponse response = get("/season/" + season);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.listFromJson(asJson(response));
}
/**
* Returns all information about movies.
* @return all information about movies
* @throws IOException if there is an error accessing the service
*/
public List<Episode> getMovies() throws IOException {
return JsonDecoder.listFromJson(asJson(get("/season/99")));
}
/**
* Returns all information about the episode with the given season and episode number.
* @param season the season number
* @param episode the episode number
* @return all information about the episode with the given season and episode number
* @throws IOException if there is an error accessing the service
*/
public Episode getEpisode(int season, int episode) throws IOException {
HttpResponse response = get("/season/" + season + "/episode/" + episode);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns all information about the movie with the given movie number.
* @param movie the movie number
* @return all information about the movie with the given movie number
* @throws IOException if there is an error accessing the service
*/
public Episode getMovie(int movie) throws IOException {
HttpResponse response = get("/season/99/episode/" + movie);
if (statusCode(response) == 404) {
return null;
}
return JsonDecoder.fromJson(asJson(response));
}
/**
* Returns a random episode or movie from the record.
* @return a random episode or movie from the record
* @throws IOException if there is an error accessing the service
*/
public Episode random() throws IOException {
String json = asJson(get("/random"));
return JsonDecoder.fromJson(json);
}
/**
* Returns a list of episodes which contain the query in its name.
* @param query a case-independent, non-empty {@code String}
* @return a list of episodes which contain the query in its name
* @throws IOException if there is an error accessing the service
* @throws IllegalArgumentException if the query is null or empty
*/
public List<Episode> search(String query) throws IOException {
if (Strings.isNullOrEmpty(query)) {
throw new IllegalArgumentException("A search query is required.");
}
String json = asJson(get("/search?q=" + URLEncoder.encode(query, "UTF-8")));
return JsonDecoder.listFromJson(json);
}
private HttpResponse get(String path) throws IOException {
return Request.Get("http://" + host + path)
.userAgent("PonyApi Java Client 1.0")
.execute()
.returnResponse();
}
private String asJson(HttpResponse response) throws IOException {
return EntityUtils.toString(response.getEntity());
}
private int statusCode(HttpResponse response) throws IOException {
return response.getStatusLine().getStatusCode();
}
}

View File

@ -0,0 +1,92 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class JsonDecoderTest {
@Test
public void episodeTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\"air_date\": 1286735400,\n"
+ "\t\t\"season\": 1,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": false\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertEquals(false, episode.isMovie);
}
@Test
public void movieTest() {
String json = "{\n"
+ "\t\"episode\":\n"
+ "\t{\n"
+ "\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\"air_date\": 1371340800,\n"
+ "\t\t\"season\": 99,\n"
+ "\t\t\"episode\": 1,\n"
+ "\t\t\"is_movie\": true\n"
+ "\t}\n"
+ "}";
Episode episode = JsonDecoder.fromJson(json);
assertEquals("Equestria Girls", episode.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate);
assertEquals(99, episode.season);
assertEquals(1, episode.episode);
assertEquals(true, episode.isMovie);
}
@Test
public void multipleTest() {
String json = "{\n"
+ "\t\"episodes\": [\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Friendship is Magic Part 1\",\n"
+ "\t\t\t\"air_date\": 1286735400,\n"
+ "\t\t\t\"season\": 1,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": false\n"
+ "\t\t},\n"
+ "\t\t{\n"
+ "\t\t\t\"name\": \"Equestria Girls\",\n"
+ "\t\t\t\"air_date\": 1371340800,\n"
+ "\t\t\t\"season\": 99,\n"
+ "\t\t\t\"episode\": 1,\n"
+ "\t\t\t\"is_movie\": true\n"
+ "\t\t}\n"
+ "\t]\n"
+ "}";
List<Episode> episodes = JsonDecoder.listFromJson(json);
assertEquals(2, episodes.size());
Episode episode1 = episodes.get(0);
assertEquals("Friendship is Magic Part 1", episode1.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode1.airDate);
assertEquals(1, episode1.season);
assertEquals(1, episode1.episode);
assertEquals(false, episode1.isMovie);
Episode episode2 = episodes.get(1);
assertEquals("Equestria Girls", episode2.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode2.airDate);
assertEquals(99, episode2.season);
assertEquals(1, episode2.episode);
assertEquals(true, episode2.isMovie);
}
}

View File

@ -0,0 +1,81 @@
package us.xeserv.ponyapi;
import org.junit.Test;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import static org.junit.Assert.*;
public class PonyApiClientTest {
private PonyApiClient client = new PonyApiClient();
@Test
public void allTest() throws Exception {
List<Episode> list = client.all();
assertFalse(list.isEmpty());
}
@Test
public void newestTest() throws IOException {
Episode episode = client.newest();
assertNotNull(episode);
}
@Test
public void lastAiredTest() throws IOException {
Episode episode = client.lastAired();
assertNotNull(episode);
}
@Test
public void getSeasonTest() throws IOException {
List<Episode> list = client.getSeason(98);
assertEquals(null, list);
list = client.getSeason(1);
assertEquals(26, list.size());
}
@Test
public void getMoviesTest() throws IOException {
List<Episode> list = client.getMovies();
assertFalse(list.isEmpty());
}
@Test
public void getEpisodeTest() throws IOException {
Episode episode = client.getEpisode(1, 1);
assertEquals("Friendship is Magic Part 1", episode.name);
assertEquals(Instant.ofEpochSecond(1286735400), episode.airDate);
assertEquals(1, episode.season);
assertEquals(1, episode.episode);
assertEquals(false, episode.isMovie);
}
@Test
public void getMovieTest() throws IOException {
Episode episode = client.getMovie(1);
assertEquals("Equestria Girls", episode.name);
assertEquals(Instant.ofEpochSecond(1371340800), episode.airDate);
assertEquals(99, episode.season);
assertEquals(1, episode.episode);
assertEquals(true, episode.isMovie);
}
@Test
public void randomTest() throws IOException {
Episode episode = client.random();
assertNotNull(episode);
}
@Test
public void searchTest() throws IOException {
List<Episode> episodes = client.search("Owl's Well");
assertEquals(1, episodes.size());
Episode episode = episodes.get(0);
assertEquals("Owl's Well That Ends Well", episode.name);
}
}

5
client/java/test.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -e
./gradlew test