From cb05020c2ed73b702e13965bcec85533e3fdbc92 Mon Sep 17 00:00:00 2001 From: glutolik Date: Wed, 2 Dec 2015 21:57:45 +0300 Subject: [PATCH 01/10] fsdf --- projects/glutolik/pom.xml | 26 ++++ .../mipt/diht/students/Reverser/Reverser.java | 15 ++ .../diht/students/TwitterStream/Main.java | 7 + projects/pom.xml | 5 +- projects/pom.xml~ | 137 ++++++++++++++++++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 projects/glutolik/pom.xml create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java create mode 100644 projects/pom.xml~ diff --git a/projects/glutolik/pom.xml b/projects/glutolik/pom.xml new file mode 100644 index 0000000..6874eec --- /dev/null +++ b/projects/glutolik/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + ru.mipt.diht.students + parent + 1.0-SNAPSHOT + + ru.mipt.diht.students + glutolik + 1.0-SNAPSHOT + glutolik + http://maven.apache.org + + UTF-8 + + + + junit + junit + 3.8.1 + test + + + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java new file mode 100644 index 0000000..24667b3 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java @@ -0,0 +1,15 @@ +/** + * Created by glutolik on 20.09.2015. + */ +public class Reverser { + public static void main (String[] args) { + int ind1, ind2; + for (ind1 = args.length - 1; ind1 >= 0; --ind1) { + String[] divided = args[ind1].split("\\s"); + for (ind2 = divided.length - 1; ind2 >= 0; --ind2) { + System.out.print(divided[ind2] + " "); + } + } + System.out.print("\n"); + } +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java new file mode 100644 index 0000000..ef35824 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java @@ -0,0 +1,7 @@ +package ru.mipt.diht.students.TwitterStream; + +/** + * Created by glutolik on 02.12.15. + */ +public class Main { +} diff --git a/projects/pom.xml b/projects/pom.xml index 5d14966..88266e0 100644 --- a/projects/pom.xml +++ b/projects/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 ru.mipt.diht.students @@ -30,6 +30,7 @@ dkhurtin ale3otik Pitovsky + glutolik diff --git a/projects/pom.xml~ b/projects/pom.xml~ new file mode 100644 index 0000000..a3f55fb --- /dev/null +++ b/projects/pom.xml~ @@ -0,0 +1,137 @@ + + + 4.0.0 + + ru.mipt.diht.students + parent + 1.0-SNAPSHOT + pom + + fizteh-java-2015 + https://github.com/KhurtinDN/fizteh-java-2015 + + + Travis CI + https://travis-ci.org/KhurtinDN/fizteh-java-2015 + + + + https://github.com/KhurtinDN/fizteh-java-2015.git + scm:git:ssh://git@github.com/fizteh-java-2015.git + scm:git:ssh://git@github.com/fizteh-java-2015.git + HEAD + + + + UTF-8 + + + + dkhurtin + ale3otik + Pitovsky + glutolik + + + + + + junit + junit + 4.12 + test + + + + org.mockito + mockito-core + 1.10.19 + test + + + + org.hamcrest + hamcrest-core + 1.3 + test + + + + com.tngtech.java + junit-dataprovider + 1.10.1 + test + + + + + + + junit + junit + test + + + + org.mockito + mockito-core + test + + + + org.hamcrest + hamcrest-core + test + + + + com.tngtech.java + junit-dataprovider + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.16 + + + validate + validate + + checkstyle.xml + checkstyle-suppressions.xml + UTF-8 + true + true + + + check + + + + + + com.puppycrawl.tools + checkstyle + 6.10.1 + + + + + + + \ No newline at end of file From cce2d9e99ec1ee4b31b4ed30a5430f7b25e76fc3 Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 15:48:35 +0300 Subject: [PATCH 02/10] commit --- projects/.gitignore | 21 ++++ projects/geolocation.properties | 1 + projects/geolocation.properties~ | 0 projects/glutolik/.gitignore | 21 ++++ projects/glutolik/pom.xml | 31 ++++++ .../diht/students/TwitterStream/Main.java | 7 -- .../{ => glutolik}/Reverser/Reverser.java | 2 +- .../TwitterStream/GeolocationUtils.java | 68 ++++++++++++ .../TwitterStream/RunTwitterStream.java | 43 ++++++++ .../glutolik/TwitterStream/SearchTweets.java | 70 ++++++++++++ .../glutolik/TwitterStream/StreamTweets.java | 66 ++++++++++++ .../TwitterStream/TerminalArguments.java | 54 ++++++++++ .../TwitterStream/TwitterStreamUtils.java | 101 ++++++++++++++++++ projects/glutolik/twitter4j.properties | 5 + projects/twitter4j.properties | 5 + projects/twitter4j.properties~ | 0 16 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 projects/.gitignore create mode 100644 projects/geolocation.properties create mode 100644 projects/geolocation.properties~ create mode 100644 projects/glutolik/.gitignore delete mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java rename projects/glutolik/src/main/java/ru/mipt/diht/students/{ => glutolik}/Reverser/Reverser.java (86%) create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/GeolocationUtils.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/SearchTweets.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/StreamTweets.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TerminalArguments.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TwitterStreamUtils.java create mode 100644 projects/glutolik/twitter4j.properties create mode 100644 projects/twitter4j.properties create mode 100644 projects/twitter4j.properties~ diff --git a/projects/.gitignore b/projects/.gitignore new file mode 100644 index 0000000..90ed889 --- /dev/null +++ b/projects/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +*.class +*.jar +*.war +*.ear +*.class +hs_err_pid* +.idea +*.iml +*.ipr +out +build +bin +target +.classpath +.project +workbench.xmi +*.swp +.settings +.checkstyle +.properties diff --git a/projects/geolocation.properties b/projects/geolocation.properties new file mode 100644 index 0000000..5e42b1b --- /dev/null +++ b/projects/geolocation.properties @@ -0,0 +1 @@ +key=AIzaSyCltC9cSKnrnqOApw5TQ155nwEBW-ZUt1E diff --git a/projects/geolocation.properties~ b/projects/geolocation.properties~ new file mode 100644 index 0000000..e69de29 diff --git a/projects/glutolik/.gitignore b/projects/glutolik/.gitignore new file mode 100644 index 0000000..90ed889 --- /dev/null +++ b/projects/glutolik/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +*.class +*.jar +*.war +*.ear +*.class +hs_err_pid* +.idea +*.iml +*.ipr +out +build +bin +target +.classpath +.project +workbench.xmi +*.swp +.settings +.checkstyle +.properties diff --git a/projects/glutolik/pom.xml b/projects/glutolik/pom.xml index 6874eec..fb0dbcb 100644 --- a/projects/glutolik/pom.xml +++ b/projects/glutolik/pom.xml @@ -22,5 +22,36 @@ 3.8.1 test + + + org.twitter4j + twitter4j-core + [4.0,) + + + + org.twitter4j + twitter4j-stream + [4.0,) + + + + com.beust + jcommander + 1.48 + + + + junit + junit + 4.11 + + + + com.google.maps + google-maps-services + 0.1.1 + + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java deleted file mode 100644 index ef35824..0000000 --- a/projects/glutolik/src/main/java/ru/mipt/diht/students/TwitterStream/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.mipt.diht.students.TwitterStream; - -/** - * Created by glutolik on 02.12.15. - */ -public class Main { -} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Reverser/Reverser.java similarity index 86% rename from projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java rename to projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Reverser/Reverser.java index 24667b3..8c428d1 100644 --- a/projects/glutolik/src/main/java/ru/mipt/diht/students/Reverser/Reverser.java +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Reverser/Reverser.java @@ -2,7 +2,7 @@ * Created by glutolik on 20.09.2015. */ public class Reverser { - public static void main (String[] args) { + public static void main(String[] args) { int ind1, ind2; for (ind1 = args.length - 1; ind1 >= 0; --ind1) { String[] divided = args[ind1].split("\\s"); diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/GeolocationUtils.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/GeolocationUtils.java new file mode 100644 index 0000000..693f17f --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/GeolocationUtils.java @@ -0,0 +1,68 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.google.maps.GeoApiContext; +import com.google.maps.GeocodingApi; +import com.google.maps.GeocodingApiRequest; +import com.google.maps.model.GeocodingResult; +import com.google.maps.model.Geometry; +import twitter4j.GeoLocation; +import twitter4j.Place; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Created by glutolik on 13.12.15. + */ +public class GeolocationUtils { + protected static final String GOOGLE_KEY = readGoogleKey(); + + private static String readGoogleKey() { + Properties properties = new Properties(); + try (FileInputStream propertiesFile = new FileInputStream("geolocation.properties")) { + properties.load(propertiesFile); + return properties.getProperty("key"); + } catch (IOException e) { + System.err.println(e.getMessage()); + return null; + } + } + + public static Geometry getCoordinates(String location) { + GeoApiContext context = new GeoApiContext().setApiKey(GOOGLE_KEY); + GeocodingApiRequest geoRequest = GeocodingApi.geocode(context, location); + try { + GeocodingResult[] locations = geoRequest.await(); + return locations[0].geometry; + } catch (Exception e) { + System.err.println("Couldn't connect to google.com"); + return null; + } + } + + public static boolean checkLocation(Place place, Geometry searchLocationForm) { + if (searchLocationForm == null) { + return true; + } + if (place == null) { + return false; + } + + double latitudeSum = 0, longitudeSum = 0; + GeoLocation[][] geoLocations = place.getBoundingBoxCoordinates(); + + for (int i = 0; i < geoLocations[0].length; ++i) { + latitudeSum += geoLocations[0][i].getLatitude(); + longitudeSum += geoLocations[0][i].getLongitude(); + } + double latitude = latitudeSum / geoLocations[0].length; + double longtitude = longitudeSum / geoLocations[0].length; + + return (latitude > searchLocationForm.bounds.southwest.lat + && latitude < searchLocationForm.bounds.northeast.lat + && longtitude > searchLocationForm.bounds.southwest.lng + && longtitude < searchLocationForm.bounds.northeast.lng); + } + +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java new file mode 100644 index 0000000..f7a4d95 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java @@ -0,0 +1,43 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.beust.jcommander.JCommander; + +/** + * Created by glutolik on 13.12.15. + */ +public class RunTwitterStream { + public static void main(String[] args) { + + TerminalArguments arguments = new TerminalArguments(); + JCommander jCommander = new JCommander(arguments, args); + + if (arguments.isHelp()) { + jCommander.usage(); + return; + } + + if (arguments.isStream()) { + StreamTweets streamTweets = new StreamTweets( + arguments.getKeyWord(), + arguments.getLocation(), + arguments.isHideRetweets() + ); + + streamTweets.beginStream(); + } else { + SearchTweets searchTweets = new SearchTweets( + arguments.getKeyWord(), + arguments.getLocation(), + arguments.isHideRetweets(), + arguments.getLimit()); + + try { + searchTweets.search(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + System.out.println(e.getMessage()); + } + } + } +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/SearchTweets.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/SearchTweets.java new file mode 100644 index 0000000..f35cd49 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/SearchTweets.java @@ -0,0 +1,70 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.google.maps.model.Geometry; +import twitter4j.*; + +import java.util.List; + +/** + * Created by glutolik on 13.12.15. + */ +public class SearchTweets { + private String keyWord = null; + private static final int MAX_TRIES = 8; + private static final int DELAY = 5000; + private int numberOfTweets; + private boolean hideRetweets = false; + private Geometry location = null; + + public SearchTweets(String key, String loc, boolean hide, int limit) throws IllegalArgumentException { + hideRetweets = hide; + numberOfTweets = limit; + keyWord = key; + + if (keyWord == null) { + throw new IllegalArgumentException("Empty key"); + } + + if (loc != null) { + location = GeolocationUtils.getCoordinates(loc); + } + } + + + public final void search() throws InterruptedException, IllegalStateException { + Twitter twitter = new TwitterFactory().getInstance(); + + Query query = new Query(); + query.setQuery(keyWord); + //query.setCount(100); + + QueryResult result = null; + for (int tries = 0; tries < MAX_TRIES; tries++) { + try { + result = twitter.search(query); + } catch (TwitterException exept) { + System.err.println("Couldn't make a request to Twitter.com. The cause is " + exept.getMessage()); + Thread.sleep(DELAY); + } + } + + if (result == null) { + throw new IllegalStateException("Couldn't connect to Twitter.com"); + } + + int number = 0; + while (query != null && number < numberOfTweets) { + List tweets = result.getTweets(); + for (Status tweet : tweets) { + if (TwitterStreamUtils.check(tweet, location, hideRetweets)) { + TwitterStreamUtils.printTweet(tweet, true); + number++; + } + query = result.nextQuery(); + } + } + if (number == 0) { + System.out.println("Didn't find any tweets"); + } + } +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/StreamTweets.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/StreamTweets.java new file mode 100644 index 0000000..c48f0e9 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/StreamTweets.java @@ -0,0 +1,66 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.google.maps.model.Geometry; +import twitter4j.*; + +import java.util.LinkedList; + + +/** + * Created by glutolik on 13.12.15. + */ +public class StreamTweets { + + private String[] keyWord = null; + private LinkedList streamQueue; + private static final int DELAY = 1000; + private static final int LIMIT = 25; + private boolean hideRetweets = false; + private Geometry location = null; + + public StreamTweets(String key, String loc, boolean hide) throws IllegalArgumentException { + if (location != null) { + location = GeolocationUtils.getCoordinates(loc); + } + if (key == null) { + throw new IllegalArgumentException("Key shouldn't be empty"); + } + keyWord = new String[]{key}; + hideRetweets = hide; + } + + public final void beginStream() { + TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); + + twitterStream.addListener(statusAdapter); + + FilterQuery filterQuery = new FilterQuery(); + filterQuery.track(keyWord); + + twitterStream.filter(filterQuery); + streamQueue = new LinkedList<>(); + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(DELAY); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + synchronized (streamQueue) { + if (!streamQueue.isEmpty()) { + Status tweet = streamQueue.poll(); + TwitterStreamUtils.printTweet(tweet, false); + } + } + } + } + + private StatusAdapter statusAdapter = new StatusAdapter() { + public void onStatus(Status tweet) { + if (streamQueue.size() < LIMIT && TwitterStreamUtils.check(tweet, location, hideRetweets)) { + synchronized (streamQueue) { + streamQueue.add(tweet); + } + } + } + }; +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TerminalArguments.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TerminalArguments.java new file mode 100644 index 0000000..cbe9db1 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TerminalArguments.java @@ -0,0 +1,54 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.beust.jcommander.Parameter; + +/** + * Created by glutolik on 13.12.15. + */ +public class TerminalArguments { + + private static final int DEFAULT_LIMIT = 25; + + @Parameter(names = {"--query", "-q"}, description = "Query or keywords for stream") + private String keyWord = null; + + @Parameter(names = {"--place", "-p"}, description = "Where do you want to find tweets from") + private String location = null; + + @Parameter(names = {"--stream", "-s"}, description = "Show new tweets") + private boolean streamMode = false; + + @Parameter(names = {"--hideRetweets"}, description = "Hides retweets") + private boolean hideRetweets = false; + + @Parameter(names = {"--limit", "-l"}, description = "Number of test will be shown") + private int limit = DEFAULT_LIMIT; + + @Parameter(names = {"-h", "--help"}, description = "print help page", help = true) + private boolean help = false; + + public final String getKeyWord() { + return keyWord; + } + + public final String getLocation() { + return location; + } + + public final boolean isStream() { + return streamMode; + } + + public final boolean isHideRetweets() { + return hideRetweets; + } + + public final int getLimit() { + return limit; + } + + public final boolean isHelp() { + return help; + } +} + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TwitterStreamUtils.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TwitterStreamUtils.java new file mode 100644 index 0000000..227b34f --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/TwitterStreamUtils.java @@ -0,0 +1,101 @@ +package ru.mipt.diht.students.glutolik.TwitterStream; + +import com.google.maps.model.Geometry; +import twitter4j.Status; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +/** + * Created by glutolik on 13.12.15. + */ +public class TwitterStreamUtils { + + + public static enum Colors { + CLEAR(0), + BLACK(30), + RED(31), + GREEN(32), + YELLOW(33), + BLUE(34), + MAGENTA(35), + CYAN(36), + WHITE(37); + + private int code; + + Colors(int colorCode) { + this.code = colorCode; + } + + public String getColor() { + return "\033[" + code + "m"; + } + } + + public static String paint(String text, Colors color) { + return color.getColor() + text + Colors.CLEAR.getColor(); + } + + public static String spellTime(Date anotherDate, Date currentDate) { + LocalDateTime currentTime = currentDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + LocalDateTime anotherTime = anotherDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + if (ChronoUnit.MINUTES.between(anotherTime, currentTime) < 2) { + return "Только что"; + } + if (ChronoUnit.HOURS.between(anotherTime, currentTime) < 1) { + return ChronoUnit.MINUTES.between(anotherTime, currentTime) + " минут назад"; + } + if (ChronoUnit.DAYS.between(anotherTime, currentTime) < 1) { + return ChronoUnit.HOURS.between(anotherTime, currentTime) + " часов назад"; + } + if (ChronoUnit.DAYS.between(anotherTime, currentTime) == 1) { + return "Вчера"; + } + return ChronoUnit.DAYS.between(anotherTime, currentTime) + " дней назад"; + } + + public static boolean check(Status tweet, Geometry location, boolean hide) { + return (GeolocationUtils.checkLocation(tweet.getPlace(), location) && (!hide || !tweet.isRetweet())); + } + + public static String paintName(String userName, Colors color) { + return paint("@" + userName, color); + } + + + public static String formate(Status tweet, boolean showTime) { + StringBuilder message = new StringBuilder(""); + if (showTime) { + message.append("[") + .append(spellTime(tweet.getCreatedAt(), new Date())) + .append("]"); + } + if (!tweet.isRetweet()) { + message.append(paintName(tweet.getUser().getScreenName(), Colors.BLUE)) + .append(": ") + .append(tweet.getText()); + + if (tweet.getRetweetCount() > 0) { + message.append(" (") + .append(tweet.getRetweetCount()) + .append(" ретвитов)"); + } + } else { + Status nativeTweet = tweet.getRetweetedStatus(); + message.append(paintName(tweet.getUser().getScreenName(), Colors.BLUE)) + .append(": ретвитнул ") + .append(paintName(nativeTweet.getUser().getScreenName(), Colors.BLUE)) + .append(": ") + .append(nativeTweet.getText()); + } + return message.toString(); + } + + public static void printTweet(Status tweet, boolean showTime) { + System.out.println(formate(tweet, showTime)); + } +} diff --git a/projects/glutolik/twitter4j.properties b/projects/glutolik/twitter4j.properties new file mode 100644 index 0000000..18e557a --- /dev/null +++ b/projects/glutolik/twitter4j.properties @@ -0,0 +1,5 @@ +debug=false +oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY +oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA +oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K +oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO diff --git a/projects/twitter4j.properties b/projects/twitter4j.properties new file mode 100644 index 0000000..18e557a --- /dev/null +++ b/projects/twitter4j.properties @@ -0,0 +1,5 @@ +debug=false +oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY +oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA +oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K +oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO diff --git a/projects/twitter4j.properties~ b/projects/twitter4j.properties~ new file mode 100644 index 0000000..e69de29 From 7c8fdb2e16603073eaf81c1bb370d247b8a420b3 Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 15:55:48 +0300 Subject: [PATCH 03/10] Twitter --- projects/glutolik/twitter4j.properties | 5 ----- projects/twitter4j.properties | 5 ----- projects/twitter4j.properties~ | 0 3 files changed, 10 deletions(-) delete mode 100644 projects/glutolik/twitter4j.properties delete mode 100644 projects/twitter4j.properties delete mode 100644 projects/twitter4j.properties~ diff --git a/projects/glutolik/twitter4j.properties b/projects/glutolik/twitter4j.properties deleted file mode 100644 index 18e557a..0000000 --- a/projects/glutolik/twitter4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -debug=false -oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY -oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA -oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K -oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO diff --git a/projects/twitter4j.properties b/projects/twitter4j.properties deleted file mode 100644 index 18e557a..0000000 --- a/projects/twitter4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -debug=false -oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY -oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA -oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K -oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO diff --git a/projects/twitter4j.properties~ b/projects/twitter4j.properties~ deleted file mode 100644 index e69de29..0000000 From f723c5d7d5ab00ff41fbb6947b5e70ab78cb99ca Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 16:04:11 +0300 Subject: [PATCH 04/10] Delete twitter4j.properties --- projects/glutolik/twitter4j.properties | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 projects/glutolik/twitter4j.properties diff --git a/projects/glutolik/twitter4j.properties b/projects/glutolik/twitter4j.properties deleted file mode 100644 index 18e557a..0000000 --- a/projects/glutolik/twitter4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -debug=false -oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY -oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA -oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K -oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO From ff9178ff7d696503fd1b27e6cc962762877f4e6a Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 16:04:25 +0300 Subject: [PATCH 05/10] Delete twitter4j.properties --- projects/twitter4j.properties | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 projects/twitter4j.properties diff --git a/projects/twitter4j.properties b/projects/twitter4j.properties deleted file mode 100644 index 18e557a..0000000 --- a/projects/twitter4j.properties +++ /dev/null @@ -1,5 +0,0 @@ -debug=false -oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY -oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA -oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K -oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO From 448f904b3d19dbd869cfef9ade1dc51cbfe09a2d Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 16:04:32 +0300 Subject: [PATCH 06/10] Delete twitter4j.properties~ --- projects/twitter4j.properties~ | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 projects/twitter4j.properties~ diff --git a/projects/twitter4j.properties~ b/projects/twitter4j.properties~ deleted file mode 100644 index e69de29..0000000 From ffccedf748216c4557bf91fc255fe0ccf9c3a151 Mon Sep 17 00:00:00 2001 From: glutolik Date: Mon, 14 Dec 2015 18:09:10 +0300 Subject: [PATCH 07/10] ThreadsCounting using CyclicBarrier --- .../glutolik/Threads/ThreadsCounting.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java new file mode 100644 index 0000000..2608438 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java @@ -0,0 +1,75 @@ +package ru.mipt.diht.students.glutolik.Threads; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +/** + * Created by glutolik on 14.12.15. + */ +public class ThreadsCounting { + private static volatile Integer numberOfThreads; + private List kinderGarten = new ArrayList<>(); + + public static Integer getNumber() { + return numberOfThreads; + } + + private class Child extends Thread { + private int id; + private CyclicBarrier barrier; + + Child(int number, CyclicBarrier barrier1) { + id = number; + barrier = barrier1; + } + + @Override + public String toString() { + return "Thread-" + id; + } + + @Override + public void run() { + try { + while (!Thread.interrupted()) { + synchronized (numberOfThreads) { + if (numberOfThreads == id) { + System.out.println(this); + } + } + barrier.await(); + } + } catch (InterruptedException exception) { + } catch (BrokenBarrierException exception1) { + exception1.printStackTrace(); + } + } + } + + public ThreadsCounting(int number) { + numberOfThreads = 0; + CyclicBarrier barrier1 = new CyclicBarrier(number, () -> { + synchronized (numberOfThreads) { + if (numberOfThreads < number) { + ++numberOfThreads; + } else { + for (Thread ch : kinderGarten) { + ch.interrupt(); + } + } + } + }); + for (int i = 0; i < number; i++) { + Thread child = new Child(i + 1, barrier1); + child.start(); + kinderGarten.add(child); + } + } + + public static void main(String[] args) { + new ThreadsCounting(Integer.valueOf(args[0])); + } + +} From 3ece5617bf1fcf502aa760fcb8a1447965a96bd8 Mon Sep 17 00:00:00 2001 From: glutolik Date: Wed, 16 Dec 2015 00:27:32 +0300 Subject: [PATCH 08/10] all done --- projects/glutolik/.gitignore | 2 +- projects/glutolik/pom.xml | 149 +- .../glutolik/Threads/BlockingQueue.java | 57 + .../students/glutolik/Threads/Muster.java | 76 + .../glutolik/Threads/ThreadsCounting.java | 42 +- .../{RunTwitterStream.java => Main.java} | 2 +- .../GeolocationUtilsTests.java | 66 + .../TwitterStreamTests.java | 50 + .../TwitterStreamUtilsTests.java | 58 + .../test/java/twitter4j/Twitter4jTests.java | 30 + .../glutolik/src/test/resources/tweets.json | 2740 +++++++++++++++++ .../glutolik/src/test/resources/tweets.json~ | 0 projects/twitter4j.properties | 5 + projects/twitter4j.properties~ | 0 14 files changed, 3194 insertions(+), 83 deletions(-) create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/BlockingQueue.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/Muster.java rename projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/{RunTwitterStream.java => Main.java} (97%) create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/GeolocationUtilsTests.java create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamTests.java create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamUtilsTests.java create mode 100644 projects/glutolik/src/test/java/twitter4j/Twitter4jTests.java create mode 100644 projects/glutolik/src/test/resources/tweets.json create mode 100644 projects/glutolik/src/test/resources/tweets.json~ create mode 100644 projects/twitter4j.properties create mode 100644 projects/twitter4j.properties~ diff --git a/projects/glutolik/.gitignore b/projects/glutolik/.gitignore index 90ed889..1e67c8e 100644 --- a/projects/glutolik/.gitignore +++ b/projects/glutolik/.gitignore @@ -18,4 +18,4 @@ workbench.xmi *.swp .settings .checkstyle -.properties +*.properties diff --git a/projects/glutolik/pom.xml b/projects/glutolik/pom.xml index fb0dbcb..45b8da5 100644 --- a/projects/glutolik/pom.xml +++ b/projects/glutolik/pom.xml @@ -1,57 +1,100 @@ - - 4.0.0 - + + 4.0.0 + + ru.mipt.diht.students + parent + 1.0-SNAPSHOT + ru.mipt.diht.students - parent + glutolik 1.0-SNAPSHOT - - ru.mipt.diht.students - glutolik - 1.0-SNAPSHOT - glutolik - http://maven.apache.org - - UTF-8 - - - - junit - junit - 3.8.1 - test - - - - org.twitter4j - twitter4j-core - [4.0,) - - - - org.twitter4j - twitter4j-stream - [4.0,) - - - - com.beust - jcommander - 1.48 - - - - junit - junit - 4.11 - - - - com.google.maps - google-maps-services - 0.1.1 - - - + glutolik + http://maven.apache.org + + UTF-8 + + + + junit + junit + 3.8.1 + test + + + + org.twitter4j + twitter4j-core + [4.0,) + + + + org.twitter4j + twitter4j-stream + [4.0,) + + + + com.beust + jcommander + 1.48 + + + + junit + junit + 4.12 + test + + + + com.google.maps + google-maps-services + 0.1.1 + + + + org.powermock + powermock-module-junit4 + 1.6.2 + test + + + + org.powermock + powermock-api-mockito + 1.6.2 + test + + + + commons-io + commons-io + 2.4 + test + + + + org.apache.commons + commons-lang3 + 3.4 + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + + com.tngtech.java + junit-dataprovider + 1.10.1 + test + + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/BlockingQueue.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/BlockingQueue.java new file mode 100644 index 0000000..6becbb6 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/BlockingQueue.java @@ -0,0 +1,57 @@ +package ru.mipt.diht.students.glutolik.Threads; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by glutolik on 14.12.15. + */ +public class BlockingQueue { + private volatile Queue queue = new LinkedList<>(); + private final int maxElements; + private Lock lock = new ReentrantLock(); + + public BlockingQueue(int number) { + maxElements = number; + } + + public final void offer(List newMembers) throws InterruptedException, IllegalArgumentException { + lock.lock(); + boolean freeSpace = true; + try { + synchronized (queue) { + while (newMembers.size() + queue.size() > maxElements) { + queue.wait(); + } + queue.addAll(newMembers); + } + } finally { + lock.unlock(); + } + } + + public final List take(int numberOfMembers) throws IllegalArgumentException, InterruptedException { + lock.lock(); + try { + if (numberOfMembers > maxElements) { + throw new IllegalArgumentException("You want to take too many members"); + } + List taken = new ArrayList<>(); + synchronized (queue) { + while (numberOfMembers > queue.size()) { + queue.wait(); + } + for (int i = 0; i < numberOfMembers; i++) { + taken.add(queue.poll()); + } + } + return taken; + } finally { + lock.unlock(); + } + } +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/Muster.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/Muster.java new file mode 100644 index 0000000..c4babc6 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/Muster.java @@ -0,0 +1,76 @@ +package ru.mipt.diht.students.glutolik.Threads; + +import java.util.Random; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by glutolik on 14.12.15. + */ +public class Muster { + private static volatile Boolean ready = true; + + + private class MusterChild extends Thread { + private int id; + private final Random random = new Random(); + private final int success = 90; + private final int maximum = 99; + private CyclicBarrier barrier; + + MusterChild(int number, CyclicBarrier barrier1) { + id = number; + barrier = barrier1; + } + + @Override + public String toString() { + return "Thread-" + id; + } + + @Override + public void run() { + while (!Thread.interrupted()) { + if (random.nextInt(maximum) > success) { + System.out.println(this + " NO"); + ready = false; + //Thread.currentThread().interrupt(); + } else { + System.out.println(this + " YES"); + //Thread.currentThread().interrupt(); + } + try { + barrier.await(); + } catch (InterruptedException ignored) { + } catch (BrokenBarrierException e) { + e.printStackTrace(); + } + } + } + } + + public final void muster(int number) { + System.out.println("Are you ready?"); + ExecutorService exec = Executors.newCachedThreadPool(); + CyclicBarrier barrier = new CyclicBarrier(number, () -> { + synchronized (ready) { + if (ready) { + System.out.println("Everyone is ready!"); + exec.shutdownNow(); + } else { + synchronized (ready) { + ready = true; + System.out.println("I'm asking again. Are you ready?"); + } + } + } + }); + for (int i = 0; i < number; i++) { + exec.execute(new MusterChild(i + 1, barrier)); + } + } + +} + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java index 2608438..9278139 100644 --- a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/Threads/ThreadsCounting.java @@ -2,8 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; /** * Created by glutolik on 14.12.15. @@ -11,6 +9,7 @@ public class ThreadsCounting { private static volatile Integer numberOfThreads; private List kinderGarten = new ArrayList<>(); + private volatile Integer current = 1; public static Integer getNumber() { return numberOfThreads; @@ -18,11 +17,10 @@ public static Integer getNumber() { private class Child extends Thread { private int id; - private CyclicBarrier barrier; - Child(int number, CyclicBarrier barrier1) { + + Child(int number) { id = number; - barrier = barrier1; } @Override @@ -32,37 +30,25 @@ public String toString() { @Override public void run() { - try { - while (!Thread.interrupted()) { - synchronized (numberOfThreads) { - if (numberOfThreads == id) { - System.out.println(this); + while (!Thread.interrupted()) { + synchronized (current) { + if (current == id) { + System.out.println(this); + current++; + Thread.currentThread().interrupt(); + if (current > numberOfThreads) { + current = 0; } } - barrier.await(); } - } catch (InterruptedException exception) { - } catch (BrokenBarrierException exception1) { - exception1.printStackTrace(); } } } public ThreadsCounting(int number) { - numberOfThreads = 0; - CyclicBarrier barrier1 = new CyclicBarrier(number, () -> { - synchronized (numberOfThreads) { - if (numberOfThreads < number) { - ++numberOfThreads; - } else { - for (Thread ch : kinderGarten) { - ch.interrupt(); - } - } - } - }); - for (int i = 0; i < number; i++) { - Thread child = new Child(i + 1, barrier1); + numberOfThreads = number; + for (int i = 0; i < numberOfThreads; i++) { + Thread child = new Child(i + 1); child.start(); kinderGarten.add(child); } diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/Main.java similarity index 97% rename from projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java rename to projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/Main.java index f7a4d95..fcb62a2 100644 --- a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/RunTwitterStream.java +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/TwitterStream/Main.java @@ -5,7 +5,7 @@ /** * Created by glutolik on 13.12.15. */ -public class RunTwitterStream { +public class Main { public static void main(String[] args) { TerminalArguments arguments = new TerminalArguments(); diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/GeolocationUtilsTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/GeolocationUtilsTests.java new file mode 100644 index 0000000..e983e18 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/GeolocationUtilsTests.java @@ -0,0 +1,66 @@ +package ru.mipt.diht.students.glutolik.TwitterStreamTests; + + +import com.google.maps.model.Bounds; +import com.google.maps.model.Geometry; +import com.google.maps.model.LatLng; +import org.junit.Test; +import ru.mipt.diht.students.glutolik.TwitterStream.GeolocationUtils; +import twitter4j.GeoLocation; +import twitter4j.Place; +import twitter4j.Status; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by glutolik on 15.12.15. + */ + +public class GeolocationUtilsTests { + @Test + public void testGetCoordinates() { + List cities = new ArrayList<>(); + cities.add("Москва"); + cities.add("Сочи"); + cities.add("Ташкент"); + List coordinates = new ArrayList<>(); + coordinates.add(new Double[]{55.755826, 37.6173}); + coordinates.add(new Double[]{43.602806091308594, 39.734153747558594}); + coordinates.add(new Double[]{-41.3, -110.733333}); + List locations = new ArrayList<>(); + locations.add(new GeoLocation(coordinates.get(0)[0], coordinates.get(0)[1])); + locations.add(new GeoLocation(coordinates.get(1)[0], coordinates.get(1)[1])); + locations.add(new GeoLocation(coordinates.get(2)[0], coordinates.get(2)[1])); + assertEquals(locations.get(0), GeolocationUtils.getCoordinates(cities.get(0))); + assertEquals(locations.get(1), GeolocationUtils.getCoordinates(cities.get(1))); + assertEquals(locations.get(2), GeolocationUtils.getCoordinates(cities.get(2))); + } + + Status mockStatus = mock(Status.class); + + @Test + public void testCheckLocation() { + Geometry mockGeometry = mock(Geometry.class); + Bounds mockBounds = mock(Bounds.class); + Place mockPlace = mock(Place.class); + mockGeometry.bounds = mockBounds;// = new LatLng(32.50, -45.90); + mockBounds.northeast = new LatLng(32.50, -45.90); + mockBounds.southwest = new LatLng(32.00, -46.00); + when(mockStatus.getPlace()).thenReturn(mockPlace); + + when(mockPlace.getBoundingBoxCoordinates()).thenReturn( + new GeoLocation[][]{{new GeoLocation(32.25, -45.97)}}); + assertTrue(GeolocationUtils.checkLocation(mockPlace, mockGeometry)); + + when(mockPlace.getBoundingBoxCoordinates()).thenReturn( + new GeoLocation[][]{{new GeoLocation(32.25, 45.97)}}); + assertFalse(GeolocationUtils.checkLocation(mockPlace, mockGeometry)); + + assertFalse(GeolocationUtils.checkLocation(null, mockGeometry)); + } +} diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamTests.java new file mode 100644 index 0000000..fff9795 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamTests.java @@ -0,0 +1,50 @@ +package ru.mipt.diht.students.glutolik.TwitterStreamTests; + +import org.junit.BeforeClass; +import org.mockito.Mock; +import org.mockito.Mockito; +import ru.mipt.diht.students.glutolik.TwitterStream.TerminalArguments; +import ru.mipt.diht.students.glutolik.TwitterStream.TwitterStreamUtils; +import twitter4j.*; + +import java.util.LinkedList; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.core.IsNot.not; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by glutolik on 15.12.15. + */ +public class TwitterStreamTests { + @Mock + private static Twitter twitter; + @Mock + private static TerminalArguments args; + + private static List tweets; + + @BeforeClass + static public void setUp() throws Exception { + twitter = mock(Twitter.class); + args = mock(TerminalArguments.class); + tweets = Twitter4jTests.tweetsFromJson("tweets.json"); + + Mockito.mock(TwitterStreamUtils.class); + + Mockito.mock(TwitterFactory.class); + Mockito.when(TwitterFactory.getSingleton()).thenReturn(twitter); + + QueryResult resultForJava = mock(QueryResult.class); + when(resultForJava.getTweets()).thenReturn(tweets); + when(twitter.search(argThat(hasProperty("query", equalTo("java"))))).thenReturn(resultForJava); + + QueryResult emptyResult = mock(QueryResult.class); + when(emptyResult.getTweets()).thenReturn(new LinkedList<>()); + when(twitter.search(argThat(hasProperty("query", not(equalTo("java")))))).thenReturn(emptyResult); + } +} diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamUtilsTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamUtilsTests.java new file mode 100644 index 0000000..2fe6b86 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/TwitterStreamTests/TwitterStreamUtilsTests.java @@ -0,0 +1,58 @@ +package ru.mipt.diht.students.glutolik.TwitterStreamTests; + +import org.junit.Test; +import ru.mipt.diht.students.glutolik.TwitterStream.TwitterStreamUtils; +import twitter4j.Status; +import twitter4j.User; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by glutolik on 15.12.15. + */ +public class TwitterStreamUtilsTests { + @Test + public void testPaintName() { + assertEquals("\033[34m@User-Name123\033[0m", TwitterStreamUtils.paintName("User-Name123", TwitterStreamUtils.Colors.BLUE)); + } + + @Test + public void testPaint() { + assertEquals("\033[34mBlueText\033[0m", + TwitterStreamUtils.paint("BlueText", TwitterStreamUtils.Colors.BLUE)); + assertEquals("\033[0mNo Color\033[0m", + TwitterStreamUtils.paint("No Color", TwitterStreamUtils.Colors.CLEAR)); + assertEquals("\033[37m\033[0m", + TwitterStreamUtils.paint("", TwitterStreamUtils.Colors.WHITE)); + } + + @Test + public void testSpellTime() { + Calendar thisTime = new GregorianCalendar(2015, 0, 1, 0, 2); + Calendar fiveMins = new GregorianCalendar(2014, 11, 31, 23, 57); + Calendar thirtyDays = new GregorianCalendar(2014, 11, 1, 0, 10); + Calendar yesterday = new GregorianCalendar(2014, 11, 31, 0, 0); + assertEquals("5 минут назад", + TwitterStreamUtils.spellTime( + fiveMins.getTime(), thisTime.getTime())); + assertEquals("30 дней назад", TwitterStreamUtils.spellTime(thirtyDays.getTime(), thisTime.getTime())); + assertEquals("Вчера", TwitterStreamUtils.spellTime(yesterday.getTime(), thisTime.getTime())); + } + + @Test + public void testFormate() { + Status mockStatus = mock(Status.class); + User mockUser = mock(User.class); + when(mockStatus.getText()).thenReturn("The dream will finally come true!"); + when(mockStatus.getUser()).thenReturn(mockUser); + when(mockUser.getScreenName()).thenReturn("Glutolik"); + String formatted = TwitterStreamUtils.formate(mockStatus, false); + assertEquals("\033[34m@Glutolik\033[0m: The dream will finally come true!", formatted); + } +} + diff --git a/projects/glutolik/src/test/java/twitter4j/Twitter4jTests.java b/projects/glutolik/src/test/java/twitter4j/Twitter4jTests.java new file mode 100644 index 0000000..241df8e --- /dev/null +++ b/projects/glutolik/src/test/java/twitter4j/Twitter4jTests.java @@ -0,0 +1,30 @@ +package twitter4j; + +import twitter4j.*; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by glutolik on 15.12.15. + */ +public class Twitter4jTests { + public static List tweetsFromJson(String resource) { + try (InputStream inputStream = Twitter4jTests.class.getResourceAsStream(resource)) { + JSONObject json = new JSONObject(IOUtils.toString(inputStream)); + JSONArray array = json.getJSONArray("statuses"); + List tweets = new ArrayList<>(array.length()); + for (int i = 0; i < array.length(); i++) { + JSONObject tweet = array.getJSONObject(i); + tweets.add(new StatusJSONImpl(tweet)); + } + return tweets; + } catch (IOException | JSONException | TwitterException e) { + throw new RuntimeException(e); + } + } +} diff --git a/projects/glutolik/src/test/resources/tweets.json b/projects/glutolik/src/test/resources/tweets.json new file mode 100644 index 0000000..d4d7fb3 --- /dev/null +++ b/projects/glutolik/src/test/resources/tweets.json @@ -0,0 +1,2740 @@ +{ + "search_metadata": { + "completed_in": 0.031, + "count": 15, + "max_id": 648945747530924033, + "max_id_str": "648945747530924033", + "next_results": "?max_id=648945609307615231&q=java&include_entities=1", + "query": "java", + "refresh_url": "?since_id=648945747530924033&q=java&include_entities=1", + "since_id": 0, + "since_id_str": "0" + }, + "statuses": [ + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:37 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945747530924033, + "id_str": "648945747530924033", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 52, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter Web Client<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Mar 03 02:42:28 +0000 2010", + "default_profile": false, + "default_profile_image": false, + "description": "Freelancer, drifter, occasional poet, sometime book reviewer. My opinions are my own and definitely not those of the Hive Mind.", + "entities": {"description": {"urls": []}}, + "favourites_count": 317, + "follow_request_sent": false, + "followers_count": 386, + "following": false, + "friends_count": 655, + "geo_enabled": true, + "has_extended_profile": false, + "id": 119247132, + "id_str": "119247132", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 17, + "location": "", + "name": "Alex Walls", + "notifications": false, + "profile_background_color": "ACDED6", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme18/bg.gif", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme18/bg.gif", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/119247132/1406185638", + "profile_image_url": "http://pbs.twimg.com/profile_images/591133017084538880/R3TvUHZ__normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/591133017084538880/R3TvUHZ__normal.jpg", + "profile_link_color": "038543", + "profile_sidebar_border_color": "EEEEEE", + "profile_sidebar_fill_color": "F6F6F6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "lxwalls", + "statuses_count": 5078, + "time_zone": "Wellington", + "url": null, + "utc_offset": 46800, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:34 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 0, + 7 + ], + "text": "Hiring" + }], + "symbols": [], + "urls": [{ + "display_url": "techfetch.com/JS/JS_view_job\u2026", + "expanded_url": "http://www.techfetch.com/JS/JS_view_job.aspx?js=2840002", + "indices": [ + 59, + 81 + ], + "url": "http://t.co/0slLn3YVTW" + }], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945732205023232, + "id_str": "648945732205023232", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "TechFetch.com<\/a>", + "text": "#Hiring Java Lead Developer - Click here for job details : http://t.co/0slLn3YVTW", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Oct 07 16:26:23 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": {"description": {"urls": []}}, + "favourites_count": 9, + "follow_request_sent": false, + "followers_count": 67, + "following": false, + "friends_count": 821, + "geo_enabled": true, + "has_extended_profile": true, + "id": 80614847, + "id_str": "80614847", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 4, + "location": "San Jose, CA", + "name": "Ankit Tomar", + "notifications": false, + "profile_background_color": "000000", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme10/bg.gif", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme10/bg.gif", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/80614847/1353177973", + "profile_image_url": "http://pbs.twimg.com/profile_images/2861537783/4b15ad7da51a7882f16fdbf16767131b_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/2861537783/4b15ad7da51a7882f16fdbf16767131b_normal.jpeg", + "profile_link_color": "FA743E", + "profile_sidebar_border_color": "000000", + "profile_sidebar_fill_color": "000000", + "profile_text_color": "000000", + "profile_use_background_image": false, + "protected": false, + "screen_name": "Ankit__Tomar", + "statuses_count": 165, + "time_zone": "New Delhi", + "url": null, + "utc_offset": 19800, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:32 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945725640962048, + "id_str": "648945725640962048", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 52, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "twicca<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 21 23:34:35 +0000 2009", + "default_profile": false, + "default_profile_image": false, + "description": "(aka Yurado/The Mind) Inmortal... →", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "about.me/yurado", + "expanded_url": "http://about.me/yurado", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/h812amM7uj" + }]} + }, + "favourites_count": 555, + "follow_request_sent": false, + "followers_count": 1353, + "following": false, + "friends_count": 1357, + "geo_enabled": true, + "has_extended_profile": true, + "id": 41702532, + "id_str": "41702532", + "is_translation_enabled": false, + "is_translator": false, + "lang": "es", + "listed_count": 14, + "location": "Bulnes, Chile", + "name": "ライオット・イン・ラゴス", + "notifications": false, + "profile_background_color": "8B542B", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/53741136/greenbackgroundA.jpg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/53741136/greenbackgroundA.jpg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/41702532/1349926347", + "profile_image_url": "http://pbs.twimg.com/profile_images/634018808819286016/IbzlRzif_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/634018808819286016/IbzlRzif_normal.jpg", + "profile_link_color": "9D582E", + "profile_sidebar_border_color": "D9B17E", + "profile_sidebar_fill_color": "EADEAA", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "ElYurado", + "statuses_count": 24722, + "time_zone": "Santiago", + "url": "http://t.co/h812amM7uj", + "utc_offset": -10800, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:32 +0000 2015", + "entities": { + "hashtags": [], + "media": [{ + "display_url": "pic.twitter.com/XYjX1C98ae", + "expanded_url": "http://twitter.com/gomezbartolomeg/status/648945723933884416/photo/1", + "id": 648945723824844802, + "id_str": "648945723824844802", + "indices": [ + 95, + 117 + ], + "media_url": "http://pbs.twimg.com/media/CQGEspMWsAIoZVp.jpg", + "media_url_https": "https://pbs.twimg.com/media/CQGEspMWsAIoZVp.jpg", + "sizes": { + "large": { + "h": 300, + "resize": "fit", + "w": 238 + }, + "medium": { + "h": 300, + "resize": "fit", + "w": 238 + }, + "small": { + "h": 300, + "resize": "fit", + "w": 238 + }, + "thumb": { + "h": 150, + "resize": "crop", + "w": 150 + } + }, + "type": "photo", + "url": "http://t.co/XYjX1C98ae" + }], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945723933884416, + "id_str": "648945723933884416", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "IFTTT<\/a>", + "text": "The Java(TM) Developers Almanac 2000 (3rd Edition) by Chan, Patrick, Rosanna Le [link removed] http://t.co/XYjX1C98ae", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Sat Feb 14 20:55:22 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "pompous offer", + "entities": {"description": {"urls": []}}, + "favourites_count": 0, + "follow_request_sent": false, + "followers_count": 62, + "following": false, + "friends_count": 40, + "geo_enabled": false, + "has_extended_profile": false, + "id": 3020129600, + "id_str": "3020129600", + "is_translation_enabled": false, + "is_translator": false, + "lang": "es", + "listed_count": 16, + "location": "", + "name": "pompous offer", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/3020129600/1423947428", + "profile_image_url": "http://pbs.twimg.com/profile_images/566702665376096256/1pBzDk6I_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/566702665376096256/1pBzDk6I_normal.jpeg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "gomezbartolomeg", + "statuses_count": 68215, + "time_zone": null, + "url": null, + "utc_offset": null, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:31 +0000 2015", + "entities": { + "hashtags": [ + { + "indices": [ + 14, + 21 + ], + "text": "Oracle" + }, + { + "indices": [ + 95, + 100 + ], + "text": "jobs" + } + ], + "symbols": [], + "urls": [{ + "display_url": "neuvoo.com/job.php?id=8n6\u2026", + "expanded_url": "http://neuvoo.com/job.php?id=8n62u4c8a7&source=twitter&lang=en&client_id=224&l=Albany%2C+New+York%2C+US&k=Oracle+%2F+PL-SQL+Microsoft+Visual+Studio+WCF+.Net+Programmer-Ajax+%2F+Java+Script", + "indices": [ + 101, + 123 + ], + "url": "http://t.co/ly0uLOAvvi" + }], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945722742730752, + "id_str": "648945722742730752", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "seed.mytweetsys.app.t00<\/a>", + "text": "Looking for a #Oracle #/ PL-SQL Microsoft Visual Studio WCF .Net Programmer-Ajax / Java Script #jobs http://t.co/ly0uLOAvvi", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Jan 21 20:54:25 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "Looking for a job in Albany? Check out our website http://t.co/tC4rdh1XtB", + "entities": { + "description": {"urls": [{ + "display_url": "neuvoo.com/jobs/?k=&l=Alb\u2026", + "expanded_url": "http://neuvoo.com/jobs/?k=&l=Albany%2C+New+York&r=15", + "indices": [ + 53, + 75 + ], + "url": "http://t.co/tC4rdh1XtB" + }]}, + "url": {"urls": [{ + "display_url": "neuvoo.com/jobs/?k=&l=Alb\u2026", + "expanded_url": "http://neuvoo.com/jobs/?k=&l=Albany%2C+New+York&r=15", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/tC4rdh1XtB" + }]} + }, + "favourites_count": 0, + "follow_request_sent": false, + "followers_count": 981, + "following": false, + "friends_count": 1118, + "geo_enabled": false, + "has_extended_profile": false, + "id": 2990565875, + "id_str": "2990565875", + "is_translation_enabled": false, + "is_translator": false, + "lang": "fr", + "listed_count": 230, + "location": "Albany, New York", + "name": "Jobs Albany", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/2990565875/1421873907", + "profile_image_url": "http://pbs.twimg.com/profile_images/558004909434359809/QA_BZDPj_normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/558004909434359809/QA_BZDPj_normal.png", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "NeuvooAlbanyNY", + "statuses_count": 20382, + "time_zone": null, + "url": "http://t.co/tC4rdh1XtB", + "utc_offset": null, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:22 +0000 2015", + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [{ + "id": 106234268, + "id_str": "106234268", + "indices": [ + 3, + 19 + ], + "name": "Matthew Green", + "screen_name": "matthew_d_green" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945685744762880, + "id_str": "648945685744762880", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "retweet_count": 39, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Wed Jul 15 03:50:32 +0000 2015", + "entities": { + "hashtags": [], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 36, + "favorited": false, + "geo": null, + "id": 621164918914592768, + "id_str": "621164918914592768", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "retweet_count": 39, + "retweeted": false, + "source": "Twitter for iPhone<\/a>", + "text": "As bad as F5 crypto is, it's not as bad as Java crypto -- which appears to have been created on a dare.", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Mon Jan 18 22:45:09 +0000 2010", + "default_profile": true, + "default_profile_image": false, + "description": "I teach cryptography at Johns Hopkins.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "blog.cryptographyengineering.com", + "expanded_url": "http://blog.cryptographyengineering.com", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/DGeUTzbDUe" + }]} + }, + "favourites_count": 6220, + "follow_request_sent": false, + "followers_count": 28439, + "following": false, + "friends_count": 418, + "geo_enabled": false, + "has_extended_profile": false, + "id": 106234268, + "id_str": "106234268", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 1256, + "location": "Baltimore, MD", + "name": "Matthew Green", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/106234268/1399054297", + "profile_image_url": "http://pbs.twimg.com/profile_images/2070934284/image_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/2070934284/image_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "matthew_d_green", + "statuses_count": 23739, + "time_zone": "Eastern Time (US & Canada)", + "url": "http://t.co/DGeUTzbDUe", + "utc_offset": -14400, + "verified": false + } + }, + "source": "Twidere for Android #3<\/a>", + "text": "RT @matthew_d_green: As bad as F5 crypto is, it's not as bad as Java crypto -- which appears to have been created on a dare.", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Tue Mar 10 00:02:52 +0000 2015", + "default_profile": false, + "default_profile_image": false, + "description": "", + "entities": {"description": {"urls": []}}, + "favourites_count": 2059, + "follow_request_sent": false, + "followers_count": 22, + "following": false, + "friends_count": 69, + "geo_enabled": false, + "has_extended_profile": false, + "id": 3082780575, + "id_str": "3082780575", + "is_translation_enabled": true, + "is_translator": false, + "lang": "en", + "listed_count": 22, + "location": "", + "name": "0xDEADBEEF", + "notifications": false, + "profile_background_color": "131516", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme14/bg.gif", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme14/bg.gif", + "profile_background_tile": true, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/3082780575/1429733813", + "profile_image_url": "http://pbs.twimg.com/profile_images/590978318935162880/Nr2UWCz8_normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/590978318935162880/Nr2UWCz8_normal.png", + "profile_link_color": "009999", + "profile_sidebar_border_color": "EEEEEE", + "profile_sidebar_fill_color": "EFEFEF", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "_dead_beef_", + "statuses_count": 2045, + "time_zone": "Belgrade", + "url": null, + "utc_offset": 7200, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:22 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945685430038528, + "id_str": "648945685430038528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 52, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 53, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter for Android<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Mon Jun 25 09:20:33 +0000 2012", + "default_profile": false, + "default_profile_image": false, + "description": "''We rest together, and then we run together. I think this is why we call our fans our friends. Because we breathe together.''", + "entities": {"description": {"urls": []}}, + "favourites_count": 5023, + "follow_request_sent": false, + "followers_count": 297, + "following": false, + "friends_count": 474, + "geo_enabled": true, + "has_extended_profile": false, + "id": 617942989, + "id_str": "617942989", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3, + "location": "primadonna, exo-l, lu fan", + "name": "brandnewsekai", + "notifications": false, + "profile_background_color": "1A1A1A", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/378800000122415797/7d200e893b948deb0705deae914cc301.png", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/378800000122415797/7d200e893b948deb0705deae914cc301.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/617942989/1433872609", + "profile_image_url": "http://pbs.twimg.com/profile_images/561606455363510272/tmgT-tHv_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/561606455363510272/tmgT-tHv_normal.jpeg", + "profile_link_color": "D8C7E0", + "profile_sidebar_border_color": "000000", + "profile_sidebar_fill_color": "CEDBD7", + "profile_text_color": "8C9C43", + "profile_use_background_image": true, + "protected": false, + "screen_name": "bleckchocole", + "statuses_count": 20595, + "time_zone": "Beijing", + "url": null, + "utc_offset": 28800, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:19 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945669814775808, + "id_str": "648945669814775808", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 51, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 50, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 51, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter for Android<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Mar 20 04:40:10 +0000 2013", + "default_profile": true, + "default_profile_image": false, + "description": "", + "entities": {"description": {"urls": []}}, + "favourites_count": 63286, + "follow_request_sent": false, + "followers_count": 902, + "following": false, + "friends_count": 2001, + "geo_enabled": true, + "has_extended_profile": false, + "id": 1282292556, + "id_str": "1282292556", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 14, + "location": "", + "name": "Larina", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1282292556/1381348772", + "profile_image_url": "http://pbs.twimg.com/profile_images/378800000572653729/eba3a5391fee331a49de4cb7ce81a78d_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/378800000572653729/eba3a5391fee331a49de4cb7ce81a78d_normal.jpeg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "roni334458", + "statuses_count": 28054, + "time_zone": null, + "url": null, + "utc_offset": null, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:18 +0000 2015", + "entities": { + "hashtags": [ + { + "indices": [ + 90, + 98 + ], + "text": "Ireland" + }, + { + "indices": [ + 99, + 106 + ], + "text": "techie" + } + ], + "media": [{ + "display_url": "pic.twitter.com/WZfLORunxv", + "expanded_url": "http://twitter.com/ZazenAcademy/status/648945666773917696/photo/1", + "id": 648945665322708993, + "id_str": "648945665322708993", + "indices": [ + 107, + 129 + ], + "media_url": "http://pbs.twimg.com/media/CQGEpPQW8AEZ4X4.jpg", + "media_url_https": "https://pbs.twimg.com/media/CQGEpPQW8AEZ4X4.jpg", + "sizes": { + "large": { + "h": 1495, + "resize": "fit", + "w": 1024 + }, + "medium": { + "h": 876, + "resize": "fit", + "w": 600 + }, + "small": { + "h": 496, + "resize": "fit", + "w": 340 + }, + "thumb": { + "h": 150, + "resize": "crop", + "w": 150 + } + }, + "type": "photo", + "url": "http://t.co/WZfLORunxv" + }], + "symbols": [], + "urls": [], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945666773917696, + "id_str": "648945666773917696", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "We are enrolling for our preparation courses for the Oracle Java Certification nationwide #Ireland #techie http://t.co/WZfLORunxv", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Mon Jun 22 18:30:02 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "Zazen Academy of Technology delivers short courses in cutting edge technologies that are in demand by the software industry.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "zazenacademy.ie", + "expanded_url": "http://www.zazenacademy.ie", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/70SrnZMB2P" + }]} + }, + "favourites_count": 27, + "follow_request_sent": false, + "followers_count": 78, + "following": false, + "friends_count": 278, + "geo_enabled": false, + "has_extended_profile": false, + "id": 3341877640, + "id_str": "3341877640", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 12, + "location": "Dublin City, Ireland", + "name": "Zazen Academy", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/3341877640/1441049772", + "profile_image_url": "http://pbs.twimg.com/profile_images/638434928087199744/uzENtEyS_normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/638434928087199744/uzENtEyS_normal.png", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "ZazenAcademy", + "statuses_count": 275, + "time_zone": "Pacific Time (US & Canada)", + "url": "http://t.co/70SrnZMB2P", + "utc_offset": -25200, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:16 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945657365983232, + "id_str": "648945657365983232", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 51, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 50, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 51, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter for Android<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Apr 01 08:09:13 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "Blunt, Intelligent, Slightly arrogant jackass with no Instagram account.\n\n\nAtheism.\nNihilism.\nScience!", + "entities": {"description": {"urls": []}}, + "favourites_count": 8199, + "follow_request_sent": false, + "followers_count": 290, + "following": false, + "friends_count": 299, + "geo_enabled": false, + "has_extended_profile": true, + "id": 3123500449, + "id_str": "3123500449", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 23, + "location": "Virgo Supercluster", + "name": "Keegan", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/3123500449/1440644095", + "profile_image_url": "http://pbs.twimg.com/profile_images/608436686310264832/8XTDOq3k_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/608436686310264832/8XTDOq3k_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "JohnDoe_997", + "statuses_count": 10816, + "time_zone": null, + "url": null, + "utc_offset": null, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:09 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945627947212800, + "id_str": "648945627947212800", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 48, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter Web Client<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Jun 01 17:24:02 +0000 2011", + "default_profile": true, + "default_profile_image": false, + "description": "All round sound chap, even if I do say so myself...", + "entities": {"description": {"urls": []}}, + "favourites_count": 187, + "follow_request_sent": false, + "followers_count": 47, + "following": false, + "friends_count": 75, + "geo_enabled": false, + "has_extended_profile": false, + "id": 309178913, + "id_str": "309178913", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 0, + "location": "Edinburgh", + "name": "HighLordAdmiral", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_image_url": "http://pbs.twimg.com/profile_images/1377754097/187661_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/1377754097/187661_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "HighLordAdmiral", + "statuses_count": 4390, + "time_zone": "Amsterdam", + "url": null, + "utc_offset": 7200, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:09 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945627649302528, + "id_str": "648945627649302528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 48, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter for iPhone<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Fri Jul 17 05:14:15 +0000 2015", + "default_profile": true, + "default_profile_image": false, + "description": "I like to think I'm pretty cool.", + "entities": {"description": {"urls": []}}, + "favourites_count": 14, + "follow_request_sent": false, + "followers_count": 17, + "following": false, + "friends_count": 80, + "geo_enabled": false, + "has_extended_profile": false, + "id": 3282141337, + "id_str": "3282141337", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 0, + "location": "", + "name": "Nathan Richards", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_image_url": "http://pbs.twimg.com/profile_images/621913217602707456/iwgRIj05_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/621913217602707456/iwgRIj05_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "C0DEED", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "live_laugh_me", + "statuses_count": 14, + "time_zone": null, + "url": null, + "utc_offset": null, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:07 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 56, + 74 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 76, + 98 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 99, + 121 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [{ + "id": 1451773004, + "id_str": "1451773004", + "indices": [ + 3, + 17 + ], + "name": "Intl. Space Station", + "screen_name": "Space_Station" + }] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945622716940288, + "id_str": "648945622716940288", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "retweeted_status": { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:38:30 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 37, + 55 + ], + "text": "NationalCoffeeDay" + }], + "symbols": [], + "urls": [ + { + "display_url": "nasa.gov/feature/nation\u2026", + "expanded_url": "http://www.nasa.gov/feature/national-coffee-day-java-in-zero-g/", + "indices": [ + 57, + 79 + ], + "url": "http://t.co/fx4lQcu0Xp" + }, + { + "display_url": "pic.twitter.com/NbOZoQDags", + "expanded_url": "http://twitter.com/Space_Station/status/648944962713878528/photo/1", + "indices": [ + 80, + 102 + ], + "url": "http://t.co/NbOZoQDags" + } + ], + "user_mentions": [] + }, + "favorite_count": 48, + "favorited": false, + "geo": null, + "id": 648944962713878528, + "id_str": "648944962713878528", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "en", + "metadata": { + "iso_language_code": "en", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 49, + "retweeted": false, + "source": "Twitter Web Client<\/a>", + "text": "How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu May 23 15:25:28 +0000 2013", + "default_profile": false, + "default_profile_image": false, + "description": "NASA's page for updates from the International Space Station, the world-class lab orbiting Earth 250 miles above. For the latest research, follow @ISS_Research.", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "nasa.gov/station", + "expanded_url": "http://www.nasa.gov/station", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/9Gk2GZYDsP" + }]} + }, + "favourites_count": 1161, + "follow_request_sent": false, + "followers_count": 281874, + "following": false, + "friends_count": 229, + "geo_enabled": false, + "has_extended_profile": false, + "id": 1451773004, + "id_str": "1451773004", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 3474, + "location": "Low Earth Orbit", + "name": "Intl. Space Station", + "notifications": false, + "profile_background_color": "C0DEED", + "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/517439388741931008/iRbQw1ch.jpeg", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/1451773004/1434028060", + "profile_image_url": "http://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/647082562125459456/pmT48eHQ_normal.jpg", + "profile_link_color": "0084B4", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "Space_Station", + "statuses_count": 3110, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/9Gk2GZYDsP", + "utc_offset": -18000, + "verified": true + } + }, + "source": "Twitter for Android<\/a>", + "text": "RT @Space_Station: How do astronauts take their coffee?\n#NationalCoffeeDay \nhttp://t.co/fx4lQcu0Xp http://t.co/NbOZoQDags", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Tue May 24 22:12:02 +0000 2011", + "default_profile": false, + "default_profile_image": false, + "description": "UWGB student, software designer, former Better Explorer developer, anime lover, and all-around weirdo. Enjoy! :) And my girlfriend does too exist!", + "entities": {"description": {"urls": []}}, + "favourites_count": 4590, + "follow_request_sent": false, + "followers_count": 193, + "following": false, + "friends_count": 483, + "geo_enabled": true, + "has_extended_profile": true, + "id": 304662576, + "id_str": "304662576", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 5, + "location": "Wisconsin, United States", + "name": "Jayke R. Huempfner", + "notifications": false, + "profile_background_color": "FFF04D", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme19/bg.gif", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme19/bg.gif", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/304662576/1395796068", + "profile_image_url": "http://pbs.twimg.com/profile_images/2473386566/z6qkl5os1d6awzm4we9e_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/2473386566/z6qkl5os1d6awzm4we9e_normal.jpeg", + "profile_link_color": "0099CC", + "profile_sidebar_border_color": "000000", + "profile_sidebar_fill_color": "000000", + "profile_text_color": "000000", + "profile_use_background_image": true, + "protected": false, + "screen_name": "JaykeBird", + "statuses_count": 8244, + "time_zone": "Central Time (US & Canada)", + "url": null, + "utc_offset": -18000, + "verified": false + } + }, + { + "contributors": null, + "coordinates": null, + "created_at": "Tue Sep 29 19:41:06 +0000 2015", + "entities": { + "hashtags": [{ + "indices": [ + 23, + 32 + ], + "text": "vacature" + }], + "symbols": [], + "urls": [{ + "display_url": "tc.tradetracker.net/?c=16131&m=585\u2026", + "expanded_url": "http://tc.tradetracker.net/?c=16131&m=585621&a=215767&u=http%3A%2F%2Fwww.stepstone.nl%2Foffers%2Foffer_detail.cfm%3Fid%3D396335%26click%3Dyes%26cid%3DAffiliate_Tradetracker_Affiliate_TradeTracker__", + "indices": [ + 59, + 81 + ], + "url": "http://t.co/eeprR11E0s" + }], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": null, + "id": 648945616471592961, + "id_str": "648945616471592961", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "nl", + "metadata": { + "iso_language_code": "nl", + "result_type": "recent" + }, + "place": null, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "Twandoos<\/a>", + "text": "Tijd voor iets nieuws? #vacature Java Software Programmeur http://t.co/eeprR11E0s", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Wed Oct 19 11:04:23 +0000 2011", + "default_profile": false, + "default_profile_image": false, + "description": "Tweet dagelijks recente #vacatures. Laat geen carrière kansen aan je voorbij gaan en volg ons!", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "werkvacature.nl", + "expanded_url": "http://www.werkvacature.nl", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/ULv7bjHdq2" + }]} + }, + "favourites_count": 2, + "follow_request_sent": false, + "followers_count": 29944, + "following": false, + "friends_count": 17399, + "geo_enabled": true, + "has_extended_profile": false, + "id": 393963023, + "id_str": "393963023", + "is_translation_enabled": false, + "is_translator": false, + "lang": "nl", + "listed_count": 164, + "location": "Nederland", + "name": "#vacature", + "notifications": false, + "profile_background_color": "EBEBEB", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme7/bg.gif", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme7/bg.gif", + "profile_background_tile": false, + "profile_image_url": "http://pbs.twimg.com/profile_images/577464263968587776/Hxvo2BtT_normal.jpeg", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/577464263968587776/Hxvo2BtT_normal.jpeg", + "profile_link_color": "89C9FA", + "profile_sidebar_border_color": "DFDFDF", + "profile_sidebar_fill_color": "F3F3F3", + "profile_text_color": "333333", + "profile_use_background_image": true, + "protected": false, + "screen_name": "VacatureWerk", + "statuses_count": 210575, + "time_zone": "Amsterdam", + "url": "http://t.co/ULv7bjHdq2", + "utc_offset": 7200, + "verified": false + } + }, + { + "contributors": null, + "coordinates": { + "coordinates": [ + 114.109497, + 22.396428 + ], + "type": "Point" + }, + "created_at": "Tue Sep 29 19:41:04 +0000 2015", + "entities": { + "hashtags": [ + { + "indices": [ + 11, + 14 + ], + "text": "IT" + }, + { + "indices": [ + 15, + 19 + ], + "text": "Job" + }, + { + "indices": [ + 48, + 57 + ], + "text": "HongKong" + }, + { + "indices": [ + 82, + 94 + ], + "text": "CareersAtTU" + }, + { + "indices": [ + 95, + 104 + ], + "text": "LifeAtTU" + }, + { + "indices": [ + 105, + 110 + ], + "text": "Jobs" + }, + { + "indices": [ + 111, + 118 + ], + "text": "Hiring" + } + ], + "symbols": [], + "urls": [{ + "display_url": "bit.ly/1iZyf8V", + "expanded_url": "http://bit.ly/1iZyf8V", + "indices": [ + 59, + 81 + ], + "url": "http://t.co/I5mcDEBCDB" + }], + "user_mentions": [] + }, + "favorite_count": 0, + "favorited": false, + "geo": { + "coordinates": [ + 22.396428, + 114.109497 + ], + "type": "Point" + }, + "id": 648945609307615232, + "id_str": "648945609307615232", + "in_reply_to_screen_name": null, + "in_reply_to_status_id": null, + "in_reply_to_status_id_str": null, + "in_reply_to_user_id": null, + "in_reply_to_user_id_str": null, + "is_quote_status": false, + "lang": "pl", + "metadata": { + "iso_language_code": "pl", + "result_type": "recent" + }, + "place": { + "attributes": {}, + "bounding_box": { + "coordinates": [[ + [ + 113.9946498, + 22.3126447 + ], + [ + 114.169205, + 22.3126447 + ], + [ + 114.169205, + 22.417555 + ], + [ + 113.9946498, + 22.417555 + ] + ]], + "type": "Polygon" + }, + "contained_within": [], + "country": "香港", + "country_code": "HK", + "full_name": "Tsuen Wan District, Hong Kong", + "id": "0118e53a43a7db78", + "name": "Tsuen Wan District", + "place_type": "admin", + "url": "https://api.twitter.com/1.1/geo/id/0118e53a43a7db78.json" + }, + "possibly_sensitive": false, + "retweet_count": 0, + "retweeted": false, + "source": "SafeTweet by TweetMyJOBS<\/a>", + "text": "TransUnion #IT #Job: System Analyst (Java/JEE) (#HongKong) http://t.co/I5mcDEBCDB #CareersAtTU #LifeAtTU #Jobs #Hiring", + "truncated": false, + "user": { + "contributors_enabled": false, + "created_at": "Thu Jan 16 20:45:42 +0000 2014", + "default_profile": false, + "default_profile_image": false, + "description": "Interested in a #Career w/ @TransUnion? #CareersAtTU offer an enjoyable workspace and seek to ensure a healthy work/life balance. #LifeAtTU", + "entities": { + "description": {"urls": []}, + "url": {"urls": [{ + "display_url": "transunion.com", + "expanded_url": "http://www.transunion.com", + "indices": [ + 0, + 22 + ], + "url": "http://t.co/ULy6IsA7GB" + }]} + }, + "favourites_count": 5, + "follow_request_sent": false, + "followers_count": 260, + "following": false, + "friends_count": 66, + "geo_enabled": true, + "has_extended_profile": false, + "id": 2294998164, + "id_str": "2294998164", + "is_translation_enabled": false, + "is_translator": false, + "lang": "en", + "listed_count": 110, + "location": "", + "name": "TransUnion Jobs", + "notifications": false, + "profile_background_color": "0295BE", + "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", + "profile_background_tile": false, + "profile_banner_url": "https://pbs.twimg.com/profile_banners/2294998164/1403035122", + "profile_image_url": "http://pbs.twimg.com/profile_images/551912214345437184/syc1Yb0R_normal.png", + "profile_image_url_https": "https://pbs.twimg.com/profile_images/551912214345437184/syc1Yb0R_normal.png", + "profile_link_color": "0295BE", + "profile_sidebar_border_color": "FFFFFF", + "profile_sidebar_fill_color": "DDEEF6", + "profile_text_color": "333333", + "profile_use_background_image": false, + "protected": false, + "screen_name": "TransUnion_Jobs", + "statuses_count": 217, + "time_zone": "Central Time (US & Canada)", + "url": "http://t.co/ULy6IsA7GB", + "utc_offset": -18000, + "verified": false + } + } + ] +} diff --git a/projects/glutolik/src/test/resources/tweets.json~ b/projects/glutolik/src/test/resources/tweets.json~ new file mode 100644 index 0000000..e69de29 diff --git a/projects/twitter4j.properties b/projects/twitter4j.properties new file mode 100644 index 0000000..18e557a --- /dev/null +++ b/projects/twitter4j.properties @@ -0,0 +1,5 @@ +debug=false +oauth.consumerKey=lNljkNgQgZk7v2mxoNVmZj8jY +oauth.consumerSecret=k8FALPe6Y1y7ZfRRylco8xvVoUm1PAPBjGlI5Mol8j2dzelbKA +oauth.accessToken=2784501325-LgojLaoUEXWt3AiBW1y3wxnswcbO8INEHPy2Z9K +oauth.accessTokenSecret=oqCEThm48zkb9fqFb6iTYmh00YJw9qVGP2LAPuEpN5kzO diff --git a/projects/twitter4j.properties~ b/projects/twitter4j.properties~ new file mode 100644 index 0000000..e69de29 From 6b84f1e697ed2bffe6063b0e762da2686159c90f Mon Sep 17 00:00:00 2001 From: glutolik Date: Fri, 18 Dec 2015 22:51:28 +0300 Subject: [PATCH 09/10] Added BlockingQueue tests --- .../BlockingQueueTests.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/BlockingQueueTests/BlockingQueueTests.java diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/BlockingQueueTests/BlockingQueueTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/BlockingQueueTests/BlockingQueueTests.java new file mode 100644 index 0000000..1dc9b45 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/BlockingQueueTests/BlockingQueueTests.java @@ -0,0 +1,58 @@ +package ru.mipt.diht.students.glutolik.BlockingQueueTests; + +import org.junit.Before; +import org.junit.Test; +import ru.mipt.diht.students.glutolik.Threads.BlockingQueue; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertEquals; + +/** + * Created by glutolik on 18.12.15. + */ +public class BlockingQueueTests { + BlockingQueue queue; + List first; + List second; + + @Before + public void setUp(){ + queue = new BlockingQueue<>(20); + + first = new ArrayList<>(); + first.add("Hello"); + first.add("NiceToMeetYou"); + first.add("How u doing?"); + first.add("Goodbye"); + + second = new ArrayList<>(); + second.add("Please come again"); + second.add("Are you kidding me?"); + } + + @Test(expected = IllegalArgumentException.class) + public void overflow() throws InterruptedException{ + first.addAll(first); + first.addAll(first); + first.addAll(second); + first.addAll(first); + first.addAll(second); + first.addAll(second); // first now contains 22 elements + + queue.offer(first); + } + + @Test(expected = IllegalArgumentException.class) + public void takeMoreThanExists() throws InterruptedException{ + queue.take(21); + } + + @Test + public void offeringTakingTest() throws InterruptedException { + queue.offer(second); + List taken = queue.take(2); + assertEquals(taken, second); + } +} From 3aa0eb0b47b3149bb19266a2c6d8420ec7f5c438 Mon Sep 17 00:00:00 2001 From: glutolik Date: Sat, 19 Dec 2015 01:07:24 +0300 Subject: [PATCH 10/10] miniORM done --- .../glutolik/MiniORM/DatabaseService.java | 151 +++++++++++++++++ .../MiniORM/DatabaseServiceUtils.java | 150 +++++++++++++++++ .../MiniORM/StatementConstructor.java | 105 ++++++++++++ .../students/glutolik/MiniORM/TColumn.java | 56 ++++++ .../DatabaseServiceTests.java | 159 ++++++++++++++++++ .../DatabaseServiceUtilsTests.java | 61 +++++++ .../StatementConstructorTests.java | 42 +++++ 7 files changed, 724 insertions(+) create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseService.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseServiceUtils.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/StatementConstructor.java create mode 100644 projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/TColumn.java create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceTests.java create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceUtilsTests.java create mode 100644 projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/StatementConstructorTests.java diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseService.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseService.java new file mode 100644 index 0000000..4fe6b63 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseService.java @@ -0,0 +1,151 @@ +package ru.mipt.diht.students.glutolik.MiniORM; + +import javafx.util.Pair; + + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by glutolik on 18.12.15. + */ +public class DatabaseService { + static final String UNNAMED = ""; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Table { + String name() default UNNAMED; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Column { + String name() default UNNAMED; + + String type(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface PrimaryKey { + } + + private static final String DATABASE_PATH = "jdbc:h2:./simple_database"; + private StatementConstructor statementBuilder = null; + private String tableName = null; + private List columnList = null; + private TColumn primaryKey = null; + private Class itemsClass; + + public DatabaseService(Class newItemsClass) throws IllegalArgumentException { + itemsClass = newItemsClass; + tableName = DatabaseServiceUtils.getTableName(itemsClass); + Pair, TColumn> pair = DatabaseServiceUtils.analyseColumns(itemsClass); + columnList = pair.getKey(); + primaryKey = pair.getValue(); + statementBuilder = new StatementConstructor(tableName, columnList, + primaryKey, itemsClass); + } + + public final StatementConstructor getStatementBuilder() { + return statementBuilder; + } + + @FunctionalInterface + public interface CheckedFunction { + R apply(T t) throws SQLException, IllegalStateException; + } + + // Осуществляет указанное действие с базой данных. + private R databaseRequest(CheckedFunction action) { + try (Connection connection = DriverManager.getConnection(DATABASE_PATH)) { + Statement statement = connection.createStatement(); + return action.apply(statement); + } catch (SQLException e) { + throw new IllegalStateException("An SQL error occurred: " + e.getMessage()); + } + } + + public final void createTable() { + databaseRequest((Statement statement) -> { + statement.execute(statementBuilder.buildCreate()); + if (primaryKey != null) { + statement.execute("ALTER TABLE " + tableName + " ADD PRIMARY KEY (" + primaryKey.getName() + ")"); + System.err.println("PK успешно добавлен."); + } + return true; + }); + } + + public final void dropTable() { + databaseRequest((Statement statement) -> { + statement.execute("DROP TABLE IF EXISTS " + tableName); + return true; + }); + } + + public final void insert(T newItem) { + int added = databaseRequest( + (Statement statement) -> statement.executeUpdate(statementBuilder.buildInsert(newItem)) + ); + + if (added != 0) { + System.err.println("Элемент успешно добавлен."); + } + } + + public final List queryForAll() { + List allItems = databaseRequest((Statement statement) -> { + ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName); + // Обрабатываем полученные результаты. + List selectAllList = new ArrayList<>(); + while (resultSet.next()) { + T item = DatabaseServiceUtils.createItemFromSqlResult(resultSet, columnList, itemsClass); + selectAllList.add(item); + } + return selectAllList; + }); + System.err.println("Get all items: " + allItems); + return allItems; + } + + public final T queryById(K key) { + T itemById = databaseRequest((Statement statement) -> { + ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName + + " WHERE " + primaryKey.getName() + "=" + DatabaseServiceUtils.getSqlValue(key)); + // Обрабатываем полученный результат. + T item = null; + if (resultSet.next()) { + item = DatabaseServiceUtils.createItemFromSqlResult(resultSet, columnList, itemsClass); + } + if (resultSet.next()) { + throw new IllegalStateException("Primary key search result is not single"); + } + return item; + }); + System.err.println("Get item by ID: " + itemById); + return itemById; + } + + public final void update(T item) { + databaseRequest((Statement statement) -> { + statement.execute(statementBuilder.buildUpdate(item)); + return true; + }); + } + + public final void delete(K key) { + databaseRequest((Statement statement) -> { + statement.execute("DELETE FROM " + tableName + " WHERE " + + primaryKey.getName() + "=" + DatabaseServiceUtils.getSqlValue(key)); + return true; + }); + } + } + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseServiceUtils.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseServiceUtils.java new file mode 100644 index 0000000..40d8d78 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/DatabaseServiceUtils.java @@ -0,0 +1,150 @@ +package ru.mipt.diht.students.glutolik.MiniORM; + +import javafx.util.Pair; + +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.Column; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.PrimaryKey; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.Table; + +import java.lang.reflect.Field; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; + + +import static java.lang.Character.isUpperCase; +import static java.lang.Character.toLowerCase; + +/** + * Created by glutolik on 18.12.15. + */ +public class DatabaseServiceUtils { + public static String camelCaseToLowerCase(String text) { + StringBuilder lowerCaseText = new StringBuilder(""); + + for (int i = 0; i < text.length(); ++i) { + char ch = text.charAt(i); + if (isUpperCase(ch)) { + if (i != 0) { + lowerCaseText.append("_"); + } + lowerCaseText.append(toLowerCase(ch)); + } else { + lowerCaseText.append(ch); + } + } + return lowerCaseText.toString(); + } + + public static String getTableName(Class itemClass) throws IllegalArgumentException { + // Проверяем, проаннотирован ли класс @Table + Table tableAnnotation; + if (itemClass.isAnnotationPresent(Table.class)) { + tableAnnotation = (Table) itemClass.getAnnotation(Table.class); + } else { + throw new IllegalArgumentException("Class has no @Table annotation"); + } + + // Если имя таблицы не указано, то сгерерируем его. + String tableName = tableAnnotation.name(); + if (Objects.equals(tableName, DatabaseService.UNNAMED)) { + tableName = camelCaseToLowerCase(itemClass.getSimpleName()); + } + return tableName; + } + + public static Pair, TColumn> analyseColumns(Class itemClass) { + List columnList = new ArrayList<>(); + TColumn primaryKey = null; + + // Пройдемся по полям класса и найдем аннотированные @Column + Field[] fields = itemClass.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(Column.class)) { + + Column column = field.getAnnotation(Column.class); + String name = column.name(); + String type = column.type(); + + // Если имя не задано, то сгернерируем. + if (name.equals(DatabaseService.UNNAMED)) { + name = camelCaseToLowerCase(field.getName()); + } + TColumn itemColumn = new TColumn(name, type, field); + columnList.add(itemColumn); + + if (field.isAnnotationPresent(PrimaryKey.class)) { + // Объявление более одного @PrimaryKey недопустимо. + if (primaryKey != null) { + throw new IllegalArgumentException("More than one primary key presents"); + } + primaryKey = itemColumn; + } + } + } + return new Pair, TColumn>(columnList, primaryKey); + } + + public static String getSqlValue(T object) { + if (object.getClass() == String.class || object.getClass() == char.class) { + return "\'" + object.toString() + "\'"; + } else { + return object.toString(); + } + } + + public static T createItemFromSqlResult(ResultSet resultSet, + List columnList, + Class itemClass) throws SQLException { + T newItem = null; + try { + // Создаем новый объект пустым конструктором. + newItem = (T) itemClass.newInstance(); + + // Перебираем все нужные столбцы-поля. + for (TColumn column : columnList) { + Field field = column.getField(); + + // Определяем какой тип получать в соостветствии с типом поля. + // String + if (field.getType() == String.class || field.getType() == char.class) { + String value = resultSet.getString(column.getName()); + field.set(newItem, value); + } + // int + if (field.getType() == int.class || field.getType() == Integer.class) { + int value = resultSet.getInt(column.getName()); + field.set(newItem, value); + } + // float + if (field.getType() == float.class || field.getType() == Double.class) { + float value = resultSet.getFloat(column.getName()); + field.set(newItem, value); + } + // boolean + if (field.getType() == boolean.class) { + boolean value = resultSet.getBoolean(column.getName()); + field.set(newItem, value); + } + // Date + if (field.getType() == Date.class) { + Date value = resultSet.getDate(column.getName()); + field.set(newItem, value); + } + // Time + if (field.getType() == Time.class) { + Time value = resultSet.getTime(column.getName()); + field.set(newItem, value); + } + } + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + return newItem; + } +} + diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/StatementConstructor.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/StatementConstructor.java new file mode 100644 index 0000000..e9caf11 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/StatementConstructor.java @@ -0,0 +1,105 @@ +package ru.mipt.diht.students.glutolik.MiniORM; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.joining; + +/** + * Created by glutolik on 18.12.15. + */ +public class StatementConstructor { + private String tableName = null; + private List columnList = null; + private TColumn primaryKey = null; + private Class itemsClass; + + public StatementConstructor(String newTableName, + List newColumnList, + TColumn newPrimaryKey, + Class newItemsClass) { + this.tableName = newTableName; + this.columnList = newColumnList; + this.primaryKey = newPrimaryKey; + this.itemsClass = newItemsClass; + } + + public final String buildCreate() { + StringBuilder createQuery = new StringBuilder(); + createQuery.append("CREATE TABLE IF NOT EXISTS ") + .append(tableName) + .append(" ("); + + List columns = new ArrayList<>(); + for (TColumn column : columnList) { + StringBuilder columnsBuilder = new StringBuilder(); + columnsBuilder.append(column.getName()) + .append(" ") + .append(column.getType()); + if (column == primaryKey) { + columnsBuilder.append(" NOT NULL"); + } + columns.add(columnsBuilder.toString()); + } + + createQuery.append(columns.stream().collect(joining(", "))).append(")"); + return createQuery.toString(); + } + + public final String buildInsert(T newItem) { + StringBuilder insertQuery = new StringBuilder(); + insertQuery.append("INSERT INTO ") + .append(tableName) + .append(" VALUES ("); + + List columns = new ArrayList<>(); + for (TColumn column : columnList) { + Field field = column.getField(); + field.setAccessible(true); + + try { + columns.add(DatabaseServiceUtils.getSqlValue(field.get(newItem))); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + insertQuery.append(columns.stream().collect(joining(", "))).append(")"); + return insertQuery.toString(); + } + + public final String buildUpdate(T item) { + StringBuilder updateStatement = new StringBuilder(); + updateStatement.append("UPDATE ") + .append(tableName) + .append(" SET "); + + List columns = new ArrayList<>(); + for (TColumn column : columnList) { + StringBuilder columnBuilder = new StringBuilder(); + columnBuilder.append(column.getName()) + .append("="); + + Field field = column.getField(); + field.setAccessible(true); + try { + columnBuilder.append(DatabaseServiceUtils.getSqlValue(field.get(item))); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + columns.add(columnBuilder.toString()); + } + + updateStatement.append(columns.stream().collect(joining(", "))); + try { + updateStatement.append(" WHERE ") + .append(primaryKey.getName()) + .append("=") + .append(DatabaseServiceUtils.getSqlValue(primaryKey.getField().get(item))); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return updateStatement.toString(); + } +} diff --git a/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/TColumn.java b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/TColumn.java new file mode 100644 index 0000000..5d36304 --- /dev/null +++ b/projects/glutolik/src/main/java/ru/mipt/diht/students/glutolik/MiniORM/TColumn.java @@ -0,0 +1,56 @@ +package ru.mipt.diht.students.glutolik.MiniORM; + +import java.lang.reflect.Field; + +/** + * Created by glutolik on 18.12.15. + */ +public class TColumn { + public TColumn(String newName, String newType, Field newField) { + this.name = newName; + this.type = newType; + this.field = newField; + } + + // Из-за CheckStyle =( + @Override + public final int hashCode() { + return 0; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + TColumn other = (TColumn) obj; + return this.name.equals(other.name) + && this.type.equals(other.type) + /*&& this.field == other.field*/; + } + + public final String getName() { + return name; + } + + public final String getType() { + return type; + } + + public final Field getField() { + return field; + } + + private String name; + private String type; + private Field field; +} diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceTests.java new file mode 100644 index 0000000..1879cbf --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceTests.java @@ -0,0 +1,159 @@ +package ru.mipt.diht.students.glutolik.DatabaseServiceTests; + +import org.junit.Before; +import org.junit.Test; + +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.Column; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.PrimaryKey; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService.Table; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Created by glutolik on 19.12.15. + */ +public class DatabaseServiceTests { + private List students = new ArrayList<>(); + + @Before + public final void initStudents() { + students.add(new Student("Peter", 497, false)); + students.add(new Student("Mike", 499, true)); + students.add(new Student("Xander", 499, true)); + students.add(new Student("Lexa", 497, true)); + students.add(new Student("Nick", 495, true)); + students.add(new Student("Mattew", 497, true)); + } + + @Test + public final void sequenceTest() { + DatabaseService studentsDB = new DatabaseService<>(Student.class); + studentsDB.dropTable(); + studentsDB.createTable(); + for (Student student : students) { + studentsDB.insert(student); + } + assertEquals(students, studentsDB.queryForAll()); + + Student studentMattew = studentsDB.queryById("Mattew"); + assertEquals(new Student("Mattew", 497, true), studentsDB.queryById("Mattew")); + + studentMattew.setGroupId(9991); + studentMattew.setHasSalary(false); + studentsDB.update(studentMattew); + assertEquals(new Student("Mattew", 9991, false), studentsDB.queryById("Mattew")); + + studentsDB.delete("Mike"); + assertEquals(null, studentsDB.queryById("Mike")); + studentsDB.queryForAll(); + } + +} + +@Table(name = "SiMpLe") +class Simple { + @Column(type = "INTEGER") + private int number; +} + +@Table +class DoublePrimaryKey { + @Column(type = "INTEGER") + @PrimaryKey + private int firstKey; + + @Column(type = "INTEGER") + @PrimaryKey + private int secondKey; + +} + +@Table +class Student { + + @Column(name = "FIO", type = "VARCHAR(255)") + @PrimaryKey + private String name; + + @Column(type = "INTEGER") + private int groupId; + + @Column(type = "BOOLEAN") + private boolean hasSalary; + + Student() { + name = null; + groupId = 0; + hasSalary = false; + } + + Student(String newName, int newGroupId, boolean newHasSalary) { + this.name = newName; + this.groupId = newGroupId; + this.hasSalary = newHasSalary; + } + + public String getName() { + return name; + } + + public void setName(String newName) { + this.name = newName; + } + + public int getGroupId() { + return groupId; + } + + public void setGroupId(int newGroupId) { + this.groupId = newGroupId; + } + + public boolean isHasSalary() { + return hasSalary; + } + + public void setHasSalary(boolean newHasSalary) { + this.hasSalary = newHasSalary; + } + + @Override + public String toString() { + return "Student{" + + "name='" + name + '\'' + + ", groupId=" + groupId + + ", hasSalary=" + hasSalary + + '}'; + } + + // Из-за CheckStyle =( + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + Student other = (Student) obj; + return this.name.equals(other.name) + && this.groupId == other.groupId + && this.hasSalary == other.hasSalary; + } +} + diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceUtilsTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceUtilsTests.java new file mode 100644 index 0000000..80e1704 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/DatabaseServiceUtilsTests.java @@ -0,0 +1,61 @@ +package ru.mipt.diht.students.glutolik.DatabaseServiceTests; + +import org.junit.Test; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseServiceUtils; +import ru.mipt.diht.students.glutolik.MiniORM.TColumn; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static ru.mipt.diht.students.glutolik.MiniORM.DatabaseServiceUtils.camelCaseToLowerCase; + +/** + * Created by glutolik on 19.12.15. + */ +public class DatabaseServiceUtilsTests { + @Test + public final void camelCaseToLowerCaseTest() { + assertEquals("to_lower_case", camelCaseToLowerCase("toLowerCase")); + assertEquals("to_lower12e", camelCaseToLowerCase("toLower12e")); + assertEquals("a_b_c", camelCaseToLowerCase("ABC")); + assertEquals("simple", camelCaseToLowerCase("simple")); + assertEquals("simple", camelCaseToLowerCase("Simple")); + } + + @Test + public final void getColumnListTest() { + List expectedList = new ArrayList<>(); + Field[] fields = Student.class.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + } + expectedList.add(new TColumn("FIO", "VARCHAR(255)", fields[0])); + expectedList.add(new TColumn("group_id", "INTEGER", fields[1])); + expectedList.add(new TColumn("has_salary", "BOOLEAN", fields[2])); + + List actualList = DatabaseServiceUtils.analyseColumns(Student.class).getKey(); + // Т.к. порядок не ганантирован, то проверяем на равенство без его учета. + assertTrue(expectedList.containsAll(actualList) + && actualList.containsAll(expectedList)); + } + + @Test + public final void getTableNameTest() { + assertEquals("SiMpLe", DatabaseServiceUtils.getTableName(Simple.class)); + assertEquals("student", DatabaseServiceUtils.getTableName(Student.class)); + assertEquals("double_primary_key", DatabaseServiceUtils.getTableName(DoublePrimaryKey.class)); + } + + @Test(expected = IllegalArgumentException.class) + public final void getTableNameNoAnnotationTest() { + assertEquals("utils_test", DatabaseServiceUtils.getTableName(DatabaseServiceUtilsTests.class)); + } + + @Test(expected = IllegalArgumentException.class) + public final void doublePrimaryKeyExceptionTest() { + DatabaseServiceUtils.analyseColumns(DoublePrimaryKey.class); + } +} diff --git a/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/StatementConstructorTests.java b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/StatementConstructorTests.java new file mode 100644 index 0000000..3df9f21 --- /dev/null +++ b/projects/glutolik/src/test/java/ru/mipt/diht/students/glutolik/DatabaseServiceTests/StatementConstructorTests.java @@ -0,0 +1,42 @@ +package ru.mipt.diht.students.glutolik.DatabaseServiceTests; + +import org.junit.Before; +import org.junit.Test; +import ru.mipt.diht.students.glutolik.MiniORM.DatabaseService; +import ru.mipt.diht.students.glutolik.MiniORM.StatementConstructor; + +import static org.junit.Assert.assertEquals; + +/** + * Created by glutolik on 19.12.15. + */ +public class StatementConstructorTests { + DatabaseService studentDBS; + StatementConstructor statementBuilder; + + @Before + public final void initStatementBuilder() { + studentDBS = new DatabaseService<>(Student.class); + statementBuilder = studentDBS.getStatementBuilder(); + } + + @Test + public final void createQueryBuilderTest() { + assertEquals("CREATE TABLE IF NOT EXISTS student (FIO VARCHAR(255) NOT NULL, group_id INTEGER, has_salary BOOLEAN)", + statementBuilder.buildCreate()); + } + + @Test + public final void updateQueryBuilderTest() { + Student student = new Student("Peter", 999, true); + assertEquals("UPDATE student SET FIO='Peter', group_id=999, has_salary=true WHERE FIO='Peter'", + statementBuilder.buildUpdate(student)); + } + + @Test + public final void insertQueryBuilderTest() { + Student student = new Student("Alex", 123, false); + assertEquals("INSERT INTO student VALUES ('Alex', 123, false)", + statementBuilder.buildInsert(student)); + } +}