PackageManager: Support multi-source
This commit is contained in:
parent
9f9f856bf9
commit
23937764f9
@ -46,6 +46,10 @@ android {
|
|||||||
abortOnError false
|
abortOnError false
|
||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility 1.8
|
||||||
|
sourceCompatibility 1.8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@ -94,3 +94,6 @@
|
|||||||
java.lang.Object readResolve();
|
java.lang.Object readResolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-keep class * extends io.neoterm.framework.database.annotation.* { *; }
|
||||||
|
-keep interface * extends io.neoterm.framework.database.annotation.* { *; }
|
||||||
|
|
||||||
|
@ -7,16 +7,14 @@ package io.neoterm.component.pm
|
|||||||
enum class Architecture {
|
enum class Architecture {
|
||||||
ALL, ARM, AARCH64, X86, X86_64;
|
ALL, ARM, AARCH64, X86, X86_64;
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun parse(arch: String): Architecture {
|
fun parse(arch: String): Architecture {
|
||||||
when (arch) {
|
return when (arch) {
|
||||||
"arm" -> return ARM
|
"arm" -> ARM
|
||||||
"aarch64" -> return AARCH64
|
"aarch64" -> AARCH64
|
||||||
"x86" -> return X86
|
"x86" -> X86
|
||||||
"x86_64" -> return X86_64
|
"x86_64" -> X86_64
|
||||||
else -> return ALL
|
else -> ALL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ public class PackageComponent implements NeoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HashMap<String, NeoPackageInfo> getPackages() {
|
public HashMap<String, NeoPackageInfo> getPackages() {
|
||||||
return queryEnabled ? neoPackages : new HashMap<String, NeoPackageInfo>();
|
return queryEnabled ? neoPackages : new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPackageCount() {
|
public int getPackageCount() {
|
||||||
|
29
app/src/main/java/io/neoterm/component/pm/Source.java
Normal file
29
app/src/main/java/io/neoterm/component/pm/Source.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package io.neoterm.component.pm;
|
||||||
|
|
||||||
|
import io.neoterm.framework.database.annotation.ID;
|
||||||
|
import io.neoterm.framework.database.annotation.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
@Table
|
||||||
|
public class Source {
|
||||||
|
@ID(autoIncrement = true)
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
public String repo;
|
||||||
|
|
||||||
|
public boolean enabled;
|
||||||
|
|
||||||
|
public Source() {
|
||||||
|
// for Database
|
||||||
|
}
|
||||||
|
|
||||||
|
public Source(String url, String repo, boolean enabled) {
|
||||||
|
this.url = url;
|
||||||
|
this.repo = repo;
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
73
app/src/main/java/io/neoterm/component/pm/SourceHelper.kt
Normal file
73
app/src/main/java/io/neoterm/component/pm/SourceHelper.kt
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package io.neoterm.component.pm
|
||||||
|
|
||||||
|
import io.neoterm.frontend.component.ComponentManager
|
||||||
|
import io.neoterm.frontend.config.NeoTermPath
|
||||||
|
import io.neoterm.frontend.logging.NLog
|
||||||
|
import io.neoterm.utils.FileUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
object SourceHelper {
|
||||||
|
fun syncSource() {
|
||||||
|
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||||
|
syncSource(sourceManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncSource(sourceManager: SourceManager) {
|
||||||
|
val sourceFile = File(NeoTermPath.SOURCE_FILE)
|
||||||
|
val content = buildString {
|
||||||
|
this.append("# Generated by NeoTerm-Preference\n")
|
||||||
|
sourceManager.getEnabledSources()
|
||||||
|
.joinTo(this, "\n") { "deb ${it.url} ${it.repo}\n" }
|
||||||
|
}
|
||||||
|
FileUtils.writeFile(sourceFile, content.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun detectSourceFiles(): List<File> {
|
||||||
|
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||||
|
val sourceFiles = ArrayList<File>()
|
||||||
|
try {
|
||||||
|
val prefixes = sourceManager.getEnabledSources()
|
||||||
|
.map { detectSourceFilePrefix(it) }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
|
||||||
|
File(NeoTermPath.PACKAGE_LIST_DIR)
|
||||||
|
.listFiles()
|
||||||
|
.filterTo(sourceFiles, { file ->
|
||||||
|
prefixes.filter { file.name.startsWith(it) }
|
||||||
|
.count() > 0
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sourceFiles.clear()
|
||||||
|
NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
fun detectSourceFilePrefix(source: Source): String {
|
||||||
|
try {
|
||||||
|
val url = URL(source.url)
|
||||||
|
val builder = StringBuilder(url.host)
|
||||||
|
if (url.port != -1) {
|
||||||
|
builder.append(":${url.port}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = url.path
|
||||||
|
if (path != null && path.isNotEmpty()) {
|
||||||
|
builder.append("_")
|
||||||
|
val fixedPath = path.replace("/", "_")
|
||||||
|
.substring(1) // skip the last '/'
|
||||||
|
builder.append(fixedPath)
|
||||||
|
}
|
||||||
|
builder.append("_dists_${source.repo.replace(" ".toRegex(), "_")}_binary-")
|
||||||
|
return builder.toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,26 +2,53 @@ package io.neoterm.component.pm
|
|||||||
|
|
||||||
import io.neoterm.App
|
import io.neoterm.App
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.frontend.config.NeoPreference
|
import io.neoterm.framework.NeoTermDatabase
|
||||||
|
import io.neoterm.frontend.config.NeoTermPath
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
class SourceManager internal constructor() {
|
class SourceManager internal constructor() {
|
||||||
val sources = mutableSetOf<String>()
|
private val database = NeoTermDatabase.instance("sources")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
NeoPreference.loadStrings(NeoPreference.KEY_SOURCES).mapTo(sources, { it })
|
if (database.findAll<Source>(Source::class.java).isEmpty()) {
|
||||||
if (sources.isEmpty()) {
|
App.get().resources.getStringArray(R.array.pref_package_source_values)
|
||||||
sources.addAll(App.get().resources.getStringArray(R.array.pref_package_source_values))
|
.forEach {
|
||||||
|
database.saveBean(Source(it, "stable main", true))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSource(sourceUrl: String) {
|
fun addSource(sourceUrl: String, repo: String, enabled: Boolean) {
|
||||||
sources.add(sourceUrl)
|
database.saveBean(Source(sourceUrl, repo, enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSource(sourceUrl: String) {
|
||||||
|
database.deleteBeanByWhere(Source::class.java, "url == '$sourceUrl'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAll(sources: List<Source>) {
|
||||||
|
database.dropAllTable()
|
||||||
|
database.saveBeans(sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllSources(): List<Source> {
|
||||||
|
return database.findAll<Source>(Source::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getEnabledSources(): List<Source> {
|
||||||
|
return getAllSources().filter { it.enabled }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMainPackageSource(): String {
|
||||||
|
return getEnabledSources()
|
||||||
|
.map { it.repo }
|
||||||
|
.singleOrNull { it.trim() == "stable main" }
|
||||||
|
?: NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun applyChanges() {
|
fun applyChanges() {
|
||||||
NeoPreference.storeStrings(NeoPreference.KEY_SOURCES, sources)
|
database.vacuum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
package io.neoterm.component.pm
|
|
||||||
|
|
||||||
import io.neoterm.R
|
|
||||||
import io.neoterm.frontend.config.NeoPreference
|
|
||||||
import io.neoterm.frontend.config.NeoTermPath
|
|
||||||
import io.neoterm.frontend.logging.NLog
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author kiva
|
|
||||||
*/
|
|
||||||
object SourceUtils {
|
|
||||||
|
|
||||||
fun detectSourceFiles(): ArrayList<File> {
|
|
||||||
val sourceFiles = ArrayList<File>()
|
|
||||||
try {
|
|
||||||
val sourceUrl = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
|
||||||
val packageFilePrefix = detectSourceFilePrefix(sourceUrl)
|
|
||||||
if (packageFilePrefix.isNotEmpty()) {
|
|
||||||
File(NeoTermPath.PACKAGE_LIST_DIR)
|
|
||||||
.listFiles()
|
|
||||||
.filterTo(sourceFiles) { it.name.startsWith(packageFilePrefix) }
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
sourceFiles.clear()
|
|
||||||
NLog.e("PM", "Failed to detect source files: ${e.localizedMessage}")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourceFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
fun detectSourceFilePrefix(sourceUrl: String): String {
|
|
||||||
try {
|
|
||||||
val url = URL(sourceUrl)
|
|
||||||
val builder = StringBuilder()
|
|
||||||
builder.append(url.host)
|
|
||||||
// https://github.com/NeoTerm/NeoTerm/issues/1
|
|
||||||
if (url.port != -1) {
|
|
||||||
builder.append(":${url.port}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val path = url.path
|
|
||||||
if (path != null && path.isNotEmpty()) {
|
|
||||||
builder.append("_")
|
|
||||||
val fixedPath = path.replace("/", "_")
|
|
||||||
.substring(1) // skip the last '/'
|
|
||||||
builder.append(fixedPath)
|
|
||||||
}
|
|
||||||
builder.append("_dists_stable_main_binary-")
|
|
||||||
return builder.toString()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
NLog.e("PM", "Failed to detect source file prefix: ${e.localizedMessage}")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
675
app/src/main/java/io/neoterm/framework/NeoTermDatabase.java
Normal file
675
app/src/main/java/io/neoterm/framework/NeoTermDatabase.java
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
package io.neoterm.framework;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.SQLException;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.neoterm.App;
|
||||||
|
import io.neoterm.framework.database.DatabaseDataType;
|
||||||
|
import io.neoterm.framework.database.OnDatabaseUpgradedListener;
|
||||||
|
import io.neoterm.framework.database.SQLStatementHelper;
|
||||||
|
import io.neoterm.framework.database.SQLTypeParser;
|
||||||
|
import io.neoterm.framework.database.NeoTermSQLiteConfig;
|
||||||
|
import io.neoterm.framework.database.TableHelper;
|
||||||
|
import io.neoterm.framework.database.ValueHelper;
|
||||||
|
import io.neoterm.framework.database.bean.TableInfo;
|
||||||
|
import io.neoterm.framework.reflection.Reflect;
|
||||||
|
import io.neoterm.frontend.logging.NLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lody, Kiva
|
||||||
|
* <p>
|
||||||
|
* 基于<b>DTO(DataToObject)</b>映射的数据库操纵模型.
|
||||||
|
* 通过少量可选的注解,即可构造数据模型.
|
||||||
|
* 增删查改异常轻松.
|
||||||
|
* @version 1.4
|
||||||
|
*/
|
||||||
|
public class NeoTermDatabase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存创建的数据库,以便防止数据库冲突.
|
||||||
|
*/
|
||||||
|
private static final Map<String, NeoTermDatabase> DAO_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库配置
|
||||||
|
*/
|
||||||
|
private NeoTermSQLiteConfig neoTermSQLiteConfig;
|
||||||
|
/**
|
||||||
|
* 内部操纵的数据库执行类
|
||||||
|
*/
|
||||||
|
private SQLiteDatabase db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认构造器
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
private NeoTermDatabase(NeoTermSQLiteConfig config) {
|
||||||
|
|
||||||
|
this.neoTermSQLiteConfig = config;
|
||||||
|
String saveDir = config.getSaveDir();
|
||||||
|
if (saveDir != null
|
||||||
|
&& saveDir.trim().length() > 0) {
|
||||||
|
this.db = createDataBaseFileOnSDCard(saveDir,
|
||||||
|
config.getDatabaseName());
|
||||||
|
} else {
|
||||||
|
this.db = new SQLiteDataBaseHelper(App.Companion.get()
|
||||||
|
.getApplicationContext()
|
||||||
|
.getApplicationContext(), config)
|
||||||
|
.getWritableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置取得用于操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(NeoTermSQLiteConfig config) {
|
||||||
|
if (config.getDatabaseName() == null) {
|
||||||
|
throw new IllegalArgumentException("DBName is null in SqLiteConfig.");
|
||||||
|
}
|
||||||
|
NeoTermDatabase dao = DAO_MAP.get(config.getDatabaseName());
|
||||||
|
if (dao == null) {
|
||||||
|
dao = new NeoTermDatabase(config);
|
||||||
|
synchronized (DAO_MAP) {
|
||||||
|
DAO_MAP.put(config.getDatabaseName(), dao);
|
||||||
|
}
|
||||||
|
} else {//更换配置
|
||||||
|
dao.applyConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dao;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据默认配置取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance() {
|
||||||
|
return instance(NeoTermSQLiteConfig.DEFAULT_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param dbName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(String dbName) {
|
||||||
|
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||||
|
config.setDatabaseName(dbName);
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param dbVersion
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(int dbVersion) {
|
||||||
|
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||||
|
config.setDatabaseVersion(dbVersion);
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(OnDatabaseUpgradedListener listener) {
|
||||||
|
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||||
|
config.setOnDatabaseUpgradedListener(listener);
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param dbName
|
||||||
|
* @param dbVersion
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(String dbName, int dbVersion) {
|
||||||
|
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||||
|
config.setDatabaseName(dbName);
|
||||||
|
config.setDatabaseVersion(dbVersion);
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得操纵数据库的WeLikeDao实例
|
||||||
|
*
|
||||||
|
* @param dbName
|
||||||
|
* @param dbVersion
|
||||||
|
* @param listener
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static NeoTermDatabase instance(String dbName, int dbVersion, OnDatabaseUpgradedListener listener) {
|
||||||
|
NeoTermSQLiteConfig config = new NeoTermSQLiteConfig();
|
||||||
|
config.setDatabaseName(dbName);
|
||||||
|
config.setDatabaseVersion(dbVersion);
|
||||||
|
config.setOnDatabaseUpgradedListener(listener);
|
||||||
|
return instance(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置为新的参数(不改变数据库名).
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
private void applyConfig(NeoTermSQLiteConfig config) {
|
||||||
|
this.neoTermSQLiteConfig.debugMode = config.debugMode;
|
||||||
|
this.neoTermSQLiteConfig.setOnDatabaseUpgradedListener(config.getOnDatabaseUpgradedListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
DAO_MAP.clear();
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.d("缓存的DAO已经全部清除,将不占用内存.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在SD卡的指定目录上创建数据库文件
|
||||||
|
*
|
||||||
|
* @param sdcardPath sd卡路径
|
||||||
|
* @param dbFileName 数据库文件名
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private SQLiteDatabase createDataBaseFileOnSDCard(String sdcardPath,
|
||||||
|
String dbFileName) {
|
||||||
|
File dbFile = new File(sdcardPath, dbFileName);
|
||||||
|
if (!dbFile.exists()) {
|
||||||
|
try {
|
||||||
|
if (dbFile.createNewFile()) {
|
||||||
|
return SQLiteDatabase.openOrCreateDatabase(dbFile, null);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("无法在 " + dbFile.getAbsolutePath() + "创建DB文件.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//数据库文件已经存在,无需再次创建.
|
||||||
|
return SQLiteDatabase.openOrCreateDatabase(dbFile, null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果表不存在,需要创建它.
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
*/
|
||||||
|
private void createTableIfNeed(Class<?> clazz) {
|
||||||
|
TableInfo tableInfo = TableHelper.from(clazz);
|
||||||
|
if (tableInfo.isCreate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isTableExist(tableInfo)) {
|
||||||
|
String sql = SQLStatementHelper.createTable(tableInfo);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(sql);
|
||||||
|
}
|
||||||
|
db.execSQL(sql);
|
||||||
|
Method afterTableCreateMethod = tableInfo.afterTableCreateMethod;
|
||||||
|
if (afterTableCreateMethod != null) {
|
||||||
|
//如果afterTableMethod存在,就调用它
|
||||||
|
try {
|
||||||
|
afterTableCreateMethod.invoke(null, this);
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断表是否存在?
|
||||||
|
*
|
||||||
|
* @param table 需要盘的的表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isTableExist(TableInfo table) {
|
||||||
|
String sql = "SELECT COUNT(*) AS c FROM sqlite_master WHERE type ='table' AND name ='"
|
||||||
|
+ table.tableName + "' ";
|
||||||
|
try (Cursor cursor = db.rawQuery(sql, null)) {
|
||||||
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
|
int count = cursor.getInt(0);
|
||||||
|
if (count > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除全部的表
|
||||||
|
*/
|
||||||
|
public void dropAllTable() {
|
||||||
|
try (Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
dropTable(cursor.getString(0));
|
||||||
|
} catch (SQLException ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据库中的表的数量
|
||||||
|
*
|
||||||
|
* @return 表的数量
|
||||||
|
*/
|
||||||
|
public int tableCount() {
|
||||||
|
try (Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||||
|
return cursor == null ? 0 : cursor.getCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据库中的所有表名组成的List.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getTableList() {
|
||||||
|
try (Cursor cursor = db.rawQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type ='table'", null)) {
|
||||||
|
List<String> tableList = new ArrayList<>();
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
tableList.add(cursor.getString(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一张表
|
||||||
|
*
|
||||||
|
* @param beanClass 表所对应的类
|
||||||
|
*/
|
||||||
|
public void dropTable(Class<?> beanClass) {
|
||||||
|
TableInfo tableInfo = TableHelper.from(beanClass);
|
||||||
|
dropTable(tableInfo.tableName);
|
||||||
|
tableInfo.isCreate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一张表
|
||||||
|
*
|
||||||
|
* @param tableName 表名
|
||||||
|
*/
|
||||||
|
public void dropTable(String tableName) {
|
||||||
|
String statement = "DROP TABLE IF EXISTS " + tableName;
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
db.execSQL(statement);
|
||||||
|
TableInfo tableInfo = TableHelper.findTableInfoByName(tableName);
|
||||||
|
if (tableInfo != null) {
|
||||||
|
tableInfo.isCreate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储一个Bean.
|
||||||
|
*
|
||||||
|
* @param bean
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> NeoTermDatabase saveBean(T bean) {
|
||||||
|
createTableIfNeed(bean.getClass());
|
||||||
|
String statement = SQLStatementHelper.insertIntoTable(bean);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
db.execSQL(statement);
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储多个Bean.
|
||||||
|
*
|
||||||
|
* @param beans
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public NeoTermDatabase saveBeans(Object[] beans) {
|
||||||
|
for (Object o : beans) {
|
||||||
|
saveBean(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储多个Bean.
|
||||||
|
*
|
||||||
|
* @param beans
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> NeoTermDatabase saveBeans(List<T> beans) {
|
||||||
|
for (Object o : beans) {
|
||||||
|
saveBean(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 寻找Bean对应的全部数据
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> List<T> findAll(Class<?> clazz) {
|
||||||
|
createTableIfNeed(clazz);
|
||||||
|
TableInfo tableInfo = TableHelper.from(clazz);
|
||||||
|
String statement = SQLStatementHelper.selectTable(tableInfo.tableName);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
List<T> list = new ArrayList<T>();
|
||||||
|
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||||
|
if (cursor == null) {
|
||||||
|
// DO NOT RETURN NULL
|
||||||
|
// null checks are ugly!
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
T object = Reflect.on(clazz).create().get();
|
||||||
|
|
||||||
|
if (tableInfo.containID) {
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||||
|
String idFieldName = tableInfo.primaryField.getName();
|
||||||
|
ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||||
|
ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName()));
|
||||||
|
}
|
||||||
|
list.add(object);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据where语句寻找Bean
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> List<T> findBeanByWhere(Class<?> clazz, String where) {
|
||||||
|
createTableIfNeed(clazz);
|
||||||
|
TableInfo tableInfo = TableHelper.from(clazz);
|
||||||
|
String statement = SQLStatementHelper.findByWhere(tableInfo, where);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||||
|
if (cursor == null) {
|
||||||
|
// DO NOT RETURN NULL
|
||||||
|
// null checks are ugly!
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
T object = Reflect.on(clazz).create().get();
|
||||||
|
if (tableInfo.containID) {
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||||
|
String idFieldName = tableInfo.primaryField.getName();
|
||||||
|
ValueHelper.setKeyValue(cursor, object, tableInfo.primaryField, dataType, cursor.getColumnIndex(idFieldName));
|
||||||
|
}
|
||||||
|
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||||
|
ValueHelper.setKeyValue(cursor, object, field, dataType, cursor.getColumnIndex(field.getName()));
|
||||||
|
}
|
||||||
|
list.add(object);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T findOneBeanByWhere(Class<?> clazz, String where) {
|
||||||
|
List<T> list = findBeanByWhere(clazz, where);
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
return list.get(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据where语句删除Bean
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public NeoTermDatabase deleteBeanByWhere(Class<?> clazz, String where) {
|
||||||
|
createTableIfNeed(clazz);
|
||||||
|
TableInfo tableInfo = TableHelper.from(clazz);
|
||||||
|
String statement = SQLStatementHelper.deleteByWhere(tableInfo, where);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
db.execSQL(statement);
|
||||||
|
} catch (SQLException ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定ID的bean
|
||||||
|
*
|
||||||
|
* @param tableClass
|
||||||
|
* @param id
|
||||||
|
* @return 删除的Bean
|
||||||
|
*/
|
||||||
|
public NeoTermDatabase deleteBeanByID(Class<?> tableClass, Object id) {
|
||||||
|
createTableIfNeed(tableClass);
|
||||||
|
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass());
|
||||||
|
if (dataType != null && tableInfo.primaryField != null) {
|
||||||
|
//判断ID类型是否与数据类型匹配
|
||||||
|
boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType);
|
||||||
|
if (!match) {//不匹配,抛出异常
|
||||||
|
throw new IllegalArgumentException("类型 " + id.getClass().getName() + " 不是主键的类型,主键的类型应该为 " + tableInfo.primaryField.getType().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String idValue = ValueHelper.valueToString(dataType, id);
|
||||||
|
String statement = SQLStatementHelper.deleteByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.execSQL(statement);
|
||||||
|
} catch (SQLException ignore) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
//删除失败
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据给定的where更新数据
|
||||||
|
*
|
||||||
|
* @param tableClass
|
||||||
|
* @param where
|
||||||
|
* @param bean
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public NeoTermDatabase updateByWhere(Class<?> tableClass, String where, Object bean) {
|
||||||
|
createTableIfNeed(tableClass);
|
||||||
|
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||||
|
String statement = SQLStatementHelper.updateByWhere(tableInfo, bean, where);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.d(statement);
|
||||||
|
}
|
||||||
|
db.execSQL(statement);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据给定的id更新数据
|
||||||
|
*
|
||||||
|
* @param tableClass
|
||||||
|
* @param id
|
||||||
|
* @param bean
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public NeoTermDatabase updateByID(Class<?> tableClass, Object id, Object bean) {
|
||||||
|
createTableIfNeed(tableClass);
|
||||||
|
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||||
|
StringBuilder subStatement = new StringBuilder();
|
||||||
|
if (tableInfo.containID) {
|
||||||
|
subStatement.append(tableInfo.primaryField.getName()).append(" = ").append(ValueHelper.valueToString(SQLTypeParser.getDataType(tableInfo.primaryField), id));
|
||||||
|
} else {
|
||||||
|
subStatement.append("_id = ").append((int) id);
|
||||||
|
}
|
||||||
|
updateByWhere(tableClass, subStatement.toString(), bean);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查找Bean
|
||||||
|
*
|
||||||
|
* @param tableClass
|
||||||
|
* @param id
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> T findBeanByID(Class<?> tableClass, Object id) {
|
||||||
|
createTableIfNeed(tableClass);
|
||||||
|
TableInfo tableInfo = TableHelper.from(tableClass);
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(id.getClass());
|
||||||
|
if (dataType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 判断ID类型是否与数据类型匹配
|
||||||
|
boolean match = SQLTypeParser.matchType(tableInfo.primaryField, dataType) || tableInfo.primaryField == null;
|
||||||
|
if (!match) {// 不匹配,抛出异常
|
||||||
|
throw new IllegalArgumentException("Type " + id.getClass().getName() + " is not the primary key, expecting " + tableInfo.primaryField.getType().getName());
|
||||||
|
}
|
||||||
|
String idValue = ValueHelper.valueToString(dataType, id);
|
||||||
|
String statement = SQLStatementHelper.findByWhere(tableInfo, tableInfo.primaryField == null ? "_id" : tableInfo.primaryField.getName() + " = " + idValue);
|
||||||
|
if (neoTermSQLiteConfig.debugMode) {
|
||||||
|
NLog.INSTANCE.w(statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Cursor cursor = db.rawQuery(statement, null)) {
|
||||||
|
if (cursor != null && cursor.getCount() > 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
T bean = Reflect.on(tableClass).create().get();
|
||||||
|
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
DatabaseDataType fieldType = tableInfo.fieldToDataTypeMap.get(field);
|
||||||
|
ValueHelper.setKeyValue(cursor, bean, field, fieldType, cursor.getColumnIndex(field.getName()));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Reflect.on(bean).set(tableInfo.containID ? tableInfo.primaryField.getName() : "_id", id);
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
// 我们允许Bean没有id字段,因此此异常可以忽略
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 VACUUM 命令压缩数据库
|
||||||
|
*/
|
||||||
|
public void vacuum() {
|
||||||
|
db.execSQL("VACUUM");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用本方法会释放当前数据库占用的内存,
|
||||||
|
* 调用后请确保你不会在接下来的代码中继续用到本实例.
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
DAO_MAP.remove(this);
|
||||||
|
this.neoTermSQLiteConfig = null;
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得内部操纵的SqliteDatabase.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public SQLiteDatabase getDatabase() {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部数据库监听器,负责派发接口.
|
||||||
|
*/
|
||||||
|
private class SQLiteDataBaseHelper extends SQLiteOpenHelper {
|
||||||
|
private final OnDatabaseUpgradedListener onDatabaseUpgradedListener;
|
||||||
|
private final boolean defaultDropAllTables;
|
||||||
|
|
||||||
|
public SQLiteDataBaseHelper(Context context, NeoTermSQLiteConfig config) {
|
||||||
|
super(context, config.getDatabaseName(), null, config.getDatabaseVersion());
|
||||||
|
this.onDatabaseUpgradedListener = config.getOnDatabaseUpgradedListener();
|
||||||
|
this.defaultDropAllTables = config.isDefaultDropAllTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
if (onDatabaseUpgradedListener != null) {
|
||||||
|
onDatabaseUpgradedListener.onDatabaseUpgraded(db, oldVersion, newVersion);
|
||||||
|
|
||||||
|
} else if (defaultDropAllTables) { // 干掉所有的表
|
||||||
|
dropAllTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public enum DatabaseDataType {
|
||||||
|
/**
|
||||||
|
* int类型
|
||||||
|
*/
|
||||||
|
INTEGER,
|
||||||
|
/**
|
||||||
|
* String类型
|
||||||
|
*/
|
||||||
|
TEXT,
|
||||||
|
/**
|
||||||
|
* float类型
|
||||||
|
*/
|
||||||
|
FLOAT,
|
||||||
|
/**
|
||||||
|
* long类型
|
||||||
|
*/
|
||||||
|
BIGINT,
|
||||||
|
/**
|
||||||
|
* double类型
|
||||||
|
*/
|
||||||
|
DOUBLE;
|
||||||
|
|
||||||
|
boolean nullable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据类型是否允许为null
|
||||||
|
*/
|
||||||
|
public DatabaseDataType nullable(boolean nullable) {
|
||||||
|
this.nullable = nullable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class NeoTermSQLiteConfig implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -4069725570156436316L;
|
||||||
|
//==============================================================
|
||||||
|
// 常量
|
||||||
|
//==============================================================
|
||||||
|
public static String DEFAULT_DB_NAME = "we_like.db";
|
||||||
|
public static NeoTermSQLiteConfig DEFAULT_CONFIG = new NeoTermSQLiteConfig();
|
||||||
|
|
||||||
|
//==============================================================
|
||||||
|
// 字段
|
||||||
|
//==============================================================
|
||||||
|
/**
|
||||||
|
* 是否为DEBUG模式
|
||||||
|
*/
|
||||||
|
public boolean debugMode = false;
|
||||||
|
/**
|
||||||
|
* 数据库名
|
||||||
|
*/
|
||||||
|
private String dbName = DEFAULT_DB_NAME;
|
||||||
|
/**
|
||||||
|
* 数据库升级监听器
|
||||||
|
*/
|
||||||
|
private OnDatabaseUpgradedListener onDatabaseUpgradedListener;
|
||||||
|
private boolean defaultDropAllTables = false;
|
||||||
|
private String saveDir;
|
||||||
|
private int dbVersion = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据库的名称
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getDatabaseName() {
|
||||||
|
return dbName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置数据库的名称
|
||||||
|
*
|
||||||
|
* @param dbName
|
||||||
|
*/
|
||||||
|
public void setDatabaseName(String dbName) {
|
||||||
|
this.dbName = dbName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据库升级监听器
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public OnDatabaseUpgradedListener getOnDatabaseUpgradedListener() {
|
||||||
|
return onDatabaseUpgradedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置数据库升级监听器
|
||||||
|
*
|
||||||
|
* @param onDatabaseUpgradedListener
|
||||||
|
*/
|
||||||
|
public void setOnDatabaseUpgradedListener(OnDatabaseUpgradedListener onDatabaseUpgradedListener) {
|
||||||
|
this.onDatabaseUpgradedListener = onDatabaseUpgradedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取得数据库保存目录
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getSaveDir() {
|
||||||
|
return saveDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置数据库的保存目录
|
||||||
|
*
|
||||||
|
* @param saveDir
|
||||||
|
*/
|
||||||
|
public void setSaveDir(String saveDir) {
|
||||||
|
this.saveDir = saveDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取DB的版本号
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getDatabaseVersion() {
|
||||||
|
return dbVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置DB的版本号
|
||||||
|
*
|
||||||
|
* @param dbVersion
|
||||||
|
*/
|
||||||
|
public void setDatabaseVersion(int dbVersion) {
|
||||||
|
this.dbVersion = dbVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App 更新时是否默认删除所有存在的表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isDefaultDropAllTables() {
|
||||||
|
return defaultDropAllTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 App 更新时是否默认删除所有存在的表
|
||||||
|
* @param defaultDropAllTables
|
||||||
|
*/
|
||||||
|
public void setDefaultDropAllTables(boolean defaultDropAllTables) {
|
||||||
|
this.defaultDropAllTables = defaultDropAllTables;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public interface OnDatabaseUpgradedListener {
|
||||||
|
/**
|
||||||
|
* @param db 数据库
|
||||||
|
* @param oldVersion 旧版本
|
||||||
|
* @param newVersion 新版本
|
||||||
|
*/
|
||||||
|
void onDatabaseUpgraded(SQLiteDatabase db, int oldVersion, int newVersion);
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import io.neoterm.framework.database.annotation.ID;
|
||||||
|
import io.neoterm.framework.database.bean.TableInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class SQLStatementHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造<b>创建表</b>的语句
|
||||||
|
*
|
||||||
|
* @param tableInfo 表信息
|
||||||
|
* @return 创建表的SQL语句
|
||||||
|
*/
|
||||||
|
public static String createTable(TableInfo tableInfo) {
|
||||||
|
StringBuilder statement = new StringBuilder();
|
||||||
|
|
||||||
|
statement.append("CREATE TABLE ").append("'")
|
||||||
|
.append(tableInfo.tableName).append("'")
|
||||||
|
.append(" (");
|
||||||
|
|
||||||
|
if (tableInfo.containID) {
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||||
|
if (dataType == null) {
|
||||||
|
throw new IllegalArgumentException("Type of " + tableInfo.primaryField.getType().getName() + " is not support in WelikeDB.");
|
||||||
|
}
|
||||||
|
statement.append("'").append(tableInfo.primaryField.getName()).append("'");
|
||||||
|
switch (dataType) {
|
||||||
|
case INTEGER:
|
||||||
|
statement.append(" INTEGER PRIMARY KEY ");
|
||||||
|
ID id = tableInfo.primaryField.getAnnotation(ID.class);
|
||||||
|
if (id != null && id.autoIncrement()) {
|
||||||
|
statement.append("AUTOINCREMENT");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statement
|
||||||
|
.append(" ")
|
||||||
|
.append(dataType.name())
|
||||||
|
.append(" PRIMARY KEY");
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.append(",");
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
statement.append("'_id' INTEGER PRIMARY KEY AUTOINCREMENT,");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||||
|
statement.append("'").append(field.getName()).append("'")
|
||||||
|
.append(" ")
|
||||||
|
.append(dataType.name());
|
||||||
|
if (!dataType.nullable) {
|
||||||
|
statement.append(" NOT NULL");
|
||||||
|
}
|
||||||
|
statement.append(",");
|
||||||
|
}
|
||||||
|
//删掉最后一个逗号
|
||||||
|
statement.deleteCharAt(statement.length() - 1);
|
||||||
|
statement.append(")");
|
||||||
|
|
||||||
|
return statement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 插入一个Bean 的语句.
|
||||||
|
*
|
||||||
|
* @param o
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String insertIntoTable(Object o) {
|
||||||
|
TableInfo tableInfo = TableHelper.from(o.getClass());
|
||||||
|
StringBuilder statement = new StringBuilder();
|
||||||
|
statement.append("INSERT INTO ").append(tableInfo.tableName).append(" ");
|
||||||
|
statement.append("VALUES(");
|
||||||
|
|
||||||
|
if (tableInfo.containID) {
|
||||||
|
DatabaseDataType primaryDataType = SQLTypeParser.getDataType(tableInfo.primaryField);
|
||||||
|
switch (primaryDataType) {
|
||||||
|
case INTEGER:
|
||||||
|
statement.append("NULL,");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
try {
|
||||||
|
statement
|
||||||
|
.append(ValueHelper.valueToString(primaryDataType, tableInfo.primaryField, o))
|
||||||
|
.append(",");
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
statement.append("NULL,");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Field field : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
DatabaseDataType dataType = tableInfo.fieldToDataTypeMap.get(field);
|
||||||
|
try {
|
||||||
|
statement.append(ValueHelper.valueToString(dataType, field, o)).append(",");
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
//不会发生...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statement.deleteCharAt(statement.length() - 1);
|
||||||
|
statement.append(")");
|
||||||
|
|
||||||
|
return statement.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据where条件创建选择语句
|
||||||
|
*
|
||||||
|
* @param tableInfo
|
||||||
|
* @param where
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String findByWhere(TableInfo tableInfo, String where) {
|
||||||
|
StringBuilder statement = new StringBuilder("SELECT * FROM ");
|
||||||
|
statement
|
||||||
|
.append(tableInfo.tableName)
|
||||||
|
.append(" ")
|
||||||
|
.append("WHERE ")
|
||||||
|
.append(where);
|
||||||
|
|
||||||
|
return statement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据where条件创建删除语句
|
||||||
|
*
|
||||||
|
* @param tableInfo
|
||||||
|
* @param where
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String deleteByWhere(TableInfo tableInfo, String where) {
|
||||||
|
StringBuilder statement = new StringBuilder("DELETE FROM ");
|
||||||
|
statement
|
||||||
|
.append(tableInfo.tableName)
|
||||||
|
.append(" ")
|
||||||
|
.append("WHERE ")
|
||||||
|
.append(where);
|
||||||
|
|
||||||
|
return statement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据where条件创建更新语句
|
||||||
|
*
|
||||||
|
* @param tableInfo
|
||||||
|
* @param bean
|
||||||
|
* @param where
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String updateByWhere(TableInfo tableInfo, Object bean, String where) {
|
||||||
|
StringBuilder builder = new StringBuilder("UPDATE ");
|
||||||
|
|
||||||
|
builder.append(tableInfo.tableName).append(" SET ");
|
||||||
|
|
||||||
|
for (Field f : tableInfo.fieldToDataTypeMap.keySet()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
builder.append(f.getName())
|
||||||
|
.append(" = ")
|
||||||
|
.append(ValueHelper.valueToString(
|
||||||
|
SQLTypeParser.getDataType(f.getType()),
|
||||||
|
f.get(bean))).append(",");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.deleteCharAt(builder.length() - 1);//删除最后一个逗号
|
||||||
|
|
||||||
|
builder.append(" WHERE ");
|
||||||
|
builder.append(where);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建选中table的语句
|
||||||
|
*
|
||||||
|
* @param tableName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String selectTable(String tableName) {
|
||||||
|
return "SELECT * FROM " + tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import io.neoterm.framework.database.annotation.Ignore;
|
||||||
|
import io.neoterm.framework.database.annotation.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class SQLTypeParser {
|
||||||
|
/**
|
||||||
|
* 根据字段类型匹配它在数据库中的对应类型.
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static DatabaseDataType getDataType(Field field) {
|
||||||
|
Class<?> clazz = field.getType();
|
||||||
|
if (clazz == (String.class)) {
|
||||||
|
return DatabaseDataType.TEXT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
} else if (clazz == (int.class) || clazz == (Integer.class)) {
|
||||||
|
return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
} else if (clazz == (float.class) || clazz == (Float.class)) {
|
||||||
|
return DatabaseDataType.FLOAT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
} else if (clazz == (long.class) || clazz == (Long.class)) {
|
||||||
|
return DatabaseDataType.BIGINT.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
} else if (clazz == (double.class) || clazz == (Double.class)) {
|
||||||
|
return DatabaseDataType.DOUBLE.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
} else if (clazz == (boolean.class) || clazz == (Boolean.class)) {
|
||||||
|
return DatabaseDataType.INTEGER.nullable((field.getAnnotation(NotNull.class) == null));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字段类型匹配它在数据库中的对应类型.
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static DatabaseDataType getDataType(Class<?> clazz) {
|
||||||
|
if (clazz == (String.class)) {
|
||||||
|
return DatabaseDataType.TEXT;
|
||||||
|
} else if (clazz == (int.class) || clazz == (Integer.class)) {
|
||||||
|
return DatabaseDataType.INTEGER;
|
||||||
|
} else if (clazz == (float.class) || clazz == (Float.class)) {
|
||||||
|
return DatabaseDataType.FLOAT;
|
||||||
|
} else if (clazz == (long.class) || clazz == (Long.class)) {
|
||||||
|
return DatabaseDataType.BIGINT;
|
||||||
|
} else if (clazz == (double.class) || clazz == (Double.class)) {
|
||||||
|
return DatabaseDataType.DOUBLE;
|
||||||
|
} else if (clazz == (boolean.class) || clazz == (Boolean.class)) {
|
||||||
|
return DatabaseDataType.INTEGER;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段类型与数据类型是否匹配?
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @param dataType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean matchType(Field field, DatabaseDataType dataType) {
|
||||||
|
DatabaseDataType fieldDataType = getDataType(field.getType());
|
||||||
|
|
||||||
|
return dataType != null && fieldDataType == (dataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段是否可以被数据库忽略?
|
||||||
|
*
|
||||||
|
* @param field
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isIgnore(Field field) {
|
||||||
|
return field.getAnnotation(Ignore.class) != null;
|
||||||
|
}
|
||||||
|
}
|
132
app/src/main/java/io/neoterm/framework/database/TableHelper.java
Normal file
132
app/src/main/java/io/neoterm/framework/database/TableHelper.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.neoterm.framework.NeoTermDatabase;
|
||||||
|
import io.neoterm.framework.database.annotation.ID;
|
||||||
|
import io.neoterm.framework.database.annotation.Table;
|
||||||
|
import io.neoterm.framework.database.bean.TableInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class TableHelper {
|
||||||
|
private static final Map<Class<?>, TableInfo> classToTableInfoMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据传入的Bean的Class将其映射为一个TableInfo.
|
||||||
|
*
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static TableInfo from(Class<?> clazz) {
|
||||||
|
TableInfo tableInfo = classToTableInfoMap.get(clazz);
|
||||||
|
if (tableInfo != null) {
|
||||||
|
return tableInfo;
|
||||||
|
}
|
||||||
|
tableInfo = new TableInfo();
|
||||||
|
//Table注解解析
|
||||||
|
Table table = clazz.getAnnotation(Table.class);
|
||||||
|
String afterTableCreateMethod = table != null ? table.afterTableCreate() : null;
|
||||||
|
if (afterTableCreateMethod != null && afterTableCreateMethod.trim().length() > 0) {
|
||||||
|
try {
|
||||||
|
Method method = clazz.getDeclaredMethod(afterTableCreateMethod, NeoTermDatabase.class);
|
||||||
|
if (method != null && Modifier.isStatic(method.getModifiers())) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
tableInfo.afterTableCreateMethod = method;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (table != null && table.name().trim().length() != 0) {
|
||||||
|
tableInfo.tableName = table.name();
|
||||||
|
} else {
|
||||||
|
tableInfo.tableName = clazz.getName().replace(".", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Field, DatabaseDataType> fieldEnumMap = new HashMap<>();
|
||||||
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
//如果这个字段加了ignore注解,我们就跳过
|
||||||
|
if (SQLTypeParser.isIgnore(field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DatabaseDataType dataType = SQLTypeParser.getDataType(field);
|
||||||
|
if (dataType != null) {
|
||||||
|
fieldEnumMap.put(field, dataType);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("The type of " + field.getName() + " is not supported in database.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tableInfo.fieldToDataTypeMap = fieldEnumMap;
|
||||||
|
buildPrimaryIDForTableInfo(tableInfo);
|
||||||
|
tableInfo.createTableStatement = SQLStatementHelper.createTable(tableInfo);
|
||||||
|
|
||||||
|
synchronized (classToTableInfoMap) {
|
||||||
|
classToTableInfoMap.put(clazz, tableInfo);
|
||||||
|
}
|
||||||
|
return tableInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为一个Bean匹配一个ID字段,如果ID字段不存在,使用默认的_id替代.
|
||||||
|
*
|
||||||
|
* @param info
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static TableInfo buildPrimaryIDForTableInfo(TableInfo info) {
|
||||||
|
|
||||||
|
Field idField = null;
|
||||||
|
ID id;
|
||||||
|
for (Field field : info.fieldToDataTypeMap.keySet()) {
|
||||||
|
id = field.getAnnotation(ID.class);
|
||||||
|
if (id != null) {
|
||||||
|
idField = field;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}//end
|
||||||
|
if (idField != null) {
|
||||||
|
//从字段表中移除ID
|
||||||
|
info.fieldToDataTypeMap.remove(idField);
|
||||||
|
info.containID = true;
|
||||||
|
info.primaryField = idField;
|
||||||
|
} else {
|
||||||
|
info.containID = false;
|
||||||
|
info.primaryField = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据表名匹配TableInfo
|
||||||
|
*
|
||||||
|
* @param tableName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static TableInfo findTableInfoByName(String tableName) {
|
||||||
|
|
||||||
|
for (TableInfo tableInfo : classToTableInfoMap.values()) {
|
||||||
|
if (tableInfo.tableName.equals(tableName)) {
|
||||||
|
return tableInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除留在内存中的TableInfo缓存
|
||||||
|
*/
|
||||||
|
public static void clearCache() {
|
||||||
|
classToTableInfoMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
118
app/src/main/java/io/neoterm/framework/database/ValueHelper.java
Normal file
118
app/src/main/java/io/neoterm/framework/database/ValueHelper.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package io.neoterm.framework.database;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class ValueHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型将数据库中的值写入到相应的字段.
|
||||||
|
*
|
||||||
|
* @param cursor 游标
|
||||||
|
* @param object 赋值对象
|
||||||
|
* @param field 赋值字段
|
||||||
|
* @param dataType 数据类型
|
||||||
|
*/
|
||||||
|
public static void setKeyValue(Cursor cursor, Object object, Field field, DatabaseDataType dataType, int index) {
|
||||||
|
switch (dataType) {
|
||||||
|
case INTEGER:
|
||||||
|
try {
|
||||||
|
field.set(object, cursor.getInt(index));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
try {
|
||||||
|
//支持Boolean类型
|
||||||
|
//因为Boolean默认当Integer处理
|
||||||
|
field.set(object, cursor.getInt(index) != 0);
|
||||||
|
} catch (IllegalAccessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TEXT:
|
||||||
|
try {
|
||||||
|
field.set(object, cursor.getString(index));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FLOAT:
|
||||||
|
try {
|
||||||
|
field.set(object, cursor.getFloat(index));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BIGINT:
|
||||||
|
try {
|
||||||
|
field.set(object, cursor.getLong(index));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DOUBLE:
|
||||||
|
try {
|
||||||
|
field.set(object, cursor.getDouble(index));
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型从字段中提取值并转换为String
|
||||||
|
*
|
||||||
|
* @param dataType
|
||||||
|
* @param field
|
||||||
|
* @param o
|
||||||
|
* @return
|
||||||
|
* @throws IllegalAccessException 无法转换时抛出的异常
|
||||||
|
*/
|
||||||
|
public static String valueToString(DatabaseDataType dataType, Field field, Object o) throws IllegalAccessException {
|
||||||
|
switch (dataType) {
|
||||||
|
case INTEGER:
|
||||||
|
Object f = field.get(o);
|
||||||
|
if (f instanceof Boolean) {
|
||||||
|
return String.valueOf(((boolean) field.get(o)) ? 1 : 0);
|
||||||
|
} else {
|
||||||
|
return String.valueOf((int) field.get(o));
|
||||||
|
}
|
||||||
|
case TEXT:
|
||||||
|
return "\"" + field.get(o) + "" + "\"";
|
||||||
|
case DOUBLE:
|
||||||
|
return String.valueOf((double) field.get(o));
|
||||||
|
case FLOAT:
|
||||||
|
return String.valueOf((float) field.get(o));
|
||||||
|
case BIGINT:
|
||||||
|
return String.valueOf((long) field.get(o));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数据类型将对象转换为String
|
||||||
|
*
|
||||||
|
* @param dataType
|
||||||
|
* @param o
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String valueToString(DatabaseDataType dataType, Object o) {
|
||||||
|
switch (dataType) {
|
||||||
|
case INTEGER:
|
||||||
|
if (o instanceof Boolean) {
|
||||||
|
return ((boolean) o) ? "1" : "0";
|
||||||
|
} else {
|
||||||
|
return String.valueOf((int) o);
|
||||||
|
}
|
||||||
|
case TEXT:
|
||||||
|
return "\"" + o + "\"";
|
||||||
|
case DOUBLE:
|
||||||
|
return String.valueOf((double) o);
|
||||||
|
case FLOAT:
|
||||||
|
return String.valueOf((float) o);
|
||||||
|
case BIGINT:
|
||||||
|
return String.valueOf((long) o);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package io.neoterm.framework.database.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ID {
|
||||||
|
/**
|
||||||
|
* 只对Integer类型的ID字段有效
|
||||||
|
*
|
||||||
|
* @return 是否为自增长
|
||||||
|
*/
|
||||||
|
boolean autoIncrement() default false;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package io.neoterm.framework.database.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
public @interface Ignore {
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package io.neoterm.framework.database.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface NotNull {
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.neoterm.framework.database.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Table {
|
||||||
|
/**
|
||||||
|
* @return 表名
|
||||||
|
*/
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return 在表创建后需要回调的方法
|
||||||
|
*/
|
||||||
|
String afterTableCreate() default "";
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.neoterm.framework.database.bean;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.neoterm.framework.database.DatabaseDataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class TableInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否包含ID
|
||||||
|
*/
|
||||||
|
public boolean containID;
|
||||||
|
/**
|
||||||
|
* 主键字段
|
||||||
|
*/
|
||||||
|
public Field primaryField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表名
|
||||||
|
*/
|
||||||
|
public String tableName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字段表
|
||||||
|
*/
|
||||||
|
public Map<Field, DatabaseDataType> fieldToDataTypeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建table的语句
|
||||||
|
*/
|
||||||
|
public String createTableStatement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已经创建
|
||||||
|
*/
|
||||||
|
public boolean isCreate = false;
|
||||||
|
|
||||||
|
public Method afterTableCreateMethod;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package io.neoterm.framework.reflection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class representing null pointer.
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class NullPointer {
|
||||||
|
}
|
560
app/src/main/java/io/neoterm/framework/reflection/Reflect.java
Normal file
560
app/src/main/java/io/neoterm/framework/reflection/Reflect.java
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
package io.neoterm.framework.reflection;
|
||||||
|
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make reflections easier and elegant.
|
||||||
|
*
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class Reflect {
|
||||||
|
private final Object mObject;
|
||||||
|
private final boolean isClass;
|
||||||
|
|
||||||
|
private Reflect(Class<?> type) {
|
||||||
|
this.mObject = type;
|
||||||
|
this.isClass = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Reflect(Object object) {
|
||||||
|
this.mObject = object;
|
||||||
|
this.isClass = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create reflector from class name.
|
||||||
|
*
|
||||||
|
* @param name Full class name
|
||||||
|
* @return Reflector
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
* @see #on(Class)
|
||||||
|
*/
|
||||||
|
public static Reflect on(String name) throws ReflectionException {
|
||||||
|
return on(forName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create reflector from class name using given class loader.
|
||||||
|
*
|
||||||
|
* @param name Full class name
|
||||||
|
* @param classLoader Given class loader
|
||||||
|
* @return Reflector
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
* @see #on(Class)
|
||||||
|
*/
|
||||||
|
public static Reflect on(String name, ClassLoader classLoader) throws ReflectionException {
|
||||||
|
return on(forName(name, classLoader));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create reflector from given class type.
|
||||||
|
* Helpful especially when you want to access static fields.
|
||||||
|
*
|
||||||
|
* @param clazz Given class type
|
||||||
|
* @return Reflector
|
||||||
|
*/
|
||||||
|
public static Reflect on(Class<?> clazz) {
|
||||||
|
return new Reflect(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap an object and return its reflector.<p>
|
||||||
|
* Helpful especially when you want to access instance fields and methods on any {@link Object}
|
||||||
|
*
|
||||||
|
* @param object The object to be wrapped
|
||||||
|
* @return Reflector
|
||||||
|
*/
|
||||||
|
public static Reflect on(Object object) {
|
||||||
|
return new Reflect(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Reflect on(Method method, Object receiver, Object... args) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
makeAccessible(method);
|
||||||
|
|
||||||
|
if (method.getReturnType() == void.class) {
|
||||||
|
method.invoke(receiver, args);
|
||||||
|
return on(receiver);
|
||||||
|
} else {
|
||||||
|
return on(method.invoke(receiver, args));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make an {@link AccessibleObject} accessible.
|
||||||
|
*
|
||||||
|
* @param accessible
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static <T extends AccessibleObject> T makeAccessible(T accessible) {
|
||||||
|
if (accessible == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessible instanceof Member) {
|
||||||
|
Member member = (Member) accessible;
|
||||||
|
|
||||||
|
if (Modifier.isPublic(member.getModifiers()) &&
|
||||||
|
Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
|
||||||
|
|
||||||
|
return accessible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accessible.isAccessible()) {
|
||||||
|
accessible.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessible;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String property(String string) {
|
||||||
|
int length = string.length();
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
return "";
|
||||||
|
} else if (length == 1) {
|
||||||
|
return string.toLowerCase();
|
||||||
|
} else {
|
||||||
|
return string.substring(0, 1).toLowerCase() + string.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Reflect on(Constructor<?> constructor, Object... args) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
return on(makeAccessible(constructor).newInstance(args));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we are wrapping another reflector, get its real object.
|
||||||
|
*/
|
||||||
|
private static Object unwrap(Object object) {
|
||||||
|
if (object instanceof Reflect) {
|
||||||
|
return ((Reflect) object).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert object arrays into elements' class type arrays.
|
||||||
|
* If encountered {@code null}, use {@link NullPointer}'s class type instead.
|
||||||
|
*
|
||||||
|
* @see Object#getClass()
|
||||||
|
*/
|
||||||
|
private static Class<?>[] convertTypes(Object... values) {
|
||||||
|
if (values == null) {
|
||||||
|
return new Class[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?>[] result = new Class[values.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
Object value = values[i];
|
||||||
|
result[i] = value == null ? NullPointer.class : value.getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a class type of a class, which may cause its static-initialization
|
||||||
|
*
|
||||||
|
* @see Class#forName(String)
|
||||||
|
*/
|
||||||
|
private static Class<?> forName(String name) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
return Class.forName(name);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> forName(String name, ClassLoader classLoader) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
return Class.forName(name, true, classLoader);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap primitive class types into object class types.
|
||||||
|
*
|
||||||
|
* @param type Class type that may be primitive class type
|
||||||
|
* @return Wrapped class type
|
||||||
|
*/
|
||||||
|
private static Class<?> wrapClassType(Class<?> type) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
} else if (type.isPrimitive()) {
|
||||||
|
if (boolean.class == type) {
|
||||||
|
return Boolean.class;
|
||||||
|
} else if (int.class == type) {
|
||||||
|
return Integer.class;
|
||||||
|
} else if (long.class == type) {
|
||||||
|
return Long.class;
|
||||||
|
} else if (short.class == type) {
|
||||||
|
return Short.class;
|
||||||
|
} else if (byte.class == type) {
|
||||||
|
return Byte.class;
|
||||||
|
} else if (double.class == type) {
|
||||||
|
return Double.class;
|
||||||
|
} else if (float.class == type) {
|
||||||
|
return Float.class;
|
||||||
|
} else if (char.class == type) {
|
||||||
|
return Character.class;
|
||||||
|
} else if (void.class == type) {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the real object that reflector operates.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the real object.
|
||||||
|
* @return The real object.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get() {
|
||||||
|
return (T) mObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a field to given value.
|
||||||
|
*
|
||||||
|
* @param name Field name
|
||||||
|
* @param value New value
|
||||||
|
* @return Reflector
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect set(String name, Object value) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
Field field = lookupField(name);
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.set(mObject, unwrap(value));
|
||||||
|
return this;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of given field
|
||||||
|
*
|
||||||
|
* @param name Field name
|
||||||
|
* @param <T> The type of value
|
||||||
|
* @return Value
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public <T> T get(String name) throws ReflectionException {
|
||||||
|
return field(name).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get field by name.
|
||||||
|
*
|
||||||
|
* @param name Field name
|
||||||
|
* @return {@link Field}
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect field(String name) throws ReflectionException {
|
||||||
|
try {
|
||||||
|
Field field = lookupField(name);
|
||||||
|
return on(field.get(mObject));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field lookupField(String name) throws ReflectionException {
|
||||||
|
Class<?> type = type();
|
||||||
|
|
||||||
|
// 先尝试取得公有字段
|
||||||
|
try {
|
||||||
|
return type.getField(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//此时尝试非公有字段
|
||||||
|
catch (NoSuchFieldException e) {
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return makeAccessible(type.getDeclaredField(name));
|
||||||
|
} catch (NoSuchFieldException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.getSuperclass();
|
||||||
|
}
|
||||||
|
while (type != null);
|
||||||
|
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all fields into a map, the key is field name and the value is its reflector.
|
||||||
|
*
|
||||||
|
* @return Map to all fields.
|
||||||
|
*/
|
||||||
|
public Map<String, Reflect> fields() {
|
||||||
|
Map<String, Reflect> result = new LinkedHashMap<String, Reflect>();
|
||||||
|
Class<?> type = type();
|
||||||
|
|
||||||
|
do {
|
||||||
|
for (Field field : type.getDeclaredFields()) {
|
||||||
|
if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
|
||||||
|
String name = field.getName();
|
||||||
|
|
||||||
|
if (!result.containsKey(name))
|
||||||
|
result.put(name, field(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.getSuperclass();
|
||||||
|
}
|
||||||
|
while (type != null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a method by name without parameters.
|
||||||
|
*
|
||||||
|
* @param name Method name
|
||||||
|
* @return Reflector to the return value of the method
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect call(String name) throws ReflectionException {
|
||||||
|
return call(name, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a method by name and parameters.
|
||||||
|
*
|
||||||
|
* @param name Method name
|
||||||
|
* @param args Parameters
|
||||||
|
* @return Reflector to the return value of the method
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect call(String name, Object... args) throws ReflectionException {
|
||||||
|
Class<?>[] types = convertTypes(args);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Method method = exactMethod(name, types);
|
||||||
|
return on(method, mObject, args);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
try {
|
||||||
|
Method method = lookupSimilarMethod(name, types);
|
||||||
|
return on(method, mObject, args);
|
||||||
|
} catch (NoSuchMethodException e1) {
|
||||||
|
throw new ReflectionException(e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method exactMethod(String name, Class<?>[] types) throws NoSuchMethodException {
|
||||||
|
Class<?> type = type();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return type.getMethod(name, types);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return type.getDeclaredMethod(name, types);
|
||||||
|
} catch (NoSuchMethodException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.getSuperclass();
|
||||||
|
}
|
||||||
|
while (type != null);
|
||||||
|
|
||||||
|
throw new NoSuchMethodException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a method that is similar to the wanted one.
|
||||||
|
*/
|
||||||
|
private Method lookupSimilarMethod(String name, Class<?>[] types) throws NoSuchMethodException {
|
||||||
|
Class<?> type = type();
|
||||||
|
|
||||||
|
for (Method method : type.getMethods()) {
|
||||||
|
if (isSignatureSimilar(method, name, types)) {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
for (Method method : type.getDeclaredMethods()) {
|
||||||
|
if (isSignatureSimilar(method, name, types)) {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.getSuperclass();
|
||||||
|
}
|
||||||
|
while (type != null);
|
||||||
|
|
||||||
|
throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSignatureSimilar(Method possiblyMatchingMethod,
|
||||||
|
String wantedMethodName,
|
||||||
|
Class<?>[] wantedParamTypes) {
|
||||||
|
return possiblyMatchingMethod.getName().equals(wantedMethodName)
|
||||||
|
&& match(possiblyMatchingMethod.getParameterTypes(), wantedParamTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance using its default constructor.
|
||||||
|
*
|
||||||
|
* @return Reflector to the return value of the method
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect create() throws ReflectionException {
|
||||||
|
return create(new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance by parameters.
|
||||||
|
*
|
||||||
|
* @param args Parameters
|
||||||
|
* @return Reflector to the return value of the method
|
||||||
|
* @throws ReflectionException If any error occurs
|
||||||
|
*/
|
||||||
|
public Reflect create(Object... args) throws ReflectionException {
|
||||||
|
Class<?>[] types = convertTypes(args);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<?> constructor = type().getDeclaredConstructor(types);
|
||||||
|
return on(constructor, args);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
for (Constructor<?> constructor : type().getDeclaredConstructors()) {
|
||||||
|
if (match(constructor.getParameterTypes(), types)) {
|
||||||
|
return on(constructor, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ReflectionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dynamic proxy based on the given type.
|
||||||
|
* If we are maintaining a Map and error occurs when calling methods,
|
||||||
|
* we will return value from Map as return value.
|
||||||
|
* Helpful especially when creating default data handlers.
|
||||||
|
*
|
||||||
|
* @param proxyType The type to be proxy-ed
|
||||||
|
* @return Proxy object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <P> P as(Class<P> proxyType) {
|
||||||
|
final boolean isMap = (mObject instanceof Map);
|
||||||
|
final InvocationHandler handler = (proxy, method, args) -> {
|
||||||
|
String name = method.getName();
|
||||||
|
try {
|
||||||
|
return on(mObject).call(name, args).get();
|
||||||
|
} catch (ReflectionException e) {
|
||||||
|
if (isMap) {
|
||||||
|
Map<String, Object> map = (Map<String, Object>) mObject;
|
||||||
|
int length = (args == null ? 0 : args.length);
|
||||||
|
|
||||||
|
// Pay special attention to those getters and setters
|
||||||
|
if (length == 0 && name.startsWith("get")) {
|
||||||
|
return map.get(property(name.substring(3)));
|
||||||
|
} else if (length == 0 && name.startsWith("is")) {
|
||||||
|
return map.get(property(name.substring(2)));
|
||||||
|
} else if (length == 1 && name.startsWith("set")) {
|
||||||
|
map.put(property(name.substring(3)), args[0]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (P) Proxy.newProxyInstance(proxyType.getClassLoader(),
|
||||||
|
new Class[]{proxyType}, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether types matches to avoid {@link ClassCastException} when calling a method.
|
||||||
|
* If encountered primitive type, convert to object type first.
|
||||||
|
*/
|
||||||
|
private boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
|
||||||
|
if (declaredTypes.length == actualTypes.length) {
|
||||||
|
for (int i = 0; i < actualTypes.length; i++) {
|
||||||
|
// nulls are acceptable on any occasions
|
||||||
|
if (actualTypes[i] == NullPointer.class) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapClassType(declaredTypes[i]).isAssignableFrom(wrapClassType(actualTypes[i]))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mObject.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof Reflect) {
|
||||||
|
return mObject.equals(((Reflect) obj).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return mObject.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the class type of the real object that reflector operates.
|
||||||
|
*
|
||||||
|
* @see Object#getClass()
|
||||||
|
*/
|
||||||
|
public Class<?> type() {
|
||||||
|
if (isClass) {
|
||||||
|
return (Class<?>) mObject;
|
||||||
|
} else {
|
||||||
|
return mObject.getClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package io.neoterm.framework.reflection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author kiva
|
||||||
|
*/
|
||||||
|
public class ReflectionException extends RuntimeException {
|
||||||
|
ReflectionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ object NeoPreference {
|
|||||||
const val KEY_FONT_SIZE = "neoterm_general_font_size"
|
const val KEY_FONT_SIZE = "neoterm_general_font_size"
|
||||||
const val KEY_CURRENT_SESSION = "neoterm_service_current_session"
|
const val KEY_CURRENT_SESSION = "neoterm_service_current_session"
|
||||||
const val KEY_SYSTEM_SHELL = "neoterm_core_system_shell"
|
const val KEY_SYSTEM_SHELL = "neoterm_core_system_shell"
|
||||||
const val KEY_SOURCES = "neoterm_source_source_list"
|
const val KEY_SOURCES = "neoterm_package_enabled_sources"
|
||||||
|
|
||||||
const val VALUE_HAPPY_EGG_TRIGGER = 8
|
const val VALUE_HAPPY_EGG_TRIGGER = 8
|
||||||
|
|
||||||
@ -56,14 +56,6 @@ object NeoPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeStrings(key: String, value: Set<String>) {
|
|
||||||
preference!!.edit().putStringSet(key, value).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadStrings(key: String): Set<String> {
|
|
||||||
return preference!!.getStringSet(key, setOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun store(key: Int, value: Any) {
|
fun store(key: Int, value: Any) {
|
||||||
store(App.get().getString(key), value)
|
store(App.get().getString(key), value)
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,9 @@ object NeoTermPath {
|
|||||||
|
|
||||||
private const val SOURCE = "http://janyo.pw:82/kiva/neoterm"
|
private const val SOURCE = "http://janyo.pw:82/kiva/neoterm"
|
||||||
|
|
||||||
val DEFAULT_SOURCE: String
|
val DEFAULT_MAIN_PACKAGE_SOURCE: String
|
||||||
|
|
||||||
init {
|
init {
|
||||||
DEFAULT_SOURCE = SOURCE
|
DEFAULT_MAIN_PACKAGE_SOURCE = SOURCE
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -96,10 +96,10 @@ class ColorSchemeActivity : BaseCustomizeActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showItemEditor(model: ColorItem) {
|
private fun showItemEditor(model: ColorItem) {
|
||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_color, null, false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null, false)
|
||||||
view.findViewById<TextView>(R.id.dialog_edit_color_info).text = getString(R.string.input_new_value)
|
view.findViewById<TextView>(R.id.dialog_edit_text_info).text = getString(R.string.input_new_value)
|
||||||
|
|
||||||
val edit = view.findViewById<EditText>(R.id.dialog_edit_color_editor)
|
val edit = view.findViewById<EditText>(R.id.dialog_edit_text_editor)
|
||||||
edit.setText(model.colorValue)
|
edit.setText(model.colorValue)
|
||||||
if (model.colorValue.isNotEmpty()) {
|
if (model.colorValue.isNotEmpty()) {
|
||||||
edit.setTextColor(TerminalColors.parse(model.colorValue))
|
edit.setTextColor(TerminalColors.parse(model.colorValue))
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.neoterm.ui.pm
|
package io.neoterm.ui.pm
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.view.MenuItemCompat
|
import android.support.v4.view.MenuItemCompat
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
@ -8,16 +9,19 @@ import android.support.v7.widget.LinearLayoutManager
|
|||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.support.v7.widget.SearchView
|
import android.support.v7.widget.SearchView
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter
|
import com.github.wrdlbrnft.sortedlistadapter.SortedListAdapter
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.component.pm.PackageComponent
|
import io.neoterm.component.pm.PackageComponent
|
||||||
|
import io.neoterm.component.pm.Source
|
||||||
import io.neoterm.component.pm.SourceManager
|
import io.neoterm.component.pm.SourceManager
|
||||||
import io.neoterm.component.pm.SourceUtils
|
import io.neoterm.component.pm.SourceHelper
|
||||||
import io.neoterm.frontend.component.ComponentManager
|
import io.neoterm.frontend.component.ComponentManager
|
||||||
import io.neoterm.frontend.config.NeoPreference
|
import io.neoterm.frontend.config.NeoPreference
|
||||||
import io.neoterm.frontend.config.NeoTermPath
|
import io.neoterm.frontend.config.NeoTermPath
|
||||||
@ -109,24 +113,16 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
|||||||
|
|
||||||
private fun changeSource() {
|
private fun changeSource() {
|
||||||
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
val sourceManager = ComponentManager.getComponent<PackageComponent>().sourceManager
|
||||||
val sourceList = sourceManager.sources
|
val sourceList = sourceManager.getAllSources()
|
||||||
|
|
||||||
val currentSource = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
|
||||||
var checkedItem = sourceList.indexOf(currentSource)
|
|
||||||
if (checkedItem == -1) {
|
|
||||||
// Users may edit source.list on his own
|
|
||||||
checkedItem = sourceList.size
|
|
||||||
sourceManager.addSource(currentSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedIndex = 0
|
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.pref_package_source)
|
.setTitle(R.string.pref_package_source)
|
||||||
.setSingleChoiceItems(sourceList.toTypedArray(), checkedItem, { _, which ->
|
.setMultiChoiceItems(sourceList.map { "${it.url} :: ${it.repo}" }.toTypedArray(),
|
||||||
selectedIndex = which
|
sourceList.map { it.enabled }.toBooleanArray(), { dialog, which, isChecked ->
|
||||||
|
sourceList[which].enabled = isChecked
|
||||||
})
|
})
|
||||||
.setPositiveButton(android.R.string.yes, { _, _ ->
|
.setPositiveButton(android.R.string.yes, { _, _ ->
|
||||||
changeSourceInternal(sourceManager, sourceList.elementAt(selectedIndex))
|
changeSourceInternal(sourceManager, sourceList)
|
||||||
})
|
})
|
||||||
.setNeutralButton(R.string.new_source, { _, _ ->
|
.setNeutralButton(R.string.new_source, { _, _ ->
|
||||||
changeSourceToUserInput(sourceManager)
|
changeSourceToUserInput(sourceManager)
|
||||||
@ -135,26 +131,51 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private fun changeSourceToUserInput(sourceManager: SourceManager) {
|
private fun changeSourceToUserInput(sourceManager: SourceManager) {
|
||||||
val editText = EditText(this)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_two_text, null, false)
|
||||||
editText.setSelectAllOnFocus(true)
|
view.findViewById<TextView>(R.id.dialog_edit_text_info).text = getString(R.string.input_new_source_url)
|
||||||
|
view.findViewById<TextView>(R.id.dialog_edit_text2_info).text = getString(R.string.input_new_source_repo)
|
||||||
|
|
||||||
|
val urlEditor = view.findViewById<EditText>(R.id.dialog_edit_text_editor)
|
||||||
|
val repoEditor = view.findViewById<EditText>(R.id.dialog_edit_text2_editor)
|
||||||
|
repoEditor.setText("stable main")
|
||||||
|
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.pref_package_source)
|
.setTitle(R.string.pref_package_source)
|
||||||
.setView(editText)
|
.setView(view)
|
||||||
.setNegativeButton(android.R.string.no, null)
|
.setNegativeButton(android.R.string.no, null)
|
||||||
.setPositiveButton(android.R.string.yes, { _, _ ->
|
.setPositiveButton(android.R.string.yes, { _, _ ->
|
||||||
val source = editText.text.toString()
|
val url = urlEditor.text.toString()
|
||||||
changeSourceInternal(sourceManager, source)
|
val repo = repoEditor.text.toString()
|
||||||
|
var errored = false
|
||||||
|
if (url.trim().isEmpty()) {
|
||||||
|
urlEditor.error = getString(R.string.error_new_source_url)
|
||||||
|
errored = true
|
||||||
|
}
|
||||||
|
if (repo.trim().isEmpty()) {
|
||||||
|
repoEditor.error = getString(R.string.error_new_source_repo)
|
||||||
|
errored = true
|
||||||
|
}
|
||||||
|
if (errored) {
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
val source = urlEditor.text.toString()
|
||||||
|
sourceManager.addSource(source, repo, true)
|
||||||
|
postChangeSource(sourceManager)
|
||||||
})
|
})
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeSourceInternal(sourceManager: SourceManager, source: String) {
|
private fun changeSourceInternal(sourceManager: SourceManager, source: List<Source>) {
|
||||||
sourceManager.addSource(source)
|
sourceManager.updateAll(source)
|
||||||
|
postChangeSource(sourceManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postChangeSource(sourceManager: SourceManager) {
|
||||||
sourceManager.applyChanges()
|
sourceManager.applyChanges()
|
||||||
NeoPreference.store(R.string.key_package_source, source)
|
NeoPreference.store(R.string.key_package_source, sourceManager.getMainPackageSource())
|
||||||
PackageUtils.syncSource()
|
SourceHelper.syncSource(sourceManager)
|
||||||
executeAptUpdate()
|
executeAptUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,17 +214,11 @@ class PackageManagerActivity : AppCompatActivity(), SearchView.OnQueryTextListen
|
|||||||
models.clear()
|
models.clear()
|
||||||
Thread {
|
Thread {
|
||||||
val pm = ComponentManager.getComponent<PackageComponent>()
|
val pm = ComponentManager.getComponent<PackageComponent>()
|
||||||
val sourceFiles = SourceUtils.detectSourceFiles()
|
val sourceFiles = SourceHelper.detectSourceFiles()
|
||||||
|
|
||||||
pm.clearPackages()
|
pm.clearPackages()
|
||||||
for (index in sourceFiles.indices) {
|
sourceFiles.forEach { pm.reloadPackages(it, false) }
|
||||||
pm.reloadPackages(sourceFiles[index], false)
|
pm.packages.values.mapTo(models, { PackageModel(it) })
|
||||||
}
|
|
||||||
|
|
||||||
val packages = pm.packages
|
|
||||||
for (packageInfo in packages.values) {
|
|
||||||
models.add(PackageModel(packageInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
this@PackageManagerActivity.runOnUiThread {
|
this@PackageManagerActivity.runOnUiThread {
|
||||||
adapter.edit()
|
adapter.edit()
|
||||||
|
@ -10,6 +10,7 @@ import android.view.View
|
|||||||
import android.widget.*
|
import android.widget.*
|
||||||
import io.neoterm.App
|
import io.neoterm.App
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
|
import io.neoterm.component.pm.SourceHelper
|
||||||
import io.neoterm.frontend.config.NeoTermPath
|
import io.neoterm.frontend.config.NeoTermPath
|
||||||
import io.neoterm.setup.ResultListener
|
import io.neoterm.setup.ResultListener
|
||||||
import io.neoterm.setup.SetupHelper
|
import io.neoterm.setup.SetupHelper
|
||||||
@ -183,7 +184,7 @@ class SetupActivity : AppCompatActivity(), View.OnClickListener, ResultListener
|
|||||||
|
|
||||||
private fun setDefaultValue(parameterEditor: EditText, id: Int) {
|
private fun setDefaultValue(parameterEditor: EditText, id: Int) {
|
||||||
setupParameter = when (id) {
|
setupParameter = when (id) {
|
||||||
R.id.setup_method_online -> NeoTermPath.DEFAULT_SOURCE
|
R.id.setup_method_online -> NeoTermPath.DEFAULT_MAIN_PACKAGE_SOURCE
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
parameterEditor.setText(setupParameter)
|
parameterEditor.setText(setupParameter)
|
||||||
@ -211,7 +212,7 @@ class SetupActivity : AppCompatActivity(), View.OnClickListener, ResultListener
|
|||||||
override fun onResult(error: Exception?) {
|
override fun onResult(error: Exception?) {
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
PackageUtils.syncSource()
|
SourceHelper.syncSource()
|
||||||
executeAptUpdate()
|
executeAptUpdate()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package io.neoterm.utils
|
package io.neoterm.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import io.neoterm.R
|
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.frontend.config.NeoPreference
|
import io.neoterm.component.pm.PackageComponent
|
||||||
|
import io.neoterm.component.pm.SourceManager
|
||||||
|
import io.neoterm.frontend.component.ComponentManager
|
||||||
import io.neoterm.frontend.config.NeoTermPath
|
import io.neoterm.frontend.config.NeoTermPath
|
||||||
import io.neoterm.frontend.floating.TerminalDialog
|
import io.neoterm.frontend.floating.TerminalDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -12,21 +13,6 @@ import java.io.File
|
|||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
object PackageUtils {
|
object PackageUtils {
|
||||||
fun syncSource() {
|
|
||||||
val source = NeoPreference.loadString(R.string.key_package_source, NeoTermPath.DEFAULT_SOURCE)
|
|
||||||
val sourceFile = File(NeoTermPath.SOURCE_FILE)
|
|
||||||
FileUtils.writeFile(sourceFile, generateSourceFile(source).toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateSourceFile(source: String): String {
|
|
||||||
return StringBuilder().append("# Generated by NeoTerm-Preference\n")
|
|
||||||
.append("deb ")
|
|
||||||
.append(source)
|
|
||||||
.append(" stable main")
|
|
||||||
.append("\n")
|
|
||||||
.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun apt(context: Context, subCommand: String, extraArgs: Array<String>?, callback: (Int, TerminalDialog) -> Unit) {
|
fun apt(context: Context, subCommand: String, extraArgs: Array<String>?, callback: (Int, TerminalDialog) -> Unit) {
|
||||||
val argArray =
|
val argArray =
|
||||||
if (extraArgs != null) arrayOf(NeoTermPath.APT_BIN_PATH, subCommand, *extraArgs)
|
if (extraArgs != null) arrayOf(NeoTermPath.APT_BIN_PATH, subCommand, *extraArgs)
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/text_margin">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/dialog_edit_color_info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_edit_color_editor"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:inputType="text"
|
|
||||||
android:maxLines="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -6,6 +6,7 @@
|
|||||||
android:padding="@dimen/text_margin">
|
android:padding="@dimen/text_margin">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:labelFor="@id/dialog_edit_text_editor"
|
||||||
android:id="@+id/dialog_edit_text_info"
|
android:id="@+id/dialog_edit_text_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
36
app/src/main/res/layout/dialog_edit_two_text.xml
Normal file
36
app/src/main/res/layout/dialog_edit_two_text.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/text_margin">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_edit_text_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:labelFor="@id/dialog_edit_text_editor"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/dialog_edit_text_editor"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_edit_text2_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:labelFor="@id/dialog_edit_text2_editor"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/dialog_edit_text2_editor"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -157,6 +157,10 @@
|
|||||||
<string name="new_session_with_profile">新建 Profile 会话</string>
|
<string name="new_session_with_profile">新建 Profile 会话</string>
|
||||||
<string name="no_profile_available">没有可用的个性化配置</string>
|
<string name="no_profile_available">没有可用的个性化配置</string>
|
||||||
<string name="no_file_picker">没有文件选择器</string>
|
<string name="no_file_picker">没有文件选择器</string>
|
||||||
|
<string name="input_new_source_url">输入 URL</string>
|
||||||
|
<string name="input_new_source_repo">输入仓库名</string>
|
||||||
|
<string name="error_new_source_url">URL 不能为空</string>
|
||||||
|
<string name="error_new_source_repo">仓库 不能为空</string>
|
||||||
|
|
||||||
<string-array name="color_item_names">
|
<string-array name="color_item_names">
|
||||||
<item>背景色</item>
|
<item>背景色</item>
|
||||||
|
@ -161,6 +161,10 @@
|
|||||||
<string name="new_session_with_profile">New Session With Profile</string>
|
<string name="new_session_with_profile">New Session With Profile</string>
|
||||||
<string name="no_profile_available">No profile available</string>
|
<string name="no_profile_available">No profile available</string>
|
||||||
<string name="no_file_picker">No file picker found</string>
|
<string name="no_file_picker">No file picker found</string>
|
||||||
|
<string name="input_new_source_url">Enter new URL</string>
|
||||||
|
<string name="input_new_source_repo">Enter new Repo</string>
|
||||||
|
<string name="error_new_source_url">URL cannot be empty</string>
|
||||||
|
<string name="error_new_source_repo">Repo cannot be empty</string>
|
||||||
|
|
||||||
<string name="default_source_url">http://janyo.pw:82/kiva/neoterm</string>
|
<string name="default_source_url">http://janyo.pw:82/kiva/neoterm</string>
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package io.neoterm
|
package io.neoterm
|
||||||
|
|
||||||
import io.neoterm.component.pm.PackageComponent
|
import io.neoterm.component.pm.PackageComponent
|
||||||
import io.neoterm.component.pm.SourceUtils
|
import io.neoterm.component.pm.SourceHelper
|
||||||
import io.neoterm.frontend.component.ComponentManager
|
import io.neoterm.frontend.component.ComponentManager
|
||||||
import junit.framework.Assert.assertEquals
|
import junit.framework.Assert.assertEquals
|
||||||
import junit.framework.Assert.assertTrue
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ class PackageManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSourceUrl() {
|
fun testSourceUrl() {
|
||||||
val url = "http://7sp0th.iok.la:81/neoterm"
|
val url = "http://7sp0th.iok.la:81/neoterm"
|
||||||
println(SourceUtils.detectSourceFilePrefix(url))
|
println(SourceHelper.detectSourceFilePrefix(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user