001package net.gdface.sdk.fse; 002 003import gu.sql2java.BaseRow; 004import gu.sql2java.Managers; 005import gu.sql2java.TableListener; 006import gu.sql2java.TableManager.Action; 007import gu.sql2java.exception.RuntimeDaoException; 008import net.gdface.sdk.fse.CodeBean; 009import net.gdface.sdk.fse.FeatureSe; 010import net.gdface.sdk.fse.FeatureSeDecorator; 011import static net.gdface.utils.SimpleLog.log; 012 013import java.lang.reflect.ParameterizedType; 014import java.util.concurrent.atomic.AtomicInteger; 015 016import static com.google.common.base.Preconditions.*; 017/** 018 * 基于数据库的特征搜索引擎 019 * 依赖库: 020 * <ul> 021 * <li>groupId: com.gitee.l0km</li> 022 * <li>artifactId: sql2java-manager</li> 023 * </ul> 024 * @author guyadong 025 * 026 * @param <F> 保存特征数据的记录 027 */ 028public abstract class BaseFseDbEngine<F extends BaseRow>{ 029 protected final FeatureSeDecorator fse; 030 private final Class<F> rawType; 031 /** 032 * 033 * @param fse {@link FeatureSe}引擎实例 034 */ 035 @SuppressWarnings("unchecked") 036 public BaseFseDbEngine(FeatureSe fse) { 037 this.fse= FeatureSeDecorator.makeDecorator(checkNotNull(fse,"fse is null")); 038 rawType = (Class<F>) ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; 039 } 040 041 /** 042 * 加载数据库中所有特征到FSE内存表 043 */ 044 protected void loaddb(){ 045 log("Loading feature data from Database into memory..."); 046 final AtomicInteger rowCount = new AtomicInteger(0); 047 Managers.managerOf(rawType).loadAll(new Action<F>(){ 048 @Override 049 public void call(F bean) throws RuntimeDaoException { 050 addFeatureBean(bean); 051 showProgress(rowCount.incrementAndGet()); 052 } 053 }); 054 } 055 /** 056 * 搜索引擎初始化 057 * @param dao Dao对象 058 */ 059 public void init(){ 060 loaddb(); 061 Managers.managerOf(rawType).registerListener(featureTableListener); 062 } 063 /** 显示完成进度 */ 064 private void showProgress(int c){ 065 // 显示完成进度 066 if(0==c%100000){ 067 System.out.printf("*"); 068 if(0==(c%1000000)){ 069 if(0==(c%=10000000)){ 070 System.out.println(); 071 }else{ 072 System.out.print(" "); 073 } 074 } 075 } 076 } 077 078 private final TableListener<F> featureTableListener = 079 new TableListener.Adapter<F>(){ 080 @Override 081 public void afterInsert(F bean) throws RuntimeDaoException { 082 addFeatureBean(bean); 083 } 084 @Override 085 public void afterDelete(F bean) throws RuntimeDaoException { 086 fse.removeFeature(featureIdOf(bean)); 087 } 088 }; 089 090 protected void addFeatureBean(F bean){ 091 if(beanFilter(bean)){ 092 byte[] feature = featureOf(bean); 093 if(null != feature){ 094 fse.addFeature( 095 featureIdOf(bean), 096 feature, ownerOf(bean)); 097 } 098 } 099 } 100 101 /** 102 * 从特征记录中返回特征数据的MD5 103 * @param bean 104 * @return MD5校验码(16字节数组) 105 */ 106 protected byte[] featureIdOf(F bean){ 107 return null; 108 } 109 /** 110 * 从特征记录中返回特征所属者ID 111 * @param bean 112 * @return 32字节HEX字符串 113 */ 114 protected String ownerOf(F bean){ 115 return null; 116 } 117 118 /** 119 * 从特征记录中返回特征数组 120 * @param bean 121 * @return 特征数据(二进制数组) 122 */ 123 protected abstract byte[] featureOf(F bean); 124 125 /** 126 * 数据库记录过滤器函数 127 * @param bean 128 * @return 返回{@code true}则加入FSE,否则跳过 129 */ 130 protected boolean beanFilter(F bean) { 131 return bean != null; 132 } 133 /** 134 * @param feature 要比对的特征码 135 * @param similarty 相似度阀值 136 * @param rows 最多返回的记录数目 137 * @return 返回包含相似度(降序)的结果数组,如果没有查到匹配的记录则返回空数组 138 * @see FeatureSe#searchCode(byte[], double, int) 139 */ 140 public CodeBean[] searchFeatures(byte[] feature, double similarty, int rows) { 141 return fse.searchCode(checkNotNull(feature,"feature is null"),similarty,rows); 142 } 143 144 @Override 145 public String toString() { 146 StringBuilder builder = new StringBuilder(); 147 builder.append("BaseFseDbEngine [fseClassName="); 148 builder.append(fse); 149 builder.append("]"); 150 return builder.toString(); 151 } 152}