diff --git a/.gitignore b/.gitignore index 216927c6..fec2ba57 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ workbench.xmi .settings .checkstyle twitter4j.properties +riskingh/twitter4j.properties +riskingh/src/main/resources/googleMaps.properties diff --git a/pom.xml b/pom.xml index a2fdfbd2..d48c78d8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + + 4.0.0 ru.fizteh.fivt.students @@ -19,7 +19,7 @@ UTF-8 - + egiby akormushin @@ -46,6 +46,7 @@ ladyae nikitarykov duha666 + riskingh diff --git a/riskingh/pom.xml b/riskingh/pom.xml new file mode 100644 index 00000000..95663eb1 --- /dev/null +++ b/riskingh/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + ru.fizteh.fivt.students + parent + 1.0-SNAPSHOT + + ru.fizteh.fivt.students + riskingh + 1.0-SNAPSHOT + riskingh + http://maven.apache.org + + UTF-8 + + + + junit + junit + 4.12 + test + + + org.twitter4j + twitter4j-core + [4.0,) + + + org.twitter4j + twitter4j-stream + 4.0.4 + + + com.beust + jcommander + 1.48 + + + org.json + json + 20151123 + + + com.google.guava + guava + 14.0 + + + com.h2database + h2 + 1.4.190 + + + diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Column.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Column.java new file mode 100644 index 00000000..7afac64a --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Column.java @@ -0,0 +1,9 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + String name() default ""; +} diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseService.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseService.java new file mode 100644 index 00000000..47f85775 --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseService.java @@ -0,0 +1,266 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import org.h2.jdbcx.JdbcConnectionPool; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.sql.*; +import java.util.*; + +import static ru.fizteh.fivt.students.riskingh.MiniORM.NameResolver.convertCamelToUnderscore; +import static ru.fizteh.fivt.students.riskingh.MiniORM.NameResolver.isGood; + +public class DatabaseService implements Closeable { + private final String connectionName; + private final String username; + private final String password; + + private Class clazz; + private JdbcConnectionPool pool; + private String tableName; + private Field[] fields; + private int pkIndex = -1; + + String getColumnName(Field f) { + String name = f.getAnnotation(Column.class).name(); + if (name.equals("")) { + return convertCamelToUnderscore(f.getName()); + } + return name; + } + + void init() throws IllegalArgumentException, IOException { + if (!clazz.isAnnotationPresent(Table.class)) { + throw new IllegalArgumentException("no @Table annotation"); + } + + tableName = clazz.getAnnotation(Table.class).name(); + if (tableName.equals("")) { + tableName = convertCamelToUnderscore(clazz.getSimpleName()); + } + + if (!isGood(tableName)) { + throw new IllegalArgumentException("Bad table name"); + } + + Set names = new HashSet<>(); + List fieldsList = new ArrayList<>(); + for (Field f: clazz.getDeclaredFields()) { + if (f.isAnnotationPresent(Column.class)) { + String name = getColumnName(f); + names.add(name); + if (!isGood(name)) { + throw new IllegalArgumentException("Bad column name"); + } + + f.setAccessible(true); + fieldsList.add(f); + if (f.isAnnotationPresent(PrimaryKey.class)) { + if (pkIndex == -1) { + pkIndex = fieldsList.size() - 1; + } else { + throw new + IllegalArgumentException("Several @PrimaryKey"); + } + } + } else if (f.isAnnotationPresent(PrimaryKey.class)) { + throw new + IllegalArgumentException("@PrimaryKey without @Column"); + } + } + + if (names.size() != fieldsList.size()) { + throw new IllegalArgumentException("Duplicate columns"); + } + + fields = new Field[fieldsList.size()]; + fields = fieldsList.toArray(fields); + + try { + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("No H2 driver found"); + } + + pool = JdbcConnectionPool.create(connectionName, username, password); + } + + DatabaseService(Class newClazz, String properties) throws IOException { + Properties credits = new Properties(); + try (InputStream inputStream = this.getClass().getResourceAsStream(properties)) { + credits.load(inputStream); + } + connectionName = credits.getProperty("connection_name"); + username = credits.getProperty("username"); + password = credits.getProperty("password"); + + clazz = newClazz; + init(); + } + + DatabaseService(Class newClazz) throws IOException { + this(newClazz, "/h2test.properties"); + } + + void createTable() throws SQLException { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append("("); + for (int i = 0; i < fields.length; ++i) { + if (i != 0) { + queryBuilder.append(", "); + } + queryBuilder.append(getColumnName(fields[i])).append(" ") + .append(H2StringsResolver.resolve(fields[i].getType())); + if (i == pkIndex) { + queryBuilder.append(" PRIMARY KEY"); + } + } + queryBuilder.append(")"); + try (Connection conn = pool.getConnection()) { + conn.createStatement().execute(queryBuilder.toString()); + } + + } + + void dropTable() throws SQLException { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("DROP TABLE IF EXISTS ").append(tableName); + try (Connection conn = pool.getConnection()) { + conn.createStatement().execute(queryBuilder.toString()); + } + } + + public void insert(T record) throws SQLException, IllegalAccessException { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("INSERT INTO ").append(tableName).append(" ("); + for (int i = 0; i < fields.length; ++i) { + if (i != 0) { + queryBuilder.append(", "); + } + queryBuilder.append(getColumnName(fields[i])).append(" "); + } + queryBuilder.append(") VALUES ("); + for (int i = 0; i < fields.length; ++i) { + if (i != 0) { + queryBuilder.append(", "); + } + queryBuilder.append("?"); + } + queryBuilder.append(")"); + + try (Connection conn = pool.getConnection()) { + PreparedStatement statement = conn.prepareStatement(queryBuilder.toString()); + for (int i = 0; i < fields.length; ++i) { + statement.setObject(i + 1, fields[i].get(record)); + } + statement.execute(); + } + } + + public void delete(T record) throws IllegalArgumentException, IllegalAccessException, SQLException { + if (pkIndex == -1) { + throw new IllegalArgumentException("NO @PrimaryKey"); + } + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("DELETE FROM ").append(tableName).append(" WHERE ") + .append(fields[pkIndex].getName()).append(" = ?"); + try (Connection conn = pool.getConnection()) { + PreparedStatement statement = conn.prepareStatement(queryBuilder.toString()); + statement.setObject(1, fields[pkIndex].get(record)); + statement.execute(); + } + } + + public void update(T record) throws IllegalArgumentException, SQLException, IllegalAccessException { + if (pkIndex == -1) { + throw new IllegalArgumentException("NO @PrimaryKey"); + } + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("UPDATE ").append(tableName).append(" SET "); + for (int i = 0; i < fields.length; ++i) { + if (i != 0) { + queryBuilder.append(", "); + } + queryBuilder.append(getColumnName(fields[i])).append(" = ?"); + } + queryBuilder.append(" WHERE ").append(getColumnName(fields[pkIndex])) + .append(" = ?"); + + try (Connection conn = pool.getConnection()) { + PreparedStatement statement = conn.prepareStatement(queryBuilder.toString()); + for (int i = 0; i < fields.length; ++i) { + statement.setObject(i + 1, fields[i].get(record)); + } + statement.setObject(fields.length + 1, fields[pkIndex].get(record)); + statement.execute(); + } + } + + public List queryForAll() throws SQLException { + List result = new ArrayList<>(); + + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("SELECT * FROM ").append(tableName); + + try (Connection conn = pool.getConnection()) { + try (ResultSet rs = conn.createStatement().executeQuery(queryBuilder.toString())) { + while (rs.next()) { + T record = clazz.newInstance(); + for (int i = 0; i < fields.length; ++i) { + if (fields[i].getClass().isAssignableFrom(Number.class)) { + Long val = rs.getLong(i + 1); + fields[i].set(record, val); + } else if (fields[i].getType() != String.class) { + fields[i].set(record, rs.getObject(i + 1)); + } else { + Clob data = rs.getClob(i + 1); + fields[i].set(record, data.getSubString(1, (int) data.length())); + } + } + result.add(record); + } + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException("wrong class"); + } + } + return result; + } + + public T queryById(K key) throws IllegalArgumentException, SQLException { + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("SELECT * FROM ").append(tableName).append(" WHERE ") + .append(fields[pkIndex].getName()).append(" = ?"); + try (Connection conn = pool.getConnection()) { + PreparedStatement statement = conn.prepareStatement(queryBuilder.toString()); + statement.setString(1, key.toString()); + try (ResultSet rs = statement.executeQuery()) { + rs.next(); + T record = clazz.newInstance(); + for (int i = 0; i < fields.length; ++i) { + if (fields[i].getClass().isAssignableFrom(Number.class)) { + Long val = rs.getLong(i + 1); + fields[i].set(record, val); + } else if (fields[i].getType() != String.class) { + fields[i].set(record, rs.getObject(i + 1)); + } else { + Clob data = rs.getClob(i + 1); + fields[i].set(record, data.getSubString(1, (int) data.length())); + } + } + return record; + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException("wrong class"); + } + } + + } + + @Override + public void close() throws IOException { + if (pool != null) { + pool.dispose(); + } + } +} diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/H2StringsResolver.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/H2StringsResolver.java new file mode 100644 index 00000000..e5bd9893 --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/H2StringsResolver.java @@ -0,0 +1,39 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +class H2StringsResolver { + private static Map h2Map; + static { + h2Map = new HashMap<>(); + h2Map.put(Integer.class, "INTEGER"); + h2Map.put(Boolean.class, "BOOLEAN"); + h2Map.put(Byte.class, "TINYINT"); + h2Map.put(Short.class, "SMALLINT"); + h2Map.put(Long.class, "BIGINT"); + h2Map.put(Double.class, "DOUBLE"); + h2Map.put(Float.class, "FLOAT"); + h2Map.put(Time.class, "TIME"); + h2Map.put(Date.class, "DATE"); + h2Map.put(Timestamp.class, "TIMESTAMP"); + h2Map.put(Character.class, "CHAR"); + h2Map.put(String.class, "CLOB"); + h2Map.put(UUID.class, "UUID"); + } + + public static String resolve(Class clazz) { + if (clazz.isArray()) { + return "ARRAY"; + } + if (h2Map.containsKey(clazz)) { + return h2Map.get(clazz); + } + return "OTHER"; + } +} + diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/NameResolver.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/NameResolver.java new file mode 100644 index 00000000..514b0889 --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/NameResolver.java @@ -0,0 +1,16 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import com.google.common.base.CaseFormat; + +class NameResolver { + static final String REGEX = "[A-Za-z0-9_-]*"; + public static Boolean isGood(String name) { + return name.matches(REGEX); + } + static String convertCamelToUnderscore(String name) { + if (Character.isLowerCase(name.charAt(0))) { + return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name); + } + return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name); + } +} diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/PrimaryKey.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/PrimaryKey.java new file mode 100644 index 00000000..b773f252 --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/PrimaryKey.java @@ -0,0 +1,8 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface PrimaryKey { +} diff --git a/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Table.java b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Table.java new file mode 100644 index 00000000..2e5a1bcd --- /dev/null +++ b/riskingh/src/main/java/ru/fizteh/fivt/students/riskingh/MiniORM/Table.java @@ -0,0 +1,9 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + String name() default ""; +} diff --git a/riskingh/src/main/resources/h2test.properties b/riskingh/src/main/resources/h2test.properties new file mode 100644 index 00000000..e8349295 --- /dev/null +++ b/riskingh/src/main/resources/h2test.properties @@ -0,0 +1,4 @@ +connection_name=jdbc:h2:~/test +username=test +password=test + diff --git a/riskingh/src/test/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseServiceTest.java b/riskingh/src/test/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseServiceTest.java new file mode 100644 index 00000000..9168b66b --- /dev/null +++ b/riskingh/src/test/java/ru/fizteh/fivt/students/riskingh/MiniORM/DatabaseServiceTest.java @@ -0,0 +1,166 @@ +package ru.fizteh.fivt.students.riskingh.MiniORM; + +import org.h2.jdbcx.JdbcConnectionPool; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.util.Properties; + +import static ru.fizteh.fivt.students.riskingh.MiniORM.NameResolver.convertCamelToUnderscore; + +public class DatabaseServiceTest { + @Test(expected = IllegalArgumentException.class) + public void testIllegalClass_1() throws Exception { + class X { + @Column + int y; + } + new DatabaseService(X.class); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalClass_2() throws Exception { + @Table + class X { + @Column + int y; + + @Column(name = "y") + String x; + } + new DatabaseService(X.class); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalClass_3_0() throws Exception { + @Table + class X { + @Column + int y; + } + DatabaseService x = new DatabaseService<>(X.class); + x.delete(new X()); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalClass_3_1() throws Exception { + @Table + class X { + @Column + int y; + } + DatabaseService x = new DatabaseService<>(X.class); + x.update(new X()); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalClass_4() throws Exception { + @Table + class X { + @PrimaryKey + @Column + int y; + + @PrimaryKey + String x; + } + new DatabaseService(X.class); + } + + @Test + public void convertTest() throws Exception { + Assert.assertEquals(convertCamelToUnderscore("mayTheForceBeWithYou"), "may_the_force_be_with_you"); + Assert.assertEquals(convertCamelToUnderscore("WhatIsLove"), "what_is_love"); + } + + private static final String CONNECTION_NAME; + private static final String USERNAME; + private static final String PASSWORD; + + static { + Properties credits = new Properties(); + try (InputStream inputStream = DatabaseServiceTest.class.getResourceAsStream("/h2test.properties")) { + credits.load(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + CONNECTION_NAME = credits.getProperty("connection_name"); + USERNAME = credits.getProperty("username"); + PASSWORD = credits.getProperty("password"); + } + + @Table + static class MyClass { + @PrimaryKey + @Column + Integer x; + + @Column + String y; + + MyClass() { + } + + MyClass(Integer x, String y) { + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (o instanceof MyClass) { + MyClass omc = (MyClass) o; + return x.equals(omc.x) && y.equals(omc.y); + } + return false; + } + } + + @Test + public void bigTest() throws Exception { + DatabaseService x = new DatabaseService<>(MyClass.class, "/h2test.properties"); + JdbcConnectionPool pool = JdbcConnectionPool.create(CONNECTION_NAME, USERNAME, PASSWORD); + Connection tmp; + tmp = pool.getConnection(); + tmp.createStatement().execute("DROP TABLE IF EXISTS my_class"); + tmp.close(); + + x.createTable(); + tmp = pool.getConnection(); + tmp.createStatement().execute("INSERT INTO my_class (x, y) VALUES (1, 'one')"); + tmp.close(); + Assert.assertEquals(x.queryById(1).y, "one"); + x.insert(new MyClass(2, "X")); + x.insert(new MyClass(3, "X")); + tmp = pool.getConnection(); + ResultSet rs = tmp.createStatement().executeQuery("SELECT * FROM my_class WHERE y = 'X'"); + int count = 0; + int sum = 0; + while(rs.next()) { + Assert.assertTrue(rs.getInt(1) == 2 || rs.getInt(1) == 3); + ++count; + sum += rs.getInt(1); + } + + rs.close(); + tmp.close(); + + Assert.assertEquals(count, 2); + Assert.assertEquals(sum, 5); + + Assert.assertEquals(x.queryForAll().size(), 3); + Assert.assertTrue(x.queryForAll().contains(new MyClass(1, "one"))); + x.update(new MyClass(1, "two")); + Assert.assertTrue(!x.queryForAll().contains(new MyClass(1, "one"))); + Assert.assertTrue(x.queryForAll().contains(new MyClass(1, "two"))); + x.delete(new MyClass(1, "")); + Assert.assertTrue(!x.queryForAll().contains(new MyClass(1, "two"))); + + x.dropTable(); + x.close(); + } +}