Skip to content

Instantly share code, notes, and snippets.

Last active November 7, 2018 14:03
Show Gist options
  • Save anonymous/f9425dc69e02fc855cd9 to your computer and use it in GitHub Desktop.
Save anonymous/f9425dc69e02fc855cd9 to your computer and use it in GitHub Desktop.
import com.google.common.base.Splitter;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.ihad.ptt.model.bean.AidBean;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
/**
* 文章編號與檔案名稱轉換工具<br />
* 將符合此格式的 [a-zA-Z-_]{8} 文章編號轉換為檔案名稱 或是 URL<br />
* 範例:<br />
* 1MVYyFDv -> M.1451110159.A.379 -> https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html<br /><br />
* 或是將符合 [M|G].[unsigned_integer].A.[HEX{3}] 格式的檔案名稱轉換為文章編號<br />
* 若為 URL 則將轉換為 {@link AidBean}, 其中包含看板名與文章編號<br />
* 範例:<br />
* https://www.ptt.cc/bbs/Gossiping/M.1451110159.A.379.html -> M.1451110159.A.379 -> 1MVYyFDv
*/
public class AidConverter {
private static final String DOMAIN_URL = "https://www.ptt.cc/bbs/";
private static final String FILE_EXT = ".html";
private static String aidTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
private static BiMap<Character, Long> table = tableInitializer();
/**
* 建立文章編號字元 Map, 方便取得對應數值
* @return
* 文章編號字元 Map
*/
private static BiMap<Character, Long> tableInitializer(){
BiMap<Character, Long> table = HashBiMap.create();
long index = 0;
int size = aidTable.length();
for( int i = 0; i < size; i++ ){
table.forcePut( aidTable.charAt(i), index );
index ++;
}
return table;
}
/**
* 將檔案名稱轉換為˙數字型態的文章序號
* @param fn 檔案名稱
* @return
* 數字型態的文章序號, 若檔案名稱格式不符將回傳 0<br />
* 轉換後的文章編號將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
* 範例: M.1451100858.A.71E
*/
private static long fn2aidu(String fn){
long aidu = 0;
long type = 0;
long v1 = 0;
long v2 = 0;
if( fn == null ) return 0;
List<String> fnList = Splitter.on(".").omitEmptyStrings().trimResults().splitToList(fn);
if( fnList.size() != 4 ) return 0;
String typeString = fnList.get(0);
String v1String = fnList.get(1);
String v2String = fnList.get(3);
if( !fnList.get(2).equals("A") ) return 0;
if( !StringUtils.isNumeric(v1String) || v1String.length() != 10 ) return 0;
switch (typeString){
case "M":
type = 0;
break;
case "G":
type = 1;
break;
default:
return 0;
}
v1 = Long.parseLong( v1String );
v2 = Long.parseLong( v2String, 16 );
aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffffL) << 12) | (v2 & 0xfff);
return aidu;
}
/**
* 將數字型態的文章序號轉換為字串型態的文章編號
* @param aidu 數字型態之文章序號
* @return
* 轉換後的文章編號將符合 [a-zA-Z-_]{8}<br />
* 範例: 1MVWgwSU
*/
private static String aidu2aidc(long aidu){
int size = table.size();
BiMap<Long, Character> inverseTable = table.inverse();
StringBuffer stringBuffer = new StringBuffer();
while( stringBuffer.length() < 8 ){
long v = aidu % size;
if( !inverseTable.containsKey( v ) ) return null;
stringBuffer.insert( 0, inverseTable.get( v ) );
aidu = aidu / size;
}
return stringBuffer.toString();
}
/**
* 將文章編號轉換為數字型態的文章序號
* @param aid 文章編號
* @return
* 數字型態的文章序號
*/
private static long aidc2aidu(String aid){
char[] aidChars = aid.toCharArray();
long aidu = 0;
for( char aidChar : aidChars ){
if( aidChar == '@' ) break;
if( !table.containsKey(aidChar) ) return 0;
long v = table.get(aidChar);
aidu = aidu << 6;
aidu = aidu | (v & 0x3f);
}
return aidu;
}
/**
* 將文章序號(數字型態)轉換為檔案名稱
* @param aidu 文章序號(數字型態)
* @return
* 轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
* 最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
* 範例: M.1451100858.A.71E
*/
private static String aidu2fn(long aidu){
long type = ((aidu >> 44) & 0xf);
long v1 = ((aidu >> 12) & 0xffffffffL);
long v2 = (aidu & 0xfff);
// casting to unsigned
// v1 = v1 & 0xffffffffL;
String hex = Long.toHexString(v2).toUpperCase();
return ((type == 0) ? "M" : "G") + "." + v1 + ".A." + StringUtils.leftPad(hex, 3, "0");
}
/**
* 將文章編號轉換為檔案名稱
* @param aid 文章編號
* @return
* 轉換後的檔案名稱, 格式將符合 [M|G].[unsigned_integer].A.[HEX{3}]<br />
* 最後的16進位表示法若未滿3個字將以0從左邊開始補齊<br />
* 範例: M.1451100858.A.71E
*/
public static String aidToFileName(String aid){
return aidu2fn( aidc2aidu(aid) );
}
/**
* 將文章編號轉換為 WEB 版 URL
* @param boardTitle 文章所屬看板名稱
* @param aid 文章編號
* @return
* WEB 版的完整 URL
*/
public static String aidToUrl(String boardTitle, String aid){
if( boardTitle.isEmpty() || aid.isEmpty() ) return "";
return DOMAIN_URL + boardTitle + "/" + aidToFileName(aid) + FILE_EXT;
}
/**
* 將檔案名稱(也就是 URL 的最後一段 不包含副檔名)轉換為文章編號
* @param fileName 檔案名稱
* @return
* 轉換後的文章編號, 若檔案名稱格式不符則將回傳 null
*/
public static String fileNameToAid(String fileName){
return aidu2aidc( fn2aidu(fileName) );
}
/**
* 將 URL 轉換為 AID 物件
* @param url PTT WEB 版的 URL
* @return
* 物件內包含 文章編號 與 看板名
* @see AidBean
*/
public static AidBean urlToAid(String url){
List<String> urlList = Splitter.on("/").omitEmptyStrings().trimResults().splitToList(url);
if( urlList.size() < 4 ) return null;
String boardTitle = urlList.get(3);
String fileName = urlList.get(4).replaceAll("\\.(html|htm)", "");
String aid = fileNameToAid( fileName );
return AidBean.Builder.anAidBean()
.withBoardTitle( boardTitle )
.withAid( aid )
.build();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment