diff --git a/projects/liza22/pom.xml b/projects/liza22/pom.xml
new file mode 100644
index 0000000..cec6fb5
--- /dev/null
+++ b/projects/liza22/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+ ru.mipt.diht.students
+ parent
+ 1.0-SNAPSHOT
+
+ ru.mipt.diht.students
+ liza22
+ 1.0-SNAPSHOT
+ liza22
+ http://maven.apache.org
+
+ UTF-8
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.0
+
+
+
+ junit
+
+ junit
+
+ 4.4
+
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3.2
+
+ 1.8
+ 1.8
+
+
+
+
+
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/App.java b/projects/liza22/src/main/java/ru/mipt/diht/students/App.java
new file mode 100644
index 0000000..31d6fa9
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/App.java
@@ -0,0 +1,11 @@
+package ru.mipt.diht.students;
+
+/**
+ * Hello world!
+ *
+ */
+public class App {
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/.gitignore b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/.gitignore
new file mode 100644
index 0000000..031401d
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/.gitignore
@@ -0,0 +1 @@
+twitter.cfg
\ No newline at end of file
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/pom.xml b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/pom.xml
new file mode 100644
index 0000000..d3f7776
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/pom.xml
@@ -0,0 +1,75 @@
+
+
+ 4.0.0
+
+ com.twitter.stream
+ twitterstream
+ 1.0
+
+
+ 1.7
+ 1.7
+
+ 1.48
+ 4.0.4
+
+
+
+
+ com.beust
+ jcommander
+ ${jcommander-version}
+
+
+ org.twitter4j
+ twitter4j-core
+ ${twitter4j-version}
+
+
+ org.twitter4j
+ twitter4j-stream
+ ${twitter4j-version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ TwitterStream
+ lib/*d
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.5.1
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ runtime
+ ${project.build.directory}/lib/
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/TwitterStream.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/TwitterStream.java
new file mode 100644
index 0000000..01aaba5
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/TwitterStream.java
@@ -0,0 +1,85 @@
+import com.beust.jcommander.JCommander;
+import config.Arguments;
+import config.Constants;
+import config.TwitterConfig;
+import core.handling.TweetHandler;
+import core.handling.TweetHandlerFactory;
+import core.providing.TweetsProvider;
+import core.providing.TweetsProviderFactory;
+import model.Mode;
+
+import java.io.*;
+
+/**
+ * Main Class.
+ */
+
+public class TwitterStream {
+
+ public static final int LINE_LENGTH = 1024;
+
+ public static void main(final String[] argsString) {
+ extractArguments(argsString);
+ Arguments arguments = Arguments.getInstance();
+
+ /* In case of HELP page is requested,
+ * just print HELP file content and exit application
+ */
+ if (arguments.isHelpRequest()) {
+ printHelp(System.out);
+ System.exit(0);
+ }
+ try {
+ // read and initialize twitter configuration
+ TwitterConfig twitterConfig = new TwitterConfig();
+ // define working mode of application
+ Mode workingMode;
+ if (arguments.isStreamMode()) {
+ workingMode = Mode.STREAM;
+ } else {
+ workingMode = Mode.QUERY;
+ }
+ // get tweets provider for selected working mode and initialize this one
+ TweetsProvider tweetsProvider = TweetsProviderFactory.getProvider(workingMode);
+ tweetsProvider.init(twitterConfig);
+ // get tweet handler for selected working mode
+ TweetHandler tweetHandler = TweetHandlerFactory.getHandler(workingMode);
+ // start tweets providing and handling
+ tweetsProvider.provide(tweetHandler);
+ } catch (Exception e) {
+ System.err.println("Application TwitterStream has been occasionally crashed "
+ + "with error = \"" + e.getMessage() + "\"");
+ System.err.println("Application will be terminated with error code.");
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Transforms arguments string to Arguments object with parsed fields.
+ * @param argsString arguments strings from the input
+ */
+ private static void extractArguments(final String[] argsString) {
+ JCommander jCommander = new JCommander();
+ jCommander.addObject(Arguments.getInstance());
+ jCommander.parse(argsString);
+ }
+
+ /**
+ * Prints the content of HELP file to the passed output stream.
+ * @param out stream where HELP page will be printed
+ */
+ private static void printHelp(OutputStream out) {
+ try {
+ byte[] buffer = new byte[LINE_LENGTH];
+ try (InputStream input = TwitterStream.class.getClassLoader().getResourceAsStream(Constants.HELP_FILE)) {
+ int length = input.read(buffer);
+ while (length != -1) {
+ out.write(buffer, 0, length);
+ length = input.read(buffer);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("Problem with reading help file: \"" + e.getMessage() + "\"");
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Arguments.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Arguments.java
new file mode 100644
index 0000000..461846a
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Arguments.java
@@ -0,0 +1,107 @@
+package config;
+
+import com.beust.jcommander.Parameter;
+
+import java.util.List;
+
+/**
+ * Arguments storage.
+ * Used JCommander library.
+ *
+ * @see http://jcommander.org/
+ */
+public final class Arguments {
+ private static final Arguments INSTANCE = new Arguments();
+
+ private Arguments() { }
+
+ public static Arguments getInstance() {
+ return INSTANCE;
+ }
+
+ @Parameter(names = {"--query", "-q"},
+ required = true,
+ description = "Query or keywords for stream")
+ private List keywords;
+
+ @Parameter(names = {"--place", "-p"},
+ required = true,
+ description = "Location for search tweets")
+ private String place;
+
+ @Parameter(names = {"--stream", "-s"},
+ description = "Stream mode when tweets printed with delay")
+ private boolean streamMode;
+
+ @Parameter(names = "--hideRetweets",
+ description = "Hides retweets at all")
+ private boolean hideRetweets;
+
+ @Parameter(names = {"--limit", "-l"},
+ description = "Limits the number of printed tweets")
+ private Integer limitOfTweets = Constants.NO_TWEETS_LIMIT;
+
+ @Parameter(names = {"--help", "-h"},
+ help = true,
+ description = "Requests the help page")
+ private boolean helpRequest;
+
+ @Parameter(names = {"--verbose", "-v"},
+ description = "Verbose mode to print more information")
+ private boolean verbose;
+
+ /**
+ * Array of keywords in case of tweets stream requested.
+ * @return array of keywords to be tracked
+ */
+ public String[] getKeywords() {
+ String[] keywordsArray = new String[keywords.size()];
+ return keywords.toArray(keywordsArray);
+ }
+
+ /**
+ * Suppose that query for Search tweets is element with 0 index.
+ * @return search tweets query
+ */
+ public String getQuery() {
+ return keywords.get(0);
+ }
+
+ public String getPlace() {
+ return place;
+ }
+
+ public boolean isStreamMode() {
+ return streamMode;
+ }
+
+ public boolean hideRetweets() {
+ return hideRetweets;
+ }
+
+ public Integer getLimitOfTweets() {
+ return limitOfTweets;
+ }
+
+ public boolean isHelpRequest() {
+ return helpRequest;
+ }
+
+ public boolean isVerboseMode() {
+ return verbose;
+ }
+
+ @Override
+ public String toString() {
+ return "Arguments{"
+ + "keywords=" + keywords
+ + ", place='" + place
+ + '\''
+ + ", streamMode=" + streamMode
+ + ", hideRetweets=" + hideRetweets
+ + ", limitOfTweets=" + limitOfTweets
+ + ", helpRequest=" + helpRequest
+ + ", verboseMode=" + verbose
+ + '}';
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Constants.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Constants.java
new file mode 100644
index 0000000..b9945af
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/Constants.java
@@ -0,0 +1,31 @@
+package config;
+
+/**
+ * Application constants storage.
+ */
+public final class Constants {
+ /**
+ * Reconnect timeout to twitter in seconds .
+ */
+ public static final int RECONNECT_TIMEOUT_SECS = 10;
+ /**
+ * Default value of limit argument.
+ */
+ public static final int NO_TWEETS_LIMIT = -1;
+ /**
+ * Delay between two printing of tweets.
+ */
+ public static final int PRINT_TWEET_DELAY_SECS = 1;
+ /**
+ * Message which is printed in verbose mode when no any tweet to print for stream.
+ */
+ public static final String NO_TWEET_MESSAGE = "...";
+ /**
+ * Name of resource - twitter config file.
+ */
+ public static final String TWITTER_CONFIG_FILE = "twitter.cfg";
+ /**
+ * Name of resource - help content file.
+ */
+ public static final String HELP_FILE = "help.txt";
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/TwitterConfig.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/TwitterConfig.java
new file mode 100644
index 0000000..9cee397
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/config/TwitterConfig.java
@@ -0,0 +1,101 @@
+package config;
+
+import twitter4j.auth.AccessToken;
+import twitter4j.conf.Configuration;
+import twitter4j.conf.ConfigurationBuilder;
+
+import java.io.*;
+
+/*
+ * Class loads and holds Twitter Access configuration from file resource
+ * and provides methods to get {@link twitter4j.conf.Configuration} and {@link twitter4j.auth.AccessToken}
+ */
+public class TwitterConfig {
+ private static final String CONSUMER_KEY_PROP_NAME = "consumerKey";
+ private static final String CONSUMER_SECRET_PROP_NAME = "consumerSecret";
+ private static final String ACCESS_TOKEN_PROP_NAME = "accessToken";
+ private static final String ACCESS_TOKEN_SECRET_PROP_NAME = "accessTokenSecret";
+
+ private String consumerKey;
+ private String consumerSecret;
+ private String accessToken;
+ private String accessTokenSecret;
+
+ public TwitterConfig() {
+ init();
+ }
+
+ private void init() {
+ File cfgFile = new File(Constants.TWITTER_CONFIG_FILE);
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(cfgFile)))) {
+ while (in.ready()) {
+ String line = in.readLine();
+ int indexOfDelimiter = line.indexOf('=');
+ if (indexOfDelimiter == -1) {
+ System.err.println("Incorrect line in twitter configuration = '" + line + "'");
+ continue;
+ }
+ String propName = line.substring(0, indexOfDelimiter);
+ String propValue = line.substring(indexOfDelimiter + 1, line.length());
+ switch (propName) {
+ case CONSUMER_KEY_PROP_NAME:
+ consumerKey = propValue;
+ break;
+ case CONSUMER_SECRET_PROP_NAME:
+ consumerSecret = propValue;
+ break;
+ case ACCESS_TOKEN_PROP_NAME:
+ accessToken = propValue;
+ break;
+ case ACCESS_TOKEN_SECRET_PROP_NAME:
+ accessTokenSecret = propValue;
+ break;
+ default:
+ System.err.println("Property '" + propName + "' not recognized");
+ }
+ }
+ } catch (FileNotFoundException e) {
+ System.err.println("Twitter config file by path = \"" + cfgFile.getAbsolutePath() + "\" not found");
+ } catch (IOException e) {
+ System.err.println("Problem with reading twitter config file: " + e.getMessage());
+ }
+
+ validate();
+ }
+
+ private void validate() {
+ if (consumerKey == null
+ || consumerSecret == null
+ || accessToken == null
+ || accessTokenSecret == null) {
+ throw new IllegalStateException("Twitter configuration file is incorrect");
+ }
+ }
+
+ public final AccessToken getAccessToken() {
+ return new AccessToken(accessToken, accessTokenSecret);
+ }
+
+ public final Configuration getConfiguration() {
+ ConfigurationBuilder configurationBuilder = new ConfigurationBuilder().
+ setOAuthConsumerKey(consumerKey).
+ setOAuthConsumerSecret(consumerSecret).
+ setOAuthAccessToken(accessToken).
+ setOAuthAccessTokenSecret(accessTokenSecret);
+ return configurationBuilder.build();
+ }
+
+ @Override
+ public final String toString() {
+ return "TwitterConfig{"
+ + "consumerKey='" + consumerKey
+ + '\''
+ + ", consumerSecret='" + consumerSecret
+ + '\''
+ + ", accessToken='" + accessToken
+ + '\''
+ + ", accessTokenSecret='" + accessTokenSecret
+ + '\''
+ + '}';
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandler.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandler.java
new file mode 100644
index 0000000..01d81fc
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandler.java
@@ -0,0 +1,15 @@
+package core.handling;
+
+import model.Tweet;
+
+/**
+ * Tweet handler interface.
+ */
+public interface TweetHandler {
+
+ /**
+ * Handles tweet by any way.
+ * @param tweet obtained tweet
+ */
+ void handle(Tweet tweet);
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandlerFactory.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandlerFactory.java
new file mode 100644
index 0000000..807bc25
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/TweetHandlerFactory.java
@@ -0,0 +1,26 @@
+package core.handling;
+
+import core.handling.impl.PrintResultOfQueryTweetsHandler;
+import core.handling.impl.PrintStreamOfTweetsHandler;
+import model.Mode;
+
+public final class TweetHandlerFactory {
+
+ private TweetHandlerFactory() { }
+
+ /**
+ * Gets tweets handler depending on the working mode.
+ * @param mode working mode of application
+ * @return tweet handler implementation
+ */
+ public static TweetHandler getHandler(Mode mode) {
+ switch (mode) {
+ case STREAM:
+ return new PrintStreamOfTweetsHandler(System.out);
+ case QUERY:
+ return new PrintResultOfQueryTweetsHandler(System.out);
+ default:
+ throw new IllegalArgumentException("Mode = " + mode + " is not supported");
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintResultOfQueryTweetsHandler.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintResultOfQueryTweetsHandler.java
new file mode 100644
index 0000000..b244d2e
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintResultOfQueryTweetsHandler.java
@@ -0,0 +1,105 @@
+package core.handling.impl;
+
+import core.handling.TweetHandler;
+import model.Tweet;
+import utils.TextUtils;
+
+import java.io.PrintStream;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This handler is used in QUERY mode of application.
+ * It prints tweets every time when new tweet is come.
+ *
+ * When new tweet is obtained (method 'handle' invoked) this handler prints
+ * formatted representation of this new tweet.
+ */
+public final class PrintResultOfQueryTweetsHandler implements TweetHandler {
+ private static final int MILLI = 1_000;
+ private static final int SEC_IN_MIN = 60;
+ private static final int STRING_SIZE = 256;
+
+ private PrintStream out;
+
+ private AtomicLong tweetCounter = new AtomicLong(0);
+
+ public PrintResultOfQueryTweetsHandler(PrintStream outStream) {
+ this.out = outStream;
+ }
+
+ @Override
+ public void handle(Tweet tweet) {
+ out.println("Tweet#" + tweetCounter.incrementAndGet() + ":");
+ out.println(formatTweet(tweet));
+ }
+
+ /*
+ * Print format is the following:
+ *
+ * If tweet IS NOT retweeted
+ * ----------------------------------------------------------------------------------------
+ * [] @:
+ * ----------------------------------------------------------------------------------------
+ *
+ * If tweet IS retweeted
+ * ----------------------------------------------------------------------------------------
+ * [] @: ретвитнул @: ( ретвитов)
+ * ----------------------------------------------------------------------------------------
+ *
+ * @param tweet tweet object to be printed
+ * @return text representation of tweet according to format
+ */
+ private static String formatTweet(Tweet tweet) {
+ StringBuilder tweetView = new StringBuilder(STRING_SIZE);
+ tweetView.append("----------------------------------------------------------------------------------------\n");
+ if (tweet.isNotRetweet()) {
+ tweetView.append("[").append(formatTime(tweet.getTime())).append("] ").
+ append("@").append(getNickname(tweet)).
+ append(": ").append(tweet.getText());
+ } else {
+ Tweet retweetedTweet = tweet.getRetweetedTweet();
+ tweetView.append("[").append(formatTime(tweet.getTime())).append("] ").
+ append("@").append(getNickname(tweet)).
+ append(": ретвитнул @").append(getNickname(retweetedTweet)).
+ append(": ").append(retweetedTweet.getText()).
+ append(" (").append(retweetedTweet.getRetweetCount()).append(" ретвитов)");
+ }
+ tweetView.append("\n----------------------------------------------------------------------------------------");
+ return tweetView.toString();
+ }
+
+ private static String getNickname(Tweet tweet) {
+ String nick = tweet.getAuthor().getName();
+ return TextUtils.coloredText(nick, TextUtils.COLOR_BLUE);
+ }
+
+ /*
+ * Time format is the following:
+ * Время должно быть в формате:
+ * "Только что" - если менее 2х минут назад
+ * "n минут назад" - если менее часа назад (n - цифрами)
+ * "n часов назад" - если более часа, но сегодня (n - цифрами)
+ * "вчера" - если вчера
+ * "n дней назад" - в остальных случаях (n - цифрами)
+ *
+ * @param then the tweet's time in milliseconds
+ * @return text representation of the tweet's time according to format
+ */
+ private static String formatTime(long then) {
+ long now = new Date().getTime();
+ long diffInMinutes = (now - then) / MILLI / SEC_IN_MIN;
+ if (diffInMinutes < 2) {
+ return "Только что";
+ } else if (TimeUnit.MINUTES.toHours(diffInMinutes) < 1) {
+ return diffInMinutes + " минут назад";
+ } else if (TimeUnit.MINUTES.toHours(diffInMinutes) >= 1 && TimeUnit.MINUTES.toDays(diffInMinutes) < 1) {
+ return TimeUnit.MINUTES.toHours(diffInMinutes) + " часов назад";
+ } else if (TimeUnit.MINUTES.toDays(diffInMinutes) >= 1 && TimeUnit.MINUTES.toDays(diffInMinutes) < 2) {
+ return "вчера";
+ } else {
+ return TimeUnit.MINUTES.toDays(diffInMinutes) + " дней назад";
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintStreamOfTweetsHandler.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintStreamOfTweetsHandler.java
new file mode 100644
index 0000000..87e6e9a
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/handling/impl/PrintStreamOfTweetsHandler.java
@@ -0,0 +1,101 @@
+package core.handling.impl;
+
+import config.Arguments;
+import config.Constants;
+import core.handling.TweetHandler;
+import model.Tweet;
+import utils.TextUtils;
+
+import java.io.PrintStream;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicLong;
+
+/*
+ * This handler is used in STREAM mode of application.
+ * It prints tweets every Constants.PRINT_TWEET_DELAY_SECS (by default every 1 second).
+ *
+ * When new tweet is obtained (method 'handle' invoked) this handler stores this tweet
+ * to internal queue. When next print step is come, handler takes tweet from queue
+ * (by 'poll' method) and prints this tweet.
+ * In case when no available tweet in queue, it will print nothing or
+ * Constants.NO_TWEET_MESSAGE in VERBOSE mode.
+ */
+public final class PrintStreamOfTweetsHandler implements TweetHandler {
+ public static final int STRING_SIZE = 256;
+ public static final int KILO = 1_000;
+ private PrintStream out;
+ private Queue tweetQueue;
+
+ private boolean started = false;
+ private AtomicLong tweetCounter = new AtomicLong(0);
+
+ public PrintStreamOfTweetsHandler(final PrintStream outStream) {
+ this.out = outStream;
+ tweetQueue = new ArrayDeque<>();
+ }
+
+ @Override
+ public void handle(Tweet tweet) {
+ tweetQueue.offer(tweet);
+
+ if (!started) {
+ // schedule timer with task of printing tweets from queue
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ Tweet next = tweetQueue.poll();
+ if (next != null) {
+ out.println("Tweet#" + tweetCounter.incrementAndGet() + ":");
+ out.println(formatTweet(next));
+ } else {
+ if (Arguments.getInstance().isVerboseMode()) {
+ out.println(Constants.NO_TWEET_MESSAGE);
+ }
+ }
+ }
+ }, 0, Constants.PRINT_TWEET_DELAY_SECS * KILO);
+ started = true;
+ }
+ }
+
+ /*
+ * Print format is the following:
+ *
+ * If tweet IS NOT retweeted
+ * ----------------------------------------------------------------------------------------
+ * @:
+ * ----------------------------------------------------------------------------------------
+ *
+ * If tweet IS retweeted
+ * ----------------------------------------------------------------------------------------
+ * @: ретвитнул @: ( ретвитов)
+ * ----------------------------------------------------------------------------------------
+ *
+ * @param tweet tweet object to be printed
+ * @return text representation of tweet according to format
+ */
+ private static String formatTweet(Tweet tweet) {
+ StringBuilder tweetView = new StringBuilder(STRING_SIZE);
+ tweetView.append("----------------------------------------------------------------------------------------\n");
+ if (tweet.isNotRetweet()) {
+ tweetView.append("@").append(getNickname(tweet)).
+ append(": ").append(tweet.getText());
+ } else {
+ Tweet retweetedTweet = tweet.getRetweetedTweet();
+ tweetView.append("@").append(getNickname(tweet)).
+ append(": ретвитнул @").append(getNickname(retweetedTweet)).
+ append(": ").append(retweetedTweet.getText()).
+ append(" (").append(retweetedTweet.getRetweetCount()).append(" ретвитов)");
+ }
+ tweetView.append("\n----------------------------------------------------------------------------------------");
+ return tweetView.toString();
+ }
+
+ private static String getNickname(Tweet tweet) {
+ String nick = tweet.getAuthor().getName();
+ return TextUtils.coloredText(nick, TextUtils.COLOR_BLUE);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProvider.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProvider.java
new file mode 100644
index 0000000..e56766a
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProvider.java
@@ -0,0 +1,14 @@
+package core.providing;
+
+import config.TwitterConfig;
+import core.handling.TweetHandler;
+
+/**
+ * Tweets provider interface.
+ */
+public interface TweetsProvider {
+
+ void init(TwitterConfig twitterConfig);
+
+ void provide(TweetHandler handler);
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProviderFactory.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProviderFactory.java
new file mode 100644
index 0000000..664dd46
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/TweetsProviderFactory.java
@@ -0,0 +1,29 @@
+package core.providing;
+
+import core.providing.impl.TweetsByQueryProvider;
+import core.providing.impl.TweetsStreamProvider;
+import model.Mode;
+
+/**
+ * Tweets provider factory.
+ */
+public final class TweetsProviderFactory {
+
+ private TweetsProviderFactory() { }
+
+ /**
+ * Gets tweets provider depending on the working mode.
+ * @param mode working mode of application
+ * @return tweets provider implementation
+ */
+ public static TweetsProvider getProvider(Mode mode) {
+ switch (mode) {
+ case STREAM:
+ return new TweetsStreamProvider();
+ case QUERY:
+ return new TweetsByQueryProvider();
+ default:
+ throw new IllegalArgumentException("Mode = " + mode + " is not supported");
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsByQueryProvider.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsByQueryProvider.java
new file mode 100644
index 0000000..ba200c3
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsByQueryProvider.java
@@ -0,0 +1,65 @@
+package core.providing.impl;
+
+import config.Arguments;
+import config.Constants;
+import config.TwitterConfig;
+import core.handling.TweetHandler;
+import core.providing.TweetsProvider;
+import core.quering.SearchQueryBuilder;
+import model.Tweet;
+import twitter4j.*;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tweets by query provider, provides requested tweets to handler.
+ *
+ * Query is built by SearchQueryBuilder and method 'search' of
+ * Twitter instance is invoked.
+ *
+ * QueryResult contains list of statuses, which transformed to
+ * internal Tweet objects, after that these tweets are sent to
+ * handler.
+ */
+public final class TweetsByQueryProvider implements TweetsProvider {
+ private Twitter twitter;
+ private Query query;
+
+ @Override
+ public void init(TwitterConfig twitterConfig) {
+ twitter = new TwitterFactory(twitterConfig.getConfiguration()).getInstance();
+ query = new SearchQueryBuilder(twitter).buildQuery();
+ }
+
+ @Override
+ public void provide(TweetHandler handler) {
+ try {
+ QueryResult result = twitter.search(query);
+ if (result.getTweets() == null || result.getTweets().isEmpty()) {
+ System.out.println("No any tweets by specified query and place found");
+ }
+ for (Status status : result.getTweets()) {
+ // transform status to Tweet object and send to handler
+ Tweet tweet = Tweet.valueOf(status);
+ if (Arguments.getInstance().hideRetweets() && tweet.isRetweet()) {
+ // skip this tweet
+ continue;
+ }
+ handler.handle(tweet);
+ }
+ } catch (TwitterException e) {
+ System.err.println("Twitter has been occasionally crashed with error: \"" + e.getMessage() + "\"");
+ System.err.println("Try one more time... [timeout = " + Constants.RECONNECT_TIMEOUT_SECS + " secs]");
+ timeout(Constants.RECONNECT_TIMEOUT_SECS);
+ provide(handler);
+ }
+ }
+
+ private static void timeout(int seconds) {
+ try {
+ TimeUnit.SECONDS.sleep(seconds);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsStreamProvider.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsStreamProvider.java
new file mode 100644
index 0000000..cc4e514
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/providing/impl/TweetsStreamProvider.java
@@ -0,0 +1,96 @@
+package core.providing.impl;
+
+import config.Arguments;
+import config.Constants;
+import config.TwitterConfig;
+import core.handling.TweetHandler;
+import core.providing.TweetsProvider;
+import core.quering.FilterQueryBuilder;
+import model.Tweet;
+import twitter4j.*;
+import twitter4j.auth.AccessToken;
+import twitter4j.conf.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tweets stream provider, provides tweets to TweetHandler.
+ *
+ * This class implements StatusAdapter and registers itself
+ * as StatusListener in TwitterStream.
+ * Every time when new status obtained the transformation to
+ * internal Tweet object performed, after that this Tweet sent
+ * to the handler.
+ */
+public final class TweetsStreamProvider extends StatusAdapter implements TweetsProvider {
+ private Configuration configuration;
+ private AccessToken accessToken;
+
+ private TwitterStream twitterStream;
+ private FilterQuery filterQuery;
+
+ private TweetHandler tweetHandler;
+
+ @Override
+ public void init(TwitterConfig twitterConfig) {
+ configuration = twitterConfig.getConfiguration();
+ accessToken = twitterConfig.getAccessToken();
+ }
+
+ @Override
+ public void provide(TweetHandler handler) {
+ this.tweetHandler = handler;
+ connect();
+ }
+
+ @Override
+ public void onStatus(Status status) {
+ Tweet tweet = Tweet.valueOf(status);
+ if (Arguments.getInstance().hideRetweets() && tweet.isRetweet()) {
+ // skip this tweet
+ return;
+ }
+ tweetHandler.handle(tweet);
+ }
+
+ @Override
+ public void onException(Exception e) {
+ System.err.println("Twitter stream has been occasionally crashed with error: \"" + e.getMessage() + "\"");
+ System.err.println("Try to reconnect... [timeout = " + Constants.RECONNECT_TIMEOUT_SECS + " secs]");
+ reconnect();
+ }
+
+ private void connect() {
+ twitterStream = new TwitterStreamFactory(configuration).getInstance(accessToken);
+ Twitter twitter = new TwitterFactory(configuration).getInstance();
+ filterQuery = new FilterQueryBuilder(twitter).buildQuery();
+ // register itself as status listener
+ // method 'onStatus' will execute every time when new tweet obtained
+ twitterStream.addListener(this);
+ twitterStream.filter(filterQuery);
+ }
+
+ private void disconnect() {
+ if (twitterStream != null) {
+ twitterStream.cleanUp();
+ twitterStream.shutdown();
+ // help GC
+ twitterStream = null;
+ }
+ }
+
+ private void reconnect() {
+ // reconnect is performed with timeout between disconnect and new connect
+ disconnect();
+ timeout(Constants.RECONNECT_TIMEOUT_SECS);
+ connect();
+ }
+
+ private static void timeout(int seconds) {
+ try {
+ TimeUnit.SECONDS.sleep(seconds);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/FilterQueryBuilder.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/FilterQueryBuilder.java
new file mode 100644
index 0000000..1b13f1a
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/FilterQueryBuilder.java
@@ -0,0 +1,55 @@
+package core.quering;
+
+import config.Arguments;
+import utils.GeoUtils;
+import twitter4j.*;
+
+/**
+ * FilterQuery builder to build query for Twitter searching
+ * based on arguments (query, place).
+ */
+public class FilterQueryBuilder {
+ private Twitter twitter;
+
+ public FilterQueryBuilder(Twitter twiter) {
+ this.twitter = twiter;
+ }
+
+ public final FilterQuery buildQuery() {
+ Arguments arguments = Arguments.getInstance();
+
+ FilterQuery query = new FilterQuery();
+ query.track(arguments.getKeywords());
+
+ // calculate and set place for tweets
+ try {
+ Place place = GeoUtils.findPlaceByName(twitter, arguments.getPlace());
+ GeoLocation[] vertices = place.getBoundingBoxCoordinates()[0];
+ /*
+ * We try to find two points of box
+ * - the first with MIN latitude and longitude
+ * - the second with MAX latitude and longitude
+ * These two points will be used as filtering location for tweets.
+ *
+ * For additional details see:
+ * https://dev.twitter.com/streaming/overview/request-parameters#locations
+ * Checked with "New York City" ({-74,40},{-73,41})
+ */
+ double minLongitude = Double.MAX_VALUE, minLatitude = Double.MAX_VALUE;
+ double maxLongitude = -Double.MAX_VALUE, maxLatitude = -Double.MAX_VALUE;
+ for (GeoLocation vertex : vertices) {
+ minLongitude = Math.min(minLongitude, vertex.getLongitude());
+ minLatitude = Math.min(minLatitude, vertex.getLatitude());
+ maxLongitude = Math.max(maxLongitude, vertex.getLongitude());
+ maxLatitude = Math.max(maxLatitude, vertex.getLatitude());
+ }
+ double[][] locations = {{minLongitude, minLatitude}, {maxLongitude, maxLatitude}};
+ query.locations(locations);
+ } catch (TwitterException te) {
+ System.err.println("Searching of places has been crashed with error = \"" + te.getMessage() + "\"");
+ System.err.println("Search query will be created without place condition.");
+ }
+
+ return query;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/SearchQueryBuilder.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/SearchQueryBuilder.java
new file mode 100644
index 0000000..3948ad3
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/core/quering/SearchQueryBuilder.java
@@ -0,0 +1,94 @@
+package core.quering;
+
+import config.Arguments;
+import config.Constants;
+import utils.GeoUtils;
+import twitter4j.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * SearchQuery builder to build query for tweets streaming
+ * based on arguments (keywords, place).
+ */
+public class SearchQueryBuilder {
+ private Twitter twitter;
+
+ public SearchQueryBuilder(Twitter twiter) {
+ this.twitter = twiter;
+ }
+
+ public final Query buildQuery() {
+ Arguments arguments = Arguments.getInstance();
+ Query query = new Query();
+ // set query for tweets
+ query.setQuery(arguments.getQuery());
+ // set limit of tweets if specified in arguments
+ if (arguments.getLimitOfTweets() != Constants.NO_TWEETS_LIMIT) {
+ query.setCount(arguments.getLimitOfTweets());
+ }
+ // calculate and set place for tweets
+ try {
+ Place place = GeoUtils.findPlaceByName(twitter, arguments.getPlace());
+ GeoLocation[] vertices = place.getBoundingBoxCoordinates()[0];
+ /*
+ * Implemented approach which described in the task:
+ *
+ * Для Twitter.search использовать среднее арифметическое широты и долготы
+ * Place.getBoundingBoxCoordinates() и радиус как половину максимального
+ * расстояния между точками.
+ */
+ GeoLocation center = getCenter(vertices);
+ double radius = getRadius(vertices);
+ query.setGeoCode(center, radius, Query.Unit.mi);
+ } catch (TwitterException te) {
+ System.err.println("Searching of places has been crashed with error = \"" + te.getMessage() + "\"");
+ System.err.println("Search query will be created without place condition.");
+ }
+
+ return query;
+ }
+
+ public static GeoLocation getCenter(GeoLocation[] vertices) {
+ double centerLatitude, centerLongitude;
+ double[] latitudes = new double[vertices.length];
+ double[] longitudes = new double[vertices.length];
+ for (int i = 0; i < vertices.length; i++) {
+ GeoLocation vertex = vertices[i];
+ latitudes[i] = vertex.getLatitude();
+ longitudes[i] = vertex.getLongitude();
+ }
+ centerLatitude = getArithmeticMean(latitudes);
+ centerLongitude = getArithmeticMean(longitudes);
+ return new GeoLocation(centerLatitude, centerLongitude);
+ }
+
+ public static double getRadius(GeoLocation[] vertices) {
+ List distances = new ArrayList<>();
+ // calculate distances between all vertices
+ for (int i = 0; i < vertices.length - 1; i++) {
+ for (int j = i + 1; j < vertices.length; j++) {
+ GeoLocation vertex1 = vertices[i];
+ GeoLocation vertex2 = vertices[j];
+ distances.add(GeoUtils.distanceBetweenTwoCoordinates(
+ vertex1.getLatitude(), vertex1.getLongitude(),
+ vertex2.getLatitude(), vertex2.getLongitude())
+ );
+ }
+ }
+ // sort list in reverse order (from MAX to MIN)
+ // and get the first - MAX of all distances
+ Collections.sort(distances, Collections.reverseOrder());
+ return distances.get(0);
+ }
+
+ public static double getArithmeticMean(double[] numbers) {
+ double sum = 0;
+ for (double num : numbers) {
+ sum += num;
+ }
+ return sum / numbers.length;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Mode.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Mode.java
new file mode 100644
index 0000000..23dc708
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Mode.java
@@ -0,0 +1,8 @@
+package model;
+
+/**
+ * Working mode of application - as tweets stream or tweets by query.
+ */
+public enum Mode {
+ STREAM, QUERY;
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Tweet.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Tweet.java
new file mode 100644
index 0000000..9ead43c
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/Tweet.java
@@ -0,0 +1,80 @@
+package model;
+
+import twitter4j.Status;
+
+/**
+ * Class represents Tweet object.
+ */
+public final class Tweet {
+ private String text;
+ private TwitterUser author;
+ private long time;
+ private long retweetCount;
+ private Tweet retweetedTweet;
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String tweetText) {
+ this.text = tweetText;
+ }
+
+ public TwitterUser getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(TwitterUser tweetAuthor) {
+ this.author = tweetAuthor;
+ }
+
+ public long getRetweetCount() {
+ return retweetCount;
+ }
+
+ public void setRetweetCount(long count) {
+ this.retweetCount = count;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long tweetTime) {
+ this.time = tweetTime;
+ }
+
+ public Tweet getRetweetedTweet() {
+ return retweetedTweet;
+ }
+
+ public void setRetweetedTweet(Tweet retweeted) {
+ this.retweetedTweet = retweeted;
+ }
+
+ public boolean isRetweet() {
+ return null != retweetedTweet;
+ }
+
+ public boolean isNotRetweet() {
+ return !isRetweet();
+ }
+
+ /**
+ * Factory method to convert twitter4j.Status object to internal model - Tweet object.
+ * @param twitter4jStatus twitter4j.Status object
+ * @return Tweet object
+ */
+ public static Tweet valueOf(Status twitter4jStatus) {
+ Tweet tweet = new Tweet();
+ if (twitter4jStatus.getRetweetedStatus() != null) {
+ tweet.setRetweetedTweet(valueOf(twitter4jStatus.getRetweetedStatus()));
+ }
+ tweet.setAuthor(TwitterUser.valueOf(twitter4jStatus.getUser()));
+ tweet.setText(twitter4jStatus.getText());
+ tweet.setTime(twitter4jStatus.getCreatedAt().getTime());
+ tweet.setRetweetCount(twitter4jStatus.getRetweetCount());
+
+ return tweet;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/TwitterUser.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/TwitterUser.java
new file mode 100644
index 0000000..a10e491
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/model/TwitterUser.java
@@ -0,0 +1,42 @@
+package model;
+
+import twitter4j.User;
+
+/**
+ * Class represents Twitter User object.
+ */
+public class TwitterUser {
+ private Long id;
+ private String name;
+
+ public TwitterUser(Long userid, String username) {
+ this.id = userid;
+ this.name = username;
+ }
+
+ public final Long getId() {
+ return id;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ /**
+ * Factory method to convert twitter4j.User object to internal model - TwitterUser object.
+ * @param twitter4jUser twitter4j.User object
+ * @return TwitterUser object
+ */
+ public static TwitterUser valueOf(User twitter4jUser) {
+ return new TwitterUser(twitter4jUser.getId(), twitter4jUser.getName());
+ }
+
+ @Override
+ public final String toString() {
+ return "TwitterUser{"
+ + "id=" + id
+ + ", name='" + name
+ + '\''
+ + '}';
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/GeoUtils.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/GeoUtils.java
new file mode 100644
index 0000000..662507b
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/GeoUtils.java
@@ -0,0 +1,51 @@
+package utils;
+
+import twitter4j.*;
+
+import static java.lang.Math.*;
+
+public final class GeoUtils {
+ public static final int CONST1 = 60;
+ public static final double CONST2 = 1.1515;
+
+ private GeoUtils() { }
+
+ /*
+ * Finds twitter4j.Place object by place name.
+ * @param twitter initialized twitter instance
+ * @param placeName name of place to search
+ * @return found twitter4j.Place object
+ * @throws TwitterException
+ */
+ public static Place findPlaceByName(Twitter twitter, String placeName) throws TwitterException {
+ GeoQuery geoQuery = new GeoQuery((String) null);
+ geoQuery.setQuery(placeName);
+ // we need the only one place
+ geoQuery.setMaxResults(1);
+ ResponseList places = twitter.searchPlaces(geoQuery);
+ if (places.isEmpty()) {
+ throw new IllegalArgumentException("Place by name = '" + placeName + "' not found");
+ }
+ return places.get(0);
+ }
+
+ /*
+ * Calculates distance between two points based on their longitude and latitude.
+ * @see
+ *
+ * @param lat1 latitude of point_1
+ * @param lon1 longitude of point_1
+ * @param lat2 latitude of point_2
+ * @param lon2 longitude of point_2
+ * @return distance in miles
+ */
+ public static double distanceBetweenTwoCoordinates(double lat1, double lon1, double lat2, double lon2) {
+ double theta = lon1 - lon2;
+ double dist = sin(toRadians(lat1)) * sin(toRadians(lat2))
+ + cos(toRadians(lat1)) * cos(toRadians(lat2)) * cos(toRadians(theta));
+ dist = acos(dist);
+ dist = toDegrees(dist);
+ dist = dist * CONST1 * CONST2;
+ return dist;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/TextUtils.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/TextUtils.java
new file mode 100644
index 0000000..e7f9304
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/java/utils/TextUtils.java
@@ -0,0 +1,16 @@
+package utils;
+
+public final class TextUtils {
+ public static final String COLOR_RESET = "\u001B[0m";
+ public static final String COLOR_BLUE = "\u001B[34m";
+
+ // to prevent instantiating
+ // this class must be used as static only
+ private TextUtils() { }
+
+ public static String coloredText(String text, String color) {
+ return color
+ + text
+ + COLOR_RESET;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/resources/help.txt b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/resources/help.txt
new file mode 100644
index 0000000..95008c4
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/src/main/resources/help.txt
@@ -0,0 +1,11 @@
+TwitterStream - it's a console application to print the stream of tweets onto the screen
+according to the specified conditions.
+
+Command usage:
+java TwitterStream \
+ [--query|-q ] \
+ [--place|-p ] \
+ [--stream|-s] \
+ [--hideRetweets] \
+ [--limit|-l ] \
+ [--help|-h]
diff --git "a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/~$\320\260\321\202\320\272\320\260\321\217 \320\270\320\275\321\201\321\202\321\200\321\203\320\272\321\206\320\270\321\217.txt" "b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/~$\320\260\321\202\320\272\320\260\321\217 \320\270\320\275\321\201\321\202\321\200\321\203\320\272\321\206\320\270\321\217.txt"
new file mode 100644
index 0000000..0ebf8fb
Binary files /dev/null and "b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/TwitterStream/~$\320\260\321\202\320\272\320\260\321\217 \320\270\320\275\321\201\321\202\321\200\321\203\320\272\321\206\320\270\321\217.txt" differ
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/pom.xml b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/pom.xml
new file mode 100644
index 0000000..9194c51
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ collection-ql
+ collection-ql
+ 1.0
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.0
+
+
+
+ junit
+ junit
+ 4.4
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3.2
+
+ 1.8
+ 1.8
+
+
+
+
+
\ No newline at end of file
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/CollectionQuery.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/CollectionQuery.java
new file mode 100644
index 0000000..e3f5374
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/CollectionQuery.java
@@ -0,0 +1,43 @@
+import client.Statistics;
+import client.Student;
+import library.core.exceptions.IncorrectQueryException;
+
+import java.time.LocalDate;
+
+import static client.Student.student;
+import static library.api.Aggregates.avg;
+import static library.api.Aggregates.constant;
+import static library.api.Aggregates.count;
+import static library.api.Conditions.like;
+import static library.api.OrderByConditions.asc;
+import static library.api.OrderByConditions.desc;
+import static library.api.Sources.from;
+import static library.api.Sources.list;
+
+public class CollectionQuery {
+
+ public static void main(String[] args) throws IncorrectQueryException {
+ final int const10 = 10;
+ final int const100 = 100;
+ Iterable statistics =
+ from(list(
+ student("ivanov", LocalDate.parse("1986-08-06"), "494"),
+ student("sidorov", LocalDate.parse("1986-08-06"), "495"),
+ student("smith", LocalDate.parse("1986-08-06"), "495"),
+ student("petrov", LocalDate.parse("1996-08-06"), "494")))
+ .select(Statistics.class, Student::getGroup, count(Student::getGroup), avg(Student::age))
+ .where(like(Student::getName, ".*ov").and(s -> s.age() > const10))
+ .groupBy(Student::getGroup)
+ .having(s -> s.getCount() > 0)
+ .orderBy(asc(Statistics::getGroup), desc(Statistics::getCount))
+ .limit(const100)
+ .union(
+ from(list(student("ivanov", LocalDate.parse("1985-08-06"), "494")))
+ .selectDistinct(Statistics.class, constant("all"), count(s -> 1),
+ avg(Student::age))
+ )
+ .execute();
+ System.out.println(statistics);
+ }
+
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Statistics.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Statistics.java
new file mode 100644
index 0000000..d41b6d5
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Statistics.java
@@ -0,0 +1,65 @@
+package client;
+
+public class Statistics {
+
+ private final String group;
+ private final Long count;
+ private final Long age;
+ private final int const1 = 31;
+
+ public final String getGroup() {
+ return group;
+ }
+
+ public final Long getCount() {
+ return count;
+ }
+
+ public final Long getAge() {
+ return age;
+ }
+
+ public Statistics(String group1, Long count1, Long age1) {
+ this.group = group1;
+ this.count = count1;
+ this.age = age1;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Statistics that = (Statistics) o;
+
+ if (!group.equals(that.group)) {
+ return false;
+ }
+ if (!count.equals(that.count)) {
+ return false;
+ }
+ return age.equals(that.age);
+
+ }
+
+ @Override
+ public final int hashCode() {
+ int result = group.hashCode();
+ result = const1 * result + count.hashCode();
+ result = const1 * result + age.hashCode();
+ return result;
+ }
+
+ @Override
+ public final String toString() {
+ return "Statistics{"
+ + "group=" + group
+ + ", count=" + count
+ + ", avg=" + age
+ + '}';
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Student.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Student.java
new file mode 100644
index 0000000..b585e54
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/Student.java
@@ -0,0 +1,39 @@
+package client;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+
+public class Student {
+ private final String name;
+
+ private final LocalDate dateOfBirth;
+
+ private final String group;
+
+ public final String getName() {
+ return name;
+ }
+
+ public Student(String name1, LocalDate dateOfBirth1, String group1) {
+ this.name = name1;
+ this.dateOfBirth = dateOfBirth1;
+ this.group = group1;
+ }
+
+ public final LocalDate getDateOfBirth() {
+ return dateOfBirth;
+ }
+
+ public final String getGroup() {
+ return group;
+ }
+
+ public final long age() {
+ return ChronoUnit.YEARS.between(getDateOfBirth(), LocalDateTime.now());
+ }
+
+ public static Student student(String name1, LocalDate dateOfBirth1, String group1) {
+ return new Student(name1, dateOfBirth1, group1);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/StudentInfo.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/StudentInfo.java
new file mode 100644
index 0000000..bb9a4fd
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/client/StudentInfo.java
@@ -0,0 +1,66 @@
+package client;
+
+public class StudentInfo {
+ private String name;
+ private String group;
+ private long age;
+
+ public StudentInfo(String name1, String group1, Long age1) {
+ this.name = name1;
+ this.group = group1;
+ this.age = age1;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ public final String getGroup() {
+ return group;
+ }
+
+ public final long getAge() {
+ return age;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ StudentInfo that = (StudentInfo) o;
+
+ if (age != that.age) {
+ return false;
+ }
+ if (!name.equals(that.name)) {
+ return false;
+ }
+ return group.equals(that.group);
+
+ }
+
+ @Override
+ public final int hashCode() {
+ final int const31 = 31;
+ final int const32 = 32;
+
+ int result = name.hashCode();
+ result = const31 * result + group.hashCode();
+ result = const31 * result + (int) (age ^ (age >>> const32));
+ return result;
+ }
+
+ @Override
+ public final String toString() {
+ return "StudentInfo{"
+ + "name=" + name
+ + ", group=" + group
+ + ", age=" + age
+ + '}';
+ }
+ }
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Aggregates.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Aggregates.java
new file mode 100644
index 0000000..73e65ef
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Aggregates.java
@@ -0,0 +1,50 @@
+package library.api;
+
+import library.core.model.aggregation.AggregateFunction;
+import library.core.model.aggregation.impl.AverageFunction;
+import library.core.model.aggregation.impl.CountFunction;
+import library.core.model.aggregation.impl.MaxFunction;
+import library.core.model.aggregation.impl.MinFunction;
+
+import java.util.function.Function;
+
+/**
+ * Aggregate functions.
+ */
+public class Aggregates {
+
+ public static AggregateFunction count(Function countingFunction) {
+ return new CountFunction<>(countingFunction);
+ }
+
+ public static AggregateFunction avg(Function averageFunction) {
+ return new AverageFunction<>(averageFunction);
+ }
+
+ public static AggregateFunction max(Function function) {
+ return new MaxFunction<>(function);
+ }
+
+ public static AggregateFunction min(Function function) {
+ return new MinFunction<>(function);
+ }
+
+ /**
+ * This function represents AggregateFunction stub and
+ * can be used in aggregate select statement.
+ * Provides always the same value - constant argument.
+ *
+ * @param constant to provide value
+ * @param source element type
+ * @param result element type
+ * @return function stub for constant value
+ */
+ public static AggregateFunction constant(R constant) {
+ return new AggregateFunction(e -> constant) {
+ @Override
+ public R apply(Iterable elements) {
+ return constant;
+ }
+ };
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Conditions.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Conditions.java
new file mode 100644
index 0000000..fd56eea
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Conditions.java
@@ -0,0 +1,25 @@
+package library.api;
+
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * "where" statement conditions.
+ */
+public class Conditions {
+
+ public static Predicate like(Function source, String mask) {
+ Objects.requireNonNull(source);
+ Objects.requireNonNull(mask);
+ return t -> {
+ Objects.requireNonNull(t);
+ String actual = source.apply(t);
+ return actual.matches(mask.replace("%", ".*"));
+ };
+ }
+
+ public static Predicate not(Predicate original) {
+ return original.negate();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/OrderByConditions.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/OrderByConditions.java
new file mode 100644
index 0000000..7c72bec
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/OrderByConditions.java
@@ -0,0 +1,18 @@
+package library.api;
+
+import java.util.Comparator;
+import java.util.function.Function;
+
+/*
+ * "orderBy" helpers - comparators with asc and desc order based on lambda function
+ */
+public class OrderByConditions {
+
+ public static Comparator asc(Function condition) {
+ return (o1, o2) -> condition.apply(o1).compareTo(condition.apply(o2));
+ }
+
+ public static Comparator desc(Function condition) {
+ return (o1, o2) -> condition.apply(o2).compareTo(condition.apply(o1));
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Query.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Query.java
new file mode 100644
index 0000000..b406532
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Query.java
@@ -0,0 +1,29 @@
+package library.api;
+
+import library.core.exceptions.IncorrectQueryException;
+
+import java.util.Comparator;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Query builder class.
+ * @param result element type
+ * @param source element type
+ */
+public interface Query {
+
+ Query where(Predicate whereCondition);
+
+ Query groupBy(Function... groupByFunction);
+
+ Query having(Predicate havingCondition);
+
+ Query orderBy(Comparator... orderByComparators);
+
+ Query limit(int limit);
+
+ Query union(Query query);
+
+ Iterable execute() throws IncorrectQueryException;
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Source.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Source.java
new file mode 100644
index 0000000..e00ec44
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Source.java
@@ -0,0 +1,10 @@
+package library.api;
+
+import java.util.function.Function;
+
+public interface Source {
+
+ Query select(Class resultClass, Function... arguments);
+
+ Query selectDistinct(Class resultClass, Function... arguments);
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Sources.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Sources.java
new file mode 100644
index 0000000..13fa126
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/api/Sources.java
@@ -0,0 +1,21 @@
+package library.api;
+
+import library.core.QuerySource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * "from" statement sources.
+ */
+public class Sources {
+
+ public static List list(T... elements) {
+ return new ArrayList<>(Arrays.asList(elements));
+ }
+
+ public static Source from(List list) {
+ return new QuerySource<>(list);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryContext.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryContext.java
new file mode 100644
index 0000000..bf3a3f6
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryContext.java
@@ -0,0 +1,167 @@
+package library.core;
+
+import library.api.Query;
+import library.core.model.GroupingCondition;
+import library.core.model.SelectArgument;
+
+import java.util.*;
+import java.util.function.Predicate;
+
+/**
+ * The most important class, which contains all information about executed query.
+ *
+ * This context is piped from one operation to another and shared between all operations.
+ * It plays the role of shared storage on the every step.
+ *
+ * @param result type
+ * @param source element type
+ */
+public final class QueryContext {
+ /**
+ * Source elements of query ("from" statement).
+ */
+ private List source;
+ /**
+ * Result class in "select" statement.
+ */
+ private Class resultClass;
+ /**
+ * Select arguments in "select" statement.
+ */
+ private List> selectArguments;
+ /**
+ * Whether distinct select requested or not.
+ */
+ private boolean distinct;
+ /**
+ * Predicate in "where" statement.
+ */
+ private Predicate where;
+ /**
+ * Grouping conditions in "groupBy" statement.
+ */
+ private List> groupingConditions;
+ /**
+ * Predicate in "having" statement.
+ */
+ private Predicate having;
+ /**
+ * Ordered comparators in "orderBy" statement.
+ */
+ private Comparator[] orderBy;
+ /**
+ * Limit of result rows.
+ */
+ private int limit;
+ /**
+ * Joined queries by "union" statement.
+ */
+ private LinkedList> unions;
+ /**
+ * Result of query execution.
+ */
+ private LinkedList result;
+
+ public List getSource() {
+ return source;
+ }
+
+ public void setSource(List source1) {
+ this.source = source1;
+ }
+
+ public Class getResultClass() {
+ return resultClass;
+ }
+
+ public void setResultClass(Class resultClass1) {
+ this.resultClass = resultClass1;
+ }
+
+
+ public List> getSelectArguments() {
+ return selectArguments;
+ }
+
+ public void setSelectArguments(List> selectArguments1) {
+ this.selectArguments = selectArguments1;
+ }
+
+ public boolean isDistinct() {
+ return distinct;
+ }
+
+ public void setDistinct(boolean distinct1) {
+ this.distinct = distinct1;
+ }
+
+ public Predicate getWhere() {
+ return where;
+ }
+
+ public void setWhere(Predicate where1) {
+ this.where = where1;
+ }
+
+ public List> getGroupingConditions() {
+ return groupingConditions;
+ }
+
+ public void setGroupingConditions(List> groupingConditions1) {
+ this.groupingConditions = groupingConditions1;
+ }
+
+ public Predicate getHaving() {
+ return having;
+ }
+
+ public void setHaving(Predicate having1) {
+ this.having = having1;
+ }
+
+ public Comparator[] getOrderBy() {
+ return orderBy;
+ }
+
+ public void setOrderBy(Comparator[] orderBy1) {
+ this.orderBy = orderBy1;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public void setLimit(int limit1) {
+ this.limit = limit1;
+ }
+
+ public List> getUnions() {
+ return unions;
+ }
+
+ public void addUnion(Query union) {
+ if (unions == null) {
+ this.unions = new LinkedList<>();
+ }
+ unions.add(union);
+ }
+
+ public List getResult() {
+ return result;
+ }
+
+ public void setResult(List result1) {
+ this.result = new LinkedList<>(result1);
+ }
+
+ public void addResult(List result1) {
+ if (this.result == null) {
+ this.result = new LinkedList<>();
+ }
+ this.result.addAll(0, result1);
+ }
+
+ public boolean isGroupingQuery() {
+ return groupingConditions != null;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryImpl.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryImpl.java
new file mode 100644
index 0000000..d5a6130
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QueryImpl.java
@@ -0,0 +1,129 @@
+package library.core;
+
+import library.api.Query;
+import library.core.exceptions.IncorrectQueryException;
+import library.core.model.GroupingCondition;
+import library.core.operations.OperationType;
+import library.core.operations.QueryOperation;
+import library.core.operations.QueryOperationFactory;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Class implements all functionality of Query interface.
+ *
+ * The main idea is:
+ * for each query statement the arguments are set to QueryContext
+ * and the corresponding operation is included in QueryOperationsChain.
+ *
+ * When method "execute" invoked all operations are ordered by
+ * their priority and executed bit by bit by using common Query context.
+ *
+ * @param result element type
+ * @param source element type
+ */
+public final class QueryImpl implements Query {
+ private final QueryContext queryContext;
+ private final List queryOperationsChain;
+
+ public QueryImpl(QueryContext queryContext1) {
+ Objects.requireNonNull(queryContext1);
+ this.queryContext = queryContext1;
+ this.queryOperationsChain = new LinkedList<>();
+ }
+
+ @Override
+ public Query where(Predicate whereCondition) {
+ Objects.requireNonNull(whereCondition);
+ queryContext.setWhere(whereCondition);
+ queryOperationsChain.add(QueryOperationFactory.getOperationByType(OperationType.WHERE_OP));
+ return this;
+ }
+
+ @Override
+ public Query groupBy(Function... groupByConditions) {
+ if (groupByConditions.length == 0) {
+ throw new IllegalArgumentException("GroupBy statement without condition(-s)");
+ }
+ List> groupingConditions =
+ IntStream.range(0, groupByConditions.length)
+ .mapToObj(i -> new GroupingCondition<>(i, groupByConditions[i]))
+ .sorted((a1, a2) -> (Integer.compare(a1.getOrder(), a2.getOrder())))
+ .collect(Collectors.toList());
+ queryContext.setGroupingConditions(groupingConditions);
+ return this;
+ }
+
+ @Override
+ public Query having(Predicate havingCondition) {
+ Objects.requireNonNull(havingCondition);
+ queryContext.setHaving(havingCondition);
+ queryOperationsChain.add(QueryOperationFactory.getOperationByType(OperationType.HAVING_OP));
+ return this;
+ }
+
+ @Override
+ public Query orderBy(Comparator... orderByComparators) {
+ if (orderByComparators.length == 0) {
+ throw new IllegalArgumentException("OrderBy statement without comparator(-s)");
+ }
+ queryContext.setOrderBy(orderByComparators);
+ queryOperationsChain.add(QueryOperationFactory.getOperationByType(OperationType.ORDER_BY_OP));
+ return this;
+ }
+
+ @Override
+ public Query limit(int limit) {
+ if (limit <= 0) {
+ throw new IllegalArgumentException("limit must be positive");
+ }
+ queryContext.setLimit(limit);
+ queryOperationsChain.add(QueryOperationFactory.getOperationByType(OperationType.LIMIT_OP));
+ return this;
+ }
+
+ @Override
+ public Query union(Query query) {
+ Objects.requireNonNull(query);
+ queryContext.addUnion(query);
+ QueryOperation unionOp = QueryOperationFactory.getOperationByType(OperationType.UNION_OP);
+ if (!queryOperationsChain.contains(unionOp)) {
+ queryOperationsChain.add(unionOp);
+ }
+ return this;
+ }
+
+ @Override
+ public Iterable execute() throws IncorrectQueryException {
+ // here will be fun !
+
+ // add select operation to chain of query operations
+ QueryOperation selectOperation;
+ if (queryContext.isGroupingQuery()) {
+ selectOperation = QueryOperationFactory.getOperationByType(OperationType.GROUPING_SELECT_OP);
+ } else {
+ selectOperation = QueryOperationFactory.getOperationByType(OperationType.SIMPLE_SELECT_OP);
+ }
+
+ queryOperationsChain.add(selectOperation);
+ if (queryContext.isDistinct()) {
+ queryOperationsChain.add(QueryOperationFactory.getOperationByType(OperationType.DISTINCT_OP));
+ }
+
+ // order operations by their order number
+ Collections.sort(queryOperationsChain, QueryOperation.ORDER_COMPARATOR);
+ // validate every statement before execution
+ for (QueryOperation op : queryOperationsChain) {
+ op.validate(queryContext);
+ }
+ // execute every operation step by step
+ for (QueryOperation op : queryOperationsChain) {
+ op.execute(queryContext);
+ }
+ return queryContext.getResult();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QuerySource.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QuerySource.java
new file mode 100644
index 0000000..0cb195e
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/QuerySource.java
@@ -0,0 +1,69 @@
+package library.core;
+
+import library.api.Query;
+import library.api.Source;
+import library.core.model.SelectArgument;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Class represents the head point of query building.
+ *
+ * To start build query a user must create QuerySource object
+ * and invoke select[Distinct] method to get Query object.
+ *
+ * When select[Distinct] method invoked, it creates new
+ * QueryContext and Query object, fills it by "select" statement
+ * parameters and return Query instance to requester.
+ *
+ * @param source element type
+ */
+public final class QuerySource implements Source {
+ private List sourceList;
+
+ public QuerySource(List sourceList1) {
+ Objects.requireNonNull(sourceList1);
+ if (sourceList1.isEmpty()) {
+ throw new IllegalArgumentException("Source collection must not be null");
+ }
+ this.sourceList = sourceList1;
+ }
+
+ @Override
+ public Query select(Class resultClass, Function... arguments) {
+ QueryContext queryContext = buildQueryContext(resultClass, arguments);
+ queryContext.setDistinct(false);
+ return new QueryImpl<>(queryContext);
+ }
+
+ @Override
+ public Query selectDistinct(Class resultClass, Function... arguments) {
+ QueryContext queryContext = buildQueryContext(resultClass, arguments);
+ queryContext.setDistinct(true);
+ return new QueryImpl<>(queryContext);
+ }
+
+ private QueryContext buildQueryContext(Class resultClass, Function... arguments) {
+ Objects.requireNonNull(resultClass);
+ if (arguments.length == 0) {
+ throw new IllegalArgumentException("The list of select arguments must not be empty");
+ }
+ QueryContext queryContext = new QueryContext<>();
+ queryContext.setSource(sourceList);
+ queryContext.setResultClass(resultClass);
+
+ // create SelectArgument objects and store in sorted list
+ List> selectArguments =
+ IntStream.range(0, arguments.length)
+ .mapToObj(i -> new SelectArgument<>(i, arguments[i]))
+ .sorted((a1, a2) -> (Integer.compare(a1.getOrder(), a2.getOrder())))
+ .collect(Collectors.toList());
+ queryContext.setSelectArguments(Collections.unmodifiableList(selectArguments));
+ return queryContext;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/exceptions/IncorrectQueryException.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/exceptions/IncorrectQueryException.java
new file mode 100644
index 0000000..7a7c11c
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/exceptions/IncorrectQueryException.java
@@ -0,0 +1,11 @@
+package library.core.exceptions;
+
+/**
+ * Checked exception to notify about incorrect query syntax.
+ */
+public class IncorrectQueryException extends Exception {
+
+ public IncorrectQueryException(String description) {
+ super(description);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/GroupingCondition.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/GroupingCondition.java
new file mode 100644
index 0000000..05c24fe
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/GroupingCondition.java
@@ -0,0 +1,45 @@
+package library.core.model;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Class wraps and keeps full information about a grouping condition in query's groupBy statement.
+ * @param source element type
+ */
+public class GroupingCondition {
+ /**
+ * Order of this condition.
+ */
+ private int order;
+ /**
+ * Transformation function - labmda or aggregate function.
+ */
+ private Function function;
+ /**
+ * Calculated values of this function for each source element (rows).
+ * These values are used to create groups of elements (rows).
+ */
+ private Map groupedValues;
+
+ public GroupingCondition(int order1, Function function1) {
+ this.order = order1;
+ this.function = function1;
+ }
+
+ public final int getOrder() {
+ return order;
+ }
+
+ public final Function getFunction() {
+ return function;
+ }
+
+ public final Map getGroupedValues() {
+ return groupedValues;
+ }
+
+ public final void setGroupedValues(Map groupedValues1) {
+ this.groupedValues = groupedValues1;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/SelectArgument.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/SelectArgument.java
new file mode 100644
index 0000000..67d003e
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/SelectArgument.java
@@ -0,0 +1,83 @@
+package library.core.model;
+
+import library.core.model.aggregation.AggregateFunction;
+
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Class wraps and keeps full information about a select argument in query.
+ * @param source element type
+ */
+public class SelectArgument {
+ /**
+ * Order of this argument in query.
+ */
+ private int order;
+ /**
+ * Transformation function - lambda or aggregate function.
+ */
+ private Function function;
+ /**
+ * Argument value in case of argument is aggregate.
+ */
+ private Object aggregatedValue;
+ /**
+ * Argument values mapped to source elements (rows) in case of argument is not aggregate.
+ */
+ private Map values;
+ /**
+ * Class type of select argument.
+ * Used to find result class constructor with required signature.
+ */
+ private Class> valueClazz;
+
+ public SelectArgument(int order1, Function function1) {
+ this.order = order1;
+ this.function = function1;
+ }
+
+ public final int getOrder() {
+ return order;
+ }
+
+ public final Function getFunction() {
+ return function;
+ }
+
+ public final Object getAggregatedValue() {
+ return aggregatedValue;
+ }
+
+ public final void setAggregateValue(Object aggregatedValue1) {
+ this.aggregatedValue = aggregatedValue1;
+ }
+
+ public final Map getValues() {
+ return values;
+ }
+
+ public final void setValues(Map values1) {
+ this.values = values1;
+ }
+
+ public final boolean isAggregate() {
+ return (function instanceof AggregateFunction);
+ }
+
+ public final AggregateFunction getAggregateFunction() {
+ if (isAggregate()) {
+ return (AggregateFunction) function;
+ } else {
+ throw new IllegalStateException("This argument is not aggregated");
+ }
+ }
+
+ public final Class> getValueClazz() {
+ return valueClazz;
+ }
+
+ public final void setValueClazz(Class> valueClazz1) {
+ this.valueClazz = valueClazz1;
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/AggregateFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/AggregateFunction.java
new file mode 100644
index 0000000..3fa5f2e
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/AggregateFunction.java
@@ -0,0 +1,42 @@
+package library.core.model.aggregation;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Aggregate function.
+ * @param source element type
+ * @param result of single function transformation type
+ * @param aggregated result type
+ */
+public abstract class AggregateFunction implements Function {
+ private Function singleFunction;
+
+ public AggregateFunction(Function singleFunction1) {
+ Objects.requireNonNull(singleFunction1);
+ this.singleFunction = singleFunction1;
+ }
+
+ /**
+ * Because function is aggregate, this applies on many elements, e.g. collection.
+ * @param elements set of elements to be applied by this aggregate function
+ * @return aggregation result
+ */
+ public abstract A apply(Iterable elements);
+
+ @Override
+ public final R apply(S t) {
+ // delegate single element transformation
+ return singleFunction.apply(t);
+ }
+
+ @Override
+ public final Function compose(Function super V, ? extends S> before) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public final Function andThen(Function super R, ? extends V> after) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/AverageFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/AverageFunction.java
new file mode 100644
index 0000000..9f8eb99
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/AverageFunction.java
@@ -0,0 +1,24 @@
+package library.core.model.aggregation.impl;
+
+import library.core.model.aggregation.AggregateFunction;
+import library.core.utils.NumberUtils;
+
+import java.util.function.Function;
+
+public class AverageFunction extends AggregateFunction {
+
+ public AverageFunction(Function singleFunction) {
+ super(singleFunction);
+ }
+
+ @Override
+ public final R apply(Iterable elements) {
+ long count = 0L;
+ Number sum = 0;
+ for (S element : elements) {
+ count++;
+ sum = NumberUtils.SUM_NUMBERS.apply(apply(element), sum);
+ }
+ return (R) NumberUtils.DIV_NUMBERS.apply(sum, count);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/CountFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/CountFunction.java
new file mode 100644
index 0000000..064bf6d
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/CountFunction.java
@@ -0,0 +1,21 @@
+package library.core.model.aggregation.impl;
+
+import library.core.model.aggregation.AggregateFunction;
+
+import java.util.function.Function;
+import java.util.stream.StreamSupport;
+
+public class CountFunction extends AggregateFunction {
+
+ public CountFunction(Function singleFunction) {
+ super(singleFunction);
+ }
+
+ @Override
+ public final Long apply(Iterable elements) {
+ // start stream, filter only not null elements and count them
+ return StreamSupport.stream(elements.spliterator(), false).
+ filter(e -> e != null).
+ count();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/ExtremumFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/ExtremumFunction.java
new file mode 100644
index 0000000..75ad113
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/ExtremumFunction.java
@@ -0,0 +1,28 @@
+package library.core.model.aggregation.impl;
+
+import library.core.model.aggregation.AggregateFunction;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+public abstract class ExtremumFunction extends AggregateFunction {
+
+ public ExtremumFunction(Function singleFunction) {
+ super(singleFunction);
+ }
+
+ @Override
+ public final R apply(Iterable elements) {
+ // collect all values (results of transformation function)
+ List values = new ArrayList<>();
+ for (S element : elements) {
+ R value = this.apply(element);
+ values.add(value);
+ }
+ // search max or min value and return this one
+ return findExtremum(values);
+ }
+
+ protected abstract R findExtremum(List values);
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MaxFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MaxFunction.java
new file mode 100644
index 0000000..e527862
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MaxFunction.java
@@ -0,0 +1,18 @@
+package library.core.model.aggregation.impl;
+
+import library.core.utils.NumberUtils;
+
+import java.util.List;
+import java.util.function.Function;
+
+public class MaxFunction extends ExtremumFunction {
+
+ public MaxFunction(Function singleFunction) {
+ super(singleFunction);
+ }
+
+ @Override
+ protected final R findExtremum(List values) {
+ return values.stream().max(NumberUtils.COMPARE_NUMBERS::apply).get();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MinFunction.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MinFunction.java
new file mode 100644
index 0000000..1ee3ec6
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/model/aggregation/impl/MinFunction.java
@@ -0,0 +1,18 @@
+package library.core.model.aggregation.impl;
+
+import library.core.utils.NumberUtils;
+
+import java.util.List;
+import java.util.function.Function;
+
+public class MinFunction extends ExtremumFunction {
+
+ public MinFunction(Function singleFunction) {
+ super(singleFunction);
+ }
+
+ @Override
+ protected final R findExtremum(List values) {
+ return values.stream().min(NumberUtils.COMPARE_NUMBERS::apply).get();
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/OperationType.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/OperationType.java
new file mode 100644
index 0000000..fceffef
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/OperationType.java
@@ -0,0 +1,27 @@
+package library.core.operations;
+
+public enum OperationType {
+ WHERE_OP (1),
+ SIMPLE_SELECT_OP (2),
+ GROUPING_SELECT_OP (2),
+ HAVING_OP (3),
+ ORDER_BY_OP (4),
+ LIMIT_OP (5),
+ DISTINCT_OP (6),
+ UNION_OP (10);
+
+ /**
+ * Order of operation to be invoked in a query execution sequence.
+ */
+ private int order;
+
+ OperationType(int order1) {
+ this.order = order1;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperation.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperation.java
new file mode 100644
index 0000000..53f4469
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperation.java
@@ -0,0 +1,44 @@
+package library.core.operations;
+
+import library.core.QueryContext;
+import library.core.exceptions.IncorrectQueryException;
+
+import java.util.Comparator;
+
+/**
+ * Class represents one query operation, such as "select", "orderBy" and etc.
+ */
+public interface QueryOperation {
+
+ /**
+ * Comparator to compare QueryOperation instances by their order.
+ */
+ Comparator ORDER_COMPARATOR =
+ (o1, o2) -> Integer.compare(o1.getType().getOrder(), o2.getType().getOrder());
+
+ /**
+ * @return type of query operation
+ */
+ OperationType getType();
+
+ /**
+ * Validates query context before execution this operation.
+ * By default do nothing.
+ * @param queryContext execution query context
+ * @param result type
+ * @param source elements type
+ * @throws IncorrectQueryException in case of any incorrect in query found
+ */
+ default void validate(final QueryContext queryContext) throws IncorrectQueryException {
+ // by default do nothing
+ }
+
+ /**
+ * Main method which executes this query operation.
+ * @param queryContext execution query context
+ * @param result type
+ * @param source elements type
+ * @throws IncorrectQueryException in case of any incorrect in query found
+ */
+ void execute(final QueryContext queryContext) throws IncorrectQueryException;
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperationFactory.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperationFactory.java
new file mode 100644
index 0000000..ed5cd09
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/QueryOperationFactory.java
@@ -0,0 +1,32 @@
+package library.core.operations;
+
+import library.core.operations.impl.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Factory for QueryOperation instances.
+ */
+public class QueryOperationFactory {
+ private static final Map OPERATIONINSTANCES;
+ static {
+ OPERATIONINSTANCES = new HashMap<>();
+ OPERATIONINSTANCES.put(OperationType.WHERE_OP, new WhereOperation());
+ OPERATIONINSTANCES.put(OperationType.SIMPLE_SELECT_OP, new SimpleSelectOperation());
+ OPERATIONINSTANCES.put(OperationType.GROUPING_SELECT_OP, new GroupingSelectOperation());
+ OPERATIONINSTANCES.put(OperationType.HAVING_OP, new HavingOperation());
+ OPERATIONINSTANCES.put(OperationType.ORDER_BY_OP, new OrderByOperation());
+ OPERATIONINSTANCES.put(OperationType.LIMIT_OP, new LimitOperation());
+ OPERATIONINSTANCES.put(OperationType.DISTINCT_OP, new DistinctOperation());
+ OPERATIONINSTANCES.put(OperationType.UNION_OP, new UnionOperation());
+ }
+
+ /**
+ * @param type requested query operation type
+ * @return query operation instance by requested type
+ */
+ public static QueryOperation getOperationByType(OperationType type) {
+ return OPERATIONINSTANCES.get(type);
+ }
+}
diff --git a/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/impl/AbstractSelectOperation.java b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/impl/AbstractSelectOperation.java
new file mode 100644
index 0000000..b74013c
--- /dev/null
+++ b/projects/liza22/src/main/java/ru/mipt/diht/students/liza22/collectionql/src/main/java/library/core/operations/impl/AbstractSelectOperation.java
@@ -0,0 +1,157 @@
+package library.core.operations.impl;
+
+import library.core.model.SelectArgument;
+import library.core.model.aggregation.AggregateFunction;
+import library.core.operations.QueryOperation;
+import library.core.QueryContext;
+import library.core.exceptions.IncorrectQueryException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Abstract SelectOperation class which contains main functionality to
+ * do select statement.
+ * This class has a common part of SimpleSelect and GroupingSelect
+ * operations.
+ */
+public abstract class AbstractSelectOperation implements QueryOperation {
+
+ @Override
+ public abstract void validate(QueryContext queryContext) throws IncorrectQueryException;
+
+ protected final List doSelect(List sourceElements, QueryContext queryContext)
+ throws IncorrectQueryException {
+ List> selectArguments = queryContext.getSelectArguments();
+ List> notAggregateArgs = selectArguments.stream().
+ filter(a -> !a.isAggregate()).
+ collect(Collectors.toList());
+ List> aggregateArgs = selectArguments.stream().
+ filter(a -> a.isAggregate()).
+ collect(Collectors.toList());
+
+ // Step#1: calculate value for each select argument
+ calculateAggregateArguments(sourceElements, aggregateArgs);
+ calculateNotAggregateArguments(sourceElements, notAggregateArgs);
+
+ // Step#2: create result class instances by using calculated
+ // in step#1 values of select arguments
+ Class resultClass = queryContext.getResultClass();
+ Class>[] constructorArgTypes = getConstructorParameterTypes(selectArguments);
+ try {
+ Constructor resultClassConstructor = resultClass.getDeclaredConstructor(constructorArgTypes);
+ List results = null;
+ if (!notAggregateArgs.isEmpty() && aggregateArgs.isEmpty()) {
+ // simple select without aggregations and grouping
+ // just calculate all select arguments for each source element
+ results = calculateResult(sourceElements, resultClassConstructor, notAggregateArgs);
+ }
+ if (notAggregateArgs.isEmpty() && !aggregateArgs.isEmpty()) {
+ // exceptional case when all select arguments are aggregate functions
+ // and no any grouping, so result will be just one row
+ R result = calculateOnlyAggregatedResult(resultClassConstructor, aggregateArgs);
+ results = Collections.singletonList(result);
+ } else if (!notAggregateArgs.isEmpty() && !aggregateArgs.isEmpty()) {
+ // case when aggregate function in arguments exist
+ // and not-aggregate also - suppose that they are grouping arguments
+ // suppose that source elements belong to only one group
+ // thus we can calculate all results and get first row, because
+ // the other will be the same
+ R result = calculateMixedResult(sourceElements.get(0), resultClassConstructor, selectArguments);
+ results = Collections.singletonList(result);
+ }
+ return results;
+ } catch (NoSuchMethodException e) {
+ throw new IncorrectQueryException("Constructor of result class = " + resultClass
+ + " with arg types = " + Arrays.toString(constructorArgTypes)
+ + " not found");
+ }
+ }
+
+ protected final void calculateNotAggregateArguments(List sourceElements,
+ List> selectArguments) {
+ for (SelectArgument selectArgument : selectArguments) {
+ // apply function for each element of source and store to map [element <-> value]
+ Map elementValues = new HashMap<>(sourceElements.size());
+ for (S element : sourceElements) {
+ Object value = selectArgument.getFunction().apply(element);
+ elementValues.put(element, value);
+ if (selectArgument.getValueClazz() == null) {
+ selectArgument.setValueClazz(value.getClass());
+ }
+ }
+ selectArgument.setValues(elementValues);
+ }
+ }
+
+ protected final void calculateAggregateArguments(List sourceElements,
+ List> selectArguments) {
+ for (SelectArgument selectArgument : selectArguments) {
+ AggregateFunction aggregateFunction = selectArgument.getAggregateFunction();
+ Object aggregateValue = aggregateFunction.apply(sourceElements);
+ selectArgument.setAggregateValue(aggregateValue);
+ selectArgument.setValueClazz(aggregateValue.getClass());
+ }
+ }
+
+ protected final List calculateResult(List sourceElements, Constructor resultClassConstructor,
+ List> selectArguments) throws IncorrectQueryException {
+ try {
+ List results = new ArrayList<>(sourceElements.size());
+ for (S sourceElement : sourceElements) {
+ R resultElement = convertToResultObject(resultClassConstructor, sourceElement, selectArguments);
+ results.add(resultElement);
+ }
+ return results;
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
+ throw new IncorrectQueryException("Cannot instantiate result class instance: " + e.getMessage());
+ }
+ }
+
+ protected final R calculateOnlyAggregatedResult(Constructor resultClassConstructor,
+ List> selectArguments)
+ throws IncorrectQueryException {
+ try {
+ return convertToResultObject(resultClassConstructor, null, selectArguments);
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
+ throw new IncorrectQueryException("Cannot instantiate result class instance: " + e.getMessage());
+ }
+ }
+
+ protected final R calculateMixedResult(S sourceElement, Constructor resultClassConstructor,
+ List> selectArguments) throws IncorrectQueryException {
+ try {
+ return convertToResultObject(resultClassConstructor, sourceElement, selectArguments);
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
+ throw new IncorrectQueryException("Cannot instantiate result class instance: " + e.getMessage());
+ }
+ }
+
+ protected static Class>[] getConstructorParameterTypes(List> arguments) {
+ Class>[] types = new Class[arguments.size()];
+ // arguments are supposed to be sorted by order
+ for (int i = 0; i < arguments.size(); i++) {
+ types[i] = arguments.get(i).getValueClazz();
+ }
+ return types;
+ }
+
+ protected static R convertToResultObject(Constructor