diff --git a/ocksumoron/src/test/java/ru/fizteh/fivt/students/ocksumoron/helloworld/AppTest.java b/ocksumoron/src/test/java/ru/fizteh/fivt/students/ocksumoron/helloworld/AppTest.java deleted file mode 100644 index ce7c89b2..00000000 --- a/ocksumoron/src/test/java/ru/fizteh/fivt/students/ocksumoron/helloworld/AppTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package test.java.ru.fizteh.fivt.students.helloworld; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; - -/** - * Unit test for simple App. - */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ -// public AppTest( String testName ) -// { -// super( testName ); -// } - - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } - - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } -} diff --git a/preidman/pom.xml b/preidman/pom.xml index 7cd55159..b9b36869 100644 --- a/preidman/pom.xml +++ b/preidman/pom.xml @@ -16,11 +16,7 @@ - - junit - junit - 3.8.1 - + org.twitter4j twitter4j-stream @@ -31,5 +27,66 @@ jcommander 1.48 + + + junit + junit + 4.12 + test + + + + + com.google.maps + + google-maps-services + + 0.1.9 + + + + + + org.json + + json + + 20151123 + + + + + 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/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Aggregates.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Aggregates.java new file mode 100644 index 00000000..eb803c19 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Aggregates.java @@ -0,0 +1,102 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx; + +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Function; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + + +public class Aggregates { + + public interface Aggregate extends Function { + T forGroup(Set set); + } + + private static Collection getValues(Collection set, Function expression) { + Set values = new HashSet<>(); + for (C element : set) { + values.add(expression.apply(element)); + } + return values; + } + + public static > Aggregate max(Function expression) { + return new Aggregate() { + + @Override + public T apply(C element) { + return (T) expression.apply(element); + } + + @Override + public T forGroup(Set set) throws ClassCastException, NoSuchElementException { + return Collections.max(getValues(set, expression)); + } + }; + } + + public static > Aggregate min(Function expression) { + return new Aggregate() { + + @Override + public T apply(C element) { + return (T) expression.apply(element); + } + + @Override + public T forGroup(Set set) throws ClassCastException, NoSuchElementException { + return Collections.min(getValues(set, expression)); + } + }; + } + + public static > Aggregate count(Function expression) { + return new Aggregate() { + + @Override + public Long apply(C element) { + return Long.valueOf(1); + } + + @Override + public Long forGroup(Set set) { + return (long) set.size(); + } + }; + } + + public static > Aggregate avg(Function expression) { + return new Aggregate() { + + @Override + public T apply(C element) { + return expression.apply(element); + } + + @Override + public T forGroup(Set set) throws ClassCastException, NoSuchElementException { + if (set.isEmpty()) { + throw new NoSuchElementException("empt"); + } + T sample = expression.apply(set.iterator().next()); + if (sample instanceof Long || sample instanceof Integer || sample instanceof Short) { + long average = 0; + for (C element : set) { + average += (Long) (expression.apply(element)); + } + return (T) Long.valueOf(average / set.size()); + } else if (sample instanceof Float || sample instanceof Double) { + double average = 0; + for (C element : set) { + average += (Double) (expression.apply(element)); + } + return (T) Double.valueOf(average / set.size()); + } + throw new ClassCastException("wrong type"); + } + }; + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Conditions.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Conditions.java new file mode 100644 index 00000000..11148b58 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Conditions.java @@ -0,0 +1,22 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx; + +import java.util.function.Function; +import java.util.function.Predicate; + +public class Conditions { + + public static Predicate rlike(Function expression, String regexp) { + return new Predicate() { + + @Override + public boolean test(T element) { + return expression.apply(element).matches(regexp); + } + }; + } + + public static Predicate like(Function expression, String pattern) { + throw new UnsupportedOperationException(); + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/OrderByConditions.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/OrderByConditions.java new file mode 100644 index 00000000..2f83f653 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/OrderByConditions.java @@ -0,0 +1,17 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx; + +import java.util.Comparator; +import java.util.function.Function; + +public class OrderByConditions { + + + public static > Comparator asc(Function expression) { + return (o1, o2) -> expression.apply(o1).compareTo(expression.apply(o2)); + } + + public static > Comparator desc(Function expression) { + return (o1, o2) -> expression.apply(o2).compareTo(expression.apply(o1)); + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Sources.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Sources.java new file mode 100644 index 00000000..11a05ef5 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/Sources.java @@ -0,0 +1,52 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +public class Sources { + + @SafeVarargs + public static List list(T... items) { + return Arrays.asList(items); + } + + @SafeVarargs + public static Set set(T... items) { + Set result = new HashSet<>(); + for (T element : items) { + result.add(element); + } + return result; + } + + + public static Stream lines(InputStream inputStream) throws IOException, ClassCastException { + StringBuilder builder = new StringBuilder(); + byte[] data = new byte[100]; + int readed = inputStream.read(data); + while (readed > 0) { + builder.append(data); + readed = inputStream.read(data); + } + inputStream.close(); + String[] lines = builder.toString().split("[\n]"); + Stream.Builder stream = Stream.builder(); + for (String line : lines) { + stream.add((T) line); + } + return stream.build(); + } + + public static Stream lines(Path file) throws ClassCastException, IOException { + InputStream input = new FileInputStream(file.toFile()); + return lines(input); + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQueryExecuteException.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQueryExecuteException.java new file mode 100644 index 00000000..6192f447 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQueryExecuteException.java @@ -0,0 +1,8 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +public class CollectionQueryExecuteException extends Exception { + + CollectionQueryExecuteException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQuerySyntaxException.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQuerySyntaxException.java new file mode 100644 index 00000000..4578d389 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/CollectionQuerySyntaxException.java @@ -0,0 +1,12 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +public class CollectionQuerySyntaxException extends Exception { + + CollectionQuerySyntaxException(String message, Throwable cause) { + super(message, cause); + } + + CollectionQuerySyntaxException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FinalRow.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FinalRow.java new file mode 100644 index 00000000..8ff4631c --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FinalRow.java @@ -0,0 +1,31 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +import java.util.HashSet; +import java.util.Set; + +class FinalRow { + private R row; + private Set from; + + FinalRow(R newRow, T elementFrom) { + from = new HashSet(); + from.add(elementFrom); + row = newRow; + } + + public R get() { + return row; + } + + public Set getFrom() { + return from; + } + + public T getAnyFrom() { + return from.iterator().next(); + } + + public void updateRow(R newRow) { + row = newRow; + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FromStmt.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FromStmt.java new file mode 100644 index 00000000..8c094e08 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/FromStmt.java @@ -0,0 +1,157 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +import java.util.function.Function; +import java.util.stream.Stream; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +public final class FromStmt { + private Iterable base; + private ExecutedTable previousTable = null; + + private FromStmt() { } + + private static final class ExecutedTable { + private final Iterable outputTable; + private final Class outputClass; + + private ExecutedTable(Class clazz, Iterable table) { + outputTable = table; + outputClass = clazz; + } + } + + public static FromStmt from(Iterable iterable) { + FromStmt stmt = new FromStmt<>(); + stmt.base = iterable; + return stmt; + } + + public static FromStmt from(Stream stream) { + FromStmt stmt = new FromStmt<>(); + List list = new ArrayList<>(); + stream.forEach(s -> list.add(s)); + stmt.base = list; + return stmt; + } + + public static FromStmt from(WhereStmt subStmt) throws CollectionQueryExecuteException { + Iterable subQueryResult = subStmt.execute(); + return from(subQueryResult); + } + + void setPreviousPart(Class clazz, Iterable table) { + previousTable = new ExecutedTable(clazz, table); + } + + private SelectStmt innerSelect(Class clazz, Function[] s, boolean isDistinct) + throws CollectionQuerySyntaxException { + if (previousTable == null) { + return new SelectStmt(base, null, clazz, isDistinct, s); + } else { + if (!previousTable.outputClass.equals(clazz)) { + throw new CollectionQuerySyntaxException("parts of union has different types"); + } + return new SelectStmt(base, (Iterable) previousTable.outputTable, clazz, isDistinct, s); + } + } + + @SafeVarargs + public final SelectStmt select(Class clazz, Function... s) + throws CollectionQuerySyntaxException { + return innerSelect(clazz, s, false); + } + + @SafeVarargs + public final SelectStmt selectDistinct(Class clazz, Function... s) + throws CollectionQuerySyntaxException { + return innerSelect(clazz, s, true); + } + + public static final class Tuple { + private F firstPart; + private S secondPart; + + private Tuple(F first, S second) { + firstPart = first; + secondPart = second; + } + + public F first() { + return firstPart; + } + + public S second() { + return secondPart; + } + } + + + public final class JoinStmt { + private Iterable secondTable; + + private JoinStmt(Iterable tableOnJoin) { + secondTable = tableOnJoin; + } + + public FromStmt> on(Predicate> joiningPredicate) { + Collection> joinedCollection = new ArrayList<>(); + for (T first : base) { + for (S second : secondTable) { + Tuple joinedRow = new Tuple(first, second); + if (joiningPredicate.test(joinedRow)) { + joinedCollection.add(joinedRow); + } + } + } + FromStmt> stmt = new FromStmt<>(); + stmt.base = joinedCollection; + stmt.previousTable = previousTable; + return stmt; + } + + public FromStmt> on(Function leftKey, Function rightKey) { + Map, Set>> possibleValues = new HashMap<>(); + for (T row : base) { + R value = leftKey.apply(row); + if (value != null) { + if (!possibleValues.containsKey(value)) { + possibleValues.put(value, new Tuple<>(new HashSet<>(), new HashSet<>())); + } + possibleValues.get(value).firstPart.add(row); + } + } + for (S row : secondTable) { + R value = rightKey.apply(row); + if (value != null) { + if (!possibleValues.containsKey(value)) { + continue; + } + possibleValues.get(value).secondPart.add(row); + } + } + + Collection> joinedCollection = new ArrayList<>(); + + possibleValues.forEach((v, tuple) -> { + for (T firstRow : tuple.first()) { + for (S secondRow : tuple.second()) { + joinedCollection.add(new Tuple(firstRow, secondRow)); + } + } + }); + return from(joinedCollection); + } + } + + public JoinStmt join(Iterable secondTable) { + return new JoinStmt<>(secondTable); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/SelectStmt.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/SelectStmt.java new file mode 100644 index 00000000..6b6b79f9 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/SelectStmt.java @@ -0,0 +1,254 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import java.util.Comparator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + + +import ru.fizteh.fivt.students.preidman.CollectionsQLEx.Aggregates.Aggregate; + +public class SelectStmt { + + private Stream stream; + private Iterable baseCollection; + private Class outputClass; + private Iterable previousPart; + private Function[] convertFunctions; + + private Function>[] groupingFunctions; + private Predicate groupingCondition = null; + private Comparator finalComparator = null; + private int finalLimit = -1; + + private boolean isDistinct; + + @SafeVarargs + SelectStmt(Iterable newBaseCollection, Iterable previousTable, + Class clazz, boolean distinct, Function... s) { + baseCollection = newBaseCollection; + previousPart = previousTable; + stream = StreamSupport.stream(newBaseCollection.spliterator(), false); + outputClass = clazz; + convertFunctions = s; + isDistinct = distinct; + groupingFunctions = null; + } + + final Class getOutputClass() { + return outputClass; + } + + private static Comparator getCombinedComparator(Iterable> comparators) { + return new Comparator() { + @Override + public int compare(T first, T second) { + for (Comparator comparator : comparators) { + int result = comparator.compare(first, second); + if (result != 0) { + return result; + } + } + return 0; + } + }; + } + + public final WhereStmt where(Predicate predicate) { + stream = stream.filter(predicate); + return new WhereStmt<>(this); + } + + @SafeVarargs + public final void groupBy(Function>... expressions) throws CollectionQuerySyntaxException { + if (groupingFunctions != null) { + throw new CollectionQuerySyntaxException("group table in the query"); + } + groupingFunctions = expressions; + } + + public final void having(Predicate condition) throws CollectionQuerySyntaxException { + if (groupingCondition != null) { + throw new CollectionQuerySyntaxException("set grouping conditions"); + } + groupingCondition = condition; + } + + private class Applier implements Consumer { + private Collection> output; + private Constructor constructor; + + Applier(Collection> outputCollection, Constructor resultConstructor) { + output = outputCollection; + constructor = resultConstructor; + } + + @Override + public void accept(T element) { + Object[] parametrs = new Object[convertFunctions.length]; + for (int i = 0; i < convertFunctions.length; ++i) { + parametrs[i] = convertFunctions[i].apply(element); + } + try { + output.add(new FinalRow(constructor.newInstance(parametrs), element)); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("construct", e); + } + } + + } + + private Constructor getAskedConstructor() throws CollectionQueryExecuteException { + Class[] outputParametrsTypes = new Class[convertFunctions.length]; + for (int i = 0; i < convertFunctions.length; ++i) { + outputParametrsTypes[i] = convertFunctions[i].apply(baseCollection.iterator().next()).getClass(); + } + try { + return outputClass.getConstructor(outputParametrsTypes); + } catch (NoSuchMethodException | SecurityException e) { + throw new CollectionQueryExecuteException("Can not found constructor", e); + } + } + + private Collection> goodGroups(Collection> table) { + List> output = new ArrayList<>(); + for (FinalRow row : table) { + if (groupingCondition == null || groupingCondition.test(row.get())) { + output.add(row); + } + } + return output; + } + + private void aggregatingGroups(Collection> table) throws CollectionQueryExecuteException { + Constructor constructor = getAskedConstructor(); + Object[] parametrs = new Object[convertFunctions.length]; + for (FinalRow row : table) { + for (int i = 0; i < convertFunctions.length; ++i) { + if (convertFunctions[i] instanceof Aggregate) { + parametrs[i] = ((Aggregate) convertFunctions[i]).forGroup(row.getFrom()); + } else { + parametrs[i] = convertFunctions[i].apply(row.getAnyFrom()); + } + } + try { + row.updateRow(constructor.newInstance(parametrs)); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new CollectionQueryExecuteException("output class not instantiated", e); + } + } + } + + private Collection convertToFinal(Collection> table) { + List output = new ArrayList<>(); + if (previousPart != null) { + for (R row : previousPart) { + output.add(row); + } + } + for (FinalRow row : table) { + output.add(row.get()); + } + return output; + } + + public final Collection execute() throws CollectionQueryExecuteException { + Collection> output = null; + output = new ArrayList<>(); + + try { + stream.forEach(new Applier(output, getAskedConstructor())); + } catch (RuntimeException e) { + if (e.getMessage().equals("constructor")) { + throw new CollectionQueryExecuteException("constructor", e.getCause()); + } else { + throw e; + } + } + + if (groupingFunctions != null) { + List> preCalcSortedTable = new ArrayList<>(); + for (FinalRow element : output) { + preCalcSortedTable.add(element); + } + List>> resultComparators = new ArrayList<>(); + for (Function> function : groupingFunctions) { + resultComparators.add((r1, r2) -> { + Comparable result1 = function.apply(r1.getAnyFrom()); + return result1.compareTo(function.apply(r2.getAnyFrom())); + }); + } + Comparator> groupsComparator = getCombinedComparator(resultComparators); + preCalcSortedTable.sort(groupsComparator); + Collection> groupedTable = new ArrayList<>(); + + FinalRow currentGroup = null; + for (FinalRow row : preCalcSortedTable) { + if (currentGroup != null && groupsComparator.compare(row, currentGroup) == 0) { + currentGroup.getFrom().add(row.getAnyFrom()); + } else { + if (currentGroup != null) { + groupedTable.add(currentGroup); + } + currentGroup = row; + } + } + groupedTable.add(currentGroup); + + aggregatingGroups(output); + output = goodGroups(groupedTable); + } + Stream finalOutput = convertToFinal(output).stream(); + if (finalLimit >= 0) { + finalOutput = finalOutput.limit(finalLimit); + } + if (finalComparator != null) { + finalOutput = finalOutput.sorted(finalComparator); + } + if (isDistinct) { + finalOutput = finalOutput.distinct(); + } + return finalOutput.collect(() -> new ArrayList(), (l, e) -> l.add(e), (l1, l2) -> l1.addAll(l2)); + } + + public final void limit(int limit) throws CollectionQuerySyntaxException { + if (limit >= 0) { + throw new CollectionQuerySyntaxException("limitation"); + } + finalLimit = limit; + } + + @SafeVarargs + public final void orderBy(Comparator... comparators) throws CollectionQuerySyntaxException { + if (finalComparator != null) { + throw new CollectionQuerySyntaxException("ordering functions"); + } + finalComparator = getCombinedComparator(Arrays.asList(comparators)); + } + + public final Stream stream() throws CollectionQueryExecuteException { + return execute().stream(); + } + + final Stream currentStream() { + return stream; + } + + + public final UnionStmt union() throws CollectionQueryExecuteException { + return new UnionStmt(this); + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/UnionStmt.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/UnionStmt.java new file mode 100644 index 00000000..7cd0bd69 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/UnionStmt.java @@ -0,0 +1,19 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + +import java.util.Collection; + +public class UnionStmt { + private Collection previousPart; + private Class previousOutputClass; + + UnionStmt(SelectStmt previousStmt) throws CollectionQueryExecuteException { + previousOutputClass = previousStmt.getOutputClass(); + previousPart = previousStmt.execute(); + } + + public final FromStmt from(Iterable list) { + FromStmt stmt = FromStmt.from(list); + stmt.setPreviousPart(previousOutputClass, previousPart); + return stmt; + } +} diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/WhereStmt.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/WhereStmt.java new file mode 100644 index 00000000..864fc159 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/CollectionsQLEx/impl/WhereStmt.java @@ -0,0 +1,46 @@ +package ru.fizteh.fivt.students.preidman.CollectionsQLEx.impl; + + +import java.util.Comparator; +import java.util.function.Function; +import java.util.function.Predicate; + +public class WhereStmt { + + private SelectStmt baseStmt; + + WhereStmt(SelectStmt selectStmt) { + baseStmt = selectStmt; + } + + @SafeVarargs + public final WhereStmt groupBy(Function>... expressions) + throws CollectionQuerySyntaxException { + baseStmt.groupBy(expressions); + return this; + } + + @SafeVarargs + public final WhereStmt orderBy(Comparator... comparators) throws CollectionQuerySyntaxException { + baseStmt.orderBy(comparators); + return this; + } + + public final WhereStmt having(Predicate condition) throws CollectionQuerySyntaxException { + baseStmt.having(condition); + return this; + } + + public final WhereStmt limit(int amount) throws CollectionQuerySyntaxException { + baseStmt.limit(amount); + return this; + } + + public final Iterable execute() throws CollectionQueryExecuteException { + return baseStmt.execute(); + } + + public final UnionStmt union() throws CollectionQueryExecuteException { + return baseStmt.union(); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseService.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseService.java new file mode 100644 index 00000000..147a3986 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseService.java @@ -0,0 +1,148 @@ +package ru.fizteh.fivt.students.preidman.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; + +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; + }); + } + } \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseServiceUtils.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseServiceUtils.java new file mode 100644 index 00000000..7e805d58 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/DatabaseServiceUtils.java @@ -0,0 +1,156 @@ +package ru.fizteh.fivt.students.preidman.MiniORM; +import javafx.util.Pair; + +import ru.fizteh.fivt.students.preidman.MiniORM.DatabaseService.Column; +import ru.fizteh.fivt.students.preidman.MiniORM.DatabaseService.PrimaryKey; +import ru.fizteh.fivt.students.preidman.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; + +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; + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/StatementConstructor.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/StatementConstructor.java new file mode 100644 index 00000000..92183aa5 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/StatementConstructor.java @@ -0,0 +1,135 @@ +package ru.fizteh.fivt.students.preidman.MiniORM; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.joining; + +public class StatementConstructor { + + private String name = null; + + private List columnList = null; + private TColumn primaryKey = null; + + private Class itemsClass; + + public StatementConstructor(String newName, + List newColumnList, + TColumn newPrimaryKey, + Class newItemsClass) { + this.name = newName; + 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(name) + .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(name) + .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(name) + .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(); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/TColumn.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/TColumn.java new file mode 100644 index 00000000..4b3fed6f --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/MiniORM/TColumn.java @@ -0,0 +1,54 @@ +package ru.fizteh.fivt.students.preidman.MiniORM; + +import java.lang.reflect.Field; + +public class TColumn { + + public TColumn(String newName, String newType, Field newField) { + + this.name = newName; + this.type = newType; + this.field = newField; + } + + @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); + } + + 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; +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/BlockingQueue.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/BlockingQueue.java new file mode 100644 index 00000000..7b534a4d --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/BlockingQueue.java @@ -0,0 +1,124 @@ +package ru.fizteh.fivt.students.preidman.threads; + +import java.util.concurrent.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.locks.*; +import java.util.ArrayList; + + +public class BlockingQueue { + + private final int volume; + private Lock l; + + private Condition takeCond; + private Condition putCond; + + private Queue queueBl; + + public BlockingQueue(int maxSize) { + + volume = maxSize; + queueBl = new LinkedList<>(); + + l = new ReentrantLock(); + + takeCond = l.newCondition(); + putCond = l.newCondition(); + + } + + public final void offer(List newqueueBl) throws InterruptedException, TimeoutException { + + offer(newqueueBl, 0); + + } + + public final void offer(List newqueueBl, long t) throws InterruptedException, TimeoutException { + + l.lock(); + + try { + + long time = TimeUnit.MILLISECONDS.toNanos(t); + + boolean isAdd = true; + + + while ((t == 0 || time > 0) && queueBl.size() + newqueueBl.size() > volume) { + + if (t == 0) putCond.await(); + else { + + time = putCond.awaitNanos(time); + if (time < 0) isAdd = false; + + } + + } + + + if (isAdd) { + + queueBl.addAll(newqueueBl); + + if (newqueueBl.size() > 0) takeCond.signal(); + + } else throw new TimeoutException(); + + } finally { + + l.unlock(); + + } + } + + + public final List take(int count) throws InterruptedException { + + return take(count, 0); + + } + + public final List take(int kol, long t) throws InterruptedException { + + l.lock(); + + try { + + long time = TimeUnit.MILLISECONDS.toNanos(t); + + while (queueBl.size() < kol && (t == 0 || time > 0)) { + + if (t == 0) takeCond.await(); + else { + + time = takeCond.awaitNanos(time); + if (time < 0) return null; + + } + + } + + List tmp = new ArrayList<>(); + + for (int i = 0; i < kol; ++i) { + + tmp.add(queueBl.poll()); + + } + + if (kol > 0) putCond.signal(); + + return tmp; + + } finally { + + l.unlock(); + + } + } + +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/NumerableThread.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/NumerableThread.java new file mode 100644 index 00000000..6123247f --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/NumerableThread.java @@ -0,0 +1,63 @@ +package ru.fizteh.fivt.students.preidman.threads; + + +public class NumerableThread { + + public static void main(String[] args) { + + new NumerableThread(Integer.valueOf(args[0])); + + } + + + + private final int kol; + + private Integer par; + + + + public class Numerator extends Thread { + + private int num; + + public Numerator(int x) { + num = x; + } + + @Override + public final void run() { + + while (true) { + + synchronized (par) { + + if (par.equals(num - 1)) { + + System.out.println("Thread-" + num); + par++; + + if (par.equals(kol)) par = 0; + + } + } + + } + } + } + + public NumerableThread(int numberOfThreads) { + + kol = numberOfThreads; + + par = 0; + + for (int i = 0; i < kol; ++i) { + + Thread count = new Numerator(i + 1); + count.start(); + + } + } + +} diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/RollingThread.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/RollingThread.java new file mode 100644 index 00000000..33c10e4e --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/threads/RollingThread.java @@ -0,0 +1,170 @@ +package ru.fizteh.fivt.students.preidman.threads; + +import java.util.concurrent.Semaphore; +import java.util.Random; + +public class RollingThread implements Runnable { + + + public static void main(String[] args) { + + new RollingThread (Integer.valueOf(args[0])).run(); + + } + + + + private int countOfReady; + + private int numberOfThreads; + private Responder[] students; + private Integer numberOfYes; + + private Semaphore turn; + private Boolean end; + + + + + + public RollingThread (int x) { + + numberOfYes = 0; + end = false; + numberOfThreads = x; + + turn = new Semaphore(numberOfThreads); + + try { + + turn.acquire(numberOfThreads); + + } catch (InterruptedException e) { + + e.printStackTrace(); + System.exit(-1); + + } + + + students = new Responder[numberOfThreads]; + + for (int i = 0; i < numberOfThreads; ++i) { + + students[i] = new Responder(); + + try { + + students[i].answPls.acquire(); + + } catch (InterruptedException e) { + + e.printStackTrace(); + System.exit(-1); + + } + } + } + + + + @Override + public final void run() { + + boolean ready = false; + + for (int i = 0; i < numberOfThreads; ++i) { + + students[i].start(); + + } + + while (!ready) { + + System.out.println("Are you ready?"); + goAnswer(); + + try { + + turn.acquire(numberOfThreads); + synchronized (numberOfYes) { + + if (countOfReady == numberOfThreads) { + + ready = true; + end = true; + goAnswer(); + + } + + numberOfYes = 0; + countOfReady = 0; + } + + } catch (InterruptedException e) { + + e.printStackTrace(); + + } + } + } + + + + private void goAnswer() { + + for (int i = 0; i < numberOfThreads; ++i) { + + students[i].answPls.release(); + + } + + } + + public class Responder extends Thread { + + private Random maybe = new Random(); + + private Semaphore answPls = new Semaphore(1); + + @Override + public final void run() { + + while (true) { + + try { + + answPls.acquire(); + + synchronized (end) { + + if (end) return; + + } + + boolean yes = true; + + if (maybe.nextInt(10) == 0) yes = false; + + if (!yes) System.out.println(getName() + ": No"); + else System.out.println(getName() + ": Yes"); + + + synchronized (numberOfYes) { + + numberOfYes++; + if (yes) countOfReady++; + + } + + turn.release(); + + } catch (InterruptedException e) { + + System.err.println(e.getMessage()); + + } + } + } + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Constants.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Constants.java new file mode 100644 index 00000000..5dc8009d --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Constants.java @@ -0,0 +1,10 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +public final class Constants { + public static final int DEFAULT_LIMIT = 1000; + public static final int SLEEP_TIME = 1000; + public static final int MAX_COUNT_OF_TRIES = 3; + public static final int RADIUS_OF_THE_EARTH = 6371; + public static final String URL_FOR_IP_NAVIGATION = "http://ipinfo.io/json"; + public static final String URL_FOR_GOOGLE_MAPS = "http://maps.googleapis.com/maps/api/geocode/json"; +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/DataParser.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/DataParser.java new file mode 100644 index 00000000..0fc91f7e --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/DataParser.java @@ -0,0 +1,28 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.time.*; + +public class DataParser { + + public String getTime(Date date) { + LocalDateTime tweetTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + LocalDateTime currentTime = LocalDateTime.now(); + WordMatcher matcher = new WordMatcher(); + if (tweetTime.isAfter(currentTime.minusMinutes(2))) { + return "только что "; + } else if (tweetTime.isAfter(currentTime.minusHours(1))) { + int minutes = (int) ChronoUnit.MINUTES.between(tweetTime, currentTime); + return minutes + " " + matcher.wordForMinutes(minutes) + " назад "; + } else if (currentTime.toLocalDate().isEqual(tweetTime.toLocalDate())) { + int hours = (int) ChronoUnit.HOURS.between(tweetTime, currentTime); + return hours + " " + matcher.wordForHours(hours) + " назад "; + } else if (tweetTime.toLocalDate().isEqual(currentTime.minusDays(1).toLocalDate())) { + return "вчера "; + } else { + int days = (int) ChronoUnit.DAYS.between(tweetTime, currentTime); + return days + " " + matcher.wordForDays(days) + " назад "; + } + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/GeoNavigator.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/GeoNavigator.java new file mode 100644 index 00000000..712c1ddd --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/GeoNavigator.java @@ -0,0 +1,37 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import org.json.JSONObject; +import java.io.IOException; +import java.net.URL; +import java.util.Scanner; + +public class GeoNavigator { + + public String searchByIP(URL url) throws IOException { + Scanner scanner = new Scanner(url.openStream()); + String response = scanner.useDelimiter("\\Z").next(); + scanner.close(); + JSONObject json = new JSONObject(response); + return json.getString("city"); + } + + public Location searchByAddress(URL url) throws IOException { + Scanner scanner = new Scanner(url.openStream()); + String response = scanner.useDelimiter("\\Z").next(); + scanner.close(); + JSONObject json = new JSONObject(response); + JSONObject geometry = json.getJSONArray("results").getJSONObject(0).getJSONObject("geometry"); + JSONObject bounds = geometry.getJSONObject("bounds"); + JSONObject location = geometry.getJSONObject("location"); + JSONObject northeast = bounds.getJSONObject("northeast"); + JSONObject southwest = bounds.getJSONObject("southwest"); + double lat = location.getDouble("lat"); + double lng = location.getDouble("lng"); + double northeastLat = northeast.getDouble("lat"); + double northeastLng = northeast.getDouble("lng"); + double southwestLat = southwest.getDouble("lat"); + double southwestLng = southwest.getDouble("lng"); + return new Location(lat, lng, northeastLat, northeastLng, + southwestLat, southwestLng); + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Location.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Location.java new file mode 100644 index 00000000..159e4679 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/Location.java @@ -0,0 +1,68 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.RADIUS_OF_THE_EARTH; + +public class Location { + private double latitude; + private double longitude; + private double northeastLat; + private double northeastLng; + private double southwestLat; + private double southwestLng; + + Location(double latitude, double longitude, + double northeastLat, double northeastLng, + double southwestLat, double southwestLng) { + this.latitude = latitude; + this.longitude = longitude; + this.northeastLat = northeastLat; + this.northeastLng = northeastLng; + this.southwestLat = southwestLat; + this.southwestLng = southwestLng; + } + + public double getLatitude() { + return latitude; + } + + public double getLongitude() { + return longitude; + } + + public double getNortheastLat() { + return northeastLat; + } + + public double getNortheastLng() { + return northeastLng; + } + + public double getSouthwestLat() { + return southwestLat; + } + + public double getSouthwestLng() { + return southwestLng; + } + + public boolean isInBounds(double lat, double lng) { + if (lat >= Math.min(southwestLat, northeastLat) + && lng > Math.min(southwestLng, northeastLng) + && lat <= Math.max(southwestLat, northeastLat) + && lng <= Math.max(southwestLng, northeastLng)) { + return true; + } + return false; + } + + public double getRadius() { + Double latDistance = Math.toRadians(southwestLat - northeastLat); + Double lonDistance = Math.toRadians(southwestLng - northeastLng); + Double xCoordinte = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + + Math.cos(Math.toRadians(southwestLat)) * Math.cos(Math.toRadians(northeastLat)) + * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); + Double angle = 2 * Math.atan2(Math.sqrt(xCoordinte), Math.sqrt(1 - xCoordinte)); + double radius = RADIUS_OF_THE_EARTH * angle / 2; + return radius; + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/ParameterParser.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/ParameterParser.java new file mode 100644 index 00000000..45756e87 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/ParameterParser.java @@ -0,0 +1,58 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import com.beust.jcommander.Parameter; +import java.util.ArrayList; +import java.util.List; + +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.DEFAULT_LIMIT; + + +public class ParameterParser { + @Parameter(names = { "-q", "--query" }, variableArity = true, + description = "Ключевые слова для поиска.") + private List queries = new ArrayList<>(); + + @Parameter(names = { "-p", "--place" }, + description = "Искать только по заданному региону или по ip, если значение равно nearby.") + private String place = null; + + @Parameter(names = { "-s", "--stream" }, + description = "Выводить результаты поиска равномерно и непрерывно с задержкой в 1 секунду.") + private boolean stream = false; + + @Parameter(names = "--hideRetweets", + description = "Не выводить ретвиты.") + private boolean hideRetweets = false; + + @Parameter(names = { "-l", "--limit" }, + description = "Выводить только заданное число твитов. Не применимо для --stream режима.") + private int limit = DEFAULT_LIMIT; + + @Parameter(names = { "-h", "--help" }, + description = "Справка.") + private boolean help = false; + + public List getQuery() { + return queries; + } + + public String getPlace() { + return place; + } + + public boolean isStream() { + return stream; + } + + public boolean hideRetweets() { + return hideRetweets; + } + + public int getLimit() { + return limit; + } + + public boolean isHelp() { + return help; + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TweetParser.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TweetParser.java new file mode 100644 index 00000000..633fabf9 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TweetParser.java @@ -0,0 +1,26 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import twitter4j.Status; + + +public class TweetParser { + public String getTweet(Status status) { + String result = "@" + status.getUser().getScreenName() + ": "; + String text = status.getText(); + if (status.isRetweet()) { + int index = text.indexOf("@"); + text = text.substring(index, text.length()); + index = text.indexOf(" "); + String retweetedName = text.substring(0, index - 1); + text = text.substring(index + 1, text.length()); + result += "ретвитнул " + retweetedName + ": "; + } + result += text; + if (!status.isRetweet() && status.getRetweetCount() > 0) { + WordMatcher matcher = new WordMatcher(); + result += " (" + status.getRetweetCount() + " " + + matcher.wordForRetweet(status.getRetweetCount()) + ")"; + } + return result; + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterService.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterService.java new file mode 100644 index 00000000..2604d295 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterService.java @@ -0,0 +1,152 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import twitter4j.*; + +import java.io.IOException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.SLEEP_TIME; +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.URL_FOR_GOOGLE_MAPS; +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.URL_FOR_IP_NAVIGATION; + +public class TwitterService { + private final Twitter twitter; + private final TwitterStream twitterStream; + + public TwitterService(Twitter twitter, TwitterStream twitterStream) { + this.twitter = twitter; + this.twitterStream = twitterStream; + } + + public List searchNonStream(ParameterParser parser) throws IOException, TwitterException { + String queryString = ""; + if (!parser.getQuery().isEmpty()) { + queryString = String.join(" ", parser.getQuery()); + } + Query query = new Query(); + if (!queryString.isEmpty()) { + query.setQuery(queryString); + } + Location searchBounds = null; + GeoNavigator navigator = new GeoNavigator(); + String address = parser.getPlace(); + if (address != null && !address.isEmpty()) { + if (address == "nearby") { + address = navigator.searchByIP(new URL(URL_FOR_IP_NAVIGATION)); + } + String url = URL_FOR_GOOGLE_MAPS + '?' + "address=" + + URLEncoder.encode(address, "utf-8") + "&sensor=false"; + searchBounds = navigator.searchByAddress(new URL(url)); + if (searchBounds != null) { + query.setGeoCode(new GeoLocation(searchBounds.getLatitude(), searchBounds.getLongitude()), + searchBounds.getRadius(), Query.Unit.km); + } + } + query.setCount(parser.getLimit()); + QueryResult result = null; + int tweetCount = 0; + List statuses = new ArrayList<>(); + while (tweetCount < parser.getLimit()) { + result = twitter.search(query); + List tweets = result.getTweets(); + for (Status tweet : tweets) { + if (parser.hideRetweets() && tweet.isRetweet()) { + continue; + } + tweetCount++; + statuses.add(tweet); + if (tweetCount == parser.getLimit()) { + break; + } + } + if (!result.hasNext()) { + break; + } + query = result.nextQuery(); + } + return statuses; + } + + public void searchStream(ParameterParser parser, Consumer listener) throws IOException { + String[] queries = parser.getQuery().toArray(new String[parser.getQuery().size()]); + Location searchBounds = null; + GeoNavigator navigator = new GeoNavigator(); + String address = parser.getPlace(); + if (address != null && !address.isEmpty()) { + if (address == "nearby") { + address = navigator.searchByIP(new URL(URL_FOR_IP_NAVIGATION)); + } + String url = URL_FOR_GOOGLE_MAPS + '?' + "address=" + + URLEncoder.encode(address, "utf-8") + "&sensor=false"; + searchBounds = navigator.searchByAddress(new URL(url)); + } + final Location bounds = searchBounds; + TweetParser tweetParser = new TweetParser(); + StatusListener statusListener = new StatusAdapter() { + @Override + public void onStatus(Status status) { + if (parser.hideRetweets() && status.isRetweet()) { + return; + } + if (parser.getQuery().size() > 0 && parser.getPlace() != null && !parser.getPlace().isEmpty()) { + if (status.getGeoLocation() == null) { + return; + } + double latitude = status.getGeoLocation().getLatitude(); + double longitude = status.getGeoLocation().getLongitude(); + if (bounds == null || !bounds.isInBounds(latitude, longitude)) { + return; + } + } + listener.accept(status); + } + }; + twitterStream.addListener(statusListener); + FilterQuery query = null; + if (queries.length > 0) { + query = new FilterQuery().track(queries); + } else if (searchBounds != null) { + double[][] boundBox = {{searchBounds.getSouthwestLat(), searchBounds.getSouthwestLng()}, + {searchBounds.getNortheastLat(), searchBounds.getNortheastLng()}}; + query = new FilterQuery().locations(boundBox); + } + if (query == null) { + twitterStream.sample(); + } else { + twitterStream.filter(query); + } + } + + public void printTweets(ParameterParser parser) throws IOException, TwitterException { + List tweetList = new ArrayList<>(); + DataParser dataParser = new DataParser(); + TweetParser tweetParser = new TweetParser(); + if (parser.isStream()) { + searchStream(parser, new Consumer() { + @Override + public void accept(Status status) { + System.out.println(tweetParser.getTweet(status)); + try { + Thread.sleep(SLEEP_TIME); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + }); + } else { + tweetList = searchNonStream(parser); + if (tweetList.isEmpty()) { + System.out.println("No tweets were found."); + } else { + for (Status status : tweetList) { + System.out.print(dataParser.getTime(status.getCreatedAt())); + System.out.println(tweetParser.getTweet(status)); + } + } + } + } +} \ No newline at end of file diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterStreamer.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterStreamer.java new file mode 100644 index 00000000..5d333278 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/TwitterStreamer.java @@ -0,0 +1,38 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + +import com.beust.jcommander.JCommander; +import twitter4j.*; +import java.io.IOException; + +import static ru.fizteh.fivt.students.preidman.twitterstream.Constants.MAX_COUNT_OF_TRIES; + + +public class TwitterStreamer { + + public static void printHelp(JCommander jCommander) { + jCommander.setProgramName("TwitterParser"); + jCommander.usage(); + } + + public static void main(String[] args) { + ParameterParser parser = new ParameterParser(); + JCommander jCommander = new JCommander(parser); + jCommander.parse(args); + Twitter twitter = new TwitterFactory().getInstance(); + TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); + TwitterService twitterService = new TwitterService(twitter, twitterStream); + int triesCounter = 0; + while (triesCounter < MAX_COUNT_OF_TRIES) { + try { + if (parser.isHelp()) { + printHelp(jCommander); + } else { + twitterService.printTweets(parser); + } + break; + } catch (IOException | TwitterException exception) { + triesCounter++; + } + } + } +} diff --git a/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/WordMatcher.java b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/WordMatcher.java new file mode 100644 index 00000000..f468b365 --- /dev/null +++ b/preidman/src/main/java/ru/fizteh/fivt/students/preidman/twitterstream/WordMatcher.java @@ -0,0 +1,49 @@ +package ru.fizteh.fivt.students.preidman.twitterstream; + + +public class WordMatcher { + + public String wordForRetweet(int count) { + if (count % 100 > 10 && count % 100 < 20) { + return "ретвитов"; + } + switch (count % 10) { + case 1 : return "ретвит"; + case 2: case 3: case 4 : return "ретвита"; + default: return "ретвитов"; + } + } + + public String wordForMinutes(int minutes) { + if (minutes % 100 > 10 && minutes % 100 < 20) { + return "минут"; + } + switch (minutes % 10) { + case 1 : return "минута"; + case 2: case 3: case 4 : return "минуты"; + default: return "минут"; + } + } + + public String wordForHours(int hours) { + if (hours % 100 > 10 && hours % 100 < 20) { + return "часов"; + } + switch (hours % 10) { + case 1 : return "час"; + case 2: case 3: case 4 : return "часа"; + default: return "часов"; + } + } + + public String wordForDays(int days) { + if (days % 100 > 10 && days % 100 < 20) { + return "дней"; + } + switch (days % 10) { + case 1 : return "день"; + case 2: case 3: case 4 : return "дня"; + default: return "дней"; + } + } +} \ No newline at end of file