package com.francetelecom.rd.stubs.engine; /* * #%L * Matos * $Id:$ * $HeadURL:$ * %% * Copyright (C) 2008 - 2014 Orange SA * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Interface with a database containing all the versions of Android. * @author Pierre Cregut * */ /** * @author Pierre Cregut * */ public class VersionDatabase { /** * URL Schema to open a database with the sqlite back-end */ final static String JDBC_SCHEME = "jdbc:sqlite:"; /** * Name of the class to load to use the sqlite back-end */ final static String JDBC_CLASS = "org.sqlite.JDBC"; final static String SUFFIX = ""; final static String [] CREATION = { "CREATE TABLE package (package_id INTEGER PRIMARY KEY, package_name VARCHAR(100) UNIQUE)", "CREATE TABLE version (version_id INTEGER PRIMARY KEY, version_name VARCHAR(20) UNIQUE)", "CREATE TABLE class (class_id INTEGER PRIMARY KEY, class_name VARCHAR(100), package_id INTEGER)", "CREATE TABLE class_version (class_id INTEGER, version_id INTEGER)", "CREATE TABLE element (element_id INTEGER PRIMARY KEY, element_name VARCHAR(30), class_id INTEGER, type_id INTEGER)", "CREATE TABLE element_version (element_id INTEGER, version_id INTEGER)", "CREATE TABLE type (type_id INTEGER PRIMARY KEY, type VARCHAR(1000) UNIQUE)", "CREATE INDEX package_index ON package(package_name)", "CREATE INDEX class_index ON class(package_id, class_name)", "CREATE UNIQUE INDEX class_version_index ON class_version(class_id, version_id)", "CREATE INDEX element_index ON element(class_id, element_name, type_id)", "CREATE UNIQUE INDEX element_version_index ON element_version(element_id, version_id)", "CREATE INDEX type_index ON type (type)" }; final static String INSERT_PACKAGE = "INSERT OR IGNORE INTO package (package_name) VALUES (?);"; final static String INSERT_VERSION = "INSERT OR IGNORE INTO version (version_name) VALUES (?);"; final static String INSERT_TYPE = "INSERT OR IGNORE INTO type (type) VALUES (?);"; final static String INSERT_CLASS = "INSERT INTO class (class_name,package_id) SELECT ?,? WHERE NOT EXISTS (SELECT 1 FROM class WHERE class_name=? AND package_id = ?);"; final static String INSERT_ELEMENT = "INSERT INTO element (element_name,class_id,type_id) SELECT ?,?,? WHERE NOT EXISTS (SELECT 1 FROM element WHERE element_name=? AND class_id = ? AND type_id = ?);"; final static String FIND_PACKAGE = "SELECT package_id FROM package WHERE package_name = ?;"; final static String FIND_VERSION = "SELECT version_id FROM version WHERE version_name = ?;"; final static String FIND_TYPE = "SELECT type_id FROM type WHERE type = ?;"; final static String FIND_CLASS = "SELECT class_id FROM class WHERE class_name = ? and package_id = ?;"; final static String FIND_ELEMENT = "SELECT element_id FROM element WHERE element_name = ? and class_id = ? and type_id = ?;"; final static String INSERT_CLASS_VERSION = "INSERT OR IGNORE INTO class_version (class_id, version_id) VALUES (?,?);"; final static String INSERT_ELEMENT_VERSION = "INSERT OR IGNORE INTO element_version (element_id, version_id) VALUES (?,?);"; final static String FIND_CLASS_VERSION = "SELECT version_name FROM version WHERE version_id NOT IN (SELECT version_id FROM class_version WHERE class_id = ?);"; final static String FIND_ELEMENT_VERSION = "SELECT version_name FROM version WHERE version_id NOT IN (SELECT version_id FROM element_version WHERE element_id = ?);"; final static String FIND_VERSION_LIST = "SELECT version_name FROM version;"; final static String FIND_PACKAGE_VERSIONS_STATS = "SELECT version_name, count(class.class_id) FROM class, class_version, version WHERE class.class_id=class_version.class_id AND class_version.version_id=version.version_id AND package_id=? GROUP BY version_name;"; final static String FIND_PACKAGE_TOTAL_CLASSES = "SELECT count(class_id) FROM class WHERE package_id=?;"; final PreparedStatement insert_package_statement; final PreparedStatement insert_version_statement; final PreparedStatement insert_type_statement; final PreparedStatement insert_class_statement; final PreparedStatement insert_element_statement; final PreparedStatement find_package_statement; final PreparedStatement find_version_statement; final PreparedStatement find_type_statement; final PreparedStatement find_class_statement; final PreparedStatement find_element_statement; final PreparedStatement insert_class_version_statement; final PreparedStatement insert_element_version_statement; final PreparedStatement find_class_version_statement; final PreparedStatement find_element_version_statement; final PreparedStatement find_version_list_statement; final PreparedStatement find_package_version_stats_statement; final PreparedStatement find_package_total_classes_statement; final static int MASK = Modifier.PUBLIC | Modifier.PROTECTED; private HashMap<Class<?>, Integer> classmap = new HashMap<Class<?>, Integer>(); private HashMap<String, Integer> packagemap = new HashMap<String, Integer>(); private HashMap<String, Integer> typemap = new HashMap<String, Integer>(); /** * Prefix added to version number for hidden element. */ public final static String HIDDEN_PREFIX = "h"; /** * Prefix added to version number for visible element. */ public final static String VISIBLE_PREFIX = "v"; /** * Database connection. */ Connection con; /** * Meta-data access on the connection and the database. */ DatabaseMetaData metadata; /** * Relocator */ final private ReflexUtil rf; /** * Current version to add */ int versionId; /** * @param dbName The name of the database file to open * @param rf a relocator. * @throws SQLException */ public VersionDatabase(String dbName, ReflexUtil rf) throws SQLException { this.rf = rf; try { Class.forName(JDBC_CLASS); } catch (ClassNotFoundException e) { assert false; } con = DriverManager.getConnection(JDBC_SCHEME + dbName + SUFFIX); metadata = con.getMetaData(); init(); insert_package_statement = con.prepareStatement(INSERT_PACKAGE); insert_class_statement = con.prepareStatement(INSERT_CLASS); insert_element_statement = con.prepareStatement(INSERT_ELEMENT); insert_version_statement = con.prepareStatement(INSERT_VERSION); insert_type_statement = con.prepareStatement(INSERT_TYPE); insert_class_version_statement = con.prepareStatement(INSERT_CLASS_VERSION); insert_element_version_statement = con.prepareStatement(INSERT_ELEMENT_VERSION); find_package_statement = con.prepareStatement(FIND_PACKAGE); find_class_statement = con.prepareStatement(FIND_CLASS); find_element_statement = con.prepareStatement(FIND_ELEMENT); find_version_statement = con.prepareStatement(FIND_VERSION); find_type_statement = con.prepareStatement(FIND_TYPE); find_class_version_statement = con.prepareStatement(FIND_CLASS_VERSION); find_element_version_statement = con.prepareStatement(FIND_ELEMENT_VERSION); find_version_list_statement = con.prepareStatement(FIND_VERSION_LIST); find_package_version_stats_statement = con.prepareStatement(FIND_PACKAGE_VERSIONS_STATS); find_package_total_classes_statement = con.prepareStatement(FIND_PACKAGE_TOTAL_CLASSES); } /** * Start the asynchronous mode */ public void open_transaction() { try { con.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } /** * Commit in asynchronous mode. */ public void close_transaction() { try { con.commit(); } catch (SQLException e) { e.printStackTrace(); } } private boolean hasTable(String tableName) { try { return metadata.getTables(null, null, tableName, null).next(); } catch (SQLException e) { e.printStackTrace(); return false; } } private void init() throws SQLException { if (!hasTable("package")) { Statement stmt = con.createStatement(); try { for (String line : CREATION) { stmt.executeUpdate(line); } } catch (SQLException e) { stmt.close(); throw e; } stmt.close(); } } private int addOrFindPackage(String name, boolean add) { Integer i = packagemap.get(name); int id; ResultSet rs = null; if (i != null) return i; try { if (add) { insert_package_statement.setString(1, name); insert_package_statement.execute(); } find_package_statement.setString(1,name); rs = find_package_statement.executeQuery(); id = (rs.next()) ? rs.getInt(1) : -1; packagemap.put(name, id); } catch (SQLException e) { e.printStackTrace(); id = -1; } if (rs != null) { try {rs.close();} catch (SQLException e) {ignore();}} return id; } private int addOrFindVersion(String name, boolean add) { int id; ResultSet rs = null; try { if (add) { insert_version_statement.setString(1, name); insert_version_statement.execute(); } find_version_statement.setString(1,name); rs = find_version_statement.executeQuery(); id = (rs.next()) ? rs.getInt(1) : -1; rs.close(); } catch (SQLException e) { e.printStackTrace(); id = -1; } if (rs != null) { try {rs.close();} catch (SQLException e) {ignore();}} return id; } /** * Set the current version value that will be used when we register classes (and their components) in the system. * @param name */ public void setVersion(String name) { versionId = addOrFindVersion(name, true); } private int addOrFindType(String type, boolean add) { int id; ResultSet rs = null; Integer i = typemap.get(type); if (i != null) return i; try { if (add) { insert_type_statement.setString(1, type); insert_type_statement.execute(); } find_type_statement.setString(1,type); rs = find_type_statement.executeQuery(); id = (rs.next()) ? rs.getInt(1) : -1; typemap.put(type,id); } catch (SQLException e) { e.printStackTrace(); id = -1; } if (rs != null) { try {rs.close();} catch (SQLException e) {ignore();}}; return id; } private int addOrFindClass(Class <?> clazz, boolean add) { int id; ResultSet rs = null; Integer i = classmap.get(clazz); if (i != null) return i; try { String packageName = packageName(clazz); String className = className(clazz); int packageId = addOrFindPackage(packageName, add); if (packageId == -1) return -1; if (add) { insert_class_statement.setString(1, className); insert_class_statement.setInt(2, packageId); insert_class_statement.setString(3, className); insert_class_statement.setInt(4, packageId); insert_class_statement.execute(); } find_class_statement.setString(1,className); find_class_statement.setInt(2, packageId); rs = find_class_statement.executeQuery(); id = (rs.next()) ? rs.getInt(1) : -1; classmap.put(clazz, id); } catch (SQLException e) { e.printStackTrace(); id = -1; } if (rs != null) { try {rs.close();} catch (SQLException e) {ignore();}}; return id; } private int addOrFindElement(String name, String type, Class <?> clazz, boolean add) { int id; ResultSet rs = null; try { int clazzId = addOrFindClass(clazz, add); int typeId = addOrFindType(type, add); if (clazzId == -1 || typeId == -1) return -1; if (add) { insert_element_statement.setString(1, name); insert_element_statement.setInt(2, clazzId); insert_element_statement.setInt(3, typeId); insert_element_statement.setString(4, name); insert_element_statement.setInt(5, clazzId); insert_element_statement.setInt(6, typeId); insert_element_statement.execute(); } find_element_statement.setString(1,name); find_element_statement.setInt(2, clazzId); find_element_statement.setInt(3, typeId); rs = find_element_statement.executeQuery(); id = (rs.next()) ? rs.getInt(1) : -1; } catch (SQLException e) { e.printStackTrace(); id = -1; } if (rs != null) { try {rs.close();} catch (SQLException e) {ignore();}}; return id; } private int addMethod(Method meth) { return addOrFindElement(meth.getName(), signature(meth), meth.getDeclaringClass(), true); } private int addMethod(Method meth, Class <?> target) { return addOrFindElement(meth.getName(), signature(meth), target, true); } private int addConstructor(Constructor <?> co) { return addOrFindElement("<init>", signature(co), co.getDeclaringClass(), true); } private int addConstructor(Constructor <?> co, Class <?> target) { return addOrFindElement("<init>", signature(co), target, true); } private int addField(Field fi) { return addOrFindElement(fi.getName(), signature(fi), fi.getDeclaringClass(), true); } private int addField(Field fi, Class <?> target) { return addOrFindElement(fi.getName(), signature(fi), target, true); } /** * Add a class version info * @param c * @return */ private boolean addVersion(Class <?> c) { int classId = addOrFindClass(c, true); try { insert_class_version_statement.setInt(1, classId); insert_class_version_statement.setInt(2, versionId); insert_class_version_statement.execute(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } } private boolean addVersion(int elementId) { try { insert_element_version_statement.setInt(1, elementId); insert_element_version_statement.setInt(2, versionId); insert_element_version_statement.execute(); return true; } catch (SQLException e) { return false; } } /** * Add a method version info * @param meth * @return */ private boolean addVersion(Method meth) { return addVersion(addMethod(meth)); } private boolean addVersion(Method meth, Class <?> target) { return addVersion(addMethod(meth, target)); } /** * Add a constructor version info * @param co * @return */ private boolean addVersion(Constructor <?> co) { return addVersion(addConstructor(co)); } private boolean addVersion(Constructor <?> co, Class<?> target) { return addVersion(addConstructor(co, target)); } /** * Add a field version info. * @param fi * @return */ private boolean addVersion(Field fi) { return addVersion(addField(fi)); } private boolean addVersion(Field fi, Class<?> target) { return addVersion(addField(fi, target)); } /** * Register a class in the system (it should be a top level class). Versions are added for * all the components according to the currently set version. * @param clazz */ public void registerClass(Class <?> clazz) { addVersion(clazz); for(Field field: clazz.getDeclaredFields()) addVersion(field); for (Class <?> subclass : clazz.getDeclaredClasses()) registerClass(subclass); for (Constructor <?> co : clazz.getDeclaredConstructors()) addVersion(co); for (Method me : clazz.getDeclaredMethods()) addVersion(me); Class <?> superClass = clazz.getSuperclass(); if (superClass != null) registerClass(superClass, clazz); } private void registerClass(Class <?> clazz, Class <?> target) { for(Field field: clazz.getDeclaredFields()) { if ((field.getModifiers() & MASK) != 0) addVersion(field,target); } for (Constructor <?> co : clazz.getDeclaredConstructors()) { if ((co.getModifiers() & MASK) != 0) addVersion(co,target); } for (Method me : clazz.getDeclaredMethods()) { if ((me.getModifiers() & MASK) != 0) addVersion(me,target); } Class <?> superClass = clazz.getSuperclass(); if (superClass != null) registerClass(superClass, target); } private String packageName(Class <?> c) { return rf.restoreString(ReflexUtil.getPackageName(c)); } private String className(Class <?> c) { String fullname = c.getName(); return fullname.substring(fullname.lastIndexOf('.') + 1); } private String signature(Field f) { return rf.restoreString (f.getType().toString()); } private String signature(Method m) { return signature(m.getParameterTypes()) + rf.restoreString(m.getReturnType().toString()); } private String signature(Constructor<?> co) { return signature(co.getParameterTypes()); } private String signature(Class<?>[] args) { StringBuilder buf = new StringBuilder("("); boolean first = true; for (Class <?> arg : args) { if (first) first = false; else buf.append(","); buf.append(rf.restoreString(arg.toString())); } buf.append(")"); return buf.toString(); } /** * Closes the database. * @throws SQLException */ public void close() throws SQLException { con.close(); } /** * Gives back the list of defined version for a class or interface * @param c * @return */ public List <String> missingVersionsOf (Class <?> c) { ArrayList <String> result = new ArrayList<String>(); ResultSet rs = null; try { int class_id = addOrFindClass(c, false); find_class_version_statement.setInt(1, class_id); rs = find_class_version_statement.executeQuery(); while(rs.next()) { result.add(rs.getString(1)); } } catch (SQLException e) { e.printStackTrace(); } if (rs != null) { try {rs.close();} catch (SQLException e){ignore();}} return result; } private List <String> missingVersionsOfElement (int element_id) { ArrayList <String> result = new ArrayList<String>(); ResultSet rs = null; try { find_element_version_statement.setInt(1, element_id); rs = find_element_version_statement.executeQuery(); while(rs.next()) { result.add(rs.getString(1)); } rs.close(); } catch (SQLException e) { e.printStackTrace(); } if (rs != null) { try {rs.close();} catch (SQLException e){ignore();}} return result; } /** * Gives back the list of defined version for a method * @param meth * @return */ public List <String> missingVersionsOf(Method meth) { int id = addOrFindElement(meth.getName(), signature(meth), meth.getDeclaringClass(), false); return missingVersionsOfElement(id); } /** * Gives back the list of defined version for a constructor * @param co * @return */ public List <String> missingVersionsOf(Constructor <?> co) { int id = addOrFindElement("<init>", signature(co), co.getDeclaringClass(), false); return missingVersionsOfElement(id); } /** * Gives back the list of defined version for a field * @param fi * @return */ public List <String> missingVersionsOf(Field fi) { int id = addOrFindElement(fi.getName(), signature(fi), fi.getDeclaringClass(), false); return missingVersionsOfElement(id); } /** * Returns a map with version_name as key and the number of elements * that are in a given version for the package. Additional key "total" * associates the total number of elements in the package * @param packageName * @return null if package cannot be found, or a Map containing the stats */ public Map<String, Integer> getVersionStats(String packageName) { HashMap<String, Integer> stats = new HashMap<String, Integer>(); int packageId = addOrFindPackage(packageName, false); if (packageId == -1) return null; ResultSet rs = null; try { find_package_version_stats_statement.setInt(1, packageId); rs = find_package_version_stats_statement.executeQuery(); while(rs.next()) { stats.put(rs.getString(1), rs.getInt(2)); } rs.close(); List<String> versions = getVersion(); for(String v : versions) { if(!stats.containsKey(v)) { stats.put(v, 0); } } find_package_total_classes_statement.setInt(1, packageId); rs = find_package_total_classes_statement.executeQuery(); if(rs.next()) stats.put("total", rs.getInt(1)); } catch (SQLException e) {e.printStackTrace();} if (rs != null) { try {rs.close();} catch (SQLException e){ignore();}} return stats; } /** * Gives the versions stored in the database * @return a list of versions */ public List<String> getVersion() { ArrayList<String> versions = new ArrayList<String>(); ResultSet rs = null; try { rs = find_version_list_statement.executeQuery(); while(rs.next()) { versions.add(rs.getString(1)); } } catch (SQLException e) {e.printStackTrace();} if (rs != null) { try {rs.close();} catch (SQLException e){ignore();}} return versions; } private void ignore() {} }