toEvict = map.entrySet().iterator().next();
+ key = toEvict.getKey();
+ value = toEvict.getValue();
+ map.remove(key);
+ size -= safeSizeOf(key, value);
+ evictionCount++;
+ }
+
+ entryRemoved(true, key, value, null);
+ }
+ }
+
+ /**
+ * Removes the entry for {@code key} if it exists.
+ *
+ * @return the previous value mapped by {@code key}.
+ */
+ public final V remove(K key) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+
+ V previous;
+ synchronized (this) {
+ previous = map.remove(key);
+ if (previous != null) {
+ size -= safeSizeOf(key, previous);
+ }
+ }
+
+ if (previous != null) {
+ entryRemoved(false, key, previous, null);
+ }
+
+ return previous;
+ }
+
+ /**
+ * Called for entries that have been evicted or removed. This method is
+ * invoked when a value is evicted to make space, removed by a call to
+ * {@link #remove}, or replaced by a call to {@link #put}. The default
+ * implementation does nothing.
+ * The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
+ * @param evicted true if the entry is being removed to make space, false
+ * if the removal was caused by a {@link #put} or {@link #remove}.
+ * @param newValue the new value for {@code key}, if it exists. If non-null,
+ * this removal was caused by a {@link #put}. Otherwise it was caused by
+ * an eviction or a {@link #remove}.
+ */
+ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {
+ }
+
+ /**
+ * Called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The
+ * default implementation returns null.
+ *
The method is called without synchronization: other threads may
+ * access the cache while this method is executing.
+ *
If a value for {@code key} exists in the cache when this method
+ * returns, the created value will be released with {@link #entryRemoved}
+ * and discarded. This can occur when multiple threads request the same key
+ * at the same time (causing multiple values to be created), or when one
+ * thread calls {@link #put} while another is creating a value for the same
+ * key.
+ */
+ protected V create(K key) {
+ return null;
+ }
+
+ private int safeSizeOf(K key, V value) {
+ int result = sizeOf(key, value);
+ if (result < 0) {
+ throw new IllegalStateException("Negative size: " + key + "=" + value);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the size of the entry for {@code key} and {@code value} in
+ * user-defined units. The default implementation returns 1 so that size
+ * is the number of entries and max size is the maximum number of entries.
+ *
An entry's size must not change while it is in the cache.
+ */
+ protected int sizeOf(K key, V value) {
+ return 1;
+ }
+
+ /**
+ * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+ */
+ public final void evictAll() {
+ trimToSize(-1); // -1 will evict 0-sized elements
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the number
+ * of entries in the cache. For all other caches, this returns the sum of
+ * the sizes of the entries in this cache.
+ */
+ public synchronized final int size() {
+ return size;
+ }
+
+ /**
+ * For caches that do not override {@link #sizeOf}, this returns the maximum
+ * number of entries in the cache. For all other caches, this returns the
+ * maximum sum of the sizes of the entries in this cache.
+ */
+ public synchronized final int maxSize() {
+ return maxSize;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned a value that was
+ * already present in the cache.
+ */
+ public synchronized final int hitCount() {
+ return hitCount;
+ }
+
+ /**
+ * Returns the number of times {@link #get} returned null or required a new
+ * value to be created.
+ */
+ public synchronized final int missCount() {
+ return missCount;
+ }
+
+ /**
+ * Returns the number of times {@link #create(Object)} returned a value.
+ */
+ public synchronized final int createCount() {
+ return createCount;
+ }
+
+ /**
+ * Returns the number of times {@link #put} was called.
+ */
+ public synchronized final int putCount() {
+ return putCount;
+ }
+
+ /**
+ * Returns the number of values that have been evicted.
+ */
+ public synchronized final int evictionCount() {
+ return evictionCount;
+ }
+
+ /**
+ * Returns a copy of the current contents of the cache, ordered from least
+ * recently accessed to most recently accessed.
+ */
+ public synchronized final Map snapshot() {
+ return new LinkedHashMap(map);
+ }
+
+ @Override
+ public synchronized final String toString() {
+ int accesses = hitCount + missCount;
+ int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+ return String.format(Locale.getDefault(),
+ "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+ maxSize, hitCount, missCount, hitPercent);
+ }
+}
diff --git a/app/src/main/java/org/xutils/cache/LruDiskCache.java b/app/src/main/java/org/xutils/cache/LruDiskCache.java
new file mode 100644
index 0000000..8101a63
--- /dev/null
+++ b/app/src/main/java/org/xutils/cache/LruDiskCache.java
@@ -0,0 +1,391 @@
+package org.xutils.cache;
+
+
+import android.text.TextUtils;
+
+import org.xutils.DbManager;
+import org.xutils.common.task.PriorityExecutor;
+import org.xutils.common.util.FileUtil;
+import org.xutils.common.util.IOUtil;
+import org.xutils.common.util.LogUtil;
+import org.xutils.common.util.MD5;
+import org.xutils.common.util.ProcessLock;
+import org.xutils.config.DbConfigs;
+import org.xutils.db.sqlite.WhereBuilder;
+import org.xutils.ex.FileLockedException;
+import org.xutils.x;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Created by wyouflf on 15/7/23.
+ * 使用sqlite索引实现的LruDiskCache
+ */
+public final class LruDiskCache {
+
+ /**
+ * key: cacheDirName
+ */
+ private static final HashMap DISK_CACHE_MAP = new HashMap(5);
+
+ private static final int LIMIT_COUNT = 5000; // 限制最多5000条数据
+ private static final long LIMIT_SIZE = 1024L * 1024L * 100L; // 限制最多100M文件
+
+ private static final int LOCK_WAIT = 1000 * 3; // 3s
+ private static final String CACHE_DIR_NAME = "xUtils_cache";
+ private static final String TEMP_FILE_SUFFIX = ".tmp";
+
+ private boolean available = false;
+ private DbManager cacheDb;
+ private File cacheDir;
+ private long diskCacheSize = LIMIT_SIZE;
+ private final Executor trimExecutor = new PriorityExecutor(1, true);
+
+ private long lastTrimTime = 0L;
+ private static final long TRIM_TIME_SPAN = 1000;
+
+ public synchronized static LruDiskCache getDiskCache(String dirName) {
+ if (TextUtils.isEmpty(dirName)) dirName = CACHE_DIR_NAME;
+ LruDiskCache cache = DISK_CACHE_MAP.get(dirName);
+ if (cache == null) {
+ cache = new LruDiskCache(dirName);
+ DISK_CACHE_MAP.put(dirName, cache);
+ }
+ return cache;
+ }
+
+ private LruDiskCache(String dirName) {
+ try {
+ this.cacheDir = FileUtil.getCacheDir(dirName);
+ if (this.cacheDir != null && (this.cacheDir.exists() || this.cacheDir.mkdirs())) {
+ available = true;
+ }
+ this.cacheDb = x.getDb(DbConfigs.HTTP.getConfig());
+ } catch (Throwable ex) {
+ available = false;
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ deleteNoIndexFiles();
+ }
+
+ public LruDiskCache setMaxSize(long maxSize) {
+ if (maxSize > 0L) {
+ long diskFreeSize = FileUtil.getDiskAvailableSize();
+ if (diskFreeSize > maxSize) {
+ diskCacheSize = maxSize;
+ } else {
+ diskCacheSize = diskFreeSize;
+ }
+ }
+ return this;
+ }
+
+ public DiskCacheEntity get(String key) {
+ if (!available || TextUtils.isEmpty(key)) return null;
+
+ DiskCacheEntity result = null;
+ try {
+ result = this.cacheDb.selector(DiskCacheEntity.class)
+ .where("key", "=", key).findFirst();
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+
+ if (result != null) {
+
+ if (result.getExpires() < System.currentTimeMillis()) {
+ return null;
+ }
+
+ { // update hint & lastAccess...
+ final DiskCacheEntity finalResult = result;
+ trimExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ finalResult.setHits(finalResult.getHits() + 1);
+ finalResult.setLastAccess(System.currentTimeMillis());
+ try {
+ cacheDb.update(finalResult, "hits", "lastAccess");
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ });
+ }
+
+ }
+
+ return result;
+ }
+
+ public void put(DiskCacheEntity entity) {
+ if (!available
+ || entity == null
+ || TextUtils.isEmpty(entity.getTextContent())
+ || entity.getExpires() < System.currentTimeMillis()) {
+ return;
+ }
+
+ try {
+ cacheDb.replace(entity);
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+
+ trimSize();
+ }
+
+ public DiskCacheFile getDiskCacheFile(String key) throws InterruptedException {
+ if (!available || TextUtils.isEmpty(key)) {
+ return null;
+ }
+
+ DiskCacheFile result = null;
+ DiskCacheEntity entity = get(key);
+ if (entity != null && new File(entity.getPath()).exists()) {
+ ProcessLock processLock = ProcessLock.tryLock(entity.getPath(), false, LOCK_WAIT);
+ if (processLock != null && processLock.isValid()) {
+ result = new DiskCacheFile(entity.getPath(), entity, processLock);
+ if (!result.exists()) {
+ try {
+ cacheDb.delete(entity);
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ result = null;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public DiskCacheFile createDiskCacheFile(DiskCacheEntity entity) throws IOException {
+ if (!available || entity == null) {
+ return null;
+ }
+
+ DiskCacheFile result = null;
+
+ entity.setPath(new File(this.cacheDir, MD5.md5(entity.getKey())).getAbsolutePath());
+ String tempFilePath = entity.getPath() + TEMP_FILE_SUFFIX;
+ ProcessLock processLock = ProcessLock.tryLock(tempFilePath, true);
+ if (processLock != null && processLock.isValid()) {
+ result = new DiskCacheFile(tempFilePath, entity, processLock);
+ if (!result.getParentFile().exists()) {
+ result.mkdirs();
+ }
+ } else {
+ throw new FileLockedException(entity.getPath());
+ }
+
+ return result;
+ }
+
+ public void clearCacheFiles() {
+ IOUtil.deleteFileOrDir(cacheDir);
+ }
+
+ /**
+ * 添加缓存文件
+ *
+ * @param cacheFile
+ */
+ /*package*/ DiskCacheFile commitDiskCacheFile(DiskCacheFile cacheFile) throws IOException {
+ if (!available || cacheFile == null) {
+ return cacheFile;
+ }
+
+ DiskCacheFile result = null;
+ DiskCacheEntity cacheEntity = cacheFile.getCacheEntity();
+ if (cacheFile.getName().endsWith(TEMP_FILE_SUFFIX)) { // is temp file
+ ProcessLock processLock = null;
+ DiskCacheFile destFile = null;
+ try {
+ String destPath = cacheEntity.getPath();
+ processLock = ProcessLock.tryLock(destPath, true, LOCK_WAIT);
+ if (processLock != null && processLock.isValid()) { // lock
+ destFile = new DiskCacheFile(destPath, cacheEntity, processLock);
+ if (cacheFile.renameTo(destFile)) {
+ try {
+ result = destFile;
+ cacheDb.replace(cacheEntity);
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+
+ trimSize();
+ } else {
+ throw new IOException("rename:" + cacheFile.getAbsolutePath());
+ }
+ } else {
+ throw new FileLockedException(destPath);
+ }
+ } catch (InterruptedException ex) {
+ result = cacheFile;
+ LogUtil.e(ex.getMessage(), ex);
+ } finally {
+ if (result == null) {
+ result = cacheFile;
+ IOUtil.closeQuietly(destFile);
+ IOUtil.closeQuietly(processLock);
+ IOUtil.deleteFileOrDir(destFile);
+ } else {
+ IOUtil.closeQuietly(cacheFile);
+ IOUtil.deleteFileOrDir(cacheFile);
+ }
+ }
+ } else {
+ result = cacheFile;
+ }
+
+ return result;
+ }
+
+ private void trimSize() {
+ trimExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (!available) return;
+
+ long current = System.currentTimeMillis();
+ if (current - lastTrimTime < TRIM_TIME_SPAN) {
+ return;
+ } else {
+ lastTrimTime = current;
+ }
+
+ // trim expires
+ deleteExpiry();
+
+ // trim db
+ try {
+ int count = (int) cacheDb.selector(DiskCacheEntity.class).count();
+ if (count > LIMIT_COUNT + 10) {
+ List rmList = cacheDb.selector(DiskCacheEntity.class)
+ .orderBy("lastAccess").orderBy("hits")
+ .limit(count - LIMIT_COUNT).offset(0).findAll();
+ if (rmList != null && rmList.size() > 0) {
+ // delete cache files
+ for (DiskCacheEntity entity : rmList) {
+ try {
+ // delete db entity
+ cacheDb.delete(entity);
+ // delete cache files
+ String path = entity.getPath();
+ if (!TextUtils.isEmpty(path)) {
+ deleteFileWithLock(path);
+ deleteFileWithLock(path + TEMP_FILE_SUFFIX);
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+
+ }
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+
+ // trim disk
+ try {
+ while (FileUtil.getFileOrDirSize(cacheDir) > diskCacheSize) {
+ List rmList = cacheDb.selector(DiskCacheEntity.class)
+ .orderBy("lastAccess").orderBy("hits").limit(10).offset(0).findAll();
+ if (rmList != null && rmList.size() > 0) {
+ // delete cache files
+ for (DiskCacheEntity entity : rmList) {
+ try {
+ // delete db entity
+ cacheDb.delete(entity);
+ // delete cache files
+ String path = entity.getPath();
+ if (!TextUtils.isEmpty(path)) {
+ deleteFileWithLock(path);
+ deleteFileWithLock(path + TEMP_FILE_SUFFIX);
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ });
+ }
+
+ private void deleteExpiry() {
+ if (!available) return;
+
+ try {
+ WhereBuilder whereBuilder = WhereBuilder.b("expires", "<", System.currentTimeMillis());
+ List rmList = cacheDb.selector(DiskCacheEntity.class).where(whereBuilder).findAll();
+ // delete db entities
+ cacheDb.delete(DiskCacheEntity.class, whereBuilder);
+ if (rmList != null && rmList.size() > 0) {
+ // delete cache files
+ for (DiskCacheEntity entity : rmList) {
+ String path = entity.getPath();
+ if (!TextUtils.isEmpty(path)) {
+ deleteFileWithLock(path);
+ }
+ }
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * 清理未被数据库索引的历史缓存文件
+ */
+ private void deleteNoIndexFiles() {
+ trimExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ if (!available) return;
+
+ try {
+ File[] fileList = cacheDir.listFiles();
+ if (fileList != null) {
+ for (File file : fileList) {
+ try {
+ long count = cacheDb.selector(DiskCacheEntity.class)
+ .where("path", "=", file.getAbsolutePath()).count();
+ if (count < 1) {
+ IOUtil.deleteFileOrDir(file);
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ });
+ }
+
+ private boolean deleteFileWithLock(String path) {
+ ProcessLock processLock = null;
+ try {
+ processLock = ProcessLock.tryLock(path, true);
+ if (processLock != null && processLock.isValid()) { // lock
+ File file = new File(path);
+ return IOUtil.deleteFileOrDir(file);
+ }
+ } finally {
+ IOUtil.closeQuietly(processLock);
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/Callback.java b/app/src/main/java/org/xutils/common/Callback.java
new file mode 100644
index 0000000..8abd2fd
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/Callback.java
@@ -0,0 +1,72 @@
+package org.xutils.common;
+
+import java.lang.reflect.Type;
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 通用回调接口
+ */
+public interface Callback {
+
+ public interface CommonCallback extends Callback {
+ void onSuccess(ResultType result);
+
+ void onError(Throwable ex, boolean isOnCallback);
+
+ void onCancelled(CancelledException cex);
+
+ void onFinished();
+ }
+
+ public interface TypedCallback extends CommonCallback {
+ Type getLoadType();
+ }
+
+ public interface CacheCallback extends CommonCallback {
+ boolean onCache(ResultType result);
+ }
+
+ public interface ProxyCacheCallback extends CacheCallback {
+ boolean onlyCache();
+ }
+
+ public interface PrepareCallback extends CommonCallback {
+ ResultType prepare(PrepareType rawData) throws Throwable;
+ }
+
+ public interface ProgressCallback extends CommonCallback {
+ void onWaiting();
+
+ void onStarted();
+
+ void onLoading(long total, long current, boolean isDownloading);
+ }
+
+ public interface GroupCallback extends Callback {
+ void onSuccess(ItemType item);
+
+ void onError(ItemType item, Throwable ex, boolean isOnCallback);
+
+ void onCancelled(ItemType item, CancelledException cex);
+
+ void onFinished(ItemType item);
+
+ void onAllFinished();
+ }
+
+ public interface Callable {
+ void call(ResultType result);
+ }
+
+ public interface Cancelable {
+ void cancel();
+
+ boolean isCancelled();
+ }
+
+ public static class CancelledException extends RuntimeException {
+ public CancelledException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/TaskController.java b/app/src/main/java/org/xutils/common/TaskController.java
new file mode 100644
index 0000000..940cee8
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/TaskController.java
@@ -0,0 +1,54 @@
+package org.xutils.common;
+
+import org.xutils.common.task.AbsTask;
+
+/**
+ * Created by wyouflf on 15/6/11.
+ * 任务管理接口
+ */
+public interface TaskController {
+
+ /**
+ * 在UI线程执行runnable.
+ * 如果已在UI线程, 则直接执行.
+ */
+ void autoPost(Runnable runnable);
+
+ /**
+ * 在UI线程执行runnable.
+ * post到msg queue.
+ */
+ void post(Runnable runnable);
+
+ /**
+ * 在UI线程执行runnable.
+ *
+ * @param delayMillis 延迟时间(单位毫秒)
+ */
+ void postDelayed(Runnable runnable, long delayMillis);
+
+ /**
+ * 在后台线程执行runnable
+ */
+ void run(Runnable runnable);
+
+ /**
+ * 移除post或postDelayed提交的, 未执行的runnable
+ */
+ void removeCallbacks(Runnable runnable);
+
+ /**
+ * 开始一个异步任务
+ */
+ AbsTask start(AbsTask task);
+
+ /**
+ * 同步执行一个任务
+ */
+ T startSync(AbsTask task) throws Throwable;
+
+ /**
+ * 批量执行异步任务
+ */
+ > Callback.Cancelable startTasks(Callback.GroupCallback groupCallback, T... tasks);
+}
diff --git a/app/src/main/java/org/xutils/common/task/AbsTask.java b/app/src/main/java/org/xutils/common/task/AbsTask.java
new file mode 100644
index 0000000..37cf580
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/AbsTask.java
@@ -0,0 +1,154 @@
+package org.xutils.common.task;
+
+import android.os.Looper;
+
+import org.xutils.common.Callback;
+
+import java.util.concurrent.Executor;
+
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 异步任务基类
+ *
+ * @param 任务返回值类型
+ */
+public abstract class AbsTask implements Callback.Cancelable {
+
+ private TaskProxy taskProxy = null;
+ private final Callback.Cancelable cancelHandler;
+
+ private volatile boolean isCancelled = false;
+ private volatile State state = State.IDLE;
+ private ResultType result;
+
+ public AbsTask() {
+ this(null);
+ }
+
+ public AbsTask(Callback.Cancelable cancelHandler) {
+ this.cancelHandler = cancelHandler;
+ }
+
+ protected abstract ResultType doBackground() throws Throwable;
+
+ protected abstract void onSuccess(ResultType result);
+
+ protected abstract void onError(Throwable ex, boolean isCallbackError);
+
+ protected void onWaiting() {
+ }
+
+ protected void onStarted() {
+ }
+
+ protected void onUpdate(int flag, Object... args) {
+ }
+
+ protected void onCancelled(Callback.CancelledException cex) {
+ }
+
+ protected void onFinished() {
+ }
+
+ public Priority getPriority() {
+ return null;
+ }
+
+ public Executor getExecutor() {
+ return null;
+ }
+
+ public Looper customLooper() {
+ return null;
+ }
+
+ protected final void update(int flag, Object... args) {
+ if (taskProxy != null) {
+ taskProxy.onUpdate(flag, args);
+ }
+ }
+
+ /**
+ * invoked via cancel()
+ */
+ protected void cancelWorks() {
+ }
+
+ /**
+ * 取消任务时是否不等待任务彻底结束, 立即收到取消的通知.
+ *
+ * @return 是否立即响应取消回调
+ */
+ protected boolean isCancelFast() {
+ return false;
+ }
+
+ @Override
+ public final void cancel() {
+ if (this.isCancelled) return;
+ synchronized (this) {
+ if (this.isCancelled) return;
+ this.isCancelled = true;
+ cancelWorks();
+ if (cancelHandler != null && !cancelHandler.isCancelled()) {
+ cancelHandler.cancel();
+ }
+ if (this.state == State.WAITING || (this.state == State.STARTED && isCancelFast())) {
+ if (taskProxy != null) {
+ taskProxy.onCancelled(new Callback.CancelledException("cancelled by user"));
+ taskProxy.onFinished();
+ } else if (this instanceof TaskProxy) {
+ this.onCancelled(new Callback.CancelledException("cancelled by user"));
+ this.onFinished();
+ }
+ }
+ }
+ }
+
+ @Override
+ public final boolean isCancelled() {
+ return isCancelled || state == State.CANCELLED ||
+ (cancelHandler != null && cancelHandler.isCancelled());
+ }
+
+ public final boolean isFinished() {
+ return this.state.value() > State.STARTED.value();
+ }
+
+ public final State getState() {
+ return state;
+ }
+
+ public final ResultType getResult() {
+ return result;
+ }
+
+ /*package*/
+ void setState(State state) {
+ this.state = state;
+ }
+
+ /*package*/
+ final void setTaskProxy(TaskProxy taskProxy) {
+ this.taskProxy = taskProxy;
+ }
+
+ /*package*/
+ final void setResult(ResultType result) {
+ this.result = result;
+ }
+
+ public enum State {
+ IDLE(0), WAITING(1), STARTED(2), SUCCESS(3), CANCELLED(4), ERROR(5);
+ private final int value;
+
+ private State(int value) {
+ this.value = value;
+ }
+
+ public int value() {
+ return value;
+ }
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/task/Priority.java b/app/src/main/java/org/xutils/common/task/Priority.java
new file mode 100644
index 0000000..ce61044
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/Priority.java
@@ -0,0 +1,9 @@
+package org.xutils.common.task;
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 任务的优先级
+ */
+public enum Priority {
+ UI_TOP, UI_NORMAL, UI_LOW, DEFAULT, BG_TOP, BG_NORMAL, BG_LOW;
+}
diff --git a/app/src/main/java/org/xutils/common/task/PriorityExecutor.java b/app/src/main/java/org/xutils/common/task/PriorityExecutor.java
new file mode 100644
index 0000000..e342b1e
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/PriorityExecutor.java
@@ -0,0 +1,113 @@
+package org.xutils.common.task;
+
+import java.util.Comparator;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 支持优先级的线程池管理类
+ */
+public class PriorityExecutor implements Executor {
+
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAXIMUM_POOL_SIZE = 256;
+ private static final int KEEP_ALIVE = 1;
+ private static final AtomicLong SEQ_SEED = new AtomicLong(0);
+
+ private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ return new Thread(runnable, "xTID#" + mCount.getAndIncrement());
+ }
+ };
+
+ private static final Comparator FIFO_CMP = new Comparator() {
+ @Override
+ public int compare(Runnable lhs, Runnable rhs) {
+ if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) {
+ PriorityRunnable lpr = ((PriorityRunnable) lhs);
+ PriorityRunnable rpr = ((PriorityRunnable) rhs);
+ int result = lpr.priority.ordinal() - rpr.priority.ordinal();
+ return result == 0 ? (int) (lpr.SEQ - rpr.SEQ) : result;
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ private static final Comparator FILO_CMP = new Comparator() {
+ @Override
+ public int compare(Runnable lhs, Runnable rhs) {
+ if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) {
+ PriorityRunnable lpr = ((PriorityRunnable) lhs);
+ PriorityRunnable rpr = ((PriorityRunnable) rhs);
+ int result = lpr.priority.ordinal() - rpr.priority.ordinal();
+ return result == 0 ? (int) (rpr.SEQ - lpr.SEQ) : result;
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ private final ThreadPoolExecutor mThreadPoolExecutor;
+
+ /**
+ * 默认工作线程数5
+ *
+ * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务.
+ */
+ public PriorityExecutor(boolean fifo) {
+ this(CORE_POOL_SIZE, fifo);
+ }
+
+ /**
+ * @param poolSize 工作线程数
+ * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务.
+ */
+ public PriorityExecutor(int poolSize, boolean fifo) {
+ BlockingQueue mPoolWorkQueue =
+ new PriorityBlockingQueue(MAXIMUM_POOL_SIZE, fifo ? FIFO_CMP : FILO_CMP);
+ mThreadPoolExecutor = new ThreadPoolExecutor(
+ poolSize,
+ MAXIMUM_POOL_SIZE,
+ KEEP_ALIVE,
+ TimeUnit.SECONDS,
+ mPoolWorkQueue,
+ sThreadFactory);
+ }
+
+ public int getPoolSize() {
+ return mThreadPoolExecutor.getCorePoolSize();
+ }
+
+ public void setPoolSize(int poolSize) {
+ if (poolSize > 0) {
+ mThreadPoolExecutor.setCorePoolSize(poolSize);
+ }
+ }
+
+ public ThreadPoolExecutor getThreadPoolExecutor() {
+ return mThreadPoolExecutor;
+ }
+
+ public boolean isBusy() {
+ return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize();
+ }
+
+ @Override
+ public void execute(Runnable runnable) {
+ if (runnable instanceof PriorityRunnable) {
+ ((PriorityRunnable) runnable).SEQ = SEQ_SEED.getAndIncrement();
+ }
+ mThreadPoolExecutor.execute(runnable);
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/task/PriorityRunnable.java b/app/src/main/java/org/xutils/common/task/PriorityRunnable.java
new file mode 100644
index 0000000..0fd3521
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/PriorityRunnable.java
@@ -0,0 +1,23 @@
+package org.xutils.common.task;
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 带有优先级的Runnable类型(仅在task包内可用)
+ */
+/*package*/ class PriorityRunnable implements Runnable {
+
+ /*package*/ long SEQ;
+
+ public final Priority priority;
+ private final Runnable runnable;
+
+ public PriorityRunnable(Priority priority, Runnable runnable) {
+ this.priority = priority == null ? Priority.DEFAULT : priority;
+ this.runnable = runnable;
+ }
+
+ @Override
+ public final void run() {
+ this.runnable.run();
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java b/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java
new file mode 100644
index 0000000..71baf7d
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java
@@ -0,0 +1,258 @@
+package org.xutils.common.task;
+
+import android.os.Looper;
+
+import org.xutils.common.Callback;
+import org.xutils.common.TaskController;
+import org.xutils.common.util.LogUtil;
+import org.xutils.x;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by wyouflf on 15/6/5.
+ * 异步任务的管理类
+ */
+public final class TaskControllerImpl implements TaskController {
+
+ private TaskControllerImpl() {
+ }
+
+ private static volatile TaskController instance;
+
+ public static void registerInstance() {
+ if (instance == null) {
+ synchronized (TaskController.class) {
+ if (instance == null) {
+ instance = new TaskControllerImpl();
+ }
+ }
+ }
+ x.Ext.setTaskController(instance);
+ }
+
+ /**
+ * run task
+ */
+ @Override
+ public AbsTask start(AbsTask task) {
+ TaskProxy proxy = null;
+ if (task instanceof TaskProxy) {
+ proxy = (TaskProxy) task;
+ } else {
+ proxy = new TaskProxy(task);
+ }
+ try {
+ proxy.doBackground();
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ return proxy;
+ }
+
+ @Override
+ public T startSync(AbsTask task) throws Throwable {
+ T result = null;
+ try {
+ task.onWaiting();
+ task.onStarted();
+ result = task.doBackground();
+ task.onSuccess(result);
+ } catch (Callback.CancelledException cex) {
+ task.onCancelled(cex);
+ } catch (Throwable ex) {
+ task.onError(ex, false);
+ throw ex;
+ } finally {
+ task.onFinished();
+ }
+ return result;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public > Callback.Cancelable startTasks(
+ final Callback.GroupCallback groupCallback, final T... tasks) {
+
+ if (tasks == null) {
+ throw new IllegalArgumentException("task must not be null");
+ }
+
+ final Runnable callIfOnAllFinished = new Runnable() {
+ private final int total = tasks.length;
+ private final AtomicInteger count = new AtomicInteger(0);
+
+ @Override
+ public void run() {
+ if (count.incrementAndGet() == total) {
+ if (groupCallback != null) {
+ try {
+ groupCallback.onAllFinished();
+ } catch (Throwable ex) {
+ try {
+ groupCallback.onError(null, ex, true);
+ } catch (Throwable throwable) {
+ LogUtil.e(throwable.getMessage(), throwable);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ for (final T task : tasks) {
+ start(new TaskProxy(task) {
+ @Override
+ protected void onSuccess(Object result) {
+ super.onSuccess(result);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (groupCallback != null) {
+ try {
+ groupCallback.onSuccess(task);
+ } catch (Throwable ex) {
+ try {
+ groupCallback.onError(task, ex, true);
+ } catch (Throwable throwable) {
+ LogUtil.e(throwable.getMessage(), throwable);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onCancelled(final Callback.CancelledException cex) {
+ super.onCancelled(cex);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (groupCallback != null) {
+ try {
+ groupCallback.onCancelled(task, cex);
+ } catch (Throwable ex) {
+ try {
+ groupCallback.onError(task, ex, true);
+ } catch (Throwable throwable) {
+ LogUtil.e(throwable.getMessage(), throwable);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onError(final Throwable ex, final boolean isCallbackError) {
+ super.onError(ex, isCallbackError);
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (groupCallback != null) {
+ try {
+ groupCallback.onError(task, ex, isCallbackError);
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onFinished() {
+ super.onFinished();
+ post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (groupCallback != null) {
+ groupCallback.onFinished(task);
+ }
+ } catch (Throwable ex) {
+ try {
+ groupCallback.onError(task, ex, true);
+ } catch (Throwable throwable) {
+ LogUtil.e(throwable.getMessage(), throwable);
+ }
+ } finally {
+ callIfOnAllFinished.run();
+ }
+ }
+ });
+ }
+ });
+ }
+
+ return new Callback.Cancelable() {
+
+ @Override
+ public void cancel() {
+ for (T task : tasks) {
+ task.cancel();
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ boolean isCancelled = true;
+ for (T task : tasks) {
+ if (!task.isCancelled()) {
+ isCancelled = false;
+ }
+ }
+ return isCancelled;
+ }
+ };
+ }
+
+ @Override
+ public void autoPost(Runnable runnable) {
+ if (runnable == null) return;
+ if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
+ runnable.run();
+ } else {
+ TaskProxy.sHandler.post(runnable);
+ }
+ }
+
+ /**
+ * run in UI thread
+ */
+ @Override
+ public void post(Runnable runnable) {
+ if (runnable == null) return;
+ TaskProxy.sHandler.post(runnable);
+ }
+
+ /**
+ * run in UI thread
+ */
+ @Override
+ public void postDelayed(Runnable runnable, long delayMillis) {
+ if (runnable == null) return;
+ TaskProxy.sHandler.postDelayed(runnable, delayMillis);
+ }
+
+ /**
+ * run in background thread
+ */
+ @Override
+ public void run(Runnable runnable) {
+ if (!TaskProxy.sDefaultExecutor.isBusy()) {
+ TaskProxy.sDefaultExecutor.execute(runnable);
+ } else {
+ new Thread(runnable).start();
+ }
+ }
+
+ /**
+ * 移除post或postDelayed提交的, 未执行的runnable
+ */
+ @Override
+ public void removeCallbacks(Runnable runnable) {
+ TaskProxy.sHandler.removeCallbacks(runnable);
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/task/TaskProxy.java b/app/src/main/java/org/xutils/common/task/TaskProxy.java
new file mode 100644
index 0000000..4d23154
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/task/TaskProxy.java
@@ -0,0 +1,254 @@
+package org.xutils.common.task;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import org.xutils.common.Callback;
+import org.xutils.common.util.LogUtil;
+import org.xutils.x;
+
+import java.util.concurrent.Executor;
+
+/**
+ * 异步任务的代理类(仅在task包内可用)
+ *
+ * @param
+ */
+/*package*/ class TaskProxy extends AbsTask {
+
+ /*package*/ static final InternalHandler sHandler = new InternalHandler();
+ /*package*/ static final PriorityExecutor sDefaultExecutor = new PriorityExecutor(true);
+
+ private final AbsTask task;
+ private final Executor executor;
+ private final Handler handler;
+ private volatile boolean callOnCanceled = false;
+ private volatile boolean callOnFinished = false;
+
+ /*package*/ TaskProxy(AbsTask task) {
+ super(task);
+ this.task = task;
+ this.task.setTaskProxy(this);
+ this.setTaskProxy(null);
+
+ // set handler
+ Looper looper = task.customLooper();
+ if (looper != null) {
+ handler = new InternalHandler(looper);
+ } else {
+ handler = sHandler;
+ }
+
+ // set executor
+ Executor taskExecutor = task.getExecutor();
+ if (taskExecutor == null) {
+ taskExecutor = sDefaultExecutor;
+ }
+ this.executor = taskExecutor;
+ }
+
+ @Override
+ protected final ResultType doBackground() throws Throwable {
+ this.onWaiting();
+ PriorityRunnable runnable = new PriorityRunnable(
+ task.getPriority(),
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // 等待过程中取消
+ if (callOnCanceled || TaskProxy.this.isCancelled()) {
+ throw new Callback.CancelledException("");
+ }
+
+ // start running
+ TaskProxy.this.onStarted();
+
+ if (TaskProxy.this.isCancelled()) { // 开始时取消
+ throw new Callback.CancelledException("");
+ }
+
+ // 执行task, 得到结果.
+ task.setResult(task.doBackground());
+ TaskProxy.this.setResult(task.getResult());
+
+ // 未在doBackground过程中取消成功
+ if (TaskProxy.this.isCancelled()) {
+ throw new Callback.CancelledException("");
+ }
+
+ // 执行成功
+ TaskProxy.this.onSuccess(task.getResult());
+ } catch (Callback.CancelledException cex) {
+ TaskProxy.this.onCancelled(cex);
+ } catch (Throwable ex) {
+ TaskProxy.this.onError(ex, false);
+ } finally {
+ TaskProxy.this.onFinished();
+ }
+ }
+ });
+ this.executor.execute(runnable);
+ return null;
+ }
+
+ @Override
+ protected void onWaiting() {
+ this.setState(State.WAITING);
+ handler.obtainMessage(MSG_WHAT_ON_WAITING, this).sendToTarget();
+ }
+
+ @Override
+ protected void onStarted() {
+ this.setState(State.STARTED);
+ handler.obtainMessage(MSG_WHAT_ON_START, this).sendToTarget();
+ }
+
+ @Override
+ protected void onSuccess(ResultType result) {
+ this.setState(State.SUCCESS);
+ handler.obtainMessage(MSG_WHAT_ON_SUCCESS, this).sendToTarget();
+ }
+
+ @Override
+ protected void onError(Throwable ex, boolean isCallbackError) {
+ this.setState(State.ERROR);
+ handler.obtainMessage(MSG_WHAT_ON_ERROR, new ArgsObj(this, ex)).sendToTarget();
+ }
+
+ @Override
+ protected void onUpdate(int flag, Object... args) {
+ // obtainMessage(int what, int arg1, int arg2, Object obj), arg2 not be used.
+ handler.obtainMessage(MSG_WHAT_ON_UPDATE, flag, flag, new ArgsObj(this, args)).sendToTarget();
+ }
+
+ @Override
+ protected void onCancelled(Callback.CancelledException cex) {
+ this.setState(State.CANCELLED);
+ handler.obtainMessage(MSG_WHAT_ON_CANCEL, new ArgsObj(this, cex)).sendToTarget();
+ }
+
+ @Override
+ protected void onFinished() {
+ handler.obtainMessage(MSG_WHAT_ON_FINISHED, this).sendToTarget();
+ }
+
+ @Override
+ /*package*/ final void setState(State state) {
+ super.setState(state);
+ this.task.setState(state);
+ }
+
+ @Override
+ public final Priority getPriority() {
+ return task.getPriority();
+ }
+
+ @Override
+ public final Executor getExecutor() {
+ return this.executor;
+ }
+
+ // ########################### inner type #############################
+ private static class ArgsObj {
+ final TaskProxy taskProxy;
+ final Object[] args;
+
+ public ArgsObj(TaskProxy taskProxy, Object... args) {
+ this.taskProxy = taskProxy;
+ this.args = args;
+ }
+ }
+
+ private final static int MSG_WHAT_BASE = 1000000000;
+ private final static int MSG_WHAT_ON_WAITING = MSG_WHAT_BASE + 1;
+ private final static int MSG_WHAT_ON_START = MSG_WHAT_BASE + 2;
+ private final static int MSG_WHAT_ON_SUCCESS = MSG_WHAT_BASE + 3;
+ private final static int MSG_WHAT_ON_ERROR = MSG_WHAT_BASE + 4;
+ private final static int MSG_WHAT_ON_UPDATE = MSG_WHAT_BASE + 5;
+ private final static int MSG_WHAT_ON_CANCEL = MSG_WHAT_BASE + 6;
+ private final static int MSG_WHAT_ON_FINISHED = MSG_WHAT_BASE + 7;
+
+ /*package*/ final static class InternalHandler extends Handler {
+
+ private InternalHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ private InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message msg) {
+ if (msg.obj == null) {
+ throw new IllegalArgumentException("msg must not be null");
+ }
+ TaskProxy taskProxy = null;
+ Object[] args = null;
+ if (msg.obj instanceof TaskProxy) {
+ taskProxy = (TaskProxy) msg.obj;
+ } else if (msg.obj instanceof ArgsObj) {
+ ArgsObj argsObj = (ArgsObj) msg.obj;
+ taskProxy = argsObj.taskProxy;
+ args = argsObj.args;
+ }
+ if (taskProxy == null) {
+ throw new RuntimeException("msg.obj not instanceof TaskProxy");
+ }
+
+ try {
+ switch (msg.what) {
+ case MSG_WHAT_ON_WAITING: {
+ taskProxy.task.onWaiting();
+ break;
+ }
+ case MSG_WHAT_ON_START: {
+ taskProxy.task.onStarted();
+ break;
+ }
+ case MSG_WHAT_ON_SUCCESS: {
+ taskProxy.task.onSuccess(taskProxy.getResult());
+ break;
+ }
+ case MSG_WHAT_ON_ERROR: {
+ assert args != null;
+ Throwable throwable = (Throwable) args[0];
+ LogUtil.d(throwable.getMessage(), throwable);
+ taskProxy.task.onError(throwable, false);
+ break;
+ }
+ case MSG_WHAT_ON_UPDATE: {
+ taskProxy.task.onUpdate(msg.arg1, args);
+ break;
+ }
+ case MSG_WHAT_ON_CANCEL: {
+ if (taskProxy.callOnCanceled) return;
+ taskProxy.callOnCanceled = true;
+ assert args != null;
+ taskProxy.task.onCancelled((org.xutils.common.Callback.CancelledException) args[0]);
+ break;
+ }
+ case MSG_WHAT_ON_FINISHED: {
+ if (taskProxy.callOnFinished) return;
+ taskProxy.callOnFinished = true;
+ taskProxy.task.onFinished();
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ } catch (Throwable ex) {
+ taskProxy.setState(State.ERROR);
+ if (msg.what != MSG_WHAT_ON_ERROR) {
+ taskProxy.task.onError(ex, true);
+ } else if (x.isDebug()) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/DensityUtil.java b/app/src/main/java/org/xutils/common/util/DensityUtil.java
new file mode 100644
index 0000000..c32a5fa
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/DensityUtil.java
@@ -0,0 +1,44 @@
+package org.xutils.common.util;
+
+import org.xutils.x;
+
+
+public final class DensityUtil {
+
+ private static float density = -1F;
+ private static int widthPixels = -1;
+ private static int heightPixels = -1;
+
+ private DensityUtil() {
+ }
+
+ public static float getDensity() {
+ if (density <= 0F) {
+ density = x.app().getResources().getDisplayMetrics().density;
+ }
+ return density;
+ }
+
+ public static int dip2px(float dpValue) {
+ return (int) (dpValue * getDensity() + 0.5F);
+ }
+
+ public static int px2dip(float pxValue) {
+ return (int) (pxValue / getDensity() + 0.5F);
+ }
+
+ public static int getScreenWidth() {
+ if (widthPixels <= 0) {
+ widthPixels = x.app().getResources().getDisplayMetrics().widthPixels;
+ }
+ return widthPixels;
+ }
+
+
+ public static int getScreenHeight() {
+ if (heightPixels <= 0) {
+ heightPixels = x.app().getResources().getDisplayMetrics().heightPixels;
+ }
+ return heightPixels;
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java b/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java
new file mode 100644
index 0000000..76fee5f
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.common.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: wyouflf
+ * Date: 13-6-19
+ * Time: PM 1:18
+ */
+public class DoubleKeyValueMap {
+
+ private final ConcurrentHashMap> k1_k2V_map;
+
+ public DoubleKeyValueMap() {
+ this.k1_k2V_map = new ConcurrentHashMap>();
+ }
+
+ public void put(K1 key1, K2 key2, V value) {
+ if (key1 == null || key2 == null || value == null) return;
+ if (k1_k2V_map.containsKey(key1)) {
+ ConcurrentHashMap k2V_map = k1_k2V_map.get(key1);
+ if (k2V_map != null) {
+ k2V_map.put(key2, value);
+ } else {
+ k2V_map = new ConcurrentHashMap();
+ k2V_map.put(key2, value);
+ k1_k2V_map.put(key1, k2V_map);
+ }
+ } else {
+ ConcurrentHashMap k2V_map = new ConcurrentHashMap();
+ k2V_map.put(key2, value);
+ k1_k2V_map.put(key1, k2V_map);
+ }
+ }
+
+ public Set getFirstKeys() {
+ return k1_k2V_map.keySet();
+ }
+
+ public ConcurrentHashMap get(K1 key1) {
+ return k1_k2V_map.get(key1);
+ }
+
+ public V get(K1 key1, K2 key2) {
+ ConcurrentHashMap k2_v = k1_k2V_map.get(key1);
+ return k2_v == null ? null : k2_v.get(key2);
+ }
+
+ public Collection getAllValues(K1 key1) {
+ ConcurrentHashMap k2_v = k1_k2V_map.get(key1);
+ return k2_v == null ? null : k2_v.values();
+ }
+
+ public Collection getAllValues() {
+ Collection result = null;
+ Set k1Set = k1_k2V_map.keySet();
+ if (k1Set != null) {
+ result = new ArrayList();
+ for (K1 k1 : k1Set) {
+ ConcurrentHashMap value1 = k1_k2V_map.get(k1);
+ if (value1 != null) {
+ Collection values = value1.values();
+ if (values != null) {
+ result.addAll(values);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public boolean containsKey(K1 key1, K2 key2) {
+ if (k1_k2V_map.containsKey(key1)) {
+ ConcurrentHashMap value1 = k1_k2V_map.get(key1);
+ if (value1 != null) {
+ return value1.containsKey(key2);
+ }
+ }
+ return false;
+ }
+
+ public boolean containsKey(K1 key1) {
+ return k1_k2V_map.containsKey(key1);
+ }
+
+ public int size() {
+ if (k1_k2V_map.size() == 0) return 0;
+
+ int result = 0;
+ for (ConcurrentHashMap k2V_map : k1_k2V_map.values()) {
+ result += k2V_map.size();
+ }
+ return result;
+ }
+
+ public void remove(K1 key1) {
+ k1_k2V_map.remove(key1);
+ }
+
+ public void remove(K1 key1, K2 key2) {
+ ConcurrentHashMap k2_v = k1_k2V_map.get(key1);
+ if (k2_v != null) {
+ k2_v.remove(key2);
+ }
+ if (k2_v == null || k2_v.isEmpty()) {
+ k1_k2V_map.remove(key1);
+ }
+ }
+
+ public void clear() {
+ if (k1_k2V_map.size() > 0) {
+ for (ConcurrentHashMap k2V_map : k1_k2V_map.values()) {
+ k2V_map.clear();
+ }
+ k1_k2V_map.clear();
+ }
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/FileUtil.java b/app/src/main/java/org/xutils/common/util/FileUtil.java
new file mode 100644
index 0000000..829988d
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/FileUtil.java
@@ -0,0 +1,136 @@
+package org.xutils.common.util;
+
+import android.os.Environment;
+import android.os.StatFs;
+
+import org.xutils.x;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+public class FileUtil {
+
+ private FileUtil() {
+ }
+
+ public static File getCacheDir(String dirName) {
+ File result = null;
+ if (isDiskAvailable()) {
+ File cacheDir = x.app().getExternalCacheDir();
+ if (cacheDir != null) {
+ result = new File(cacheDir, dirName);
+ }
+ }
+ if (result == null) {
+ result = new File(x.app().getCacheDir(), dirName);
+ }
+ if (result.exists() || result.mkdirs()) {
+ return result;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 检查磁盘空间是否大于10mb
+ *
+ * @return true 大于
+ */
+ public static boolean isDiskAvailable() {
+ long size = getDiskAvailableSize();
+ return size > 10 * 1024 * 1024L; // > 10bm
+ }
+
+ /**
+ * 获取磁盘可用空间
+ *
+ * @return byte
+ */
+ public static long getDiskAvailableSize() {
+ if (!existsSdcard()) return 0;
+ File path = Environment.getExternalStorageDirectory(); // 取得sdcard文件路径
+ StatFs stat = new StatFs(path.getAbsolutePath());
+ long blockSize = 0;
+ long availableBlocks = 0;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ blockSize = stat.getBlockSizeLong();
+ availableBlocks = stat.getAvailableBlocksLong();
+ } else {
+ blockSize = stat.getBlockSize();
+ availableBlocks = stat.getAvailableBlocks();
+ }
+ return availableBlocks * blockSize;
+ }
+
+ public static Boolean existsSdcard() {
+ return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
+ }
+
+ public static long getFileOrDirSize(File file) {
+ if (!file.exists()) return 0;
+ if (!file.isDirectory()) return file.length();
+
+ long length = 0;
+ File[] list = file.listFiles();
+ if (list != null) { // 文件夹被删除时, 子文件正在被写入, 文件属性异常返回null.
+ for (File item : list) {
+ length += getFileOrDirSize(item);
+ }
+ }
+
+ return length;
+ }
+
+ /**
+ * 复制文件到指定文件
+ *
+ * @param fromPath 源文件
+ * @param toPath 复制到的文件
+ * @return true 成功,false 失败
+ */
+ public static boolean copy(String fromPath, String toPath) {
+ boolean result = false;
+ File from = new File(fromPath);
+ if (!from.exists()) {
+ return result;
+ }
+
+ File toFile = new File(toPath);
+ IOUtil.deleteFileOrDir(toFile);
+ File toDir = toFile.getParentFile();
+ if (toDir.exists() || toDir.mkdirs()) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ in = new FileInputStream(from);
+ out = new FileOutputStream(toFile);
+ IOUtil.copy(in, out);
+ result = true;
+ } catch (Throwable ex) {
+ LogUtil.d(ex.getMessage(), ex);
+ result = false;
+ } finally {
+ IOUtil.closeQuietly(in);
+ IOUtil.closeQuietly(out);
+ }
+ }
+ return result;
+ }
+
+ public static boolean deleteFileOrDir(File path) {
+ if (path == null || !path.exists()) {
+ return true;
+ }
+ if (path.isFile()) {
+ return path.delete();
+ }
+ File[] files = path.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteFileOrDir(file);
+ }
+ }
+ return path.delete();
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/IOUtil.java b/app/src/main/java/org/xutils/common/util/IOUtil.java
new file mode 100644
index 0000000..7d6b7b3
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/IOUtil.java
@@ -0,0 +1,127 @@
+package org.xutils.common.util;
+
+import android.database.Cursor;
+import android.text.TextUtils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+public class IOUtil {
+
+ private IOUtil() {
+ }
+
+ public static void closeQuietly(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (Throwable ex) {
+ LogUtil.d(ex.getMessage(), ex);
+ }
+ }
+ }
+
+ public static void closeQuietly(Cursor cursor) {
+ if (cursor != null) {
+ try {
+ cursor.close();
+ } catch (Throwable ex) {
+ LogUtil.d(ex.getMessage(), ex);
+ }
+ }
+ }
+
+ public static byte[] readBytes(InputStream in) throws IOException {
+ if (!(in instanceof BufferedInputStream)) {
+ in = new BufferedInputStream(in);
+ }
+ ByteArrayOutputStream out = null;
+ try {
+ out = new ByteArrayOutputStream();
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) != -1) {
+ out.write(buf, 0, len);
+ }
+ return out.toByteArray();
+ } finally {
+ closeQuietly(out);
+ }
+ }
+
+ public static byte[] readBytes(InputStream in, long skip, int size) throws IOException {
+ byte[] result = null;
+ if (skip > 0) {
+ long skipped = 0;
+ while (skip > 0 && (skipped = in.skip(skip)) > 0) {
+ skip -= skipped;
+ }
+ }
+ result = new byte[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = (byte) in.read();
+ }
+ return result;
+ }
+
+ public static String readStr(InputStream in) throws IOException {
+ return readStr(in, "UTF-8");
+ }
+
+ public static String readStr(InputStream in, String charset) throws IOException {
+ if (TextUtils.isEmpty(charset)) charset = "UTF-8";
+
+ if (!(in instanceof BufferedInputStream)) {
+ in = new BufferedInputStream(in);
+ }
+ Reader reader = new InputStreamReader(in, charset);
+ StringBuilder sb = new StringBuilder();
+ char[] buf = new char[1024];
+ int len;
+ while ((len = reader.read(buf)) >= 0) {
+ sb.append(buf, 0, len);
+ }
+ return sb.toString();
+ }
+
+ public static void writeStr(OutputStream out, String str) throws IOException {
+ writeStr(out, str, "UTF-8");
+ }
+
+ public static void writeStr(OutputStream out, String str, String charset) throws IOException {
+ if (TextUtils.isEmpty(charset)) charset = "UTF-8";
+
+ Writer writer = new OutputStreamWriter(out, charset);
+ writer.write(str);
+ writer.flush();
+ }
+
+ public static void copy(InputStream in, OutputStream out) throws IOException {
+ if (!(in instanceof BufferedInputStream)) {
+ in = new BufferedInputStream(in);
+ }
+ if (!(out instanceof BufferedOutputStream)) {
+ out = new BufferedOutputStream(out);
+ }
+ int len = 0;
+ byte[] buffer = new byte[1024];
+ while ((len = in.read(buffer)) != -1) {
+ out.write(buffer, 0, len);
+ }
+ out.flush();
+ }
+
+ public static boolean deleteFileOrDir(File path) {
+ return FileUtil.deleteFileOrDir(path);
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/KeyValue.java b/app/src/main/java/org/xutils/common/util/KeyValue.java
new file mode 100644
index 0000000..2cba986
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/KeyValue.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.common.util;
+
+public class KeyValue {
+ public final String key;
+ public final Object value;
+
+ public KeyValue(String key, Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * 获取value的字符串值, 为null时返回空字符串
+ */
+ public String getValueStrOrEmpty() {
+ return value == null ? "" : value.toString();
+ }
+
+ /**
+ * 获取value的字符串值, 为null时返回null
+ */
+ public String getValueStrOrNull() {
+ return value == null ? null : value.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ KeyValue keyValue = (KeyValue) o;
+
+ return key == null ? keyValue.key == null : key.equals(keyValue.key);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return key != null ? key.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return "KeyValue{" + "key='" + key + '\'' + ", value=" + value + '}';
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/LogUtil.java b/app/src/main/java/org/xutils/common/util/LogUtil.java
new file mode 100644
index 0000000..3914b0d
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/LogUtil.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.common.util;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.xutils.x;
+
+import java.util.Locale;
+
+/**
+ * Log工具,类似android.util.Log。
+ * tag自动产生,格式: customTagPrefix:className.methodName(L:lineNumber),
+ * customTagPrefix为空时只输出:className.methodName(L:lineNumber)。
+ * Author: wyouflf
+ * Date: 13-7-24
+ * Time: 下午12:23
+ */
+public class LogUtil {
+
+ public static String customTagPrefix = "x_log";
+
+ private LogUtil() {
+ }
+
+ private static String generateTag() {
+ StackTraceElement caller = new Throwable().getStackTrace()[2];
+ String tag = "%s.%s(L:%d)";
+ String callerClazzName = caller.getClassName();
+ callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
+ tag = String.format(Locale.getDefault(), tag, callerClazzName, caller.getMethodName(), caller.getLineNumber());
+ tag = TextUtils.isEmpty(customTagPrefix) ? tag : customTagPrefix + ":" + tag;
+ return tag;
+ }
+
+ public static void d(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.d(tag, content);
+ }
+
+ public static void d(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.d(tag, content, tr);
+ }
+
+ public static void e(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.e(tag, content);
+ }
+
+ public static void e(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.e(tag, content, tr);
+ }
+
+ public static void i(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.i(tag, content);
+ }
+
+ public static void i(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.i(tag, content, tr);
+ }
+
+ public static void v(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.v(tag, content);
+ }
+
+ public static void v(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.v(tag, content, tr);
+ }
+
+ public static void w(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.w(tag, content);
+ }
+
+ public static void w(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.w(tag, content, tr);
+ }
+
+ public static void w(Throwable tr) {
+ if (!x.isDebug()) return;
+ String tag = generateTag();
+
+ Log.w(tag, tr);
+ }
+
+
+ public static void wtf(String content) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.wtf(tag, content);
+ }
+
+ public static void wtf(String content, Throwable tr) {
+ if (!x.isDebug() || TextUtils.isEmpty(content)) return;
+ String tag = generateTag();
+
+ Log.wtf(tag, content, tr);
+ }
+
+ public static void wtf(Throwable tr) {
+ if (!x.isDebug()) return;
+ String tag = generateTag();
+
+ Log.wtf(tag, tr);
+ }
+
+}
diff --git a/app/src/main/java/org/xutils/common/util/MD5.java b/app/src/main/java/org/xutils/common/util/MD5.java
new file mode 100644
index 0000000..224de76
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/MD5.java
@@ -0,0 +1,64 @@
+package org.xutils.common.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public final class MD5 {
+
+ private MD5() {
+ }
+
+ private static final char[] hexDigits =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ public static String toHexString(byte[] bytes) {
+ if (bytes == null) return "";
+ StringBuilder hex = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ hex.append(hexDigits[(b >> 4) & 0x0F]);
+ hex.append(hexDigits[b & 0x0F]);
+ }
+ return hex.toString();
+ }
+
+ public static String md5(File file) throws IOException {
+ MessageDigest messagedigest = null;
+ FileInputStream in = null;
+ FileChannel ch = null;
+ byte[] encodeBytes = null;
+ try {
+ messagedigest = MessageDigest.getInstance("MD5");
+ in = new FileInputStream(file);
+ ch = in.getChannel();
+ MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+ messagedigest.update(byteBuffer);
+ encodeBytes = messagedigest.digest();
+ } catch (NoSuchAlgorithmException neverHappened) {
+ throw new RuntimeException(neverHappened);
+ } finally {
+ IOUtil.closeQuietly(in);
+ IOUtil.closeQuietly(ch);
+ }
+
+ return toHexString(encodeBytes);
+ }
+
+ public static String md5(String string) {
+ byte[] encodeBytes = null;
+ try {
+ encodeBytes = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
+ } catch (NoSuchAlgorithmException neverHappened) {
+ throw new RuntimeException(neverHappened);
+ } catch (UnsupportedEncodingException neverHappened) {
+ throw new RuntimeException(neverHappened);
+ }
+
+ return toHexString(encodeBytes);
+ }
+}
diff --git a/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java b/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java
new file mode 100644
index 0000000..f7ea273
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java
@@ -0,0 +1,97 @@
+package org.xutils.common.util;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+public class ParameterizedTypeUtil {
+
+ private ParameterizedTypeUtil() {
+ }
+
+ public static Type getParameterizedType(
+
+ final Type ownerType,
+ final Class> declaredClass,
+ int paramIndex) {
+
+ Class> clazz = null;
+ ParameterizedType pt = null;
+ Type[] ats = null;
+ TypeVariable>[] tps = null;
+ if (ownerType instanceof ParameterizedType) {
+ pt = (ParameterizedType) ownerType;
+ clazz = (Class>) pt.getRawType();
+ ats = pt.getActualTypeArguments();
+ tps = clazz.getTypeParameters();
+ } else {
+ clazz = (Class>) ownerType;
+ }
+ if (declaredClass == clazz) {
+ if (ats != null) {
+ return ats[paramIndex];
+ }
+ return Object.class;
+ }
+
+ Type[] types = clazz.getGenericInterfaces();
+ if (types != null) {
+ for (int i = 0; i < types.length; i++) {
+ Type t = types[i];
+ if (t instanceof ParameterizedType) {
+ Class> cls = (Class>) ((ParameterizedType) t).getRawType();
+ if (declaredClass.isAssignableFrom(cls)) {
+ try {
+ return getTrueType(getParameterizedType(t, declaredClass, paramIndex), tps, ats);
+ } catch (Throwable ex) {
+ LogUtil.w(ex.getMessage(), ex);
+ }
+ }
+ }
+ }
+ }
+
+ Class> superClass = clazz.getSuperclass();
+ if (superClass != null) {
+ if (declaredClass.isAssignableFrom(superClass)) {
+ return getTrueType(
+ getParameterizedType(clazz.getGenericSuperclass(),
+ declaredClass, paramIndex), tps, ats);
+ }
+ }
+
+ throw new IllegalArgumentException("FindGenericType:" + ownerType +
+ ", declaredClass: " + declaredClass + ", index: " + paramIndex);
+
+ }
+
+
+ private static Type getTrueType(
+
+ Type type,
+ TypeVariable>[] typeVariables,
+ Type[] actualTypes) {
+
+ if (type instanceof TypeVariable>) {
+ TypeVariable> tv = (TypeVariable>) type;
+ String name = tv.getName();
+ if (actualTypes != null) {
+ for (int i = 0; i < typeVariables.length; i++) {
+ if (name.equals(typeVariables[i].getName())) {
+ return actualTypes[i];
+ }
+ }
+ }
+ return tv;
+ } else if (type instanceof GenericArrayType) {
+ Type ct = ((GenericArrayType) type).getGenericComponentType();
+ if (ct instanceof Class>) {
+ return Array.newInstance((Class>) ct, 0).getClass();
+ }
+ }
+ return type;
+ }
+
+}
diff --git a/app/src/main/java/org/xutils/common/util/ProcessLock.java b/app/src/main/java/org/xutils/common/util/ProcessLock.java
new file mode 100644
index 0000000..9d939fb
--- /dev/null
+++ b/app/src/main/java/org/xutils/common/util/ProcessLock.java
@@ -0,0 +1,234 @@
+package org.xutils.common.util;
+
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import org.xutils.x;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.text.DecimalFormat;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 进程间锁, 仅在同一个应用中有效.
+ */
+public final class ProcessLock implements Closeable {
+
+ private final String mLockName;
+ private final FileLock mFileLock;
+ private final File mFile;
+ private final Closeable mStream;
+ private final boolean mWriteMode;
+
+ private final static String LOCK_FILE_DIR = "process_lock";
+ /**
+ * key1: lockName
+ * key2: fileLock.hashCode()
+ */
+ private final static DoubleKeyValueMap LOCK_MAP = new DoubleKeyValueMap();
+
+ static {
+ File dir = x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE);
+ IOUtil.deleteFileOrDir(dir);
+ }
+
+ private ProcessLock(String lockName, File file, FileLock fileLock, Closeable stream, boolean writeMode) {
+ mLockName = lockName;
+ mFileLock = fileLock;
+ mFile = file;
+ mStream = stream;
+ mWriteMode = writeMode;
+ }
+
+ /**
+ * 获取进程锁
+ *
+ * @param lockName 锁的名称, 相同的名称被认为是同一个锁.
+ * @param writeMode 是否写入模式(支持读并发).
+ * @return null 或 进程锁, 如果锁已经被占用, 返回null.
+ */
+ public static ProcessLock tryLock(final String lockName, final boolean writeMode) {
+ return tryLockInternal(lockName, customHash(lockName), writeMode);
+ }
+
+ /**
+ * 获取进程锁
+ *
+ * @param lockName 锁的名称, 相同的名称被认为是同一个锁.
+ * @param writeMode 是否写入模式(支持读并发).
+ * @param maxWaitTimeMillis 最大值 1000 * 60
+ * @return null 或 进程锁, 如果锁已经被占用, 则在超时时间内继续尝试获取该锁.
+ */
+ public static ProcessLock tryLock(final String lockName, final boolean writeMode, final long maxWaitTimeMillis) throws InterruptedException {
+ ProcessLock lock = null;
+ long expiryTime = System.currentTimeMillis() + maxWaitTimeMillis;
+ String hash = customHash(lockName);
+ synchronized (LOCK_MAP) {
+ while (System.currentTimeMillis() < expiryTime) {
+ lock = tryLockInternal(lockName, hash, writeMode);
+ if (lock != null) {
+ break;
+ } else {
+ try {
+ LOCK_MAP.wait(10);
+ } catch (InterruptedException iex) {
+ throw iex;
+ } catch (Throwable ignored) {
+ }
+ }
+ }
+ }
+
+ return lock;
+ }
+
+ /**
+ * 锁是否有效
+ */
+ public boolean isValid() {
+ return isValid(mFileLock);
+ }
+
+ /**
+ * 释放锁
+ */
+ public void release() {
+ release(mLockName, mFileLock, mFile, mStream);
+ }
+
+ /**
+ * 释放锁
+ */
+ @Override
+ public void close() throws IOException {
+ release();
+ }
+
+ private static boolean isValid(FileLock fileLock) {
+ return fileLock != null && fileLock.isValid();
+ }
+
+ private static void release(String lockName, FileLock fileLock, File file, Closeable stream) {
+ synchronized (LOCK_MAP) {
+ if (fileLock != null) {
+ try {
+ LOCK_MAP.remove(lockName, fileLock.hashCode());
+ ConcurrentHashMap locks = LOCK_MAP.get(lockName);
+ if (locks == null || locks.isEmpty()) {
+ IOUtil.deleteFileOrDir(file);
+ }
+
+ if (fileLock.channel().isOpen()) {
+ fileLock.release();
+ }
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ } finally {
+ IOUtil.closeQuietly(fileLock.channel());
+ }
+ }
+
+ IOUtil.closeQuietly(stream);
+
+ LOCK_MAP.notifyAll();
+ }
+ }
+
+ private final static DecimalFormat FORMAT = new DecimalFormat("0.##################");
+
+ // 取得字符串的自定义hash值, 尽量保证255字节内的hash不重复.
+ private static String customHash(String str) {
+ if (TextUtils.isEmpty(str)) return "0";
+ double hash = 0.0;
+ byte[] bytes = str.getBytes();
+ for (int i = 0; i < str.length(); i++) {
+ hash = (255.0 * hash + bytes[i]) * 0.005;
+ }
+ return FORMAT.format(hash);
+ }
+
+ private static ProcessLock tryLockInternal(final String lockName, final String hash, final boolean writeMode) {
+ synchronized (LOCK_MAP) {
+
+ ConcurrentHashMap locks = LOCK_MAP.get(lockName);
+ if (locks != null && !locks.isEmpty()) {
+ Iterator> itr = locks.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry entry = itr.next();
+ ProcessLock value = entry.getValue();
+ if (value != null) {
+ if (!value.isValid()) {
+ itr.remove();
+ } else if (writeMode) {
+ return null;
+ } else if (value.mWriteMode) {
+ return null;
+ }
+ } else {
+ itr.remove();
+ }
+ }
+ }
+
+ FileChannel channel = null;
+ Closeable stream = null;
+ try {
+ File file = new File(
+ x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE),
+ hash);
+ if (file.exists() || file.createNewFile()) {
+
+ if (writeMode) {
+ FileOutputStream out = new FileOutputStream(file, false);
+ channel = out.getChannel();
+ stream = out;
+ } else {
+ FileInputStream in = new FileInputStream(file);
+ channel = in.getChannel();
+ stream = in;
+ }
+ if (channel != null) {
+ FileLock fileLock = channel.tryLock(0L, Long.MAX_VALUE, !writeMode);
+ if (isValid(fileLock)) {
+ ProcessLock result = new ProcessLock(lockName, file, fileLock, stream, writeMode);
+ LOCK_MAP.put(lockName, fileLock.hashCode(), result);
+ return result;
+ } else {
+ release(lockName, fileLock, file, stream);
+ }
+ } else {
+ throw new IOException("can not get file channel:" + file.getAbsolutePath());
+ }
+ }
+ } catch (Throwable ex) {
+ LogUtil.d("tryLock: " + lockName + ", " + ex.getMessage());
+ IOUtil.closeQuietly(stream);
+ IOUtil.closeQuietly(channel);
+ }
+
+ LOCK_MAP.notifyAll();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return mLockName + ": " + mFile.getName();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ this.release();
+ }
+}
diff --git a/app/src/main/java/org/xutils/config/DbConfigs.java b/app/src/main/java/org/xutils/config/DbConfigs.java
new file mode 100644
index 0000000..e6453b8
--- /dev/null
+++ b/app/src/main/java/org/xutils/config/DbConfigs.java
@@ -0,0 +1,61 @@
+package org.xutils.config;
+
+import org.xutils.DbManager;
+import org.xutils.common.util.LogUtil;
+import org.xutils.ex.DbException;
+
+/**
+ * Created by wyouflf on 15/7/31.
+ * 全局db配置
+ */
+public enum DbConfigs {
+ HTTP(new DbManager.DaoConfig()
+ .setDbName("xUtils_http_cache.db")
+ .setDbVersion(2)
+ .setDbOpenListener(new DbManager.DbOpenListener() {
+ @Override
+ public void onDbOpened(DbManager db) {
+ db.getDatabase().enableWriteAheadLogging();
+ }
+ })
+ .setDbUpgradeListener(new DbManager.DbUpgradeListener() {
+ @Override
+ public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
+ try {
+ db.dropDb(); // 默认删除所有表
+ } catch (DbException ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ })),
+
+ COOKIE(new DbManager.DaoConfig()
+ .setDbName("xUtils_http_cookie.db")
+ .setDbVersion(1)
+ .setDbOpenListener(new DbManager.DbOpenListener() {
+ @Override
+ public void onDbOpened(DbManager db) {
+ db.getDatabase().enableWriteAheadLogging();
+ }
+ })
+ .setDbUpgradeListener(new DbManager.DbUpgradeListener() {
+ @Override
+ public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
+ try {
+ db.dropDb(); // 默认删除所有表
+ } catch (DbException ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }));
+
+ private DbManager.DaoConfig config;
+
+ DbConfigs(DbManager.DaoConfig config) {
+ this.config = config;
+ }
+
+ public DbManager.DaoConfig getConfig() {
+ return config;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/CursorUtils.java b/app/src/main/java/org/xutils/db/CursorUtils.java
new file mode 100644
index 0000000..84296df
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/CursorUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db;
+
+import android.database.Cursor;
+
+import org.xutils.db.table.ColumnEntity;
+import org.xutils.db.table.DbModel;
+import org.xutils.db.table.TableEntity;
+
+import java.util.HashMap;
+
+/*package*/ final class CursorUtils {
+
+ public static T getEntity(TableEntity table, final Cursor cursor) throws Throwable {
+ T entity = table.createEntity();
+ HashMap columnMap = table.getColumnMap();
+ int columnCount = cursor.getColumnCount();
+ for (int i = 0; i < columnCount; i++) {
+ String columnName = cursor.getColumnName(i);
+ ColumnEntity column = columnMap.get(columnName);
+ if (column != null) {
+ column.setValueFromCursor(entity, cursor, i);
+ }
+ }
+ return entity;
+ }
+
+ public static DbModel getDbModel(final Cursor cursor) {
+ DbModel result = new DbModel();
+ int columnCount = cursor.getColumnCount();
+ for (int i = 0; i < columnCount; i++) {
+ result.add(cursor.getColumnName(i), cursor.getString(i));
+ }
+ return result;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/DbManagerImpl.java b/app/src/main/java/org/xutils/db/DbManagerImpl.java
new file mode 100644
index 0000000..7266d85
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/DbManagerImpl.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Build;
+
+import org.xutils.DbManager;
+import org.xutils.common.util.IOUtil;
+import org.xutils.common.util.KeyValue;
+import org.xutils.common.util.LogUtil;
+import org.xutils.db.sqlite.SqlInfo;
+import org.xutils.db.sqlite.SqlInfoBuilder;
+import org.xutils.db.sqlite.WhereBuilder;
+import org.xutils.db.table.ColumnEntity;
+import org.xutils.db.table.DbBase;
+import org.xutils.db.table.DbModel;
+import org.xutils.db.table.TableEntity;
+import org.xutils.ex.DbException;
+import org.xutils.x;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class DbManagerImpl extends DbBase {
+
+ //*************************************** create instance ****************************************************
+
+ /**
+ * key: dbName
+ */
+ private final static HashMap DAO_MAP = new HashMap();
+
+ private SQLiteDatabase database;
+ private DaoConfig daoConfig;
+ private boolean allowTransaction;
+
+ private DbManagerImpl(DaoConfig config) throws DbException {
+ if (config == null) {
+ throw new IllegalArgumentException("daoConfig may not be null");
+ }
+
+ this.daoConfig = config;
+ this.allowTransaction = config.isAllowTransaction();
+ try {
+ this.database = openOrCreateDatabase(config);
+ DbOpenListener dbOpenListener = config.getDbOpenListener();
+ if (dbOpenListener != null) {
+ dbOpenListener.onDbOpened(this);
+ }
+ } catch (DbException ex) {
+ IOUtil.closeQuietly(this.database);
+ throw ex;
+ } catch (Throwable ex) {
+ IOUtil.closeQuietly(this.database);
+ throw new DbException(ex.getMessage(), ex);
+ }
+ }
+
+ public synchronized static DbManager getInstance(DaoConfig daoConfig) throws DbException {
+
+ if (daoConfig == null) {//使用默认配置
+ daoConfig = new DaoConfig();
+ }
+
+ DbManagerImpl dao = DAO_MAP.get(daoConfig);
+ if (dao == null) {
+ dao = new DbManagerImpl(daoConfig);
+ DAO_MAP.put(daoConfig, dao);
+ } else {
+ dao.daoConfig = daoConfig;
+ }
+
+ // update the database if needed
+ SQLiteDatabase database = dao.database;
+ int oldVersion = database.getVersion();
+ int newVersion = daoConfig.getDbVersion();
+ if (oldVersion != newVersion) {
+ if (oldVersion != 0) {
+ DbUpgradeListener upgradeListener = daoConfig.getDbUpgradeListener();
+ if (upgradeListener != null) {
+ upgradeListener.onUpgrade(dao, oldVersion, newVersion);
+ } else {
+ dao.dropDb();
+ }
+ }
+ database.setVersion(newVersion);
+ }
+
+ return dao;
+ }
+
+ @Override
+ public SQLiteDatabase getDatabase() {
+ return database;
+ }
+
+ @Override
+ public DaoConfig getDaoConfig() {
+ return daoConfig;
+ }
+
+ //*********************************************** operations ********************************************************
+
+ @Override
+ public void saveOrUpdate(Object entity) throws DbException {
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ table.createTableIfNotExists();
+ for (Object item : entities) {
+ saveOrUpdateWithoutTransaction(table, item);
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ table.createTableIfNotExists();
+ saveOrUpdateWithoutTransaction(table, entity);
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public void replace(Object entity) throws DbException {
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ table.createTableIfNotExists();
+ for (Object item : entities) {
+ execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, item));
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ table.createTableIfNotExists();
+ execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity));
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public void save(Object entity) throws DbException {
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ table.createTableIfNotExists();
+ for (Object item : entities) {
+ execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item));
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ table.createTableIfNotExists();
+ execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity));
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public boolean saveBindingId(Object entity) throws DbException {
+ boolean result = false;
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return false;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ table.createTableIfNotExists();
+ for (Object item : entities) {
+ if (!saveBindingIdWithoutTransaction(table, item)) {
+ throw new DbException("saveBindingId error, transaction will not commit!");
+ }
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ table.createTableIfNotExists();
+ result = saveBindingIdWithoutTransaction(table, entity);
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ return result;
+ }
+
+ @Override
+ public void deleteById(Class> entityType, Object idValue) throws DbException {
+ TableEntity> table = this.getTable(entityType);
+ if (!table.tableIsExists()) return;
+ try {
+ beginTransaction();
+
+ execNonQuery(SqlInfoBuilder.buildDeleteSqlInfoById(table, idValue));
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public void delete(Object entity) throws DbException {
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ if (!table.tableIsExists()) return;
+ for (Object item : entities) {
+ execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, item));
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ if (!table.tableIsExists()) return;
+ execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, entity));
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public void delete(Class> entityType) throws DbException {
+ delete(entityType, null);
+ }
+
+ @Override
+ public int delete(Class> entityType, WhereBuilder whereBuilder) throws DbException {
+ TableEntity> table = this.getTable(entityType);
+ if (!table.tableIsExists()) return 0;
+ int result = 0;
+ try {
+ beginTransaction();
+
+ result = executeUpdateDelete(SqlInfoBuilder.buildDeleteSqlInfo(table, whereBuilder));
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ return result;
+ }
+
+ @Override
+ public void update(Object entity, String... updateColumnNames) throws DbException {
+ try {
+ beginTransaction();
+
+ if (entity instanceof List) {
+ List> entities = (List>) entity;
+ if (entities.isEmpty()) return;
+ TableEntity> table = this.getTable(entities.get(0).getClass());
+ if (!table.tableIsExists()) return;
+ for (Object item : entities) {
+ execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, item, updateColumnNames));
+ }
+ } else {
+ TableEntity> table = this.getTable(entity.getClass());
+ if (!table.tableIsExists()) return;
+ execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity, updateColumnNames));
+ }
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+ }
+
+ @Override
+ public int update(Class> entityType, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException {
+ TableEntity> table = this.getTable(entityType);
+ if (!table.tableIsExists()) return 0;
+
+ int result = 0;
+ try {
+ beginTransaction();
+
+ result = executeUpdateDelete(SqlInfoBuilder.buildUpdateSqlInfo(table, whereBuilder, nameValuePairs));
+
+ setTransactionSuccessful();
+ } finally {
+ endTransaction();
+ }
+
+ return result;
+ }
+
+ @Override
+ public T findById(Class entityType, Object idValue) throws DbException {
+ TableEntity table = this.getTable(entityType);
+ if (!table.tableIsExists()) return null;
+
+ Selector selector = Selector.from(table).where(table.getId().getName(), "=", idValue);
+ String sql = selector.limit(1).toString();
+ Cursor cursor = execQuery(sql);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ return CursorUtils.getEntity(table, cursor);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public T findFirst(Class entityType) throws DbException {
+ return this.selector(entityType).findFirst();
+ }
+
+ @Override
+ public List findAll(Class entityType) throws DbException {
+ return this.selector(entityType).findAll();
+ }
+
+ @Override
+ public Selector selector(Class entityType) throws DbException {
+ return Selector.from(this.getTable(entityType));
+ }
+
+ @Override
+ public DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException {
+ Cursor cursor = execQuery(sqlInfo);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ return CursorUtils.getDbModel(cursor);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List findDbModelAll(SqlInfo sqlInfo) throws DbException {
+ List dbModelList = new ArrayList();
+
+ Cursor cursor = execQuery(sqlInfo);
+ if (cursor != null) {
+ try {
+ while (cursor.moveToNext()) {
+ dbModelList.add(CursorUtils.getDbModel(cursor));
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return dbModelList;
+ }
+
+ //******************************************** config ******************************************************
+
+ private SQLiteDatabase openOrCreateDatabase(DaoConfig config) {
+ SQLiteDatabase result = null;
+
+ File dbDir = config.getDbDir();
+ if (dbDir != null && (dbDir.exists() || dbDir.mkdirs())) {
+ File dbFile = new File(dbDir, config.getDbName());
+ result = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
+ } else {
+ result = x.app().openOrCreateDatabase(config.getDbName(), 0, null);
+ }
+ return result;
+ }
+
+ //***************************** private operations with out transaction *****************************
+ private void saveOrUpdateWithoutTransaction(TableEntity> table, Object entity) throws DbException {
+ ColumnEntity id = table.getId();
+ if (id.isAutoId()) {
+ if (id.getColumnValue(entity) != null) {
+ execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity));
+ } else {
+ saveBindingIdWithoutTransaction(table, entity);
+ }
+ } else {
+ execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity));
+ }
+ }
+
+ private boolean saveBindingIdWithoutTransaction(TableEntity> table, Object entity) throws DbException {
+ ColumnEntity id = table.getId();
+ if (id.isAutoId()) {
+ execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity));
+ long idValue = getLastAutoIncrementId(table.getName());
+ if (idValue == -1) {
+ return false;
+ }
+ id.setAutoIdValue(entity, idValue);
+ return true;
+ } else {
+ execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity));
+ return true;
+ }
+ }
+
+ //************************************************ tools ***********************************
+
+ private long getLastAutoIncrementId(String tableName) throws DbException {
+ long id = -1;
+ Cursor cursor = execQuery("SELECT seq FROM sqlite_sequence WHERE name='" + tableName + "' LIMIT 1");
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ id = cursor.getLong(0);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return id;
+ }
+
+ /**
+ * 关闭数据库.
+ * 同一个库的是单实例的, 尽量不要调用这个方法, 会自动释放.
+ */
+ @Override
+ public void close() throws IOException {
+ if (DAO_MAP.containsKey(daoConfig)) {
+ DAO_MAP.remove(daoConfig);
+ this.database.close();
+ }
+ }
+
+ ///////////////////////////////////// exec sql /////////////////////////////////////////////////////
+
+ private void beginTransaction() {
+ if (allowTransaction) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && database.isWriteAheadLoggingEnabled()) {
+ database.beginTransactionNonExclusive();
+ } else {
+ database.beginTransaction();
+ }
+ }
+ }
+
+ private void setTransactionSuccessful() {
+ if (allowTransaction) {
+ database.setTransactionSuccessful();
+ }
+ }
+
+ private void endTransaction() {
+ if (allowTransaction) {
+ database.endTransaction();
+ }
+ }
+
+
+ @Override
+ public int executeUpdateDelete(SqlInfo sqlInfo) throws DbException {
+ SQLiteStatement statement = null;
+ try {
+ statement = sqlInfo.buildStatement(database);
+ return statement.executeUpdateDelete();
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ if (statement != null) {
+ try {
+ statement.releaseReference();
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int executeUpdateDelete(String sql) throws DbException {
+ SQLiteStatement statement = null;
+ try {
+ statement = database.compileStatement(sql);
+ return statement.executeUpdateDelete();
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ if (statement != null) {
+ try {
+ statement.releaseReference();
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void execNonQuery(SqlInfo sqlInfo) throws DbException {
+ SQLiteStatement statement = null;
+ try {
+ statement = sqlInfo.buildStatement(database);
+ statement.execute();
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ if (statement != null) {
+ try {
+ statement.releaseReference();
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void execNonQuery(String sql) throws DbException {
+ try {
+ database.execSQL(sql);
+ } catch (Throwable e) {
+ throw new DbException(e);
+ }
+ }
+
+ @Override
+ public Cursor execQuery(SqlInfo sqlInfo) throws DbException {
+ try {
+ return database.rawQuery(sqlInfo.getSql(), sqlInfo.getBindArgsAsStrArray());
+ } catch (Throwable e) {
+ throw new DbException(e);
+ }
+ }
+
+ @Override
+ public Cursor execQuery(String sql) throws DbException {
+ try {
+ return database.rawQuery(sql, null);
+ } catch (Throwable e) {
+ throw new DbException(e);
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/xutils/db/DbModelSelector.java b/app/src/main/java/org/xutils/db/DbModelSelector.java
new file mode 100644
index 0000000..0fe2003
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/DbModelSelector.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db;
+
+import android.database.Cursor;
+import android.text.TextUtils;
+
+import org.xutils.common.util.IOUtil;
+import org.xutils.db.sqlite.WhereBuilder;
+import org.xutils.db.table.DbModel;
+import org.xutils.db.table.TableEntity;
+import org.xutils.ex.DbException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: wyouflf
+ * Date: 13-8-10
+ * Time: 下午2:15
+ */
+public final class DbModelSelector {
+
+ private String[] columnExpressions;
+ private String groupByColumnName;
+ private WhereBuilder having;
+
+ private Selector> selector;
+
+ private DbModelSelector(TableEntity> table) {
+ selector = Selector.from(table);
+ }
+
+ protected DbModelSelector(Selector> selector, String groupByColumnName) {
+ this.selector = selector;
+ this.groupByColumnName = groupByColumnName;
+ }
+
+ protected DbModelSelector(Selector> selector, String[] columnExpressions) {
+ this.selector = selector;
+ this.columnExpressions = columnExpressions;
+ }
+
+ /*package*/
+ static DbModelSelector from(TableEntity> table) {
+ return new DbModelSelector(table);
+ }
+
+ public DbModelSelector where(WhereBuilder whereBuilder) {
+ selector.where(whereBuilder);
+ return this;
+ }
+
+ public DbModelSelector where(String columnName, String op, Object value) {
+ selector.where(columnName, op, value);
+ return this;
+ }
+
+ public DbModelSelector and(String columnName, String op, Object value) {
+ selector.and(columnName, op, value);
+ return this;
+ }
+
+ public DbModelSelector and(WhereBuilder where) {
+ selector.and(where);
+ return this;
+ }
+
+ public DbModelSelector or(String columnName, String op, Object value) {
+ selector.or(columnName, op, value);
+ return this;
+ }
+
+ public DbModelSelector or(WhereBuilder where) {
+ selector.or(where);
+ return this;
+ }
+
+ public DbModelSelector expr(String expr) {
+ selector.expr(expr);
+ return this;
+ }
+
+ public DbModelSelector groupBy(String columnName) {
+ this.groupByColumnName = columnName;
+ return this;
+ }
+
+ public DbModelSelector having(WhereBuilder whereBuilder) {
+ this.having = whereBuilder;
+ return this;
+ }
+
+ public DbModelSelector select(String... columnExpressions) {
+ this.columnExpressions = columnExpressions;
+ return this;
+ }
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public DbModelSelector orderBy(String columnName) {
+ selector.orderBy(columnName);
+ return this;
+ }
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public DbModelSelector orderBy(String columnName, boolean desc) {
+ selector.orderBy(columnName, desc);
+ return this;
+ }
+
+ public DbModelSelector limit(int limit) {
+ selector.limit(limit);
+ return this;
+ }
+
+ public DbModelSelector offset(int offset) {
+ selector.offset(offset);
+ return this;
+ }
+
+ public TableEntity> getTable() {
+ return selector.getTable();
+ }
+
+ public DbModel findFirst() throws DbException {
+ TableEntity> table = selector.getTable();
+ if (!table.tableIsExists()) return null;
+
+ this.limit(1);
+ Cursor cursor = table.getDb().execQuery(this.toString());
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ return CursorUtils.getDbModel(cursor);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return null;
+ }
+
+ public List findAll() throws DbException {
+ TableEntity> table = selector.getTable();
+ if (!table.tableIsExists()) return null;
+
+ List result = null;
+
+ Cursor cursor = table.getDb().execQuery(this.toString());
+ if (cursor != null) {
+ try {
+ result = new ArrayList();
+ while (cursor.moveToNext()) {
+ DbModel entity = CursorUtils.getDbModel(cursor);
+ result.add(entity);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("SELECT ");
+ if (columnExpressions != null && columnExpressions.length > 0) {
+ for (String columnExpression : columnExpressions) {
+ result.append(columnExpression);
+ result.append(",");
+ }
+ result.deleteCharAt(result.length() - 1);
+ } else {
+ if (!TextUtils.isEmpty(groupByColumnName)) {
+ result.append(groupByColumnName);
+ } else {
+ result.append("*");
+ }
+ }
+ result.append(" FROM ").append("\"").append(selector.getTable().getName()).append("\"");
+ WhereBuilder whereBuilder = selector.getWhereBuilder();
+ if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
+ result.append(" WHERE ").append(whereBuilder.toString());
+ }
+ if (!TextUtils.isEmpty(groupByColumnName)) {
+ result.append(" GROUP BY ").append("\"").append(groupByColumnName).append("\"");
+ if (having != null && having.getWhereItemSize() > 0) {
+ result.append(" HAVING ").append(having.toString());
+ }
+ }
+ List orderByList = selector.getOrderByList();
+ if (orderByList != null && orderByList.size() > 0) {
+ result.append(" ORDER BY ");
+ for (Selector.OrderBy orderBy : orderByList) {
+ result.append(orderBy.toString()).append(',');
+ }
+ result.deleteCharAt(result.length() - 1);
+ }
+ if (selector.getLimit() > 0) {
+ result.append(" LIMIT ").append(selector.getLimit());
+ result.append(" OFFSET ").append(selector.getOffset());
+ }
+ return result.toString();
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/Selector.java b/app/src/main/java/org/xutils/db/Selector.java
new file mode 100644
index 0000000..95879fc
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/Selector.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db;
+
+import android.database.Cursor;
+
+import org.xutils.common.util.IOUtil;
+import org.xutils.db.sqlite.WhereBuilder;
+import org.xutils.db.table.DbModel;
+import org.xutils.db.table.TableEntity;
+import org.xutils.ex.DbException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Author: wyouflf
+ * Date: 13-8-9
+ * Time: 下午10:19
+ */
+public final class Selector {
+
+ private final TableEntity table;
+
+ private WhereBuilder whereBuilder;
+ private List orderByList;
+ private int limit = 0;
+ private int offset = 0;
+
+ private Selector(TableEntity table) {
+ this.table = table;
+ }
+
+ /*package*/
+ static Selector from(TableEntity table) {
+ return new Selector(table);
+ }
+
+ public Selector where(WhereBuilder whereBuilder) {
+ this.whereBuilder = whereBuilder;
+ return this;
+ }
+
+ public Selector where(String columnName, String op, Object value) {
+ this.whereBuilder = WhereBuilder.b(columnName, op, value);
+ return this;
+ }
+
+ public Selector and(String columnName, String op, Object value) {
+ this.whereBuilder.and(columnName, op, value);
+ return this;
+ }
+
+ public Selector and(WhereBuilder where) {
+ this.whereBuilder.and(where);
+ return this;
+ }
+
+ public Selector or(String columnName, String op, Object value) {
+ this.whereBuilder.or(columnName, op, value);
+ return this;
+ }
+
+ public Selector or(WhereBuilder where) {
+ this.whereBuilder.or(where);
+ return this;
+ }
+
+ public Selector expr(String expr) {
+ if (this.whereBuilder == null) {
+ this.whereBuilder = WhereBuilder.b();
+ }
+ this.whereBuilder.expr(expr);
+ return this;
+ }
+
+ public DbModelSelector groupBy(String columnName) {
+ return new DbModelSelector(this, columnName);
+ }
+
+ public DbModelSelector select(String... columnExpressions) {
+ return new DbModelSelector(this, columnExpressions);
+ }
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public Selector orderBy(String columnName) {
+ if (orderByList == null) {
+ orderByList = new ArrayList(5);
+ }
+ orderByList.add(new OrderBy(columnName));
+ return this;
+ }
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public Selector orderBy(String columnName, boolean desc) {
+ if (orderByList == null) {
+ orderByList = new ArrayList(5);
+ }
+ orderByList.add(new OrderBy(columnName, desc));
+ return this;
+ }
+
+ public Selector limit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Selector offset(int offset) {
+ this.offset = offset;
+ return this;
+ }
+
+ public TableEntity getTable() {
+ return table;
+ }
+
+ public WhereBuilder getWhereBuilder() {
+ return whereBuilder;
+ }
+
+ public List getOrderByList() {
+ return orderByList;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getOffset() {
+ return offset;
+ }
+
+ public T findFirst() throws DbException {
+ if (!table.tableIsExists()) return null;
+
+ this.limit(1);
+ Cursor cursor = table.getDb().execQuery(this.toString());
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ return CursorUtils.getEntity(table, cursor);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return null;
+ }
+
+ public List findAll() throws DbException {
+ if (!table.tableIsExists()) return null;
+
+ List result = null;
+ Cursor cursor = table.getDb().execQuery(this.toString());
+ if (cursor != null) {
+ try {
+ result = new ArrayList();
+ while (cursor.moveToNext()) {
+ T entity = CursorUtils.getEntity(table, cursor);
+ result.add(entity);
+ }
+ } catch (Throwable e) {
+ throw new DbException(e);
+ } finally {
+ IOUtil.closeQuietly(cursor);
+ }
+ }
+ return result;
+ }
+
+ public long count() throws DbException {
+ if (!table.tableIsExists()) return 0;
+
+ DbModelSelector dmSelector = this.select("count(\"" + table.getId().getName() + "\") as count");
+ DbModel firstModel = dmSelector.findFirst();
+ if (firstModel != null) {
+ return firstModel.getLong("count", 0);
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("SELECT ");
+ result.append("*");
+ result.append(" FROM ").append("\"").append(table.getName()).append("\"");
+ if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) {
+ result.append(" WHERE ").append(whereBuilder.toString());
+ }
+ if (orderByList != null && orderByList.size() > 0) {
+ result.append(" ORDER BY ");
+ for (OrderBy orderBy : orderByList) {
+ result.append(orderBy.toString()).append(',');
+ }
+ result.deleteCharAt(result.length() - 1);
+ }
+ if (limit > 0) {
+ result.append(" LIMIT ").append(limit);
+ result.append(" OFFSET ").append(offset);
+ }
+ return result.toString();
+ }
+
+ public static class OrderBy {
+ private String columnName;
+ private boolean desc;
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public OrderBy(String columnName) {
+ this.columnName = columnName;
+ }
+
+ /**
+ * 排序条件, 默认ASC
+ */
+ public OrderBy(String columnName, boolean desc) {
+ this.columnName = columnName;
+ this.desc = desc;
+ }
+
+ @Override
+ public String toString() {
+ return "\"" + columnName + "\"" + (desc ? " DESC" : " ASC");
+ }
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/annotation/Column.java b/app/src/main/java/org/xutils/db/annotation/Column.java
new file mode 100644
index 0000000..b36f28d
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/annotation/Column.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Column {
+
+ String name();
+
+ String property() default "";
+
+ boolean isId() default false;
+
+ boolean autoGen() default true;
+}
diff --git a/app/src/main/java/org/xutils/db/annotation/Table.java b/app/src/main/java/org/xutils/db/annotation/Table.java
new file mode 100644
index 0000000..0189dba
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/annotation/Table.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.xutils.db.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Table {
+
+ String name();
+
+ String onCreated() default "";
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java b/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java
new file mode 100644
index 0000000..cf3f5df
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java
@@ -0,0 +1,28 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class BooleanColumnConverter implements ColumnConverter {
+ @Override
+ public Boolean getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getInt(index) == 1;
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Boolean fieldValue) {
+ if (fieldValue == null) return null;
+ return fieldValue ? 1 : 0;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java
new file mode 100644
index 0000000..f9de5a0
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class ByteArrayColumnConverter implements ColumnConverter {
+ @Override
+ public byte[] getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getBlob(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(byte[] fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.BLOB;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java
new file mode 100644
index 0000000..e574bec
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class ByteColumnConverter implements ColumnConverter {
+ @Override
+ public Byte getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : (byte) cursor.getInt(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Byte fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java b/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java
new file mode 100644
index 0000000..4d39523
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java
@@ -0,0 +1,28 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class CharColumnConverter implements ColumnConverter {
+ @Override
+ public Character getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : (char) cursor.getInt(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Character fieldValue) {
+ if (fieldValue == null) return null;
+ return (int) fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/ColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ColumnConverter.java
new file mode 100644
index 0000000..9b6b0ec
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/ColumnConverter.java
@@ -0,0 +1,19 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午8:57
+ */
+public interface ColumnConverter {
+
+ T getFieldValue(final Cursor cursor, int index);
+
+ Object fieldValue2DbValue(T fieldValue);
+
+ ColumnDbType getColumnDbType();
+}
diff --git a/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java b/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java
new file mode 100644
index 0000000..68dc20a
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java
@@ -0,0 +1,108 @@
+package org.xutils.db.converter;
+
+import org.xutils.common.util.LogUtil;
+
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:27
+ */
+public final class ColumnConverterFactory {
+
+ private ColumnConverterFactory() {
+ }
+
+ public static ColumnConverter getColumnConverter(Class columnType) {
+ ColumnConverter result = null;
+ if (columnType_columnConverter_map.containsKey(columnType.getName())) {
+ result = columnType_columnConverter_map.get(columnType.getName());
+ } else if (ColumnConverter.class.isAssignableFrom(columnType)) {
+ try {
+ ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance();
+ columnType_columnConverter_map.put(columnType.getName(), columnConverter);
+ result = columnConverter;
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+
+ if (result == null) {
+ throw new RuntimeException("Database Column Not Support: " + columnType.getName() +
+ ", please impl ColumnConverter or use ColumnConverterFactory#registerColumnConverter(...)");
+ }
+
+ return result;
+ }
+
+ public static void registerColumnConverter(Class columnType, ColumnConverter columnConverter) {
+ columnType_columnConverter_map.put(columnType.getName(), columnConverter);
+ }
+
+ public static boolean isSupportColumnConverter(Class columnType) {
+ if (columnType_columnConverter_map.containsKey(columnType.getName())) {
+ return true;
+ } else if (ColumnConverter.class.isAssignableFrom(columnType)) {
+ try {
+ ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance();
+ columnType_columnConverter_map.put(columnType.getName(), columnConverter);
+ return true;
+ } catch (Throwable ex) {
+ LogUtil.e(ex.getMessage(), ex);
+ }
+ }
+ return false;
+ }
+
+ private static final ConcurrentHashMap columnType_columnConverter_map;
+
+ static {
+ columnType_columnConverter_map = new ConcurrentHashMap();
+
+ BooleanColumnConverter booleanColumnConverter = new BooleanColumnConverter();
+ columnType_columnConverter_map.put(boolean.class.getName(), booleanColumnConverter);
+ columnType_columnConverter_map.put(Boolean.class.getName(), booleanColumnConverter);
+
+ ByteArrayColumnConverter byteArrayColumnConverter = new ByteArrayColumnConverter();
+ columnType_columnConverter_map.put(byte[].class.getName(), byteArrayColumnConverter);
+
+ ByteColumnConverter byteColumnConverter = new ByteColumnConverter();
+ columnType_columnConverter_map.put(byte.class.getName(), byteColumnConverter);
+ columnType_columnConverter_map.put(Byte.class.getName(), byteColumnConverter);
+
+ CharColumnConverter charColumnConverter = new CharColumnConverter();
+ columnType_columnConverter_map.put(char.class.getName(), charColumnConverter);
+ columnType_columnConverter_map.put(Character.class.getName(), charColumnConverter);
+
+ DateColumnConverter dateColumnConverter = new DateColumnConverter();
+ columnType_columnConverter_map.put(Date.class.getName(), dateColumnConverter);
+
+ DoubleColumnConverter doubleColumnConverter = new DoubleColumnConverter();
+ columnType_columnConverter_map.put(double.class.getName(), doubleColumnConverter);
+ columnType_columnConverter_map.put(Double.class.getName(), doubleColumnConverter);
+
+ FloatColumnConverter floatColumnConverter = new FloatColumnConverter();
+ columnType_columnConverter_map.put(float.class.getName(), floatColumnConverter);
+ columnType_columnConverter_map.put(Float.class.getName(), floatColumnConverter);
+
+ IntegerColumnConverter integerColumnConverter = new IntegerColumnConverter();
+ columnType_columnConverter_map.put(int.class.getName(), integerColumnConverter);
+ columnType_columnConverter_map.put(Integer.class.getName(), integerColumnConverter);
+
+ LongColumnConverter longColumnConverter = new LongColumnConverter();
+ columnType_columnConverter_map.put(long.class.getName(), longColumnConverter);
+ columnType_columnConverter_map.put(Long.class.getName(), longColumnConverter);
+
+ ShortColumnConverter shortColumnConverter = new ShortColumnConverter();
+ columnType_columnConverter_map.put(short.class.getName(), shortColumnConverter);
+ columnType_columnConverter_map.put(Short.class.getName(), shortColumnConverter);
+
+ SqlDateColumnConverter sqlDateColumnConverter = new SqlDateColumnConverter();
+ columnType_columnConverter_map.put(java.sql.Date.class.getName(), sqlDateColumnConverter);
+
+ StringColumnConverter stringColumnConverter = new StringColumnConverter();
+ columnType_columnConverter_map.put(String.class.getName(), stringColumnConverter);
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java b/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java
new file mode 100644
index 0000000..3bc1dfa
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java
@@ -0,0 +1,30 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+import java.util.Date;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class DateColumnConverter implements ColumnConverter {
+ @Override
+ public Date getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : new Date(cursor.getLong(index));
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Date fieldValue) {
+ if (fieldValue == null) return null;
+ return fieldValue.getTime();
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java b/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java
new file mode 100644
index 0000000..13e5434
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class DoubleColumnConverter implements ColumnConverter {
+ @Override
+ public Double getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getDouble(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Double fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.REAL;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java b/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java
new file mode 100644
index 0000000..884c4b8
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class FloatColumnConverter implements ColumnConverter {
+ @Override
+ public Float getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getFloat(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Float fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.REAL;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java b/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java
new file mode 100644
index 0000000..c3d684b
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class IntegerColumnConverter implements ColumnConverter {
+ @Override
+ public Integer getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getInt(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Integer fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java b/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java
new file mode 100644
index 0000000..a4a760e
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class LongColumnConverter implements ColumnConverter {
+ @Override
+ public Long getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getLong(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Long fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java
new file mode 100644
index 0000000..86bf131
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java
@@ -0,0 +1,27 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class ShortColumnConverter implements ColumnConverter {
+ @Override
+ public Short getFieldValue(final Cursor cursor, int index) {
+ return cursor.isNull(index) ? null : cursor.getShort(index);
+ }
+
+ @Override
+ public Object fieldValue2DbValue(Short fieldValue) {
+ return fieldValue;
+ }
+
+ @Override
+ public ColumnDbType getColumnDbType() {
+ return ColumnDbType.INTEGER;
+ }
+}
diff --git a/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java b/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java
new file mode 100644
index 0000000..b000674
--- /dev/null
+++ b/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java
@@ -0,0 +1,28 @@
+package org.xutils.db.converter;
+
+import android.database.Cursor;
+
+import org.xutils.db.sqlite.ColumnDbType;
+
+/**
+ * Author: wyouflf
+ * Date: 13-11-4
+ * Time: 下午10:51
+ */
+public class SqlDateColumnConverter implements ColumnConverter